import os import png import pprint pp = pprint.PrettyPrinter(indent=4) IMAGE_CACHE = {} class Color: def __init__(self, r: int, g: int, b: int, a=255): self.r = max(0, min(255, r)) self.g = max(0, min(255, g)) self.b = max(0, min(255, b)) self.a = max(0, min(255, a)) def __str__(self): return "RGBA(%s, %s, %s, %s)" % (self.r, self.g, self.b, self.a) def __repr__(self): return self.__str__() def __eq__(self, other): return self.r == other.r and self.g == other.g and self.b == other.b and self.a == other.a def __ne__(self, other): return not self == other def __hash__(self): return self.r * 0x1000000 + self.g * 0x10000 + self.b * 0x100 + self.a def is_transparent(self) -> bool: return self.a == 0 def is_opaque(self) -> bool: return self.a == 255 def is_black(self) -> bool: return self.r == 0 and self.g == 0 and self.b == 0 def is_white(self) -> bool: return self.r == 255 and self.g == 255 and self.b == 255 def is_gray(self) -> bool: return self.r == 128 and self.g == 128 and self.b == 128 def is_red(self) -> bool: return self.r == 255 and self.g == 0 and self.b == 0 def is_green(self) -> bool: return self.r == 0 and self.g == 255 and self.b == 0 def is_blue(self) -> bool: return self.r == 0 and self.g == 0 and self.b == 255 def is_yellow(self) -> bool: return self.r == 255 and self.g == 255 and self.b == 0 def is_cyan(self) -> bool: return self.r == 0 and self.g == 255 and self.b == 255 def is_magenta(self) -> bool: return self.r == 255 and self.g == 0 and self.b == 255 @staticmethod def red(): return Color(255, 0, 0, 255) @staticmethod def green(): return Color(0, 255, 0, 255) @staticmethod def blue(): return Color(0, 0, 255, 255) @staticmethod def magenta(): return Color(255, 0, 255, 255) @staticmethod def yellow(): return Color(255, 255, 0, 255) @staticmethod def cyan(): return Color(0, 255, 255, 255) @staticmethod def white(): return Color(255, 255, 255, 255) @staticmethod def black(): return Color(0, 0, 0, 255) @staticmethod def gray(): return Color(128, 128, 128, 255) @staticmethod def clear(): return Color(0, 0, 0, 0) class Image: def __init__(self, width: int, height: int): self.width = width self.height = height self.data = [[Color(0, 0, 0) for _ in range(width)] for _ in range(height)] def __str__(self): return "Image(%s, %s)" % (self.width, self.height) def __repr__(self): return self.__str__() def get(self, x: int, y: int) -> Color: return self.data[y][x] def set(self, x: int, y: int, color: Color) -> 'Image': if type(color) != Color: raise ValueError('color is not of type Color') self.data[y][x] = color return self def cut_out(self, offset_x: int, offset_y: int, width: int, height: int) -> 'Image': if offset_x < 0 or offset_x >= self.width: raise ValueError("start_x is out of bounds") if offset_y < 0 or offset_y >= self.height: raise ValueError("start_y is out of bounds") result = Image(width, height) for y in range(height): for x in range(width): result.set(x, y, self.get(x + offset_x, y + offset_y)) return result def set_range(self, offset_x: int, offset_y: int, image: 'Image', operation=lambda x, y: y) -> 'Image': if offset_x < 0 or offset_x >= self.width: raise ValueError("start_x is out of bounds") if offset_y < 0 or offset_y >= self.height: raise ValueError("start_y is out of bounds") for y in range(image.height): for x in range(image.width): self.set(x + offset_x, y + offset_y, operation(self.get(x, y), image.get(x, y))) return self def fill(self, color: Color) -> 'Image': for y in range(self.height): for x in range(self.width): self.set(x, y, color) return self def flip_horizontal(self) -> 'Image': image = Image(self.width, self.height) for x in range(self.width): for y in range(self.height): image.set(x, y, self.get(self.width - x - 1, y)) self.data = image.data return self def image_from_file(filename: str) -> Image: if filename in IMAGE_CACHE: print("found in cache!") return IMAGE_CACHE[filename] print("not found in cache; parsing...") image_data = png.Reader(os.path.abspath(filename)).read() image_width = image_data[0] image_height = image_data[1] print("read image with size (%s, %s)" % (image_width, image_height)) pixel_data = list(image_data[2]) result = Image(image_width, image_height) if image_data[3]["alpha"]: for y in range(image_height): for x in range(image_width): color = Color(pixel_data[y][4 * x], pixel_data[y][4 * x + 1], pixel_data[y][4 * x + 2], pixel_data[y][4 * x + 3]) result.set(x, y, color) else: for y in range(image_height): for x in range(image_width): color = Color(pixel_data[y][3 * x], pixel_data[y][3 * x + 1], pixel_data[y][3 * x + 2]) result.set(x, y, color) IMAGE_CACHE[filename] = result return result def write_image_to_file(filename: str, image: Image) -> None: flat_data = [] for y in range(image.height): for x in range(image.width): pixel = image.get(x, y) flat_data.append(pixel.r) flat_data.append(pixel.g) flat_data.append(pixel.b) flat_data.append(pixel.a) file = open(filename, 'wb') writer = png.Writer(image.width, image.height, alpha=True) boxed_data = writer.array_scanlines(flat_data) writer.write(file, boxed_data) file.close() image_from_file("image/tinctures/metals.png")