/*
 * Copyright (c) 2000, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * This file contains the code to link the Java Image I/O JPEG plug-in
 * to the IJG library used to read and write JPEG files.  Much of it has
 * been copied, updated, and annotated from the jpegdecoder.c AWT JPEG
 * decoder.  Where that code was unclear, the present author has either
 * rewritten the relevant section or commented it for the sake of future
 * maintainers.
 *
 * In particular, the way the AWT code handled progressive JPEGs seems
 * to me to be only accidentally correct and somewhat inefficient.  The
 * scheme used here represents the way I think it should work. (REV 11/00)
 */

#include <stdlib.h>
#include <setjmp.h>
#include <assert.h>
#include <string.h>
#include <limits.h>

/* java native interface headers */
#include "jni.h"
#include "jni_util.h"

#include "com_sun_imageio_plugins_jpeg_JPEGImageReader.h"
#include "com_sun_imageio_plugins_jpeg_JPEGImageWriter.h"

/* headers from the JPEG library */
#include <jpeglib.h>
#include "jerror.h"

#undef MAX
#define MAX(a,b)        ((a) > (b) ? (a) : (b))

/* Cached Java method ids */
static jmethodID ImageInputStream_readID;
static jmethodID ImageInputStream_skipBytesID;
static jmethodID JPEGImageReader_warningOccurredID;
static jmethodID JPEGImageReader_warningWithMessageID;
static jmethodID JPEGImageReader_setImageDataID;
static jmethodID JPEGImageReader_acceptPixelsID;
static jmethodID JPEGImageReader_pushBackID;
static jmethodID JPEGImageReader_passStartedID;
static jmethodID JPEGImageReader_passCompleteID;
static jmethodID ImageOutputStream_writeID;
static jmethodID JPEGImageWriter_warningOccurredID;
static jmethodID JPEGImageWriter_warningWithMessageID;
static jmethodID JPEGImageWriter_writeMetadataID;
static jmethodID JPEGImageWriter_grabPixelsID;
static jfieldID JPEGQTable_tableID;
static jfieldID JPEGHuffmanTable_lengthsID;
static jfieldID JPEGHuffmanTable_valuesID;

/*
 * Defined in jpegdecoder.c.  Copy code from there if and
 * when that disappears. */
extern JavaVM *jvm;

/*
 * The following sets of defines must match the warning messages in the
 * Java code.
 */

/* Reader warnings */
#define READ_NO_EOI          0

/* Writer warnings */

/* Return codes for various ops */
#define OK     1
#define NOT_OK 0

/*
 * First we define two objects, one for the stream and buffer and one
 * for pixels.  Both contain references to Java objects and pointers to
 * pinned arrays.  These objects can be used for either input or
 * output.  Pixels can be accessed as either INT32s or bytes.
 * Every I/O operation will have one of each these objects, one for
 * the stream and the other to hold pixels, regardless of the I/O direction.
 */

/******************** StreamBuffer definition ************************/

typedef struct streamBufferStruct {
    jobject stream;            // ImageInputStream or ImageOutputStream
    jbyteArray hstreamBuffer;  // Handle to a Java buffer for the stream
    JOCTET *buf;               // Pinned buffer pointer */
    size_t bufferOffset;          // holds offset between unpin and the next pin
    size_t bufferLength;          // Allocated, nut just used
    int suspendable;           // Set to true to suspend input
    long remaining_skip;       // Used only on input
} streamBuffer, *streamBufferPtr;

/*
 * This buffer size was set to 64K in the old classes, 4K by default in the
 * IJG library, with the comment "an efficiently freadable size", and 1K
 * in AWT.
 * Unlike in the other Java designs, these objects will persist, so 64K
 * seems too big and 1K seems too small.  If 4K was good enough for the
 * IJG folks, it's good enough for me.
 */
#define STREAMBUF_SIZE 4096

/*
 * Used to signal that no data need be restored from an unpin to a pin.
 * I.e. the buffer is empty.
 */
#define NO_DATA ((size_t)-1)

// Forward reference
static void resetStreamBuffer(JNIEnv *env, streamBufferPtr sb);

/*
 * Initialize a freshly allocated StreamBuffer object.  The stream is left
 * null, as it will be set from Java by setSource, but the buffer object
 * is created and a global reference kept.  Returns OK on success, NOT_OK
 * if allocating the buffer or getting a global reference for it failed.
 */
static int initStreamBuffer(JNIEnv *env, streamBufferPtr sb) {
    /* Initialize a new buffer */
    jbyteArray hInputBuffer = (*env)->NewByteArray(env, STREAMBUF_SIZE);
    if (hInputBuffer == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Initializing Reader");
        return NOT_OK;
    }
    sb->bufferLength = (*env)->GetArrayLength(env, hInputBuffer);
    sb->hstreamBuffer = (*env)->NewGlobalRef(env, hInputBuffer);
    if (sb->hstreamBuffer == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Initializing Reader");
        return NOT_OK;
    }


    sb->stream = NULL;

    sb->buf = NULL;

    resetStreamBuffer(env, sb);

    return OK;
}

/*
 * Free all resources associated with this streamBuffer.  This must
 * be called to dispose the object to avoid leaking global references, as
 * resetStreamBuffer does not release the buffer reference.
 */
static void destroyStreamBuffer(JNIEnv *env, streamBufferPtr sb) {
    resetStreamBuffer(env, sb);
    if (sb->hstreamBuffer != NULL) {
        (*env)->DeleteGlobalRef(env, sb->hstreamBuffer);
    }
}

// Forward reference
static void unpinStreamBuffer(JNIEnv *env,
                              streamBufferPtr sb,
                              const JOCTET *next_byte);
/*
 * Resets the state of a streamBuffer object that has been in use.
 * The global reference to the stream is released, but the reference
 * to the buffer is retained.  The buffer is unpinned if it was pinned.
 * All other state is reset.
 */
static void resetStreamBuffer(JNIEnv *env, streamBufferPtr sb) {
    if (sb->stream != NULL) {
        (*env)->DeleteGlobalRef(env, sb->stream);
        sb->stream = NULL;
    }
    unpinStreamBuffer(env, sb, NULL);
    sb->bufferOffset = NO_DATA;
    sb->suspendable = FALSE;
    sb->remaining_skip = 0;
}

/*
 * Pins the data buffer associated with this stream.  Returns OK on
 * success, NOT_OK on failure, as GetPrimitiveArrayCritical may fail.
 */
static int pinStreamBuffer(JNIEnv *env,
                           streamBufferPtr sb,
                           const JOCTET **next_byte) {
    if (sb->hstreamBuffer != NULL) {
        assert(sb->buf == NULL);
        sb->buf =
            (JOCTET *)(*env)->GetPrimitiveArrayCritical(env,
                                                        sb->hstreamBuffer,
                                                        NULL);
        if (sb->buf == NULL) {
            return NOT_OK;
        }
        if (sb->bufferOffset != NO_DATA) {
            *next_byte = sb->buf + sb->bufferOffset;
        }
    }
    return OK;
}

/*
 * Unpins the data buffer associated with this stream.
 */
static void unpinStreamBuffer(JNIEnv *env,
                              streamBufferPtr sb,
                              const JOCTET *next_byte) {
    if (sb->buf != NULL) {
        assert(sb->hstreamBuffer != NULL);
        if (next_byte == NULL) {
            sb->bufferOffset = NO_DATA;
        } else {
            sb->bufferOffset = next_byte - sb->buf;
        }
        (*env)->ReleasePrimitiveArrayCritical(env,
                                              sb->hstreamBuffer,
                                              sb->buf,
                                              0);
        sb->buf = NULL;
    }
}

/*
 * Clear out the streamBuffer.  This just invalidates the data in the buffer.
 */
static void clearStreamBuffer(streamBufferPtr sb) {
    sb->bufferOffset = NO_DATA;
}

/*************************** end StreamBuffer definition *************/

/*************************** Pixel Buffer definition ******************/

typedef struct pixelBufferStruct {
    jobject hpixelObject;   // Usually a DataBuffer bank as a byte array
    unsigned int byteBufferLength;
    union pixptr {
        INT32         *ip;  // Pinned buffer pointer, as 32-bit ints
        unsigned char *bp;  // Pinned buffer pointer, as bytes
    } buf;
} pixelBuffer, *pixelBufferPtr;

/*
 * Initialize a freshly allocated PixelBuffer.  All fields are simply
 * set to NULL, as we have no idea what size buffer we will need.
 */
static void initPixelBuffer(pixelBufferPtr pb) {
    pb->hpixelObject = NULL;
    pb->byteBufferLength = 0;
    pb->buf.ip = NULL;
}

/*
 * Set the pixelBuffer to use the given buffer, acquiring a new global
 * reference for it.  Returns OK on success, NOT_OK on failure.
 */
static int setPixelBuffer(JNIEnv *env, pixelBufferPtr pb, jobject obj) {
    pb->hpixelObject = (*env)->NewGlobalRef(env, obj);
    if (pb->hpixelObject == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Setting Pixel Buffer");
        return NOT_OK;
    }
    pb->byteBufferLength = (*env)->GetArrayLength(env, pb->hpixelObject);
    return OK;
}

// Forward reference
static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb);

/*
 * Resets a pixel buffer to its initial state.  Unpins any pixel buffer,
 * releases the global reference, and resets fields to NULL.  Use this
 * method to dispose the object as well (there is no destroyPixelBuffer).
 */
static void resetPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
    if (pb->hpixelObject != NULL) {
        unpinPixelBuffer(env, pb);
        (*env)->DeleteGlobalRef(env, pb->hpixelObject);
        pb->hpixelObject = NULL;
        pb->byteBufferLength = 0;
    }
}

/*
 * Pins the data buffer.  Returns OK on success, NOT_OK on failure.
 */
static int pinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {
    if (pb->hpixelObject != NULL) {
        assert(pb->buf.ip == NULL);
        pb->buf.bp = (unsigned char *)(*env)->GetPrimitiveArrayCritical
            (env, pb->hpixelObject, NULL);
        if (pb->buf.bp == NULL) {
            return NOT_OK;
        }
    }
    return OK;
}

/*
 * Unpins the data buffer.
 */
static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) {

    if (pb->buf.ip != NULL) {
        assert(pb->hpixelObject != NULL);
        (*env)->ReleasePrimitiveArrayCritical(env,
                                              pb->hpixelObject,
                                              pb->buf.ip,
                                              0);
        pb->buf.ip = NULL;
    }
}

/********************* end PixelBuffer definition *******************/

/********************* ImageIOData definition ***********************/

#define MAX_BANDS 4
#define JPEG_BAND_SIZE 8
#define NUM_BAND_VALUES (1<<JPEG_BAND_SIZE)
#define MAX_JPEG_BAND_VALUE (NUM_BAND_VALUES-1)
#define HALF_MAX_JPEG_BAND_VALUE (MAX_JPEG_BAND_VALUE>>1)

/* The number of possible incoming values to be scaled. */
#define NUM_INPUT_VALUES (1 << 16)

/*
 * The principal imageioData object, opaque to I/O direction.
 * Each JPEGImageReader will have associated with it a
 * jpeg_decompress_struct, and similarly each JPEGImageWriter will
 * have associated with it a jpeg_compress_struct.  In order to
 * ensure that these associations persist from one native call to
 * the next, and to provide a central locus of imageio-specific
 * data, we define an imageioData struct containing references
 * to the Java object and the IJG structs.  The functions
 * that manipulate these objects know whether input or output is being
 * performed and therefore know how to manipulate the contents correctly.
 * If for some reason they don't, the direction can be determined by
 * checking the is_decompressor field of the jpegObj.
 * In order for lower level code to determine a
 * Java object given an IJG struct, such as for dispatching warnings,
 * we use the client_data field of the jpeg object to store a pointer
 * to the imageIOData object.  Maintenance of this pointer is performed
 * exclusively within the following access functions.  If you
 * change that, you run the risk of dangling pointers.
 */
