blob: ac7c917c7d4675d9aba44a51da23d9651aecbc61 [file] [log] [blame]
#!/usr/bin/env python
"""
pipcolours - extract all colours present in source image,
producing a PNG that has each colour exactly once.
Also performs grey and alpha reduction when possible:
if all colour pixels are grey, output PNG is grey;
if all pixels are opaque, output PNG has no alpha channel.
"""
import argparse
import collections
import itertools
import png
def png_colours(out, inp):
r = png.Reader(file=inp)
_, _, rows, info = r.asDirect()
rows, info = unique_colours(rows, info)
writer = png.Writer(**info)
writer.write(out, rows)
def jasc_counts(out, inp):
r = png.Reader(file=inp)
_, _, rows, info = r.asDirect()
print("JASC-PAL", file=out)
# A version field? (the internet is unclear)
print("0100", file=out)
colour_dict = count_colours(rows, info)
items = sorted(colour_dict.items())
print(len(items), file=out)
for colour, count in items:
print(*itertools.chain(colour, ["#", count]), file=out)
def jasc_colours(out, inp):
r = png.Reader(file=inp)
_, _, rows, info = r.read()
print("JASC-PAL", file=out)
# A version field? (the internet is unclear)
print("0100", file=out)
if "palette" in info:
print(len(info["palette"]), file=out)
for pixel in info["palette"]:
print(*pixel, file=out)
return
rows, info = unique_colours(rows, info)
planes = info["planes"]
bitdepth = info["bitdepth"]
(row,) = rows
n = len(row) // planes
print(n, file=out)
for pixel in png.group(row, planes):
print(*pixel, file=out)
def count_colours(rows, info):
planes = info["planes"]
bitdepth = info["bitdepth"]
count = collections.Counter()
for row in rows:
for pixel in png.group(row, planes):
count[pixel] += 1
return count
def unique_colours(rows, info):
planes = info["planes"]
bitdepth = info["bitdepth"]
col = set()
for row in rows:
col = col.union(png.group(row, planes))
col, planes = channel_reduce(col, planes, bitdepth)
col = sorted(col)
width = len(col)
info = dict(
width=width,
height=1,
bitdepth=bitdepth,
greyscale=planes in (1, 2),
alpha=planes in (2, 4),
planes=planes,
)
row = list(itertools.chain(*col))
return [row], info
def channel_reduce(col, planes, bitdepth):
"""Attempt to reduce the number of channels in the set of colours."""
col, planes = reduce_grey(col, planes)
col, planes = reduce_alpha(col, planes, bitdepth)
return col, planes
def reduce_grey(col, planes):
"""
Reduce a colour image to grey if
all intensities match in all pixels.
"""
if planes >= 3:
def isgrey(c):
return c[0] == c[1] == c[2]
if all(isgrey(c) for c in col):
# Every colour is grey, convert to 1- or 2-tuples.
col = set(x[0::3] for x in col)
planes -= 2
return col, planes
def reduce_alpha(col, planes, bitdepth):
"""
Remove alpha channel if all pixels are fully opaque.
"""
maxval = 2 ** bitdepth - 1
if planes in (2, 4):
def isopaque(c):
return c[-1] == maxval
if all(isopaque(c) for c in col):
# Every pixel is opaque, remove alpha channel.
col = set(x[:-1] for x in col)
planes -= 1
return col, planes
def main(argv=None):
import sys
if argv is None:
argv = sys.argv
argv = argv[1:]
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(description=__doc__)
version = "%(prog)s " + png.__version__
parser.add_argument("--version", action="version", version=version)
parser.add_argument(
"--jasc", action="store_true", help="output in JASC format (plain text)"
)
parser.add_argument("--count", action="store_true")
parser.add_argument(
"input", nargs="?", default="-", type=png.cli_open, metavar="PNG"
)
args = parser.parse_args(argv)
if args.count:
return jasc_counts(sys.stdout, args.input)
if args.jasc:
return jasc_colours(sys.stdout, args.input)
out = png.binary_stdout()
return png_colours(out, args.input)
if __name__ == "__main__":
main()