ID:2209235
 
(See the best response by Super Saiyan X.)
Hi all.
What i'm currently trying to do is write a coating system for food in our game (SS13 server), so people can dip things in batter and deepfry them. its a gloriously unhealthy quest.

For most normal foods, ive got the batter overlay working fine, but i'm having a problem with fruits and vegetables, which are a bit more complex.

First up, here's the code i'm using atm (called from within the food item)
var/icon/I = new /icon(icon, icon_state)
I.Blend(new /icon('icons/obj/food_custom.dmi', rgb(255,255,255)),ICON_ADD)
I.Blend(new /icon('icons/obj/food_custom.dmi', coating.icon_raw),ICON_MULTIPLY)
var/image/J = image(I)
J.alpha = batter_alpha
J.blend_mode = BLEND_OVERLAY
J.tag = "coating"
overlays += J


This takes the icon of the food, blends it with a plain white image to create a mask, and then masks that with the batter image, to get a cutout of batter shaped like the food. this works great mostly.

However, fruits and vegetables in SS13 are generated through a complex hydroponics system that allows for a variety of properties to change depending on genetics. In DM terms, they have no base icon, but are instead constructed from overlays. And even their overlays have overlays!

Ill post and dissect their code below in a moment. First an overview of what i need to do:

My aim here is to develop a means of fetching a complete representation of an object's visuals, ideally in an icon that i can use to blend with other things. This seems an inordinately complex task.

        plant_icon = image('icons/obj/hydroponics_products.dmi',"blank")
var/image/fruit_base = image('icons/obj/hydroponics_products.dmi',"[seed.get_trait(TRAIT_PRODUCT_ICON)]-product")
fruit_base.color = "[seed.get_trait(TRAIT_PRODUCT_COLOUR)]"
plant_icon.overlays |= fruit_base
if("[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf" in icon_states('icons/obj/hydroponics_products.dmi'))
var/image/fruit_leaves = image('icons/obj/hydroponics_products.dmi',"[seed.get_trait(TRAIT_PRODUCT_ICON)]-leaf")
fruit_leaves.color = "[seed.get_trait(TRAIT_PLANT_COLOUR)]"
plant_icon.overlays |= fruit_leaves
plant_controller.plant_icon_cache[icon_key] = plant_icon
overlays |= plant_icon



This is code for a fruit.
It starts with generating an image, plant icon, and then two other images are added to it as overlays.

Creating a double nested overlay which is the entireity of a fruit's visuals.

The way i'm thinking of going at this would be to recurse through images in the overlays list, and overlays attached to those overlays, and try to blend them all down into one.

But i run into a major wall at the first hurdle. icon.blend cannot blend images, only other icons
What do i do about this? How do you blend images together?

There's another theoretical hurdle that i read in the documentation - overlays are converted into some internal format. so i'm wondering, do they even still exist as image objects?

And what about dealing with non-images used as overlays? I know its possible to use iconstates, other objects, icons, and images in the overlays list, and they all just work. how does all that function internally.

Do i have to iterate through the list, typecheck against all possibilities and apply different behaviour to each?

And my most important question. Is this really so hard to do?
Is there some built in/native function or var that i can use to just grab an object's visuals in one go? Does a client really handle all this complexity just to display an icon?
Best response
atom.appearance?
what do i do with that? I can't think of anything an appearance is useful for, except cloning items.


Its not an icon so i can't use it to mask another icon. its really just a big list of vars for easy copying, isnt it? I don't see a way it can be useful here
This is not an answer to your general question, just some background information that might help:
http://www.byond.com/forum/?post=1863944

As for what you want to do, don't use icon blending. You can accomplish everything with regular objects. The general idea is to take the object, make it fully opaque with the alpha component as white using a color matrix (list(0,0,0,0, 0,0,0,0, 0,0,0,0, 1,1,1,0, 0,0,0,1)) to make an alpha mask, and multiply that against the KEEP_TOGETHER texture and you'll have your textured object. Only problem is that if the texture is transparent somewhere, the result will be black on those pixels, so either you have to do a second pass multiplying against a mask of the texture or ensure the texture is fully opaque.
MisterPerson this sounds like a curious solution, can you elaborate more, provide code examples?

I've never heard of a color matrix, nor this keep together thing. More information would be helpful
well, MisterPerson doesn't seem to be replying anymore. No matter, i went off and found another solution.

Turns out we already had a function in our codebase to do this. I'll repost it here for posterity.

It's really big and probably overcomplicated and needlessly expensive, but i dont have the time to try to optimise it. i'm just caching its output to avoid running more than once per object. it works pretty well

/*
Get flat icon by DarkCampainger. As it says on the tin, will return an icon with all the overlays
as a single icon. Useful for when you want to manipulate an icon via the above as overlays are not normally included.
The _flatIcons list is a cache for generated icon files.
*/


proc // Creates a single icon from a given /atom or /image. Only the first argument is required.
getFlatIcon(image/A, defdir=2, deficon=null, defstate="", defblend=BLEND_DEFAULT, always_use_defdir = 0)
// We start with a blank canvas, otherwise some icon procs crash silently
var/icon/flat = icon('icons/effects/effects.dmi', "icon_state"="nothing") // Final flattened icon
if(!A)
return flat
if(A.alpha <= 0)
return flat
var/noIcon = FALSE

