From c7ab0404c28c41375884b022125aa08b4c6bf36a Mon Sep 17 00:00:00 2001 From: Spacek531 Date: Sat, 17 Dec 2022 00:11:19 -0800 Subject: [PATCH] add color remapping --- rct-graphics-helper/magick_command.py | 8 +++- rct-graphics-helper/models/palette.py | 46 +++++++++++++++++++ .../operators/render_tiles_operator.py | 6 ++- rct-graphics-helper/palette_manager.py | 36 ++++++++++----- .../frame_processors/post_processor.py | 22 +++------ .../properties/general_properties.py | 17 ++++++- .../rct_graphics_helper_panel.py | 12 +++++ rct-graphics-helper/renderer.py | 3 ++ 8 files changed, 119 insertions(+), 31 deletions(-) diff --git a/rct-graphics-helper/magick_command.py b/rct-graphics-helper/magick_command.py index 54adec2..36998ca 100644 --- a/rct-graphics-helper/magick_command.py +++ b/rct-graphics-helper/magick_command.py @@ -16,6 +16,10 @@ def as_montage(self, inputs): "\" \"".join(inputs) + \ "\" +append -background none" + # Writes rbg or rbga data to output + def pixel_data(self, image): + self.full_command = '"'+ image + '" -format %c -depth 8 histogram:info:-' + # Writes the current result to the MPR for reuse in the same command. The cached result can be referenced using mpr:{id} def write_to_cache(self, id, delete_previous=False, next_file=""): delete_addition = "" @@ -100,10 +104,10 @@ def clone(self): # Gets the cli command to perform the ImageMagick operation - def get_command_string(self, magick_path, output): + def get_command_string(self, magick_path, output = None): if self.use_repage: self.full_command = self.full_command + " +repage" - final_command = magick_path + " " + self.full_command + " \"" + output + "\"" + final_command = magick_path + " " + self.full_command + (output and " \"" + output + "\"" or "") if os.name == "posix": final_command = final_command.replace("(", "\(").replace(")", "\)") return final_command diff --git a/rct-graphics-helper/models/palette.py b/rct-graphics-helper/models/palette.py index d9dc96a..8ebd10e 100644 --- a/rct-graphics-helper/models/palette.py +++ b/rct-graphics-helper/models/palette.py @@ -9,6 +9,7 @@ import subprocess import os +import re from ..magick_command import MagickCommand from ..res.res import res_path @@ -176,9 +177,37 @@ } } +palettes_for_recolor = [ + "black", "lavender_purple", "violet_purple","blue", "teal","yellow_green", + "sea_green","light_olive_green","dark_olive_green","lime_green","yellow", + "bright_yellow","orange","salmon","sandy_brown","bordeaux_red","bright_red", + "magenta","recolor_1","recolor_1_orct2" +] + +def create_remap_tuple(paletteName): + if not paletteName in palette_colors_details: + return + palette = palette_colors_details[paletteName] + return tuple([paletteName, palette["title"], palette["Description"]]) + +def create_remap_enumlist(defaultSelection): + myPaletteColors = palettes_for_recolor.copy() + myPaletteColors.remove(defaultSelection) + options = [create_remap_tuple(i) for i in myPaletteColors] + options.insert(0,create_remap_tuple(defaultSelection)) + return options + palette_base_path = os.path.join(res_path, "palettes") palette_groups_path = os.path.join(palette_base_path, "groups") +class RGBA: + def __init__(self, hexString, red, green, blue, alpha = 255): + self.hex = hexString + self.red = int(red) + self.green = int(green) + self.blue = int(blue) + self.alpha = int(alpha) + # Collection of color groups to create a palette from @@ -188,6 +217,7 @@ def __init__(self, path=None, colors=[]): self.generated = False self.invalidated = False self.path = "" + self.shades = [] if path != None: self.path = path @@ -242,3 +272,19 @@ def generate_output(self, renderer, output_path): self.path = output_path self.generated = True self.invalidated = False + + # generates a list of hexadecimal colors usable in ImageMagick + def get_shades(self, renderer): + cmd = MagickCommand("") + cmd.pixel_data(self.path) + raw_output = subprocess.check_output(cmd.get_command_string( + renderer.magick_path), shell = True) + output = raw_output.decode("utf-8") + data_rgb = re.findall(" \(([\d,]+)\)", output) + data_hex = re.findall("#[\w]+", output) + if (len(data_rgb) != len(data_hex)): + print(len(data_rgb), data_rgb) + print(len(data_hex), data_hex) + assert(len(data_rgb) != len(data_hex)) + colors_present = [RGBA(data_hex[i], *(data_rgb[i].split(","))) for i in range(len(data_rgb))] + self.shades = [shade.hex for shade in colors_present if shade.alpha == 255] diff --git a/rct-graphics-helper/operators/render_tiles_operator.py b/rct-graphics-helper/operators/render_tiles_operator.py index 8bf7952..87a1a24 100644 --- a/rct-graphics-helper/operators/render_tiles_operator.py +++ b/rct-graphics-helper/operators/render_tiles_operator.py @@ -22,7 +22,11 @@ def create_task(self, context): scene = context.scene props = scene.rct_graphics_helper_static_properties general_props = scene.rct_graphics_helper_general_properties - + + # Update the remap palettes with the ones we set. These colors will be recolored into OpenRCT2's remap 1, 2, 3 + # colors on materials with the appropriate index set + self.palette_manager.set_recolor_palettes(general_props.primary_remap_input, general_props.secondary_remap_input, + general_props.tertiary_remap_input) # Create the list of frames with our parameters self.task_builder.clear() self.task_builder.set_anti_aliasing_with_background( diff --git a/rct-graphics-helper/palette_manager.py b/rct-graphics-helper/palette_manager.py index 0fdaef4..bfd5aee 100644 --- a/rct-graphics-helper/palette_manager.py +++ b/rct-graphics-helper/palette_manager.py @@ -12,7 +12,7 @@ import bpy import math -from .models.palette import Palette, palette_base_path +from .models.palette import Palette, palette_base_path, palette_groups_path default_full_palette = Palette(os.path.join( palette_base_path, "default_full_palette.bmp"), [ @@ -52,12 +52,6 @@ "transparent" ]) -recolor_1_palette = Palette(os.path.join( - palette_base_path, "recolor_1_palette.bmp"), [ - "recolor_1", - "transparent" -]) - recolor_1_orct2_palette = Palette(os.path.join( palette_base_path, "recolor_1_orct2_palette.bmp"), [ "recolor_1_orct2", @@ -86,11 +80,7 @@ class PaletteManager: def __init__(self): - self.recolor_palettes = [ - recolor_1_palette, - recolor_2_palette, - recolor_3_palette - ] + self.recolor_palettes = [] self.orct2_recolor_palettes = [ recolor_1_orct2_palette, @@ -98,6 +88,28 @@ def __init__(self): recolor_3_palette ] + def set_recolor_palettes(self, recolor1, recolor2, recolor3): + self.recolor_palettes = [ + Palette( + os.path.join(palette_groups_path, recolor1 + ".png"), + [ recolor1 ] + ), + Palette( + os.path.join(palette_groups_path, recolor2 + ".png"), + [ recolor2 ] + ), + Palette( + os.path.join(palette_groups_path, recolor3 + ".png"), + [ recolor3 ] + ), + ] + + def get_recolor_shades(self, renderer): + for palette in self.recolor_palettes: + palette.get_shades(renderer) + for palette in self.orct2_recolor_palettes: + palette.get_shades(renderer) + # Gets a base palette for the selected palette mode for the selected number of recolorables def get_base_palette(self, selected_palette_mode, recolors, preference="FULL"): if selected_palette_mode == "AUTO": diff --git a/rct-graphics-helper/processors/sub_processes/frame_processors/post_processor.py b/rct-graphics-helper/processors/sub_processes/frame_processors/post_processor.py index a921bf4..ffa44ba 100644 --- a/rct-graphics-helper/processors/sub_processes/frame_processors/post_processor.py +++ b/rct-graphics-helper/processors/sub_processes/frame_processors/post_processor.py @@ -43,6 +43,9 @@ def process(self, frame, callback=None): magick_command.quantize(self.renderer.get_palette_path( frame.base_palette), self.renderer.floyd_steinberg_diffusion) + # this should be moved somewhere it can run once per palette change + self.renderer.get_recolor_shades() + # Force the recolorables to a palette that only contains the recolorable color channels_to_exclude_for_mai = ["Green", "Blue"] @@ -58,21 +61,10 @@ def process(self, frame, callback=None): forced_color_render = MagickCommand("mpr:render") forced_color_render.quantize(self.renderer.get_palette_path( palette), self.renderer.floyd_steinberg_diffusion) - - if i == 0: - # Replace our clover green recolor 1 with the OpenRCT2 orange recolor 1 - forced_color_render.replace_color("#003F21", "#6F332F") - forced_color_render.replace_color("#00672F", "#83372F") - forced_color_render.replace_color("#0B7B41", "#973F33") - forced_color_render.replace_color("#178F51", "#AB4333") - forced_color_render.replace_color("#1FA35C", "#BF4B2F") - forced_color_render.replace_color("#27B768", "#D34F2B") - forced_color_render.replace_color("#3BDB7F", "#E75723") - forced_color_render.replace_color("#5BEF98", "#FF5F1F") - forced_color_render.replace_color("#77F3A9", "#FF7F27") - forced_color_render.replace_color("#97F7BE", "#FF9B33") - forced_color_render.replace_color("#B7FBD0", "#FFB73F") - forced_color_render.replace_color("#D7FFE5", "#FFCF4B") + + # Replace our input color with the appropriate orct2 remap color + for i in range(min(len(palette.shades), len(orct2_palette.shades))): + forced_color_render.replace_color(palette.shades[i],orct2_palette.shades[i]) magick_command.mask_mix(forced_color_render, mask) diff --git a/rct-graphics-helper/properties/general_properties.py b/rct-graphics-helper/properties/general_properties.py index db87757..c3a4407 100644 --- a/rct-graphics-helper/properties/general_properties.py +++ b/rct-graphics-helper/properties/general_properties.py @@ -11,7 +11,7 @@ import math import os -from ..models.palette import palette_colors, palette_colors_details +from ..models.palette import palette_colors, palette_colors_details, create_remap_enumlist class GeneralProperties(bpy.types.PropertyGroup): @@ -94,6 +94,21 @@ class GeneralProperties(bpy.types.PropertyGroup): description="Which color groups to dither to. Recolorables will be excluded from this palette when used to avoid conflicts.", size=len(defaults)) + primary_remap_input = bpy.props.EnumProperty( + name="Primary Color", + items= create_remap_enumlist("recolor_1") + ) + + secondary_remap_input = bpy.props.EnumProperty( + name="Secondary Color", + items= create_remap_enumlist("magenta") + ) + + tertiary_remap_input = bpy.props.EnumProperty( + name="Tertiary Color", + items= create_remap_enumlist("yellow") + ) + render_mode = bpy.props.EnumProperty( name="Render Mode", items=( diff --git a/rct-graphics-helper/rct_graphics_helper_panel.py b/rct-graphics-helper/rct_graphics_helper_panel.py index 973db9c..dee26a0 100644 --- a/rct-graphics-helper/rct_graphics_helper_panel.py +++ b/rct-graphics-helper/rct_graphics_helper_panel.py @@ -82,6 +82,18 @@ def draw(self, context): row = layout.row() row.separator() + row = layout.row() + row.label("Remap Colors:") + + row = layout.row() + row.prop(properties,"primary_remap_input") + + row = layout.row() + row.prop(properties,"secondary_remap_input") + + row = layout.row() + row.prop(properties,"tertiary_remap_input") + row = layout.row() row.label("Dither Palette:") diff --git a/rct-graphics-helper/renderer.py b/rct-graphics-helper/renderer.py index ab089a6..3268ee4 100644 --- a/rct-graphics-helper/renderer.py +++ b/rct-graphics-helper/renderer.py @@ -114,6 +114,9 @@ def _render_finished_safe(self): if callback != None: callback() + def get_recolor_shades(self): + self.palette_manager.get_recolor_shades(self) + def get_palette_path(self, palette): palette.prepare(self) return palette.path