/**
 * @file exifiron.c
 * Orientation correction and optimization of Exif JPEG images
 * @author Marko Mäkelä (msmakela@nic.funet.fi)
 */

/* Copyright © 2003 Marko Mäkelä.

   This file is part of EXIFIRON, a program for orientation correction and
   shrinking of digital photographs in JPEG EXIF format.

   EXIFIRON is free software; you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   EXIFIRON is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   The GNU General Public License is often shipped with GNU software, and
   is generally kept in a file called COPYING or LICENSE.  If you do not
   have a copy of the license, write to the Free Software Foundation,
   59 Temple Place, Suite 330, Boston, MA 02111 USA. */

/*
 * The code for lossless JPEG image transformations (rotating and flipping)
 * has been derived from the file transupp.c, which is part of the jpegtran
 * utility of the Independent JPEG Group,
 * Copyright © 1997-2001 Thomas G. Lane.
 * For conditions of distribution and use, see the accompanying README file.
 *
 * According to comments in transupp.c, the transformations were initially
 * designed and coded by Guido Vollbeding.
 *
 * In this file, we have removed the options for cropping and trimming.
 */

#if defined WIN32 || defined __WIN32
# undef __STRICT_ANSI__
# include <sys/types.h>
# include <sys/utime.h>
#else
# include <sys/types.h>
# include <utime.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <time.h>
#include "jpeglib.h"
#include "jerror.h"

/* code adapted from transupp.c begins */

/** Lossless image transformations */
enum JXFORM_CODE {
  JXFORM_NONE,		/**< no transformation */
  JXFORM_FLIP_H,	/**< horizontal flip */
  JXFORM_FLIP_V,	/**< vertical flip */
  JXFORM_TRANSPOSE,	/**< transpose across UL-to-LR axis */
  JXFORM_TRANSVERSE,	/**< transpose across UR-to-LL axis */
  JXFORM_ROT_90,	/**< 90-degree clockwise rotation */
  JXFORM_ROT_180,	/**< 180-degree rotation */
  JXFORM_ROT_270	/**< 270-degree clockwise (or 90 ccw) */
};

/** JPEG image transformation information.
 * This structure has been adapted from transupp.c.
 */
struct jpeg_transform_info
{
  /** @name Options: set by caller */
  /*@{*/
  /** image transform operator */
  enum JXFORM_CODE transform;
  /*@}*/

  /** Internal workspace: caller should not touch these */
  /*@{*/
  /** workspace for transformations */
  struct jvirt_barray_control* * workspace_coef_arrays;
  /** destination width */
  JDIMENSION output_width;
  /** destination height */
  JDIMENSION output_height;
  /** destination sampling factor (iMCU size), horizontal */
  int max_h_samp_factor;
  /** destination sampling factor (iMCU size), vertical */
  int max_v_samp_factor;
  /*@}*/
};

/** Horizontal flip; done in-place, so no separate dest array is required.
 * The code has been adapted from transupp.c.
 * @param srcinfo		source image information
 * @param dstinfo		destination image information
 * @param src_coef_arrays	coefficient arrays for the source image
 */
static void
flip_h (struct jpeg_decompress_struct* srcinfo,
	struct jpeg_compress_struct* dstinfo,
	struct jvirt_barray_control** src_coef_arrays)
{
  JDIMENSION MCU_cols, comp_width, blk_x, blk_y;
  int ci, k, offset_y;
  JBLOCKARRAY buffer;
  JCOEFPTR ptr1, ptr2;
  JCOEF temp1, temp2;
  jpeg_component_info* compptr;

  /* Horizontal mirroring of DCT blocks is accomplished by swapping
   * pairs of blocks in-place.  Within a DCT block, we perform horizontal
   * mirroring by changing the signs of odd-numbered columns.
   * Partial iMCUs at the right edge are left untouched.
   */
  MCU_cols = srcinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);

  for (ci = 0; ci < dstinfo->num_components; ci++) {
    compptr = dstinfo->comp_info + ci;
    comp_width = MCU_cols * compptr->h_samp_factor;
    for (blk_y = 0; blk_y < compptr->height_in_blocks;
	 blk_y += compptr->v_samp_factor) {
      buffer = (*srcinfo->mem->access_virt_barray)
	((j_common_ptr) srcinfo, src_coef_arrays[ci], blk_y,
	 (JDIMENSION) compptr->v_samp_factor, TRUE);
      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
	/* Do the mirroring */
	for (blk_x = 0; blk_x * 2 < comp_width; blk_x++) {
	  ptr1 = buffer[offset_y][blk_x];
	  ptr2 = buffer[offset_y][comp_width - blk_x - 1];
	  /* this unrolled loop doesn't need to know which row it's on... */
	  for (k = 0; k < DCTSIZE2; k += 2) {
	    temp1 = *ptr1;	/* swap even column */
	    temp2 = *ptr2;
	    *ptr1++ = temp2;
	    *ptr2++ = temp1;
	    temp1 = *ptr1;	/* swap odd column with sign change */
	    temp2 = *ptr2;
	    *ptr1++ = -temp2;
	    *ptr2++ = -temp1;
	  }
	}
      }
    }
  }
}

/** Vertical flip.
 * The code has been adapted from transupp.c.
 * @param srcinfo		source image information
 * @param dstinfo		destination image information
 * @param src_coef_arrays	coefficient arrays for the source image
 * @param dst_coef_arrays	coefficient arrays for the destination image
 */
static void
flip_v (struct jpeg_decompress_struct* srcinfo,
	struct jpeg_compress_struct* dstinfo,
	struct jvirt_barray_control** src_coef_arrays,
	struct jvirt_barray_control** dst_coef_arrays)
{
  JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
  int ci, i, j, offset_y;
  JBLOCKARRAY src_buffer, dst_buffer;
  JBLOCKROW src_row_ptr, dst_row_ptr;
  JCOEFPTR src_ptr, dst_ptr;
  jpeg_component_info* compptr;

  /* We output into a separate array because we can't touch different
   * rows of the source virtual array simultaneously.  Otherwise, this
   * is a pretty straightforward analog of horizontal flip.
   * Within a DCT block, vertical mirroring is done by changing the signs
   * of odd-numbered rows.
   * Partial iMCUs at the bottom edge are copied verbatim.
   */
  MCU_rows = srcinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);

  for (ci = 0; ci < dstinfo->num_components; ci++) {
    compptr = dstinfo->comp_info + ci;
    comp_height = MCU_rows * compptr->v_samp_factor;
    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
	 dst_blk_y += compptr->v_samp_factor) {
      dst_buffer = (*srcinfo->mem->access_virt_barray)
	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
	 (JDIMENSION) compptr->v_samp_factor, TRUE);
      src_buffer = (*srcinfo->mem->access_virt_barray)
	((j_common_ptr) srcinfo, src_coef_arrays[ci],
	 comp_height - dst_blk_y -
	 (JDIMENSION) compptr->v_samp_factor,
	 (JDIMENSION) compptr->v_samp_factor, FALSE);
      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
	dst_row_ptr = dst_buffer[offset_y];
	src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
	for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
	     dst_blk_x++) {
	  dst_ptr = dst_row_ptr[dst_blk_x];
	  src_ptr = src_row_ptr[dst_blk_x];
	  for (i = 0; i < DCTSIZE; i += 2) {
	    /* copy even row */
	    for (j = 0; j < DCTSIZE; j++)
	      *dst_ptr++ = *src_ptr++;
	    /* copy odd row with sign change */
	    for (j = 0; j < DCTSIZE; j++)
	      *dst_ptr++ = - *src_ptr++;
	  }
	}
      }
    }
  }
}

/** Transpose source into destination.
 * The code has been adapted from transupp.c.
 * @param srcinfo		source image information
 * @param dstinfo		destination image information
 * @param src_coef_arrays	coefficient arrays for the source image
 * @param dst_coef_arrays	coefficient arrays for the destination image
 */
