mirror of
https://github.com/freedoom/freedoom.git
synced 2025-09-04 04:25:46 -04:00
Initial work on grayscale PNG support for update-palette
This commit is contained in:
parent
2da59702c2
commit
8aa60e87e9
1 changed files with 59 additions and 20 deletions
|
@ -16,16 +16,25 @@
|
||||||
# - pypng: https://gitlab.com/drj11/pypng (retrieved Sept 10 2023)
|
# - pypng: https://gitlab.com/drj11/pypng (retrieved Sept 10 2023)
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import array
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from itertools import zip_longest
|
from itertools import cycle, zip_longest
|
||||||
import struct
|
import struct
|
||||||
|
from operator import eq
|
||||||
import os
|
import os
|
||||||
from os.path import dirname, relpath, realpath, join, normpath
|
from os.path import dirname, relpath, realpath, join, normpath
|
||||||
import png
|
import png
|
||||||
from sys import argv
|
from sys import argv
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
|
# Misc. utility functions
|
||||||
|
# https://docs.python.org/3.8/library/itertools.html#itertools-recipes
|
||||||
|
def grouper(iterable, n, fillvalue=None):
|
||||||
|
"Collect data into fixed-length chunks or blocks"
|
||||||
|
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
|
||||||
|
args = [iter(iterable)] * n
|
||||||
|
return zip_longest(*args, fillvalue=fillvalue)
|
||||||
|
|
||||||
|
|
||||||
# Parse the command line arguments, and return a dict with the arguments
|
# Parse the command line arguments, and return a dict with the arguments
|
||||||
def parse_args():
|
def parse_args():
|
||||||
|
@ -51,14 +60,6 @@ def parse_args():
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
# https://docs.python.org/3.8/library/itertools.html#itertools-recipes
|
|
||||||
def grouper(iterable, n, fillvalue=None):
|
|
||||||
"Collect data into fixed-length chunks or blocks"
|
|
||||||
# grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
|
|
||||||
args = [iter(iterable)] * n
|
|
||||||
return zip_longest(*args, fillvalue=fillvalue)
|
|
||||||
|
|
||||||
|
|
||||||
# Compare the old palette and the new palette, and return a dict with the
|
# Compare the old palette and the new palette, and return a dict with the
|
||||||
# differences.
|
# differences.
|
||||||
def compare_palettes(directory, new_palette):
|
def compare_palettes(directory, new_palette):
|
||||||
|
@ -115,8 +116,38 @@ def compare_palettes(directory, new_palette):
|
||||||
for colour, indices in old_palette_duplicates.items():
|
for colour, indices in old_palette_duplicates.items():
|
||||||
if all(map(lambda i: i in old_to_new.keys(), indices)):
|
if all(map(lambda i: i in old_to_new.keys(), indices)):
|
||||||
replaced[colour] = old_to_new[min(indices)]
|
replaced[colour] = old_to_new[min(indices)]
|
||||||
else:
|
print("replaced", replaced)
|
||||||
replaced[colour] = None
|
|
||||||
|
# Find the closest colour in the old palette for each grayscale colour
|
||||||
|
def is_grayscale(colour):
|
||||||
|
return all(map(eq, colour[1], cycle((colour[1][0],))))
|
||||||
|
# def to_grayscale(colour):
|
||||||
|
# return colour[0] if is_grayscale(colour) else colour
|
||||||
|
grayscale_colours_in_palette = dict(
|
||||||
|
map(lambda g: (g[0], g[1][0]),
|
||||||
|
filter(is_grayscale, enumerate(new_palette)))
|
||||||
|
)
|
||||||
|
def closest_grayscale(grayscale):
|
||||||
|
nonlocal grayscale_colours_in_palette
|
||||||
|
distances = sorted(map(
|
||||||
|
lambda kv: (kv[0], abs(kv[1] - grayscale)),
|
||||||
|
grayscale_colours_in_palette.items()
|
||||||
|
), key=lambda kv: kv[1])
|
||||||
|
closest = distances[0][1]
|
||||||
|
distances = sorted(
|
||||||
|
filter(lambda kv: kv[1] == closest, distances),
|
||||||
|
key=lambda kv: kv[0])
|
||||||
|
return distances[0][0]
|
||||||
|
# This is a map from grayscale to palette index
|
||||||
|
gray_map = array.array('B', map(closest_grayscale, range(256)))
|
||||||
|
# Turn it into a grayscale to colour map, in case any grayscale colours
|
||||||
|
# were changed in the new palette
|
||||||
|
gray_map = dict(
|
||||||
|
map(lambda g: (g[0], old_to_new[g[1]]),
|
||||||
|
filter(
|
||||||
|
lambda g: g[1] in old_to_new,
|
||||||
|
enumerate(gray_map))))
|
||||||
|
print(len(gray_map), gray_map)
|
||||||
|
|
||||||
# Replace the keys in old_to_new, which are indices, with the old palette
|
# Replace the keys in old_to_new, which are indices, with the old palette
|
||||||
# colours they correspond to. This way, we have a colour-to-colour dict.
|
# colours they correspond to. This way, we have a colour-to-colour dict.
|
||||||
|
@ -125,12 +156,12 @@ def compare_palettes(directory, new_palette):
|
||||||
replaced.get(old_palette[iv[0]], iv[1])),
|
replaced.get(old_palette[iv[0]], iv[1])),
|
||||||
old_to_new.items()
|
old_to_new.items()
|
||||||
)))
|
)))
|
||||||
return old_to_new
|
return old_to_new, gray_map
|
||||||
|
|
||||||
|
|
||||||
# "Stolen" from the map-color-index script
|
# "Stolen" from the map-color-index script
|
||||||
# Process a directory recursively for PNG files.
|
# Process a directory recursively for PNG files.
|
||||||
def process_dir(colour_map, dry_run, directory, palette):
|
def process_dir(colour_map, gray_map, dry_run, directory, palette):
|
||||||
pngs_changed_count = 0
|
pngs_changed_count = 0
|
||||||
pngs_examined_count = 0
|
pngs_examined_count = 0
|
||||||
|
|
||||||
|
@ -140,12 +171,12 @@ def process_dir(colour_map, dry_run, directory, palette):
|
||||||
continue
|
continue
|
||||||
png_path = os.path.join(dirpath, png_base)
|
png_path = os.path.join(dirpath, png_base)
|
||||||
pngs_examined_count += 1
|
pngs_examined_count += 1
|
||||||
if process_png(colour_map, png_path, dry_run, directory):
|
if process_png(colour_map, gray_map, png_path, dry_run, directory):
|
||||||
pngs_changed_count += 1
|
pngs_changed_count += 1
|
||||||
|
|
||||||
|
|
||||||
# Process a PNG file in place.
|
# Process a PNG file in place.
|
||||||
def process_png(colour_map, png_path, dry, directory):
|
def process_png(colour_map, gray_map, png_path, dry, directory):
|
||||||
# Read the PNG file
|
# Read the PNG file
|
||||||
png_reader = png.Reader(filename=png_path)
|
png_reader = png.Reader(filename=png_path)
|
||||||
|
|
||||||
|
@ -216,8 +247,9 @@ def process_png(colour_map, png_path, dry, directory):
|
||||||
|
|
||||||
return modified, new_rows
|
return modified, new_rows
|
||||||
|
|
||||||
# Modify the PLTE chunk if necessary, and check if the PLTE modifications
|
def maybe_modify_grayscale_image(rows, channels):
|
||||||
# affect the IDAT chunk.
|
pass
|
||||||
|
|
||||||
plte_modified = False
|
plte_modified = False
|
||||||
idat_modified = False
|
idat_modified = False
|
||||||
width, height, rows, info = png_reader.read()
|
width, height, rows, info = png_reader.read()
|
||||||
|
@ -232,6 +264,8 @@ def process_png(colour_map, png_path, dry, directory):
|
||||||
has_alpha = info.get("alpha", False)
|
has_alpha = info.get("alpha", False)
|
||||||
channels = info.get("planes")
|
channels = info.get("planes")
|
||||||
if is_paletted:
|
if is_paletted:
|
||||||
|
# Modify the PLTE chunk if necessary, and check if the PLTE
|
||||||
|
# modifications affect the colours used in the IDAT chunk.
|
||||||
plte_modified, new_palette = maybe_modify_plte(info["palette"])
|
plte_modified, new_palette = maybe_modify_plte(info["palette"])
|
||||||
if plte_modified:
|
if plte_modified:
|
||||||
idat_modified = any(map(is_paletted_colour_changed, rows))
|
idat_modified = any(map(is_paletted_colour_changed, rows))
|
||||||
|
@ -239,6 +273,8 @@ def process_png(colour_map, png_path, dry, directory):
|
||||||
new_palette = None
|
new_palette = None
|
||||||
idat_modified, rows = maybe_modify_truecolour_image(
|
idat_modified, rows = maybe_modify_truecolour_image(
|
||||||
rows, channels, transparent_colour)
|
rows, channels, transparent_colour)
|
||||||
|
else:
|
||||||
|
idat_modified, rows = maybe_modify_grayscale_image(rows, channels)
|
||||||
|
|
||||||
# Write the modified PNG file
|
# Write the modified PNG file
|
||||||
if idat_modified:
|
if idat_modified:
|
||||||
|
@ -257,8 +293,11 @@ def process_png(colour_map, png_path, dry, directory):
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
directory = normpath(join(dirname(realpath(argv[0])), ".."))
|
directory = normpath(join(dirname(realpath(argv[0])), ".."))
|
||||||
comparison = compare_palettes(directory, args.palette)
|
colour_map, gray_map = compare_palettes(directory, args.palette)
|
||||||
process_dir(comparison, directory=directory, **vars(args))
|
print("The rest of the script is disabled for development right now")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
process_dir(colour_map, gray_map, directory=directory, **vars(args))
|
||||||
# Replace old playpal-base.lmp
|
# Replace old playpal-base.lmp
|
||||||
if not args.dry_run:
|
if not args.dry_run:
|
||||||
playpal_base_path = (
|
playpal_base_path = (
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue