216 lines
6.5 KiB
Python
Executable File
216 lines
6.5 KiB
Python
Executable File
#!/home/mongar/Escritorio/pruebas_oc/venv/bin/python3
|
|
|
|
# priweavepng
|
|
# Weave selected channels from input PNG files into
|
|
# a multi-channel output PNG.
|
|
|
|
import collections
|
|
import re
|
|
|
|
from array import array
|
|
|
|
import png
|
|
|
|
"""
|
|
priweavepng file1.png [file2.png ...]
|
|
|
|
The `priweavepng` tool combines channels from the input images and
|
|
weaves a selection of those channels into an output image.
|
|
|
|
Conceptually an intermediate image is formed consisting of
|
|
all channels of all input images in the order given on the command line
|
|
and in the order of each channel in its image.
|
|
Then from 1 to 4 channels are selected and
|
|
an image is output with those channels.
|
|
The limit on the number of selected channels is
|
|
imposed by the PNG image format.
|
|
|
|
The `-c n` option selects channel `n`.
|
|
Further channels can be selected either by repeating the `-c` option,
|
|
or using a comma separated list.
|
|
For example `-c 3,2,1` will select channels 3, 2, and 1 in that order;
|
|
if the input is an RGB PNG, this will swop the Red and Blue channels.
|
|
The order is significant, the order in which the options are given is
|
|
the order of the output channels.
|
|
It is permissible, and sometimes useful
|
|
(for example, grey to colour expansion, see below),
|
|
to repeat the same channel.
|
|
|
|
If no `-c` option is used the default is
|
|
to select all of the input channels, up to the first 4.
|
|
|
|
`priweavepng` does not care about the meaning of the channels
|
|
and treats them as a matrix of values.
|
|
|
|
The numer of output channels determines the colour mode of the PNG file:
|
|
L (1-channel, Grey), LA (2-channel, Grey+Alpha),
|
|
RGB (3-channel, Red+Green+Blue), RGBA (4-channel, Red+Green+Blue+Alpha).
|
|
|
|
The `priweavepng` tool can be used for a variety of
|
|
channel building, swopping, and extraction effects:
|
|
|
|
Combine 3 grayscale images into RGB colour:
|
|
priweavepng grey1.png grey2.png grey3.png
|
|
|
|
Swop Red and Blue channels in colour image:
|
|
priweavepng -c 3 -c 2 -c 1 rgb.png
|
|
|
|
Extract Green channel as a greyscale image:
|
|
priweavepng -c 2 rgb.png
|
|
|
|
Convert a greyscale image to a colour image (all grey):
|
|
priweavepng -c 1 -c 1 -c 1 grey.png
|
|
|
|
Add alpha mask from a separate (greyscale) image:
|
|
priweavepng rgb.png grey.png
|
|
|
|
Extract alpha mask into a separate (greyscale) image:
|
|
priweavepng -c 4 rgba.png
|
|
|
|
Steal alpha mask from second file and add to first.
|
|
Note that the intermediate image in this example has 7 channels:
|
|
priweavepng -c 1 -c 2 -c 3 -c 7 rgb.png rgba.png
|
|
|
|
Take Green channel from 3 successive colour images to make a new RGB image:
|
|
priweavepng -c 2 -c 5 -c 8 rgb1.png rgb2.png rgb3.png
|
|
|
|
"""
|
|
|
|
Image = collections.namedtuple("Image", "rows info")
|
|
|
|
# For each channel in the intermediate raster,
|
|
# model:
|
|
# - image: the input image (0-based);
|
|
# - i: the channel index within that image (0-based);
|
|
# - bitdepth: the bitdepth of this channel.
|
|
Channel = collections.namedtuple("Channel", "image i bitdepth")
|
|
|
|
|
|
class Error(Exception):
|
|
pass
|
|
|
|
|
|
def weave(out, args):
|
|
"""Stack the input PNG files and extract channels
|
|
into a single output PNG.
|
|
"""
|
|
|
|
paths = args.input
|
|
|
|
if len(paths) < 1:
|
|
raise Error("Required input is missing.")
|
|
|
|
# List of Image instances
|
|
images = []
|
|
# Channel map. Maps from channel number (starting from 1)
|
|
# to an (image_index, channel_index) pair.
|
|
channel_map = dict()
|
|
channel = 1
|
|
|
|
for image_index, path in enumerate(paths):
|
|
inp = png.cli_open(path)
|
|
rows, info = png.Reader(file=inp).asDirect()[2:]
|
|
rows = list(rows)
|
|
image = Image(rows, info)
|
|
images.append(image)
|
|
# A later version of PyPNG may intelligently support
|
|
# PNG files with heterogenous bitdepths.
|
|
# For now, assumes bitdepth of all channels in image
|
|
# is the same.
|
|
channel_bitdepth = (image.info["bitdepth"],) * image.info["planes"]
|
|
for i in range(image.info["planes"]):
|
|
channel_map[channel + i] = Channel(image_index, i, channel_bitdepth[i])
|
|
channel += image.info["planes"]
|
|
|
|
assert channel - 1 == sum(image.info["planes"] for image in images)
|
|
|
|
# If no channels, select up to first 4 as default.
|
|
if not args.channel:
|
|
args.channel = range(1, channel)[:4]
|
|
|
|
out_channels = len(args.channel)
|
|
if not (0 < out_channels <= 4):
|
|
raise Error("Too many channels selected (must be 1 to 4)")
|
|
alpha = out_channels in (2, 4)
|
|
greyscale = out_channels in (1, 2)
|
|
|
|
bitdepth = tuple(image.info["bitdepth"] for image in images)
|
|
arraytype = "BH"[max(bitdepth) > 8]
|
|
|
|
size = [image.info["size"] for image in images]
|
|
# Currently, fail unless all images same size.
|
|
if len(set(size)) > 1:
|
|
raise NotImplementedError("Cannot cope when sizes differ - sorry!")
|
|
size = size[0]
|
|
|
|
# Values per row, of output image
|
|
vpr = out_channels * size[0]
|
|
|
|
def weave_row_iter():
|
|
"""
|
|
Yield each woven row in turn.
|
|
"""
|
|
# The zip call creates an iterator that yields
|
|
# a tuple with each element containing the next row
|
|
# for each of the input images.
|
|
for row_tuple in zip(*(image.rows for image in images)):
|
|
# output row
|
|
row = array(arraytype, [0] * vpr)
|
|
# for each output channel select correct input channel
|
|
for out_channel_i, selection in enumerate(args.channel):
|
|
channel = channel_map[selection]
|
|
# incoming row (make it an array)
|
|
irow = array(arraytype, row_tuple[channel.image])
|
|
n = images[channel.image].info["planes"]
|
|
row[out_channel_i::out_channels] = irow[channel.i :: n]
|
|
yield row
|
|
|
|
w = png.Writer(
|
|
size[0],
|
|
size[1],
|
|
greyscale=greyscale,
|
|
alpha=alpha,
|
|
bitdepth=bitdepth,
|
|
interlace=args.interlace,
|
|
)
|
|
w.write(out, weave_row_iter())
|
|
|
|
|
|
def comma_list(s):
|
|
"""
|
|
Type and return a list of integers.
|
|
"""
|
|
|
|
return [int(c) for c in re.findall(r"\d+", s)]
|
|
|
|
|
|
def main(argv=None):
|
|
import argparse
|
|
import itertools
|
|
import sys
|
|
|
|
if argv is None:
|
|
argv = sys.argv
|
|
argv = argv[1:]
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"-c",
|
|
"--channel",
|
|
action="append",
|
|
type=comma_list,
|
|
help="list of channels to extract",
|
|
)
|
|
parser.add_argument("--interlace", action="store_true", help="write interlaced PNG")
|
|
parser.add_argument("input", nargs="+")
|
|
args = parser.parse_args(argv)
|
|
|
|
if args.channel:
|
|
args.channel = list(itertools.chain(*args.channel))
|
|
|
|
return weave(png.binary_stdout(), args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|