static void
transpose (struct jpeg_decompress_struct* srcinfo,
	   struct jpeg_compress_struct* dstinfo,
	   struct jvirt_barray_control** src_coef_arrays,
	   struct jvirt_barray_control** dst_coef_arrays)
{
  JDIMENSION dst_blk_x, dst_blk_y;
  int ci, i, j, offset_x, offset_y;
  JBLOCKARRAY src_buffer, dst_buffer;
  JCOEFPTR src_ptr, dst_ptr;
  jpeg_component_info* compptr;

  /* Transposing pixels within a block just requires transposing the
   * DCT coefficients.
   * Partial iMCUs at the edges require no special treatment; we simply
   * process all the available DCT blocks for every component.
   */
  for (ci = 0; ci < dstinfo->num_components; ci++) {
    compptr = dstinfo->comp_info + ci;
    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
	 dst_blk_y += compptr->v_samp_factor) {
      dst_buffer = (*srcinfo->mem->access_virt_barray)
	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
	 (JDIMENSION) compptr->v_samp_factor, TRUE);
      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
	for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
	     dst_blk_x += compptr->h_samp_factor) {
	  src_buffer = (*srcinfo->mem->access_virt_barray)
	    ((j_common_ptr) srcinfo, src_coef_arrays[ci],
	     dst_blk_x,
	     (JDIMENSION) compptr->h_samp_factor, FALSE);
	  for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
	    dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
	    src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
	    for (i = 0; i < DCTSIZE; i++)
	      for (j = 0; j < DCTSIZE; j++)
		dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
	  }
	}
      }
    }
  }
}

/** 90 degree clockwise rotation (transposition and horizontal mirroring).
 * The code has been adapted from transupp.c.
 * @param srcinfo		source image information
 * @param dstinfo		destination image information
 * @param src_coef_arrays	coefficient arrays for the source image
 * @param dst_coef_arrays	coefficient arrays for the destination image
 */
static void
rot90 (struct jpeg_decompress_struct* srcinfo,
       struct jpeg_compress_struct* dstinfo,
       struct jvirt_barray_control** src_coef_arrays,
       struct jvirt_barray_control** dst_coef_arrays)
{
  JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
  int ci, i, j, offset_x, offset_y;
  JBLOCKARRAY src_buffer, dst_buffer;
  JCOEFPTR src_ptr, dst_ptr;
  jpeg_component_info* compptr;

  /* Because of the horizontal mirror step, we can't process partial iMCUs
   * at the (output) right edge properly.  They just get transposed and
   * not mirrored.
   */
  MCU_cols = srcinfo->image_height / (dstinfo->max_h_samp_factor * DCTSIZE);

  for (ci = 0; ci < dstinfo->num_components; ci++) {
    compptr = dstinfo->comp_info + ci;
    comp_width = MCU_cols * compptr->h_samp_factor;
    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
	 dst_blk_y += compptr->v_samp_factor) {
      dst_buffer = (*srcinfo->mem->access_virt_barray)
	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
	 (JDIMENSION) compptr->v_samp_factor, TRUE);
      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
	for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
	     dst_blk_x += compptr->h_samp_factor) {
	  src_buffer = (*srcinfo->mem->access_virt_barray)
	    ((j_common_ptr) srcinfo, src_coef_arrays[ci],
	     comp_width - dst_blk_x -
	     (JDIMENSION) compptr->h_samp_factor,
	     (JDIMENSION) compptr->h_samp_factor, FALSE);
	  for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
	    dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
	    src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
	      [dst_blk_y + offset_y];
	    for (i = 0; i < DCTSIZE; i++) {
	      for (j = 0; j < DCTSIZE; j++)
		dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
	      i++;
	      for (j = 0; j < DCTSIZE; j++)
		dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
	    }
	  }
	}
      }
    }
  }
}

/** 270 degree clockwise rotation (horizontal mirroring and transposition).
 * The code has been adapted from transupp.c.
 * @param srcinfo		source image information
 * @param dstinfo		destination image information
 * @param src_coef_arrays	coefficient arrays for the source image
 * @param dst_coef_arrays	coefficient arrays for the destination image
 */
static void
rot270 (struct jpeg_decompress_struct* srcinfo,
	struct jpeg_compress_struct* dstinfo,
	struct jvirt_barray_control** src_coef_arrays,
	struct jvirt_barray_control** dst_coef_arrays)
{
  JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
  int ci, i, j, offset_x, offset_y;
  JBLOCKARRAY src_buffer, dst_buffer;
  JCOEFPTR src_ptr, dst_ptr;
  jpeg_component_info* compptr;

  /* Because of the horizontal mirror step, we can't process partial iMCUs
   * at the (output) bottom edge properly.  They just get transposed and
   * not mirrored.
   */
  MCU_rows = srcinfo->image_width / (dstinfo->max_v_samp_factor * DCTSIZE);

  for (ci = 0; ci < dstinfo->num_components; ci++) {
    compptr = dstinfo->comp_info + ci;
    comp_height = MCU_rows * compptr->v_samp_factor;
    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
	 dst_blk_y += compptr->v_samp_factor) {
      dst_buffer = (*srcinfo->mem->access_virt_barray)
	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
	 (JDIMENSION) compptr->v_samp_factor, TRUE);
      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
	for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
	     dst_blk_x += compptr->h_samp_factor) {
	  src_buffer = (*srcinfo->mem->access_virt_barray)
	    ((j_common_ptr) srcinfo, src_coef_arrays[ci],
	     dst_blk_x,
	     (JDIMENSION) compptr->h_samp_factor, FALSE);
	  for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
	    dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
	    src_ptr = src_buffer[offset_x]
	      [comp_height - dst_blk_y - offset_y - 1];
	    for (i = 0; i < DCTSIZE; i++) {
	      for (j = 0; j < DCTSIZE; j++) {
		dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
		j++;
		dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
	      }
	    }
	  }
	}
      }
    }
  }
}

/** 180 degree rotation (vertical and horizontal mirroring).
 * The code has been adapted from transupp.c.
 * @param srcinfo		source image information
 * @param dstinfo		destination image information
 * @param src_coef_arrays	coefficient arrays for the source image
 * @param dst_coef_arrays	coefficient arrays for the destination image
 */
static void
rot180 (struct jpeg_decompress_struct* srcinfo,
	struct jpeg_compress_struct* dstinfo,
	struct jvirt_barray_control** src_coef_arrays,
	struct jvirt_barray_control** dst_coef_arrays)
{
  JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
  int ci, i, j, offset_y;
  JBLOCKARRAY src_buffer, dst_buffer;
  JBLOCKROW src_row_ptr, dst_row_ptr;
  JCOEFPTR src_ptr, dst_ptr;
  jpeg_component_info* compptr;

  MCU_cols = srcinfo->image_width / (dstinfo->max_h_samp_factor * DCTSIZE);
  MCU_rows = srcinfo->image_height / (dstinfo->max_v_samp_factor * DCTSIZE);

  for (ci = 0; ci < dstinfo->num_components; ci++) {
    compptr = dstinfo->comp_info + ci;
    comp_width = MCU_cols * compptr->h_samp_factor;
    comp_height = MCU_rows * compptr->v_samp_factor;
    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
	 dst_blk_y += compptr->v_samp_factor) {
      dst_buffer = (*srcinfo->mem->access_virt_barray)
	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
	 (JDIMENSION) compptr->v_samp_factor, TRUE);
      src_buffer = (*srcinfo->mem->access_virt_barray)
	((j_common_ptr) srcinfo, src_coef_arrays[ci],
	 comp_height - dst_blk_y -
	 (JDIMENSION) compptr->v_samp_factor,
	 (JDIMENSION) compptr->v_samp_factor, FALSE);
      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
	dst_row_ptr = dst_buffer[offset_y];
	src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
	for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
	  dst_ptr = dst_row_ptr[dst_blk_x];
	  src_ptr = src_row_ptr[comp_width - dst_blk_x - 1];
	  for (i = 0; i < DCTSIZE; i += 2) {
	    /* For even row, negate every odd column. */
	    for (j = 0; j < DCTSIZE; j += 2) {
	      *dst_ptr++ = *src_ptr++;
	      *dst_ptr++ = - *src_ptr++;
	    }
	    /* For odd row, negate every even column. */
	    for (j = 0; j < DCTSIZE; j += 2) {
	      *dst_ptr++ = - *src_ptr++;
	      *dst_ptr++ = *src_ptr++;
	    }
	  }
	}
      }
    }
  }
}

