#!/bin/sh # ppmuncomposite 0.0.1 (2004-Feb-12-Thu) # http://www.nicemice.net/amc/utils/#ppmuncomposite # Adam M. Costello # http://www.nicemice.net/amc/ # ppmuncomposite [ -raw ] # The first two files are inputs, the second two are outputs. The # first two contain images already composited onto black and white # backgrounds, respectively. The second two will contain the # reverse-engineered uncomposited (non-premultiplied) image and # alpha channel, respectively. The inputs must have the same width # and height. There is no guarantee that the two input files can # be reproduced by compositing the output onto black and white; # approximations may be necessary in general. But if the two input # files were created by compositing an RGBA image onto black and white # backgrounds, then this program will reconstruct that RGBA image, to # within roundoff error. # # If the -raw option is supplied, the inputs must be raw PPM files with # maxval = 65535 (0xFFFF), and the outputs will be likewise. No pre- # or post- processing will be done. The -raw option is appropriate # only if the RGB samples are proportional to light intensity (not # gamma-encoded). # # If the -raw option is not supplied, the inputs may have any # maxval, the outputs will have maxval = 255 (0xFF), and pre- and # post-processing will be performed. The inputs will be pre-processed # with pnmdepth 65535 | pnmgamma -ungamma -srgbramp, and the outputs # will be post-processed with pnmgamma -srgbramp | pnmdepth 255. This # is appropriate when the input is sRGB, and 8-bit sRGB output is # desired. # # If the default pre- and post-processing is not exactly what you want, # then apply your own custom pre- and post-processing before and after # calling ppmuncomposite -raw. # # This script needs either the compiled program uncomposite-raw to be in # $PATH, or it needs the source code uncomposite-raw.c to be in the same # directory as the script so that it can be compiled on-demand using cc. # # The uncompositing operation is performed on intensity samples, rather # than gamma-encoded samples, because the compositing operation is # properly performed on intensity samples, not gamma-encoded samples. # See the PNG specification section "Alpha channel processing". # Unfortunately, some software (including common web browsers) perform # the compositing operation directly on gamma-encoded samples, which # causes partially transparent pixels to be too dark, making drop # shadows darker than intended and creating thin dark halos around # antialiased edges. One could compensate for this error by designing # the alpha channel to be misused in this way (for example, one could # perform a -raw uncomposite operation on gamma-encoded samples), but # then after the broken display software gets fixed the partially # transparent pixels will be too light. I recommend following the specs # and encouraging others to do the same. # # Although I cite the PNG spec above, the fundamental reasons for # performing compositing operations on intensity samples, rather than # gamma-encoded samples, have nothing to do with PNG. Here is a # motivating example: Create an image where every odd line is black # and every even line is white, and create another image that is just # the opposite. Both will appear to be the same solid gray when viewed # from a sufficient distance. Now mix the two equally, by performing # a composite operation with a uniform alpha channel of 0.5. The # result, intuitively, should be truly solid gray, the same gray that # the original images appeared to be. If the compositing operation was # performed on intensity samples, that will indeed be the result, but if # the compositing was performed on gamma-encoded samples, the mixture # will look darker than the original images. # # By the way, similar arguments imply that all operations that mix # pixels (including all scaling and filtering operations) should be # performed on intensity samples, not gamma-encoded samples. pnmgamma_opts=-srgbramp #pnmgamma_opts=-cieramp #pnmgamma_opts=2.2 # -srgbramp is the right choice for sRGB images (which are the default # for the web), but it's a fairly new option to pnmgamma, and might not # be available if your pnmgamma is old. tmpdir=/tmp/ppmuncomposite-$$ cleanup() { rm -rf $tmpdir } fail() { echo "$*" >&2 cleanup exit 1 } usage=" usage: $0 [ -raw ] " # Parse the command-line options. raw=no while :; do case "$1" in -raw) raw=yes; shift ;; -*) fail "unknown option $1$usage" ;; *) break ;; esac done [ $# -eq 4 ] || fail "need four file arguments$usage" onblack=$1 onwhite=$2 uncomp=$3 alpha=$4 # Make temporary directory. mkdir $tmpdir || fail "could not mkdir $tmpdir" # The uncompositing will be done by a C program, because the netpbm # utilities are not quite up to the task (in particular, pnmarith has no # -divide option). We'll try to use the compiled program if we can find # it, otherwise we'll try to find and compile the source. if type uncomposite-raw > /dev/null 2>&1; then uncomposite_raw=uncomposite-raw else uncomposite_raw=$tmpdir/uncomposite-raw source=`dirname "$0"`/uncomposite-raw.c [ -f "$source" ] || fail "file not found: $source" cc -o "$uncomposite_raw" "$source" fi # The C program inputs and outputs raw (headerless) image data, so we # need to strip off the headers. # ppmsplit # # Read a PPM file from stdin and writes its header to
and its # data to . Also verifies that it meets the restrictions for # -raw, otherwise it aborts and returns unsuccessfully. ppmsplit() { header_out=$1 data_out=$2 read line header=$line set -- $line [ "$1" = P6 ] || return 1 shift wordcount= while :; do while [ $# -ge 1 ]; do case "$1" in '#'*) break ;; esac lastword=$1 shift wordcount="x$wordcount" done case "$wordcount" in xxx) break ;; xxx*) return 1 ;; esac read line header="$header $line" set -- $line done [ "$lastword" = 65535 ] || return 1 printf '%s\n' "$header" > "$header_out" cat > "$data_out" } # Strip off the headers (after preprocessing unless -raw was supplied). # The stripped-off header (it should be the same for both inputs) is # reused for the outputs, except that P6 becomes P5 for . if [ $raw = yes ]; then ppmsplit "$uncomp" $tmpdir/onblack.data < "$onblack" || fail "$onblack does not meet the restrictions for -raw" ppmsplit /dev/null $tmpdir/onwhite.data < "$onwhite" || fail "$onwhite does not meet the restrictions for -raw" uncomp_header=$uncomp alpha_header=$alpha else pnmdepth 65535 < "$onblack" | pnmgamma -ungamma $pnmgamma_opts | ppmsplit $tmpdir/uncomp.header $tmpdir/onblack.data pnmdepth 65535 < "$onwhite" | pnmgamma -ungamma $pnmgamma_opts | ppmsplit /dev/null $tmpdir/onwhite.data uncomp_header=$tmpdir/uncomp.header alpha_header=$tmpdir/alpha.header fi printf P5 > "$alpha_header" tail -c +3 < "$uncomp_header" >> "$alpha_header" # Uncomposite. "$uncomposite_raw" $tmpdir/onblack.data $tmpdir/onwhite.data \ $tmpdir/uncomp.data $tmpdir/alpha.data # Merge the raw outputs with their headers. if [ $raw = yes ]; then cat $tmpdir/uncomp.data >> "$uncomp" cat $tmpdir/alpha.data >> "$alpha" else cat $tmpdir/uncomp.header $tmpdir/uncomp.data | pnmgamma $pnmgamma_opts | pnmdepth 255 > "$uncomp" cat $tmpdir/alpha.header $tmpdir/alpha.data | pnmdepth 255 > "$alpha" fi # Clean up. cleanup