mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 11:40:13 +01:00
incoming update
This commit is contained in:
153
dev/color2alpha.py
Normal file
153
dev/color2alpha.py
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
from PIL import Image
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
def gimp_color_to_alpha_exact(image_path, target_color=(0, 0, 0), output_path=None):
|
||||||
|
"""
|
||||||
|
Exact replication of GIMP's Color to Alpha algorithm
|
||||||
|
|
||||||
|
Args:
|
||||||
|
image_path: Path to input image
|
||||||
|
target_color: RGB tuple of color to remove (default: black)
|
||||||
|
output_path: Optional output path
|
||||||
|
|
||||||
|
GIMP Settings replicated:
|
||||||
|
- Transparency threshold: 0
|
||||||
|
- Opacity threshold: 1
|
||||||
|
- Mode: replace
|
||||||
|
- Opacity: 100%
|
||||||
|
"""
|
||||||
|
img = Image.open(image_path).convert("RGBA")
|
||||||
|
data = np.array(img, dtype=np.float64)
|
||||||
|
|
||||||
|
# Normalize to 0-1 range for calculations
|
||||||
|
data = data / 255.0
|
||||||
|
target = np.array(target_color, dtype=np.float64) / 255.0
|
||||||
|
|
||||||
|
height, width = data.shape[:2]
|
||||||
|
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width):
|
||||||
|
pixel = data[y, x]
|
||||||
|
r, g, b, a = pixel[0], pixel[1], pixel[2], pixel[3]
|
||||||
|
|
||||||
|
# GIMP's Color to Alpha algorithm
|
||||||
|
tr, tg, tb = target[0], target[1], target[2]
|
||||||
|
|
||||||
|
# Calculate the alpha based on how much of the target color is present
|
||||||
|
if tr == 0.0 and tg == 0.0 and tb == 0.0:
|
||||||
|
# Special case for pure black target
|
||||||
|
# Alpha is the maximum of the RGB components
|
||||||
|
new_alpha = max(r, g, b)
|
||||||
|
|
||||||
|
if new_alpha > 0:
|
||||||
|
# Remove the black component, scale remaining color
|
||||||
|
data[y, x, 0] = r / new_alpha if new_alpha > 0 else 0
|
||||||
|
data[y, x, 1] = g / new_alpha if new_alpha > 0 else 0
|
||||||
|
data[y, x, 2] = b / new_alpha if new_alpha > 0 else 0
|
||||||
|
else:
|
||||||
|
# Pure black becomes transparent
|
||||||
|
data[y, x, 0] = 0
|
||||||
|
data[y, x, 1] = 0
|
||||||
|
data[y, x, 2] = 0
|
||||||
|
new_alpha = 0
|
||||||
|
|
||||||
|
# Replace mode: completely replace the alpha
|
||||||
|
data[y, x, 3] = new_alpha * a
|
||||||
|
|
||||||
|
else:
|
||||||
|
# General case for non-black target colors
|
||||||
|
# Calculate alpha as minimum ratio needed to remove target color
|
||||||
|
alpha_r = (r - tr) / (1.0 - tr) if tr < 1.0 else 0
|
||||||
|
alpha_g = (g - tg) / (1.0 - tg) if tg < 1.0 else 0
|
||||||
|
alpha_b = (b - tb) / (1.0 - tb) if tb < 1.0 else 0
|
||||||
|
|
||||||
|
new_alpha = max(0, max(alpha_r, alpha_g, alpha_b))
|
||||||
|
|
||||||
|
if new_alpha > 0:
|
||||||
|
# Calculate new RGB values
|
||||||
|
data[y, x, 0] = (r - tr) / new_alpha + tr if new_alpha > 0 else tr
|
||||||
|
data[y, x, 1] = (g - tg) / new_alpha + tg if new_alpha > 0 else tg
|
||||||
|
data[y, x, 2] = (b - tb) / new_alpha + tb if new_alpha > 0 else tb
|
||||||
|
else:
|
||||||
|
data[y, x, 0] = tr
|
||||||
|
data[y, x, 1] = tg
|
||||||
|
data[y, x, 2] = tb
|
||||||
|
|
||||||
|
# Replace mode: completely replace the alpha
|
||||||
|
data[y, x, 3] = new_alpha * a
|
||||||
|
|
||||||
|
# Convert back to 0-255 range and uint8
|
||||||
|
data = np.clip(data * 255.0, 0, 255).astype(np.uint8)
|
||||||
|
result = Image.fromarray(data)
|
||||||
|
|
||||||
|
if output_path:
|
||||||
|
result.save(output_path)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def gimp_color_to_alpha_vectorized(image_path, target_color=(0, 0, 0), output_path=None):
|
||||||
|
"""
|
||||||
|
Vectorized version of GIMP's Color to Alpha algorithm for better performance
|
||||||
|
"""
|
||||||
|
img = Image.open(image_path).convert("RGBA")
|
||||||
|
data = np.array(img, dtype=np.float64) / 255.0
|
||||||
|
|
||||||
|
target = np.array(target_color, dtype=np.float64) / 255.0
|
||||||
|
tr, tg, tb = target[0], target[1], target[2]
|
||||||
|
|
||||||
|
r, g, b, a = data[:,:,0], data[:,:,1], data[:,:,2], data[:,:,3]
|
||||||
|
|
||||||
|
if tr == 0.0 and tg == 0.0 and tb == 0.0:
|
||||||
|
# Special case for black target - vectorized
|
||||||
|
new_alpha = np.maximum(np.maximum(r, g), b)
|
||||||
|
|
||||||
|
# Avoid division by zero
|
||||||
|
safe_alpha = np.where(new_alpha > 0, new_alpha, 1)
|
||||||
|
|
||||||
|
# Scale RGB values
|
||||||
|
new_r = np.where(new_alpha > 0, r / safe_alpha, 0)
|
||||||
|
new_g = np.where(new_alpha > 0, g / safe_alpha, 0)
|
||||||
|
new_b = np.where(new_alpha > 0, b / safe_alpha, 0)
|
||||||
|
|
||||||
|
# Apply new values
|
||||||
|
data[:,:,0] = new_r
|
||||||
|
data[:,:,1] = new_g
|
||||||
|
data[:,:,2] = new_b
|
||||||
|
data[:,:,3] = new_alpha * a
|
||||||
|
|
||||||
|
else:
|
||||||
|
# General case for non-black colors - vectorized
|
||||||
|
alpha_r = np.where(tr < 1.0, (r - tr) / (1.0 - tr), 0)
|
||||||
|
alpha_g = np.where(tg < 1.0, (g - tg) / (1.0 - tg), 0)
|
||||||
|
alpha_b = np.where(tb < 1.0, (b - tb) / (1.0 - tb), 0)
|
||||||
|
|
||||||
|
new_alpha = np.maximum(0, np.maximum(np.maximum(alpha_r, alpha_g), alpha_b))
|
||||||
|
|
||||||
|
# Calculate new RGB
|
||||||
|
safe_alpha = np.where(new_alpha > 0, new_alpha, 1)
|
||||||
|
new_r = np.where(new_alpha > 0, (r - tr) / safe_alpha + tr, tr)
|
||||||
|
new_g = np.where(new_alpha > 0, (g - tg) / safe_alpha + tg, tg)
|
||||||
|
new_b = np.where(new_alpha > 0, (b - tb) / safe_alpha + tb, tb)
|
||||||
|
|
||||||
|
data[:,:,0] = new_r
|
||||||
|
data[:,:,1] = new_g
|
||||||
|
data[:,:,2] = new_b
|
||||||
|
data[:,:,3] = new_alpha * a
|
||||||
|
|
||||||
|
# Convert back to uint8
|
||||||
|
data = np.clip(data * 255.0, 0, 255).astype(np.uint8)
|
||||||
|
result = Image.fromarray(data)
|
||||||
|
|
||||||
|
if output_path:
|
||||||
|
result.save(output_path)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Usage examples
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Basic usage - convert black to alpha
|
||||||
|
|
||||||
|
#for i in range(13):
|
||||||
|
gimp_color_to_alpha_exact("gradient_clear.png", output_path= "gradient_clear.png")
|
||||||
|
|
||||||
|
print("Color to Alpha processing complete!")
|
||||||
@@ -4,6 +4,8 @@ import logging
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import random
|
import random
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
from raylib import SHADER_UNIFORM_FLOAT, SHADER_UNIFORM_VEC3
|
||||||
from libs.audio import audio
|
from libs.audio import audio
|
||||||
from libs.animation import Animation, MoveAnimation
|
from libs.animation import Animation, MoveAnimation
|
||||||
from libs.global_data import Crown, Difficulty
|
from libs.global_data import Crown, Difficulty
|
||||||
@@ -18,6 +20,45 @@ BOX_CENTER = 594 * tex.screen_scale
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def rgb_to_hue(r, g, b):
|
||||||
|
rf = r / 255.0
|
||||||
|
gf = g / 255.0
|
||||||
|
bf = b / 255.0
|
||||||
|
|
||||||
|
max_val = max(rf, gf, bf)
|
||||||
|
min_val = min(rf, gf, bf)
|
||||||
|
delta = max_val - min_val
|
||||||
|
|
||||||
|
if delta == 0:
|
||||||
|
return 0 # Gray/white, no hue
|
||||||
|
|
||||||
|
if max_val == rf:
|
||||||
|
hue = 60.0 * (((gf - bf) / delta) % 6)
|
||||||
|
elif max_val == gf:
|
||||||
|
hue = 60.0 * ((bf - rf) / delta + 2.0)
|
||||||
|
else:
|
||||||
|
hue = 60.0 * ((rf - gf) / delta + 4.0)
|
||||||
|
|
||||||
|
if hue < 0:
|
||||||
|
hue += 360.0
|
||||||
|
|
||||||
|
return hue
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_hue_shift(source_rgb, target_rgb):
|
||||||
|
source_hue = rgb_to_hue(*source_rgb)
|
||||||
|
target_hue = rgb_to_hue(*target_rgb)
|
||||||
|
|
||||||
|
shift = (target_hue - source_hue) / 360.0
|
||||||
|
|
||||||
|
# Normalize to 0.0-1.0 range
|
||||||
|
while shift < 0:
|
||||||
|
shift += 1.0
|
||||||
|
while shift >= 1.0:
|
||||||
|
shift -= 1.0
|
||||||
|
|
||||||
|
return shift
|
||||||
|
|
||||||
class BaseBox():
|
class BaseBox():
|
||||||
OUTLINE_MAP = {
|
OUTLINE_MAP = {
|
||||||
1: ray.Color(0, 77, 104, 255),
|
1: ray.Color(0, 77, 104, 255),
|
||||||
@@ -69,6 +110,17 @@ class BaseBox():
|
|||||||
|
|
||||||
def load_text(self):
|
def load_text(self):
|
||||||
self.name = OutlinedText(self.text_name, tex.skin_config["song_box_name"].font_size, ray.WHITE, outline_thickness=5, vertical=True)
|
self.name = OutlinedText(self.text_name, tex.skin_config["song_box_name"].font_size, ray.WHITE, outline_thickness=5, vertical=True)
|
||||||
|
'''
|
||||||
|
self.shader = ray.load_shader('', 'shader/colortransform.fs')
|
||||||
|
source_rgb = (142, 212, 30)
|
||||||
|
target_rgb = (209, 162, 19)
|
||||||
|
source_color = ray.ffi.new('float[3]', [source_rgb[0]/255.0, source_rgb[1]/255.0, source_rgb[2]/255.0])
|
||||||
|
target_color = ray.ffi.new('float[3]', [target_rgb[0]/255.0, target_rgb[1]/255.0, target_rgb[2]/255.0])
|
||||||
|
source_loc = ray.get_shader_location(self.shader, 'sourceColor')
|
||||||
|
target_loc = ray.get_shader_location(self.shader, 'targetColor')
|
||||||
|
ray.set_shader_value(self.shader, source_loc, source_color, SHADER_UNIFORM_VEC3)
|
||||||
|
ray.set_shader_value(self.shader, target_loc, target_color, SHADER_UNIFORM_VEC3)
|
||||||
|
'''
|
||||||
|
|
||||||
def move_box(self, current_time: float):
|
def move_box(self, current_time: float):
|
||||||
if self.position != self.target_position and self.move is None:
|
if self.position != self.target_position and self.move is None:
|
||||||
@@ -96,10 +148,12 @@ class BaseBox():
|
|||||||
self.open_fade.update(current_time)
|
self.open_fade.update(current_time)
|
||||||
|
|
||||||
def _draw_closed(self, x: float, y: float, outer_fade_override: float):
|
def _draw_closed(self, x: float, y: float, outer_fade_override: float):
|
||||||
|
#ray.begin_shader_mode(self.shader)
|
||||||
tex.draw_texture('box', 'folder_texture_left', frame=self.texture_index, x=x, fade=outer_fade_override)
|
tex.draw_texture('box', 'folder_texture_left', frame=self.texture_index, x=x, fade=outer_fade_override)
|
||||||
offset = 1 * tex.screen_scale if self.texture_index == 3 or self.texture_index >= 9 and self.texture_index not in {10,11,12} else 0
|
offset = 1 * tex.screen_scale if self.texture_index == 3 or self.texture_index >= 9 and self.texture_index not in {10,11,12} else 0
|
||||||
tex.draw_texture('box', 'folder_texture', frame=self.texture_index, x=x, x2=tex.skin_config["song_box_bg"].width, y=offset, fade=outer_fade_override)
|
tex.draw_texture('box', 'folder_texture', frame=self.texture_index, x=x, x2=tex.skin_config["song_box_bg"].width, y=offset, fade=outer_fade_override)
|
||||||
tex.draw_texture('box', 'folder_texture_right', frame=self.texture_index, x=x, fade=outer_fade_override)
|
tex.draw_texture('box', 'folder_texture_right', frame=self.texture_index, x=x, fade=outer_fade_override)
|
||||||
|
#ray.end_shader_mode()
|
||||||
if self.texture_index == BaseBox.DEFAULT_INDEX:
|
if self.texture_index == BaseBox.DEFAULT_INDEX:
|
||||||
tex.draw_texture('box', 'genre_overlay', x=x, y=y, fade=outer_fade_override)
|
tex.draw_texture('box', 'genre_overlay', x=x, y=y, fade=outer_fade_override)
|
||||||
elif self.texture_index == BaseBox.DIFFICULTY_SORT_INDEX:
|
elif self.texture_index == BaseBox.DIFFICULTY_SORT_INDEX:
|
||||||
@@ -938,20 +992,27 @@ class DanCourse(FileSystemItem):
|
|||||||
self.charts: list[tuple[TJAParser, int, int, int]] = []
|
self.charts: list[tuple[TJAParser, int, int, int]] = []
|
||||||
for chart in data["charts"]:
|
for chart in data["charts"]:
|
||||||
hash = chart["hash"]
|
hash = chart["hash"]
|
||||||
#chart_title = chart["title"]
|
chart_title = chart["title"]
|
||||||
#chart_subtitle = chart["subtitle"]
|
chart_subtitle = chart["subtitle"]
|
||||||
difficulty = chart["difficulty"]
|
difficulty = chart["difficulty"]
|
||||||
if hash in global_data.song_hashes:
|
if hash in global_data.song_hashes:
|
||||||
path = Path(global_data.song_hashes[hash][0]["file_path"])
|
path = Path(global_data.song_hashes[hash][0]["file_path"])
|
||||||
if (path.parent.parent / "box.def").exists():
|
|
||||||
_, genre_index, _ = parse_box_def(path.parent.parent)
|
|
||||||
else:
|
|
||||||
genre_index = 9
|
|
||||||
tja = TJAParser(path)
|
|
||||||
self.charts.append((tja, genre_index, difficulty, tja.metadata.course_data[difficulty].level))
|
|
||||||
else:
|
else:
|
||||||
pass
|
for key, value in global_data.song_hashes.items():
|
||||||
#do something with song_title, song_subtitle
|
for i in range(len(value)):
|
||||||
|
song = value[i]
|
||||||
|
if (song["title"]["en"].strip() == chart_title and
|
||||||
|
song["subtitle"]["en"].strip() == chart_subtitle.removeprefix('--') and
|
||||||
|
Path(song["file_path"]).exists()):
|
||||||
|
hash_val = key
|
||||||
|
path = Path(global_data.song_hashes[hash_val][i]["file_path"])
|
||||||
|
break
|
||||||
|
if (path.parent.parent / "box.def").exists():
|
||||||
|
_, genre_index, _ = parse_box_def(path.parent.parent)
|
||||||
|
else:
|
||||||
|
genre_index = 9
|
||||||
|
tja = TJAParser(path)
|
||||||
|
self.charts.append((tja, genre_index, difficulty, tja.metadata.course_data[difficulty].level))
|
||||||
self.exams = []
|
self.exams = []
|
||||||
for exam in data["exams"]:
|
for exam in data["exams"]:
|
||||||
self.exams.append(Exam(exam["type"], exam["value"][0], exam["value"][1], exam["range"]))
|
self.exams.append(Exam(exam["type"], exam["value"][0], exam["value"][1], exam["range"]))
|
||||||
@@ -1037,7 +1098,7 @@ class FileNavigator:
|
|||||||
|
|
||||||
self._generate_objects_recursive(root_path)
|
self._generate_objects_recursive(root_path)
|
||||||
|
|
||||||
if self.favorite_folder is not None:
|
if self.favorite_folder is not None and self.favorite_folder.path.exists():
|
||||||
song_list = self._read_song_list(self.favorite_folder.path)
|
song_list = self._read_song_list(self.favorite_folder.path)
|
||||||
for song_obj in song_list:
|
for song_obj in song_list:
|
||||||
if str(song_obj) in self.all_song_files:
|
if str(song_obj) in self.all_song_files:
|
||||||
@@ -1121,16 +1182,8 @@ class FileNavigator:
|
|||||||
for tja_path in sorted(tja_files):
|
for tja_path in sorted(tja_files):
|
||||||
song_key = str(tja_path)
|
song_key = str(tja_path)
|
||||||
if song_key not in self.all_song_files and tja_path.name == "dan.json":
|
if song_key not in self.all_song_files and tja_path.name == "dan.json":
|
||||||
valid_dan = True
|
song_obj = DanCourse(tja_path, tja_path.name)
|
||||||
with open(tja_path, 'r', encoding='utf-8') as file:
|
self.all_song_files[song_key] = song_obj
|
||||||
dan_data = json.load(file)
|
|
||||||
for chart in dan_data["charts"]:
|
|
||||||
hash = chart["hash"]
|
|
||||||
if hash not in global_data.song_hashes:
|
|
||||||
valid_dan = False
|
|
||||||
if valid_dan:
|
|
||||||
song_obj = DanCourse(tja_path, tja_path.name)
|
|
||||||
self.all_song_files[song_key] = song_obj
|
|
||||||
elif song_key not in self.all_song_files and tja_path in global_data.song_paths:
|
elif song_key not in self.all_song_files and tja_path in global_data.song_paths:
|
||||||
song_obj = SongFile(tja_path, tja_path.name, texture_index)
|
song_obj = SongFile(tja_path, tja_path.name, texture_index)
|
||||||
song_obj.box.get_scores()
|
song_obj.box.get_scores()
|
||||||
|
|||||||
111
libs/texture.py
111
libs/texture.py
@@ -62,14 +62,23 @@ class TextureWrapper:
|
|||||||
self.animations: dict[int, BaseAnimation] = dict()
|
self.animations: dict[int, BaseAnimation] = dict()
|
||||||
self.skin_config: dict[str, SkinInfo] = dict()
|
self.skin_config: dict[str, SkinInfo] = dict()
|
||||||
self.graphics_path = Path(get_config()['paths']['graphics_path'])
|
self.graphics_path = Path(get_config()['paths']['graphics_path'])
|
||||||
if (self.graphics_path / "skin_config.json").exists():
|
self.parent_graphics_path = Path(get_config()['paths']['graphics_path'])
|
||||||
data = json.loads((self.graphics_path / "skin_config.json").read_text())
|
if not (self.graphics_path / "skin_config.json").exists():
|
||||||
self.skin_config: dict[str, SkinInfo] = {
|
raise Exception("skin is missing a skin_config.json")
|
||||||
k: SkinInfo(v.get('x', 0), v.get('y', 0), v.get('font_size', 0), v.get('width', 0), v.get('height', 0)) for k, v in data.items()
|
|
||||||
}
|
data = json.loads((self.graphics_path / "skin_config.json").read_text())
|
||||||
|
self.skin_config: dict[str, SkinInfo] = {
|
||||||
|
k: SkinInfo(v.get('x', 0), v.get('y', 0), v.get('font_size', 0), v.get('width', 0), v.get('height', 0)) for k, v in data.items()
|
||||||
|
}
|
||||||
self.screen_width = int(self.skin_config["screen"].width)
|
self.screen_width = int(self.skin_config["screen"].width)
|
||||||
self.screen_height = int(self.skin_config["screen"].height)
|
self.screen_height = int(self.skin_config["screen"].height)
|
||||||
self.screen_scale = self.screen_width / 1280
|
self.screen_scale = self.screen_width / 1280
|
||||||
|
if "parent" in data["screen"]:
|
||||||
|
parent = data["screen"]["parent"]
|
||||||
|
self.parent_graphics_path = Path("Graphics") / parent
|
||||||
|
parent_data = json.loads((self.parent_graphics_path / "skin_config.json").read_text())
|
||||||
|
for k, v in parent_data.items():
|
||||||
|
self.skin_config[k] = SkinInfo(v.get('x', 0) * self.screen_scale, v.get('y', 0) * self.screen_scale, v.get('font_size', 0) * self.screen_scale, v.get('width', 0) * self.screen_scale, v.get('height', 0) * self.screen_scale)
|
||||||
|
|
||||||
def unload_textures(self):
|
def unload_textures(self):
|
||||||
"""Unload all textures and animations."""
|
"""Unload all textures and animations."""
|
||||||
@@ -133,26 +142,62 @@ class TextureWrapper:
|
|||||||
tex_object.controllable = [tex_mapping.get("controllable", False)]
|
tex_object.controllable = [tex_mapping.get("controllable", False)]
|
||||||
|
|
||||||
def load_animations(self, screen_name: str):
|
def load_animations(self, screen_name: str):
|
||||||
"""Load animations for a screen."""
|
"""Load animations for a screen, falling back to parent if not found."""
|
||||||
screen_path = self.graphics_path / screen_name
|
screen_path = self.graphics_path / screen_name
|
||||||
|
parent_screen_path = self.parent_graphics_path / screen_name
|
||||||
|
|
||||||
if (screen_path / 'animation.json').exists():
|
if (screen_path / 'animation.json').exists():
|
||||||
with open(screen_path / 'animation.json') as json_file:
|
with open(screen_path / 'animation.json') as json_file:
|
||||||
self.animations = parse_animations(json.loads(json_file.read()))
|
self.animations = parse_animations(json.loads(json_file.read()))
|
||||||
logger.info(f"Animations loaded for screen: {screen_name}")
|
logger.info(f"Animations loaded for screen: {screen_name}")
|
||||||
|
elif self.parent_graphics_path != self.graphics_path and (parent_screen_path / 'animation.json').exists():
|
||||||
|
with open(parent_screen_path / 'animation.json') as json_file:
|
||||||
|
anim_json = json.loads(json_file.read())
|
||||||
|
for anim in anim_json:
|
||||||
|
if "total_distance" in anim and not isinstance(anim["total_distance"], dict):
|
||||||
|
anim["total_distance"] = anim["total_distance"] * self.screen_scale
|
||||||
|
self.animations = parse_animations(anim_json)
|
||||||
|
logger.info(f"Animations loaded for screen: {screen_name} (from parent)")
|
||||||
|
|
||||||
def load_zip(self, screen_name: str, subset: str):
|
def load_zip(self, screen_name: str, subset: str):
|
||||||
"""Load textures from a zip file."""
|
"""Load textures from child zip, using parent texture.json if child doesn't have one."""
|
||||||
zip = (self.graphics_path / screen_name / subset).with_suffix('.zip')
|
zip_path = (self.graphics_path / screen_name / subset).with_suffix('.zip')
|
||||||
|
parent_zip_path = (self.parent_graphics_path / screen_name / subset).with_suffix('.zip')
|
||||||
|
|
||||||
if screen_name in self.textures and subset in self.textures[screen_name]:
|
if screen_name in self.textures and subset in self.textures[screen_name]:
|
||||||
return
|
return
|
||||||
try:
|
|
||||||
with zipfile.ZipFile(zip, 'r') as zip_ref:
|
|
||||||
if 'texture.json' not in zip_ref.namelist():
|
|
||||||
raise Exception(f"texture.json file missing from {zip}")
|
|
||||||
|
|
||||||
with zip_ref.open('texture.json') as json_file:
|
# Child zip must exist
|
||||||
tex_mapping_data: dict[str, dict] = json.loads(json_file.read().decode('utf-8'))
|
if not zip_path.exists():
|
||||||
self.textures[zip.stem] = dict()
|
logger.warning(f"Zip file not found: {subset} for screen {screen_name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
||||||
|
# Try to get texture.json from child first, then parent
|
||||||
|
tex_mapping_data = None
|
||||||
|
texture_json_source = "child"
|
||||||
|
|
||||||
|
if 'texture.json' in zip_ref.namelist():
|
||||||
|
with zip_ref.open('texture.json') as json_file:
|
||||||
|
tex_mapping_data = json.loads(json_file.read().decode('utf-8'))
|
||||||
|
elif self.parent_graphics_path != self.graphics_path and parent_zip_path.exists():
|
||||||
|
# Fall back to parent's texture.json
|
||||||
|
with zipfile.ZipFile(parent_zip_path, 'r') as parent_zip_ref:
|
||||||
|
if 'texture.json' in parent_zip_ref.namelist():
|
||||||
|
with parent_zip_ref.open('texture.json') as json_file:
|
||||||
|
tex_mapping_data = json.loads(json_file.read().decode('utf-8'))
|
||||||
|
for tex_map in tex_mapping_data:
|
||||||
|
for key in tex_mapping_data[tex_map]:
|
||||||
|
if key in ["x", "y", "x2", "y2"]:
|
||||||
|
tex_mapping_data[tex_map][key] = tex_mapping_data[tex_map][key] * self.screen_scale
|
||||||
|
texture_json_source = "parent"
|
||||||
|
else:
|
||||||
|
raise Exception(f"texture.json file missing from both {zip_path} and {parent_zip_path}")
|
||||||
|
else:
|
||||||
|
raise Exception(f"texture.json file missing from {zip_path}")
|
||||||
|
|
||||||
|
self.textures[zip_path.stem] = dict()
|
||||||
|
|
||||||
encoding = sys.getfilesystemencoding()
|
encoding = sys.getfilesystemencoding()
|
||||||
for tex_name in tex_mapping_data:
|
for tex_name in tex_mapping_data:
|
||||||
@@ -166,11 +211,11 @@ class TextureWrapper:
|
|||||||
extracted_path = Path(temp_dir) / tex_name
|
extracted_path = Path(temp_dir) / tex_name
|
||||||
if extracted_path.is_dir():
|
if extracted_path.is_dir():
|
||||||
frames = [ray.LoadTexture(str(frame).encode(encoding)) for frame in sorted(extracted_path.iterdir(),
|
frames = [ray.LoadTexture(str(frame).encode(encoding)) for frame in sorted(extracted_path.iterdir(),
|
||||||
key=lambda x: int(x.stem)) if frame.is_file()]
|
key=lambda x: int(x.stem)) if frame.is_file()]
|
||||||
else:
|
else:
|
||||||
frames = [ray.LoadTexture(str(extracted_path).encode(encoding))]
|
frames = [ray.LoadTexture(str(extracted_path).encode(encoding))]
|
||||||
self.textures[zip.stem][tex_name] = Texture(tex_name, frames, tex_mapping)
|
self.textures[zip_path.stem][tex_name] = Texture(tex_name, frames, tex_mapping)
|
||||||
self._read_tex_obj_data(tex_mapping, self.textures[zip.stem][tex_name])
|
self._read_tex_obj_data(tex_mapping, self.textures[zip_path.stem][tex_name])
|
||||||
elif f"{tex_name}.png" in zip_ref.namelist():
|
elif f"{tex_name}.png" in zip_ref.namelist():
|
||||||
tex_mapping = tex_mapping_data[tex_name]
|
tex_mapping = tex_mapping_data[tex_name]
|
||||||
|
|
||||||
@@ -181,30 +226,34 @@ class TextureWrapper:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
tex = ray.LoadTexture(temp_path.encode(encoding))
|
tex = ray.LoadTexture(temp_path.encode(encoding))
|
||||||
self.textures[zip.stem][tex_name] = Texture(tex_name, tex, tex_mapping)
|
self.textures[zip_path.stem][tex_name] = Texture(tex_name, tex, tex_mapping)
|
||||||
self._read_tex_obj_data(tex_mapping, self.textures[zip.stem][tex_name])
|
self._read_tex_obj_data(tex_mapping, self.textures[zip_path.stem][tex_name])
|
||||||
finally:
|
finally:
|
||||||
os.unlink(temp_path)
|
os.unlink(temp_path)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Texture {tex_name} was not found in {zip}")
|
logger.error(f"Texture {tex_name} was not found in {zip_path}")
|
||||||
logger.info(f"Textures loaded from zip: {zip}")
|
|
||||||
|
json_note = f" (texture.json from {texture_json_source})" if texture_json_source == "parent" else ""
|
||||||
|
logger.info(f"Textures loaded from zip: {zip_path}{json_note}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Failed to load textures from zip {zip}: {e}")
|
logger.error(f"Failed to load textures from zip {zip_path}: {e}")
|
||||||
|
|
||||||
def load_screen_textures(self, screen_name: str) -> None:
|
def load_screen_textures(self, screen_name: str) -> None:
|
||||||
"""Load textures for a screen."""
|
"""Load textures for a screen."""
|
||||||
screen_path = self.graphics_path / screen_name
|
screen_path = self.graphics_path / screen_name
|
||||||
|
|
||||||
if not screen_path.exists():
|
if not screen_path.exists():
|
||||||
logger.warning(f"Textures for Screen {screen_name} do not exist")
|
logger.warning(f"Textures for Screen {screen_name} do not exist")
|
||||||
return
|
return
|
||||||
if (screen_path / 'animation.json').exists():
|
|
||||||
with open(screen_path / 'animation.json') as json_file:
|
# Load animations
|
||||||
self.animations = parse_animations(json.loads(json_file.read()))
|
self.load_animations(screen_name)
|
||||||
logger.info(f"Animations loaded for screen: {screen_name}")
|
|
||||||
for zip in screen_path.iterdir():
|
# Load zip files from child screen path only
|
||||||
if zip.is_dir() or zip.suffix != ".zip":
|
for zip_file in screen_path.iterdir():
|
||||||
continue
|
if zip_file.is_file() and zip_file.suffix == ".zip":
|
||||||
self.load_zip(screen_name, zip.name)
|
self.load_zip(screen_name, zip_file.stem)
|
||||||
|
|
||||||
logger.info(f"Screen textures loaded for: {screen_name}")
|
logger.info(f"Screen textures loaded for: {screen_name}")
|
||||||
|
|
||||||
def control(self, tex_object: Texture, index: int = 0):
|
def control(self, tex_object: Texture, index: int = 0):
|
||||||
|
|||||||
45
shader/colortransform.fs
Normal file
45
shader/colortransform.fs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#version 330
|
||||||
|
|
||||||
|
in vec2 fragTexCoord;
|
||||||
|
in vec4 fragColor;
|
||||||
|
out vec4 finalColor;
|
||||||
|
|
||||||
|
uniform sampler2D texture0;
|
||||||
|
uniform vec3 sourceColor; // RGB as 0-1
|
||||||
|
uniform vec3 targetColor; // RGB as 0-1
|
||||||
|
|
||||||
|
vec3 rgb2hsv(vec3 c) {
|
||||||
|
vec4 K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);
|
||||||
|
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
||||||
|
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
|
||||||
|
float d = q.x - min(q.w, q.y);
|
||||||
|
float e = 1.0e-10;
|
||||||
|
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 hsv2rgb(vec3 c) {
|
||||||
|
vec4 K = vec4(1.0, 2.0/3.0, 1.0/3.0, 3.0);
|
||||||
|
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||||
|
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 texColor = texture(texture0, fragTexCoord);
|
||||||
|
|
||||||
|
vec3 sourceHSV = rgb2hsv(sourceColor);
|
||||||
|
vec3 targetHSV = rgb2hsv(targetColor);
|
||||||
|
vec3 pixelHSV = rgb2hsv(texColor.rgb);
|
||||||
|
|
||||||
|
// Calculate the transformation
|
||||||
|
float hueShift = targetHSV.x - sourceHSV.x;
|
||||||
|
float satScale = sourceHSV.y > 0.0 ? targetHSV.y / sourceHSV.y : 1.0;
|
||||||
|
float valScale = sourceHSV.z > 0.0 ? targetHSV.z / sourceHSV.z : 1.0;
|
||||||
|
|
||||||
|
// Apply transformation
|
||||||
|
pixelHSV.x = fract(pixelHSV.x + hueShift);
|
||||||
|
pixelHSV.y = clamp(pixelHSV.y * satScale, 0.0, 1.0);
|
||||||
|
pixelHSV.z = clamp(pixelHSV.z * valScale, 0.0, 1.0);
|
||||||
|
|
||||||
|
vec3 rgb = hsv2rgb(pixelHSV);
|
||||||
|
finalColor = vec4(rgb, texColor.a) * fragColor;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user