Index: rdjpgcom.1 --- rdjpgcom.1.orig 1997-10-11 15:46:48.000000000 -0700 +++ rdjpgcom.1 2003-04-01 14:39:47.000000000 -0800 @@ -7,6 +7,10 @@ .B \-verbose ] [ +.B \-raw +.I key +] +[ .I filename ] .LP @@ -29,6 +33,22 @@ Causes .B rdjpgcom to also display the JPEG image dimensions. +.TP +.BI \-raw " key" +Causes a single comment selected by +.I key +to be display in "raw" (byte-for-byte) format. +Valid keys are +.B COM +and +.BI APP n +where +.I n +is a number from 0 to 15. +The key is not case-sensitive. +Many digital cameras store interesting information in the +.B APP1 +key. .PP Switch names may be abbreviated, and are not case sensitive. .SH HINTS @@ -43,8 +63,13 @@ .B rdjpgcom will also attempt to print the contents of any "APP12" markers as text. Some digital cameras produce APP12 markers containing useful textual -information. If you like, you can modify the source code to print -other APPn marker types as well. +information. +.PP +If the +.B \-verbose +switch is doubled, +.B rdjpegcom +will print all "APPn" markers, not just APP12. .SH SEE ALSO .BR cjpeg (1), .BR djpeg (1), Index: rdjpgcom.c --- rdjpgcom.c.orig 1997-10-11 15:41:04.000000000 -0700 +++ rdjpgcom.c 2003-04-19 03:21:02.000000000 -0700 @@ -119,8 +119,22 @@ #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_APP0 0xE0 /* Application-specific marker, type 0 */ +#define M_APP1 0xE1 /* Application-specific marker, type 1 */ +#define M_APP2 0xE2 /* Application-specific marker, type 2 */ +#define M_APP3 0xE3 /* Application-specific marker, type 3 */ +#define M_APP4 0xE4 /* Application-specific marker, type 4 */ +#define M_APP5 0xE5 /* Application-specific marker, type 5 */ +#define M_APP6 0xE6 /* Application-specific marker, type 6 */ +#define M_APP7 0xE7 /* Application-specific marker, type 7 */ +#define M_APP8 0xE8 /* Application-specific marker, type 8 */ +#define M_APP9 0xE9 /* Application-specific marker, type 9 */ +#define M_APP10 0xEA /* Application-specific marker, type 10 */ +#define M_APP11 0xEB /* Application-specific marker, type 11 */ +#define M_APP12 0xEC /* Application-specific marker, type 12 */ +#define M_APP13 0xED /* Application-specific marker, type 13 */ +#define M_APP14 0xEE /* Application-specific marker, type 14 */ +#define M_APP15 0xEF /* Application-specific marker, type 15 */ #define M_COM 0xFE /* COMment */ @@ -215,10 +229,13 @@ * 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. + * + * If raw is nonzero, we don't have to do all that work, because we're + * just printing one header in raw mode. */ static void -process_COM (void) +process_COM (int raw) { unsigned int length; int ch; @@ -233,12 +250,14 @@ while (length > 0) { ch = read_1_byte(); - /* Emit the character in a readable form. + /* Emit the character in a readable form (unless raw). * 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') { + if (raw) { + putchar(ch); + } else if (ch == '\r') { printf("\n"); } else if (ch == '\n') { if (lastch != '\r') @@ -253,7 +272,8 @@ lastch = ch; length--; } - printf("\n"); + if (!raw) + printf("\n"); } @@ -263,7 +283,7 @@ */ static void -process_SOFn (int marker) +process_SOFn (int marker, int reallyprint) { unsigned int length; unsigned int image_height, image_width; @@ -295,9 +315,11 @@ 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 (reallyprint) { + 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"); @@ -321,7 +343,7 @@ */ static int -scan_JPEG_header (int verbose) +scan_JPEG_header (int verbose, int raw) { int marker; @@ -350,7 +372,7 @@ case M_SOF14: /* Differential progressive, arithmetic */ case M_SOF15: /* Differential lossless, arithmetic */ if (verbose) - process_SOFn(marker); + process_SOFn(marker, !raw); else skip_variable(); break; @@ -362,16 +384,36 @@ return marker; case M_COM: - process_COM(); + if (raw == 0 || raw == marker) + process_COM( raw); + else + skip_variable(); break; - case M_APP12: + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP13: + case M_APP14: + case M_APP15: /* Some digital camera makers put useful textual information into * APP12 markers, so we print those out too when in -verbose mode. + * In double-verbose mode, we print all APP markers. */ - if (verbose) { - printf("APP12 contains:\n"); - process_COM(); + if (verbose > 1 || (verbose && marker == M_APP12) + || (raw != 0 && raw == marker)) { + if (!raw) + printf("APP%d contains:\n", marker & 0xF); + process_COM(raw); } else skip_variable(); break; @@ -399,6 +441,9 @@ fprintf(stderr, "Switches (names may be abbreviated):\n"); fprintf(stderr, " -verbose Also display dimensions of JPEG image\n"); + fprintf(stderr, " -verbose -verbose to show all APP comments\n"); + fprintf(stderr, " -raw x Display only raw comment of type x\n"); + fprintf(stderr, " Valid types are COM or APPn where n is 0 to 15\n"); exit(EXIT_FAILURE); } @@ -438,6 +483,7 @@ { int argn; char * arg; + int raw = 0; int verbose = 0; /* On Mac, fetch a command line. */ @@ -457,6 +503,45 @@ arg++; /* advance over '-' */ if (keymatch(arg, "verbose", 1)) { verbose++; + } else if (keymatch(arg, "raw", 1) && argn < argc + 1) { + argn++; + arg = argv[argn]; + if (keymatch(arg, "com", 1)) { + raw = M_COM; + } else if (keymatch(arg, "app15", 5)) { + raw = M_APP15; + } else if (keymatch(arg, "app14", 5)) { + raw = M_APP14; + } else if (keymatch(arg, "app13", 5)) { + raw = M_APP13; + } else if (keymatch(arg, "app12", 5)) { + raw = M_APP12; + } else if (keymatch(arg, "app11", 5)) { + raw = M_APP11; + } else if (keymatch(arg, "app10", 5)) { + raw = M_APP10; + } else if (keymatch(arg, "app9", 4)) { + raw = M_APP9; + } else if (keymatch(arg, "app8", 4)) { + raw = M_APP8; + } else if (keymatch(arg, "app7", 4)) { + raw = M_APP7; + } else if (keymatch(arg, "app6", 4)) { + raw = M_APP6; + } else if (keymatch(arg, "app5", 4)) { + raw = M_APP5; + } else if (keymatch(arg, "app4", 4)) { + raw = M_APP4; + } else if (keymatch(arg, "app3", 4)) { + raw = M_APP3; + } else if (keymatch(arg, "app2", 4)) { + raw = M_APP2; + } else if (keymatch(arg, "app1", 4)) { + raw = M_APP1; + } else if (keymatch(arg, "app0", 4)) { + raw = M_APP0; + } else + usage(); } else usage(); } @@ -488,7 +573,7 @@ } /* Scan the JPEG headers. */ - (void) scan_JPEG_header(verbose); + (void) scan_JPEG_header(verbose, raw); /* All done. */ exit(EXIT_SUCCESS); Index: wrjpgcom.1 --- wrjpgcom.1.orig 1995-06-15 17:14:05.000000000 -0700 +++ wrjpgcom.1 2003-04-01 14:43:57.000000000 -0800 @@ -13,6 +13,9 @@ .BI \-cfile " name" ] [ +.BI \-app "n name" +] +[ .I filename ] .LP @@ -46,6 +49,12 @@ .TP .BI \-cfile " name" Read text for new COM block from named file. +.TP +.BI \-app "n name" +Read an application-specific comment from named file. +The value +.I n +must be between 0 and 15. .PP If you have only one line of comment text to add, you can provide it on the command line with @@ -53,10 +62,20 @@ The comment text must be surrounded with quotes so that it is treated as a single argument. Longer comments can be read from a text file. .PP -If you give neither -.B \-comment -nor +You can also replace an application-specific comment using the +.BI \-app n +switch. +Since these comments usually have binary components, the contents will +be read from the named file. +Usually, this file will have been extracted from another image using +.BR rdjpgcom (1). +If the file does not have that marker, it will be inserted. +.PP +If you give none of +.BR \-comment , .BR \-cfile , +or +.BR \-app\fIn\fP , then .B wrjpgcom will read the comment text from standard input. (In this case an input image @@ -94,6 +113,19 @@ .I in.jpg .B > .I out.jpg +.PP +Copy the Exif tags from an image to a processed version: +.IP +.B rdjpgcom -raw app1 +.I orig.jpg +.B > +.I comment.app1 +.IP +.B wrjpgcom -app1 +.I comment.app1 +.I changed.jpg +.B > +.I final.jpg .SH SEE ALSO .BR cjpeg (1), .BR djpeg (1), Index: wrjpgcom.c --- wrjpgcom.c.orig 1997-10-22 21:47:03.000000000 -0700 +++ wrjpgcom.c 2003-04-01 14:26:03.000000000 -0800 @@ -170,6 +170,22 @@ #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 0 */ +#define M_APP1 0xE1 /* Application-specific marker, type 1 */ +#define M_APP2 0xE2 /* Application-specific marker, type 2 */ +#define M_APP3 0xE3 /* Application-specific marker, type 3 */ +#define M_APP4 0xE4 /* Application-specific marker, type 4 */ +#define M_APP5 0xE5 /* Application-specific marker, type 5 */ +#define M_APP6 0xE6 /* Application-specific marker, type 6 */ +#define M_APP7 0xE7 /* Application-specific marker, type 7 */ +#define M_APP8 0xE8 /* Application-specific marker, type 8 */ +#define M_APP9 0xE9 /* Application-specific marker, type 9 */ +#define M_APP10 0xEA /* Application-specific marker, type 10 */ +#define M_APP11 0xEB /* Application-specific marker, type 11 */ +#define M_APP12 0xEC /* Application-specific marker, type 12 */ +#define M_APP13 0xED /* Application-specific marker, type 13 */ +#define M_APP14 0xEE /* Application-specific marker, type 14 */ +#define M_APP15 0xEF /* Application-specific marker, type 15 */ #define M_COM 0xFE /* COMment */ @@ -286,7 +302,7 @@ */ static int -scan_JPEG_header (int keep_COM) +scan_JPEG_header (int keep_COM, int app_marker) { int marker; @@ -333,6 +349,30 @@ } break; + case M_APP0: + case M_APP1: + case M_APP2: + case M_APP3: + case M_APP4: + case M_APP5: + case M_APP6: + case M_APP7: + case M_APP8: + case M_APP9: + case M_APP10: + case M_APP11: + case M_APP12: + case M_APP13: + case M_APP14: + case M_APP15: + if (marker == app_marker) { + skip_variable(); + } else { + write_marker(marker); + copy_variable(); + } + break; + default: /* Anything else just gets copied */ write_marker(marker); copy_variable(); /* we assume it has a parameter count... */ @@ -365,9 +405,11 @@ fprintf(stderr, " -replace Delete any existing comments\n"); fprintf(stderr, " -comment \"text\" Insert comment with given text\n"); fprintf(stderr, " -cfile name Read comment from named file\n"); + fprintf(stderr, " -appN name Read appN comment from named file\n"); + fprintf(stderr, " N is 0 to 15\n"); fprintf(stderr, "Notice that you must put quotes around the comment text\n"); fprintf(stderr, "when you use -comment.\n"); - fprintf(stderr, "If you do not give either -comment or -cfile on the command line,\n"); + fprintf(stderr, "If you do not give any of -comment, -cfile, or -appN on the command line,\n"); fprintf(stderr, "then the comment text is read from standard input.\n"); fprintf(stderr, "It can be multiple lines, up to %u characters total.\n", (unsigned int) MAX_COM_LENGTH); @@ -415,6 +457,10 @@ int argn; char * arg; int keep_COM = 1; + int app_marker = 0; + char * app_arg = NULL; + FILE * app_file = NULL; + unsigned int app_length = 0; char * comment_arg = NULL; FILE * comment_file = NULL; unsigned int comment_length = 0; @@ -467,6 +513,13 @@ } } comment_length = (unsigned int) strlen(comment_arg); + } else if (strncmp(arg, "app", 3) == 0 || strncmp(arg, "APP", 3) == 0) { + app_marker = M_APP0 + atoi(arg + 3); + if (++argn >= argc) usage(); + if ((app_file = fopen(argv[argn], "r")) == NULL) { + fprintf(stderr, "%s: can't open %s\n", progname, argv[argn]); + exit(EXIT_FAILURE); + } } else usage(); } @@ -477,7 +530,8 @@ /* If there is neither -comment nor -cfile, we will read the comment text * from stdin; in this case there MUST be an input JPEG file name. */ - if (comment_arg == NULL && comment_file == NULL && argn >= argc) + if (comment_arg == NULL && comment_file == NULL && app_file == NULL + && argn >= argc) usage(); /* Open the input file. */ @@ -534,7 +588,7 @@ #endif /* TWO_FILE_COMMANDLINE */ /* Collect comment text from comment_file or stdin, if necessary */ - if (comment_arg == NULL) { + if (comment_arg == NULL && app_file == NULL) { FILE * src_file; int c; @@ -555,13 +609,44 @@ fclose(comment_file); } + /* Collect application text from app_file, if necessary */ + if (app_file != NULL) { + int ch; + + app_arg = (char *) malloc((size_t) MAX_COM_LENGTH); + if (app_arg == NULL) + ERREXIT("Insufficient memory"); + app_length = 0; + while ((ch = getc(app_file)) != EOF) { + if (app_length >= (unsigned int) MAX_COM_LENGTH) { + fprintf(stderr, + "Application-specif comment text may not exceed %u bytes\n", + (unsigned int) MAX_COM_LENGTH); + exit(EXIT_FAILURE); + } + app_arg[app_length++] = (char) ch; + } + fclose(app_file); + } + /* Copy JPEG headers until SOFn marker; * we will insert the new comment marker just before SOFn. * This (a) causes the new comment to appear after, rather than before, * existing comments; and (b) ensures that comments come after any JFIF * or JFXX markers, as required by the JFIF specification. */ - marker = scan_JPEG_header(keep_COM); + marker = scan_JPEG_header(keep_COM, app_marker); + + /* Insert the new application marker, if any */ + if (app_length != 0) { + write_marker(app_marker); + write_2_bytes(app_length + 2); + while (app_length > 0) { + write_1_byte(*app_arg++); + app_length--; + } + } + /* Insert the new COM marker, but only if nonempty text has been supplied */ if (comment_length > 0) { write_marker(M_COM); @@ -571,6 +656,7 @@ comment_length--; } } + /* Duplicate the remainder of the source file. * Note that any COM markers occuring after SOF will not be touched. */