def pngquant_bts(img, ncolors=8): img = conv2png(img) img = Image.open(BytesIO(img)).convert('RGBA') w, h = img.width, img.height bytes = img.tobytes() attr = liq.Attr() attr.max_colors = ncolors img = attr.create_rgba(bytes, w, h, 0) res = img.quantize(attr) res.dithering_level = 1.0 bytes = res.remap_image(img) palette = res.get_palette() img = Image.frombytes('P', (w, h), bytes) palette_data = [] for color in palette: palette_data.append(color.r) palette_data.append(color.g) palette_data.append(color.b) img.putpalette(palette_data) bio = BytesIO() img.save(bio, 'PNG', optimize=True) return bio.getvalue()
def test_against(image_filename, points): q_in = QtGui.QImage(image_filename) for pos, expected in points: qcolor = q_in.pixelColor(*pos) assert (qcolor.red(), qcolor.green(), qcolor.blue(), qcolor.alpha()) == expected attr = liq.Attr() img = liq_Qt.to_liq(q_in, attr) result = img.quantize(attr) q_out = liq_Qt.from_liq(result, img) assert q_out.format() == QtGui.QImage.Format.Format_Indexed8 q_out_rgba = q_out.convertToFormat(QtGui.QImage.Format.Format_ARGB32) for pos, expected in points: qcolor = q_out_rgba.pixelColor(*pos) r, g, b, a = qcolor.red(), qcolor.green(), qcolor.blue( ), qcolor.alpha() print(pos, expected, (r, g, b, a)) assert near(r, expected[0]) assert near(g, expected[1]) assert near(b, expected[2]) assert near(a, expected[3])
def test_bitmap_not_available_error(): """ Trigger LIQ_BITMAP_NOT_AVAILABLE and ensure that liq.BitmapNotAvailableError is raised """ attr = liq.Attr() hist = liq.Histogram(attr) with pytest.raises(liq.BitmapNotAvailableError): result = hist.quantize(attr)
def image_callback(value, image): # Load the background as an Image width, height, input_pixels = utils.load_test_image(value) attr = liq.Attr() background = attr.create_rgba(input_pixels, width, height, 0) backgrounds.append(background) # Test both the getter and setter methods image.background = background with pytest.raises(AttributeError): image.background
def test_attr_copy(): """ Test Attr.copy() """ width, height, input_pixels = utils.load_test_image('flower') attr = liq.Attr() attr.max_colors = 88 attr.min_posterization = 3 attr.min_quality = 55 attr2 = attr.copy() assert attr2.max_colors == 88 assert attr2.min_posterization == 3 assert attr2.min_quality == 55
def convertAllToEnpg(imgs): """ Palette-reduce the two images to 256-colors (both with the same palette), save them as PNGs, and save them as ENPGs Currently assumes both images are 256x256. """ n = len(imgs) # Combine into one PIL image comb = PIL.Image.new('RGBA', (256 * n, 256), (0, 0, 0, 0)) for i, img in enumerate(imgs): comb.paste(qImageToPilImage(img), (256 * i, 0)) # Quantize # combQ = comb.quantize(255, 3) # leave one color for transparent attr = liq.Attr() attr.max_colors = 255 # leave one color for transparent comb_liq = libimagequant_integrations.PIL.to_liq(comb, attr) combQ = libimagequant_integrations.PIL.from_liq(comb_liq.quantize(attr), comb_liq) # Create the ENPG data arrays ENPG_LEN = 256 * 256 + 256 * 2 enpgs = [bytearray(ENPG_LEN) for i in range(n)] # Convert the palette to RGB555 and put it in both the enpgs pal888 = combQ.getpalette()[:255 * 3] for enpg in enpgs: struct.pack_into('<H', enpg, 256**2, 0x8000) shrink = lambda c: min((c + 4) >> 3, 0x1F) & 0x1F for i, (r, g, b) in enumerate(grouper(pal888, 3)): rgb = shrink(b) << 10 | shrink(g) << 5 | shrink(r) for enpg in enpgs: struct.pack_into('<H', enpg, 256**2 + 2 * i + 2, rgb) # Put the color indices in the enpgs for i, enpg in enumerate(enpgs): for y in range(256): for x in range(256): alpha = comb.getpixel((256 * i + x, y))[3] if alpha < 255: col = 0 else: col = combQ.getpixel((256 * i + x, y)) + 1 enpg[y * 256 + x] = col return enpgs
def test_unsupported_error(): """ Trigger LIQ_UNSUPPORTED and ensure that liq.UnsupportedError is raised """ # A simple way of getting LIQ_UNSUPPORTED is adding more than 256 # fixed colors to a histogram attr = liq.Attr() hist = liq.Histogram(attr) for i in range(256): hist.add_fixed_color(liq.Color(i, 0, 0, 255), 0) with pytest.raises(liq.UnsupportedError): hist.add_fixed_color(liq.Color(255, 255, 0, 255), 0)
def test_buffer_too_small_error(): """ Trigger LIQ_BUFFER_TOO_SMALL and ensure that liq.BufferTooSmallError is raised """ # Load the background as an Image width, height, input_pixels = utils.load_test_image('test-card') attr = liq.Attr() background = attr.create_rgba(input_pixels, width, height, 0) def image_callback(value, image): # The image is too large, so using it as a background should fail with pytest.raises(liq.BufferTooSmallError): image.background = background utils.try_multiple_values('alpha-gradient', [None], image_callback=image_callback)
def main(argv): if len(argv) < 2: print('Please specify a path to a PNG file', file=sys.stderr) return 1 input_png_file_path = argv[1] # Load PNG file and decode it as raw RGBA pixels # This uses the Pillow library for PNG reading (not part of libimagequant) img = PIL.Image.open(input_png_file_path).convert('RGBA') width = img.width height = img.height input_rgba_pixels = img.tobytes() # Use libimagequant to make a palette for the RGBA pixels attr = liq.Attr() input_image = attr.create_rgba(input_rgba_pixels, width, height, 0) result = input_image.quantize(attr) # Use libimagequant to make new image pixels from the palette result.dithering_level = 1.0 raw_8bit_pixels = result.remap_image(input_image) palette = result.get_palette() # Save converted pixels as a PNG file # This uses the Pillow library for PNG writing (not part of libimagequant) img = PIL.Image.frombytes('P', (width, height), raw_8bit_pixels) palette_data = [] for color in palette: palette_data.append(color.r) palette_data.append(color.g) palette_data.append(color.b) img.putpalette(palette_data) output_png_file_path = 'quantized_example.png' img.save(output_png_file_path) print('Written ' + output_png_file_path)
def test_histogram_add_fixed_color(): """ Test Histogram.add_fixed_color """ width_A, height_A, input_pixels_A = utils.load_test_image('flower') width_B, height_B, input_pixels_B = utils.load_test_image('flower-huechange-1') assert width_A == width_B width, height = width_A, height_A attr = liq.Attr() hist = liq.Histogram(attr) image_A = attr.create_rgba(input_pixels_A, width, height, 0) hist.add_image(attr, image_A) image_B = attr.create_rgba(input_pixels_B, width, height, 0) hist.add_image(attr, image_B) FIXED_COLORS = [ liq.Color(255, 0, 0, 255), # red liq.Color(0, 0, 255, 255), # blue liq.Color(128, 0, 128, 255), # purple liq.Color(255, 255, 0, 255), # yellow ] for fixedColor in FIXED_COLORS: hist.add_fixed_color(fixedColor, 0) result = hist.quantize(attr) result_palette = result.get_palette() # Check that we have a decently-sized palette assert len(result_palette) > 128 # Try remapping both images -- make sure we don't get an exception # or something result.remap_image(image_A) result.remap_image(image_B) # Check that the fixed colors are present in the output palette for fixedColor in FIXED_COLORS: assert fixedColor in result_palette
def try_multiple_values(img, values, *, attr_callback=None, image_callback=None, result_callback=None, allow_exceptions=False): """ Helper function that lets you easily perform multiple quantizations and ensure that all of the outputs are different. Use attr_callback(value, attr) to modify the Attr object, and use result_callback(value, result) to modify the Result object. """ width, height, input_pixels = load_test_image(img) tuples = [] for value in values: attr = input_image = result = exception = None try: attr = liq.Attr() if attr_callback is not None: attr_callback(value, attr) input_image = attr.create_rgba(input_pixels, width, height, 0) if image_callback is not None: image_callback(value, input_image) result = input_image.quantize(attr) if result_callback is not None: result_callback(value, result) except Exception as e: if allow_exceptions: exception = e else: raise tuples.append((attr, input_image, result, exception)) return tuples
def test_against(image_filename, points): png_in_check = png.Reader(filename=image_filename) width, height, data, info = png_in_check.read_flat() for pos, expected in points: start = (pos[1] * width + pos[0]) * 4 assert data[start + 0] == expected[0] assert data[start + 1] == expected[1] assert data[start + 2] == expected[2] assert data[start + 3] == expected[3] attr = liq.Attr() png_in = png.Reader(filename=image_filename) img = liq_png.to_liq(png_in, attr) result = img.quantize(attr) png_out, data = liq_png.from_liq(result, img) temp_output_buffer = io.BytesIO() png_out.write_array(temp_output_buffer, data) temp_output_buffer.seek(0) rereader_check = png.Reader(file=temp_output_buffer) _, _, _, info = rereader_check.read() assert info.get('palette') temp_output_buffer.seek(0) rereader = png.Reader(file=temp_output_buffer) _, _, data, _ = rereader.asRGBA() data = list(data) # so we can index into it for (x, y), expected in points: r, g, b, a = data[y][x * 4:x * 4 + 4] print((x, y), expected, (r, g, b, a)) assert near(r, expected[0]) assert near(g, expected[1]) assert near(b, expected[2]) assert near(a, expected[3])
def test_against(image_filename, points): sk_in = skimage.io.imread(image_filename) assert sk_in.shape[2] == 4 for (x, y), expected in points: assert np.array_equal(sk_in[y, x], np.array(expected)) attr = liq.Attr() img = liq_skimage.to_liq(sk_in, attr) result = img.quantize(attr) sk_out_px, sk_out_pal = liq_skimage.from_liq(result, img) assert sk_out_px.shape[2] == 1 assert sk_out_pal.shape[1] == 4 for (x, y), expected in points: r, g, b, a = sk_out_pal[sk_out_px[y, x][0]] print((x, y), expected, (r, g, b, a)) assert near(r, expected[0]) assert near(g, expected[1]) assert near(b, expected[2]) assert near(a, expected[3])
def test_against(image_filename, points): pil_in = PIL.Image.open(image_filename) for pos, expected in points: assert pil_in.getpixel(pos) == expected attr = liq.Attr() img = liq_PIL.to_liq(pil_in, attr) result = img.quantize(attr) pil_out = liq_PIL.from_liq(result, img) assert pil_out.mode == 'P' pil_out_rgba = pil_out.convert('RGBA') for pos, expected in points: r, g, b, a = pil_out_rgba.getpixel(pos) print(pos, expected, (r, g, b, a)) assert near(r, expected[0]) assert near(g, expected[1]) assert near(b, expected[2]) assert near(a, expected[3])
def test_histogram_basic(): """ Basic test of Histogram: - __init__() - add_image() - quantize() """ # Testing with three input images width_A, height_A, input_pixels_A = utils.load_test_image('flower') width_B, height_B, input_pixels_B = utils.load_test_image('flower-huechange-1') width_C, height_C, input_pixels_C = utils.load_test_image('flower-huechange-2') assert width_A == width_B == width_C assert height_A == height_B == height_C width, height = width_A, height_A attr = liq.Attr() hist = liq.Histogram(attr) image_A = attr.create_rgba(input_pixels_A, width, height, 0) hist.add_image(attr, image_A) image_B = attr.create_rgba(input_pixels_B, width, height, 0) hist.add_image(attr, image_B) image_C = attr.create_rgba(input_pixels_C, width, height, 0) hist.add_image(attr, image_C) result = hist.quantize(attr) # Check that we have a decently-sized palette assert len(result.get_palette()) > 128 # Try remapping all three images -- make sure we don't get an # exception or something result.remap_image(image_A) result.remap_image(image_B) result.remap_image(image_C)
def saveImagePair(img1, img2, fn1, fn2, rom, firstFileID): """ Palette-reduce the two images to 256-colors (both with the same palette), save them as PNGs, and save them as ENPGs Currently assumes both images are 256x256. """ # Temp img1.save('out-png/' + fn1 + '.png') img2.save('out-png/' + fn2 + '.png') # Convert both to PIL Images pimg1, pimg2 = map(qImageToPilImage, [img1, img2]) # Combine comb = PIL.Image.new('RGBA', (512, 256), (0, 0, 0, 0)) comb.paste(pimg1, (0, 0)) comb.paste(pimg2, (256, 0)) # Quantize # combQ = comb.quantize(255, 3) # leave one color for transparent attr = liq.Attr() attr.max_colors = 255 # leave one color for transparent comb_liq = libimagequant_integrations.PIL.to_liq(comb, attr) combQ = libimagequant_integrations.PIL.from_liq(comb_liq.quantize(attr), comb_liq) # Create the ENPG data arrays ENPG_LEN = 256 * 256 + 256 * 2 enpg1, enpg2 = bytearray(ENPG_LEN), bytearray(ENPG_LEN) # Convert the palette to RGB555 and put it in both the enpgs pal888 = combQ.getpalette()[:255*3] struct.pack_into('<H', enpg1, 256**2, 0x8000) struct.pack_into('<H', enpg2, 256**2, 0x8000) shrink = lambda c: min((c + 4) >> 3, 0x1F) & 0x1F for i, (r, g, b) in enumerate(grouper(pal888, 3)): rgb = shrink(b) << 10 | shrink(g) << 5 | shrink(r) struct.pack_into('<H', enpg1, 256**2 + 2 * i + 2, rgb) struct.pack_into('<H', enpg2, 256**2 + 2 * i + 2, rgb) # Put the color indices in the enpgs for y in range(256): for x in range(256): alpha = comb.getpixel((x, y))[3] if alpha < 255: col = 0 else: col = combQ.getpixel((x, y)) + 1 enpg1[y * 256 + x] = col for y in range(256): for x in range(256): alpha = comb.getpixel((x + 256, y))[3] if alpha < 255: col = 0 else: col = combQ.getpixel((x + 256, y)) + 1 enpg2[y * 256 + x] = col # Compress them enpg1Compressed = ndspy.lz10.compress(enpg1) enpg2Compressed = ndspy.lz10.compress(enpg2) # Save them with open('out-enpg/' + fn1 + '.enpg', 'wb') as f: f.write(enpg1) with open('out-enpg/' + fn2 + '.enpg', 'wb') as f: f.write(enpg2) with open('out-enpg-lz/' + fn1 + '.enpg', 'wb') as f: f.write(enpg1Compressed) with open('out-enpg-lz/' + fn2 + '.enpg', 'wb') as f: f.write(enpg2Compressed) # Insert them into the rom rom.files[firstFileID] = enpg1Compressed rom.files[firstFileID + 1] = enpg2Compressed rom.filenames['zc_crsin'].files[firstFileID - rom.filenames['zc_crsin'].firstID] = f'{fn1}.enpg' rom.filenames['zc_crsin'].files[firstFileID - rom.filenames['zc_crsin'].firstID + 1] = f'{fn2}.enpg' # Render them as PNGs and save them elsewhere (for quality inspection) enpgPng1, enpgPng2 = map(enpgToImage, [enpg1, enpg2]) enpgPng1.save('out-enpg-png/' + fn1 + '.png') enpgPng2.save('out-enpg-png/' + fn2 + '.png')
def test_histogram_add_colors(): """ Test Histogram.add_colors(), as well as the HistogramEntry class """ # First, quantize flower-huechange-1.jpg on its own width, height, input_pixels = utils.load_test_image('flower-huechange-1') attr = liq.Attr() other_image = attr.create_rgba(input_pixels, width, height, 0) result = other_image.quantize(attr) result_pixels = result.remap_image(other_image) result_palette = result.get_palette() # ~ # Create a list of HistogramEntrys for it entries = [] for i, color in enumerate(result_palette): count = result_pixels.count(i) entry = liq.HistogramEntry(color, count) entries.append(entry) # Test HistogramEntry getters assert entry.color == color assert entry.count == count # Test setters entry.color = result_palette[0] entry.count = 50 assert entry.color == result_palette[0] assert entry.count == 50 # (Set properties back to what they should be) entry.color = color entry.count = count assert entries # ~ # Set up a Histogram attr = liq.Attr() hist = liq.Histogram(attr) # Add flower.jpg as an image width, height, input_pixels = utils.load_test_image('flower') image = attr.create_rgba(input_pixels, width, height, 0) hist.add_image(attr, image) # Add the HistogramEntrys for flower-huechange-1.jpg hist.add_colors(attr, entries, 0) # And quantize result = hist.quantize(attr) # ~ # Check that we have a decently-sized palette assert len(result.get_palette()) > 128 # Try remapping both images -- make sure we don't get an exception # or something result.remap_image(image) result.remap_image(other_image)