diff --git a/code/__HELPERS/matrices/color_matrix.dm b/code/__HELPERS/matrices/color_matrix.dm index e4a4a9bb48..bcc48b45f5 100644 --- a/code/__HELPERS/matrices/color_matrix.dm +++ b/code/__HELPERS/matrices/color_matrix.dm @@ -138,6 +138,25 @@ round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), ro var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001) return list(cosval,sinval,0,0, -sinval,cosval,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) +/** + * Builds a color matrix that transforms the hue, saturation, and value, all in one operation. + */ +/proc/color_matrix_hsv(hue, saturation, value) + hue = clamp(360 - hue, 0, 360) + + // This is very much a rough approximation of hueshifting. This carries some artifacting, such as negative values that simply shouldn't exist, but it does get the job done, and that's what matters. + var/cos_a = cos(hue) // These have to be inverted from 360, otherwise the hue's inverted + var/sin_a = sin(hue) + var/rot_x = cos_a + (1 - cos_a) / 3 + var/rot_y = (1 - cos_a) / 3 - 0.5774 * sin_a // 0.5774 is sqrt(1/3) + var/rot_z = (1 - cos_a) / 3 + 0.5774 * sin_a + + return list( + round((((1-saturation) * LUMA_R) + (rot_x * saturation)) * value, 0.01), round((((1-saturation) * LUMA_R) + (rot_y * saturation)) * value, 0.01), round((((1-saturation) * LUMA_R) + (rot_z * saturation)) * value, 0.01), + round((((1-saturation) * LUMA_G) + (rot_z * saturation)) * value, 0.01), round((((1-saturation) * LUMA_G) + (rot_x * saturation)) * value, 0.01), round((((1-saturation) * LUMA_G) + (rot_y * saturation)) * value, 0.01), + round((((1-saturation) * LUMA_B) + (rot_y * saturation)) * value, 0.01), round((((1-saturation) * LUMA_B) + (rot_z * saturation)) * value, 0.01), round((((1-saturation) * LUMA_B) + (rot_x * saturation)) * value, 0.01), + 0, 0, 0 + ) //Returns a matrix addition of A with B /proc/color_matrix_add(list/A, list/B) diff --git a/code/game/machinery/colormate.dm b/code/game/machinery/colormate.dm index 1528142a3b..a208fc0bd2 100644 --- a/code/game/machinery/colormate.dm +++ b/code/game/machinery/colormate.dm @@ -1,6 +1,10 @@ +#define COLORMATE_TINT 1 +#define COLORMATE_HSV 2 +#define COLORMATE_MATRIX 3 + /obj/machinery/gear_painter name = "\improper Color Mate" - desc = "A machine to give your apparel a fresh new color! Recommended to use with white items for best results." + desc = "A machine to give your apparel a fresh new color!" icon = 'icons/obj/vending.dmi' icon_state = "colormate" // light_mask = "colormate-light-mask" @@ -10,7 +14,11 @@ var/atom/movable/inserted var/activecolor = "#FFFFFF" var/list/color_matrix_last - var/matrix_mode = FALSE + var/active_mode = COLORMATE_HSV + + var/build_hue = 0 + var/build_sat = 1 + var/build_val = 1 /// Allow holder'd mobs var/allow_mobs = TRUE /// Minimum lightness for normal mode @@ -71,8 +79,8 @@ return if(!QDELETED(H)) H.release() + insert_mob(victim, user) - temp = "[victim] has been inserted." SStgui.update_uis(src) if(is_type_in_list(I, allowed_types) && is_operational()) @@ -85,7 +93,6 @@ inserted = I update_icon() - temp = "[I] has been inserted." SStgui.update_uis(src) else @@ -134,7 +141,7 @@ /obj/machinery/gear_painter/ui_data(mob/user) . = list() - .["matrixactive"] = matrix_mode + .["activemode"] = active_mode .["matrixcolors"] = list( "rr" = color_matrix_last[1], "rg" = color_matrix_last[2], @@ -149,6 +156,9 @@ "cg" = color_matrix_last[11], "cb" = color_matrix_last[12] ) + .["buildhue"] = build_hue + .["buildsat"] = build_sat + .["buildval"] = build_val if(temp) .["temp"] = temp if(inserted) @@ -166,7 +176,7 @@ if(inserted) switch(action) if("switch_modes") - matrix_mode = text2num(params["mode"]) + active_mode = text2num(params["mode"]) return TRUE if("choose_color") var/chosen_color = input(usr, "Choose a color: ", "Colormate color picking", activecolor) as color|null @@ -174,14 +184,11 @@ activecolor = chosen_color return TRUE if("paint") - if(!check_valid_color(activecolor, usr)) - return TRUE - inserted.add_atom_colour(activecolor, FIXED_COLOUR_PRIORITY) - playsound(src, 'sound/effects/spray3.ogg', 50, 1) + do_paint(usr) temp = "Painted Successfully!" return TRUE if("drop") - temp = "Ejected \the [inserted]!" + temp = "" drop_item() return TRUE if("clear") @@ -192,76 +199,95 @@ if("set_matrix_color") color_matrix_last[params["color"]] = params["value"] return TRUE - if("matrix_paint") - var/list/cm = rgb_construct_color_matrix( - text2num(color_matrix_last[1]), - text2num(color_matrix_last[2]), - text2num(color_matrix_last[3]), - text2num(color_matrix_last[4]), - text2num(color_matrix_last[5]), - text2num(color_matrix_last[6]), - text2num(color_matrix_last[7]), - text2num(color_matrix_last[8]), - text2num(color_matrix_last[9]), - text2num(color_matrix_last[10]), - text2num(color_matrix_last[11]), - text2num(color_matrix_last[12]) - ) - if(!check_valid_color(cm, usr)) - return TRUE - inserted.add_atom_colour(cm, FIXED_COLOUR_PRIORITY) - playsound(src, 'sound/effects/spray3.ogg', 50, 1) - temp = "Matrix Painted Successfully!" + if("set_hue") + build_hue = clamp(text2num(params["buildhue"]), 0, 360) return TRUE + if("set_sat") + build_sat = clamp(text2num(params["buildsat"]), -10, 10) + return TRUE + if("set_val") + build_val = clamp(text2num(params["buildval"]), -10, 10) + return TRUE + + +/obj/machinery/gear_painter/proc/do_paint(mob/user) + var/color_to_use + switch(active_mode) + if(COLORMATE_TINT) + color_to_use = activecolor + if(COLORMATE_MATRIX) + color_to_use = rgb_construct_color_matrix( + text2num(color_matrix_last[1]), + text2num(color_matrix_last[2]), + text2num(color_matrix_last[3]), + text2num(color_matrix_last[4]), + text2num(color_matrix_last[5]), + text2num(color_matrix_last[6]), + text2num(color_matrix_last[7]), + text2num(color_matrix_last[8]), + text2num(color_matrix_last[9]), + text2num(color_matrix_last[10]), + text2num(color_matrix_last[11]), + text2num(color_matrix_last[12]) + ) + if(COLORMATE_HSV) + color_to_use = color_matrix_hsv(build_hue, build_sat, build_val) + color_matrix_last = color_to_use + if(!color_to_use || !check_valid_color(color_to_use, user)) + to_chat(user, "Invalid color.") + return FALSE + inserted.add_atom_colour(color_to_use, FIXED_COLOUR_PRIORITY) + playsound(src, 'sound/effects/spray3.ogg', 50, 1) + return TRUE + /// Produces the preview image of the item, used in the UI, the way the color is not stacking is a sin. /obj/machinery/gear_painter/proc/build_preview() if(inserted) //sanity - if(matrix_mode) - var/list/cm = rgb_construct_color_matrix( - text2num(color_matrix_last[1]), - text2num(color_matrix_last[2]), - text2num(color_matrix_last[3]), - text2num(color_matrix_last[4]), - text2num(color_matrix_last[5]), - text2num(color_matrix_last[6]), - text2num(color_matrix_last[7]), - text2num(color_matrix_last[8]), - text2num(color_matrix_last[9]), - text2num(color_matrix_last[10]), - text2num(color_matrix_last[11]), - text2num(color_matrix_last[12]) - ) - if(!check_valid_color(cm, usr)) - temp = "Failed to generate preview: Invalid color!" - return getFlatIcon(inserted, defdir=SOUTH, no_anim=TRUE) + var/list/cm + switch(active_mode) + if(COLORMATE_MATRIX) + cm = rgb_construct_color_matrix( + text2num(color_matrix_last[1]), + text2num(color_matrix_last[2]), + text2num(color_matrix_last[3]), + text2num(color_matrix_last[4]), + text2num(color_matrix_last[5]), + text2num(color_matrix_last[6]), + text2num(color_matrix_last[7]), + text2num(color_matrix_last[8]), + text2num(color_matrix_last[9]), + text2num(color_matrix_last[10]), + text2num(color_matrix_last[11]), + text2num(color_matrix_last[12]) + ) + if(!check_valid_color(cm, usr)) + return getFlatIcon(inserted, defdir=SOUTH, no_anim=TRUE) - var/cur_color = inserted.color - inserted.color = null - inserted.color = cm - var/icon/preview = getFlatIcon(inserted, defdir=SOUTH, no_anim=TRUE) - inserted.color = cur_color + if(COLORMATE_TINT) + if(!check_valid_color(activecolor, usr)) + return getFlatIcon(inserted, defdir=SOUTH, no_anim=TRUE) - . = preview + if(COLORMATE_HSV) + cm = color_matrix_hsv(build_hue, build_sat, build_val) + color_matrix_last = cm + if(!check_valid_color(cm, usr)) + return getFlatIcon(inserted, defdir=SOUTH, no_anim=TRUE) - else - if(!check_valid_color(activecolor, usr)) - temp = "Failed to generate preview: Invalid color!" - return getFlatIcon(inserted, defdir=SOUTH, no_anim=TRUE) + var/cur_color = inserted.color + inserted.color = null + inserted.color = (active_mode == COLORMATE_TINT ? activecolor : cm) + var/icon/preview = getFlatIcon(inserted, defdir=SOUTH, no_anim=TRUE) + inserted.color = cur_color + temp = "" - var/cur_color = inserted.color - inserted.color = null - inserted.color = activecolor - var/icon/preview = getFlatIcon(inserted, defdir=SOUTH, no_anim=TRUE) - inserted.color = cur_color - - . = preview + . = preview /obj/machinery/gear_painter/proc/check_valid_color(list/cm, mob/user) if(!islist(cm)) // normal var/list/HSV = ReadHSV(RGBtoHSV(cm)) if(HSV[3] < minimum_normal_lightness) - temp = "[cm] is far too dark (min lightness [minimum_normal_lightness]!" + temp = "[cm] is too dark (Minimum lightness: [minimum_normal_lightness])" return FALSE return TRUE else // matrix @@ -275,6 +301,6 @@ COLORTEST("FFFFFF", cm) #undef COLORTEST if(passed < minimum_matrix_tests) - temp = "[english_list(color)] is not allowed (passed [passed] out of 4, minimum [minimum_matrix_tests], minimum lightness [minimum_matrix_lightness])." + temp = "Matrix is too dark. (passed [passed] out of [minimum_matrix_tests] required tests. Minimum lightness: [minimum_matrix_lightness])." return FALSE return TRUE diff --git a/tgui/packages/tgui/interfaces/Colormate.js b/tgui/packages/tgui/interfaces/Colormate.js index f376547102..02810f2943 100644 --- a/tgui/packages/tgui/interfaces/Colormate.js +++ b/tgui/packages/tgui/interfaces/Colormate.js @@ -1,115 +1,120 @@ import { useBackend } from '../backend'; -import { Button, Flex, Icon, NoticeBox, NumberInput, Section, Tabs } from '../components'; +import { Button, Icon, NoticeBox, NumberInput, Section, Table, Tabs, Slider } from '../components'; import { Window } from '../layouts'; export const Colormate = (props, context) => { const { act, data } = useBackend(context); - const { matrixactive, temp } = data; + const { activemode, temp } = data; const item = data.item || []; return ( - {temp ? ( - {temp} - ) : (null)} - {Object.keys(item).length ? ( - <> - -
-
Item:
- -
-
-
Preview:
- -
-
-
+
+ {temp ? ( + {temp} + ) : (null)} + {Object.keys(item).length ? ( + <> + + +
+
Item:
+ +
+
+ +
+
Preview:
+ +
+
+
act('switch_modes', { - mode: "0", + mode: 1, })} > - Regular coloring + Tint coloring (Simple) act('switch_modes', { - mode: "1", + mode: 2, })} > - Matrixed coloring + HSV coloring (Normal) + + act('switch_modes', { + mode: 3, + })} > + Matrix coloring (Advanced) - {matrixactive ? ( - <> -
Coloring: {item.name}
- - - ) : ( - <> -
Coloring: {item.name}
- - - )} -
- - ) : ( -
+
Coloring: {item.name}
+ + +
+ + ) : (
No item inserted.
-
- )} + )} +
); }; -export const ColormateNoMatrix = (props, context) => { +export const ColormateTint = (props, context) => { const { act, data } = useBackend(context); return ( -
- - -
+