/* IconProcs by Lummox JR version 1.0 A library for manipulating icons and colors in BYOND See documentation in IconProcs.html */ #define TO_HEX_DIGIT(n) ascii2text((n&15) + ((n&15)<10 ? 48 : 87)) icon // Multiply all alpha values by this float proc/ChangeOpacity(opacity = 1.0) MapColors(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,opacity, 0,0,0,0) // Convert to grayscale proc/GrayScale() MapColors(0.3,0.3,0.3, 0.59,0.59,0.59, 0.11,0.11,0.11, 0,0,0) proc/ColorTone(tone) GrayScale() var/list/TONE = ReadRGB(tone) var/gray = round(TONE[1]*0.3 + TONE[2]*0.59 + TONE[3]*0.11, 1) var/icon/upper = (255-gray) ? new(src) : null if(gray) MapColors(255/gray,0,0, 0,255/gray,0, 0,0,255/gray, 0,0,0) Blend(tone, ICON_MULTIPLY) else SetIntensity(0) if(255-gray) upper.Blend(rgb(gray,gray,gray), ICON_SUBTRACT) upper.MapColors((255-TONE[1])/(255-gray),0,0,0, 0,(255-TONE[2])/(255-gray),0,0, 0,0,(255-TONE[3])/(255-gray),0, 0,0,0,0, 0,0,0,1) Blend(upper, ICON_ADD) // Take the minimum color of two icons; combine transparency as if blending with ICON_ADD proc/MinColors(icon) var/icon/I = new(src) I.Opaque() I.Blend(icon, ICON_SUBTRACT) Blend(I, ICON_SUBTRACT) // Take the maximum color of two icons; combine opacity as if blending with ICON_OR proc/MaxColors(icon) var/icon/I if(isicon(icon)) I = new(icon) else // solid color I = new(src) I.Blend("#000000", ICON_OVERLAY) I.SwapColor("#000000", null) I.Blend(icon, ICON_OVERLAY) var/icon/J = new(src) J.Opaque() I.Blend(J, ICON_SUBTRACT) Blend(I, ICON_OR) // make this icon fully opaque--transparent pixels become black proc/Opaque(background = "#000000") SwapColor(null, background) MapColors(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,0, 0,0,0,1) // Change a grayscale icon into a white icon where the original color becomes the alpha // I.e., black -> transparent, gray -> translucent white, white -> solid white 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) proc/UseAlphaMask(mask) Opaque() AddAlphaMask(mask) 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))