typedef struct imageIODataStruct {
    j_common_ptr jpegObj;     // Either struct is fine
    jobject imageIOobj;       // A JPEGImageReader or a JPEGImageWriter

    streamBuffer streamBuf;   // Buffer for the stream
    pixelBuffer pixelBuf;     // Buffer for pixels

    jboolean abortFlag;       // Passed down from Java abort method
} imageIOData, *imageIODataPtr;

/*
 * Allocate and initialize a new imageIOData object to associate the
 * jpeg object and the Java object.  Returns a pointer to the new object
 * on success, NULL on failure.
 */
static imageIODataPtr initImageioData (JNIEnv *env,
                                       j_common_ptr cinfo,
                                       jobject obj) {

    imageIODataPtr data = (imageIODataPtr) malloc (sizeof(imageIOData));
    if (data == NULL) {
        return NULL;
    }

    data->jpegObj = cinfo;
    cinfo->client_data = data;

#ifdef DEBUG_IIO_JPEG
    printf("new structures: data is %p, cinfo is %p\n", data, cinfo);
#endif

    data->imageIOobj = (*env)->NewWeakGlobalRef(env, obj);
    if (data->imageIOobj == NULL) {
        free (data);
        return NULL;
    }
    if (initStreamBuffer(env, &data->streamBuf) == NOT_OK) {
        (*env)->DeleteWeakGlobalRef(env, data->imageIOobj);
        free (data);
        return NULL;
    }
    initPixelBuffer(&data->pixelBuf);

    data->abortFlag = JNI_FALSE;

    return data;
}

/*
 * Resets the imageIOData object to its initial state, as though
 * it had just been allocated and initialized.
 */
static void resetImageIOData(JNIEnv *env, imageIODataPtr data) {
    resetStreamBuffer(env, &data->streamBuf);
    resetPixelBuffer(env, &data->pixelBuf);
    data->abortFlag = JNI_FALSE;
}

/*
 * Releases all resources held by this object and its subobjects,
 * frees the object, and returns the jpeg object.  This method must
 * be called to avoid leaking global references.
 * Note that the jpeg object is not freed or destroyed, as that is
 * the client's responsibility, although the client_data field is
 * cleared.
 */
static j_common_ptr destroyImageioData(JNIEnv *env, imageIODataPtr data) {
    j_common_ptr ret = data->jpegObj;
    (*env)->DeleteWeakGlobalRef(env, data->imageIOobj);
    destroyStreamBuffer(env, &data->streamBuf);
    resetPixelBuffer(env, &data->pixelBuf);
    ret->client_data = NULL;
    free(data);
    return ret;
}

/******************** end ImageIOData definition ***********************/

/******************** Java array pinning and unpinning *****************/

/* We use Get/ReleasePrimitiveArrayCritical functions to avoid
 * the need to copy array elements for the above two objects.
 *
 * MAKE SURE TO:
 *
 * - carefully insert pairs of RELEASE_ARRAYS and GET_ARRAYS around
 *   callbacks to Java.
 * - call RELEASE_ARRAYS before returning to Java.
 *
 * Otherwise things will go horribly wrong. There may be memory leaks,
 * excessive pinning, or even VM crashes!
 *
 * Note that GetPrimitiveArrayCritical may fail!
 */

/*
 * Release (unpin) all the arrays in use during a read.
 */
static void RELEASE_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET *next_byte)
{
    unpinStreamBuffer(env, &data->streamBuf, next_byte);

    unpinPixelBuffer(env, &data->pixelBuf);

}

/*
 * Get (pin) all the arrays in use during a read.
 */
static int GET_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET **next_byte) {
    if (pinStreamBuffer(env, &data->streamBuf, next_byte) == NOT_OK) {
        return NOT_OK;
    }

    if (pinPixelBuffer(env, &data->pixelBuf) == NOT_OK) {
        RELEASE_ARRAYS(env, data, *next_byte);
        return NOT_OK;
    }
    return OK;
}

/****** end of Java array pinning and unpinning ***********/

/****** Error Handling *******/

/*
 * Set up error handling to use setjmp/longjmp.  This is the third such
 * setup, as both the AWT jpeg decoder and the com.sun... JPEG classes
 * setup thier own.  Ultimately these should be integrated, as they all
 * do pretty much the same thing.
 */

struct sun_jpeg_error_mgr {
  struct jpeg_error_mgr pub;    /* "public" fields */

  jmp_buf setjmp_buffer;        /* for return to caller */
};

typedef struct sun_jpeg_error_mgr * sun_jpeg_error_ptr;

/*
 * Here's the routine that will replace the standard error_exit method:
 */

METHODDEF(void)
sun_jpeg_error_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a sun_jpeg_error_mgr struct */
  sun_jpeg_error_ptr myerr = (sun_jpeg_error_ptr) cinfo->err;

  /* For Java, we will format the message and put it in the error we throw. */

  /* Return control to the setjmp point */
  longjmp(myerr->setjmp_buffer, 1);
}

/*
 * Error Message handling
 *
 * This overrides the output_message method to send JPEG messages
 *
 */

METHODDEF(void)
sun_jpeg_output_message (j_common_ptr cinfo)
{
  char buffer[JMSG_LENGTH_MAX];
  jstring string;
  imageIODataPtr data = (imageIODataPtr) cinfo->client_data;
  JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
  jobject theObject;

  /* Create the message */
  (*cinfo->err->format_message) (cinfo, buffer);

  // Create a new java string from the message
  string = (*env)->NewStringUTF(env, buffer);

  theObject = data->imageIOobj;

  if (cinfo->is_decompressor) {
      (*env)->CallVoidMethod(env, theObject,
                             JPEGImageReader_warningWithMessageID,
                             string);
  } else {
      (*env)->CallVoidMethod(env, theObject,
                             JPEGImageWriter_warningWithMessageID,
                             string);
  }
}

/* End of verbatim copy from jpegdecoder.c */

/*************** end of error handling *********************/

/*************** Shared utility code ***********************/