/** Transverse (horizontal mirroring, transposition, horizontal mirroring)
 * The code has been adapted from transupp.c.
 * @param srcinfo		source image information
 * @param dstinfo		destination image information
 * @param src_coef_arrays	coefficient arrays for the source image
 * @param dst_coef_arrays	coefficient arrays for the destination image
 */
static void
transverse (struct jpeg_decompress_struct* srcinfo,
	    struct jpeg_compress_struct* dstinfo,
	    struct jvirt_barray_control** src_coef_arrays,
	    struct jvirt_barray_control** dst_coef_arrays)
{
  JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
  int ci, i, j, offset_x, offset_y;
  JBLOCKARRAY src_buffer, dst_buffer;
  JCOEFPTR src_ptr, dst_ptr;
  jpeg_component_info* compptr;

  MCU_cols = srcinfo->image_height / (dstinfo->max_h_samp_factor * DCTSIZE);
  MCU_rows = srcinfo->image_width / (dstinfo->max_v_samp_factor * DCTSIZE);

  for (ci = 0; ci < dstinfo->num_components; ci++) {
    compptr = dstinfo->comp_info + ci;
    comp_width = MCU_cols * compptr->h_samp_factor;
    comp_height = MCU_rows * compptr->v_samp_factor;
    for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
	 dst_blk_y += compptr->v_samp_factor) {
      dst_buffer = (*srcinfo->mem->access_virt_barray)
	((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
	 (JDIMENSION) compptr->v_samp_factor, TRUE);
      for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
	for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
	     dst_blk_x += compptr->h_samp_factor) {
	  src_buffer = (*srcinfo->mem->access_virt_barray)
	    ((j_common_ptr) srcinfo, src_coef_arrays[ci],
	     comp_width - dst_blk_x -
	     (JDIMENSION) compptr->h_samp_factor,
	     (JDIMENSION) compptr->h_samp_factor, FALSE);
	  for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
	    dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
	    src_ptr = src_buffer[compptr->h_samp_factor - offset_x - 1]
	      [comp_height - dst_blk_y - offset_y - 1];
	    for (i = 0; i < DCTSIZE; i++) {
	      for (j = 0; j < DCTSIZE; j++) {
		dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
		j++;
		dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
	      }
	      i++;
	      for (j = 0; j < DCTSIZE; j++) {
		dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
		j++;
		dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
	      }
	    }
	  }
	}
      }
    }
  }
}

/** Request workspace for the image transformation.
 * This routine figures out the size that the output image will be
 * (which implies that all the transform parameters must be set before
 * it is called).
 *
 * We allocate the workspace virtual arrays from the source decompression
 * object, so that all the arrays (both the original data and the workspace)
 * will be taken into account while making memory management decisions.
 * Hence, this routine must be called after jpeg_read_header (which reads
 * the image dimensions) and before jpeg_read_coefficients (which realizes
 * the source's virtual arrays).

 * The code has been adapted from transupp.c.
 * @param srcinfo	source image information
 * @param info		transformation information
 */
static void
jtransform_request_workspace (struct jpeg_decompress_struct* srcinfo,
			      struct jpeg_transform_info* info)
{
  struct jvirt_barray_control** coef_arrays = NULL;
  JDIMENSION width_in_iMCUs, height_in_iMCUs;
  JDIMENSION width_in_blocks, height_in_blocks;
  int ci, h_samp_factor, v_samp_factor;

  /* If there is only one output component, force the iMCU size to be 1;
   * else use the source iMCU size.  (This allows us to do the right thing
   * when reducing color to grayscale, and also provides a handy way of
   * cleaning up "funny" grayscale images whose sampling factors are not 1×1.)
   */

  switch (info->transform) {
  case JXFORM_TRANSPOSE:
  case JXFORM_TRANSVERSE:
  case JXFORM_ROT_90:
  case JXFORM_ROT_270:
    info->output_width = srcinfo->image_height;
    info->output_height = srcinfo->image_width;
    if (srcinfo->num_components == 1) {
      info->max_h_samp_factor = 1;
      info->max_v_samp_factor = 1;
    }
    else {
      info->max_h_samp_factor = srcinfo->max_v_samp_factor;
      info->max_v_samp_factor = srcinfo->max_h_samp_factor;
    }
    break;
  default:
    info->output_width = srcinfo->image_width;
    info->output_height = srcinfo->image_height;
    if (srcinfo->num_components == 1) {
      info->max_h_samp_factor = 1;
      info->max_v_samp_factor = 1;
    }
    else {
      info->max_h_samp_factor = srcinfo->max_h_samp_factor;
      info->max_v_samp_factor = srcinfo->max_v_samp_factor;
    }
    break;
  }

  /* Figure out whether we need workspace arrays,
   * and if so whether they are transposed relative to the source.
   */
  switch (info->transform) {
  case JXFORM_NONE:
  case JXFORM_FLIP_H:
    break;
  case JXFORM_ROT_90:
  case JXFORM_ROT_270:
  case JXFORM_TRANSVERSE:
  case JXFORM_TRANSPOSE:
  case JXFORM_ROT_180:
  case JXFORM_FLIP_V:
    /* Allocate workspace.
     * Note that we allocate arrays padded out to the next iMCU boundary,
     * so that transform routines need not worry about missing edge blocks.
     */
    coef_arrays = (struct jvirt_barray_control* *)
      (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
				    sizeof (struct jvirt_barray_control*) *
				    srcinfo->num_components);
    width_in_iMCUs = (JDIMENSION)
      (info->output_width - 1) / info->max_h_samp_factor / DCTSIZE + 1;
    height_in_iMCUs = (JDIMENSION)
      (info->output_height - 1) / info->max_v_samp_factor / DCTSIZE + 1;
    for (ci = 0; ci < srcinfo->num_components; ci++) {
      jpeg_component_info* compptr = srcinfo->comp_info + ci;
      if (srcinfo->num_components == 1) {
	/* we're going to force samp factors to 1×1 in this case */
	h_samp_factor = v_samp_factor = 1;
      }
      else if (info->transform == JXFORM_ROT_180 ||
	       info->transform == JXFORM_FLIP_V) {
	h_samp_factor = compptr->h_samp_factor;
	v_samp_factor = compptr->v_samp_factor;
      }
      else { /* transposed */
	h_samp_factor = compptr->v_samp_factor;
	v_samp_factor = compptr->h_samp_factor;
      }
      width_in_blocks = width_in_iMCUs * h_samp_factor;
      height_in_blocks = height_in_iMCUs * v_samp_factor;
      coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
	((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE,
	 width_in_blocks, height_in_blocks, (JDIMENSION) v_samp_factor);
    }
  }

  info->workspace_coef_arrays = coef_arrays;
}


/** Transpose destination image parameters.
 * The code has been adapted from transupp.c.
 * @param dstinfo	destination image information
 */
static void
transpose_critical_parameters (struct jpeg_compress_struct* dstinfo)
{
  int tblno, i, j, ci, itemp;
  JQUANT_TBL* qtblptr;
  UINT16 qtemp;

  /* Transpose sampling factors */
  for (ci = 0; ci < dstinfo->num_components; ci++) {
    jpeg_component_info* compptr = dstinfo->comp_info + ci;
    itemp = compptr->h_samp_factor;
    compptr->h_samp_factor = compptr->v_samp_factor;
    compptr->v_samp_factor = itemp;
  }

  /* Transpose quantization tables */
  for (tblno = 0; tblno < NUM_QUANT_TBLS; tblno++) {
    qtblptr = dstinfo->quant_tbl_ptrs[tblno];
    if (qtblptr != NULL) {
      for (i = 0; i < DCTSIZE; i++) {
	for (j = 0; j < i; j++) {
	  qtemp = qtblptr->quantval[i*DCTSIZE+j];
	  qtblptr->quantval[i*DCTSIZE+j] = qtblptr->quantval[j*DCTSIZE+i];
	  qtblptr->quantval[j*DCTSIZE+i] = qtemp;
	}
      }
    }
  }
}

/** Adjust output image parameters as needed.
 * This must be called after jpeg_copy_critical_parameters ()
 * and before jpeg_write_coefficients ().
 * The code has been adapted from transupp.c.
 * @param srcinfo		source image information
 * @param dstinfo		destination image information
 * @param src_coef_arrays	coefficient arrays for the source image
 * @param info			transformation information
 * @return	virtual coefficient arrays for jpeg_write_coefficients ()
 */
static struct jvirt_barray_control**
jtransform_adjust_parameters (struct jpeg_decompress_struct* srcinfo,
			      struct jpeg_compress_struct* dstinfo,
			      struct jvirt_barray_control** src_coef_arrays,
			      struct jpeg_transform_info* info)
{
  /* Correct the destination's image dimensions as necessary
   * for rotate/flip operations.
   */
  dstinfo->image_width = info->output_width;
  dstinfo->image_height = info->output_height;

