mirror of
https://github.com/KrisKennaway/ii-pix.git
synced 2025-04-08 19:39:24 +00:00
Merge pull request #5 from KrisKennaway/mono
Add support for DHGR mono conversions and fix compatibility with python 3.10
This commit is contained in:
commit
05a3624866
11
README.md
11
README.md
@ -1,4 +1,4 @@
|
||||
# ][-pix 2.0
|
||||
# ][-pix 2.1
|
||||
|
||||
][-pix is an image conversion utility targeting Apple II graphics modes, currently Double Hi-Res
|
||||
(enhanced //e, //c, //gs) and Super Hi-Res (//gs).
|
||||
@ -39,11 +39,11 @@ To convert an image, the basic command is:
|
||||
python convert.py <mode> [<flags>] <input> <output>
|
||||
```
|
||||
where
|
||||
* `mode` is `dhr` for Double Hi-Res (560x192), or `shr` for Super Hi-Res (320x200)
|
||||
* `mode` is `dhr` for Double Hi-Res Colour (560x192), `dhr_mono` for Double Hi-Res Mono (560x192), or `shr` for Super Hi-Res (320x200)
|
||||
* `input` is the source image file to convert (e.g. `my-image.jpg`)
|
||||
* `output` is the output filename to produce (e.g. `my-image.dhr`)
|
||||
|
||||
The following flags are supported in both `dhr` and `shr` modes:
|
||||
The following flags are supported in all modes:
|
||||
|
||||
* `--show-input` Whether to show the input image before conversion. (default: False)
|
||||
* `--show-output` Whether to show the output image after conversion. (default: True)
|
||||
@ -158,6 +158,11 @@ python convert.py shr examples/shr/rabbit-kitten-original.png examples/shr/rabbi
|
||||
|
||||
# Version history
|
||||
|
||||
## v2.1 (2023-01-21)
|
||||
|
||||
* Added support for DHGR mono conversions
|
||||
* Fixed compatibility with python 3.10
|
||||
|
||||
## v2.0 (2022-07-16)
|
||||
|
||||
* Added support for Super Hi-Res 320x200 image conversions
|
||||
|
32
convert.py
32
convert.py
@ -79,6 +79,10 @@ def main():
|
||||
"value of --palette)")
|
||||
dhr_parser.set_defaults(func=convert_dhr)
|
||||
|
||||
dhr_mono_parser = subparsers.add_parser("dhr_mono")
|
||||
add_common_args(dhr_mono_parser)
|
||||
dhr_mono_parser.set_defaults(func=convert_dhr_mono)
|
||||
|
||||
shr_parser = subparsers.add_parser("shr")
|
||||
add_common_args(shr_parser)
|
||||
shr_parser.add_argument(
|
||||
@ -106,25 +110,29 @@ def prepare_image(image_filename: str, show_input: bool, screen,
|
||||
# Open and resize source image
|
||||
image = image_py.open(image_filename)
|
||||
if show_input:
|
||||
image_py.resize(image, screen.X_RES, screen.Y_RES,
|
||||
srgb_output=False).show()
|
||||
rgb = np.array(
|
||||
image_py.resize(image, screen.X_RES, screen.Y_RES,
|
||||
gamma=gamma_correct)).astype(np.float32) / 255
|
||||
return rgb
|
||||
|
||||
image_py.resize(image, screen.X_RES, screen.Y_RES * 2,
|
||||
srgb_output=True).show()
|
||||
return image_py.resize(image, screen.X_RES, screen.Y_RES,
|
||||
gamma=gamma_correct)
|
||||
|
||||
def convert_dhr(args):
|
||||
palette = palette_py.PALETTES[args.palette]()
|
||||
screen = screen_py.DHGRScreen(palette)
|
||||
rgb = prepare_image(args.input, args.show_input, screen, args.gamma_correct)
|
||||
convert_dhr_py.convert(screen, rgb, args)
|
||||
screen = screen_py.DHGRNTSCScreen(palette)
|
||||
image = prepare_image(args.input, args.show_input, screen,
|
||||
args.gamma_correct)
|
||||
convert_dhr_py.convert(screen, image, args)
|
||||
|
||||
|
||||
def convert_dhr_mono(args):
|
||||
screen = screen_py.DHGRScreen()
|
||||
image = prepare_image(args.input, args.show_input, screen, args.gamma_correct)
|
||||
convert_dhr_py.convert_mono(screen, image, args)
|
||||
|
||||
|
||||
def convert_shr(args):
|
||||
screen = screen_py.SHR320Screen()
|
||||
rgb = prepare_image(args.input, args.show_input, screen, args.gamma_correct)
|
||||
convert_shr_py.convert(screen, rgb, args)
|
||||
image = prepare_image(args.input, args.show_input, screen, args.gamma_correct)
|
||||
convert_shr_py.convert(screen, image, args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -10,7 +10,27 @@ import screen as screen_py
|
||||
import image as image_py
|
||||
|
||||
|
||||
def convert(screen: screen_py.DHGRScreen, rgb: np.ndarray, args):
|
||||
def _output(out_image: Image, args):
|
||||
if args.show_output:
|
||||
out_image.show()
|
||||
|
||||
if args.save_preview:
|
||||
# Save Double hi-res image
|
||||
outfile = os.path.join(
|
||||
os.path.splitext(args.output)[0] + "-preview.png")
|
||||
out_image.save(outfile, "PNG")
|
||||
|
||||
|
||||
def _write(screen: screen_py.DHGRScreen, bitmap: np.ndarray, args):
|
||||
screen.pack(bitmap)
|
||||
with open(args.output, "wb") as f:
|
||||
f.write(bytes(screen.aux))
|
||||
f.write(bytes(screen.main))
|
||||
|
||||
|
||||
def convert(screen: screen_py.DHGRNTSCScreen, image: Image, args):
|
||||
rgb = np.array(image).astype(np.float32) / 255
|
||||
|
||||
# Conversion matrix from RGB to CAM16UCS colour values. Indexed by
|
||||
# 24-bit RGB value
|
||||
base_dir = os.path.dirname(__file__)
|
||||
@ -24,7 +44,7 @@ def convert(screen: screen_py.DHGRScreen, rgb: np.ndarray, args):
|
||||
# Show output image by rendering in target palette
|
||||
output_palette_name = args.show_palette or args.palette
|
||||
output_palette = palette_py.PALETTES[output_palette_name]()
|
||||
output_screen = screen_py.DHGRScreen(output_palette)
|
||||
output_screen = screen_py.DHGRNTSCScreen(output_palette)
|
||||
if output_palette_name == "ntsc":
|
||||
output_srgb = output_screen.bitmap_to_image_ntsc(bitmap)
|
||||
else:
|
||||
@ -34,15 +54,15 @@ def convert(screen: screen_py.DHGRScreen, rgb: np.ndarray, args):
|
||||
Image.fromarray(output_srgb), screen.X_RES, screen.Y_RES * 2,
|
||||
srgb_output=True)
|
||||
|
||||
if args.show_output:
|
||||
out_image.show()
|
||||
_output(out_image, args)
|
||||
_write(screen, bitmap, args)
|
||||
|
||||
if args.save_preview:
|
||||
# Save Double hi-res image
|
||||
outfile = os.path.join(
|
||||
os.path.splitext(args.output)[0] + "-preview.png")
|
||||
out_image.save(outfile, "PNG")
|
||||
screen.pack(bitmap)
|
||||
with open(args.output, "wb") as f:
|
||||
f.write(bytes(screen.aux))
|
||||
f.write(bytes(screen.main))
|
||||
def convert_mono(screen: screen_py.DHGRScreen, image: Image, args):
|
||||
image = image.convert("1")
|
||||
|
||||
out_image = Image.fromarray((np.array(image) * 255).astype(np.uint8))
|
||||
out_image = image_py.resize(
|
||||
out_image, screen.X_RES, screen.Y_RES * 2, srgb_output=True)
|
||||
|
||||
_output(out_image, args)
|
||||
_write(screen, np.array(image).astype(bool), args)
|
||||
|
@ -362,7 +362,9 @@ class ClusterPalette:
|
||||
self._palette_lines[palette_idx] = [worst_line]
|
||||
|
||||
|
||||
def convert(screen, rgb: np.ndarray, args):
|
||||
def convert(screen, image: Image, args):
|
||||
rgb = np.array(image).astype(np.float32) / 255
|
||||
|
||||
# Conversion matrix from RGB to CAM16UCS colour values. Indexed by
|
||||
# 24-bit RGB value
|
||||
base_dir = os.path.dirname(__file__)
|
||||
|
@ -11,7 +11,7 @@ class DitherPattern:
|
||||
class NoDither(DitherPattern):
|
||||
"""No dithering."""
|
||||
PATTERN = np.array(((0, 0), (0, 0)),
|
||||
dtype=np.float32).reshape(2, 2) / np.float(16)
|
||||
dtype=np.float32).reshape(2, 2) / np.float32(16)
|
||||
ORIGIN = (0, 1)
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ class FloydSteinbergDither(DitherPattern):
|
||||
# 0 * 7
|
||||
# 3 5 1
|
||||
PATTERN = np.array(((0, 0, 7), (3, 5, 1)),
|
||||
dtype=np.float32).reshape(2, 3) / np.float(16)
|
||||
dtype=np.float32).reshape(2, 3) / np.float32(16)
|
||||
ORIGIN = (0, 1)
|
||||
|
||||
|
||||
@ -31,7 +31,7 @@ class FloydSteinbergDither2(DitherPattern):
|
||||
PATTERN = np.array(
|
||||
((0, 0, 0, 0, 0, 7),
|
||||
(3, 5, 1, 0, 0, 0)),
|
||||
dtype=np.float32).reshape(2, 6) / np.float(16)
|
||||
dtype=np.float32).reshape(2, 6) / np.float32(16)
|
||||
ORIGIN = (0, 2)
|
||||
|
||||
|
||||
|
@ -315,13 +315,15 @@ cdef (unsigned char)[:, ::1] _convert_cam16ucs_to_rgb12_iigs(float[:, ::1] point
|
||||
rgb = colour.convert(point_cam, "CAM16UCS", "RGB").astype(np.float32)
|
||||
|
||||
# TODO: precompute this conversion matrix since it's static. This accounts for about 10% of the CPU time here.
|
||||
rgb12_iigs = np.clip(
|
||||
# Convert to Rec.601 R'G'B'
|
||||
colour.YCbCr_to_RGB(
|
||||
# Gamma correct and convert Rec.709 R'G'B' to YCbCr
|
||||
colour.RGB_to_YCbCr(
|
||||
linear_to_srgb_array(rgb), K=colour.WEIGHTS_YCBCR['ITU-R BT.709']),
|
||||
K=colour.WEIGHTS_YCBCR['ITU-R BT.601']), 0, 1).astype(np.float32) * 15
|
||||
rgb12_iigs = np.ascontiguousarray(
|
||||
np.clip(
|
||||
# Convert to Rec.601 R'G'B'
|
||||
colour.YCbCr_to_RGB(
|
||||
# Gamma correct and convert Rec.709 R'G'B' to YCbCr
|
||||
colour.RGB_to_YCbCr(
|
||||
linear_to_srgb_array(rgb), K=colour.WEIGHTS_YCBCR['ITU-R BT.709']),
|
||||
K=colour.WEIGHTS_YCBCR['ITU-R BT.601']), 0, 1)
|
||||
).astype(np.float32) * 15
|
||||
return np.round(rgb12_iigs).astype(np.uint8)
|
||||
|
||||
# Wrapper around _convert_cam16ucs_to_rgb12_iigs to allow calling from python while retaining fast path for cython
|
||||
|
3
image.py
3
image.py
@ -45,3 +45,6 @@ def resize(
|
||||
np.uint8))
|
||||
else:
|
||||
return res
|
||||
|
||||
def resize_mono(image: Image, x_res, y_res) -> Image:
|
||||
return image.resize((x_res, y_res), Image.LANCZOS)
|
||||
|
@ -30,7 +30,8 @@ def main():
|
||||
rgb24_to_cam16ucs = colour.convert(all_rgb24, "RGB", "CAM16UCS").astype(
|
||||
np.float32)
|
||||
del all_rgb24
|
||||
np.save("data/rgb24_to_cam16ucs.npy", rgb24_to_cam16ucs)
|
||||
np.save("data/rgb24_to_cam16ucs.npy", np.ascontiguousarray(
|
||||
rgb24_to_cam16ucs))
|
||||
del rgb24_to_cam16ucs
|
||||
|
||||
print("Precomputing conversion matrix from 12-bit //gs RGB to CAM16UCS "
|
||||
@ -60,7 +61,8 @@ def main():
|
||||
rgb12_iigs_to_cam16ucs = colour.convert(
|
||||
rgb12_iigs, "RGB", "CAM16UCS").astype(np.float32)
|
||||
del rgb12_iigs
|
||||
np.save("data/rgb12_iigs_to_cam16ucs.npy", rgb12_iigs_to_cam16ucs)
|
||||
np.save("data/rgb12_iigs_to_cam16ucs.npy", np.ascontiguousarray(
|
||||
rgb12_iigs_to_cam16ucs))
|
||||
del rgb12_iigs_to_cam16ucs
|
||||
|
||||
|
||||
|
11
screen.py
11
screen.py
@ -71,10 +71,9 @@ class DHGRScreen:
|
||||
X_RES = 560
|
||||
Y_RES = 192
|
||||
|
||||
def __init__(self, palette: palette_py.Palette):
|
||||
def __init__(self):
|
||||
self.main = np.zeros(8192, dtype=np.uint8)
|
||||
self.aux = np.zeros(8192, dtype=np.uint8)
|
||||
self.palette = palette
|
||||
|
||||
@staticmethod
|
||||
def y_to_base_addr(y: int) -> int:
|
||||
@ -112,6 +111,11 @@ class DHGRScreen:
|
||||
self.main[addr:addr + 40] = main_col[y, :]
|
||||
return
|
||||
|
||||
class DHGRNTSCScreen(DHGRScreen):
|
||||
def __init__(self, palette: palette_py.Palette):
|
||||
self.palette = palette
|
||||
super(DHGRNTSCScreen, self).__init__()
|
||||
|
||||
def bitmap_to_image_rgb(self, bitmap: np.ndarray) -> np.ndarray:
|
||||
"""Convert our 2-bit bitmap image into a RGB image.
|
||||
|
||||
@ -161,7 +165,8 @@ class DHGRScreen:
|
||||
|
||||
# Apply effect of saturation
|
||||
yuv_to_rgb = np.array(
|
||||
((1, 0, 0), (0, saturation, 0), (0, 0, saturation)), dtype=np.float)
|
||||
((1, 0, 0), (0, saturation, 0), (0, 0, saturation)),
|
||||
dtype=np.float32)
|
||||
# Apply hue phase rotation
|
||||
yuv_to_rgb = np.matmul(np.array(
|
||||
((1, 0, 0), (0, np.cos(hue), np.sin(hue)), (0, -np.sin(hue),
|
||||
|
Loading…
x
Reference in New Issue
Block a user