Skip to content

Commit

Permalink
Getflaticon optimisation (#724)
Browse files Browse the repository at this point in the history
* 60285

* 77940

* Update icons.dm

* Update icons.dm

* Update unsorted.dm
  • Loading branch information
Helg2 authored Dec 2, 2024
1 parent a8a515e commit 3316ee9
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 155 deletions.
282 changes: 136 additions & 146 deletions code/__HELPERS/icons.dm
Original file line number Diff line number Diff line change
Expand Up @@ -683,208 +683,191 @@ ColorTone(rgb, tone)
return BlendRGB(tone, "#ffffff", (gray - tone_gray) / ((255 - tone_gray) || 1))


/// Creates a single icon from a given /atom or /image. Only the first argument is required.
/proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = FALSE)
//Define... defines.
/// Create a single [/icon] from a given [/atom] or [/image].
///
/// Very low-performance. Should usually only be used for HTML, where BYOND's
/// appearance system (overlays/underlays, etc.) is not available.
///
/// Only the first argument is required.
/proc/getFlatIcon(image/appearance, defdir, deficon, defstate, defblend, start = TRUE, no_anim = FALSE)
// Loop through the underlays, then overlays, sorting them into the layers list
#define PROCESS_OVERLAYS_OR_UNDERLAYS(flat, process, base_layer) \
for(var/i in 1 to process.len) { \
var/image/current = process[i]; \
if (!current) { \
continue; \
} \
if (current.plane != FLOAT_PLANE && current.plane != appearance.plane) { \
continue; \
} \
var/current_layer = current.layer; \
if (current_layer < 0) { \
if (current_layer <= -1000) { \
return flat; \
} \
current_layer = base_layer + appearance.layer + current_layer / 1000; \
} \
for (var/index_to_compare_to in 1 to layers.len) { \
var/compare_to = layers[index_to_compare_to]; \
if (current_layer < layers[compare_to]) { \
layers.Insert(index_to_compare_to, current); \
break; \
} \
} \
layers[current] = current_layer; \
}

var/static/icon/flat_template = icon('icons/effects/effects.dmi', "nothing")
var/icon/flat = icon(flat_template)

if(!appearance || appearance.alpha <= 0)
return flat

#define BLANK icon(flat_template)
#define SET_SELF(SETVAR) do { \
var/icon/SELF_ICON = icon(icon(curicon, curstate, base_icon_dir), "", SOUTH, no_anim ? 1 : null); \
if(A.alpha < 255) { \
SELF_ICON.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY);\
} \
if(A.color) { \
if(islist(A.color)){ \
SELF_ICON.MapColors(arglist(A.color))} \
else{ \
SELF_ICON.Blend(A.color, ICON_MULTIPLY)} \
} \
##SETVAR=SELF_ICON;\
} while (0)
#define INDEX_X_LOW 1
#define INDEX_X_HIGH 2
#define INDEX_Y_LOW 3
#define INDEX_Y_HIGH 4

#define flatX1 flat_size[INDEX_X_LOW]
#define flatX2 flat_size[INDEX_X_HIGH]
#define flatY1 flat_size[INDEX_Y_LOW]
#define flatY2 flat_size[INDEX_Y_HIGH]
#define addX1 add_size[INDEX_X_LOW]
#define addX2 add_size[INDEX_X_HIGH]
#define addY1 add_size[INDEX_Y_LOW]
#define addY2 add_size[INDEX_Y_HIGH]

if(!A || A.alpha <= 0)
return BLANK

var/noIcon = FALSE
if(start)
if(!defdir)
defdir = A.dir
defdir = appearance.dir
if(!deficon)
deficon = A.icon
deficon = appearance.icon
if(!defstate)
defstate = A.icon_state
defstate = appearance.icon_state
if(!defblend)
defblend = A.blend_mode
defblend = appearance.blend_mode

var/curicon = A.icon || deficon
var/curstate = A.icon_state || defstate
var/curicon = appearance.icon || deficon
var/curstate = appearance.icon_state || defstate
var/curdir = (!appearance.dir || appearance.dir == SOUTH) ? defdir : appearance.dir

if(!((noIcon = (!curicon))))
var/render_icon = curicon

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

var/curdir
var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have

//These should use the parent's direction (most likely)
if(!A.dir || A.dir == SOUTH)
curdir = defdir
else
curdir = A.dir

//Try to remove/optimize this section ASAP, CPU hog.
//Determines if there's directionals.
if(!noIcon && curdir != SOUTH)
var/exist = FALSE
var/static/list/checkdirs = list(NORTH, EAST, WEST)
for(var/i in checkdirs) //Not using GLOB for a reason.
if(length(icon_states(icon(curicon, curstate, i))))
exist = TRUE
break
if(!exist)
base_icon_dir = SOUTH
//
if(render_icon)
//Try to remove/optimize this section if you can, it's a CPU hog.
//Determines if there're directionals.
if(curdir != SOUTH)
// icon states either have 1, 4 or 8 dirs. We only have to check
// one of NORTH, EAST or WEST to know that this isn't a 1-dir icon_state since they just have SOUTH.
if(!length(icon_states(icon(curicon, curstate, NORTH))))
base_icon_dir = SOUTH

