/* * jpegtran.c * * Copyright (C) 1995-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. * * plenty of changes by Gerd Hoffmann , with focus on * digital image processing and sane exif handling: * * - does transformations only (flip/rotate/transpose/transverse). * - also transforms the exif thumbnail if present. * - can automatically figure transformation from the * exif orientation tag. * - updates the exif orientation tag. * - updates the exif pixel dimension tags. * * This file contains a command-line user interface for JPEG transcoding. * It is very similar to cjpeg.c, but provides lossless transcoding between * different JPEG file formats. It also provides some lossless and sort-of- * lossless transformations of JPEG data. */ #include #include #include #include #include #include #include #include #include #include #include #include "transupp.h" /* Support routines for jpegtran */ #include "jpegtools.h" #include "misc.h" #include #include #include #include static int do_transform(struct jpeg_decompress_struct *src, struct jpeg_compress_struct *dst, JXFORM_CODE transform, unsigned char *comment, char *thumbnail, int tsize, unsigned int flags); static JXFORM_CODE transmagic[] = { [ 1 ] = JXFORM_NONE, [ 2 ] = JXFORM_FLIP_H, [ 3 ] = JXFORM_ROT_180, [ 4 ] = JXFORM_FLIP_V, [ 5 ] = JXFORM_TRANSPOSE, [ 6 ] = JXFORM_ROT_90, [ 7 ] = JXFORM_TRANSVERSE, [ 8 ] = JXFORM_ROT_270, }; #if 0 static char *transname[] = { [ JXFORM_NONE ] = "none", [ JXFORM_FLIP_H ] = "flip h", [ JXFORM_FLIP_V ] = "flip v", [ JXFORM_TRANSPOSE ] = "transpose", [ JXFORM_TRANSVERSE ] = "transverse", [ JXFORM_ROT_90 ] = "rot 90", [ JXFORM_ROT_180 ] = "rot 190", [ JXFORM_ROT_270 ] = "rot 270", }; #endif /* ---------------------------------------------------------------------- */ /* libjpeg error handler -- exit via longjump */ struct longjmp_error_mgr { struct jpeg_error_mgr jpeg; jmp_buf setjmp_buffer; }; static void longjmp_error_exit(j_common_ptr cinfo) { struct longjmp_error_mgr *h = (struct longjmp_error_mgr*)cinfo->err; (*cinfo->err->output_message)(cinfo); longjmp(h->setjmp_buffer, 1); } /* ---------------------------------------------------------------------- */ static long get_int(ExifData *ed, ExifEntry *ee) { ExifByteOrder o = exif_data_get_byte_order(ed); long value; switch (ee->format) { case EXIF_FORMAT_SHORT: value = exif_get_short (ee->data, o); break; case EXIF_FORMAT_LONG: value = exif_get_long (ee->data, o); break; case EXIF_FORMAT_SLONG: value = exif_get_slong (ee->data, o); break; default: fprintf(stderr,"get_int oops\n"); exit(1); } return value; } static void set_int(ExifData *ed, ExifEntry *ee, long value) { ExifByteOrder o = exif_data_get_byte_order(ed); switch (ee->format) { case EXIF_FORMAT_SHORT: exif_set_short (ee->data, o, value); break; case EXIF_FORMAT_LONG: exif_set_long (ee->data, o, value); break; case EXIF_FORMAT_SLONG: exif_set_slong (ee->data, o, value); break; default: fprintf(stderr,"set_int oops\n"); exit(1); } } static void update_orientation(ExifData *ed, int ifd, int orientation) { ExifEntry *ee; ee = exif_content_get_entry(ed->ifd[ifd], 0x0112); if (NULL == ee) return; set_int(ed,ee,orientation); } static void update_dimension(ExifData *ed, JXFORM_CODE transform, int src_x, int src_y) { static struct { int idf; int tag; int x; } fields[] = { { .idf = EXIF_IFD_EXIF, .tag = EXIF_TAG_PIXEL_X_DIMENSION, .x = 1, },{ .idf = EXIF_IFD_EXIF, .tag = EXIF_TAG_PIXEL_Y_DIMENSION, .x = 0, },{ .idf = EXIF_IFD_INTEROPERABILITY, .tag = EXIF_TAG_RELATED_IMAGE_WIDTH, .x = 1, },{ .idf = EXIF_IFD_INTEROPERABILITY, .tag = EXIF_TAG_RELATED_IMAGE_LENGTH, .x = 0, } }; ExifEntry *ee; int i; for (i = 0; i < sizeof(fields)/sizeof(fields[0]); i++) { ee = exif_content_get_entry(ed->ifd[fields[i].idf], fields[i].tag); if (!ee) continue; switch (transform) { case JXFORM_ROT_90: case JXFORM_ROT_270: case JXFORM_TRANSPOSE: case JXFORM_TRANSVERSE: /* x/y reversed */ set_int(ed, ee, fields[i].x ? src_y : src_x); break; default: /* normal */ set_int(ed, ee, fields[i].x ? src_x : src_y); break; } } } static int get_orientation(ExifData *ed) { ExifEntry *ee; ee = exif_content_get_entry(ed->ifd[EXIF_IFD_0], 0x0112); if (NULL == ee) return 1; /* top - left */ return get_int(ed,ee); } /* ---------------------------------------------------------------------- */ struct th { struct jpeg_decompress_struct src; struct jpeg_compress_struct dst; struct jpeg_error_mgr jsrcerr, jdsterr; unsigned char *in; unsigned char *out; int isize, osize; }; static void thumbnail_src_init(struct jpeg_decompress_struct *cinfo) { struct th *h = container_of(cinfo, struct th, src); cinfo->src->next_input_byte = h->in; cinfo->src->bytes_in_buffer = h->isize; } static int thumbnail_src_fill(struct jpeg_decompress_struct *cinfo) { fprintf(stderr,"jpeg: panic: no more thumbnail input data\n"); exit(1); } static void thumbnail_src_skip(struct jpeg_decompress_struct *cinfo, long num_bytes) { cinfo->src->next_input_byte += num_bytes; } static void thumbnail_src_term(struct jpeg_decompress_struct *cinfo) { /* nothing */ } static void thumbnail_dest_init(struct jpeg_compress_struct *cinfo) { struct th *h = container_of(cinfo, struct th, dst); h->osize = h->isize * 2; h->out = malloc(h->osize); cinfo->dest->next_output_byte = h->out; cinfo->dest->free_in_buffer = h->osize; } static boolean thumbnail_dest_flush(struct jpeg_compress_struct *cinfo) { fprintf(stderr,"jpeg: panic: output buffer full\n"); exit(1); } static void thumbnail_dest_term(struct jpeg_compress_struct *cinfo) { struct th *h = container_of(cinfo, struct th, dst); h->osize -= cinfo->dest->free_in_buffer; } static struct jpeg_source_mgr thumbnail_src = { .init_source = thumbnail_src_init, .fill_input_buffer = thumbnail_src_fill, .skip_input_data = thumbnail_src_skip, .resync_to_restart = jpeg_resync_to_restart, .term_source = thumbnail_src_term, }; static struct jpeg_destination_mgr thumbnail_dst = { .init_destination = thumbnail_dest_init, .empty_output_buffer = thumbnail_dest_flush, .term_destination = thumbnail_dest_term, }; static void do_thumbnail(ExifData *ed, JXFORM_CODE transform) { struct th th; if (JXFORM_NONE == transform) return; memset(&th,0,sizeof(th)); th.in = ed->data; th.isize = ed->size; /* setup src */ th.src.err = jpeg_std_error(&th.jsrcerr); jpeg_create_decompress(&th.src); th.src.src = &thumbnail_src; /* setup dst */ th.dst.err = jpeg_std_error(&th.jdsterr); jpeg_create_compress(&th.dst); th.dst.dest = &thumbnail_dst; /* transform image */ do_transform(&th.src,&th.dst,transform,NULL,NULL,0,JFLAG_TRANSFORM_IMAGE); /* cleanup */ jpeg_destroy_decompress(&th.src); jpeg_destroy_compress(&th.dst); /* replace thumbnail */ free(ed->data); ed->data = th.out; ed->size = th.osize; } static void do_exif(struct jpeg_decompress_struct *src, JXFORM_CODE *transform, char *thumbnail, int tsize, unsigned int flags) { jpeg_saved_marker_ptr mark; ExifData *ed = NULL; unsigned char *data; unsigned int size; for (mark = src->marker_list; NULL != mark; mark = mark->next) { if (mark->marker != JPEG_APP0 +1) continue; ed = exif_data_new_from_data(mark->data,mark->data_length); break; } if (flags & JFLAG_UPDATE_THUMBNAIL) { if (NULL == ed) ed = exif_data_new(); if (NULL == mark) { mark = src->mem->alloc_large((j_common_ptr)src,JPOOL_IMAGE,sizeof(*mark)); memset(mark,0,sizeof(*mark)); mark->marker = JPEG_APP0 +1; mark->next = src->marker_list; src->marker_list = mark; } if (ed->data) free(ed->data); ed->data = thumbnail; ed->size = tsize; } if (NULL == ed) return; if (-1 == *transform) { /* automagic image transformation */ int orientation = get_orientation(ed); *transform = JXFORM_NONE; if (orientation >= 1 && orientation <= 8) *transform = transmagic[orientation]; #if 0 if (debug) fprintf(stderr,"autotrans: %s\n",transname[*transform]); #endif } /* update exif data */ if (flags & JFLAG_UPDATE_ORIENTATION) { update_orientation(ed,EXIF_IFD_0,1); update_orientation(ed,EXIF_IFD_1,1); } if (ed->data && ed->data[0] == 0xff && ed->data[1] == 0xd8 && (flags & JFLAG_TRANSFORM_THUMBNAIL)) do_thumbnail(ed,*transform); update_dimension(ed, (flags & JFLAG_TRANSFORM_IMAGE) ? *transform : JXFORM_NONE, src->image_width, src->image_height); /* build new exif data block */ exif_data_save_data(ed,&data,&size); exif_data_unref(ed); /* update jpeg APP1 (EXIF) marker */ mark->data = src->mem->alloc_large((j_common_ptr)src,JPOOL_IMAGE,size); mark->original_length = size; mark->data_length = size; memcpy(mark->data,data,size); free(data); } /* ---------------------------------------------------------------------- */ static void do_comment(struct jpeg_decompress_struct *src, unsigned char *comment) { jpeg_saved_marker_ptr mark; int size; /* find or create comment marker */ for (mark = src->marker_list;; mark = mark->next) { if (mark->marker == JPEG_COM) break; if (NULL == mark->next) { mark->next = src->mem->alloc_large((j_common_ptr)src,JPOOL_IMAGE, sizeof(*mark)); mark = mark->next; memset(mark,0,sizeof(*mark)); mark->marker = JPEG_COM; break; } } /* update comment marker */ size = strlen(comment) +1; mark->data = src->mem->alloc_large((j_common_ptr)src,JPOOL_IMAGE,size); mark->original_length = size; mark->data_length = size; memcpy(mark->data,comment,size); } static int do_transform(struct jpeg_decompress_struct *src, struct jpeg_compress_struct *dst, JXFORM_CODE transform, unsigned char *comment, char *thumbnail, int tsize, unsigned int flags) { jvirt_barray_ptr * src_coef_arrays; jvirt_barray_ptr * dst_coef_arrays; jpeg_transform_info transformoption; jcopy_markers_setup(src, JCOPYOPT_ALL); if (JPEG_HEADER_OK != jpeg_read_header(src, TRUE)) return -1; do_exif(src,&transform,thumbnail,tsize,flags); if (-1 == transform) transform = JXFORM_NONE; if (!(flags & JFLAG_TRANSFORM_IMAGE)) transform = JXFORM_NONE; if ((flags & JFLAG_UPDATE_COMMENT) && NULL != comment) do_comment(src,comment); memset(&transformoption,0,sizeof(transformoption)); transformoption.transform = transform; transformoption.trim = FALSE; transformoption.force_grayscale = FALSE; /* Any space needed by a transform option must be requested before * jpeg_read_coefficients so that memory allocation will be done right. */ jtransform_request_workspace(src, &transformoption); src_coef_arrays = jpeg_read_coefficients(src); jpeg_copy_critical_parameters(src, dst); dst_coef_arrays = jtransform_adjust_parameters (src, dst, src_coef_arrays, &transformoption); /* Start compressor (note no image data is actually written here) */ jpeg_write_coefficients(dst, dst_coef_arrays); /* Copy to the output file any extra markers that we want to preserve */ jcopy_markers_execute(src, dst, JCOPYOPT_ALL); /* Execute image transformation, if any */ jtransform_execute_transformation(src, dst, src_coef_arrays, &transformoption); /* Finish compression and release memory */ jpeg_finish_compress(dst); jpeg_finish_decompress(src); return 0; } /* ---------------------------------------------------------------------- */ int jpeg_transform_fp(FILE *in, FILE *out, JXFORM_CODE transform, unsigned char *comment, char *thumbnail, int tsize, unsigned int flags) { struct jpeg_decompress_struct src; struct jpeg_compress_struct dst; struct jpeg_error_mgr jdsterr; struct longjmp_error_mgr jsrcerr; /* setup src */ src.err = jpeg_std_error(&jsrcerr.jpeg); jsrcerr.jpeg.error_exit = longjmp_error_exit; if (setjmp(jsrcerr.setjmp_buffer)) /* something went wrong within the jpeg library ... */ goto oops; jpeg_create_decompress(&src); jpeg_stdio_src(&src, in); /* setup dst */ dst.err = jpeg_std_error(&jdsterr); jpeg_create_compress(&dst); jpeg_stdio_dest(&dst, out); /* transform image */ do_transform(&src,&dst,transform,comment,thumbnail,tsize,flags); /* cleanup */ jpeg_destroy_decompress(&src); jpeg_destroy_compress(&dst); return 0; oops: jpeg_destroy_decompress(&src); jpeg_destroy_compress(&dst); return -1; } int jpeg_transform_files(char *infile, char *outfile, JXFORM_CODE transform, unsigned char *comment, char *thumbnail, int tsize, unsigned int flags) { int rc; FILE *in; FILE *out; /* open infile */ in = fopen(infile,"r"); if (NULL == in) { fprintf(stderr,"open %s: %s\n",infile,strerror(errno)); return -1; } /* open outfile */ out = fopen(outfile,"w"); if (NULL == out) { fprintf(stderr,"open %s: %s\n",outfile,strerror(errno)); fclose(in); return -1; } /* go! */ rc = jpeg_transform_fp(in,out,transform,comment,thumbnail,tsize,flags); fclose(in); fclose(out); return rc; } int jpeg_transform_inplace(char *file, JXFORM_CODE transform, unsigned char *comment, char *thumbnail, int tsize, unsigned int flags) { char *tmpfile; char *bakfile; struct stat st; int fd; FILE *in = NULL; FILE *out = NULL; /* are we allowed to write to the file? */ if (0 != access(file,W_OK)) { fprintf(stderr,"access %s: %s\n",file,strerror(errno)); return -1; } /* open infile */ in = fopen(file,"r"); if (NULL == in) { fprintf(stderr,"open %s: %s\n",file,strerror(errno)); return -1; } /* open tmpfile */ tmpfile = malloc(strlen(file)+10); sprintf(tmpfile,"%s.XXXXXX",file); fd = mkstemp(tmpfile); if (-1 == fd) { fprintf(stderr,"mkstemp(%s): %s\n",tmpfile,strerror(errno)); goto oops; } out = fdopen(fd,"w"); /* copy owner and permissions */ if (-1 == fstat(fileno(in),&st)) { fprintf(stderr,"fstat(%s): %s\n",file,strerror(errno)); goto oops; } if (-1 == fchown(fileno(out),st.st_uid,st.st_gid)) { fprintf(stderr,"fchown(%s): %s\n",tmpfile,strerror(errno)); goto oops; } if (-1 == fchmod(fileno(out),st.st_mode)) { fprintf(stderr,"fchmod(%s): %s\n",tmpfile,strerror(errno)); goto oops; } /* transform */ if (0 != jpeg_transform_fp(in,out,transform,comment,thumbnail,tsize,flags)) goto oops; /* worked ok -- commit */ fclose(in); fclose(out); if (flags & JFLAG_FILE_BACKUP) { bakfile = malloc(strlen(file)+2); sprintf(bakfile,"%s~",file); rename(file,bakfile); free(bakfile); } rename(tmpfile,file); if (flags & JFLAG_FILE_KEEP_TIME) { struct utimbuf u; u.actime = st.st_atime; u.modtime = st.st_mtime; utime(file,&u); } /* cleanup & return */ free(tmpfile); return 0; oops: /* something went wrong -- rollback */ if (in) fclose(in); if (out) { fclose(out); unlink(tmpfile); } return -1; }