var/curicon
if(A.icon)
curicon = A.icon
else
curicon = deficon

if(!curicon)
noIcon = TRUE // Do not render this object.

var/curstate
if(A.icon_state)
curstate = A.icon_state
else
curstate = defstate

if(!noIcon && !(curstate in icon_states(curicon)))
if("" in icon_states(curicon))
curstate = ""
else
noIcon = TRUE // Do not render this object.

var/curdir
if(A.dir != 2 && !always_use_defdir)
curdir = A.dir
else
curdir = defdir

var/curblend
if(A.blend_mode == BLEND_DEFAULT)
curblend = defblend
else
curblend = A.blend_mode

// Layers will be a sorted list of icons/overlays, based on the order in which they are displayed
var/list/layers = list()
var/image/copy
// Add the atom's icon itself, without pixel_x/y offsets.
if(!noIcon)
copy = image(icon=curicon, icon_state=curstate, layer=A.layer, dir=curdir)
copy.color = A.color
copy.alpha = A.alpha
copy.blend_mode = curblend
layers[copy] = A.layer

// Loop through the underlays, then overlays, sorting them into the layers list
var/list/process = A.underlays // Current list being processed
var/pSet=0 // Which list is being processed: 0 = underlays, 1 = overlays
var/curIndex=1 // index of 'current' in list being processed
var/current // Current overlay being sorted
var/currentLayer // Calculated layer that overlay appears on (special case for FLOAT_LAYER)
var/compare // The overlay 'add' is being compared against
var/cmpIndex // The index in the layers list of 'compare'
while(TRUE)
if(curIndex<=process.len)
current = process[curIndex]
if(current)
currentLayer = current:layer
if(currentLayer<0) // Special case for FLY_LAYER
if(currentLayer <= -1000) return flat
if(pSet == 0) // Underlay
currentLayer = A.layer+currentLayer/1000
else // Overlay
currentLayer = A.layer+(1000+currentLayer)/1000

// Sort add into layers list
for(cmpIndex=1,cmpIndex<=layers.len,cmpIndex++)
compare = layers[cmpIndex]
if(currentLayer < layers[compare]) // Associated value is the calculated layer
layers.Insert(cmpIndex,current)
layers[current] = currentLayer
break
if(cmpIndex>layers.len) // Reached end of list without inserting
layers[current]=currentLayer // Place at end

curIndex++
else if(pSet == 0) // Switch to overlays
curIndex = 1
pSet = 1
process = A.overlays
else // All done
break

var/icon/add // Icon of overlay being added

// Current dimensions of flattened icon
var/{flatX1=1;flatX2=flat.Width();flatY1=1;flatY2=flat.Height()}
// Dimensions of overlay being added
var/{addX1;addX2;addY1;addY2}

for(var/I in layers)

if(I:alpha == 0)
continue

if(I == copy) // 'I' is an /image based on the object being flattened.
curblend = BLEND_OVERLAY
add = icon(I:icon, I:icon_state, I:dir)
// This checks for a silent failure mode of the icon routine. If the requested dir
// doesn't exist in this icon state it returns a 32x32 icon with 0 alpha.
if (I:dir != SOUTH && add.Width() == 32 && add.Height() == 32)
// Check every pixel for blank (computationally expensive, but the process is limited
// by the amount of film on the station, only happens when we hit something that's
// turned, and bails at the very first pixel it sees.
var/blankpixel;
for(var/y;y<=32;y++)
for(var/x;x<32;x++)
blankpixel = isnull(add.GetPixel(x,y))
if(!blankpixel)
break
if(!blankpixel)
break
// If we ALWAYS returned a null (which happens when GetPixel encounters something with alpha 0)
if (blankpixel)
// Pull the default direction.
add = icon(I:icon, I:icon_state)
else // 'I' is an appearance object.
if(istype(A,/obj/machinery/atmospherics) && I in A.underlays)
var/image/Im = I
add = getFlatIcon(new/image(I), Im.dir, curicon, curstate, curblend, 1)
else
add = getFlatIcon(new/image(I), curdir, curicon, curstate, curblend, always_use_defdir)

// Find the new dimensions of the flat icon to fit the added overlay
addX1 = min(flatX1, I:pixel_x+1)
addX2 = max(flatX2, I:pixel_x+add.Width())
addY1 = min(flatY1, I:pixel_y+1)
addY2 = max(flatY2, I:pixel_y+add.Height())

if(addX1!=flatX1 || addX2!=flatX2 || addY1!=flatY1 || addY2!=flatY2)
// Resize the flattened icon so the new icon fits
flat.Crop(addX1-flatX1+1, addY1-flatY1+1, addX2-flatX1+1, addY2-flatY1+1)
flatX1=addX1;flatX2=addX2
flatY1=addY1;flatY2=addY2
var/iconmode
if(I in A.overlays)
iconmode = ICON_OVERLAY
else if(I in A.underlays)
iconmode = ICON_UNDERLAY
else
iconmode = blendMode2iconMode(curblend)
// Blend the overlay into the flattened icon
flat.Blend(add, iconmode, I:pixel_x + 2 - flatX1, I:pixel_y + 2 - flatY1)

if(A.color)
flat.Blend(A.color, ICON_MULTIPLY)
if(A.alpha < 255)
flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY)

return icon(flat, "", SOUTH)