static void imageio_set_stream(JNIEnv *env,
                               j_common_ptr cinfo,
                               imageIODataPtr data,
                               jobject stream){
    streamBufferPtr sb;
    sun_jpeg_error_ptr jerr;

    sb = &data->streamBuf;

    resetStreamBuffer(env, sb);  // Removes any old stream

    /* Now we need a new global reference for the stream */
    if (stream != NULL) { // Fix for 4411955
        sb->stream = (*env)->NewGlobalRef(env, stream);
        if (sb->stream == NULL) {
            JNU_ThrowByName(env,
                            "java/lang/OutOfMemoryError",
                            "Setting Stream");
            return;
        }
    }

    /* And finally reset state */
    data->abortFlag = JNI_FALSE;

    /* Establish the setjmp return context for sun_jpeg_error_exit to use. */
    jerr = (sun_jpeg_error_ptr) cinfo->err;

    if (setjmp(jerr->setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error
           while aborting. */
        if (!(*env)->ExceptionOccurred(env)) {
            char buffer[JMSG_LENGTH_MAX];
            (*cinfo->err->format_message) (cinfo,
                                           buffer);
            JNU_ThrowByName(env, "javax/imageio/IIOException", buffer);
        }
        return;
    }

    jpeg_abort(cinfo);  // Frees any markers, but not tables

}

static void imageio_reset(JNIEnv *env,
                          j_common_ptr cinfo,
                          imageIODataPtr data) {
    sun_jpeg_error_ptr jerr;

    resetImageIOData(env, data);  // Mapping to jpeg object is retained.

    /* Establish the setjmp return context for sun_jpeg_error_exit to use. */
    jerr = (sun_jpeg_error_ptr) cinfo->err;

    if (setjmp(jerr->setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error
           while aborting. */
        if (!(*env)->ExceptionOccurred(env)) {
            char buffer[JMSG_LENGTH_MAX];
            (*cinfo->err->format_message) (cinfo, buffer);
            JNU_ThrowByName(env, "javax/imageio/IIOException", buffer);
        }
        return;
    }

    jpeg_abort(cinfo);  // Does not reset tables

}

static void imageio_dispose(j_common_ptr info) {

    if (info != NULL) {
        free(info->err);
        info->err = NULL;
        if (info->is_decompressor) {
            j_decompress_ptr dinfo = (j_decompress_ptr) info;
            free(dinfo->src);
            dinfo->src = NULL;
        } else {
            j_compress_ptr cinfo = (j_compress_ptr) info;
            free(cinfo->dest);
            cinfo->dest = NULL;
        }
        jpeg_destroy(info);
        free(info);
    }
}

static void imageio_abort(JNIEnv *env, jobject this,
                          imageIODataPtr data) {
    data->abortFlag = JNI_TRUE;
}

static int setQTables(JNIEnv *env,
                      j_common_ptr cinfo,
                      jobjectArray qtables,
                      boolean write) {
    jsize qlen;
    jobject table;
    jintArray qdata;
    jint *qdataBody;
    JQUANT_TBL *quant_ptr;
    int i, j;
    j_compress_ptr comp;
    j_decompress_ptr decomp;

    qlen = (*env)->GetArrayLength(env, qtables);
#ifdef DEBUG_IIO_JPEG
    printf("in setQTables, qlen = %d, write is %d\n", qlen, write);
#endif
    if (qlen > NUM_QUANT_TBLS) {
        /* Ignore extra qunterization tables. */
        qlen = NUM_QUANT_TBLS;
    }
    for (i = 0; i < qlen; i++) {
        table = (*env)->GetObjectArrayElement(env, qtables, i);
        qdata = (*env)->GetObjectField(env, table, JPEGQTable_tableID);
        qdataBody = (*env)->GetPrimitiveArrayCritical(env, qdata, NULL);

        if (cinfo->is_decompressor) {
            decomp = (j_decompress_ptr) cinfo;
            if (decomp->quant_tbl_ptrs[i] == NULL) {
                decomp->quant_tbl_ptrs[i] =
                    jpeg_alloc_quant_table(cinfo);
            }
            quant_ptr = decomp->quant_tbl_ptrs[i];
        } else {
            comp = (j_compress_ptr) cinfo;
            if (comp->quant_tbl_ptrs[i] == NULL) {
                comp->quant_tbl_ptrs[i] =
                    jpeg_alloc_quant_table(cinfo);
            }
            quant_ptr = comp->quant_tbl_ptrs[i];
        }

        for (j = 0; j < 64; j++) {
            quant_ptr->quantval[j] = (UINT16)qdataBody[j];
        }
        quant_ptr->sent_table = !write;
        (*env)->ReleasePrimitiveArrayCritical(env,
                                              qdata,
                                              qdataBody,
                                              0);
    }
    return qlen;
}

static void setHuffTable(JNIEnv *env,
                         JHUFF_TBL *huff_ptr,
                         jobject table) {

    jshortArray huffLens;
    jshortArray huffValues;
    jshort *hlensBody, *hvalsBody;
    jsize hlensLen, hvalsLen;
    int i;

    // lengths
    huffLens = (*env)->GetObjectField(env,
                                      table,
                                      JPEGHuffmanTable_lengthsID);
    hlensLen = (*env)->GetArrayLength(env, huffLens);
    hlensBody = (*env)->GetShortArrayElements(env,
                                              huffLens,
                                              NULL);
    if (hlensLen > 16) {
        /* Ignore extra elements of bits array. Only 16 elements can be
           stored. 0-th element is not used. (see jpeglib.h, line 107)  */
        hlensLen = 16;
    }
    for (i = 1; i <= hlensLen; i++) {
        huff_ptr->bits[i] = (UINT8)hlensBody[i-1];
    }
    (*env)->ReleaseShortArrayElements(env,
                                      huffLens,
                                      hlensBody,
                                      JNI_ABORT);
    // values
    huffValues = (*env)->GetObjectField(env,
                                        table,
                                        JPEGHuffmanTable_valuesID);
    hvalsLen = (*env)->GetArrayLength(env, huffValues);
    hvalsBody = (*env)->GetShortArrayElements(env,
                                              huffValues,
                                              NULL);

    if (hvalsLen > 256) {
        /* Ignore extra elements of hufval array. Only 256 elements
           can be stored. (see jpeglib.h, line 109)                  */
        hlensLen = 256;
    }
    for (i = 0; i < hvalsLen; i++) {
        huff_ptr->huffval[i] = (UINT8)hvalsBody[i];
    }
    (*env)->ReleaseShortArrayElements(env,
                                      huffValues,
                                      hvalsBody,
                                      JNI_ABORT);
}

static int setHTables(JNIEnv *env,
                      j_common_ptr cinfo,
                      jobjectArray DCHuffmanTables,
                      jobjectArray ACHuffmanTables,
                      boolean write) {
    int i;
    jobject table;
    JHUFF_TBL *huff_ptr;
    j_compress_ptr comp;
    j_decompress_ptr decomp;
    jsize hlen = (*env)->GetArrayLength(env, DCHuffmanTables);

    if (hlen > NUM_HUFF_TBLS) {
        /* Ignore extra DC huffman tables. */
        hlen = NUM_HUFF_TBLS;
    }
    for (i = 0; i < hlen; i++) {
        if (cinfo->is_decompressor) {
            decomp = (j_decompress_ptr) cinfo;
            if (decomp->dc_huff_tbl_ptrs[i] == NULL) {
                decomp->dc_huff_tbl_ptrs[i] =
                    jpeg_alloc_huff_table(cinfo);
            }
            huff_ptr = decomp->dc_huff_tbl_ptrs[i];
        } else {
            comp = (j_compress_ptr) cinfo;
            if (comp->dc_huff_tbl_ptrs[i] == NULL) {
                comp->dc_huff_tbl_ptrs[i] =
                    jpeg_alloc_huff_table(cinfo);
            }
            huff_ptr = comp->dc_huff_tbl_ptrs[i];
        }
        table = (*env)->GetObjectArrayElement(env, DCHuffmanTables, i);
        setHuffTable(env, huff_ptr, table);
        huff_ptr->sent_table = !write;
    }
    hlen = (*env)->GetArrayLength(env, ACHuffmanTables);
    if (hlen > NUM_HUFF_TBLS) {
        /* Ignore extra AC huffman tables. */
        hlen = NUM_HUFF_TBLS;
    }
    for (i = 0; i < hlen; i++) {
        if (cinfo->is_decompressor) {
            decomp = (j_decompress_ptr) cinfo;
            if (decomp->ac_huff_tbl_ptrs[i] == NULL) {
                decomp->ac_huff_tbl_ptrs[i] =
                    jpeg_alloc_huff_table(cinfo);
            }
            huff_ptr = decomp->ac_huff_tbl_ptrs[i];
        } else {
            comp = (j_compress_ptr) cinfo;
            if (comp->ac_huff_tbl_ptrs[i] == NULL) {
                comp->ac_huff_tbl_ptrs[i] =
                    jpeg_alloc_huff_table(cinfo);
            }
            huff_ptr = comp->ac_huff_tbl_ptrs[i];
        }
        table = (*env)->GetObjectArrayElement(env, ACHuffmanTables, i);
        setHuffTable(env, huff_ptr, table);
        huff_ptr->sent_table = !write;
    }
    return hlen;
}


/*************** end of shared utility code ****************/

/********************** Reader Support **************************/

/********************** Source Management ***********************/

/*
 * INPUT HANDLING:
 *
 * The JPEG library's input management is defined by the jpeg_source_mgr
 * structure which contains two fields to convey the information in the
 * buffer and 5 methods which perform all buffer management.  The library
 * defines a standard input manager that uses stdio for obtaining compressed
 * jpeg data, but here we need to use Java to get our data.
 *
 * We use the library jpeg_source_mgr but our own routines that access
 * imageio-specific information in the imageIOData structure.
 */

/*
 * Initialize source.  This is called by jpeg_read_header() before any
 * data is actually read.  Unlike init_destination(), it may leave
 * bytes_in_buffer set to 0 (in which case a fill_input_buffer() call
 * will occur immediately).
 */

GLOBAL(void)
imageio_init_source(j_decompress_ptr cinfo)
{
    struct jpeg_source_mgr *src = cinfo->src;
    src->next_input_byte = NULL;
    src->bytes_in_buffer = 0;
}

/*
 * This is called whenever bytes_in_buffer has reached zero and more
 * data is wanted.  In typical applications, it should read fresh data
 * into the buffer (ignoring the current state of next_input_byte and
 * bytes_in_buffer), reset the pointer & count to the start of the
 * buffer, and return TRUE indicating that the buffer has been reloaded.
 * It is not necessary to fill the buffer entirely, only to obtain at
 * least one more byte.  bytes_in_buffer MUST be set to a positive value
 * if TRUE is returned.  A FALSE return should only be used when I/O
 * suspension is desired (this mode is discussed in the next section).
 */
/*
 * Note that with I/O suspension turned on, this procedure should not
 * do any work since the JPEG library has a very simple backtracking
 * mechanism which relies on the fact that the buffer will be filled
 * only when it has backed out to the top application level.  When
 * suspendable is turned on, imageio_fill_suspended_buffer will
 * do the actual work of filling the buffer.
 */

GLOBAL(boolean)
imageio_fill_input_buffer(j_decompress_ptr cinfo)
{
    struct jpeg_source_mgr *src = cinfo->src;
    imageIODataPtr data = (imageIODataPtr) cinfo->client_data;
    streamBufferPtr sb = &data->streamBuf;
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    int ret;

    /* This is where input suspends */
    if (sb->suspendable) {
        return FALSE;
    }

#ifdef DEBUG_IIO_JPEG
    printf("Filling input buffer, remaining skip is %ld, ",
           sb->remaining_skip);
    printf("Buffer length is %d\n", sb->bufferLength);
#endif

    /*
     * Definitively skips.  Could be left over if we tried to skip
     * more than a buffer's worth but suspended when getting the next
     * buffer.  Now we aren't suspended, so we can catch up.
     */
    if (sb->remaining_skip) {
        src->skip_input_data(cinfo, 0);
    }

    /*
     * Now fill a complete buffer, or as much of one as the stream
     * will give us if we are near the end.
     */
    RELEASE_ARRAYS(env, data, src->next_input_byte);
    ret = (*env)->CallIntMethod(env,
                                sb->stream,
                                ImageInputStream_readID,
                                sb->hstreamBuffer, 0,
                                sb->bufferLength);
    if ((*env)->ExceptionOccurred(env)
        || !GET_ARRAYS(env, data, &(src->next_input_byte))) {
            cinfo->err->error_exit((j_common_ptr) cinfo);
    }

#ifdef DEBUG_IIO_JPEG
      printf("Buffer filled. ret = %d\n", ret);
#endif
    /*
     * If we have reached the end of the stream, then the EOI marker
     * is missing.  We accept such streams but generate a warning.
     * The image is likely to be corrupted, though everything through
     * the end of the last complete MCU should be usable.
     */
    if (ret <= 0) {
        jobject reader = data->imageIOobj;
#ifdef DEBUG_IIO_JPEG
      printf("YO! Early EOI! ret = %d\n", ret);
#endif
        RELEASE_ARRAYS(env, data, src->next_input_byte);
        (*env)->CallVoidMethod(env, reader,
                               JPEGImageReader_warningOccurredID,
                               READ_NO_EOI);
        if ((*env)->ExceptionOccurred(env)
            || !GET_ARRAYS(env, data, &(src->next_input_byte))) {
            cinfo->err->error_exit((j_common_ptr) cinfo);
        }

        sb->buf[0] = (JOCTET) 0xFF;
        sb->buf[1] = (JOCTET) JPEG_EOI;
        ret = 2;
    }

    src->next_input_byte = sb->buf;
    src->bytes_in_buffer = ret;

    return TRUE;
}

/*
 * With I/O suspension turned on, the JPEG library requires that all
 * buffer filling be done at the top application level, using this
 * function.  Due to the way that backtracking works, this procedure
 * saves all of the data that was left in the buffer when suspension
 * occured and read new data only at the end.
 */

GLOBAL(void)
imageio_fill_suspended_buffer(j_decompress_ptr cinfo)
{
    struct jpeg_source_mgr *src = cinfo->src;
    imageIODataPtr data = (imageIODataPtr) cinfo->client_data;
    streamBufferPtr sb = &data->streamBuf;
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    jint ret;
    size_t offset, buflen;

    /*
     * The original (jpegdecoder.c) had code here that called
     * InputStream.available and just returned if the number of bytes
     * available was less than any remaining skip.  Presumably this was
     * to avoid blocking, although the benefit was unclear, as no more
     * decompression can take place until more data is available, so
     * the code would block on input a little further along anyway.
     * ImageInputStreams don't have an available method, so we'll just
     * block in the skip if we have to.
     */

    if (sb->remaining_skip) {
        src->skip_input_data(cinfo, 0);
    }

    /* Save the data currently in the buffer */
    offset = src->bytes_in_buffer;
    if (src->next_input_byte > sb->buf) {
        memcpy(sb->buf, src->next_input_byte, offset);
    }
    RELEASE_ARRAYS(env, data, src->next_input_byte);
    buflen = sb->bufferLength - offset;
    if (buflen <= 0) {
        if (!GET_ARRAYS(env, data, &(src->next_input_byte))) {
            cinfo->err->error_exit((j_common_ptr) cinfo);
        }
        return;
    }

    ret = (*env)->CallIntMethod(env, sb->stream,
                                ImageInputStream_readID,
                                sb->hstreamBuffer,
                                offset, buflen);
    if ((*env)->ExceptionOccurred(env)
        || !GET_ARRAYS(env, data, &(src->next_input_byte))) {
        cinfo->err->error_exit((j_common_ptr) cinfo);
    }
    /*
     * If we have reached the end of the stream, then the EOI marker
     * is missing.  We accept such streams but generate a warning.
     * The image is likely to be corrupted, though everything through
     * the end of the last complete MCU should be usable.
     */
    if (ret <= 0) {
        jobject reader = data->imageIOobj;
        RELEASE_ARRAYS(env, data, src->next_input_byte);
        (*env)->CallVoidMethod(env, reader,
                               JPEGImageReader_warningOccurredID,
                               READ_NO_EOI);
        if ((*env)->ExceptionOccurred(env)
            || !GET_ARRAYS(env, data, &(src->next_input_byte))) {
            cinfo->err->error_exit((j_common_ptr) cinfo);
        }

        sb->buf[offset] = (JOCTET) 0xFF;
        sb->buf[offset + 1] = (JOCTET) JPEG_EOI;
        ret = 2;
    }

    src->next_input_byte = sb->buf;
    src->bytes_in_buffer = ret + offset;

    return;
}

/*
 * Skip num_bytes worth of data.  The buffer pointer and count are
 * advanced over num_bytes input bytes, using the input stream
 * skipBytes method if the skip is greater than the number of bytes
 * in the buffer.  This is used to skip over a potentially large amount of
 * uninteresting data (such as an APPn marker).  bytes_in_buffer will be
 * zero on return if the skip is larger than the current contents of the
 * buffer.
 *
 * A negative skip count is treated as a no-op.  A zero skip count
 * skips any remaining skip from a previous skip while suspended.
 *
 * Note that with I/O suspension turned on, this procedure does not
 * call skipBytes since the JPEG library has a very simple backtracking
 * mechanism which relies on the fact that the application level has
 * exclusive control over actual I/O.
 */

GLOBAL(void)
imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
    struct jpeg_source_mgr *src = cinfo->src;
    imageIODataPtr data = (imageIODataPtr) cinfo->client_data;
    streamBufferPtr sb = &data->streamBuf;
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    jlong ret;
    jobject reader;

    if (num_bytes < 0) {
        return;
    }
    num_bytes += sb->remaining_skip;
    sb->remaining_skip = 0;

    /* First the easy case where we are skipping <= the current contents. */
    ret = src->bytes_in_buffer;
    if (ret >= num_bytes) {
        src->next_input_byte += num_bytes;
        src->bytes_in_buffer -= num_bytes;
        return;
    }

    /*
     * We are skipping more than is in the buffer.  We empty the buffer and,
     * if we aren't suspended, call the Java skipBytes method.  We always
     * leave the buffer empty, to be filled by either fill method above.
     */
    src->bytes_in_buffer = 0;
    src->next_input_byte = sb->buf;

    num_bytes -= (long)ret;
    if (sb->suspendable) {
        sb->remaining_skip = num_bytes;
        return;
    }

    RELEASE_ARRAYS(env, data, src->next_input_byte);
    ret = (*env)->CallLongMethod(env,
                                 sb->stream,
                                 ImageInputStream_skipBytesID,
                                 (jlong) num_bytes);
    if ((*env)->ExceptionOccurred(env)
        || !GET_ARRAYS(env, data, &(src->next_input_byte))) {
            cinfo->err->error_exit((j_common_ptr) cinfo);
    }

    /*
     * If we have reached the end of the stream, then the EOI marker
     * is missing.  We accept such streams but generate a warning.
     * The image is likely to be corrupted, though everything through
     * the end of the last complete MCU should be usable.
     */
    if (ret <= 0) {
        reader = data->imageIOobj;
        RELEASE_ARRAYS(env, data, src->next_input_byte);
        (*env)->CallVoidMethod(env,
                               reader,
                               JPEGImageReader_warningOccurredID,
                               READ_NO_EOI);

        if ((*env)->ExceptionOccurred(env)
            || !GET_ARRAYS(env, data, &(src->next_input_byte))) {
                cinfo->err->error_exit((j_common_ptr) cinfo);
        }
        sb->buf[0] = (JOCTET) 0xFF;
        sb->buf[1] = (JOCTET) JPEG_EOI;
        src->bytes_in_buffer = 2;
        src->next_input_byte = sb->buf;
    }
}

/*
 * Terminate source --- called by jpeg_finish_decompress() after all
 * data for an image has been read.  In our case pushes back any
 * remaining data, as it will be for another image and must be available
 * for java to find out that there is another image.  Also called if
 * reseting state after reading a tables-only image.
 */

GLOBAL(void)
imageio_term_source(j_decompress_ptr cinfo)
{
    // To pushback, just seek back by src->bytes_in_buffer
    struct jpeg_source_mgr *src = cinfo->src;
    imageIODataPtr data = (imageIODataPtr) cinfo->client_data;
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);
    jobject reader = data->imageIOobj;
    if (src->bytes_in_buffer > 0) {
         RELEASE_ARRAYS(env, data, src->next_input_byte);
         (*env)->CallVoidMethod(env,
                                reader,
                                JPEGImageReader_pushBackID,
                                src->bytes_in_buffer);

         if ((*env)->ExceptionOccurred(env)
             || !GET_ARRAYS(env, data, &(src->next_input_byte))) {
             cinfo->err->error_exit((j_common_ptr) cinfo);
         }
         src->bytes_in_buffer = 0;
         //src->next_input_byte = sb->buf;
    }
}

