|
- #!C:\odoobuild\WinPy64\python-3.12.3.amd64\python.exe
-
- # 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()
|