ID:2828819
 
(See the best response by Lummox JR.)
Problem description:
I have a frying pan item that can have ingredient items put into it, and it scales each ingredient down and renders it in the pan. To avoid the ingredients overlapping the obverse rim of the pan, we overlay a copy of that rim. This goes over the blood overlay, so we then have to re-add the blood overlay, only over the new front rim.
However there are a few issue with this. One is that items that are big enough to appear pixel-south of the pan lid appear under it. I want to use ICON_ADD to mask this, which would also obviate the need for redrawing the front rim and second blood overlay, however, I'm just not quite there in terms of understanding how to do this, between the concepts of icons, images, and mutable appearances. Below the current code is my attempted refactor. It "almost" works except the scaled icon is in the bottom right corner, and Shift() doesn't seem to work. Do I need to re-Blend() the new 16x16 icon onto a blank 32x32 (world.icon_size) icon again and *then* Shift()? Also I don't know if it's efficient to create new temporary icons every time (are they stored on disk or something?), and feel like the mutable appearance thing might be better.. but I don't know how to mask those.

Also, ideally I would like to take a snapshot of each ingredient that includes any overlays it has on it, if such a thing is feasible. For example if there was a blood-stained knife in the pan I'd want it to appear bloody in the pan as well.

If anyone could please tell me the ideal way to do this, I would be greatly appreciative.

Code:
/obj/item/weapon/reagent_containers/pan/update_icon()

overlays.len = 0

if(blood_overlay)
overlays += blood_overlay

//ingredients:
if(contents.len)
var/matrix/M = matrix()
M.Scale(0.5, 0.5)
for(var/atom/content in contents)
var/mutable_appearance/mini_ingredient = image("icon"=content)
mini_ingredient.transform = M
mini_ingredient.pixel_x = 0
mini_ingredient.pixel_y = 0
mini_ingredient.layer = FLOAT_LAYER
mini_ingredient.plane = FLOAT_PLANE
overlays += mini_ingredient

//put a front over the ingredients where they're occluded from view by the side of the pan
var/image/pan_front = image('icons/obj/pan.dmi', src, "pan_front")
overlays += pan_front
//put blood back onto the pan front
if(blood_overlay)

var/icon/I = new /icon('icons/obj/pan.dmi', "pan_front")
I.Blend(new /icon('icons/effects/blood.dmi', rgb(255,255,255)),ICON_ADD) //fills the icon_state with white (except where it's transparent)
I.Blend(new /icon('icons/effects/blood.dmi', "itemblood"),ICON_MULTIPLY) //adds blood and the remaining white areas become transparant

var/image/frontblood = image(I)
frontblood.color = blood_color

overlays += frontblood


Attempted refactor:
/obj/item/weapon/reagent_containers/pan/update_icon()

overlays.Cut()

if(blood_overlay)
overlays += blood_overlay

//non-reagent ingredients:
if(contents.len)
//start with blank icon
var/icon/pan_contents = new /icon('icons/obj/pan.dmi', "pan_blank")
for(var/atom/content in contents)
//overlay each ingredient
var/icon/thisingredient = new /icon(content.icon, content.icon_state)
pan_contents.Blend(thisingredient, ICON_OVERLAY)
message_admins(world.icon_size)
//half-size
var/sizeval = world.icon_size / 2
pan_contents.Scale(sizeval, sizeval)
pan_contents.Shift(NORTHEAST, sizeval)
//mask it with pan_mask
pan_contents.Blend(new /icon('icons/obj/pan.dmi', "pan_mask"), ICON_ADD)
overlays += pan_contents
Best response
I suggest avoiding icon math or overlays entirely, and instead use visual contents.

obj/item/weapon/reagent_containers/pan/update_icon()
var/obj/vc_container
if(visual_contents.len > contents.len)
visual_contents.len = contents.len
while(visual_contents.len < contents.len)
// use an intermediary container object so it can be given a unique transform
// use the golden angle to arrange things; start at 45 degrees
var/angle = 137.51 * visual_contents.len + 45
vc_container = new
vc_container.layer = FLOAT_LAYER
vc_container.plane = FLOAT_PLANE
vc_container.vis_flags = VIS_INHERIT_ID // items in pan won't be clickable
vc_container.transform = matrix(0.5, 0.5, MATRIX_SCALE) *\
matrix(0, 12, MATRIX_TRANSLATE) *\
matrix(angle, MATRIX_ROTATE)
vis_contents += vc_container
var/idx = 0
for(var/obj/O in src)
// each ingredient should also have VIS_INHERIT_LAYER and VIS_INHERIT_PLANE flags
// just set them now
O.vis_flags |= VIS_INHERIT_LAYER | VIS_INHERIT_PLANE
vis_contents[++idx].vis_contents = O
Thank you for replying. I didn't know about this concept and it seems interesting, but one issue is that I need to apply a mask so that pixels of the ingredient sprite don't appear in certain areas (below the "opening" of the pan). The sprite is at an oblique angle so any ingredient pixel below a certain boundary has to be made transparent (so it looks like it's being covered by the wall of the pan). Like if you were looking at an opaque jar from the side with a ruler in it, you'd only see the top half of the ruler sticking out.
If you are using vis_contents or overlays, a new feature you can take advantage of is BLEND_INSET_OVERLAY. You need the masking area to have KEEP_TOGETHER set, and the objects that should blend against that area should have BLEND_INSET_OVERLAY set and be children of the masking area.
Interesting. Can I use that to only show the area of pan_contents where the mask is opaque, but not show the mask itself? I guess I'm asking the same thing as this person:
http://www.byond.com/forum/post/2732292

Is there an example of what you're describing somewhere that I can look at?
In that case, look up the alpha mask filter in the reference. You can invert the alpha mask, and only mask the spaces on the bottom side of the pan against the KT's contents.

DM me a test environment with nothing but the frying pan icon and the contents of the frying pan positioned/scaled into place on the screen, and I can implement your masking setup and share it back with you.
I'm the same user on a different account (this account wasn't allowed to post for some reason). Apologies for the very late response. Thank you. I'm not exactly sure what you mean by a test environment, how would I go about extracting that from the current game code?
.