/********************* end of source manager ******************/

/********************* ICC profile support ********************/
/*
 * The following routines are modified versions of the ICC
 * profile support routines available from the IJG website.
 * The originals were written by Todd Newman
 * <tdn@eccentric.esd.sgi.com> and modified by Tom Lane for
 * the IJG.  They are further modified to fit in the context
 * of the imageio JPEG plug-in.
 */

/*
 * Since an ICC profile can be larger than the maximum size of a JPEG marker
 * (64K), we need provisions to split it into multiple markers.  The format
 * defined by the ICC specifies one or more APP2 markers containing the
 * following data:
 *      Identifying string      ASCII "ICC_PROFILE\0"  (12 bytes)
 *      Marker sequence number  1 for first APP2, 2 for next, etc (1 byte)
 *      Number of markers       Total number of APP2's used (1 byte)
 *      Profile data            (remainder of APP2 data)
 * Decoders should use the marker sequence numbers to reassemble the profile,
 * rather than assuming that the APP2 markers appear in the correct sequence.
 */

#define ICC_MARKER  (JPEG_APP0 + 2)     /* JPEG marker code for ICC */
#define ICC_OVERHEAD_LEN  14            /* size of non-profile data in APP2 */
#define MAX_BYTES_IN_MARKER  65533      /* maximum data len of a JPEG marker */
#define MAX_DATA_BYTES_IN_ICC_MARKER  (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN)


/*
 * Handy subroutine to test whether a saved marker is an ICC profile marker.
 */

static boolean
marker_is_icc (jpeg_saved_marker_ptr marker)
{
  return
    marker->marker == ICC_MARKER &&
    marker->data_length >= ICC_OVERHEAD_LEN &&
    /* verify the identifying string */
    GETJOCTET(marker->data[0]) == 0x49 &&
    GETJOCTET(marker->data[1]) == 0x43 &&
    GETJOCTET(marker->data[2]) == 0x43 &&
    GETJOCTET(marker->data[3]) == 0x5F &&
    GETJOCTET(marker->data[4]) == 0x50 &&
    GETJOCTET(marker->data[5]) == 0x52 &&
    GETJOCTET(marker->data[6]) == 0x4F &&
    GETJOCTET(marker->data[7]) == 0x46 &&
    GETJOCTET(marker->data[8]) == 0x49 &&
    GETJOCTET(marker->data[9]) == 0x4C &&
    GETJOCTET(marker->data[10]) == 0x45 &&
    GETJOCTET(marker->data[11]) == 0x0;
}

/*
 * See if there was an ICC profile in the JPEG file being read;
 * if so, reassemble and return the profile data as a new Java byte array.
 * If there was no ICC profile, return NULL.
 *
 * If the file contains invalid ICC APP2 markers, we throw an IIOException
 * with an appropriate message.
 */

jbyteArray
read_icc_profile (JNIEnv *env, j_decompress_ptr cinfo)
{
    jpeg_saved_marker_ptr marker;
    int num_markers = 0;
    int num_found_markers = 0;
    int seq_no;
    JOCTET *icc_data;
    JOCTET *dst_ptr;
    unsigned int total_length;
#define MAX_SEQ_NO  255         // sufficient since marker numbers are bytes
    jpeg_saved_marker_ptr icc_markers[MAX_SEQ_NO + 1];
    int first;         // index of the first marker in the icc_markers array
    int last;          // index of the last marker in the icc_markers array
    jbyteArray data = NULL;

    /* This first pass over the saved markers discovers whether there are
     * any ICC markers and verifies the consistency of the marker numbering.
     */

    for (seq_no = 0; seq_no <= MAX_SEQ_NO; seq_no++)
        icc_markers[seq_no] = NULL;


    for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) {
        if (marker_is_icc(marker)) {
            if (num_markers == 0)
                num_markers = GETJOCTET(marker->data[13]);
            else if (num_markers != GETJOCTET(marker->data[13])) {
                JNU_ThrowByName(env, "javax/imageio/IIOException",
                     "Invalid icc profile: inconsistent num_markers fields");
                return NULL;
            }
            seq_no = GETJOCTET(marker->data[12]);

            /* Some third-party tools produce images with profile chunk
             * numeration started from zero. It is inconsistent with ICC
             * spec, but seems to be recognized by majority of image
             * processing tools, so we should be more tolerant to this
             * departure from the spec.
             */
            if (seq_no < 0 || seq_no > num_markers) {
                JNU_ThrowByName(env, "javax/imageio/IIOException",
                     "Invalid icc profile: bad sequence number");
                return NULL;
            }
            if (icc_markers[seq_no] != NULL) {
                JNU_ThrowByName(env, "javax/imageio/IIOException",
                     "Invalid icc profile: duplicate sequence numbers");
                return NULL;
            }
            icc_markers[seq_no] = marker;
            num_found_markers ++;
        }
    }

    if (num_markers == 0)
        return NULL;  // There is no profile

    if (num_markers != num_found_markers) {
        JNU_ThrowByName(env, "javax/imageio/IIOException",
                        "Invalid icc profile: invalid number of icc markers");
        return NULL;
    }

    first = icc_markers[0] ? 0 : 1;
    last = num_found_markers + first;

    /* Check for missing markers, count total space needed.
     */
    total_length = 0;
    for (seq_no = first; seq_no < last; seq_no++) {
        unsigned int length;
        if (icc_markers[seq_no] == NULL) {
            JNU_ThrowByName(env, "javax/imageio/IIOException",
                 "Invalid icc profile: missing sequence number");
            return NULL;
        }
        /* check the data length correctness */
        length = icc_markers[seq_no]->data_length;
        if (ICC_OVERHEAD_LEN > length || length > MAX_BYTES_IN_MARKER) {
            JNU_ThrowByName(env, "javax/imageio/IIOException",
                 "Invalid icc profile: invalid data length");
            return NULL;
        }
        total_length += (length - ICC_OVERHEAD_LEN);
    }

    if (total_length <= 0) {
        JNU_ThrowByName(env, "javax/imageio/IIOException",
              "Invalid icc profile: found only empty markers");
        return NULL;
    }

    /* Allocate a Java byte array for assembled data */

    data = (*env)->NewByteArray(env, total_length);
    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/OutOfMemoryError",
                        "Reading ICC profile");
        return NULL;
    }

    icc_data = (JOCTET *)(*env)->GetPrimitiveArrayCritical(env,
                                                           data,
                                                           NULL);
    if (icc_data == NULL) {
        JNU_ThrowByName(env, "javax/imageio/IIOException",
                        "Unable to pin icc profile data array");
        return NULL;
    }

    /* and fill it in */
    dst_ptr = icc_data;
    for (seq_no = first; seq_no < last; seq_no++) {
        JOCTET FAR *src_ptr = icc_markers[seq_no]->data + ICC_OVERHEAD_LEN;
        unsigned int length =
            icc_markers[seq_no]->data_length - ICC_OVERHEAD_LEN;

        memcpy(dst_ptr, src_ptr, length);
        dst_ptr += length;
    }

    /* finally, unpin the array */
    (*env)->ReleasePrimitiveArrayCritical(env,
                                          data,
                                          icc_data,
                                          0);


    return data;
}

/********************* end of ICC profile support *************/

/********************* Reader JNI calls ***********************/

JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_initReaderIDs
    (JNIEnv *env,
     jclass cls,
     jclass ImageInputStreamClass,
     jclass qTableClass,
     jclass huffClass) {

    ImageInputStream_readID = (*env)->GetMethodID(env,
                                                  ImageInputStreamClass,
                                                  "read",
                                                  "([BII)I");
    ImageInputStream_skipBytesID = (*env)->GetMethodID(env,
                                                       ImageInputStreamClass,
                                                       "skipBytes",
                                                       "(J)J");
    JPEGImageReader_warningOccurredID = (*env)->GetMethodID(env,
                                                            cls,
                                                            "warningOccurred",
                                                            "(I)V");
    JPEGImageReader_warningWithMessageID =
        (*env)->GetMethodID(env,
                            cls,
                            "warningWithMessage",
                            "(Ljava/lang/String;)V");
    JPEGImageReader_setImageDataID = (*env)->GetMethodID(env,
                                                         cls,
                                                         "setImageData",
                                                         "(IIIII[B)V");
    JPEGImageReader_acceptPixelsID = (*env)->GetMethodID(env,
                                                         cls,
                                                         "acceptPixels",
                                                         "(IZ)V");
    JPEGImageReader_passStartedID = (*env)->GetMethodID(env,
                                                        cls,
                                                        "passStarted",
                                                        "(I)V");
    JPEGImageReader_passCompleteID = (*env)->GetMethodID(env,
                                                         cls,
                                                         "passComplete",
                                                         "()V");
    JPEGImageReader_pushBackID = (*env)->GetMethodID(env,
                                                     cls,
                                                     "pushBack",
                                                     "(I)V");
    JPEGQTable_tableID = (*env)->GetFieldID(env,
                                            qTableClass,
                                            "qTable",
                                            "[I");

    JPEGHuffmanTable_lengthsID = (*env)->GetFieldID(env,
                                                    huffClass,
                                                    "lengths",
                                                    "[S");

    JPEGHuffmanTable_valuesID = (*env)->GetFieldID(env,
                                                    huffClass,
                                                    "values",
                                                    "[S");
}

