]> granicus.if.org Git - libjpeg-turbo/commitdiff
Expose libjpeg lossless transform feature in TurboJPEG/OSS
authorDRC <dcommander@users.sourceforge.net>
Sat, 26 Feb 2011 22:02:37 +0000 (22:02 +0000)
committerDRC <dcommander@users.sourceforge.net>
Sat, 26 Feb 2011 22:02:37 +0000 (22:02 +0000)
git-svn-id: svn+ssh://svn.code.sf.net/p/libjpeg-turbo/code/trunk@464 632fc199-4ca6-4c93-a231-07263d6284db

Makefile.am
jpgtest.c
turbojpeg-mapfile
turbojpeg-mapfile.jni
turbojpeg.h
turbojpegl.c

index 30c9c6d2e11c075c3ed98dabba10008d1489cc8b..3131953ce345656aa956e26238d0064c71bacfdf 100644 (file)
@@ -34,7 +34,8 @@ libjpeg_la_SOURCES += jdarith.c
 
 endif
 
-libturbojpeg_la_SOURCES = $(libjpeg_la_SOURCES) turbojpegl.c turbojpeg.h
+libturbojpeg_la_SOURCES = $(libjpeg_la_SOURCES) turbojpegl.c turbojpeg.h \
+       transupp.c transupp.h
 
 if WITH_JNI
 
