[Javascript] Javascript read out EXIF data
Paul Novitski
javascriptlist at dandemutande.org
Mon Feb 9 17:43:03 CST 2004
PS: see http://www.faqs.org/faqs/jpeg-faq/ for Lane's thorough description
of JPEG formatting.
-------------- next part --------------
' rdjpgcom.c
'
' Copyright (C) 1994-1997, Thomas G. Lane.
' This file is part of the Independent JPEG Group's software.
' For conditions of distribution and use, see the accompanying README file.
'
' This file contains a very simple stand-alone application that displays
' the text in COM (comment) markers in a JFIF file.
' This may be useful as an example of the minimum logic needed to parse
' JPEG markers.
'/
' Return next input byte, or EOF if no more
'==================
function NEXTBYTE()
'==================
NEXTBYTE=ReadNextHexPair()
end function
' Error exit handler
'==================
sub ERREXIT(msg)
'==================
debug.print
debug.print msg
end
end sub
' Read one byte, testing for EOF
'==================
function read_1_byte ()
'==================
dim c as byte
on error resume next
c = NEXTBYTE()
if Err<>0 then
ERREXIT("Premature EOF in JPEG file")
end if
return c
end function
' Read 2 bytes, convert to unsigned int
' All 2-byte quantities in JPEG markers are MSB first
static unsigned int
'==================
function read_2_bytes (void)
'==================
int c1, c2
c1 = NEXTBYTE()
if (c1 = EOF)
ERREXIT("Premature EOF in JPEG file")
c2 = NEXTBYTE()
if (c2 = EOF)
ERREXIT("Premature EOF in JPEG file")
return (((unsigned int) c1) << 8) + ((unsigned int) c2)
end function
'
* JPEG markers consist of one or more 0xFF bytes, followed by a marker
* code byte (which is not an FF). Here are the marker codes of interest
* in this program. (See jdmarker.c for a more complete list.)
#define M_SOF0 0xC0 ' Start Of Frame N
#define M_SOF1 0xC1 ' N indicates which compression process
#define M_SOF2 0xC2 ' Only SOF0-SOF2 are now in common use
#define M_SOF3 0xC3
#define M_SOF5 0xC5 ' NB: codes C4 and CC are NOT SOF markers
#define M_SOF6 0xC6
#define M_SOF7 0xC7
#define M_SOF9 0xC9
#define M_SOF10 0xCA
#define M_SOF11 0xCB
#define M_SOF13 0xCD
#define M_SOF14 0xCE
#define M_SOF15 0xCF
#define M_SOI 0xD8 ' Start Of Image (beginning of datastream)
#define M_EOI 0xD9 ' End Of Image (end of datastream)
#define M_SOS 0xDA ' Start Of Scan (begins compressed data)
#define M_APP0 0xE0 ' Application-specific marker, type N
#define M_APP12 0xEC ' (we don't bother to list all 16 APPn's)
#define M_COM 0xFE ' COMment
'
* Find the next JPEG marker and return its marker code.
* We expect at least one FF byte, possibly more if the compressor used FFs
* to pad the file.
* There could also be non-FF garbage between markers. The treatment of such
* garbage is unspecified we choose to skip over it but emit a warning msg.
* NB: this routine must not be used after seeing SOS marker, since it will
* not deal correctly with FF/00 sequences in the compressed image data...
static int
'==================
function next_marker (void)
'==================
int c
int discarded_bytes = 0
' Find 0xFF byte count and skip any non-FFs.
c = read_1_byte()
while (c != 0xFF) {
discarded_bytes++
c = read_1_byte()
' Get marker code byte, swallowing any duplicate FF bytes. Extra FFs
* are legal as pad bytes, so don't count them in discarded_bytes.
do {
c = read_1_byte()
} while (c = 0xFF)
if (discarded_bytes != 0) {
fprintf(stderr, "Warning: garbage data found in JPEG file\n")
}
return c
end function
'
* Read the initial marker, which should be SOI.
* For a JFIF file, the first two bytes of the file should be literally
* 0xFF M_SOI. To be more general, we could use next_marker, but if the
* input file weren't actually JPEG at all, next_marker might read the whole
* file and then return a misleading error message...
static int
'==================
function first_marker (void)
'==================
int c1, c2
c1 = NEXTBYTE()
c2 = NEXTBYTE()
if (c1 != 0xFF || c2 != M_SOI)
ERREXIT("Not a JPEG file")
return c2
end function
'
* Most types of marker are followed by a variable-length parameter segment.
* This routine skips over the parameters for any marker we don't otherwise
* want to process.
* Note that we MUST skip the parameter segment explicitly in order not to
* be fooled by 0xFF bytes that might appear within the parameter segment
* such bytes do NOT introduce new markers.
static void
'==================
function skip_variable (void)
'==================
' Skip over an unknown or uninteresting variable-length marker
{
unsigned int length
' Get the marker parameter length count
length = read_2_bytes()
' Length includes itself, so must be at least 2
if (length < 2)
ERREXIT("Erroneous JPEG marker length")
length -= 2
' Skip over the remaining bytes
while (length > 0) {
(void) read_1_byte()
length--
end function
'
* Process a COM marker.
* We want to print out the marker contents as legible text
* we must guard against non-text junk and varying newline representations.
static void
'==================
function process_COM (void)
'==================
unsigned int length
int ch
int lastch = 0
' Get the marker parameter length count
length = read_2_bytes()
' Length includes itself, so must be at least 2
if (length < 2)
ERREXIT("Erroneous JPEG marker length")
length -= 2
while (length > 0) {
ch = read_1_byte()
' Emit the character in a readable form.
* Nonprintables are converted to \nnn form,
* while \ is converted to \\.
* Newlines in CR, CR/LF, or LF form will be printed as one newline.
if (ch = '\r') {
printf("\n")
} else if (ch = '\n') {
if (lastch != '\r')
printf("\n")
} else if (ch = '\\') {
printf("\\\\")
} else if (isprint(ch)) {
putc(ch, stdout)
} else {
printf("\\%03o", ch)
}
lastch = ch
length--
}
printf("\n")
end function
'
* Process a SOFn marker.
* This code is only needed if you want to know the image dimensions...
static void
'==================
function process_SOFn (int marker)
'==================
unsigned int length
unsigned int image_height, image_width
int data_precision, num_components
const char * process
int ci
length = read_2_bytes() ' usual parameter length count
data_precision = read_1_byte()
image_height = read_2_bytes()
image_width = read_2_bytes()
num_components = read_1_byte()
switch (marker) {
case M_SOF0: process = "Baseline" break
case M_SOF1: process = "Extended sequential" break
case M_SOF2: process = "Progressive" break
case M_SOF3: process = "Lossless" break
case M_SOF5: process = "Differential sequential" break
case M_SOF6: process = "Differential progressive" break
case M_SOF7: process = "Differential lossless" break
case M_SOF9: process = "Extended sequential, arithmetic coding" break
case M_SOF10: process = "Progressive, arithmetic coding" break
case M_SOF11: process = "Lossless, arithmetic coding" break
case M_SOF13: process = "Differential sequential, arithmetic coding" break
case M_SOF14: process = "Differential progressive, arithmetic coding" break
case M_SOF15: process = "Differential lossless, arithmetic coding" break
default: process = "Unknown" break
}
printf("JPEG image is %uw * %uh, %d color components, %d bits per sample\n",
image_width, image_height, num_components, data_precision)
printf("JPEG process: %s\n", process)
if (length != (unsigned int) (8 + num_components * 3))
ERREXIT("Bogus SOF marker length")
for (ci = 0 ci < num_components ci++) {
(void) read_1_byte() ' Component ID code
(void) read_1_byte() ' H, V sampling factors
(void) read_1_byte() ' Quantization table number
}
end function
'
* Parse the marker stream until SOS or EOI is seen
* display any COM markers.
* While the companion program wrjpgcom will always insert COM markers before
* SOFn, other implementations might not, so we scan to SOS before stopping.
* If we were only interested in the image dimensions, we would stop at SOFn.
* (Conversely, if we only cared about COM markers, there would be no need
* for special code to handle SOFn we could treat it like other markers.)
static int
'==================
function scan_JPEG_header (int verbose)
'==================
int marker
' Expect SOI at start of file
if (first_marker() != M_SOI)
ERREXIT("Expected SOI marker first")
' Scan miscellaneous markers until we reach SOS.
for () {
marker = next_marker()
switch (marker) {
' Note that marker codes 0xC4, 0xC8, 0xCC are not, and must not be,
* treated as SOFn. C4 in particular is actually DHT.
case M_SOF0: ' Baseline
case M_SOF1: ' Extended sequential, Huffman
case M_SOF2: ' Progressive, Huffman
case M_SOF3: ' Lossless, Huffman
case M_SOF5: ' Differential sequential, Huffman
case M_SOF6: ' Differential progressive, Huffman
case M_SOF7: ' Differential lossless, Huffman
case M_SOF9: ' Extended sequential, arithmetic
case M_SOF10: ' Progressive, arithmetic
case M_SOF11: ' Lossless, arithmetic
case M_SOF13: ' Differential sequential, arithmetic
case M_SOF14: ' Differential progressive, arithmetic
case M_SOF15: ' Differential lossless, arithmetic
if (verbose)
process_SOFn(marker)
else
skip_variable()
break
case M_SOS: ' stop before hitting compressed data
return marker
case M_EOI: ' in case it's a tables-only JPEG stream
return marker
case M_COM:
process_COM()
break
case M_APP12:
' Some digital camera makers put useful textual information into
* APP12 markers, so we print those out too when in -verbose mode.
if (verbose) {
printf("APP12 contains:\n")
process_COM()
} else
skip_variable()
break
default: ' Anything else just gets skipped
skip_variable() ' we assume it has a parameter count...
break
}
} ' end loop
end function
' Command line parsing code
static const char * progname ' program name for error messages
static void
'==================
function usage (void)
'==================
' complain about bad command line
{
fprintf(stderr, "rdjpgcom displays any textual comments in a JPEG file.\n")
fprintf(stderr, "Usage: %s [switches] [inputfile]\n", progname)
fprintf(stderr, "Switches (names may be abbreviated):\n")
fprintf(stderr, " -verbose Also display dimensions of JPEG image\n")
exit(EXIT_FAILURE)
end function
static int
'==================
function keymatch (char * arg, const char * keyword, int minchars)
'==================
' Case-insensitive matching of (possibly abbreviated) keyword switches.
' keyword is the constant keyword (must be lower case already),
' minchars is length of minimum legal abbreviation.
{
register int ca, ck
register int nmatched = 0
while ((ca = *arg++) != '\0') {
if ((ck = *keyword++) = '\0')
return 0 ' arg longer than keyword, no good
if (isupper(ca)) ' force arg to lcase (assume ck is already)
ca = tolower(ca)
if (ca != ck)
return 0 ' no good
nmatched++ ' count matched characters
}
' reached end of argument fail if it's too short for unique abbrev
if (nmatched < minchars)
return 0
return 1 ' A-OK
end function
'
* The main program.
'==================
function main (int argc, char **argv)
'==================
int argn
char * arg
int verbose = 0
' On Mac, fetch a command line.
#ifdef USE_CCOMMAND
argc = ccommand(&argv)
#endif
progname = argv[0]
if (progname = NULL || progname[0] = 0)
progname = "rdjpgcom" ' in case C library doesn't provide it
' Parse switches, if any
for (argn = 1 argn < argc argn++) {
arg = argv[argn]
if (arg[0] != '-')
break ' not switch, must be file name
arg++ ' advance over '-'
if (keymatch(arg, "verbose", 1)) {
verbose++
} else
usage()
}
' Open the input file.
' Unix style: expect zero or one file name
if (argn < argc-1) {
fprintf(stderr, "%s: only one input file\n", progname)
usage()
}
if (argn < argc) {
if ((infile = fopen(argv[argn], READ_BINARY)) = NULL) {
fprintf(stderr, "%s: can't open %s\n", progname, argv[argn])
exit(EXIT_FAILURE)
}
} else {
' default input file is stdin
#ifdef USE_SETMODE ' need to hack file mode?
setmode(fileno(stdin), O_BINARY)
#endif
#ifdef USE_FDOPEN ' need to re-open in binary mode?
if ((infile = fdopen(fileno(stdin), READ_BINARY)) = NULL) {
fprintf(stderr, "%s: can't open stdin\n", progname)
exit(EXIT_FAILURE)
}
#else
infile = stdin
#endif
}
' Scan the JPEG headers.
(void) scan_JPEG_header(verbose)
' All done.
exit(EXIT_SUCCESS)
return 0 ' suppress no-return-value warnings
end function
More information about the Javascript
mailing list