JNIEXPORT jlong JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_initJPEGImageReader
    (JNIEnv *env,
     jobject this) {

    imageIODataPtr ret;
    struct sun_jpeg_error_mgr *jerr;

    /* This struct contains the JPEG decompression parameters and pointers to
     * working space (which is allocated as needed by the JPEG library).
     */
    struct jpeg_decompress_struct *cinfo =
        malloc(sizeof(struct jpeg_decompress_struct));
    if (cinfo == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Initializing Reader");
        return 0;
    }

    /* We use our private extension JPEG error handler.
     */
    jerr = malloc (sizeof(struct sun_jpeg_error_mgr));
    if (jerr == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Initializing Reader");
        free(cinfo);
        return 0;
    }

    /* We set up the normal JPEG error routines, then override error_exit. */
    cinfo->err = jpeg_std_error(&(jerr->pub));
    jerr->pub.error_exit = sun_jpeg_error_exit;
    /* We need to setup our own print routines */
    jerr->pub.output_message = sun_jpeg_output_message;
    /* Now we can setjmp before every call to the library */

    /* Establish the setjmp return context for sun_jpeg_error_exit to use. */
    if (setjmp(jerr->setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error. */
        char buffer[JMSG_LENGTH_MAX];
        (*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo,
                                      buffer);
        JNU_ThrowByName(env, "javax/imageio/IIOException", buffer);
        return 0;
    }

    /* Perform library initialization */
    jpeg_create_decompress(cinfo);

    // Set up to keep any APP2 markers, as these might contain ICC profile
    // data
    jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF);

    /*
     * Now set up our source.
     */
    cinfo->src =
        (struct jpeg_source_mgr *) malloc (sizeof(struct jpeg_source_mgr));
    if (cinfo->src == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/OutOfMemoryError",
                        "Initializing Reader");
        imageio_dispose((j_common_ptr)cinfo);
        return 0;
    }
    cinfo->src->bytes_in_buffer = 0;
    cinfo->src->next_input_byte = NULL;
    cinfo->src->init_source = imageio_init_source;
    cinfo->src->fill_input_buffer = imageio_fill_input_buffer;
    cinfo->src->skip_input_data = imageio_skip_input_data;
    cinfo->src->resync_to_restart = jpeg_resync_to_restart; // use default
    cinfo->src->term_source = imageio_term_source;

    /* set up the association to persist for future calls */
    ret = initImageioData(env, (j_common_ptr) cinfo, this);
    if (ret == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Initializing Reader");
        imageio_dispose((j_common_ptr)cinfo);
        return 0;
    }
    return ptr_to_jlong(ret);
}

/*
 * When we set a source from Java, we set up the stream in the streamBuf
 * object.  If there was an old one, it is released first.
 */

JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_setSource
    (JNIEnv *env,
     jobject this,
     jlong ptr,
     jobject source) {

    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_common_ptr cinfo;

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use reader after dispose()");
        return;
    }

    cinfo = data->jpegObj;

    imageio_set_stream(env, cinfo, data, source);

    imageio_init_source((j_decompress_ptr) cinfo);
}

#define JPEG_APP1  (JPEG_APP0 + 1)  /* EXIF APP1 marker code  */

/*
 * For EXIF images, the APP1 will appear immediately after the SOI,
 * so it's safe to only look at the first marker in the list.
 * (see http://www.exif.org/Exif2-2.PDF, section 4.7, page 58)
 */
#define IS_EXIF(c) \
    (((c)->marker_list != NULL) && ((c)->marker_list->marker == JPEG_APP1))

JNIEXPORT jboolean JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader
    (JNIEnv *env,
     jobject this,
     jlong ptr,
     jboolean clearFirst,
     jboolean reset) {

    int ret;
    int h_samp0, h_samp1, h_samp2;
    int v_samp0, v_samp1, v_samp2;
    jboolean retval = JNI_FALSE;
    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_decompress_ptr cinfo;
    struct jpeg_source_mgr *src;
    sun_jpeg_error_ptr jerr;
    jbyteArray profileData = NULL;

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use reader after dispose()");
        return JNI_FALSE;
    }

    cinfo = (j_decompress_ptr) data->jpegObj;
    src = cinfo->src;

    /* Establish the setjmp return context for sun_jpeg_error_exit to use. */
    jerr = (sun_jpeg_error_ptr) cinfo->err;

    if (setjmp(jerr->setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error
           while reading the header. */
        RELEASE_ARRAYS(env, data, src->next_input_byte);
        if (!(*env)->ExceptionOccurred(env)) {
            char buffer[JMSG_LENGTH_MAX];
            (*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo,
                                          buffer);
            JNU_ThrowByName(env, "javax/imageio/IIOException", buffer);
        }
        return retval;
    }

#ifdef DEBUG_IIO_JPEG
    printf("In readImageHeader, data is %p cinfo is %p\n", data, cinfo);
    printf("clearFirst is %d\n", clearFirst);
#endif

    if (GET_ARRAYS(env, data, &src->next_input_byte) == NOT_OK) {
        JNU_ThrowByName(env,
                        "javax/imageio/IIOException",
                        "Array pin failed");
        return retval;
    }

    /*
     * Now clear the input buffer if the Java code has done a seek
     * on the stream since the last call, invalidating any buffer contents.
     */
    if (clearFirst) {
        clearStreamBuffer(&data->streamBuf);
        src->next_input_byte = NULL;
        src->bytes_in_buffer = 0;
    }

    ret = jpeg_read_header(cinfo, FALSE);

    if (ret == JPEG_HEADER_TABLES_ONLY) {
        retval = JNI_TRUE;
        imageio_term_source(cinfo);  // Pushback remaining buffer contents
#ifdef DEBUG_IIO_JPEG
        printf("just read tables-only image; q table 0 at %p\n",
               cinfo->quant_tbl_ptrs[0]);
#endif
        RELEASE_ARRAYS(env, data, src->next_input_byte);
    } else {
        /*
         * Now adjust the jpeg_color_space variable, which was set in
         * default_decompress_parms, to reflect our differences from IJG
         */

        switch (cinfo->jpeg_color_space) {
        default :
          break;
        case JCS_YCbCr:

            /*
             * There are several possibilities:
             *  - we got image with embeded colorspace
             *     Use it. User knows what he is doing.
             *  - we got JFIF image
             *     Must be YCbCr (see http://www.w3.org/Graphics/JPEG/jfif3.pdf, page 2)
             *  - we got EXIF image
             *     Must be YCbCr (see http://www.exif.org/Exif2-2.PDF, section 4.7, page 63)
             *  - something else
             *     Apply heuristical rules to identify actual colorspace.
             */

            if (cinfo->saw_Adobe_marker) {
                if (cinfo->Adobe_transform != 1) {
                    /*
                     * IJG guesses this is YCbCr and emits a warning
                     * We would rather not guess.  Then the user knows
                     * To read this as a Raster if at all
                     */
                    cinfo->jpeg_color_space = JCS_UNKNOWN;
                    cinfo->out_color_space = JCS_UNKNOWN;
                }
            } else if (!cinfo->saw_JFIF_marker && !IS_EXIF(cinfo)) {
                /*
                 * IJG assumes all unidentified 3-channels are YCbCr.
                 * We assume that only if the second two channels are
                 * subsampled (either horizontally or vertically).  If not,
                 * we assume RGB.
                 *
                 * 4776576: Some digital cameras output YCbCr JPEG images
                 * that do not contain a JFIF APP0 marker but are only
                 * vertically subsampled (no horizontal subsampling).
                 * We should only assume this is RGB data if the subsampling
                 * factors for the second two channels are the same as the
                 * first (check both horizontal and vertical factors).
                 */
                h_samp0 = cinfo->comp_info[0].h_samp_factor;
                h_samp1 = cinfo->comp_info[1].h_samp_factor;
                h_samp2 = cinfo->comp_info[2].h_samp_factor;

                v_samp0 = cinfo->comp_info[0].v_samp_factor;
                v_samp1 = cinfo->comp_info[1].v_samp_factor;
                v_samp2 = cinfo->comp_info[2].v_samp_factor;

                if ((h_samp1 == h_samp0) && (h_samp2 == h_samp0) &&
                    (v_samp1 == v_samp0) && (v_samp2 == v_samp0))
                {
                    cinfo->jpeg_color_space = JCS_RGB;
                    /* output is already RGB, so it stays the same */
                }
            }
            break;
#ifdef YCCALPHA
        case JCS_YCC:
            cinfo->out_color_space = JCS_YCC;
            break;
#endif
        case JCS_YCCK:
            if ((cinfo->saw_Adobe_marker) && (cinfo->Adobe_transform != 2)) {
                /*
                 * IJG guesses this is YCCK and emits a warning
                 * We would rather not guess.  Then the user knows
                 * To read this as a Raster if at all
                 */
                cinfo->jpeg_color_space = JCS_UNKNOWN;
                cinfo->out_color_space = JCS_UNKNOWN;
            }
            break;
        case JCS_CMYK:
            /*
             * IJG assumes all unidentified 4-channels are CMYK.
             * We assume that only if the second two channels are
             * not subsampled (either horizontally or vertically).
             * If they are, we assume YCCK.
             */
            h_samp0 = cinfo->comp_info[0].h_samp_factor;
            h_samp1 = cinfo->comp_info[1].h_samp_factor;
            h_samp2 = cinfo->comp_info[2].h_samp_factor;

            v_samp0 = cinfo->comp_info[0].v_samp_factor;
            v_samp1 = cinfo->comp_info[1].v_samp_factor;
            v_samp2 = cinfo->comp_info[2].v_samp_factor;

            if ((h_samp1 > h_samp0) && (h_samp2 > h_samp0) ||
                (v_samp1 > v_samp0) && (v_samp2 > v_samp0))
            {
                cinfo->jpeg_color_space = JCS_YCCK;
                /* Leave the output space as CMYK */
            }
        }
        RELEASE_ARRAYS(env, data, src->next_input_byte);

        /* read icc profile data */
        profileData = read_icc_profile(env, cinfo);

        if ((*env)->ExceptionCheck(env)) {
            return retval;
        }

        (*env)->CallVoidMethod(env, this,
                               JPEGImageReader_setImageDataID,
                               cinfo->image_width,
                               cinfo->image_height,
                               cinfo->jpeg_color_space,
                               cinfo->out_color_space,
                               cinfo->num_components,
                               profileData);
        if (reset) {
            jpeg_abort_decompress(cinfo);
        }
    }

    return retval;
}


JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_setOutColorSpace
    (JNIEnv *env,
     jobject this,
     jlong ptr,
     jint code) {

    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_decompress_ptr cinfo;

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use reader after dispose()");
        return;
    }

    cinfo = (j_decompress_ptr) data->jpegObj;

    cinfo->out_color_space = code;

}