var/list/icon_dimensions = get_icon_dimensions(curicon)
var/icon_width = icon_dimensions["width"]
var/icon_height = icon_dimensions["height"]
if(icon_width != 32 || icon_height != 32)
flat.Scale(icon_width, icon_height)

if(!base_icon_dir)
base_icon_dir = curdir

ASSERT(!BLEND_DEFAULT) //I might just be stupid but lets make sure this define is 0.

var/curblend = A.blend_mode || defblend
var/curblend = appearance.blend_mode || defblend

if(length(A.overlays) || length(A.underlays))
var/icon/flat = BLANK
if(length(appearance.overlays) || length(appearance.underlays))
// 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 = base_icon_dir)
copy.color = A.color
copy.alpha = A.alpha
if(render_icon)
copy = image(icon = curicon, icon_state = curstate, layer = appearance.layer, dir = base_icon_dir)
copy.color = appearance.color
copy.alpha = appearance.alpha
copy.blend_mode = curblend
layers[copy] = A.layer

// Loop through the underlays, then overlays, sorting them into the layers list
for(var/process_set in 0 to 1)
var/list/process = process_set ? A.overlays : A.underlays
for(var/i in 1 to length(process))
var/image/current = process[i]
if(!current)
continue
if(current.plane != FLOAT_PLANE && current.plane != A.plane)
continue
var/current_layer = current.layer
if(current_layer < 0)
if(current_layer <= -1000)
return flat
current_layer = process_set + A.layer + current_layer * 0.001

for(var/p in 1 to length(layers))
var/image/cmp = layers[p]
if(current_layer < layers[cmp])
layers.Insert(p, current)
break
layers[current] = current_layer

//sortTim(layers, GLOBAL_PROC_REF(cmp_image_layer_asc))
layers[copy] = appearance.layer

PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.underlays, 0)
PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.overlays, 1)

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

// Current dimensions of flattened icon
var/list/flat_size = list(1, flat.Width(), 1, flat.Height())
// Dimensions of overlay being added
var/list/add_size[4]
var/flatX1 = 1
var/flatX2 = flat.Width()
var/flatY1 = 1
var/flatY2 = flat.Height()

var/addX1 = 0
var/addX2 = 0
var/addY1 = 0
var/addY2 = 0

for(var/V in layers)
var/image/I = V
if(I.alpha == 0)
for(var/image/layer_image as anything in layers)
if(layer_image.alpha == 0)
continue

if(I == copy) // 'I' is an /image based on the object being flattened.
if(layer_image ) // 'layer_image ' is an /image based on the object being flattened.
curblend = BLEND_OVERLAY
add = icon(I.icon, I.icon_state, base_icon_dir)
add = icon(layer_image.icon, layer_image.icon_state, base_icon_dir)
else // 'I' is an appearance object.
add = getFlatIcon(image(I), curdir, curicon, curstate, curblend, FALSE, no_anim)
add = getFlatIcon(image(layer_image ), curdir, curicon, curstate, curblend, FALSE, no_anim)
if(!add)
continue

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

if(
addX1 != flatX1 \
&& addX2 != flatX2 \
&& addY1 != flatY1 \
&& addY2 != flatY2 \
)

if(flat_size ~! add_size)
// Resize the flattened icon so the new icon fits
flat.Crop(
addX1 - flatX1 + 1,
addY1 - flatY1 + 1,
addX2 - flatX1 + 1,
addY2 - flatY1 + 1
addX1 - flatX1 + 1,
addY1 - flatY1 + 1,
addX2 - flatX1 + 1,
addY2 - flatY1 + 1
)
flat_size = add_size.Copy()
flatX1 = addX1
flatX2 = addY1
flatY1 = addX2
flatY2 = addY2

// Blend the overlay into the flattened icon
flat.Blend(add, blendMode2iconMode(curblend), I.pixel_x + 2 - flatX1, I.pixel_y + 2 - flatY1)
flat.Blend(add, blendMode2iconMode(curblend), layer_image.pixel_x + 2 - flatX1, layer_image.pixel_y + 2 - flatY1)

if(A.color)
if(islist(A.color))
flat.MapColors(arglist(A.color))
if(appearance.color)
if(islist(appearance.color))
flat.MapColors(arglist(appearance.color))
else
flat.Blend(A.color, ICON_MULTIPLY)
flat.Blend(appearance.color, ICON_MULTIPLY)

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

