aboutsummaryrefslogtreecommitdiffstats
path: root/jpegtools.c
diff options
context:
space:
mode:
authorkraxel <kraxel>2004-03-28 11:31:57 +0000
committerkraxel <kraxel>2004-03-28 11:31:57 +0000
commite9e9684117719204929821028ba9dbb7915ea119 (patch)
tree6dd2d71940b33a9f960945335e9124ef4fea9fe7 /jpegtools.c
downloadfbida-e9e9684117719204929821028ba9dbb7915ea119.tar.gz
Initial revision
Diffstat (limited to 'jpegtools.c')
-rw-r--r--jpegtools.c621
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;
+}