index 0ec2a1534df12c13168b9006667a4d212e696e19..c60cd2a7c9cf2ca5a33dbfeead581759843a4afd 100644 (file)
--- a/jpgtest.c
+++ b/jpgtest.c
@@ -119,7 +119,8 @@ int decomptest(unsigned char *srcbuf, unsigned char **jpegbuf,
                {
                        for(j=0; j<w; j+=tilesizex)
                        {
-                               int tempw=min(tilesizex, w-j), temph=min(tilesizey, h-i);
+                               int tempw=dotile? min(tilesizex, w-j):scaledw;
+                               int temph=dotile? min(tilesizey, h-i):scaledh;
                                if(yuv==YUVDECODE)
                                {
                                        if(tjDecompressToYUV(hnd, jpegbuf[tilen], comptilesize[tilen],
@@ -127,7 +128,7 @@ int decomptest(unsigned char *srcbuf, unsigned char **jpegbuf,
                                                _throwtj("executing tjDecompressToYUV()");
                                }
                                else if(tjDecompress(hnd, jpegbuf[tilen], comptilesize[tilen],
-                                       &rgbbuf[pitch*i+ps*j], scaledw, pitch, scaledh, ps, flags)==-1)
+                                       &rgbbuf[pitch*i+ps*j], tempw, pitch, temph, ps, flags)==-1)
                                        _throwtj("executing tjDecompress()");
                                tilen++;
                        }
@@ -390,61 +391,167 @@ void dotest(unsigned char *srcbuf, int w, int h, int jpegsub, int qual,
 void dodecomptest(char *filename)
 {
        FILE *file=NULL;  tjhandle hnd=NULL;
-       unsigned char *jpegbuf=NULL;
+       unsigned char **jpegbuf=NULL, *srcbuf=NULL;
+       unsigned long *comptilesize=NULL, srcbufsize, jpgbufsize;
        int w=0, h=0, jpegsub=-1;
-       unsigned long jpgbufsize=0;
        char *temp=NULL;
+       int i, j, tilesizex, tilesizey, numtilesx, numtilesy;
+       double start, elapsed;
+       int flags=(forcemmx?TJ_FORCEMMX:0)|(forcesse?TJ_FORCESSE:0)
+               |(forcesse2?TJ_FORCESSE2:0)|(forcesse3?TJ_FORCESSE3:0);
+       int ps=_ps[pf], tilen;
 
        useppm=1;
 
        if((file=fopen(filename, "rb"))==NULL)
                _throwunix("opening file");
-       if(fseek(file, 0, SEEK_END)<0 || (jpgbufsize=ftell(file))<0)
+       if(fseek(file, 0, SEEK_END)<0 || (srcbufsize=ftell(file))<0)
                _throwunix("determining file size");
-       if((jpegbuf=(unsigned char *)malloc(jpgbufsize))==NULL)
+       if((srcbuf=(unsigned char *)malloc(srcbufsize))==NULL)
                _throwunix("allocating memory");
        if(fseek(file, 0, SEEK_SET)<0)
                _throwunix("setting file position");
-       if(fread(jpegbuf, jpgbufsize, 1, file)<1)
+       if(fread(srcbuf, srcbufsize, 1, file)<1)
                _throwunix("reading JPEG data");
        fclose(file);  file=NULL;
 
        temp=strrchr(filename, '.');
        if(temp!=NULL) *temp='\0';
 
-       if((hnd=tjInitDecompress())==NULL) _throwtj("executing tjInitDecompress()");
-       if(tjDecompressHeader2(hnd, jpegbuf, jpgbufsize, &w, &h, &jpegsub)==-1)
+       if((hnd=tjInitTransform())==NULL) _throwtj("executing tjInitTransform()");
+       if(tjDecompressHeader2(hnd, srcbuf, srcbufsize, &w, &h, &jpegsub)==-1)
                _throwtj("executing tjDecompressHeader2()");
-       if(tjDestroy(hnd)==-1) _throwtj("executing tjDestroy()");
-       hnd=NULL;
 
-       if(quiet==1)
-       {
-               printf("All performance values in Mpixels/sec\n\n");
-               printf("Bitmap\tBitmap\tJPEG\tImage Size\tDecomp\n"),
-               printf("Format\tOrder\tFormat\t  X    Y  \tPerf\n\n");
-               printf("%s\t%s\t%s\t%-4d  %-4d\t", _pfname[pf], bu?"BU":"TD",
-                       _subnamel[jpegsub], w, h);
-       }
-       else
+       if(yuv) dotile=0;
+
+       if(dotile)
        {
-               if(yuv==YUVDECODE)
-                       printf(">>>>>  JPEG --> YUV %s  <<<<<\n", _subnamel[jpegsub]);
-               else
+               tilesizex=tilesizey=512;
+               if(quiet==1)
+               {
+                       printf("All performance values in Mpixels/sec\n\n");
+                       printf("Bitmap\tBitmap\tJPEG\tTile Size\tXform\tCompr\tDecomp\n");
+                       printf("Format\tOrder\tFormat\t X    Y  \tPerf \tRatio\tPerf\n\n");
+               }
+               else if(!quiet)
+               {
                        printf(">>>>>  JPEG --> %s (%s)  <<<<<\n", _pfname[pf],
                                bu?"Bottom-up":"Top-down");
-               printf("\nImage size: %d x %d", w, h);
-               if(scalefactor!=1) printf(" --> %d x %d", (w+scalefactor-1)/scalefactor,
-                       (h+scalefactor-1)/scalefactor);
-               printf("\n");
+               }
+               do
+               {
+                       tilesizex*=2;  if(tilesizex>w) tilesizex=w;
+                       tilesizey*=2;  if(tilesizey>h) tilesizey=h;
+                       numtilesx=(w+tilesizex-1)/tilesizex;
+                       numtilesy=(h+tilesizey-1)/tilesizey;
+                       if((comptilesize=(unsigned long *)malloc(sizeof(unsigned long)
+                               *numtilesx*numtilesy))==NULL
+                               || (jpegbuf=(unsigned char **)malloc(sizeof(unsigned char *)
+                               *numtilesx*numtilesy))==NULL)
+                               _throwunix("allocating image buffers");
+                       memset(jpegbuf, 0, sizeof(unsigned char *)*numtilesx*numtilesy);
+
+                       for(i=0; i<numtilesx*numtilesy; i++)
+                       {
+                               if((jpegbuf[i]=(unsigned char *)malloc(
+                                       TJBUFSIZE(tilesizex, tilesizey))) == NULL)
+                                       _throwunix("allocating image buffers");
+                       }
+
+                       if(quiet==1)
+                       {
+                               printf("%s\t%s\t%s\t",  _pfname[pf], bu?"BU":"TD", _subnamel[jpegsub]);
+                               if(tilesizex==w && tilesizey==h) printf("Full     \t");
+                               else printf("%-4d %-4d\t", tilesizex, tilesizey);
+                       }
+
+                       start=rrtime();
+                       jpgbufsize=0;  tilen=0;
+                       for(i=0; i<h; i+=tilesizey)
+                       {
+                               for(j=0; j<w; j+=tilesizex)
+                                       {
+                                               int tempw=min(tilesizex, w-j), temph=min(tilesizey, h-i);
+                                               if(tjTransform(hnd, srcbuf, srcbufsize, jpegbuf[tilen],
+                                                       &comptilesize[tilen], j, i, tempw, temph, TJXFORM_NONE,
+                                                       TJXFORM_CROP, flags)
+                                                       ==-1)
+                                               _throwtj("executing tjTransform()");
+                                       jpgbufsize+=comptilesize[tilen];
+                                       tilen++;
+                               }
+                       }
+                       elapsed=rrtime()-start;
+
+                       if(quiet)
+                       {
+                               printsigfig((double)(w*h)/1000000./elapsed, 4);
+                               printf("%c", quiet==2? '\n':'\t');
+                               printsigfig((double)(w*h*ps)/(double)jpgbufsize, 4);
+                               printf("%c", quiet==2? '\n':'\t');
+                       }
+                       else if(!quiet)
+                       {
+                               if(tilesizex==w && tilesizey==h) printf("\nFull image\n");
+                               else printf("\nTile size: %d x %d\n", tilesizex, tilesizey);
+                               printf("X--> Frame rate:           %f fps\n", 1.0/elapsed);
+                               printf("     Output image size:    %lu bytes\n", jpgbufsize);
+                               printf("     Compression ratio:    %f:1\n",
+                                       (double)(w*h*ps)/(double)jpgbufsize);
+                               printf("     Source throughput:    %f Megapixels/sec\n",
+                                       (double)(w*h)/1000000./elapsed);
+                               printf("     Output bit stream:    %f Megabits/sec\n",
+                                       (double)jpgbufsize*8./1000000./elapsed);
+                       }
+
+                       if(decomptest(NULL, jpegbuf, comptilesize, NULL, w, h, jpegsub, 0,
+                               filename, tilesizex, tilesizey)==-1)
+                               goto bailout;
+
+                       // Cleanup
+                       for(i=0; i<numtilesx*numtilesy; i++)
+                               {free(jpegbuf[i]);  jpegbuf[i]=NULL;}
+                       free(jpegbuf);  jpegbuf=NULL;
+                       if(comptilesize) {free(comptilesize);  comptilesize=NULL;}
+               } while(tilesizex<w || tilesizey<h);
        }
+       else
+       {
+               if(quiet==1)
+               {
+                       printf("All performance values in Mpixels/sec\n\n");
+                       printf("Bitmap\tBitmap\tJPEG\tImage Size\tDecomp\n");
+                       printf("Format\tOrder\tFormat\t  X    Y  \tPerf\n\n");
+                       printf("%s\t%s\t%s\t%-4d  %-4d\t", _pfname[pf], bu?"BU":"TD",
+                               _subnamel[jpegsub], w, h);
+               }
+               else if(!quiet)
+               {
+                       if(yuv==YUVDECODE)
+                               printf(">>>>>  JPEG --> YUV %s  <<<<<\n", _subnamel[jpegsub]);
+                       else
+                               printf(">>>>>  JPEG --> %s (%s)  <<<<<\n", _pfname[pf],
+                                       bu?"Bottom-up":"Top-down");
+                       printf("\nImage size: %d x %d", w, h);
+                       if(scalefactor!=1) printf(" --> %d x %d", (w+scalefactor-1)/scalefactor,
+                               (h+scalefactor-1)/scalefactor);
+                       printf("\n");
+               }
 
-       decomptest(NULL, &jpegbuf, &jpgbufsize, NULL, w, h, jpegsub, 0, filename, w,
-               h);
+               decomptest(NULL, &srcbuf, &srcbufsize, NULL, w, h, jpegsub, 0, filename,
+                       w, h);
+       }
 
        bailout:
        if(file) {fclose(file);  file=NULL;}
-       if(jpegbuf) {free(jpegbuf);  jpegbuf=NULL;}
+       if(jpegbuf)
+       {
+               for(i=0; i<numtilesx*numtilesy; i++)
+                       {if(jpegbuf[i]) free(jpegbuf[i]);  jpegbuf[i]=NULL;}
+               free(jpegbuf);  jpegbuf=NULL;
+       }
+       if(comptilesize) {free(comptilesize);  comptilesize=NULL;}
+       if(srcbuf) {free(srcbuf);  srcbuf=NULL;}
        if(hnd) {tjDestroy(hnd);  hnd=NULL;}
        return;
 }
index 4ad45d1b42cae298485fed038eb47c39f3f6bf15..32bfbabfc2655abc8131c4a372a09a7d762a0228 100755 (executable)
@@ -26,4 +26,6 @@ TURBOJPEG_1.2
 {
        global:
                tjGetScaledSize;
+               tjInitTransform;
+               tjTransform;
 } TURBOJPEG_1.1;
index acdef70f6309c01ce758dfefb31d779705565ba9..0d5d1db44a4aec7888a58cb12f1f2ef2a3240bd1 100755 (executable)
@@ -26,6 +26,8 @@ TURBOJPEG_1.2
 {
        global:
                tjGetScaledSize;
+               tjInitTransform;
+               tjTransform;
                Java_org_libjpegturbo_turbojpeg_TJ_bufSize;
                Java_org_libjpegturbo_turbojpeg_TJ_bufSizeYUV;
                Java_org_libjpegturbo_turbojpeg_TJCompressor_init;
index 1c798fb9f22559909c0006a4690efcb7f212e5b7..a08a434e7e6d5d8c62e3699198b0d46cc4f51cce 100644 (file)
@@ -56,6 +56,55 @@ enum {TJ_444=0, TJ_422, TJ_420, TJ_GRAYSCALE};
 #define TJ_YUV           512
   /* Nothing to see here.  Pay no attention to the man behind the curtain. */
 
+/* Transform operations for tjTransform() */
+#define NUMXFORMOPT 8
+
+enum {
+TJXFORM_NONE=0,     /* Do not transform the position of the image pixels */
+TJXFORM_HFLIP,      /* Flip (mirror) image horizontally.  This transform is
+                       imperfect if there are any partial MCU blocks on the
+                       right edge (see below for explanation.) */
+TJXFORM_VFLIP,      /* Flip (mirror) image vertically.  This transform is
+                       imperfect if there are any partial MCU blocks on the
+                       bottom edge. */
+TJXFORM_TRANSPOSE,  /* Transpose image (flip/mirror along upper left to lower
+                       right axis.)  This transform is always perfect. */
+TJXFORM_TRANSVERSE, /* Transpose image (flip/mirror along upper right to lower
+                       left axis.)  This transform is imperfect if there are
+                       any partial MCU blocks in the image. */
+TJXFORM_ROT90,      /* Rotate image clockwise by 90 degrees.  This transform
+                       is imperfect if there are any partial MCU blocks on the
+                       bottom edge. */
+TJXFORM_ROT180,     /* Rotate image 180 degrees.  This transform is imperfect
+                       if there are any partial MCU blocks in the image. */
+TJXFORM_ROT270      /* Rotate image counter-clockwise by 90 degrees.  This
+                       transform is imperfect if there are any partial MCU
+                       blocks on the right edge. */
+};
+
+/* Transform options (these can be OR'ed together) */
+#define TJXFORM_PERFECT  1
+  /* This will cause the tjTransform() function to return an error if the
+     transform is not perfect.  Lossless transforms operate on MCU blocks,
+     which are 8x8 pixels if no chrominance subsampling is used, or 16x8 for
+     4:2:2 or 16x16 for 4:2:0.  If the image's width or height is not evenly
+     divisible by the MCU size, then there will be partial MCU blocks on the
+     right and/or bottom edges.  It is not possible to move these partial MCU
+     blocks to the top or left of the image, so any transform that would
+     require that is "imperfect."  If this option is not specified, then any
+     partial MCU blocks that cannot be transformed will be left in place, which
+     will create odd-looking strips on the right or bottom edge of the image.
+     */
+#define TJXFORM_TRIM     2
+  /* This option will cause tjTransform() to discard any partial MCU blocks
+     that cannot be transformed. */
+#define TJXFORM_CROP     4
+  /* This option will enable lossless cropping.  See the description of
+     tjTransform() below for more information. */
+#define TJXFORM_GRAY     8
+  /* This option will discard the color data in the input image and produce
+     a grayscale output image. */
+
 typedef void* tjhandle;
 
 #define TJPAD(p) (((p)+3)&(~3))
@@ -92,7 +141,7 @@ DLLEXPORT tjhandle DLLCALL tjInitCompress(void);
      int jpegsubsamp, int jpegqual, int flags)
 
   [INPUT] j = instance handle previously returned from a call to
-     tjInitCompress()
+     tjInitCompress() or tjInitTransform()
   [INPUT] srcbuf = pointer to user-allocated image buffer containing RGB or
      grayscale pixels to be compressed
   [INPUT] width = width (in pixels) of the source image
@@ -177,7 +226,7 @@ DLLEXPORT unsigned long DLLCALL TJBUFSIZEYUV(int width, int height,
   (AKA "YUV420P") format.
 
   [INPUT] j = instance handle previously returned from a call to
-     tjInitCompress()
+     tjInitCompress() or tjInitTransform()
   [INPUT] srcbuf = pointer to user-allocated image buffer containing RGB or
      grayscale pixels to be encoded
   [INPUT] width = width (in pixels) of the source image
@@ -227,7 +276,7 @@ DLLEXPORT tjhandle DLLCALL tjInitDecompress(void);
      int *width, int *height, int *jpegsubsamp)
 
   [INPUT] j = instance handle previously returned from a call to
-     tjInitDecompress()
+     tjInitDecompress() or tjInitTransform()
   [INPUT] srcbuf = pointer to a user-allocated buffer containing a JPEG image
   [INPUT] size = size of the JPEG image buffer (in bytes)
   [OUTPUT] width = width (in pixels) of the JPEG image
@@ -276,7 +325,7 @@ DLLEXPORT int DLLCALL tjGetScaledSize(int input_width, int input_height,
      int flags)
 
   [INPUT] j = instance handle previously returned from a call to
-     tjInitDecompress()
+     tjInitDecompress() or tjInitTransform()
   [INPUT] srcbuf = pointer to a user-allocated buffer containing the JPEG image
      to decompress
   [INPUT] size = size of the JPEG image buffer (in bytes)
@@ -332,7 +381,7 @@ DLLEXPORT int DLLCALL tjDecompress(tjhandle j,
   used), then an intermediate buffer copy will be performed within TurboJPEG.
 
   [INPUT] j = instance handle previously returned from a call to
-     tjInitDecompress()
+     tjInitDecompress() or tjInitTransform()
   [INPUT] srcbuf = pointer to a user-allocated buffer containing the JPEG image
      to decompress
   [INPUT] size = size of the JPEG image buffer (in bytes)
@@ -350,13 +399,66 @@ DLLEXPORT int DLLCALL tjDecompressToYUV(tjhandle j,
        unsigned char *dstbuf, int flags);
 
 
+/*
+  tjhandle tjInitTransform(void)
+
+  Creates a new JPEG transformer instance, allocates memory for the structures,
+  and returns a handle to the instance.  Most applications will only need to
+  call this once at the beginning of the program or once for each concurrent
+  thread.  Don't try to create a new instance every time you transform an
+  image, because this may cause performance to suffer in some TurboJPEG
+  implementations.
+
+  RETURNS: NULL on error
+*/
+DLLEXPORT tjhandle DLLCALL tjInitTransform(void);
+
+
+/*
+  int tjTransform(tjhandle j,
+     unsigned char *srcbuf, unsigned long srcsize,
+     unsigned char *dstbuf, unsigned long *dstsize, 
+     int x, int y, int w, int h, int op, int options, int flags)
+
+  [INPUT] j = instance handle previously returned from a call to
+     tjInitTransform()
+  [INPUT] srcbuf = pointer to a user-allocated buffer containing the JPEG image
+     to transform
+  [INPUT] srcsize = size of the source JPEG image buffer (in bytes)
+  [INPUT] dstbuf = pointer to user-allocated image buffer which will receive
+     the transformed JPEG image.  Use the TJBUFSIZE(width, height) function to
+     determine the appropriate size for this buffer based on the cropped width
+     and height.
+  [OUTPUT] dstsize = pointer to unsigned long which receives the size (in
+     bytes) of the transformed image
+  [INPUT] x, y, w, h = the left edge, top edge, width, and height of the
+     cropping region.  If (x, y) does not fall on an MCU boundary, then x and
+     y will be silently moved left and/or up to the nearest MCU boundary.  You
+     can call tjGetCroppedSize() to determine how (or if) x, y, w, and h will
+     be modified ahead of time, so you can allocate the output buffer
+     appropriately.
+  [INPUT] op = one of the transform operations described in the "Transform
+     operations" section above.
+  [INPUT] options = the bitwise OR of one or more of the transform options
+     described in the "Transform options" section above.
+  [INPUT] flags = the bitwise OR of one or more of the flags described in the
+     "Flags" section above.
+
+  RETURNS: 0 on success, -1 on error
+*/
+DLLEXPORT int DLLCALL tjTransform(tjhandle j,
+       unsigned char *srcbuf, unsigned long size,
+       unsigned char *dstbuf, unsigned long *dstsize,
+       int x, int y, int w, int h, int op, int options, int flags);
+
+
 /*
   int tjDestroy(tjhandle h)
 
   Frees structures associated with a compression or decompression instance
   
   [INPUT] h = instance handle (returned from a previous call to
-     tjInitCompress() or tjInitDecompress()
+     tjInitCompress(), tjInitDecompress(), or tjInitTransform()
 
   RETURNS: 0 on success, -1 on error
 */
index 010a072f295871bfbaf2c4f34d42b5ee0ca892f0..4755144e044b0db3cbcc039eae4c11df6d438180 100644 (file)
@@ -23,6 +23,7 @@
 #include <jerror.h>
 #include <setjmp.h>
 #include "./turbojpeg.h"
+#include "transupp.h"
 
 #ifndef min
  #define min(a,b) ((a)<(b)?(a):(b))
@@ -69,6 +70,10 @@ typedef struct _jpgstruct
 static const int hsampfactor[NUMSUBOPT]={1, 2, 2, 1};
 static const int vsampfactor[NUMSUBOPT]={1, 1, 2, 1};
 static const int pixelsize[NUMSUBOPT]={3, 3, 3, 1};
+static const JXFORM_CODE xformtypes[NUMXFORMOPT]={
+       JXFORM_NONE, JXFORM_FLIP_H, JXFORM_FLIP_V, JXFORM_TRANSPOSE,
+       JXFORM_TRANSVERSE, JXFORM_ROT_90, JXFORM_ROT_180, JXFORM_ROT_270
+};
 
 #define _throw(c) {sprintf(lasterror, "%s", c);  retval=-1;  goto bailout;}
 #define checkhandle(h) jpgstruct *j=(jpgstruct *)h; \
@@ -87,12 +92,8 @@ static void destination_noop(struct jpeg_compress_struct *cinfo)
 {
 }
 
-DLLEXPORT tjhandle DLLCALL tjInitCompress(void)
+static tjhandle _tjInitCompress(jpgstruct *j)
 {
-       jpgstruct *j=NULL;
-       if((j=(jpgstruct *)malloc(sizeof(jpgstruct)))==NULL)
-               {sprintf(lasterror, "Memory allocation failure");  return NULL;}
-       memset(j, 0, sizeof(jpgstruct));
        j->cinfo.err=jpeg_std_error(&j->jerr.pub);
        j->jerr.pub.error_exit=my_error_exit;
        j->jerr.pub.output_message=my_output_message;
@@ -112,6 +113,15 @@ DLLEXPORT tjhandle DLLCALL tjInitCompress(void)
        return (tjhandle)j;
 }
 
+DLLEXPORT tjhandle DLLCALL tjInitCompress(void)
+{
+       jpgstruct *j=NULL;
+       if((j=(jpgstruct *)malloc(sizeof(jpgstruct)))==NULL)
+               {sprintf(lasterror, "Memory allocation failure");  return NULL;}
+       memset(j, 0, sizeof(jpgstruct));
+       return _tjInitCompress(j);
+}
+
 
 DLLEXPORT unsigned long DLLCALL TJBUFSIZE(int width, int height)
 {
@@ -364,12 +374,8 @@ static void source_noop (struct jpeg_decompress_struct *dinfo)
 {
 }
 
-DLLEXPORT tjhandle DLLCALL tjInitDecompress(void)
+static tjhandle _tjInitDecompress(jpgstruct *j)
 {
-       jpgstruct *j;
-       if((j=(jpgstruct *)malloc(sizeof(jpgstruct)))==NULL)
-               {sprintf(lasterror, "Memory allocation failure");  return NULL;}
-       memset(j, 0, sizeof(jpgstruct));
        j->dinfo.err=jpeg_std_error(&j->jerr.pub);
        j->jerr.pub.error_exit=my_error_exit;
        j->jerr.pub.output_message=my_output_message;
@@ -391,6 +397,15 @@ DLLEXPORT tjhandle DLLCALL tjInitDecompress(void)
        return (tjhandle)j;
 }
 
+DLLEXPORT tjhandle DLLCALL tjInitDecompress(void)
+{
+       jpgstruct *j;
+       if((j=(jpgstruct *)malloc(sizeof(jpgstruct)))==NULL)
+               {sprintf(lasterror, "Memory allocation failure");  return NULL;}
+       memset(j, 0, sizeof(jpgstruct));
+       return _tjInitDecompress(j);
+}
+
 
 DLLEXPORT int DLLCALL tjDecompressHeader2(tjhandle h,
        unsigned char *srcbuf, unsigned long size,
@@ -682,6 +697,120 @@ DLLEXPORT int DLLCALL tjDecompressToYUV(tjhandle h,
 }
 
 
+// Transformation
+
+DLLEXPORT tjhandle DLLCALL tjInitTransform(void)
+{
+       jpgstruct *j=NULL;  tjhandle tj=NULL;
+       if((j=(jpgstruct *)malloc(sizeof(jpgstruct)))==NULL)
+               {sprintf(lasterror, "Memory allocation failure");  return NULL;}
+       memset(j, 0, sizeof(jpgstruct));
+       tj=_tjInitCompress(j);
+       if(!tj) return NULL;
+       tj=_tjInitDecompress(j);
+       return tj;
+}
+
+
+DLLEXPORT int DLLCALL tjTransform(tjhandle hnd,
+       unsigned char *srcbuf, unsigned long srcsize,
+       unsigned char *dstbuf, unsigned long *dstsize,
+       int x, int y, int w, int h, int op, int options, int flags)
+{
+       jpeg_transform_info xinfo;
+       jvirt_barray_ptr *srccoefs, *dstcoefs;
+       int retval=0;
+
+       checkhandle(hnd);
+
+       if(srcbuf==NULL || srcsize<=0 || dstbuf==NULL || dstsize==NULL
+               || x<0 || y<0 || w<0 || h<0 || op<0 || op>=NUMXFORMOPT
+               || flags<0)
+               _throw("Invalid argument in tjTransform()");
+       if(!j->initc || !j->initd)
+               _throw("Instance has not been initialized for transformation");
+
+       if(flags&TJ_FORCEMMX) putenv("JSIMD_FORCEMMX=1");
+       else if(flags&TJ_FORCESSE) putenv("JSIMD_FORCESSE=1");
+       else if(flags&TJ_FORCESSE2) putenv("JSIMD_FORCESSE2=1");
+
+       if(setjmp(j->jerr.jb))
+       {  // this will execute if LIBJPEG has an error
+               retval=-1;
+               goto bailout;
+       }
+
+       j->jsms.bytes_in_buffer=srcsize;
+       j->jsms.next_input_byte=srcbuf;
+
+       xinfo.transform=xformtypes[op];
+       xinfo.perfect=(options&TJXFORM_PERFECT)? 1:0;
+       xinfo.trim=(options&TJXFORM_TRIM)? 1:0;
+       xinfo.force_grayscale=(options&TJXFORM_GRAY)? 1:0;
+       xinfo.crop=(options&TJXFORM_CROP)? 1:0;
+
+       if(xinfo.crop)
+       {
+               xinfo.crop_xoffset=x;  xinfo.crop_xoffset_set=JCROP_POS;
+               xinfo.crop_yoffset=y;  xinfo.crop_yoffset_set=JCROP_POS;
+               if(w!=0)
+               {
+                       xinfo.crop_width=w;  xinfo.crop_width_set=JCROP_POS;
+               }
+               if(h!=0)
+               {
+                       xinfo.crop_height=h; xinfo.crop_height_set=JCROP_POS;
+               }
+       }
+
+       jcopy_markers_setup(&j->dinfo, JCOPYOPT_NONE);
+       jpeg_read_header(&j->dinfo, TRUE);
+
+       if(!jtransform_request_workspace(&j->dinfo, &xinfo))
+               _throw("Transform is not perfect");
+
+       if(!xinfo.crop)
+       {
+               w=j->dinfo.image_width;  h=j->dinfo.image_height;
+       }
+       else
+       {
+               w=xinfo.crop_width;  h=xinfo.crop_height;
+       }
+
+       j->jdms.next_output_byte=dstbuf;
+       j->jdms.free_in_buffer=TJBUFSIZE(w, h);
+
+       if(xinfo.crop)
+       {
+               if((x%xinfo.iMCU_sample_width)!=0 || (y%xinfo.iMCU_sample_height)!=0)
+               {
+                       sprintf(lasterror, "To crop this JPEG image, x must be a multiple of %d and y must be a multiple\n"
+                               "of %d.\n", xinfo.iMCU_sample_width, xinfo.iMCU_sample_height);
+                       retval=-1;  goto bailout;
+               }
+       }
+
+       srccoefs=jpeg_read_coefficients(&j->dinfo);
+       jpeg_copy_critical_parameters(&j->dinfo, &j->cinfo);
+       dstcoefs=jtransform_adjust_parameters(&j->dinfo, &j->cinfo, srccoefs,
+               &xinfo);
+       jpeg_write_coefficients(&j->cinfo, dstcoefs);
+       jcopy_markers_execute(&j->dinfo, &j->cinfo, JCOPYOPT_ALL);
+       jtransform_execute_transformation(&j->dinfo, &j->cinfo, srccoefs, &xinfo);
+
+       jpeg_finish_compress(&j->cinfo);
+       jpeg_finish_decompress(&j->dinfo);
+
+       *dstsize=TJBUFSIZE(w, h)-(unsigned long)(j->jdms.free_in_buffer);
+
+       bailout:
+       if(j->cinfo.global_state>CSTATE_START) jpeg_abort_compress(&j->cinfo);
+       if(j->dinfo.global_state>DSTATE_START) jpeg_abort_decompress(&j->dinfo);
+       return retval;
+}
+
+
 // General
 
 DLLEXPORT char* DLLCALL tjGetErrorStr(void)