if(no_anim)
//Clean up repeated frames
var/icon/cleaned = new /icon()
cleaned.Insert(flat, "", SOUTH, 1, 0)
. = cleaned
return cleaned
else
. = icon(flat, "", SOUTH)
else //There's no overlays.
if(!noIcon)
SET_SELF(.)

//Clear defines
#undef flatX1
#undef flatX2
#undef flatY1
#undef flatY2
#undef addX1
#undef addX2
#undef addY1
#undef addY2

#undef INDEX_X_LOW
#undef INDEX_X_HIGH
#undef INDEX_Y_LOW
#undef INDEX_Y_HIGH

#undef BLANK
#undef SET_SELF
return icon(flat, "", SOUTH)
else if (render_icon) // There's no overlays.
var/icon/final_icon = icon(icon(curicon, curstate, base_icon_dir), "", SOUTH, no_anim ? TRUE : null)

if (appearance.alpha < 255)
final_icon.Blend(rgb(255,255,255, appearance.alpha), ICON_MULTIPLY)

if (appearance.color)
if (islist(appearance.color))
final_icon.MapColors(arglist(appearance.color))
else
final_icon.Blend(appearance.color, ICON_MULTIPLY)

return final_icon

#undef PROCESS_OVERLAYS_OR_UNDERLAYS

/proc/getHologramIcon(icon/A, safety = TRUE)//If safety is on, a new icon is not created.
var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon.
Expand Down Expand Up @@ -1276,3 +1259,10 @@ GLOBAL_LIST_EMPTY(transformation_animation_objects)
image_to_center.pixel_y = y_offset

return image_to_center

/// Returns a list containing the width and height of an icon file
/proc/get_icon_dimensions(icon_path)
if (isnull(GLOB.icon_dimensions[icon_path]))
var/icon/my_icon = icon(icon_path)
GLOB.icon_dimensions[icon_path] = list("width" = my_icon.Width(), "height" = my_icon.Height())
return GLOB.icon_dimensions[icon_path]
6 changes: 3 additions & 3 deletions code/__HELPERS/unsorted.dm
Original file line number Diff line number Diff line change
Expand Up @@ -1077,9 +1077,9 @@ will handle it, but:
var/pixel_y_offset = AM.pixel_y + M.get_y_shift()

//Irregular objects
var/icon/AMicon = icon(AM.icon, AM.icon_state)
var/AMiconheight = AMicon.Height()
var/AMiconwidth = AMicon.Width()
var/list/icon_dimensions = get_icon_dimensions(AM.icon)
var/AMiconheight = icon_dimensions["height"]
var/AMiconwidth = icon_dimensions["width"]
if(AMiconheight != world.icon_size || AMiconwidth != world.icon_size)
pixel_x_offset += ((AMiconwidth/world.icon_size)-1)*(world.icon_size*0.5)
pixel_y_offset += ((AMiconheight/world.icon_size)-1)*(world.icon_size*0.5)
Expand Down
2 changes: 2 additions & 0 deletions code/_globalvars/lists/icons.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// Cache of the width and height of icon files, to avoid repeating the same expensive operation
GLOBAL_LIST_EMPTY(icon_dimensions)
5 changes: 2 additions & 3 deletions code/modules/mob/dead/observer/observer.dm
Original file line number Diff line number Diff line change
Expand Up @@ -654,9 +654,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER)
if(!istype(target))
return

var/icon/I = icon(target.icon, target.icon_state, target.dir)

var/orbitsize = (I.Width() + I.Height()) * 0.5
var/list/icon_dimensions = get_icon_dimensions(target.icon)
var/orbitsize = (icon_dimensions["width"] + icon_dimensions["height"]) * 0.5
orbitsize -= (orbitsize / world.icon_size) * (world.icon_size * 0.25)

var/rot_seg
Expand Down
6 changes: 3 additions & 3 deletions code/modules/mob/mob_helpers.dm
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,9 @@ GLOBAL_LIST_INIT(organ_rel_size, list(
A.target = source
if(!alert_overlay)
alert_overlay = new(source)
var/icon/I = icon(source.icon)
var/iheight = I.Height()
var/iwidth = I.Width()
var/list/icon_dimensions = get_icon_dimensions(source.icon)
var/iheight = icon_dimensions["width"]
var/iwidth = icon_dimensions["height"]
var/higher_power = (iheight > iwidth) ? iheight : iwidth
if(higher_power > 32)
var/diff = 32 / higher_power
Expand Down
1 change: 1 addition & 0 deletions tgmc.dme
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
#include "code\_globalvars\lists\client.dm"
#include "code\_globalvars\lists\flavor_misc.dm"
#include "code\_globalvars\lists\hud.dm"
#include "code\_globalvars\lists\icons.dm"
#include "code\_globalvars\lists\keybinding.dm"
#include "code\_globalvars\lists\mapping.dm"
#include "code\_globalvars\lists\mobs.dm"
Expand Down

0 comments on commit 3316ee9

Please sign in to comment.