PVRG JPEG

Skip to the examples bo swap

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.

Building From Source

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:

  1. Untar the tarball.
    gunzip < JPEGv1.2.1.tar.gz | tar xf -
  2. Apply the patch.
    gunzip < pvrg-jpeg-etb.patch.gz | patch -p0
  3. Change to the new directory.
    cd jpegdir
  4. Make the program.
    make

If something goes wrong, you fiddle with the source.

Some possible problems:

What Can You Do With PVRG JPEG?

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 bo norm bo parts bo parts bo parts bo norm bo norm bo norm
pineapple pineapple norm pineapple parts pineapple parts pineapple parts pineapple norm pineapple norm pineapple norm

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
bo norm bo swap pineapple norm 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
bo norm bo inv bo inv1 bo inv2 bo inv3 bo inv12 bo inv13 bo inv23
pineapple norm pineapple inv pineapple inv1 pineapple inv2 pineapple inv3 pineapple inv12 pineapple inv13 pineapple inv23

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).

RGB used as YCrCb bo rgb pineapple rgb
GBR used as YCrCb
blue inverted
bo gBr pineapple gBr
YCrCb used as RGB,
chroma scaled
bo ycrcb pineapple ycrcb
YCrCb used as RGB,
chroma scaled and normalized
bo ycrcbnorm pineapple ycrcbnorm
YCrCb used as RGB,
chroma scaled and normalized
darker chroma inverted
bo ycrcbnorminv2 pineapple ycrcbnorminv3
Swap Y Channels bo colorswap pineapple colorswap

Final Examples

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
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.

Other Programs Used

Besides PVRG JPEG and my CMYK tools, I made use of the following programs for these examples:

cjpeg
IJG tool to compress PGM and PPM files to JPEGs.
djpeg
IJG tool to decompress JPEGs into PGM or PPM files.
pgmnorm
NetPBM tool to normalize PGM files.
pnminvert
NetPBM tool to invert any of the portable image files.
pnmscale
NetPBM tool to resize any of the portable image files.
ppmtogif
NetPBM tool to compress PGM and PPM files to GIFs.
ppmtorgb3
NetPBM tool to split PPM files to PGM formatted color channels.
rawtopgm
NetPBM tool to convert raw image data to PGM formatted data.
rgb3toppm
NetPBM tool to combine PGM formated color channels into an RGB image.
tail
(GNU version.) Tool to print the end of a file. Since PBM, PGM, and PPM images usually have a three line header, I use "tail +4" to remove it.

Author

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.