From b3d9164862bb05bf7c02b88b1f71a8f3f8f40ff9 Mon Sep 17 00:00:00 2001 From: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com> Date: Mon, 17 Jul 2023 14:59:20 +0100 Subject: [PATCH] Reworks RGB procs (#21474) * Reworks RGB procs * Oops --- code/__HELPERS/colour_helpers.dm | 47 + code/__HELPERS/icon_helpers.dm | 239 +++++ code/__HELPERS/iconprocs.dm | 301 ++++++ code/__HELPERS/icons.dm | 984 ------------------ code/game/objects/items/shooting_range.dm | 2 +- code/game/turfs/turf.dm | 2 +- .../mob/dead/observer/observer_base.dm | 2 +- .../living/carbon/human/human_update_icons.dm | 4 +- code/modules/mob/living/silicon/ai/ai_mob.dm | 17 + code/modules/paperwork/photography.dm | 27 + paradise.dme | 4 +- 11 files changed, 639 insertions(+), 990 deletions(-) create mode 100644 code/__HELPERS/colour_helpers.dm create mode 100644 code/__HELPERS/icon_helpers.dm create mode 100644 code/__HELPERS/iconprocs.dm delete mode 100644 code/__HELPERS/icons.dm diff --git a/code/__HELPERS/colour_helpers.dm b/code/__HELPERS/colour_helpers.dm new file mode 100644 index 00000000000..b9853409725 --- /dev/null +++ b/code/__HELPERS/colour_helpers.dm @@ -0,0 +1,47 @@ +/proc/BlendRGB(rgb1, rgb2, amount) + var/list/RGB1 = rgb2num(rgb1) + var/list/RGB2 = rgb2num(rgb2) + + // add missing alpha if needed + if(RGB1.len < RGB2.len) RGB1 += 255 + else if(RGB2.len < RGB1.len) RGB2 += 255 + var/usealpha = RGB1.len > 3 + + var/r = round(RGB1[1] + (RGB2[1] - RGB1[1]) * amount, 1) + var/g = round(RGB1[2] + (RGB2[2] - RGB1[2]) * amount, 1) + var/b = round(RGB1[3] + (RGB2[3] - RGB1[3]) * amount, 1) + var/alpha = usealpha ? round(RGB1[4] + (RGB2[4] - RGB1[4]) * amount, 1) : null + + return isnull(alpha) ? rgb(r, g, b) : rgb(r, g, b, alpha) + +/proc/rand_hex_color() + var/list/colours = list("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f") + var/list/output_colour = list() + + for(var/i = 0; i < 6; i++) + output_colour += pick(colours) + + return "#[output_colour.Join("")]" + + +// Change grayscale color to black->tone->white range +/proc/ColorTone(rgb, tone) + var/list/RGB = rgb2num(rgb) + var/list/TONE = rgb2num(tone) + + var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 + var/tone_gray = TONE[1]*0.3 + TONE[2]*0.59 + TONE[3]*0.11 + + if(gray <= tone_gray) return BlendRGB("#000000", tone, gray/(tone_gray || 1)) + else return BlendRGB(tone, "#ffffff", (gray-tone_gray)/((255-tone_gray) || 1)) + + +/proc/adjust_brightness(color, value) + if(!color) return "#FFFFFF" + if(!value) return color + + var/list/RGB = rgb2num(color) + RGB[1] = clamp(RGB[1]+value,0,255) + RGB[2] = clamp(RGB[2]+value,0,255) + RGB[3] = clamp(RGB[3]+value,0,255) + return rgb(RGB[1],RGB[2],RGB[3]) diff --git a/code/__HELPERS/icon_helpers.dm b/code/__HELPERS/icon_helpers.dm new file mode 100644 index 00000000000..a64f4e5c7e9 --- /dev/null +++ b/code/__HELPERS/icon_helpers.dm @@ -0,0 +1,239 @@ +// Creates a single icon from a given /atom or /image. Only the first argument is required. +// This proc is utterly HATEFUL +/proc/getFlatIcon(image/A, defdir, deficon, defstate, defblend, start = TRUE, no_anim = FALSE) + //Define... defines. + var/static/icon/flat_template = icon('icons/effects/effects.dmi', "nothing") + + #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 + if(!deficon) + deficon = A.icon + if(!defstate) + defstate = A.icon_state + if(!defblend) + defblend = A.blend_mode + + var/curicon = A.icon || deficon + var/curstate = A.icon_state || defstate + + if(!((noIcon = (!curicon)))) + var/curstates = icon_states(curicon) + if(!(curstate in curstates)) + if("" in curstates) + curstate = "" + else + noIcon = TRUE // Do not render this object. + + 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(!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 + + if(A.overlays.len || A.underlays.len) + var/icon/flat = BLANK + // 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 + 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 process.len) + 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 / 1000 + + for(var/p in 1 to layers.len) + var/image/cmp = layers[p] + if(current_layer < layers[cmp]) + layers.Insert(p, current) + break + layers[current] = current_layer + + 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] + + for(var/V in layers) + var/image/I = V + 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, base_icon_dir) + else // 'I' is an appearance object. + add = getFlatIcon(image(I), 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()) + ) + + 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 + ) + flat_size = add_size.Copy() + + // Blend the overlay into the flattened icon + flat.Blend(add, blendMode2iconMode(curblend), I.pixel_x + 2 - flatX1, I.pixel_y + 2 - flatY1) + + if(A.color) + if(islist(A.color)) + flat.MapColors(arglist(A.color)) + else + flat.Blend(A.color, ICON_MULTIPLY) + + if(A.alpha < 255) + flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) + + if(no_anim) + //Clean up repeated frames + var/icon/cleaned = new /icon() + cleaned.Insert(flat, "", SOUTH, 1, 0) + . = 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 + + +//Imagine removing pixels from the main icon that are covered by pixels from the mask icon. +//Standard behaviour is to cut pixels from the main icon that are covered by pixels from the mask icon unless passed mask_ready, see below. +/proc/get_icon_difference(icon/main, icon/mask, mask_ready) + /*You should skip prep if the mask is already sprited properly. This significantly improves performance by eliminating most of the realtime icon work. + e.g. A 'ready' mask is a mask where the part you want cut out is missing (no pixels, 0 alpha) from the sprite, and everything else is solid white.*/ + + if(istype(main) && istype(mask)) + if(!mask_ready) //Prep the mask if we're using a regular old sprite and not a special-made mask. + mask.Blend(rgb(255,255,255), ICON_SUBTRACT) //Make all pixels on the mask as black as possible. + mask.Opaque(rgb(255,255,255)) //Make the transparent pixels (background) white. + mask.BecomeAlphaMask() //Make all the black pixels vanish (fully transparent), leaving only the white background pixels. + + main.AddAlphaMask(mask) //Make the pixels in the main icon that are in the transparent zone of the mask icon also vanish (fully transparent). + return main + +///Checks if the given iconstate exists in the given file, caching the result. Setting no_sprite to TRUE will print a stack trace ONCE. +/proc/icon_exists(file, state, no_sprite) + var/static/list/icon_states_cache = list() + if(icon_states_cache[file]?[state]) + return TRUE + + if(icon_states_cache[file]?[state] == FALSE) + return FALSE + + var/list/states = icon_states(file) + + if(!icon_states_cache[file]) + icon_states_cache[file] = list() + + if(state in states) + icon_states_cache[file][state] = TRUE + return TRUE + else + icon_states_cache[file][state] = FALSE + if(no_sprite) + stack_trace("Icon Lookup for state: [state] in file [file] failed.") + return FALSE diff --git a/code/__HELPERS/iconprocs.dm b/code/__HELPERS/iconprocs.dm new file mode 100644 index 00000000000..eb86df49e86 --- /dev/null +++ b/code/__HELPERS/iconprocs.dm @@ -0,0 +1,301 @@ +/* +IconProcs README + +A BYOND library for manipulating icons and colors + +by Lummox JR + +version 1.0 + +============================================================ + + AA EDIT - Removed a bunch of procs we have natively now + +============================================================ + +The IconProcs library was made to make a lot of common icon operations much easier. BYOND's icon manipulation +routines are very capable but some of the advanced capabilities like using alpha transparency can be unintuitive to beginners. + +CHANGING ICONS + +Several new procs have been added to the /icon datum to simplify working with icons. To use them, +remember you first need to setup an /icon var like so: + + var/icon/my_icon = new('iconfile.dmi') + +icon/ChangeOpacity(amount = 1) + A very common operation in DM is to try to make an icon more or less transparent. Making an icon more + transparent is usually much easier than making it less so, however. This proc basically is a frontend + for MapColors() which can change opacity any way you like, in much the same way that SetIntensity() + can make an icon lighter or darker. If amount is 0.5, the opacity of the icon will be cut in half. + If amount is 2, opacity is doubled and anything more than half-opaque will become fully opaque. +icon/GrayScale() + Converts the icon to grayscale instead of a fully colored icon. Alpha values are left intact. +icon/ColorTone(tone) + Similar to GrayScale(), this proc converts the icon to a range of black -> tone -> white, where tone is an + RGB color (its alpha is ignored). This can be used to create a sepia tone or similar effect. + See also the global ColorTone() proc. +icon/MinColors(icon) + The icon is blended with a second icon where the minimum of each RGB pixel is the result. + Transparency may increase, as if the icons were blended with ICON_ADD. You may supply a color in place of an icon. +icon/MaxColors(icon) + The icon is blended with a second icon where the maximum of each RGB pixel is the result. + Opacity may increase, as if the icons were blended with ICON_OR. You may supply a color in place of an icon. +icon/Opaque(background = "#000000") + All alpha values are set to 255 throughout the icon. Transparent pixels become black, or whatever background color you specify. +icon/BecomeAlphaMask() + You can convert a simple grayscale icon into an alpha mask to use with other icons very easily with this proc. + The black parts become transparent, the white parts stay white, and anything in between becomes a translucent shade of white. +icon/AddAlphaMask(mask) + The alpha values of the mask icon will be blended with the current icon. Anywhere the mask is opaque, + the current icon is untouched. Anywhere the mask is transparent, the current icon becomes transparent. + Where the mask is translucent, the current icon becomes more transparent. +icon/UseAlphaMask(mask, mode) + Sometimes you may want to take the alpha values from one icon and use them on a different icon. + This proc will do that. Just supply the icon whose alpha mask you want to use, and src will change + so it has the same colors as before but uses the mask for opacity. + +COLOR MANAGEMENT AND HSV + +RGB isn't the only way to represent color. Sometimes it's more useful to work with a model called HSV, which stands for hue, saturation, and value. + + * The hue of a color describes where it is along the color wheel. It goes from red to yellow to green to + cyan to blue to magenta and back to red. + * The saturation of a color is how much color is in it. A color with low saturation will be more gray, + and with no saturation at all it is a shade of gray. + * The value of a color determines how bright it is. A high-value color is vivid, moderate value is dark, + and no value at all is black. + +Just as BYOND uses "#rrggbb" to represent RGB values, a similar format is used for HSV: "#hhhssvv". The hue is three +hex digits because it ranges from 0 to 0x5FF. + + * 0 to 0xFF - red to yellow + * 0x100 to 0x1FF - yellow to green + * 0x200 to 0x2FF - green to cyan + * 0x300 to 0x3FF - cyan to blue + * 0x400 to 0x4FF - blue to magenta + * 0x500 to 0x5FF - magenta to red + +Knowing this, you can figure out that red is "#000ffff" in HSV format, which is hue 0 (red), saturation 255 (as colorful as possible), +value 255 (as bright as possible). Green is "#200ffff" and blue is "#400ffff". + +More than one HSV color can match the same RGB color. + +Here are some procs you can use for color management: + +ReadRGB(rgb) + Takes an RGB string like "#ffaa55" and converts it to a list such as list(255,170,85). If an RGBA format is used + that includes alpha, the list will have a fourth item for the alpha value. +hsv(hue, sat, val, apha) + Counterpart to rgb(), this takes the values you input and converts them to a string in "#hhhssvv" or "#hhhssvvaa" + format. Alpha is not included in the result if null. +ReadHSV(rgb) + Takes an HSV string like "#100FF80" and converts it to a list such as list(256,255,128). If an HSVA format is used that + includes alpha, the list will have a fourth item for the alpha value. +RGBtoHSV(rgb) + Takes an RGB or RGBA string like "#ffaa55" and converts it into an HSV or HSVA color such as "#080aaff". +HSVtoRGB(hsv) + Takes an HSV or HSVA string like "#080aaff" and converts it into an RGB or RGBA color such as "#ff55aa". +BlendRGB(rgb1, rgb2, amount) + Blends between two RGB or RGBA colors using regular RGB blending. If amount is 0, the first color is the result; + if 1, the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. + The returned value is an RGB or RGBA color. +BlendHSV(hsv1, hsv2, amount) + Blends between two HSV or HSVA colors using HSV blending, which tends to produce nicer results than regular RGB + blending because the brightness of the color is left intact. If amount is 0, the first color is the result; if 1, + the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. + The returned value is an HSV or HSVA color. +BlendRGBasHSV(rgb1, rgb2, amount) + Like BlendHSV(), but the colors used and the return value are RGB or RGBA colors. The blending is done in HSV form. +HueToAngle(hue) + Converts a hue to an angle range of 0 to 360. Angle 0 is red, 120 is green, and 240 is blue. +AngleToHue(hue) + Converts an angle to a hue in the valid range. +RotateHue(hsv, angle) + Takes an HSV or HSVA value and rotates the hue forward through red, green, and blue by an angle from 0 to 360. + (Rotating red by 60° produces yellow.) The result is another HSV or HSVA color with the same saturation and value + as the original, but a different hue. +GrayScale(rgb) + Takes an RGB or RGBA color and converts it to grayscale. Returns an RGB or RGBA string. +ColorTone(rgb, tone) + Similar to GrayScale(), this proc converts an RGB or RGBA color to a range of black -> tone -> white instead of + using strict shades of gray. The tone value is an RGB color; any alpha value is ignored. +*/ + +/* +Get Flat Icon DEMO by DarkCampainger + +This is a test for the get flat icon proc, modified approprietly for icons and their states. +Probably not a good idea to run this unless you want to see how the proc works in detail. +mob + icon = 'old_or_unused.dmi' + icon_state = "green" + + Login() + // Testing image underlays + underlays += image(icon='old_or_unused.dmi',icon_state="red") + underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = 32) + underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = -32) + + // Testing image overlays + overlays += image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = -32) + overlays += image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = 32) + overlays += image(icon='old_or_unused.dmi',icon_state="green", pixel_x = -32, pixel_y = -32) + + // Testing icon file overlays (defaults to mob's state) + overlays += '_flat_demoIcons2.dmi' + + // Testing icon_state overlays (defaults to mob's icon) + overlays += "white" + + // Testing dynamic icon overlays + var/icon/I = icon('old_or_unused.dmi', icon_state="aqua") + I.Shift(NORTH,16,1) + overlays+=I + + // Testing dynamic image overlays + I=image(icon=I,pixel_x = -32, pixel_y = 32) + overlays+=I + + // Testing object types (and layers) + overlays+=/obj/effect/overlayTest + + loc = locate (10,10,1) + verb + Browse_Icon() + set name = "1. Browse Icon" + // Give it a name for the cache + var/iconName = "[ckey(src.name)]_flattened.dmi" + // Send the icon to src's local cache + src<

") + + Output_Icon() + set name = "2. Output Icon" + to_chat(src, "Icon is: [bicon(getFlatIcon(src))]") + + Label_Icon() + set name = "3. Label Icon" + // Give it a name for the cache + var/iconName = "[ckey(src.name)]_flattened.dmi" + // Copy the file to the rsc manually + var/icon/I = fcopy_rsc(getFlatIcon(src)) + // Send the icon to src's local cache + src< transparent, gray -> translucent white, white -> solid white +/icon/proc/BecomeAlphaMask() + SwapColor(null, "#000000ff") // don't let transparent become gray + MapColors(0,0,0,0.3, 0,0,0,0.59, 0,0,0,0.11, 0,0,0,0, 1,1,1,0) + +/icon/proc/UseAlphaMask(mask) + Opaque() + AddAlphaMask(mask) + +/icon/proc/AddAlphaMask(mask) + var/icon/M = new(mask) + M.Blend("#ffffff", ICON_SUBTRACT) + // apply mask + Blend(M, ICON_ADD) diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm deleted file mode 100644 index 46049e777c6..00000000000 --- a/code/__HELPERS/icons.dm +++ /dev/null @@ -1,984 +0,0 @@ -/* -IconProcs README - -A BYOND library for manipulating icons and colors - -by Lummox JR - -version 1.0 - -The IconProcs library was made to make a lot of common icon operations much easier. BYOND's icon manipulation -routines are very capable but some of the advanced capabilities like using alpha transparency can be unintuitive to beginners. - -CHANGING ICONS - -Several new procs have been added to the /icon datum to simplify working with icons. To use them, -remember you first need to setup an /icon var like so: - - var/icon/my_icon = new('iconfile.dmi') - -icon/ChangeOpacity(amount = 1) - A very common operation in DM is to try to make an icon more or less transparent. Making an icon more - transparent is usually much easier than making it less so, however. This proc basically is a frontend - for MapColors() which can change opacity any way you like, in much the same way that SetIntensity() - can make an icon lighter or darker. If amount is 0.5, the opacity of the icon will be cut in half. - If amount is 2, opacity is doubled and anything more than half-opaque will become fully opaque. -icon/GrayScale() - Converts the icon to grayscale instead of a fully colored icon. Alpha values are left intact. -icon/ColorTone(tone) - Similar to GrayScale(), this proc converts the icon to a range of black -> tone -> white, where tone is an - RGB color (its alpha is ignored). This can be used to create a sepia tone or similar effect. - See also the global ColorTone() proc. -icon/MinColors(icon) - The icon is blended with a second icon where the minimum of each RGB pixel is the result. - Transparency may increase, as if the icons were blended with ICON_ADD. You may supply a color in place of an icon. -icon/MaxColors(icon) - The icon is blended with a second icon where the maximum of each RGB pixel is the result. - Opacity may increase, as if the icons were blended with ICON_OR. You may supply a color in place of an icon. -icon/Opaque(background = "#000000") - All alpha values are set to 255 throughout the icon. Transparent pixels become black, or whatever background color you specify. -icon/BecomeAlphaMask() - You can convert a simple grayscale icon into an alpha mask to use with other icons very easily with this proc. - The black parts become transparent, the white parts stay white, and anything in between becomes a translucent shade of white. -icon/AddAlphaMask(mask) - The alpha values of the mask icon will be blended with the current icon. Anywhere the mask is opaque, - the current icon is untouched. Anywhere the mask is transparent, the current icon becomes transparent. - Where the mask is translucent, the current icon becomes more transparent. -icon/UseAlphaMask(mask, mode) - Sometimes you may want to take the alpha values from one icon and use them on a different icon. - This proc will do that. Just supply the icon whose alpha mask you want to use, and src will change - so it has the same colors as before but uses the mask for opacity. - -COLOR MANAGEMENT AND HSV - -RGB isn't the only way to represent color. Sometimes it's more useful to work with a model called HSV, which stands for hue, saturation, and value. - - * The hue of a color describes where it is along the color wheel. It goes from red to yellow to green to - cyan to blue to magenta and back to red. - * The saturation of a color is how much color is in it. A color with low saturation will be more gray, - and with no saturation at all it is a shade of gray. - * The value of a color determines how bright it is. A high-value color is vivid, moderate value is dark, - and no value at all is black. - -Just as BYOND uses "#rrggbb" to represent RGB values, a similar format is used for HSV: "#hhhssvv". The hue is three -hex digits because it ranges from 0 to 0x5FF. - - * 0 to 0xFF - red to yellow - * 0x100 to 0x1FF - yellow to green - * 0x200 to 0x2FF - green to cyan - * 0x300 to 0x3FF - cyan to blue - * 0x400 to 0x4FF - blue to magenta - * 0x500 to 0x5FF - magenta to red - -Knowing this, you can figure out that red is "#000ffff" in HSV format, which is hue 0 (red), saturation 255 (as colorful as possible), -value 255 (as bright as possible). Green is "#200ffff" and blue is "#400ffff". - -More than one HSV color can match the same RGB color. - -Here are some procs you can use for color management: - -ReadRGB(rgb) - Takes an RGB string like "#ffaa55" and converts it to a list such as list(255,170,85). If an RGBA format is used - that includes alpha, the list will have a fourth item for the alpha value. -hsv(hue, sat, val, apha) - Counterpart to rgb(), this takes the values you input and converts them to a string in "#hhhssvv" or "#hhhssvvaa" - format. Alpha is not included in the result if null. -ReadHSV(rgb) - Takes an HSV string like "#100FF80" and converts it to a list such as list(256,255,128). If an HSVA format is used that - includes alpha, the list will have a fourth item for the alpha value. -RGBtoHSV(rgb) - Takes an RGB or RGBA string like "#ffaa55" and converts it into an HSV or HSVA color such as "#080aaff". -HSVtoRGB(hsv) - Takes an HSV or HSVA string like "#080aaff" and converts it into an RGB or RGBA color such as "#ff55aa". -BlendRGB(rgb1, rgb2, amount) - Blends between two RGB or RGBA colors using regular RGB blending. If amount is 0, the first color is the result; - if 1, the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. - The returned value is an RGB or RGBA color. -BlendHSV(hsv1, hsv2, amount) - Blends between two HSV or HSVA colors using HSV blending, which tends to produce nicer results than regular RGB - blending because the brightness of the color is left intact. If amount is 0, the first color is the result; if 1, - the second color is the result. 0.5 produces an average of the two. Values outside the 0 to 1 range are allowed as well. - The returned value is an HSV or HSVA color. -BlendRGBasHSV(rgb1, rgb2, amount) - Like BlendHSV(), but the colors used and the return value are RGB or RGBA colors. The blending is done in HSV form. -HueToAngle(hue) - Converts a hue to an angle range of 0 to 360. Angle 0 is red, 120 is green, and 240 is blue. -AngleToHue(hue) - Converts an angle to a hue in the valid range. -RotateHue(hsv, angle) - Takes an HSV or HSVA value and rotates the hue forward through red, green, and blue by an angle from 0 to 360. - (Rotating red by 60° produces yellow.) The result is another HSV or HSVA color with the same saturation and value - as the original, but a different hue. -GrayScale(rgb) - Takes an RGB or RGBA color and converts it to grayscale. Returns an RGB or RGBA string. -ColorTone(rgb, tone) - Similar to GrayScale(), this proc converts an RGB or RGBA color to a range of black -> tone -> white instead of - using strict shades of gray. The tone value is an RGB color; any alpha value is ignored. -*/ - -/* -Get Flat Icon DEMO by DarkCampainger - -This is a test for the get flat icon proc, modified approprietly for icons and their states. -Probably not a good idea to run this unless you want to see how the proc works in detail. -mob - icon = 'old_or_unused.dmi' - icon_state = "green" - - Login() - // Testing image underlays - underlays += image(icon='old_or_unused.dmi',icon_state="red") - underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = 32) - underlays += image(icon='old_or_unused.dmi',icon_state="red", pixel_x = -32) - - // Testing image overlays - overlays += image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = -32) - overlays += image(icon='old_or_unused.dmi',icon_state="green", pixel_x = 32, pixel_y = 32) - overlays += image(icon='old_or_unused.dmi',icon_state="green", pixel_x = -32, pixel_y = -32) - - // Testing icon file overlays (defaults to mob's state) - overlays += '_flat_demoIcons2.dmi' - - // Testing icon_state overlays (defaults to mob's icon) - overlays += "white" - - // Testing dynamic icon overlays - var/icon/I = icon('old_or_unused.dmi', icon_state="aqua") - I.Shift(NORTH,16,1) - overlays+=I - - // Testing dynamic image overlays - I=image(icon=I,pixel_x = -32, pixel_y = 32) - overlays+=I - - // Testing object types (and layers) - overlays+=/obj/effect/overlayTest - - loc = locate (10,10,1) - verb - Browse_Icon() - set name = "1. Browse Icon" - // Give it a name for the cache - var/iconName = "[ckey(src.name)]_flattened.dmi" - // Send the icon to src's local cache - src<

") - - Output_Icon() - set name = "2. Output Icon" - to_chat(src, "Icon is: [bicon(getFlatIcon(src))]") - - Label_Icon() - set name = "3. Label Icon" - // Give it a name for the cache - var/iconName = "[ckey(src.name)]_flattened.dmi" - // Copy the file to the rsc manually - var/icon/I = fcopy_rsc(getFlatIcon(src)) - // Send the icon to src's local cache - src< transparent, gray -> translucent white, white -> solid white -/icon/proc/BecomeAlphaMask() - SwapColor(null, "#000000ff") // don't let transparent become gray - MapColors(0,0,0,0.3, 0,0,0,0.59, 0,0,0,0.11, 0,0,0,0, 1,1,1,0) - -/icon/proc/UseAlphaMask(mask) - Opaque() - AddAlphaMask(mask) - -/icon/proc/AddAlphaMask(mask) - var/icon/M = new(mask) - M.Blend("#ffffff", ICON_SUBTRACT) - // apply mask - Blend(M, ICON_ADD) - -/* - HSV format is represented as "#hhhssvv" or "#hhhssvvaa" - - Hue ranges from 0 to 0x5ff (1535) - - 0x000 = red - 0x100 = yellow - 0x200 = green - 0x300 = cyan - 0x400 = blue - 0x500 = magenta - - Saturation is from 0 to 0xff (255) - - More saturation = more color - Less saturation = more gray - - Value ranges from 0 to 0xff (255) - - Higher value means brighter color - */ - -/proc/ReadRGB(rgb) - if(!rgb) return - - // interpret the HSV or HSVA value - var/i=1,start=1 - if(text2ascii(rgb) == 35) ++start // skip opening # - var/ch,which=0,r=0,g=0,b=0,alpha=0,usealpha - var/digits=0 - for(i=start, i<=length(rgb), ++i) - ch = text2ascii(rgb, i) - if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) break - ++digits - if(digits == 8) break - - var/single = digits < 6 - if(digits != 3 && digits != 4 && digits != 6 && digits != 8) return - if(digits == 4 || digits == 8) usealpha = 1 - for(i=start, digits>0, ++i) - ch = text2ascii(rgb, i) - if(ch >= 48 && ch <= 57) ch -= 48 - else if(ch >= 65 && ch <= 70) ch -= 55 - else if(ch >= 97 && ch <= 102) ch -= 87 - else break - --digits - switch(which) - if(0) - r = (r << 4) | ch - if(single) - r |= r << 4 - ++which - else if(!(digits & 1)) ++which - if(1) - g = (g << 4) | ch - if(single) - g |= g << 4 - ++which - else if(!(digits & 1)) ++which - if(2) - b = (b << 4) | ch - if(single) - b |= b << 4 - ++which - else if(!(digits & 1)) ++which - if(3) - alpha = (alpha << 4) | ch - if(single) alpha |= alpha << 4 - - . = list(r, g, b) - if(usealpha) . += alpha - -/proc/ReadHSV(hsv) - if(!hsv) return - - // interpret the HSV or HSVA value - var/i=1,start=1 - if(text2ascii(hsv) == 35) ++start // skip opening # - var/ch,which=0,hue=0,sat=0,val=0,alpha=0,usealpha - var/digits=0 - for(i=start, i<=length(hsv), ++i) - ch = text2ascii(hsv, i) - if(ch < 48 || (ch > 57 && ch < 65) || (ch > 70 && ch < 97) || ch > 102) break - ++digits - if(digits == 9) break - if(digits > 7) usealpha = 1 - if(digits <= 4) ++which - if(digits <= 2) ++which - for(i=start, digits>0, ++i) - ch = text2ascii(hsv, i) - if(ch >= 48 && ch <= 57) ch -= 48 - else if(ch >= 65 && ch <= 70) ch -= 55 - else if(ch >= 97 && ch <= 102) ch -= 87 - else break - --digits - switch(which) - if(0) - hue = (hue << 4) | ch - if(digits == (usealpha ? 6 : 4)) ++which - if(1) - sat = (sat << 4) | ch - if(digits == (usealpha ? 4 : 2)) ++which - if(2) - val = (val << 4) | ch - if(digits == (usealpha ? 2 : 0)) ++which - if(3) - alpha = (alpha << 4) | ch - - . = list(hue, sat, val) - if(usealpha) . += alpha - -/proc/HSVtoRGB(hsv) - if(!hsv) return "#000000" - var/list/HSV = ReadHSV(hsv) - if(!HSV) return "#000000" - - var/hue = HSV[1] - var/sat = HSV[2] - var/val = HSV[3] - - // Compress hue into easier-to-manage range - hue -= hue >> 8 - if(hue >= 0x5fa) hue -= 0x5fa - - var/hi,mid,lo,r,g,b - hi = val - lo = round((255 - sat) * val / 255, 1) - mid = lo + round(abs(round(hue, 510) - hue) * (hi - lo) / 255, 1) - if(hue >= 765) - if(hue >= 1275) {r=hi; g=lo; b=mid} - else if(hue >= 1020) {r=mid; g=lo; b=hi } - else {r=lo; g=mid; b=hi } - else - if(hue >= 510) {r=lo; g=hi; b=mid} - else if(hue >= 255) {r=mid; g=hi; b=lo } - else {r=hi; g=mid; b=lo } - - return (HSV.len > 3) ? rgb(r,g,b,HSV[4]) : rgb(r,g,b) - -/proc/RGBtoHSV(rgb) - if(!rgb) return "#0000000" - var/list/RGB = ReadRGB(rgb) - if(!RGB) return "#0000000" - - var/r = RGB[1] - var/g = RGB[2] - var/b = RGB[3] - var/hi = max(r,g,b) - var/lo = min(r,g,b) - - var/val = hi - var/sat = hi ? round((hi-lo) * 255 / hi, 1) : 0 - var/hue = 0 - - if(sat) - var/dir - var/mid - if(hi == r) - if(lo == b) {hue=0; dir=1; mid=g} - else {hue=1535; dir=-1; mid=b} - else if(hi == g) - if(lo == r) {hue=512; dir=1; mid=b} - else {hue=511; dir=-1; mid=r} - else if(hi == b) - if(lo == g) {hue=1024; dir=1; mid=r} - else {hue=1023; dir=-1; mid=g} - hue += dir * round((mid-lo) * 255 / (hi-lo), 1) - - return hsv(hue, sat, val, (RGB.len>3 ? RGB[4] : null)) - -/proc/hsv(hue, sat, val, alpha) - if(hue < 0 || hue >= 1536) hue %= 1536 - if(hue < 0) hue += 1536 - if((hue & 0xFF) == 0xFF) - ++hue - if(hue >= 1536) hue = 0 - if(sat < 0) sat = 0 - if(sat > 255) sat = 255 - if(val < 0) val = 0 - if(val > 255) val = 255 - . = "#" - . += TO_HEX_DIGIT(hue >> 8) - . += TO_HEX_DIGIT(hue >> 4) - . += TO_HEX_DIGIT(hue) - . += TO_HEX_DIGIT(sat >> 4) - . += TO_HEX_DIGIT(sat) - . += TO_HEX_DIGIT(val >> 4) - . += TO_HEX_DIGIT(val) - if(!isnull(alpha)) - if(alpha < 0) alpha = 0 - if(alpha > 255) alpha = 255 - . += TO_HEX_DIGIT(alpha >> 4) - . += TO_HEX_DIGIT(alpha) - -/* - Smooth blend between HSV colors - - amount=0 is the first color - amount=1 is the second color - amount=0.5 is directly between the two colors - - amount<0 or amount>1 are allowed - */ -/proc/BlendHSV(hsv1, hsv2, amount) - var/list/HSV1 = ReadHSV(hsv1) - var/list/HSV2 = ReadHSV(hsv2) - - // add missing alpha if needed - if(HSV1.len < HSV2.len) HSV1 += 255 - else if(HSV2.len < HSV1.len) HSV2 += 255 - var/usealpha = HSV1.len > 3 - - // normalize hsv values in case anything is screwy - if(HSV1[1] > 1536) HSV1[1] %= 1536 - if(HSV2[1] > 1536) HSV2[1] %= 1536 - if(HSV1[1] < 0) HSV1[1] += 1536 - if(HSV2[1] < 0) HSV2[1] += 1536 - if(!HSV1[3]) {HSV1[1] = 0; HSV1[2] = 0} - if(!HSV2[3]) {HSV2[1] = 0; HSV2[2] = 0} - - // no value for one color means don't change saturation - if(!HSV1[3]) HSV1[2] = HSV2[2] - if(!HSV2[3]) HSV2[2] = HSV1[2] - // no saturation for one color means don't change hues - if(!HSV1[2]) HSV1[1] = HSV2[1] - if(!HSV2[2]) HSV2[1] = HSV1[1] - - // Compress hues into easier-to-manage range - HSV1[1] -= HSV1[1] >> 8 - HSV2[1] -= HSV2[1] >> 8 - - var/hue_diff = HSV2[1] - HSV1[1] - if(hue_diff > 765) hue_diff -= 1530 - else if(hue_diff <= -765) hue_diff += 1530 - - var/hue = round(HSV1[1] + hue_diff * amount, 1) - var/sat = round(HSV1[2] + (HSV2[2] - HSV1[2]) * amount, 1) - var/val = round(HSV1[3] + (HSV2[3] - HSV1[3]) * amount, 1) - var/alpha = usealpha ? round(HSV1[4] + (HSV2[4] - HSV1[4]) * amount, 1) : null - - // normalize hue - if(hue < 0 || hue >= 1530) hue %= 1530 - if(hue < 0) hue += 1530 - // decompress hue - hue += round(hue / 255) - - return hsv(hue, sat, val, alpha) - -/* - Smooth blend between RGB colors - - amount=0 is the first color - amount=1 is the second color - amount=0.5 is directly between the two colors - - amount<0 or amount>1 are allowed - */ -/proc/BlendRGB(rgb1, rgb2, amount) - var/list/RGB1 = ReadRGB(rgb1) - var/list/RGB2 = ReadRGB(rgb2) - - // add missing alpha if needed - if(RGB1.len < RGB2.len) RGB1 += 255 - else if(RGB2.len < RGB1.len) RGB2 += 255 - var/usealpha = RGB1.len > 3 - - var/r = round(RGB1[1] + (RGB2[1] - RGB1[1]) * amount, 1) - var/g = round(RGB1[2] + (RGB2[2] - RGB1[2]) * amount, 1) - var/b = round(RGB1[3] + (RGB2[3] - RGB1[3]) * amount, 1) - var/alpha = usealpha ? round(RGB1[4] + (RGB2[4] - RGB1[4]) * amount, 1) : null - - return isnull(alpha) ? rgb(r, g, b) : rgb(r, g, b, alpha) - -/proc/BlendRGBasHSV(rgb1, rgb2, amount) - return HSVtoRGB(RGBtoHSV(rgb1), RGBtoHSV(rgb2), amount) - -/proc/HueToAngle(hue) - // normalize hsv in case anything is screwy - if(hue < 0 || hue >= 1536) hue %= 1536 - if(hue < 0) hue += 1536 - // Compress hue into easier-to-manage range - hue -= hue >> 8 - return hue / (1530/360) - -/proc/AngleToHue(angle) - // normalize hsv in case anything is screwy - if(angle < 0 || angle >= 360) angle -= 360 * round(angle / 360) - var/hue = angle * (1530/360) - // Decompress hue - hue += round(hue / 255) - return hue - - -// positive angle rotates forward through red->green->blue -/proc/RotateHue(hsv, angle) - var/list/HSV = ReadHSV(hsv) - - // normalize hsv in case anything is screwy - if(HSV[1] >= 1536) HSV[1] %= 1536 - if(HSV[1] < 0) HSV[1] += 1536 - - // Compress hue into easier-to-manage range - HSV[1] -= HSV[1] >> 8 - - if(angle < 0 || angle >= 360) angle -= 360 * round(angle / 360) - HSV[1] = round(HSV[1] + angle * (1530/360), 1) - - // normalize hue - if(HSV[1] < 0 || HSV[1] >= 1530) HSV[1] %= 1530 - if(HSV[1] < 0) HSV[1] += 1530 - // decompress hue - HSV[1] += round(HSV[1] / 255) - - return hsv(HSV[1], HSV[2], HSV[3], (HSV.len > 3 ? HSV[4] : null)) - -// Convert an rgb color to grayscale -/proc/GrayScale(rgb) - var/list/RGB = ReadRGB(rgb) - var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 - return (RGB.len > 3) ? rgb(gray, gray, gray, RGB[4]) : rgb(gray, gray, gray) - -// Change grayscale color to black->tone->white range -/proc/ColorTone(rgb, tone) - var/list/RGB = ReadRGB(rgb) - var/list/TONE = ReadRGB(tone) - - var/gray = RGB[1]*0.3 + RGB[2]*0.59 + RGB[3]*0.11 - var/tone_gray = TONE[1]*0.3 + TONE[2]*0.59 + TONE[3]*0.11 - - if(gray <= tone_gray) return BlendRGB("#000000", tone, gray/(tone_gray || 1)) - else return BlendRGB(tone, "#ffffff", (gray-tone_gray)/((255-tone_gray) || 1)) - - -/* -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. -*/ - -// 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. - var/static/icon/flat_template = icon('icons/effects/effects.dmi', "nothing") - - #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 - if(!deficon) - deficon = A.icon - if(!defstate) - defstate = A.icon_state - if(!defblend) - defblend = A.blend_mode - - var/curicon = A.icon || deficon - var/curstate = A.icon_state || defstate - - if(!((noIcon = (!curicon)))) - var/curstates = icon_states(curicon) - if(!(curstate in curstates)) - if("" in curstates) - curstate = "" - else - noIcon = TRUE // Do not render this object. - - 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(!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 - - if(A.overlays.len || A.underlays.len) - var/icon/flat = BLANK - // 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 - 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 process.len) - 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 / 1000 - - for(var/p in 1 to layers.len) - var/image/cmp = layers[p] - if(current_layer < layers[cmp]) - layers.Insert(p, current) - break - layers[current] = current_layer - - 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] - - for(var/V in layers) - var/image/I = V - 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, base_icon_dir) - else // 'I' is an appearance object. - add = getFlatIcon(image(I), 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()) - ) - - 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 - ) - flat_size = add_size.Copy() - - // Blend the overlay into the flattened icon - flat.Blend(add, blendMode2iconMode(curblend), I.pixel_x + 2 - flatX1, I.pixel_y + 2 - flatY1) - - if(A.color) - if(islist(A.color)) - flat.MapColors(arglist(A.color)) - else - flat.Blend(A.color, ICON_MULTIPLY) - - if(A.alpha < 255) - flat.Blend(rgb(255, 255, 255, A.alpha), ICON_MULTIPLY) - - if(no_anim) - //Clean up repeated frames - var/icon/cleaned = new /icon() - cleaned.Insert(flat, "", SOUTH, 1, 0) - . = 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 - -/proc/getIconMask(atom/A)//By yours truly. Creates a dynamic mask for a mob/whatever. /N - var/icon/alpha_mask = new(A.icon,A.icon_state)//So we want the default icon and icon state of A. - for(var/V in A.overlays)//For every image in overlays. var/image/I will not work, don't try it. - var/image/I = V - if(I.layer>A.layer) - continue//If layer is greater than what we need, skip it. - var/icon/image_overlay = new(I.icon,I.icon_state)//Blend only works with icon objects. - //Also, icons cannot directly set icon_state. Slower than changing variables but whatever. - alpha_mask.Blend(image_overlay,ICON_OR)//OR so they are lumped together in a nice overlay. - return alpha_mask//And now return the mask. - -/mob/proc/AddCamoOverlay(atom/A)//A is the atom which we are using as the overlay. - var/icon/opacity_icon = new(A.icon, A.icon_state)//Don't really care for overlays/underlays. - //Now we need to culculate overlays+underlays and add them together to form an image for a mask. - //var/icon/alpha_mask = getFlatIcon(src)//Accurate but SLOW. Not designed for running each tick. Could have other uses I guess. - var/icon/alpha_mask = getIconMask(src)//Which is why I created that proc. Also a little slow since it's blending a bunch of icons together but good enough. - opacity_icon.AddAlphaMask(alpha_mask)//Likely the main source of lag for this proc. Probably not designed to run each tick. - opacity_icon.ChangeOpacity(0.4)//Front end for MapColors so it's fast. 0.5 means half opacity and looks the best in my opinion. - for(var/i=0,i<5,i++)//And now we add it as overlays. It's faster than creating an icon and then merging it. - var/image/I = image("icon" = opacity_icon, "icon_state" = A.icon_state, "layer" = layer+0.8)//So it's above other stuff but below weapons and the like. - switch(i)//Now to determine offset so the result is somewhat blurred. - if(1) I.pixel_x-- - if(2) I.pixel_x++ - if(3) I.pixel_y-- - if(4) I.pixel_y++ - overlays += I//And finally add the overlay. - -/proc/getHologramIcon(icon/A, safety=1)//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. - var/icon/alpha_mask - flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish. - flat_icon.ChangeOpacity(0.5)//Make it half transparent. - if(A.Height() == 64) - alpha_mask = new('icons/mob/ancient_machine.dmi', "scanline2")//Scaline for tall icons. - else - alpha_mask = new('icons/effects/effects.dmi', "scanline")//Scanline effect. - flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect. - return flat_icon - -//For photo camera. -/proc/build_composite_icon(atom/A) - var/icon/composite = icon(A.icon, A.icon_state, A.dir, 1) - for(var/O in A.overlays) - var/image/I = O - composite.Blend(icon(I.icon, I.icon_state, I.dir, 1), ICON_OVERLAY) - return composite - -/proc/adjust_brightness(color, value) - if(!color) return "#FFFFFF" - if(!value) return color - - var/list/RGB = ReadRGB(color) - RGB[1] = clamp(RGB[1]+value,0,255) - RGB[2] = clamp(RGB[2]+value,0,255) - RGB[3] = clamp(RGB[3]+value,0,255) - return rgb(RGB[1],RGB[2],RGB[3]) - -/proc/sort_atoms_by_layer(list/atoms) - // Comb sort icons based on levels - var/list/result = atoms.Copy() - var/gap = result.len - var/swapped = 1 - while(gap > 1 || swapped) - swapped = 0 - if(gap > 1) - gap = round(gap / 1.3) // 1.3 is the emperic comb sort coefficient - if(gap < 1) - gap = 1 - for(var/i = 1; gap + i <= result.len; i++) - var/atom/l = result[i] //Fucking hate - var/atom/r = result[gap+i] //how lists work here - if(l.layer > r.layer) //no "result[i].layer" for me - result.Swap(i, gap + i) - swapped = 1 - return result - -//Interface for using DrawBox() to draw 1 pixel on a coordinate. -//Returns the same icon specifed in the argument, but with the pixel drawn -/proc/DrawPixel(icon/I, colour, drawX, drawY) - if(!I) - return 0 - var/Iwidth = I.Width() - var/Iheight = I.Height() - if(drawX > Iwidth || drawX <= 0) - return 0 - if(drawY > Iheight || drawY <= 0) - return 0 - I.DrawBox(colour,drawX, drawY) - return I - -//Interface for easy drawing of one pixel on an atom. -/atom/proc/DrawPixelOn(colour, drawX, drawY) - var/icon/I = new(icon) - var/icon/J = DrawPixel(I, colour, drawX, drawY) - if(J) //Only set the icon if it succeeded, the icon without the pixel is 1000x better than a black square. - icon = J - return J - return 0 - -//Hook, override to run code on- wait this is images -//Images have dir without being an atom, so they get their own definition. -//Lame. -/image/proc/setDir(newdir) - dir = newdir - -/proc/rand_hex_color() - var/list/colors = list("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f") - var/color="" - for(var/i=0;i<6;i++) - color = color+pick(colors) - return "#[color]" - -//Imagine removing pixels from the main icon that are covered by pixels from the mask icon. -//Standard behaviour is to cut pixels from the main icon that are covered by pixels from the mask icon unless passed mask_ready, see below. -/proc/get_icon_difference(icon/main, icon/mask, mask_ready) - /*You should skip prep if the mask is already sprited properly. This significantly improves performance by eliminating most of the realtime icon work. - e.g. A 'ready' mask is a mask where the part you want cut out is missing (no pixels, 0 alpha) from the sprite, and everything else is solid white.*/ - - if(istype(main) && istype(mask)) - if(!mask_ready) //Prep the mask if we're using a regular old sprite and not a special-made mask. - mask.Blend(rgb(255,255,255), ICON_SUBTRACT) //Make all pixels on the mask as black as possible. - mask.Opaque(rgb(255,255,255)) //Make the transparent pixels (background) white. - mask.BecomeAlphaMask() //Make all the black pixels vanish (fully transparent), leaving only the white background pixels. - - main.AddAlphaMask(mask) //Make the pixels in the main icon that are in the transparent zone of the mask icon also vanish (fully transparent). - return main - -///Checks if the given iconstate exists in the given file, caching the result. Setting no_sprite to TRUE will print a stack trace ONCE. -/proc/icon_exists(file, state, no_sprite) - var/static/list/icon_states_cache = list() - if(icon_states_cache[file]?[state]) - return TRUE - - if(icon_states_cache[file]?[state] == FALSE) - return FALSE - - var/list/states = icon_states(file) - - if(!icon_states_cache[file]) - icon_states_cache[file] = list() - - if(state in states) - icon_states_cache[file][state] = TRUE - return TRUE - else - icon_states_cache[file][state] = FALSE - if(no_sprite) - stack_trace("Icon Lookup for state: [state] in file [file] failed.") - return FALSE diff --git a/code/game/objects/items/shooting_range.dm b/code/game/objects/items/shooting_range.dm index 29bb7d10c77..267ec6fec6a 100644 --- a/code/game/objects/items/shooting_range.dm +++ b/code/game/objects/items/shooting_range.dm @@ -98,7 +98,7 @@ bullet_hole.pixel_y = p_y - 1 if(decaltype == DECALTYPE_SCORCH) if(P.damage >= 20 || istype(P, /obj/item/projectile/beam/practice)) - bullet_hole.setDir(pick(NORTH,SOUTH,EAST,WEST))// random scorch design. light_scorch does not have different directions + bullet_hole.dir = pick(NORTH,SOUTH,EAST,WEST) // random scorch design. light_scorch does not have different directions else bullet_hole.icon_state = "light_scorch" else diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index e1a4acb1c4a..e10badc2057 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -508,7 +508,7 @@ I.appearance = AM.appearance I.appearance_flags = RESET_COLOR|RESET_ALPHA|RESET_TRANSFORM I.loc = src - I.setDir(AM.dir) + I.dir = AM.dir I.alpha = 128 LAZYADD(blueprint_data, I) diff --git a/code/modules/mob/dead/observer/observer_base.dm b/code/modules/mob/dead/observer/observer_base.dm index e4b5ee6a059..19e07381fb6 100644 --- a/code/modules/mob/dead/observer/observer_base.dm +++ b/code/modules/mob/dead/observer/observer_base.dm @@ -258,7 +258,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp /mob/dead/observer/Move(NewLoc, direct) update_parallax_contents() setDir(direct) - ghostimage.setDir(dir) + ghostimage.dir = dir if(NewLoc) forceMove(NewLoc, direct) diff --git a/code/modules/mob/living/carbon/human/human_update_icons.dm b/code/modules/mob/living/carbon/human/human_update_icons.dm index 7f85d00357a..d061ec01264 100644 --- a/code/modules/mob/living/carbon/human/human_update_icons.dm +++ b/code/modules/mob/living/carbon/human/human_update_icons.dm @@ -228,7 +228,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) if(husk) base_icon.ColorTone(husk_color_mod) else if(hulk) - var/list/tone = ReadRGB(hulk_color_mod) + var/list/tone = rgb2num(hulk_color_mod) base_icon.MapColors(rgb(tone[1],0,0),rgb(0,tone[2],0),rgb(0,0,tone[3])) //Handle husk overlay. @@ -385,7 +385,7 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) if(gradient) var/icon/icn_alpha_mask = icon(gradient.icon, gradient.icon_state) var/icon/icn_gradient = icon(gradient.icon, "full") - var/list/icn_color = ReadRGB(O.h_grad_colour) + var/list/icn_color = rgb2num(O.h_grad_colour) icn_gradient.MapColors(rgb(icn_color[1], 0, 0), rgb(0, icn_color[2], 0), rgb(0, 0, icn_color[3])) icn_gradient.ChangeOpacity(O.h_grad_alpha / 255) icn_gradient.AddAlphaMask(icn_alpha_mask) diff --git a/code/modules/mob/living/silicon/ai/ai_mob.dm b/code/modules/mob/living/silicon/ai/ai_mob.dm index a06089b9eea..5ec2d304c0a 100644 --- a/code/modules/mob/living/silicon/ai/ai_mob.dm +++ b/code/modules/mob/living/silicon/ai/ai_mob.dm @@ -966,6 +966,23 @@ GLOBAL_LIST_INIT(ai_verbs_default, list( AISD.emotion = emote AISD.update_icon() +// I would love to scope this locally to the AI class, however its used by holopads as well +// I wish we had nice OOP -aa07 +/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. + var/icon/alpha_mask + flat_icon.ColorTone(rgb(125,180,225)) // Let's make it bluish. + flat_icon.ChangeOpacity(0.5) // Make it half transparent. + + if(A.Height() == 64) + alpha_mask = new('icons/mob/ancient_machine.dmi', "scanline2") //Scaline for tall icons. + else + alpha_mask = new('icons/effects/effects.dmi', "scanline") //Scanline effect. + flat_icon.AddAlphaMask(alpha_mask) //Finally, let's mix in a distortion effect. + + return flat_icon + + //I am the icon meister. Bow fefore me. //>fefore /mob/living/silicon/ai/proc/ai_hologram_change() set name = "Change Hologram" diff --git a/code/modules/paperwork/photography.dm b/code/modules/paperwork/photography.dm index 6a1b0e22cff..3ce718dbdab 100644 --- a/code/modules/paperwork/photography.dm +++ b/code/modules/paperwork/photography.dm @@ -629,3 +629,30 @@ GLOBAL_LIST_INIT(SpookyGhosts, list("ghost","shade","shade2","ghost-narsie","hor 'sound/hallucinations/look_up1.ogg', 'sound/hallucinations/look_up2.ogg', 'sound/hallucinations/over_here1.ogg', 'sound/hallucinations/over_here2.ogg', 'sound/hallucinations/over_here3.ogg',\ 'sound/hallucinations/turn_around1.ogg', 'sound/hallucinations/turn_around2.ogg', 'sound/hallucinations/veryfar_noise.ogg', 'sound/hallucinations/wail.ogg') user << pick(creepyasssounds) + + +/obj/item/camera/proc/build_composite_icon(atom/A) + var/icon/composite = icon(A.icon, A.icon_state, A.dir, 1) + for(var/O in A.overlays) + var/image/I = O + composite.Blend(icon(I.icon, I.icon_state, I.dir, 1), ICON_OVERLAY) + return composite + +/obj/item/camera/proc/sort_atoms_by_layer(list/atoms) + // Comb sort icons based on levels + var/list/result = atoms.Copy() + var/gap = result.len + var/swapped = 1 + while(gap > 1 || swapped) + swapped = 0 + if(gap > 1) + gap = round(gap / 1.3) // 1.3 is the emperic comb sort coefficient + if(gap < 1) + gap = 1 + for(var/i = 1; gap + i <= result.len; i++) + var/atom/l = result[i] //Fucking hate + var/atom/r = result[gap+i] //how lists work here + if(l.layer > r.layer) //no "result[i].layer" for me + result.Swap(i, gap + i) + swapped = 1 + return result diff --git a/paradise.dme b/paradise.dme index 28ae45ef6e6..a3ff650fff5 100644 --- a/paradise.dme +++ b/paradise.dme @@ -119,6 +119,7 @@ #include "code\__HELPERS\api.dm" #include "code\__HELPERS\bitflag_lists.dm" #include "code\__HELPERS\cmp.dm" +#include "code\__HELPERS\colour_helpers.dm" #include "code\__HELPERS\constants.dm" #include "code\__HELPERS\debugger.dm" #include "code\__HELPERS\experimental.dm" @@ -126,9 +127,10 @@ #include "code\__HELPERS\filters.dm" #include "code\__HELPERS\game.dm" #include "code\__HELPERS\global_lists.dm" +#include "code\__HELPERS\icon_helpers.dm" +#include "code\__HELPERS\iconprocs.dm" #include "code\__HELPERS\heap.dm" #include "code\__HELPERS\icon_smoothing.dm" -#include "code\__HELPERS\icons.dm" #include "code\__HELPERS\lighting_helpers.dm" #include "code\__HELPERS\lists.dm" #include "code\__HELPERS\matrices.dm"