JNIEXPORT jboolean JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage
    (JNIEnv *env,
     jobject this,
     jlong ptr,
     jbyteArray buffer,
     jint numBands,
     jintArray srcBands,
     jintArray bandSizes,
     jint sourceXStart,
     jint sourceYStart,
     jint sourceWidth,
     jint sourceHeight,
     jint stepX,
     jint stepY,
     jobjectArray qtables,
     jobjectArray DCHuffmanTables,
     jobjectArray ACHuffmanTables,
     jint minProgressivePass,  // Counts from 0
     jint maxProgressivePass,
     jboolean wantUpdates) {


    struct jpeg_source_mgr *src;
    JSAMPROW scanLinePtr = NULL;
    jint bands[MAX_BANDS];
    int i;
    jint *body;
    int scanlineLimit;
    int pixelStride;
    unsigned char *in, *out, *pixelLimit;
    int targetLine;
    int skipLines, linesLeft;
    pixelBufferPtr pb;
    sun_jpeg_error_ptr jerr;
    boolean done;
    boolean mustScale = FALSE;
    boolean progressive = FALSE;
    boolean orderedBands = TRUE;
    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_decompress_ptr cinfo;
    size_t numBytes;

    /* verify the inputs */

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use reader after dispose()");
        return JNI_FALSE;
    }

    if ((buffer == NULL) || (srcBands == NULL))  {
        JNU_ThrowNullPointerException(env, 0);
        return JNI_FALSE;
    }

    cinfo = (j_decompress_ptr) data->jpegObj;

    if ((numBands < 1) || (numBands > MAX_BANDS) ||
        (sourceXStart < 0) || (sourceXStart >= (jint)cinfo->image_width) ||
        (sourceYStart < 0) || (sourceYStart >= (jint)cinfo->image_height) ||
        (sourceWidth < 1) || (sourceWidth > (jint)cinfo->image_width) ||
        (sourceHeight < 1) || (sourceHeight > (jint)cinfo->image_height) ||
        (stepX < 1) || (stepY < 1) ||
        (minProgressivePass < 0) ||
        (maxProgressivePass < minProgressivePass))
    {
        JNU_ThrowByName(env, "javax/imageio/IIOException",
                        "Invalid argument to native readImage");
        return JNI_FALSE;
    }

    if (stepX > (jint)cinfo->image_width) {
        stepX = cinfo->image_width;
    }
    if (stepY > (jint)cinfo->image_height) {
        stepY = cinfo->image_height;
    }

    /*
     * First get the source bands array and copy it to our local array
     * so we don't have to worry about pinning and unpinning it again.
     */

    body = (*env)->GetIntArrayElements(env, srcBands, NULL);
    if (body == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Initializing Read");
        return JNI_FALSE;
    }

    for (i = 0; i < numBands; i++) {
        bands[i] = body[i];
        if (orderedBands && (bands[i] != i)) {
            orderedBands = FALSE;
        }
    }

    (*env)->ReleaseIntArrayElements(env, srcBands, body, JNI_ABORT);

#ifdef DEBUG_IIO_JPEG
    printf("---- in reader.read ----\n");
    printf("numBands is %d\n", numBands);
    printf("bands array: ");
    for (i = 0; i < numBands; i++) {
        printf("%d ", bands[i]);
    }
    printf("\n");
    printf("jq table 0 at %p\n",
               cinfo->quant_tbl_ptrs[0]);
#endif

    data = (imageIODataPtr) cinfo->client_data;
    src = cinfo->src;

    /* Set the buffer as our PixelBuffer */
    pb = &data->pixelBuf;

    if (setPixelBuffer(env, pb, buffer) == NOT_OK) {
        return data->abortFlag;  // We already threw an out of memory exception
    }

    /* Establish the setjmp return context for sun_jpeg_error_exit to use. */
    jerr = (sun_jpeg_error_ptr) cinfo->err;

    if (setjmp(jerr->setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error
           while reading. */
        RELEASE_ARRAYS(env, data, src->next_input_byte);
        if (!(*env)->ExceptionOccurred(env)) {
            char buffer[JMSG_LENGTH_MAX];
            (*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo,
                                          buffer);
            JNU_ThrowByName(env, "javax/imageio/IIOException", buffer);
        }
        if (scanLinePtr != NULL) {
            free(scanLinePtr);
            scanLinePtr = NULL;
        }
        return data->abortFlag;
    }

    if (GET_ARRAYS(env, data, &src->next_input_byte) == NOT_OK) {
        JNU_ThrowByName(env,
                        "javax/imageio/IIOException",
                        "Array pin failed");
        return data->abortFlag;
    }

    // If there are no tables in our structure and table arguments aren't
    // NULL, use the table arguments.
    if ((qtables != NULL) && (cinfo->quant_tbl_ptrs[0] == NULL)) {
        (void) setQTables(env, (j_common_ptr) cinfo, qtables, TRUE);
    }

    if ((DCHuffmanTables != NULL) && (cinfo->dc_huff_tbl_ptrs[0] == NULL)) {
        setHTables(env, (j_common_ptr) cinfo,
                   DCHuffmanTables,
                   ACHuffmanTables,
                   TRUE);
    }

    progressive = jpeg_has_multiple_scans(cinfo);
    if (progressive) {
        cinfo->buffered_image = TRUE;
        cinfo->input_scan_number = minProgressivePass+1; // Java count from 0
#define MAX_JAVA_INT 2147483647 // XXX Is this defined in JNI somewhere?
        if (maxProgressivePass < MAX_JAVA_INT) {
            maxProgressivePass++; // For testing
        }
    }

    data->streamBuf.suspendable = FALSE;

    jpeg_start_decompress(cinfo);

    if (numBands !=  cinfo->output_components) {
        JNU_ThrowByName(env, "javax/imageio/IIOException",
                        "Invalid argument to native readImage");
        return data->abortFlag;
    }

    if (cinfo->output_components <= 0 ||
        cinfo->image_width > (0xffffffffu / (unsigned int)cinfo->output_components))
    {
        JNU_ThrowByName(env, "javax/imageio/IIOException",
                        "Invalid number of output components");
        return data->abortFlag;
    }

    // Allocate a 1-scanline buffer
    scanLinePtr = (JSAMPROW)malloc(cinfo->image_width*cinfo->output_components);
    if (scanLinePtr == NULL) {
        RELEASE_ARRAYS(env, data, src->next_input_byte);
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Reading JPEG Stream");
        return data->abortFlag;
    }

    // loop over progressive passes
    done = FALSE;
    while (!done) {
        if (progressive) {
            // initialize the next pass.  Note that this skips up to
            // the first interesting pass.
            jpeg_start_output(cinfo, cinfo->input_scan_number);
            if (wantUpdates) {
                (*env)->CallVoidMethod(env, this,
                                       JPEGImageReader_passStartedID,
                                       cinfo->input_scan_number-1);
            }
        } else if (wantUpdates) {
            (*env)->CallVoidMethod(env, this,
                                   JPEGImageReader_passStartedID,
                                   0);

        }

        // Skip until the first interesting line
        while ((data->abortFlag == JNI_FALSE)
               && ((jint)cinfo->output_scanline < sourceYStart)) {
            jpeg_read_scanlines(cinfo, &scanLinePtr, 1);
        }

        scanlineLimit = sourceYStart+sourceHeight;
        pixelLimit = scanLinePtr
            +(sourceXStart+sourceWidth)*cinfo->output_components;

        pixelStride = stepX*cinfo->output_components;
        targetLine = 0;

        while ((data->abortFlag == JNI_FALSE)
               && ((jint)cinfo->output_scanline < scanlineLimit)) {

            jpeg_read_scanlines(cinfo, &scanLinePtr, 1);

            // Now mangle it into our buffer
            out = data->pixelBuf.buf.bp;

            if (orderedBands && (pixelStride == numBands)) {
                // Optimization: The component bands are ordered sequentially,
                // so we can simply use memcpy() to copy the intermediate
                // scanline buffer into the raster.
                in = scanLinePtr + (sourceXStart * cinfo->output_components);
                if (pixelLimit > in) {
                    numBytes = pixelLimit - in;
                    if (numBytes > data->pixelBuf.byteBufferLength) {
                        numBytes = data->pixelBuf.byteBufferLength;
                    }
                    memcpy(out, in, numBytes);
                }
            } else {
                numBytes = numBands;
                for (in = scanLinePtr+sourceXStart*cinfo->output_components;
                     in < pixelLimit &&
                       numBytes <= data->pixelBuf.byteBufferLength;
                     in += pixelStride) {
                    for (i = 0; i < numBands; i++) {
                        *out++ = *(in+bands[i]);
                    }
                    numBytes += numBands;
                }
            }

            // And call it back to Java
            RELEASE_ARRAYS(env, data, src->next_input_byte);
            (*env)->CallVoidMethod(env,
                                   this,
                                   JPEGImageReader_acceptPixelsID,
                                   targetLine++,
                                   progressive);

            if ((*env)->ExceptionOccurred(env)
                || !GET_ARRAYS(env, data, &(src->next_input_byte))) {
                cinfo->err->error_exit((j_common_ptr) cinfo);
            }

            // And skip over uninteresting lines to the next subsampled line
            // Ensure we don't go past the end of the image

            // Lines to skip based on subsampling
            skipLines = stepY - 1;
            // Lines left in the image
            linesLeft =  scanlineLimit - cinfo->output_scanline;
            // Take the minimum
            if (skipLines > linesLeft) {
                skipLines = linesLeft;
            }
            for(i = 0; i < skipLines; i++) {
                jpeg_read_scanlines(cinfo, &scanLinePtr, 1);
            }
        }
        if (progressive) {
            jpeg_finish_output(cinfo); // Increments pass counter
            // Call Java to notify pass complete
            if (jpeg_input_complete(cinfo)
                || (cinfo->input_scan_number > maxProgressivePass)) {
                done = TRUE;
            }
        } else {
            done = TRUE;
        }
        if (wantUpdates) {
            (*env)->CallVoidMethod(env, this,
                                   JPEGImageReader_passCompleteID);
        }

    }
    /*
     * We are done, but we might not have read all the lines, or all
     * the passes, so use jpeg_abort instead of jpeg_finish_decompress.
     */
    if (cinfo->output_scanline == cinfo->output_height) {
        //    if ((cinfo->output_scanline == cinfo->output_height) &&
        //(jpeg_input_complete(cinfo))) {  // We read the whole file
        jpeg_finish_decompress(cinfo);
    } else {
        jpeg_abort_decompress(cinfo);
    }

    free(scanLinePtr);

    RELEASE_ARRAYS(env, data, src->next_input_byte);

    return data->abortFlag;
}

JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_abortRead
    (JNIEnv *env,
     jobject this,
     jlong ptr) {

    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use reader after dispose()");
        return;
    }

    imageio_abort(env, this, data);

}

JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_resetLibraryState
    (JNIEnv *env,
     jobject this,
     jlong ptr) {
    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_decompress_ptr cinfo;

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use reader after dispose()");
        return;
    }

    cinfo = (j_decompress_ptr) data->jpegObj;

    jpeg_abort_decompress(cinfo);
}


JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_resetReader
    (JNIEnv *env,
     jobject this,
     jlong ptr) {

    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_decompress_ptr cinfo;
    sun_jpeg_error_ptr jerr;

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use reader after dispose()");
        return;
    }

    cinfo = (j_decompress_ptr) data->jpegObj;

    jerr = (sun_jpeg_error_ptr) cinfo->err;

    imageio_reset(env, (j_common_ptr) cinfo, data);

    /*
     * The tables have not been reset, and there is no way to do so
     * in IJG without leaking memory.  The only situation in which
     * this will cause a problem is if an image-only stream is read
     * with this object without initializing the correct tables first.
     * This situation, which should cause an error, might work but
     * produce garbage instead.  If the huffman tables are wrong,
     * it will fail during the decode.  If the q tables are wrong, it
     * will look strange.  This is very unlikely, so don't worry about
     * it.  To be really robust, we would keep a flag for table state
     * and consult it to catch exceptional situations.
     */

    /* above does not clean up the source, so we have to */

    /*
      We need to explicitly initialize exception handler or we may
       longjump to random address from the term_source()
     */

    if (setjmp(jerr->setjmp_buffer)) {

        /*
          We may get IOException from pushBack() here.

          However it could be legal if original input stream was closed
          earlier (for example because network connection was closed).
          Unfortunately, image inputstream API has no way to check whether
          stream is already closed or IOException was thrown because of some
          other IO problem,
          And we can not avoid call to pushBack() on closed stream for the
          same reason.

          So, for now we will silently eat this exception.

          NB: this may be changed in future when ImageInputStream API will
          become more flexible.
        */

        if ((*env)->ExceptionOccurred(env)) {
            (*env)->ExceptionClear(env);
        }
    } else {
        cinfo->src->term_source(cinfo);
    }

    cinfo->src->bytes_in_buffer = 0;
    cinfo->src->next_input_byte = NULL;
}

JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_disposeReader
    (JNIEnv *env,
     jclass reader,
     jlong ptr) {

    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_common_ptr info = destroyImageioData(env, data);

    imageio_dispose(info);
}

/********************** end of Reader *************************/

/********************** Writer Support ************************/

/********************** Destination Manager *******************/

METHODDEF(void)
/*
 * Initialize destination --- called by jpeg_start_compress
 * before any data is actually written.  The data arrays
 * must be pinned before this is called.
 */
imageio_init_destination (j_compress_ptr cinfo)
{
    struct jpeg_destination_mgr *dest = cinfo->dest;
    imageIODataPtr data = (imageIODataPtr) cinfo->client_data;
    streamBufferPtr sb = &data->streamBuf;
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    if (sb->buf == NULL) {
        // We forgot to pin the array
        (*env)->FatalError(env, "Output buffer not pinned!");
    }

    dest->next_output_byte = sb->buf;
    dest->free_in_buffer = sb->bufferLength;
}

/*
 * Empty the output buffer --- called whenever buffer fills up.
 *
 * This routine writes the entire output buffer
 * (ignoring the current state of next_output_byte & free_in_buffer),
 * resets the pointer & count to the start of the buffer, and returns TRUE
 * indicating that the buffer has been dumped.
 */

METHODDEF(boolean)
imageio_empty_output_buffer (j_compress_ptr cinfo)
{
    struct jpeg_destination_mgr *dest = cinfo->dest;
    imageIODataPtr data = (imageIODataPtr) cinfo->client_data;
    streamBufferPtr sb = &data->streamBuf;
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));

    (*env)->CallVoidMethod(env,
                           sb->stream,
                           ImageOutputStream_writeID,
                           sb->hstreamBuffer,
                           0,
                           sb->bufferLength);
    if ((*env)->ExceptionOccurred(env)
        || !GET_ARRAYS(env, data,
                       (const JOCTET **)(&dest->next_output_byte))) {
            cinfo->err->error_exit((j_common_ptr) cinfo);
    }

    dest->next_output_byte = sb->buf;
    dest->free_in_buffer = sb->bufferLength;

    return TRUE;
}

/*
 * After all of the data has been encoded there may still be some
 * more left over in some of the working buffers.  Now is the
 * time to clear them out.
 */
METHODDEF(void)
imageio_term_destination (j_compress_ptr cinfo)
{
    struct jpeg_destination_mgr *dest = cinfo->dest;
    imageIODataPtr data = (imageIODataPtr) cinfo->client_data;
    streamBufferPtr sb = &data->streamBuf;
    JNIEnv *env = (JNIEnv *)JNU_GetEnv(jvm, JNI_VERSION_1_2);

    /* find out how much needs to be written */
    /* this conversion from size_t to jint is safe, because the lenght of the buffer is limited by jint */
    jint datacount = (jint)(sb->bufferLength - dest->free_in_buffer);
    if (datacount != 0) {
        RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));

        (*env)->CallVoidMethod(env,
                               sb->stream,
                               ImageOutputStream_writeID,
                               sb->hstreamBuffer,
                               0,
                               datacount);

        if ((*env)->ExceptionOccurred(env)
            || !GET_ARRAYS(env, data,
                           (const JOCTET **)(&dest->next_output_byte))) {
            cinfo->err->error_exit((j_common_ptr) cinfo);
        }
    }

    dest->next_output_byte = NULL;
    dest->free_in_buffer = 0;

}

/*
 * Flush the destination buffer.  This is not called by the library,
 * but by our code below.  This is the simplest implementation, though
 * certainly not the most efficient.
 */
METHODDEF(void)
imageio_flush_destination(j_compress_ptr cinfo)
{
    imageio_term_destination(cinfo);
    imageio_init_destination(cinfo);
}

/********************** end of destination manager ************/

/********************** Writer JNI calls **********************/


JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_initWriterIDs
    (JNIEnv *env,
     jclass cls,
     jclass IOSClass,
     jclass qTableClass,
     jclass huffClass) {

    ImageOutputStream_writeID = (*env)->GetMethodID(env,
                                                    IOSClass,
                                                    "write",
                                                    "([BII)V");

    JPEGImageWriter_warningOccurredID = (*env)->GetMethodID(env,
                                                            cls,
                                                            "warningOccurred",
                                                            "(I)V");
    JPEGImageWriter_warningWithMessageID =
        (*env)->GetMethodID(env,
                            cls,
                            "warningWithMessage",
                            "(Ljava/lang/String;)V");

    JPEGImageWriter_writeMetadataID = (*env)->GetMethodID(env,
                                                          cls,
                                                          "writeMetadata",
                                                          "()V");
    JPEGImageWriter_grabPixelsID = (*env)->GetMethodID(env,
                                                       cls,
                                                       "grabPixels",
                                                       "(I)V");

    JPEGQTable_tableID = (*env)->GetFieldID(env,
                                            qTableClass,
                                            "qTable",
                                            "[I");

    JPEGHuffmanTable_lengthsID = (*env)->GetFieldID(env,
                                                    huffClass,
                                                    "lengths",
                                                    "[S");

    JPEGHuffmanTable_valuesID = (*env)->GetFieldID(env,
                                                    huffClass,
                                                    "values",
                                                    "[S");
}

JNIEXPORT jlong JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_initJPEGImageWriter
    (JNIEnv *env,
     jobject this) {

    imageIODataPtr ret;
    struct sun_jpeg_error_mgr *jerr;
    struct jpeg_destination_mgr *dest;

    /* This struct contains the JPEG compression parameters and pointers to
     * working space (which is allocated as needed by the JPEG library).
     */
    struct jpeg_compress_struct *cinfo =
        malloc(sizeof(struct jpeg_compress_struct));
    if (cinfo == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Initializing Writer");
        return 0;
    }

    /* We use our private extension JPEG error handler.
     */
    jerr = malloc (sizeof(struct sun_jpeg_error_mgr));
    if (jerr == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Initializing Writer");
        free(cinfo);
        return 0;
    }

    /* We set up the normal JPEG error routines, then override error_exit. */
    cinfo->err = jpeg_std_error(&(jerr->pub));
    jerr->pub.error_exit = sun_jpeg_error_exit;
    /* We need to setup our own print routines */
    jerr->pub.output_message = sun_jpeg_output_message;
    /* Now we can setjmp before every call to the library */

    /* Establish the setjmp return context for sun_jpeg_error_exit to use. */
    if (setjmp(jerr->setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error. */
        char buffer[JMSG_LENGTH_MAX];
        (*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo,
                                      buffer);
        JNU_ThrowByName(env, "javax/imageio/IIOException", buffer);
        return 0;
    }

    /* Perform library initialization */
    jpeg_create_compress(cinfo);

    /* Now set up the destination  */
    dest = malloc(sizeof(struct jpeg_destination_mgr));
    if (dest == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Initializing Writer");
        imageio_dispose((j_common_ptr)cinfo);
        return 0;
    }

    dest->init_destination = imageio_init_destination;
    dest->empty_output_buffer = imageio_empty_output_buffer;
    dest->term_destination = imageio_term_destination;
    dest->next_output_byte = NULL;
    dest->free_in_buffer = 0;

    cinfo->dest = dest;

    /* set up the association to persist for future calls */
    ret = initImageioData(env, (j_common_ptr) cinfo, this);
    if (ret == NULL) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Initializing Writer");
        imageio_dispose((j_common_ptr)cinfo);
        return 0;
    }
    return ptr_to_jlong(ret);
}

JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_setDest
    (JNIEnv *env,
     jobject this,
     jlong ptr,
     jobject destination) {

    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_compress_ptr cinfo;

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use writer after dispose()");
        return;
    }

    cinfo = (j_compress_ptr) data->jpegObj;

    imageio_set_stream(env, data->jpegObj, data, destination);


    // Don't call the init method, as that depends on pinned arrays
    cinfo->dest->next_output_byte = NULL;
    cinfo->dest->free_in_buffer = 0;
}

JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables
    (JNIEnv *env,
     jobject this,
     jlong ptr,
     jobjectArray qtables,
     jobjectArray DCHuffmanTables,
     jobjectArray ACHuffmanTables) {

    struct jpeg_destination_mgr *dest;
    sun_jpeg_error_ptr jerr;
    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_compress_ptr cinfo;

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use writer after dispose()");
        return;
    }

    cinfo = (j_compress_ptr) data->jpegObj;
    dest = cinfo->dest;

    /* Establish the setjmp return context for sun_jpeg_error_exit to use. */
    jerr = (sun_jpeg_error_ptr) cinfo->err;

    if (setjmp(jerr->setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error
           while writing. */
        RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
        if (!(*env)->ExceptionOccurred(env)) {
            char buffer[JMSG_LENGTH_MAX];
            (*cinfo->err->format_message) ((j_common_ptr) cinfo,
                                          buffer);
            JNU_ThrowByName(env, "javax/imageio/IIOException", buffer);
        }
        return;
    }

    if (GET_ARRAYS(env, data,
                   (const JOCTET **)(&dest->next_output_byte)) == NOT_OK) {
        JNU_ThrowByName(env,
                        "javax/imageio/IIOException",
                        "Array pin failed");
        return;
    }

    jpeg_suppress_tables(cinfo, TRUE);  // Suppress writing of any current

    data->streamBuf.suspendable = FALSE;
    if (qtables != NULL) {
#ifdef DEBUG_IIO_JPEG
        printf("in writeTables: qtables not NULL\n");
#endif
        setQTables(env, (j_common_ptr) cinfo, qtables, TRUE);
    }

    if (DCHuffmanTables != NULL) {
        setHTables(env, (j_common_ptr) cinfo,
                   DCHuffmanTables, ACHuffmanTables, TRUE);
    }

    jpeg_write_tables(cinfo); // Flushes the buffer for you
    RELEASE_ARRAYS(env, data, NULL);
}

