diff options
Diffstat (limited to 'jpegtools.c')
-rw-r--r-- | jpegtools.c | 621 |
1 files changed, 621 insertions, 0 deletions
diff --git a/jpegtools.c b/jpegtools.c new file mode 100644 index 0000000..90ac7dd --- /dev/null +++ b/jpegtools.c @@ -0,0 +1,621 @@ +/* + * 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 Knorr <kraxel@bytesex.org>, 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 <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <utime.h> +#include <setjmp.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <jpeglib.h> +#include "jpeg/transupp.h" /* Support routines for jpegtran */ +#include "jpegtools.h" + +#include "misc.h" + +#include <libexif/exif-data.h> +#include <libexif/exif-utils.h> +#include <libexif/exif-ifd.h> +#include <libexif/exif-tag.h> + +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->next; + src->marker_list->next = 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; +} |