In the early nineties the Portable Video Research Group (PVRG) at
Stanford created an alternative
to the now common (on unix) Independent
JPEG Group (IJG) cjpeg
/djpeg
JPEG file format (JFIF) compression and decompression tools. The PVRG
called their project merely 'JPEG' and put source up on a FTP server
that no longer exists. Those two factors combined have led to the PVRG
code being very hard to locate, which is a shame since it is good
stuff. I like to use it along with the excellent
NetPBM tools. Those are
a set of programs to manipulate the portable image map files:
PBM, portable bitmap (one bit per pixel);
PGM, portable graymap (two to sixteen bits per pixel);
PPM, portable pixmap (one to sixteen bits per channel per pixel, three channels); and
PAM, portable anymap (one to sixteen bits per channel per pixel, any number of channels).
I found the code a few years ago at a Norwegian college, but it has since been locked up or removed from there (the pages return "401 Forbidden" errors). Recently, I found a new source, this time at an archive site called "Link Everything Online" (LEO) run by a German institution, the Informatik der Technischen Universität München. Additionally, the unmodfied PVRG files are available here, but I have less bandwidth than LEO, so try them first.
I've made some small modifications to the 1.2.1 version to make
it a bit more friendly for my use. Chiefly, I added command line options
to specify the basename of output files in decode mode and to specify
PGM formatted output files. There are a few other very minor changes.
Here are the diffs: pvrg-jpeg-etb.patch.gz.
If you follow my examples, I'll assume you are using my version. Everything
I do can be done with the originals, but you might need a
rawtopgm
step, and the filenames will be different.
Too often today people forget that farms are the source of their food. So, too, Unix newcomers forget that code is the source of their programs. This program is not available as an RPM or in a ports directory. You must deal with the code.
The steps, roughly:
gunzip < JPEGv1.2.1.tar.gz | tar xf -
gunzip < pvrg-jpeg-etb.patch.gz | patch -p0
cd jpegdir
make
If something goes wrong, you fiddle with the source.
Some possible problems:
lex
(or flex
) build it again. If you
are using flex
, the lexer.l file will likely cause an
error, though. Add a line containing '%option yylineno
' just
before the '%{
' line.
lex
, that program might object to my patch
to lexer.l, which makes the file flex
compatible. Delete
the '%option yylineno
' line and try again.
Well, if you have three separate files with Y, Cr, and Cb data you can make a regular JPEG. That's a bit boring, seeing how so many other programs can make JPEGs and are less picky about input formats. I mostly like it for disassembly of existing JPEGs. It is interesting to see the component parts. Here are the commands and output I used for the two example images I'm using:
$ jpeg -d -g -s bo-w448-h576-norm.jpg -o bo-parts > GlobalWidth:448 GlobalHeight:576 ResyncInverval:0 >> C: 1 N: bo-parts.1.pgm W: 448 H: 576 hf:2 vf:2 >> C: 2 N: bo-parts.2.pgm W: 224 H: 288 hf:1 vf:1 >> C: 3 N: bo-parts.3.pgm W: 224 H: 288 hf:1 vf:1 $ jpeg -d -g -s pineapple-w448-h576-norm.jpg -o pineapple-parts > GlobalWidth:448 GlobalHeight:576 ResyncInverval:0 >> C: 1 N: pineapple-parts.1.pgm W: 448 H: 576 hf:2 vf:2 >> C: 2 N: pineapple-parts.2.pgm W: 224 H: 288 hf:1 vf:1 >> C: 3 N: pineapple-parts.3.pgm W: 224 H: 288 hf:1 vf:1 $
Here is a thumbnail table of the images. The PGM files are presented in GIF format. The RGB channels of the originals are also presented for comparision. Click on any image to see the full size version.
normal | Y | Cr | Cb | R | G | B | |
---|---|---|---|---|---|---|---|
bo | |||||||
pineapple |
Look at that. Quarter size (half width, half height) images for the
chroma channels. And while the luminence channel looks a lot like a black
and white version of the image, the chroma channels look much different.
What happens if we swap the two chroma channels? Since jpeg
reads in raw files, not PGM I'll re-split it.
$ jpeg -d -s bo-w448-h576-norm.jpg -o bo-parts > GlobalWidth:448 GlobalHeight:576 ResyncInverval:0 >> C: 1 N: bo-parts.1 W: 448 H: 576 hf:2 vf:2 >> C: 2 N: bo-parts.2 W: 224 H: 288 hf:1 vf:1 >> C: 3 N: bo-parts.3 W: 224 H: 288 hf:1 vf:1 $ jpeg -JFIF -s bo-w448-h576-swap.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 bo-parts.1 \ -hf 1 -vf 1 bo-parts.3 -hf 1 -vf 1 bo-parts.2 $ jpeg -d -s pineapple-w448-h576-norm.jpg -o pineapple-parts > GlobalWidth:448 GlobalHeight:576 ResyncInverval:0 >> C: 1 N: pineapple-parts.1 W: 448 H: 576 hf:2 vf:2 >> C: 2 N: pineapple-parts.2 W: 224 H: 288 hf:1 vf:1 >> C: 3 N: pineapple-parts.3 W: 224 H: 288 hf:1 vf:1 $ jpeg -JFIF -s pineapple-w448-h576-swap.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 pineapple-parts.1 \ -hf 1 -vf 1 pineapple-parts.3 -hf 1 -vf 1 pineapple-parts.2 $
When I reassemble the image, I copy the vertical and horizontal
sampling frequency info for each image (-hf
and
-vf
). I also specify the full output image width and height
(-iw
and -ih
). Also, I specify that the output
file include a JFIF header. Without that only some programs will be able
to read the JPEG.
And here's what those look like:
bo normal | bo swap | pineapple swap | pineapple swap |
---|---|---|---|
Look at those color changes. FUN!
What else can we do? How about inverting single channels of the decomposed images?
# Note: shell is ksh, bash should also work. Won't work in csh/tsch. $ for each in *.pgm ; do pnminvert $each | tail +4 > ${each%.pgm}-invert tail +4 $each > ${each%.pgm} done $ jpeg -JFIF -s bo-w448-h576-inv1.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 bo-parts.1-invert \ -hf 1 -vf 1 bo-parts.2 -hf 1 -vf 1 bo-parts.3 $ jpeg -JFIF -s bo-w448-h576-inv2.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 bo-parts.1 \ -hf 1 -vf 1 bo-parts.2-invert -hf 1 -vf 1 bo-parts.3 $ jpeg -JFIF -s bo-w448-h576-inv3.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 bo-parts.1 \ -hf 1 -vf 1 bo-parts.2 -hf 1 -vf 1 bo-parts.3-invert $ jpeg -JFIF -s pineapple-w448-h576-inv1.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 pineapple-parts.1-invert \ -hf 1 -vf 1 pineapple-parts.2 -hf 1 -vf 1 pineapple-parts.3 $ jpeg -JFIF -s pineapple-w448-h576-inv2.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 pineapple-parts.1 \ -hf 1 -vf 1 pineapple-parts.2-invert -hf 1 -vf 1 pineapple-parts.3 $ jpeg -JFIF -s pineapple-w448-h576-inv3.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 pineapple-parts.1 \ -hf 1 -vf 1 pineapple-parts.2 -hf 1 -vf 1 pineapple-parts.3-invert $ jpeg -JFIF -s bo-w448-h576-inv13.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 bo-parts.1-invert \ -hf 1 -vf 1 bo-parts.2 -hf 1 -vf 1 bo-parts.3-invert $ jpeg -JFIF -s bo-w448-h576-inv23.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 bo-parts.1 \ -hf 1 -vf 1 bo-parts.2-invert -hf 1 -vf 1 bo-parts.3-invert $ jpeg -JFIF -s bo-w448-h576-inv12.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 bo-parts.1-invert \ -hf 1 -vf 1 bo-parts.2-invert -hf 1 -vf 1 bo-parts.3 $ jpeg -JFIF -s pineapple-w448-h576-inv13.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 pineapple-parts.1-invert \ -hf 1 -vf 1 pineapple-parts.2 -hf 1 -vf 1 pineapple-parts.3-invert $ jpeg -JFIF -s pineapple-w448-h576-inv23.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 pineapple-parts.1 \ -hf 1 -vf 1 pineapple-parts.2-invert -hf 1 -vf 1 pineapple-parts.3-invert $ jpeg -JFIF -s pineapple-w448-h576-inv12.jpg -iw 448 -ih 576 \ -hf 2 -vf 2 pineapple-parts.1-invert \ -hf 1 -vf 1 pineapple-parts.2-invert -hf 1 -vf 1 pineapple-parts.3 $
What do those look like?
normal | normal invert | invert Y only | invert Cr only | invert Cb only | invert Y + Cr | invert Y + Cb | invert Cr + Cb |
---|---|---|---|---|---|---|---|
Now, what if we took the RGB channels and fed them in as if they were YCrCb? Or used the YCrCb as RGB channels? Or if we swapped the Y channels between the two images? Some examples (without command lines).
The feature of PVRG JPEG that first lead me to using it is the program's total comfort with dealing with JPEGs that have odd channel makeup. The JFIF specification allows a large number (I think 256) of components in a file. Each component is a separate grayscale image, usually representing one of the channels. At least one has to be the same width as the full image, and at least one has to be the same height as the full image. Most JPEG software can only deal with three component images, with each component being full or half-size in each dimension.
Some better programs can deal with four component images, with the same size restrictions. But JFIF lets us down here and doesn't provide a way of stating what each component is. Do we have a CMYK image? Or RGBA (RGB plus alpha channel)?
I stumbled across a four channel CMYK JPEG ("in the wild" so to speak)
that could be displayed in its original context but not elsewhere.
The convert
tool from ImageMagick claims to support CMYK,
but it turned this into pure black. djpeg
and Gimp simply
wouldn't do any better. But jpeg
allowed me to extract
the individual channels, and I wrote a
cmyk4toppm
program that put them
together.
PVRG jpeg
can create and decode JPEGs with non-standard sampling
sizes, e.g. Y full size, Cr and Cb at quarter height and quarter width. I've
not found anything else that can read those, and I've never seen them
in the wild, so that's just academic. PVRG jpeg
can create
and decode JPEGs with arbitrary numbers of channels. Here are a couple of
examples:
$ ppmtocmyk4 bo-w448-h576-norm.jpg $ ppmtocmyk4 pineapple-w448-h576-norm.jpg $ for each in *.cyn *.mag *.yel *.blk ; do mv $each tmp tail +4 tmp > $each rm tmp done $ jpeg -JFIF -s bo-w448-h576-cmyk.jpg -iw 448 -ih 576 \ -hf 1 -vf 1 bo-w448-h576-norm.cyn \ -hf 1 -vf 1 bo-w448-h576-norm.mag \ -hf 1 -vf 1 bo-w448-h576-norm.yel \ -hf 1 -vf 1 bo-w448-h576-norm.blk $ jpeg -JFIF -s pineapple-w448-h576-cmyk.jpg -iw 448 -ih 576 \ -hf 1 -vf 1 pineapple-w448-h576-norm.cyn \ -hf 1 -vf 1 pineapple-w448-h576-norm.mag \ -hf 1 -vf 1 pineapple-w448-h576-norm.yel \ -hf 1 -vf 1 pineapple-w448-h576-norm.blk $ jpeg -JFIF -s whoa-w448-h576.jpg -iw 448 -ih 576 \ -hf 1 -vf 1 pineapple-w448-h576-norm.cyn \ -hf 1 -vf 1 bo-w448-h576-norm.cyn \ -hf 1 -vf 1 pineapple-w448-h576-norm.mag \ -hf 1 -vf 1 bo-w448-h576-norm.mag \ -hf 1 -vf 1 pineapple-w448-h576-norm.yel \ -hf 1 -vf 1 bo-w448-h576-norm.yel \ -hf 1 -vf 1 pineapple-w448-h576-norm.blk \ -hf 1 -vf 1 bo-w448-h576-norm.blk $
If your browser can display those (quite likely it cannot) here's what they look like:
bo cmyk | pineapple cmyk | whoa |
---|---|---|
Lastly, for the curious, here is the four channel JPEG that sent me on the original quest. This file was extracted from the data inside a PDF (Adobe Portable Document Format) file, where it functioned as a watermark beneath the text. PDF viewers and converters have no problem with it. This file oddly has the magenta and yellow channels at half-height, half-width sampling, even though that is really most useful only for chroma channels. The original PDF may or may not still be available from the US govenment. As a US government produced file, it is copyright free.
Besides PVRG JPEG and my CMYK tools, I made use of the following programs for these examples:
tail +4
"
to remove it.
This web page and the etb patch to the PVRG JPEG code is by Eli the Bearded / Benjamin Elijah Griffin. Page copyright February 2005, reproduction of text, my code, and images is permitted under the Creative Commons Attribution-ShareAlike License. Contact me.
This page should be valid HTML.