JNIEXPORT jboolean JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage
    (JNIEnv *env,
     jobject this,
     jlong ptr,
     jbyteArray buffer,
     jint inCs, jint outCs,
     jint numBands,
     jintArray bandSizes,
     jint srcWidth,
     jint destWidth, jint destHeight,
     jint stepX, jint stepY,
     jobjectArray qtables,
     jboolean writeDQT,
     jobjectArray DCHuffmanTables,
     jobjectArray ACHuffmanTables,
     jboolean writeDHT,
     jboolean optimize,
     jboolean progressive,
     jint numScans,
     jintArray scanInfo,
     jintArray componentIds,
     jintArray HsamplingFactors,
     jintArray VsamplingFactors,
     jintArray QtableSelectors,
     jboolean haveMetadata,
     jint restartInterval) {

    struct jpeg_destination_mgr *dest;
    JSAMPROW scanLinePtr;
    int i, j;
    int pixelStride;
    unsigned char *in, *out, *pixelLimit, *scanLineLimit;
    unsigned int scanLineSize, pixelBufferSize;
    int targetLine;
    pixelBufferPtr pb;
    sun_jpeg_error_ptr jerr;
    jint *ids, *hfactors, *vfactors, *qsels;
    jsize qlen, hlen;
    int *scanptr;
    jint *scanData;
    jint *bandSize;
    int maxBandValue, halfMaxBandValue;
    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_compress_ptr cinfo;
    UINT8** scale = NULL;


    /* verify the inputs */

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use writer after dispose()");
        return JNI_FALSE;
    }

    if ((buffer == NULL) ||
        (qtables == NULL) ||
        // H tables can be null if optimizing
        (componentIds == NULL) ||
        (HsamplingFactors == NULL) || (VsamplingFactors == NULL) ||
        (QtableSelectors == NULL) ||
        ((numScans != 0) && (scanInfo != NULL))) {

        JNU_ThrowNullPointerException(env, 0);
        return JNI_FALSE;

    }

    scanLineSize = destWidth * numBands;
    if ((inCs < 0) || (inCs > JCS_YCCK) ||
        (outCs < 0) || (outCs > JCS_YCCK) ||
        (numBands < 1) || (numBands > MAX_BANDS) ||
        (srcWidth < 0) ||
        (destWidth < 0) || (destWidth > srcWidth) ||
        (destHeight < 0) ||
        (stepX < 0) || (stepY < 0) ||
        ((INT_MAX / numBands) < destWidth))  /* destWidth causes an integer overflow */
    {
        JNU_ThrowByName(env, "javax/imageio/IIOException",
                        "Invalid argument to native writeImage");
        return JNI_FALSE;
    }

    if (stepX > srcWidth) {
        stepX = srcWidth;
    }

    bandSize = (*env)->GetIntArrayElements(env, bandSizes, NULL);

    for (i = 0; i < numBands; i++) {
        if (bandSize[i] != JPEG_BAND_SIZE) {
            if (scale == NULL) {
                scale = (UINT8**) calloc(numBands, sizeof(UINT8*));

                if (scale == NULL) {
                    JNU_ThrowByName( env, "java/lang/OutOfMemoryError",
                                     "Writing JPEG Stream");
                    return JNI_FALSE;
                }
            }

            maxBandValue = (1 << bandSize[i]) - 1;

            scale[i] = (UINT8*) malloc((maxBandValue + 1) * sizeof(UINT8));

            if (scale[i] == NULL) {
                JNU_ThrowByName( env, "java/lang/OutOfMemoryError",
                                 "Writing JPEG Stream");
                return JNI_FALSE;
            }

            halfMaxBandValue = maxBandValue >> 1;

            for (j = 0; j <= maxBandValue; j++) {
                scale[i][j] = (UINT8)
                    ((j*MAX_JPEG_BAND_VALUE + halfMaxBandValue)/maxBandValue);
            }
        }
    }

    (*env)->ReleaseIntArrayElements(env, bandSizes,
                                    bandSize, JNI_ABORT);

    cinfo = (j_compress_ptr) data->jpegObj;
    dest = cinfo->dest;

    /* Set the buffer as our PixelBuffer */
    pb = &data->pixelBuf;

    if (setPixelBuffer(env, pb, buffer) == NOT_OK) {
        return data->abortFlag;  // We already threw an out of memory exception
    }

    // Allocate a 1-scanline buffer
    scanLinePtr = (JSAMPROW)malloc(scanLineSize);
    if (scanLinePtr == NULL) {
        RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Writing JPEG Stream");
        return data->abortFlag;
    }
    scanLineLimit = scanLinePtr + scanLineSize;

    /* Establish the setjmp return context for sun_jpeg_error_exit to use. */
    jerr = (sun_jpeg_error_ptr) cinfo->err;

    if (setjmp(jerr->setjmp_buffer)) {
        /* If we get here, the JPEG code has signaled an error
           while writing. */
        RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
        if (!(*env)->ExceptionOccurred(env)) {
            char buffer[JMSG_LENGTH_MAX];
            (*cinfo->err->format_message) ((j_common_ptr) cinfo,
                                          buffer);
            JNU_ThrowByName(env, "javax/imageio/IIOException", buffer);
        }

        if (scale != NULL) {
            for (i = 0; i < numBands; i++) {
                if (scale[i] != NULL) {
                    free(scale[i]);
                }
            }
            free(scale);
        }

        free(scanLinePtr);
        return data->abortFlag;
    }

    // set up parameters
    cinfo->image_width = destWidth;
    cinfo->image_height = destHeight;
    cinfo->input_components = numBands;
    cinfo->in_color_space = inCs;

    jpeg_set_defaults(cinfo);

    jpeg_set_colorspace(cinfo, outCs);

    cinfo->optimize_coding = optimize;

    cinfo->write_JFIF_header = FALSE;
    cinfo->write_Adobe_marker = FALSE;
    // copy componentIds
    ids = (*env)->GetIntArrayElements(env, componentIds, NULL);
    hfactors = (*env)->GetIntArrayElements(env, HsamplingFactors, NULL);
    vfactors = (*env)->GetIntArrayElements(env, VsamplingFactors, NULL);
    qsels = (*env)->GetIntArrayElements(env, QtableSelectors, NULL);

    if ((ids == NULL) ||
        (hfactors == NULL) || (vfactors == NULL) ||
        (qsels == NULL)) {
        JNU_ThrowByName( env,
                         "java/lang/OutOfMemoryError",
                         "Writing JPEG");
        return JNI_FALSE;
    }

    for (i = 0; i < numBands; i++) {
        cinfo->comp_info[i].component_id = ids[i];
        cinfo->comp_info[i].h_samp_factor = hfactors[i];
        cinfo->comp_info[i].v_samp_factor = vfactors[i];
        cinfo->comp_info[i].quant_tbl_no = qsels[i];
    }

    (*env)->ReleaseIntArrayElements(env, componentIds,
                                    ids, JNI_ABORT);
    (*env)->ReleaseIntArrayElements(env, HsamplingFactors,
                                    hfactors, JNI_ABORT);
    (*env)->ReleaseIntArrayElements(env, VsamplingFactors,
                                    vfactors, JNI_ABORT);
    (*env)->ReleaseIntArrayElements(env, QtableSelectors,
                                    qsels, JNI_ABORT);

    jpeg_suppress_tables(cinfo, TRUE);  // Disable writing any current

    qlen = setQTables(env, (j_common_ptr) cinfo, qtables, writeDQT);

    if (!optimize) {
        // Set the h tables
        hlen = setHTables(env,
                          (j_common_ptr) cinfo,
                          DCHuffmanTables,
                          ACHuffmanTables,
                          writeDHT);
    }

    if (GET_ARRAYS(env, data,
                   (const JOCTET **)(&dest->next_output_byte)) == NOT_OK) {
        JNU_ThrowByName(env,
                        "javax/imageio/IIOException",
                        "Array pin failed");
        return data->abortFlag;
    }

    data->streamBuf.suspendable = FALSE;

    if (progressive) {
        if (numScans == 0) { // then use default scans
            jpeg_simple_progression(cinfo);
        } else {
            cinfo->num_scans = numScans;
            // Copy the scanInfo to a local array
            // The following is copied from jpeg_simple_progression:
  /* Allocate space for script.
   * We need to put it in the permanent pool in case the application performs
   * multiple compressions without changing the settings.  To avoid a memory
   * leak if jpeg_simple_progression is called repeatedly for the same JPEG
   * object, we try to re-use previously allocated space, and we allocate
   * enough space to handle YCbCr even if initially asked for grayscale.
   */
            if (cinfo->script_space == NULL
                || cinfo->script_space_size < numScans) {
                cinfo->script_space_size = MAX(numScans, 10);
                cinfo->script_space = (jpeg_scan_info *)
                    (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo,
                                                JPOOL_PERMANENT,
                                                cinfo->script_space_size
                                                * sizeof(jpeg_scan_info));
            }
            cinfo->scan_info = cinfo->script_space;
            scanptr = (int *) cinfo->script_space;
            scanData = (*env)->GetIntArrayElements(env, scanInfo, NULL);
            // number of jints per scan is 9
            // We avoid a memcpy to handle different size ints
            for (i = 0; i < numScans*9; i++) {
                scanptr[i] = scanData[i];
            }
            (*env)->ReleaseIntArrayElements(env, scanInfo,
                                            scanData, JNI_ABORT);

        }
    }

    cinfo->restart_interval = restartInterval;

#ifdef DEBUG_IIO_JPEG
    printf("writer setup complete, starting compressor\n");
#endif

    // start the compressor; tables must already be set
    jpeg_start_compress(cinfo, FALSE); // Leaves sent_table alone

    if (haveMetadata) {
        // Flush the buffer
        imageio_flush_destination(cinfo);
        // Call Java to write the metadata
        RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
        (*env)->CallVoidMethod(env,
                               this,
                               JPEGImageWriter_writeMetadataID);
        if ((*env)->ExceptionOccurred(env)
            || !GET_ARRAYS(env, data,
                           (const JOCTET **)(&dest->next_output_byte))) {
                cinfo->err->error_exit((j_common_ptr) cinfo);
         }
    }

    targetLine = 0;
    pixelBufferSize = srcWidth * numBands;
    pixelStride = numBands * stepX;

    // for each line in destHeight
    while ((data->abortFlag == JNI_FALSE)
           && (cinfo->next_scanline < cinfo->image_height)) {
        // get the line from Java
        RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte));
        (*env)->CallVoidMethod(env,
                               this,
                               JPEGImageWriter_grabPixelsID,
                               targetLine);
        if ((*env)->ExceptionOccurred(env)
            || !GET_ARRAYS(env, data,
                           (const JOCTET **)(&dest->next_output_byte))) {
                cinfo->err->error_exit((j_common_ptr) cinfo);
         }

        // subsample it into our buffer

        in = data->pixelBuf.buf.bp;
        out = scanLinePtr;
        pixelLimit = in + ((pixelBufferSize > data->pixelBuf.byteBufferLength) ?
                           data->pixelBuf.byteBufferLength : pixelBufferSize);
        for (; (in < pixelLimit) && (out < scanLineLimit); in += pixelStride) {
            for (i = 0; i < numBands; i++) {
                if (scale !=NULL && scale[i] != NULL) {
                    *out++ = scale[i][*(in+i)];
#ifdef DEBUG_IIO_JPEG
                    if (in == data->pixelBuf.buf.bp){ // Just the first pixel
                        printf("in %d -> out %d, ", *(in+i), *(out-i-1));
                    }
#endif

#ifdef DEBUG_IIO_JPEG
                    if (in == data->pixelBuf.buf.bp){ // Just the first pixel
                        printf("\n");
                    }
#endif
                } else {
                    *out++ = *(in+i);
                }
            }
        }
        // write it out
        jpeg_write_scanlines(cinfo, (JSAMPARRAY)&scanLinePtr, 1);
        targetLine += stepY;
    }

    /*
     * We are done, but we might not have done all the lines,
     * so use jpeg_abort instead of jpeg_finish_compress.
     */
    if (cinfo->next_scanline == cinfo->image_height) {
        jpeg_finish_compress(cinfo);  // Flushes buffer with term_dest
    } else {
        jpeg_abort((j_common_ptr)cinfo);
    }

    if (scale != NULL) {
        for (i = 0; i < numBands; i++) {
            if (scale[i] != NULL) {
                free(scale[i]);
            }
        }
        free(scale);
    }

    free(scanLinePtr);
    RELEASE_ARRAYS(env, data, NULL);
    return data->abortFlag;
}

JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_abortWrite
    (JNIEnv *env,
     jobject this,
     jlong ptr) {

    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use writer after dispose()");
        return;
    }

    imageio_abort(env, this, data);
}

JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_resetWriter
    (JNIEnv *env,
     jobject this,
     jlong ptr) {
    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_compress_ptr cinfo;

    if (data == NULL) {
        JNU_ThrowByName(env,
                        "java/lang/IllegalStateException",
                        "Attempting to use writer after dispose()");
        return;
    }

    cinfo = (j_compress_ptr) data->jpegObj;

    imageio_reset(env, (j_common_ptr) cinfo, data);

    /*
     * The tables have not been reset, and there is no way to do so
     * in IJG without leaking memory.  The only situation in which
     * this will cause a problem is if an image-only stream is written
     * with this object without initializing the correct tables first,
     * which should not be possible.
     */

    cinfo->dest->next_output_byte = NULL;
    cinfo->dest->free_in_buffer = 0;
}

JNIEXPORT void JNICALL
Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_disposeWriter
    (JNIEnv *env,
     jclass writer,
     jlong ptr) {

    imageIODataPtr data = (imageIODataPtr)jlong_to_ptr(ptr);
    j_common_ptr info = destroyImageioData(env, data);

    imageio_dispose(info);
}