  /* Transpose destination image parameters */
  switch (info->transform) {
  case JXFORM_TRANSPOSE:
  case JXFORM_TRANSVERSE:
  case JXFORM_ROT_90:
  case JXFORM_ROT_270:
    transpose_critical_parameters (dstinfo);
  case JXFORM_NONE:
  case JXFORM_FLIP_H:
  case JXFORM_FLIP_V:
  case JXFORM_ROT_180:
    break;
  }

  return info->workspace_coef_arrays
    ? info->workspace_coef_arrays
    : src_coef_arrays;
}

/** Execute the actual transformation, if any.
 * The code has been adapted from transupp.c.
 * @param srcinfo		source image information
 * @param dstinfo		dest. image info from jpeg_write_coefficients
 * @param src_coef_arrays	coefficient arrays for the source image
 * (modified by most transformations)
 * @param info			transformation information
 */
static void
jtransform_execute_transform (struct jpeg_decompress_struct* srcinfo,
			      struct jpeg_compress_struct* dstinfo,
			      struct jvirt_barray_control** src_coef_arrays,
			      struct jpeg_transform_info* info)
{
  struct jvirt_barray_control** dst_coef_arrays = info->workspace_coef_arrays;

  /* Note: conditions tested here should match those in switch statement
   * in jtransform_request_workspace ()
   */
  switch (info->transform) {
  case JXFORM_NONE:
    break;
  case JXFORM_FLIP_H:
    flip_h (srcinfo, dstinfo, src_coef_arrays);
    break;
  case JXFORM_FLIP_V:
    flip_v (srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
    break;
  case JXFORM_TRANSPOSE:
    transpose (srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
    break;
  case JXFORM_TRANSVERSE:
    transverse (srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
    break;
  case JXFORM_ROT_90:
    rot90 (srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
    break;
  case JXFORM_ROT_180:
    rot180 (srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
    break;
  case JXFORM_ROT_270:
    rot270 (srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
    break;
  }
}

/** Setup decompression object to save desired markers in memory.
 * This must be called before jpeg_read_header () to have the desired effect.
 * The code has been adapted from transupp.c.
 * @param cinfo		source image information
 */
static void
jcopy_markers_setup (struct jpeg_decompress_struct* cinfo)
{
  int m;
  /* save COM markers */
  jpeg_save_markers (cinfo, JPEG_COM, 0xFFFF);
  /* Save APP0..APP15 markers */
  for (m = 0; m < 16; m++)
    jpeg_save_markers (cinfo, JPEG_APP0 + m, 0xFFFF);
}

/** Copy markers saved in the given source object to the destination object.
 * This should be called just after jpeg_start_compress () or
 * jpeg_write_coefficients ().
 * Note that those routines will have written the SOI, and also the
 * JFIF APP0 or Adobe APP14 markers if selected.
 * The code has been adapted from transupp.c.
 * @param srcinfo		source image information
 * @param dstinfo		destination image information
 */
static void
jcopy_markers_execute (struct jpeg_decompress_struct* srcinfo,
		       struct jpeg_compress_struct* dstinfo)
{
  jpeg_saved_marker_ptr marker;

  /* To avoid confusion, we do not output JFIF and Adobe APP14 markers
   * if the encoder library already wrote one.
   */
  for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
    if (dstinfo->write_JFIF_header &&
	marker->marker == JPEG_APP0 &&
	marker->data_length >= 5 &&
	GETJOCTET (marker->data[0]) == 0x4A &&
	GETJOCTET (marker->data[1]) == 0x46 &&
	GETJOCTET (marker->data[2]) == 0x49 &&
	GETJOCTET (marker->data[3]) == 0x46 &&
	GETJOCTET (marker->data[4]) == 0)
      continue;			/* reject duplicate JFIF */
    if (dstinfo->write_Adobe_marker &&
	marker->marker == JPEG_APP0+14 &&
	marker->data_length >= 5 &&
	GETJOCTET (marker->data[0]) == 0x41 &&
	GETJOCTET (marker->data[1]) == 0x64 &&
	GETJOCTET (marker->data[2]) == 0x6F &&
	GETJOCTET (marker->data[3]) == 0x62 &&
	GETJOCTET (marker->data[4]) == 0x65)
      continue;			/* reject duplicate Adobe */
#ifdef NEED_FAR_POINTERS
    /* We could use jpeg_write_marker if the data weren't FAR... */
    {
      unsigned int i;
      jpeg_write_m_header (dstinfo, marker->marker, marker->data_length);
      for (i = 0; i < marker->data_length; i++)
	jpeg_write_m_byte (dstinfo, marker->data[i]);
    }
#else
    jpeg_write_marker (dstinfo, marker->marker,
		       marker->data, marker->data_length);
#endif
  }
}
/* code adapted from transupp.c ends */

/** Byte order of the Exif data */
enum endianness {
  LSB,	/**< most significant byte first (Motorola) */
  MSB	/**< least significant byte first (Intel) */
};

/** Read a 2-octet quantity from the Exif record
 * @param data		offset to the Exif data
 * @param endian	the byte order of the data
 * @return		the quantity read
 */
static unsigned
exif_read2 (JOCTET FAR* data,
	    enum endianness endian)
{
  return endian
    ? GETJOCTET (data[0]) << 8 | GETJOCTET (data[1])
    : GETJOCTET (data[0]) | GETJOCTET (data[1]) << 8;
}

/** Read a 4-octet quantity from the Exif record
 * @param data		offset to the Exif data
 * @param endian	the byte order of the data
 * @return		the quantity read
 */
static unsigned long
exif_read4 (JOCTET FAR* data,
	    enum endianness endian)
{
  return endian
    ? ((unsigned long) GETJOCTET (data[0])) << 24 |
    ((unsigned long) GETJOCTET (data[1])) << 16 |
    ((unsigned long) GETJOCTET (data[2])) << 8 |
    ((unsigned long) GETJOCTET (data[3]))
    : ((unsigned long) GETJOCTET (data[0])) |
    ((unsigned long) GETJOCTET (data[1])) << 8 |
    ((unsigned long) GETJOCTET (data[2])) << 16 |
    ((unsigned long) GETJOCTET (data[3])) << 24;
}

/** Parse an Exif date stamp and convert it to seconds since the epoch
 * @param date	the date to be checked
 * @return	seconds since the epoch; -1 on failure
 */
static time_t
exif_date (JOCTET FAR* date)
{
  struct tm stamp;
  time_t t;
  unsigned year, month, day, h, m, s;
  unsigned i;
  if (date[19])
    return -1; /* not NUL-terminated */
  for (i = 19; i--; ) {
    switch (i) {
    case 4: case 7: case 13: case 16:
      if (date[i] != ':')
	return -1; /* improper field terminator */
      break;
    case 10:
      if (date[i] != ' ')
	return -1; /* date and time not separated by space */
      break;
    default:
      if (date[i] < '0' || date[i] > '9')
	return -1; /* non-digits in date or time */
    }
  }
  year =
    (date[0] - '0') * 1000 + (date[1] - '0') * 100 +
    (date[2] - '0') * 10 + (date[3] - '0') - 1900;
  month = (date[5] - '0') * 10 + (date[6] - '0') - 1;
  day = (date[8] - '0') * 10 + (date[9] - '0');
  h = (date[11] - '0') * 10 + (date[12] - '0');
  m = (date[14] - '0') * 10 + (date[15] - '0');
  s = (date[17] - '0') * 10 + (date[18] - '0');
  stamp.tm_sec = s; stamp.tm_min = m; stamp.tm_hour = h;
  stamp.tm_mday = day; stamp.tm_mon = month; stamp.tm_year = year;
  stamp.tm_wday = stamp.tm_yday = stamp.tm_isdst = -1;
  t = mktime (&stamp);
  return
    stamp.tm_sec == s && stamp.tm_min == m && stamp.tm_hour == h &&
    stamp.tm_mday == day && stamp.tm_mon == month && stamp.tm_year == year
    ? t
    : -1;
}

/** error messages from the Exif parser */
static const char* exif_message_table[] = {
  0,
  "Exif: unknown byte order",
  "Exif: invalid string data",
  "Exif: IFD0 offset out of bounds",
  "Exif: premature end of IFD0",
  "Exif: IFD1 offset out of bounds",
  "Exif: premature end of IFD1",
  "Exif: unexpected \"next\" pointer in IFD1",
  "Exif: duplicate Orientation record",
  "Exif: invalid Orientation record",
  "Exif: duplicate DateTime record",
  "Exif: invalid DateTime record",
  "Exif: duplicate DateTimeOriginal record",
  "Exif: invalid DateTimeOriginal record",
  "Exif: duplicate DateTimeDigitized record",
  "Exif: invalid DateTimeDigitized record",
  "Exif: duplicate SubIFD",
  "Exif: SubIFD offset out of bounds",
  "Exif: premature end of SubIFD",
  "Exif: unexpected \"next\" pointer in SubIFD",
  "Exif: duplicate ExifImageWidth",
  "Exif: invalid ExifImageWidth",
  "Exif: duplicate ExifImageHeight",
  "Exif: invalid ExifImageHeight",
  "Exif: duplicate JpegIFByteCount",
  "Exif: JpegIFByteCount out of bounds",
  "Exif: duplicate JpegIFOffset",
  "Exif: JpegIFOffset out of bounds",
};

/** error codes from the Exif parser */
enum exif_message {
  EXIF_FIRSTCODE = 1000,	/**< first message code */
  EXIF_UNKNOWN_ENDIAN,		/**< unknown byte order */
  EXIF_STRING_INVALID,		/**< invalid string data in IFD0 */
  EXIF_IFD0_OOB,		/**< IFD0 out of bounds */
  EXIF_IFD0_EOF,		/**< unexpected end of IFD0 */
  EXIF_IFD1_OOB,		/**< IFD1 out of bounds */
  EXIF_IFD1_EOF,		/**< unexpected end of IFD1 */
  EXIF_IFD1_UNEX,		/**< unexpected next pointer in IFD1 */
  EXIF_ORIENTATION_DUP,		/**< duplicate Orientation record */
  EXIF_ORIENTATION_INVALID,	/**< invalid Orientation record */
  EXIF_DATETIME_DUP,		/**< duplicate DateTime record */
  EXIF_DATETIME_INVALID,	/**< invalid DateTime record */
  EXIF_DTORIGINAL_DUP,		/**< duplicate DateTimeOriginal record */
  EXIF_DTORIGINAL_INVALID,	/**< invalid DateTimeOriginal record */
  EXIF_DTDIGITIZED_DUP,		/**< duplicate DateTimeDigitized record */
  EXIF_DTDIGITIZED_INVALID,	/**< invalid DateTimeDigitized record */
  EXIF_SUBIFD_DUP,		/**< duplicate SubIFD */
  EXIF_SUBIFD_OOB,		/**< SubIFD out of bounds */
  EXIF_SUBIFD_EOF,		/**< unexpected end of SubIFD */
  EXIF_SUBIFD_UNEX,		/**< unexpected next pointer in SubIFD */
  EXIF_WIDTH_DUP,		/**< duplicate ExifImageWidth */
  EXIF_WIDTH_INVALID,		/**< invalid ExifImageWidth */
  EXIF_HEIGHT_DUP,		/**< duplicate ExifImageHeight */
  EXIF_HEIGHT_INVALID,		/**< invalid ExifImageHeight */
  EXIF_JPEGIFB_DUP,		/**< duplicate JpegIFByteCount */
  EXIF_JPEGIFB_OOB,		/**< JpegIFByteCount out of bounds */
  EXIF_JPEGIFO_DUP,		/**< duplicate JpegIFOffset */
  EXIF_JPEGIFO_OOB,		/**< JpegIFOffset out of bounds */
  EXIF_LASTCODE
};

/** Parse the Exif information
 * @param cinfo		decompression structure
 * @param xform		automatic (-1) or enum JXFORM_CODE
 * @param timestamp	(input) pointer to timestamp to collect:
 * 0=DateTime, 1=DateTimeOriginal, 2=DateTimeDigitized
 *			(output) the timestamp of the image in the Exif data
 * @param rmifd1	flag: remove IFD1 (containing the thumbnail image)
 * @return		operation to perform on the image, -1 if not Exif
 */
static enum JXFORM_CODE
exif_parse (struct jpeg_decompress_struct* cinfo,
	    int xform,
	    time_t* timestamp,
	    int rmifd1)
{
  jpeg_saved_marker_ptr marker;

  for (marker = cinfo->marker_list; marker; marker = marker->next) {
    /** byte order of the Exif data */
    enum endianness endian = LSB;
    /** current image file directory (IFD) offset */
    unsigned long offset;

    if (marker->marker != JPEG_APP0 + 1 ||
	marker->data_length < 14)
      continue; /* Exif data is in an APP1 marker of at least 14 octets */

    if (GETJOCTET (marker->data[0]) != 0x45 ||
	GETJOCTET (marker->data[1]) != 0x78 ||
	GETJOCTET (marker->data[2]) != 0x69 ||
	GETJOCTET (marker->data[3]) != 0x66 ||
	GETJOCTET (marker->data[4]) ||
	GETJOCTET (marker->data[5]))
      continue; /* no Exif header */

    if (GETJOCTET (marker->data[6]) == 0x4d &&
	GETJOCTET (marker->data[7]) == 0x4d &&
	GETJOCTET (marker->data[8]) == 0x00 &&
	GETJOCTET (marker->data[9]) == 0x2a)
      endian = MSB;
    else if (GETJOCTET (marker->data[6]) == 0x49 &&
	     GETJOCTET (marker->data[7]) == 0x49 &&
	     GETJOCTET (marker->data[8]) == 0x2a &&
	     GETJOCTET (marker->data[9]) == 0x00)
      endian = LSB;
    else
      ERREXIT (cinfo, EXIF_UNKNOWN_ENDIAN);

    /* Get first IFD offset (offset to IFD0) */
    offset = exif_read4 (&marker->data[10], endian);
    if (offset >= (unsigned long) marker->data_length - (10 + 2))
      ERREXIT (cinfo, EXIF_IFD0_OOB);
    else {
      /** pointer to the start of the Exif data */
      JOCTET FAR* exif_data = &marker->data[6];
      /** length of Exif data */
      unsigned exif_length = marker->data_length - 6;
      /** Exif SubIFD offset */
      unsigned long subifd_offset;
      /** Exif IFD1 offset */
      unsigned long ifd1_offset;
      /** Location of the Exif IFD1 offset */
      unsigned int ifd1_offset_ptr;
      /** Location of the ExifImageWidth */
      unsigned int width_ptr = 0;
      /** Location of the ExifImageHeight */
      unsigned int height_ptr = 0;
      /** number of entries in the current image file directory */
      unsigned int ifdsize;
      /** end of current image file directory */
      unsigned int ifdend;
      /** thumbnail image size */
      unsigned long thumb_size = 0;
      /** thumbnail image offset */
      unsigned long thumb_offset = 0;
      /** string offset in a string-based IFD entry */
      unsigned long string_offset = 0;
      /** the contents of the DateTime field */
      time_t datetime = -1;
      /** the contents of the DateTimeDigitized field */
      time_t datetimeDigitized = -1;
      /** the contents of the DateTimeOriginal field */
      time_t datetimeOriginal = -1;
      /** present orientation (0=unknown, 1..8=known) */
      int orientation = 0;

      /* Get the number of IFD0 entries */
      ifdsize = exif_read2 (&exif_data[offset], endian);
      offset += 2;
      /* Get the IFD1 offset (thumbnail image) */
      ifd1_offset_ptr = ifdend = offset + 12 * ifdsize;
      if (ifdend >= exif_length - 4)
	ERREXIT (cinfo, EXIF_IFD0_EOF);
      ifd1_offset = exif_read4 (&exif_data[ifdend], endian);
      if (ifd1_offset &&
	  (ifd1_offset < ifdend + 4 ||
	   ifd1_offset > exif_length - 2))
	ERREXIT (cinfo, EXIF_IFD1_OOB);
      /* Exif SubIFD offset */
      subifd_offset = 0;

      /* Search for interesting tags in IFD0 */
      for (; ifdsize--; offset += 12) {
	unsigned int tagnum = exif_read2 (&exif_data[offset], endian);
	switch (tagnum) {
	default:
	  if (exif_read2 (&exif_data[offset + 2], endian) == 2) {
	    /* check string pointers */
	    unsigned long string_len =
	      exif_read4 (&exif_data[offset + 4], endian);
	    string_offset = exif_read4 (&exif_data[offset + 8], endian);
	    if (string_offset < ifdend ||
		string_offset >
		(ifd1_offset ? ifd1_offset : exif_length) - string_len ||
		exif_data[string_offset + string_len - 1])
	      ERREXIT (cinfo, EXIF_STRING_INVALID);
	  }
	  break;
	case 0x0112: /* Orientation */
	  if (orientation)
	    ERREXIT (cinfo, EXIF_ORIENTATION_DUP);
	  if (exif_read2 (&exif_data[offset + 2], endian) != 3 ||
	      exif_read4 (&exif_data[offset + 4], endian) != 1 ||
	      exif_read2 (&exif_data[offset + 10], endian) ||
	      (orientation = exif_read2 (&exif_data[offset + 8], endian)) <
	      1 ||
	      orientation > 8)
	    ERREXIT (cinfo, EXIF_ORIENTATION_INVALID);
	  /* set the orientation to 1 (top, left-hand) */
	  switch (endian) {
	  case LSB:
	    exif_data[offset + 8] = 1;
	    break;
	  case MSB:
	    exif_data[offset + 9] = 1;
	    break;
	  }
	  break;
	case 0x0132: /* DateTime */
	  if (datetime != -1)
	    ERREXIT (cinfo, EXIF_DATETIME_DUP);
	  if (exif_read2 (&exif_data[offset + 2], endian) != 2 ||
	      exif_read4 (&exif_data[offset + 4], endian) != 20 ||
	      (string_offset = exif_read4 (&exif_data[offset + 8], endian)) <
	      ifdend ||
	      string_offset > (ifd1_offset ? ifd1_offset : exif_length) - 20)
	    ERREXIT (cinfo, EXIF_DATETIME_INVALID);
	  datetime = exif_date (exif_data + string_offset);
	  if (datetime == -1)
	    ERREXIT (cinfo, EXIF_DATETIME_INVALID);
	  break;
	case 0x8769: /* Exif SubIFD offset */
	  if (subifd_offset)
	    ERREXIT (cinfo, EXIF_SUBIFD_DUP);
	  if (exif_read2 (&exif_data[offset + 2], endian) != 4 ||
	      exif_read4 (&exif_data[offset + 4], endian) != 1 ||
	      (subifd_offset = exif_read4 (&exif_data[offset + 8], endian)) <
	      ifdend ||
	      subifd_offset > (ifd1_offset ? ifd1_offset : exif_length) - 2)
	    ERREXIT (cinfo, EXIF_SUBIFD_OOB);
	  break;
	}
      }

      if ((offset = subifd_offset)) {
	/* Get the number of SubIFD entries */
	ifdsize = exif_read2 (&exif_data[offset], endian);
	offset += 2;
	ifdend = offset + 12 * ifdsize;
	if (ifdend >= (ifd1_offset ? ifd1_offset : exif_length) - 4)
	  ERREXIT (cinfo, EXIF_SUBIFD_EOF);
	/* Get the next IFD offset (should be zero) */
	if (exif_read4 (&exif_data[ifdend], endian))
	  ERREXIT (cinfo, EXIF_SUBIFD_UNEX);
	for (; ifdsize--; offset += 12) {
	  unsigned int tagnum = exif_read2 (&exif_data[offset], endian);
	  switch (tagnum) {
	  default:
	    if (exif_read2 (&exif_data[offset + 2], endian) == 2) {
	      /* check string pointers */
	      unsigned long string_len =
		exif_read4 (&exif_data[offset + 4], endian);
	      string_offset = exif_read4 (&exif_data[offset + 8], endian);
	      if (string_offset < ifdend ||
		  string_offset >
		  (ifd1_offset ? ifd1_offset : exif_length) - string_len)
		ERREXIT (cinfo, EXIF_STRING_INVALID);
	    }
	    break;
	  case 0x9003: /* DateTimeOriginal */
	  case 0x9004: /* DateTimeDigitized */
	    if (exif_read2 (&exif_data[offset + 2], endian) != 2 ||
		exif_read4 (&exif_data[offset + 4], endian) != 20 ||
		(string_offset = exif_read4 (&exif_data[offset + 8], endian)) <
		ifdend ||
		string_offset > (ifd1_offset ? ifd1_offset : exif_length) - 20)
	      ERREXIT (cinfo, tagnum == 0x9003
		       ? EXIF_DTORIGINAL_INVALID
		       : EXIF_DTDIGITIZED_INVALID);
	    if (tagnum == 0x9003) {
	      if (datetimeOriginal != -1)
		ERREXIT (cinfo, EXIF_DTORIGINAL_DUP);
	      if ((datetimeOriginal = exif_date (exif_data + string_offset))
		  == -1)
		ERREXIT (cinfo, EXIF_DTORIGINAL_INVALID);
	    }
	    else {
	      if (datetimeDigitized != -1)
		ERREXIT (cinfo, EXIF_DTDIGITIZED_DUP);
	      if ((datetimeDigitized = exif_date (exif_data + string_offset))
		  == - 1)
		ERREXIT (cinfo, EXIF_DTDIGITIZED_INVALID);
	    }
	    break;
	  case 0xa002: /* ExifImageWidth */
	  case 0xa003: /* ExifImageHeight */
	    if (tagnum == 0xa002 ? width_ptr : height_ptr)
	      ERREXIT (cinfo, tagnum == 0xa002
		       ? EXIF_WIDTH_DUP
		       : EXIF_HEIGHT_DUP);
	    if (exif_read4 (&exif_data[offset + 4], endian) != 1)
	      goto errImageDimension;
	    switch (exif_read2 (&exif_data[offset + 2], endian)) {
	    case 3: /* unsigned short */
	    case 4: /* unsigned long */
	      break;
	    default:
	    errImageDimension:
	      ERREXIT (cinfo, tagnum == 0xa002
		       ? EXIF_WIDTH_INVALID
		       : EXIF_HEIGHT_INVALID);
	    }
	    if (tagnum == 0xa002)
	      width_ptr = offset;
	    else
	      height_ptr = offset;
	    break;
	  }
	}
      }

      if ((offset = ifd1_offset)) {
	/* Get the number of IFD1 entries */
	ifdsize = exif_read2 (&exif_data[offset], endian);
	offset += 2;
	ifdend = offset + 12 * ifdsize;
	if (ifdend >= exif_length - 4)
	  ERREXIT (cinfo, EXIF_IFD1_EOF);
	/* Get the next IFD offset (should be zero) */
	if (exif_read4 (&exif_data[ifdend], endian))
	  ERREXIT (cinfo, EXIF_IFD1_UNEX);
	for (; ifdsize--; offset += 12) {
	  unsigned int tagnum = exif_read2 (&exif_data[offset], endian);
	  switch (tagnum) {
	  default:
	    if (exif_read2 (&exif_data[offset + 2], endian) == 2) {
	      /* check string pointers */
	      unsigned long string_len =
		exif_read4 (&exif_data[offset + 4], endian);
	      string_offset = exif_read4 (&exif_data[offset + 8], endian);
	      if (string_offset < ifdend ||
		  string_offset > exif_length - string_len)
		ERREXIT (cinfo, EXIF_STRING_INVALID);
	    }
	    break;
	  case 0x201: /* JPEGIFOffset */
	    if (thumb_offset)
	      ERREXIT (cinfo, EXIF_JPEGIFO_DUP);
	    if (exif_read2 (&exif_data[offset + 2], endian) != 4 ||
		exif_read4 (&exif_data[offset + 4], endian) != 1 ||
		(thumb_offset = exif_read4 (&exif_data[offset + 8], endian)) <
		ifdend ||
		thumb_offset + thumb_size > exif_length + 1)
	      ERREXIT (cinfo, EXIF_JPEGIFO_OOB);
	    /* "exif_length + 1" tolerates an off-by-1 error
	       of Sony DCR-PC110E */
	    break;
	  case 0x202:
	    if (thumb_size)
	      ERREXIT (cinfo, EXIF_JPEGIFB_DUP);
	    if (exif_read2 (&exif_data[offset + 2], endian) != 4 ||
		exif_read4 (&exif_data[offset + 4], endian) != 1 ||
		!(thumb_size = exif_read4 (&exif_data[offset + 8], endian)) ||
		thumb_offset + thumb_size > exif_length + 1)
	      ERREXIT (cinfo, EXIF_JPEGIFB_OOB);
	    /* "exif_length + 1" tolerates an off-by-1 error
	       of Sony DCR-PC110E */
	    break;
	  }
	}
      }

#if 0
      if (thumb_offset && thumb_size) {
	if (thumb_offset + thumb_size > exif_length)
	  thumb_size = exif_length - thumb_offset;
	if (!exif_data[thumb_offset + thumb_size - 1])
	  thumb_size--; /* some cameras add a terminating NUL character */
	/* output the thumbnail */
	fwrite (exif_data + thumb_offset, 1, thumb_size, stdout);
      }
#endif

      /* remove the IFD1 entry (which contains the thumbnail image) */
      if (rmifd1 && ifd1_offset) {
	exif_data[ifd1_offset_ptr] =
	  exif_data[ifd1_offset_ptr + 1] = 
	  exif_data[ifd1_offset_ptr + 2] = 
	  exif_data[ifd1_offset_ptr + 3] = 0;
	exif_length = ifd1_offset;
	marker->data_length = exif_length + 6;
      }

      if (timestamp) {
	switch (*timestamp) {
	default: *timestamp = datetime; break;
	case 1: *timestamp = datetimeOriginal; break;
	case 2: *timestamp = datetimeDigitized; break;
	}
      }

      if (xform < 0 || xform > 7) {
	switch (orientation) {
	default: xform = JXFORM_NONE; break;
	case 2: xform = JXFORM_FLIP_H; break;
	case 3: xform = JXFORM_ROT_180; break;
	case 4: xform = JXFORM_FLIP_V; break;
	case 5: xform = JXFORM_TRANSPOSE; break;
	case 6: xform = JXFORM_ROT_90; break;
	case 7: xform = JXFORM_TRANSVERSE; break;
	case 8: xform = JXFORM_ROT_270; break;
	}
      }

      /* adjust ExifImageWidth and ExifImageHeight */
      if (width_ptr && height_ptr) {
	unsigned height = cinfo->image_height, width = cinfo->image_width;
	switch (xform) {
	case JXFORM_TRANSPOSE:
	case JXFORM_TRANSVERSE:
	case JXFORM_ROT_90:
	case JXFORM_ROT_270:
	  width = cinfo->image_height, height = cinfo->image_width;
	  break;
	case JXFORM_NONE:
	case JXFORM_FLIP_H:
	case JXFORM_FLIP_V:
	case JXFORM_ROT_180:
	  break;
	}
	switch (endian) {
	case LSB:
	  exif_data[width_ptr + 8] = width;
	  exif_data[width_ptr + 9] = width >> 8;
	  exif_data[width_ptr + 10] = exif_data[width_ptr + 11] = 0;
	  exif_data[height_ptr + 8] = height;
	  exif_data[height_ptr + 9] = height >> 8;
	  exif_data[height_ptr + 10] = exif_data[height_ptr + 11] = 0;
	  break;
	case MSB:
	  if (exif_data[width_ptr + 3] == 4) { /* unsigned long */
	    exif_data[width_ptr + 11] = width;
	    exif_data[width_ptr + 10] = width >> 8;
	    exif_data[width_ptr + 9] = exif_data[width_ptr + 8] = 0;
	  }
	  else { /* unsigned short */
	    exif_data[width_ptr + 11] = exif_data[width_ptr + 10] = 0;
	    exif_data[width_ptr + 9] = width;
	    exif_data[width_ptr + 8] = width >> 8;
	  }
	  if (exif_data[height_ptr + 3] == 4) { /* unsigned long */
	    exif_data[height_ptr + 11] = height;
	    exif_data[height_ptr + 10] = height >> 8;
	    exif_data[height_ptr + 9] = exif_data[height_ptr + 8] = 0;
	  }
	  else { /* unsigned short */
	    exif_data[height_ptr + 11] = exif_data[height_ptr + 10] = 0;
	    exif_data[height_ptr + 9] = height;
	    exif_data[height_ptr + 8] = height >> 8;
	  }
	  break;
	}
      }
      return xform;
    }
  }

  return -1;
}

/** libjpeg error manager */
struct exif_error_mgr
{
  /** "public" fields */
  struct jpeg_error_mgr pub;
  /** for returning to caller */
  jmp_buf setjmp_buffer;
};

/**
 * Custom error exit handler
 * @param cinfo		the libjpeg context
 */
static void
exif_error_exit (struct jpeg_common_struct* cinfo)
{
  longjmp (((struct exif_error_mgr*) cinfo->err)->setjmp_buffer, 1);
}

int
main (int argc, char **argv)
{
  /** libjpeg decompression context */
  struct jpeg_decompress_struct srcinfo;
  /** libjpeg compression context */
  struct jpeg_compress_struct dstinfo;
  /** libjpeg error handler */
  struct exif_error_mgr jerr;

  /** flag: remove Exif IFD1 */
  int rmifd1 = 1;
  /** image transformation (-1=according to the Orientation tag) */
  int transformation = -1;
  /** time stamp for output files */
  enum { tdt, tdtDigitized, tdtOriginal, tdtNone } tOutput = tdt;
  /** flag: disable the processing of options */
  int ignopt = 0;
  /** flag: use the progressive JPEG process */
  int progressive = 1;

  srcinfo.err = dstinfo.err = jpeg_std_error (&jerr.pub);
  jerr.pub.addon_message_table = exif_message_table;
  jerr.pub.first_addon_message = EXIF_FIRSTCODE;
  jerr.pub.last_addon_message = EXIF_LASTCODE;

  jpeg_create_decompress (&srcinfo);
  jpeg_create_compress (&dstinfo);

  jerr.pub.error_exit = exif_error_exit;

  for (argv++; argc > 1; argv++, argc--) {
    struct jpeg_transform_info info;
    struct jvirt_barray_control** src_coef_arrays;
    struct jvirt_barray_control** dst_coef_arrays;
    time_t datetime;
    const char* opts = ignopt || **argv != '-' ? 0 : *argv;
    /** destination file name */
    char* dstname = 0;
    /** input or output file */
    FILE* f;
    /** flag: is this a JFIF image (not Exif) */
    int jfif = 0;

    if (opts && !strcmp (opts, "--")) {
      argv++;
      argc--;
      ignopt = 1;
      continue;
    }

    while (opts && *++opts) { /* process all flags */
      switch (*opts) {
      default:
	putc ('-', stderr);
	putc (*opts, stderr);
	fputs (": unknown option (use -h for help)\n", stderr);
	return 1;
      case '?':
      case 'h':
	fputs ("exifiron 1.0\n"
	       "Orientation correction and shrinking of Exif files\n"
	       "Usage: exifiron [options] file ...\n"
	       "Options:\n"
	       "-h"
	       "\tusage\n"
	       "-0"
	       "\tautomatic orientation correction (default)\n"
	       "-1,-u"
	       "\tno transformation\n"
	       "-2,-f"
	       "\thorizontal flip\n"
	       "-3,-U"
	       "\tupside down\n"
	       "-4,-F"
	       "\tvertical flip\n"
	       "-5,-t"
	       "\ttranspose\n"
	       "-6,-R"
	       "\trotate right\n"
	       "-7,-T"
	       "\ttransverse\n"
	       "-8,-r"
	       "\trotate left\n"
	       "-e"
	       "\tremove thumbnail (default)\n"
	       "-E"
	       "\tpreserve thumbnail\n"
	       "-d,-D,-O"
	       "\tstamp with DateTime{,Digitized,Original} (default=-d)\n"
	       "-o"
	       "\tstamp with current time\n"
	       "-b"
	       "\tbaseline JPEG\n"
	       "-p"
	       "\tprogressive JPEG (default)\n", stderr);
	return 0;

      case '0': transformation = -1; break;
      case '1': case 'u': transformation = JXFORM_NONE; break;
      case '2': case 'f': transformation = JXFORM_FLIP_H; break;
      case '3': case 'U': transformation = JXFORM_ROT_180; break;
      case '4': case 'F': transformation = JXFORM_FLIP_V; break;
      case '5': case 't': transformation = JXFORM_TRANSPOSE; break;
      case '6': case 'R': transformation = JXFORM_ROT_90; break;
      case '7': case 'T': transformation = JXFORM_TRANSVERSE; break;
      case '8': case 'r': transformation = JXFORM_ROT_270; break;

      case 'e': rmifd1 = 1; break;
      case 'E': rmifd1 = 0; break;

      case 'd': tOutput = tdt; break;
      case 'D': tOutput = tdtDigitized; break;
      case 'O': tOutput = tdtOriginal; break;
      case 'o': tOutput = tdtNone; break;

      case 'b': progressive = 0; break;
      case 'p': progressive = 1; break;
      }
    }

    if (opts == *argv + 1)
      f = stdin; /* file name '-' */
    else if (opts)
      continue; /* get next parameters */
    else if (!(f = fopen (*argv, "rb"))) {
      fputs (*argv, stderr);
      perror (": open (reading)");
      return 2;
    }

    if (setjmp (jerr.setjmp_buffer)) {
      /** error message buffer for libjpeg */
      static char jpeg_msg[JMSG_LENGTH_MAX];
      fputs (*argv, stderr);
      srcinfo.err->format_message ((j_common_ptr) &srcinfo, jpeg_msg);
      fputs (": ", stderr);
      fputs (jpeg_msg, stderr);
      putc ('\n', stderr);
      if (f && !opts) fclose (f);
      if (dstname) free (dstname);
      jpeg_destroy_compress (&dstinfo);
      jpeg_destroy_decompress (&srcinfo);
      return 2;
    }

    jpeg_stdio_src (&srcinfo, f);
    jcopy_markers_setup (&srcinfo);
    jpeg_read_header (&srcinfo, TRUE);

    datetime = tOutput;
    info.transform = exif_parse (&srcinfo, transformation,
				 tOutput == tdtNone ? 0 : &datetime,
				 rmifd1);
    jfif = info.transform == -1;
    if (jfif)
      info.transform = JXFORM_NONE;

    jtransform_request_workspace (&srcinfo, &info);

    /* Read source file as DCT coefficients */
    src_coef_arrays = jpeg_read_coefficients (&srcinfo);
    if (opts)
      f = stdout;
    else
      fclose (f), f = 0;
    /* Initialize destination compression parameters from source values */
    jpeg_copy_critical_parameters (&srcinfo, &dstinfo);

    dst_coef_arrays = jtransform_adjust_parameters (&srcinfo, &dstinfo,
						    src_coef_arrays,
						    &info);
    if (!opts) {
      /* allocate destination file name, change the last character to '0' */
      unsigned len = strlen (*argv) + 1;
      if (!(dstname = malloc (len))) {
	fputs (*argv, stderr);
	fputs (": out of memory\n", stderr);
	goto quit;
      }
      memcpy (dstname, *argv, len);
      dstname[len - 2] = '0';
      if (!(f = fopen (dstname, "wb"))) {
	fputs (dstname, stderr);
	perror (": open (writing)");
	free (dstname); dstname = 0;
	goto quit;
      }
    }
    /* to do: allocate destination file name, open destination file */
    jpeg_stdio_dest (&dstinfo, f);
    dstinfo.optimize_coding = TRUE;
    dstinfo.write_JFIF_header = jfif;
    if (progressive)
      jpeg_simple_progression (&dstinfo);
    jpeg_write_coefficients (&dstinfo, dst_coef_arrays);
    jcopy_markers_execute (&srcinfo, &dstinfo);

    jtransform_execute_transform (&srcinfo, &dstinfo, src_coef_arrays, &info);
    jpeg_finish_compress (&dstinfo);
    if (!opts) {
      fclose (f);
      if (datetime != tOutput) {
	/* modify the file timestamp */
	struct utimbuf utim;
	utim.actime = utim.modtime = datetime;
	if (utime (dstname, &utim)) {
	  fputs (*argv, stderr);
	  perror (": utime");
	  /* non-fatal error, don't abort */
	}
      }
      if (rename (dstname, *argv)) {
	fputs (dstname, stderr);
	fputs (" -> ", stderr);
	fputs (*argv, stderr);
	perror (": rename");
	free (dstname), dstname = 0;
      quit:
	jpeg_destroy_compress (&dstinfo);
	jpeg_destroy_decompress (&srcinfo);
	return 2;
      }
      free (dstname), dstname = 0;
    }
    f = 0;
    (void) jpeg_finish_decompress (&srcinfo);
  }

  jpeg_destroy_compress (&dstinfo);
  jpeg_destroy_decompress (&srcinfo);
  return 0;
}
