-
Notifications
You must be signed in to change notification settings - Fork 2
Two Tone JPEG made incredibly easy.
Now that I can upload using the Eye-Fi, but only JPEGs, is there a better way than stuffing the data into the comment blocks?
Yes. If you figure out how to do JPEG headers, you can construct a table which uses byte values to generate 8×8 regions of black or white.
This is useful to create images with text like you might put on a LCD, or for barcodes like QR Codes. This way you can use something like a smartphone to scan thumbnails or do something with the zxing project.
I highly recommend jpeg snoop and the explanation pages at impulse adventure:
http://www.impulseadventure.com/photo/jpeg-huffman-coding.html
JPEG image data (without chroma) is one stream encoding 8×8 blocks with DC and AC components, i.e. the average pixel luminance of the block, then some coding for lighter and darker. You have the DC then the AC as bits pointing into huffman tables.
This is for grayscale. Color adds Cr and Cb chroma channels to the Y luminance channel. See the site above for more details.
It starts with 50% gray, then each value is a delta, so to go black, you need -FullScale/2, to get white, +FS/2. Then if the next pixel is the same, you use 0, or if it switches, the delta is +FS or -FS to flip the bit. 5 codes. If you optimize a jpeg created from random black and white 8×8 blocks, it creates the codes with bitlengths of 1/4/4/4/4 for the DC, and 1 for the AC. What we want is 7/7/7/7/7 for DC and 1 for AC to get 8 bits to make it very easy. After some experimenting and editing the tables, I created a header and routine that converts PBM files (P1, width and height, then 0s and 1s for each bit).
This is an image created with the program:
(the image size is large so you might want to open it in a webpage, then text editor)
http://www.zdez.org/wg.jpg
bytejpeg.c:
#include <stdio.h>
unsigned char jpeg0[] = {
0xff, 0xd8,
0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00,
0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
0xFF,0xDB,0x00,0x43,0x00,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,
0xff, 0xc1, 0x00, 0x0b, 0x08,
// address 0x5e
0x00, 0x80, // height
0x00, 0x80, // width
0x01, 0x01, 0x11, 0x00,
// DC table
0xff, 0xc4, 0x00, 0x15, 0x00,
1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
6, 0,
// AC Table
0xff, 0xc4, 0x00, 0x14, 0x10,
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0,
// Image scan header
0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00
};
unsigned char ibuf[4096];
unsigned width = 64, height = 64;
main() {
int j,k, m;
gets(ibuf); //P1
gets(ibuf); // width/height
j = sscanf( ibuf, "%d %d", &width, &height );
if( j != 2 )
return;
// set height and width in header
jpeg0[0x5e] = height >> 5;
jpeg0[0x5f] = height << 3;
jpeg0[0x60] = width >> 5;
jpeg0[0x61] = width << 3;
// write out header
fwrite( jpeg0, 1, sizeof(jpeg0), stdout );
// skip to image, got first
do {
j = getchar();
} while ( j != '0' && j != '1' );
// put half full scale, 3e for white, 40 for black
putchar( j == '1' ? 0x3e : 0x40 );
k = j;
for(;;) {
j = getchar();
if( j < 0 )
break;
if( j != '0' && j != '1' )
continue;
if( k == j ) {
putchar(0x80); // code for no change
continue;
}
putchar( j == '1' ? 0 : 0x7e ); // full scale flip
k = j;
}
putchar(0x80); // one last for EOF
putchar(0xFF); // end marker
putchar(0xd9);
}