#include "jpegtransform.h"

#include <stdio.h>
#include <setjmp.h>
#include <qglobal.h>
#include <qbuffer.h>

/**
 * Lots of lowlevel stuff from libjpeg6b utilities jpegtran.c and transupp.c,
 * (C)1995-1997, Thomas G. Lane.
 *
 * PixiePlus formatting and tweaks (C)2003, Daniel M. Duley.
 */

extern "C" {
#define XMD_H
#include <jpeglib.h>
#include <jerror.h>
#undef const

#define TRANSFORMS_SUPPORTED 1
#define BUFFER_SIZE 4096

struct mosfet_jpeg_destination_mgr : public jpeg_destination_mgr {
    QBuffer *destBuffer;
    JOCTET buffer[BUFFER_SIZE];
public:
    mosfet_jpeg_destination_mgr(QBuffer *);
};

static void mosfet_init_destination(j_compress_ptr cinfo)
{
    mosfet_jpeg_destination_mgr* dest = (mosfet_jpeg_destination_mgr*)cinfo->dest;
    dest->destBuffer->open(IO_ReadWrite | IO_Truncate);
}

static boolean mosfet_empty_output_buffer(j_compress_ptr cinfo)
{
    mosfet_jpeg_destination_mgr *dest = (mosfet_jpeg_destination_mgr*)cinfo->dest;
    if(dest->destBuffer->writeBlock((char*)dest->buffer, BUFFER_SIZE) != BUFFER_SIZE)
        return(false);

    dest->next_output_byte = dest->buffer;
    dest->free_in_buffer = BUFFER_SIZE;
    return(true);
}

static void mosfet_term_destination(j_compress_ptr cinfo)
{
    mosfet_jpeg_destination_mgr* dest = (mosfet_jpeg_destination_mgr*)cinfo->dest;
    Q_LONG n = BUFFER_SIZE - dest->free_in_buffer;
    dest->destBuffer->writeBlock((char*)dest->buffer, n );
}

inline mosfet_jpeg_destination_mgr::mosfet_jpeg_destination_mgr(QBuffer *buf)
{
    jpeg_destination_mgr::init_destination = mosfet_init_destination;
    jpeg_destination_mgr::empty_output_buffer = mosfet_empty_output_buffer;
    jpeg_destination_mgr::term_destination = mosfet_term_destination;
    destBuffer = buf;
    next_output_byte = buffer;
    free_in_buffer = BUFFER_SIZE;
}

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

typedef struct {
  /* Options: set by caller */
  JXFORM_CODE transform;        /* image transform operator */
  boolean trim;                 /* if TRUE, trim partial MCUs as needed */
  boolean force_grayscale;      /* if TRUE, convert color image to grayscale */

  /* Internal workspace: caller should not touch these */
  int num_components;           /* # of components in workspace */
  jvirt_barray_ptr * workspace_coef_arrays; /* workspace for transformations */
} jpeg_transform_info;

/* Request any required workspace */
void jtransform_request_workspace(j_decompress_ptr srcinfo,
                                  jpeg_transform_info *info);
/* Adjust output image parameters */
jvirt_barray_ptr* jtransform_adjust_parameters(j_decompress_ptr srcinfo,
                                               j_compress_ptr dstinfo,
                                               jvirt_barray_ptr *src_coef_arrays,
                                               jpeg_transform_info *info);
/* Execute the actual transformation, if any */
void jtransform_execute_transformation(j_decompress_ptr srcinfo,
                                       j_compress_ptr dstinfo,
                                       jvirt_barray_ptr *src_coef_arrays,
                                       jpeg_transform_info *info);

typedef enum {
        JCOPYOPT_NONE,          /* copy no optional markers */
        JCOPYOPT_COMMENTS,      /* copy only comment (COM) markers */
        JCOPYOPT_ALL            /* copy all optional markers */
} JCOPY_OPTION;

#define JCOPYOPT_DEFAULT  JCOPYOPT_COMMENTS     /* recommended default */

/* Setup decompression object to save desired markers in memory */
void jcopy_markers_setup(j_decompress_ptr srcinfo, JCOPY_OPTION option);
/* Copy markers saved in the given source object to the destination object */
void jcopy_markers_execute(j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
                           JCOPY_OPTION option);

long jdiv_round_up(long a, long b);
long jround_up(long a, long b);
void jcopy_sample_rows(JSAMPARRAY input_array, int source_row,
                       JSAMPARRAY output_array, int dest_row,
                       int num_rows, JDIMENSION num_cols);
void jcopy_block_row(JBLOCKROW input_row, JBLOCKROW output_row,
                     JDIMENSION num_blocks);
void jzero_far(void FAR * target, size_t bytestozero);

const int jpeg_natural_order[DCTSIZE2+16] = {
    0,  1,  8, 16,  9,  2,  3, 10,
    17, 24, 32, 25, 18, 11,  4,  5,
    12, 19, 26, 33, 40, 48, 41, 34,
    27, 20, 13,  6,  7, 14, 21, 28,
    35, 42, 49, 56, 57, 50, 43, 36,
    29, 22, 15, 23, 30, 37, 44, 51,
    58, 59, 52, 45, 38, 31, 39, 46,
    53, 60, 61, 54, 47, 55, 62, 63,
    63, 63, 63, 63, 63, 63, 63, 63, /* extra entries for safety in decoder */
    63, 63, 63, 63, 63, 63, 63, 63
};


/* Implementations of lowlevel transform functions */
void do_flip_h (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
                jvirt_barray_ptr *src_coef_arrays)
/* Horizontal flip; done in-place, so no separate dest array is required */
{
    JDIMENSION MCU_cols, comp_width, blk_x, blk_y;
    int ci, k, offset_y;
    JBLOCKARRAY buffer;
    JCOEFPTR ptr1, ptr2;
    JCOEF temp1, temp2;
    jpeg_component_info *compptr;

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

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

void do_flip_v (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
                jvirt_barray_ptr *src_coef_arrays,
                jvirt_barray_ptr *dst_coef_arrays)
/* Vertical flip */
{
    JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
    int ci, i, j, offset_y;
    JBLOCKARRAY src_buffer, dst_buffer;
    JBLOCKROW src_row_ptr, dst_row_ptr;
    JCOEFPTR src_ptr, dst_ptr;
    jpeg_component_info *compptr;

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

    for (ci = 0; ci < dstinfo->num_components; ci++) {
        compptr = dstinfo->comp_info + ci;
        comp_height = MCU_rows * compptr->v_samp_factor;
        for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
             dst_blk_y += compptr->v_samp_factor) {
            dst_buffer = (*srcinfo->mem->access_virt_barray)
                ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
                 (JDIMENSION) compptr->v_samp_factor, TRUE);
            if (dst_blk_y < comp_height) {
                /* Row is within the mirrorable area. */
                src_buffer = (*srcinfo->mem->access_virt_barray)
                    ((j_common_ptr) srcinfo, src_coef_arrays[ci],
                     comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor,
                     (JDIMENSION) compptr->v_samp_factor, FALSE);
            } else {
                /* Bottom-edge blocks will be copied verbatim. */
                src_buffer = (*srcinfo->mem->access_virt_barray)
                    ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y,
                     (JDIMENSION) compptr->v_samp_factor, FALSE);
            }
            for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
                if (dst_blk_y < comp_height) {
	  /* Row is within the mirrorable area. */
                    dst_row_ptr = dst_buffer[offset_y];
                    src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
                    for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
                         dst_blk_x++) {
                        dst_ptr = dst_row_ptr[dst_blk_x];
                        src_ptr = src_row_ptr[dst_blk_x];
                        for (i = 0; i < DCTSIZE; i += 2) {
                            /* copy even row */
                            for (j = 0; j < DCTSIZE; j++)
                                *dst_ptr++ = *src_ptr++;
                            /* copy odd row with sign change */
                            for (j = 0; j < DCTSIZE; j++)
                                *dst_ptr++ = - *src_ptr++;
                        }
                    }
                } else {
                    /* Just copy row verbatim. */
                    jcopy_block_row(src_buffer[offset_y], dst_buffer[offset_y],
                                    compptr->width_in_blocks);
                }
            }
        }
    }
}


void do_transpose (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
                   jvirt_barray_ptr *src_coef_arrays,
                   jvirt_barray_ptr *dst_coef_arrays)
/* Transpose source into destination */
{
    JDIMENSION dst_blk_x, dst_blk_y;
    int ci, i, j, offset_x, offset_y;
    JBLOCKARRAY src_buffer, dst_buffer;
    JCOEFPTR src_ptr, dst_ptr;
    jpeg_component_info *compptr;

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


void do_rot_90 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
                jvirt_barray_ptr *src_coef_arrays,
                jvirt_barray_ptr *dst_coef_arrays)
/* 90 degree rotation is equivalent to
 *   1. Transposing the image;
 *   2. Horizontal mirroring.
 * These two steps are merged into a single processing routine.
 */
{
    JDIMENSION MCU_cols, comp_width, dst_blk_x, dst_blk_y;
    int ci, i, j, offset_x, offset_y;
    JBLOCKARRAY src_buffer, dst_buffer;
    JCOEFPTR src_ptr, dst_ptr;
    jpeg_component_info *compptr;

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

    for (ci = 0; ci < dstinfo->num_components; ci++) {
        compptr = dstinfo->comp_info + ci;
        comp_width = MCU_cols * compptr->h_samp_factor;
        for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
             dst_blk_y += compptr->v_samp_factor) {
            dst_buffer = (*srcinfo->mem->access_virt_barray)
                ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
                 (JDIMENSION) compptr->v_samp_factor, TRUE);
            for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
                for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
                     dst_blk_x += compptr->h_samp_factor) {
                    src_buffer = (*srcinfo->mem->access_virt_barray)
                        ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
                         (JDIMENSION) compptr->h_samp_factor, FALSE);
                    for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
                        src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
                        if (dst_blk_x < comp_width) {
                            /* Block is within the mirrorable area. */
                            dst_ptr = dst_buffer[offset_y]
                                [comp_width - dst_blk_x - offset_x - 1];
                            for (i = 0; i < DCTSIZE; i++) {
                                for (j = 0; j < DCTSIZE; j++)
                                    dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
                                i++;
                                for (j = 0; j < DCTSIZE; j++)
                                    dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
                            }
                        } else {
                            /* Edge blocks are transposed but not mirrored. */
                            dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
                            for (i = 0; i < DCTSIZE; i++)
                                for (j = 0; j < DCTSIZE; j++)
                                    dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
                        }
                    }
                }
            }
        }
    }
}


void do_rot_270 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
                 jvirt_barray_ptr *src_coef_arrays,
                 jvirt_barray_ptr *dst_coef_arrays)
/* 270 degree rotation is equivalent to
 *   1. Horizontal mirroring;
 *   2. Transposing the image.
 * These two steps are merged into a single processing routine.
 */
{
    JDIMENSION MCU_rows, comp_height, dst_blk_x, dst_blk_y;
    int ci, i, j, offset_x, offset_y;
    JBLOCKARRAY src_buffer, dst_buffer;
    JCOEFPTR src_ptr, dst_ptr;
    jpeg_component_info *compptr;

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

    for (ci = 0; ci < dstinfo->num_components; ci++) {
        compptr = dstinfo->comp_info + ci;
        comp_height = MCU_rows * compptr->v_samp_factor;
        for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
             dst_blk_y += compptr->v_samp_factor) {
            dst_buffer = (*srcinfo->mem->access_virt_barray)
                ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
                 (JDIMENSION) compptr->v_samp_factor, TRUE);
            for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
                for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
                     dst_blk_x += compptr->h_samp_factor) {
                    src_buffer = (*srcinfo->mem->access_virt_barray)
                        ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
                         (JDIMENSION) compptr->h_samp_factor, FALSE);
                    for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
                        dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
                        if (dst_blk_y < comp_height) {
                            /* Block is within the mirrorable area. */
                            src_ptr = src_buffer[offset_x]
                                [comp_height - dst_blk_y - offset_y - 1];
                            for (i = 0; i < DCTSIZE; i++) {
                                for (j = 0; j < DCTSIZE; j++) {
                                    dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
                                    j++;
                                    dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
                                }
                            }
                        } else {
                            /* Edge blocks are transposed but not mirrored. */
                            src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
                            for (i = 0; i < DCTSIZE; i++)
                                for (j = 0; j < DCTSIZE; j++)
                                    dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
                        }
                    }
                }
            }
        }
    }
}


void do_rot_180 (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
                 jvirt_barray_ptr *src_coef_arrays,
                 jvirt_barray_ptr *dst_coef_arrays)
/* 180 degree rotation is equivalent to
 *   1. Vertical mirroring;
 *   2. Horizontal mirroring.
 * These two steps are merged into a single processing routine.
 */
{
    JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
    int ci, i, j, offset_y;
    JBLOCKARRAY src_buffer, dst_buffer;
    JBLOCKROW src_row_ptr, dst_row_ptr;
    JCOEFPTR src_ptr, dst_ptr;
    jpeg_component_info *compptr;

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

    for (ci = 0; ci < dstinfo->num_components; ci++) {
        compptr = dstinfo->comp_info + ci;
        comp_width = MCU_cols * compptr->h_samp_factor;
        comp_height = MCU_rows * compptr->v_samp_factor;
        for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
             dst_blk_y += compptr->v_samp_factor) {
            dst_buffer = (*srcinfo->mem->access_virt_barray)
                ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
                 (JDIMENSION) compptr->v_samp_factor, TRUE);
            if (dst_blk_y < comp_height) {
                /* Row is within the vertically mirrorable area. */
                src_buffer = (*srcinfo->mem->access_virt_barray)
                    ((j_common_ptr) srcinfo, src_coef_arrays[ci],
                     comp_height - dst_blk_y - (JDIMENSION) compptr->v_samp_factor,
                     (JDIMENSION) compptr->v_samp_factor, FALSE);
            } else {
                /* Bottom-edge rows are only mirrored horizontally. */
                src_buffer = (*srcinfo->mem->access_virt_barray)
                    ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_y,
                     (JDIMENSION) compptr->v_samp_factor, FALSE);
            }
            for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
                if (dst_blk_y < comp_height) {
                    /* Row is within the mirrorable area. */
                    dst_row_ptr = dst_buffer[offset_y];
                    src_row_ptr = src_buffer[compptr->v_samp_factor - offset_y - 1];
                    /* Process the blocks that can be mirrored both ways. */
                    for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) {
                        dst_ptr = dst_row_ptr[dst_blk_x];
                        src_ptr = src_row_ptr[comp_width - dst_blk_x - 1];
                        for (i = 0; i < DCTSIZE; i += 2) {
                            /* For even row, negate every odd column. */
                            for (j = 0; j < DCTSIZE; j += 2) {
                                *dst_ptr++ = *src_ptr++;
                                *dst_ptr++ = - *src_ptr++;
                            }
                            /* For odd row, negate every even column. */
                            for (j = 0; j < DCTSIZE; j += 2) {
                                *dst_ptr++ = - *src_ptr++;
                                *dst_ptr++ = *src_ptr++;
                            }
                        }
                    }
                    /* Any remaining right-edge blocks are only mirrored vertically. */
                    for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
                        dst_ptr = dst_row_ptr[dst_blk_x];
                        src_ptr = src_row_ptr[dst_blk_x];
                        for (i = 0; i < DCTSIZE; i += 2) {
                            for (j = 0; j < DCTSIZE; j++)
                                *dst_ptr++ = *src_ptr++;
                            for (j = 0; j < DCTSIZE; j++)
                                *dst_ptr++ = - *src_ptr++;
                        }
                    }
                } else {
                    /* Remaining rows are just mirrored horizontally. */
                    dst_row_ptr = dst_buffer[offset_y];
                    src_row_ptr = src_buffer[offset_y];
                    /* Process the blocks that can be mirrored. */
                    for (dst_blk_x = 0; dst_blk_x < comp_width; dst_blk_x++) {
                        dst_ptr = dst_row_ptr[dst_blk_x];
                        src_ptr = src_row_ptr[comp_width - dst_blk_x - 1];
                        for (i = 0; i < DCTSIZE2; i += 2) {
                            *dst_ptr++ = *src_ptr++;
                            *dst_ptr++ = - *src_ptr++;
                        }
                    }
                    /* Any remaining right-edge blocks are only copied. */
                    for (; dst_blk_x < compptr->width_in_blocks; dst_blk_x++) {
                        dst_ptr = dst_row_ptr[dst_blk_x];
                        src_ptr = src_row_ptr[dst_blk_x];
                        for (i = 0; i < DCTSIZE2; i++)
                            *dst_ptr++ = *src_ptr++;
                    }
                }
            }
        }
    }
}


void do_transverse (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
                    jvirt_barray_ptr *src_coef_arrays,
                    jvirt_barray_ptr *dst_coef_arrays)
/* Transverse transpose is equivalent to
 *   1. 180 degree rotation;
 *   2. Transposition;
 * or
 *   1. Horizontal mirroring;
 *   2. Transposition;
 *   3. Horizontal mirroring.
 * These steps are merged into a single processing routine.
 */
{
    JDIMENSION MCU_cols, MCU_rows, comp_width, comp_height, dst_blk_x, dst_blk_y;
    int ci, i, j, offset_x, offset_y;
    JBLOCKARRAY src_buffer, dst_buffer;
    JCOEFPTR src_ptr, dst_ptr;
    jpeg_component_info *compptr;

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

    for (ci = 0; ci < dstinfo->num_components; ci++) {
        compptr = dstinfo->comp_info + ci;
        comp_width = MCU_cols * compptr->h_samp_factor;
        comp_height = MCU_rows * compptr->v_samp_factor;
        for (dst_blk_y = 0; dst_blk_y < compptr->height_in_blocks;
             dst_blk_y += compptr->v_samp_factor) {
            dst_buffer = (*srcinfo->mem->access_virt_barray)
                ((j_common_ptr) srcinfo, dst_coef_arrays[ci], dst_blk_y,
                 (JDIMENSION) compptr->v_samp_factor, TRUE);
            for (offset_y = 0; offset_y < compptr->v_samp_factor; offset_y++) {
                for (dst_blk_x = 0; dst_blk_x < compptr->width_in_blocks;
                     dst_blk_x += compptr->h_samp_factor) {
                    src_buffer = (*srcinfo->mem->access_virt_barray)
                        ((j_common_ptr) srcinfo, src_coef_arrays[ci], dst_blk_x,
                         (JDIMENSION) compptr->h_samp_factor, FALSE);
                    for (offset_x = 0; offset_x < compptr->h_samp_factor; offset_x++) {
                        if (dst_blk_y < comp_height) {
                            src_ptr = src_buffer[offset_x]
                                [comp_height - dst_blk_y - offset_y - 1];
                            if (dst_blk_x < comp_width) {
                                /* Block is within the mirrorable area. */
                                dst_ptr = dst_buffer[offset_y]
                                    [comp_width - dst_blk_x - offset_x - 1];
                                for (i = 0; i < DCTSIZE; i++) {
                                    for (j = 0; j < DCTSIZE; j++) {
                                        dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
                                        j++;
                                        dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
                                    }
                                    i++;
                                    for (j = 0; j < DCTSIZE; j++) {
                                        dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
                                        j++;
                                        dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
                                    }
                                }
                            } else {
                                /* Right-edge blocks are mirrored in y only */
                                dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
                                for (i = 0; i < DCTSIZE; i++) {
                                    for (j = 0; j < DCTSIZE; j++) {
                                        dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
                                        j++;
                                        dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
                                    }
                                }
                            }
                        } else {
                            src_ptr = src_buffer[offset_x][dst_blk_y + offset_y];
                            if (dst_blk_x < comp_width) {
                                /* Bottom-edge blocks are mirrored in x only */
                                dst_ptr = dst_buffer[offset_y]
                                    [comp_width - dst_blk_x - offset_x - 1];
                                for (i = 0; i < DCTSIZE; i++) {
                                    for (j = 0; j < DCTSIZE; j++)
                                        dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
                                    i++;
                                    for (j = 0; j < DCTSIZE; j++)
                                        dst_ptr[j*DCTSIZE+i] = -src_ptr[i*DCTSIZE+j];
                                }
                            } else {
                                /* At lower right corner, just transpose, no mirroring */
                                dst_ptr = dst_buffer[offset_y][dst_blk_x + offset_x];
                                for (i = 0; i < DCTSIZE; i++)
                                    for (j = 0; j < DCTSIZE; j++)
                                        dst_ptr[j*DCTSIZE+i] = src_ptr[i*DCTSIZE+j];
                            }
                        }
                    }
                }
            }
        }
    }
}


/* Request any required workspace.
 *
 * We allocate the workspace virtual arrays from the source decompression
 * object, so that all the arrays (both the original data and the workspace)
 * will be taken into account while making memory management decisions.
 * Hence, this routine must be called after jpeg_read_header (which reads
 * the image dimensions) and before jpeg_read_coefficients (which realizes
 * the source's virtual arrays).
 */


void jtransform_request_workspace (j_decompress_ptr srcinfo,
                                   jpeg_transform_info *info)
{
    jvirt_barray_ptr *coef_arrays = NULL;
    jpeg_component_info *compptr;
    int ci;

    if (info->force_grayscale &&
        srcinfo->jpeg_color_space == JCS_YCbCr &&
        srcinfo->num_components == 3) {
        /* We'll only process the first component */
        info->num_components = 1;
    } else {
        /* Process all the components */
        info->num_components = srcinfo->num_components;
    }

    switch (info->transform) {
    case JXFORM_NONE:
    case JXFORM_FLIP_H:
        /* Don't need a workspace array */
        break;
    case JXFORM_FLIP_V:
    case JXFORM_ROT_180:
        /* Need workspace arrays having same dimensions as source image.
         * Note that we allocate arrays padded out to the next iMCU boundary,
         * so that transform routines need not worry about missing edge blocks.
         */
        coef_arrays = (jvirt_barray_ptr *)
            (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
                                          sizeof(jvirt_barray_ptr) * info->num_components);
        for (ci = 0; ci < info->num_components; ci++) {
            compptr = srcinfo->comp_info + ci;
            coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
                ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE,
                 (JDIMENSION) jround_up((long) compptr->width_in_blocks,
                                        (long) compptr->h_samp_factor),
                 (JDIMENSION) jround_up((long) compptr->height_in_blocks,
                                        (long) compptr->v_samp_factor),
                 (JDIMENSION) compptr->v_samp_factor);
        }
        break;
    case JXFORM_TRANSPOSE:
    case JXFORM_TRANSVERSE:
    case JXFORM_ROT_90:
    case JXFORM_ROT_270:
        /* Need workspace arrays having transposed dimensions.
         * Note that we allocate arrays padded out to the next iMCU boundary,
         * so that transform routines need not worry about missing edge blocks.
         */
        coef_arrays = (jvirt_barray_ptr *)
            (*srcinfo->mem->alloc_small) ((j_common_ptr) srcinfo, JPOOL_IMAGE,
                                          sizeof(jvirt_barray_ptr) * info->num_components);
        for (ci = 0; ci < info->num_components; ci++) {
            compptr = srcinfo->comp_info + ci;
            coef_arrays[ci] = (*srcinfo->mem->request_virt_barray)
                ((j_common_ptr) srcinfo, JPOOL_IMAGE, FALSE,
                 (JDIMENSION) jround_up((long) compptr->height_in_blocks,
                                        (long) compptr->v_samp_factor),
                 (JDIMENSION) jround_up((long) compptr->width_in_blocks,
                                        (long) compptr->h_samp_factor),
                 (JDIMENSION) compptr->h_samp_factor);
        }
        break;
    }
    info->workspace_coef_arrays = coef_arrays;
}


/* Transpose destination image parameters */

void transpose_critical_parameters (j_compress_ptr dstinfo)
{
    int tblno, i, j, ci, itemp;
    jpeg_component_info *compptr;
    JQUANT_TBL *qtblptr;
    JDIMENSION dtemp;
    UINT16 qtemp;

    /* Transpose basic image dimensions */
    dtemp = dstinfo->image_width;
    dstinfo->image_width = dstinfo->image_height;
    dstinfo->image_height = dtemp;

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

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


/* Trim off any partial iMCUs on the indicated destination edge */

void trim_right_edge (j_compress_ptr dstinfo)
{
    int ci, max_h_samp_factor;
    JDIMENSION MCU_cols;

    /* We have to compute max_h_samp_factor ourselves,
     * because it hasn't been set yet in the destination
     * (and we don't want to use the source's value).
     */
    max_h_samp_factor = 1;
    for (ci = 0; ci < dstinfo->num_components; ci++) {
        int h_samp_factor = dstinfo->comp_info[ci].h_samp_factor;
        max_h_samp_factor = QMAX(max_h_samp_factor, h_samp_factor);
    }
    MCU_cols = dstinfo->image_width / (max_h_samp_factor * DCTSIZE);
    if (MCU_cols > 0)		/* can't trim to 0 pixels */
        dstinfo->image_width = MCU_cols * (max_h_samp_factor * DCTSIZE);
}

void trim_bottom_edge (j_compress_ptr dstinfo)
{
    int ci, max_v_samp_factor;
    JDIMENSION MCU_rows;

    /* We have to compute max_v_samp_factor ourselves,
     * because it hasn't been set yet in the destination
     * (and we don't want to use the source's value).
     */
    max_v_samp_factor = 1;
    for (ci = 0; ci < dstinfo->num_components; ci++) {
        int v_samp_factor = dstinfo->comp_info[ci].v_samp_factor;
        max_v_samp_factor = QMAX(max_v_samp_factor, v_samp_factor);
    }
    MCU_rows = dstinfo->image_height / (max_v_samp_factor * DCTSIZE);
    if (MCU_rows > 0)		/* can't trim to 0 pixels */
        dstinfo->image_height = MCU_rows * (max_v_samp_factor * DCTSIZE);
}


/* Adjust output image parameters as needed.
 *
 * This must be called after jpeg_copy_critical_parameters()
 * and before jpeg_write_coefficients().
 *
 * The return value is the set of virtual coefficient arrays to be written
 * (either the ones allocated by jtransform_request_workspace, or the
 * original source data arrays).  The caller will need to pass this value
 * to jpeg_write_coefficients().
 */

jvirt_barray_ptr* jtransform_adjust_parameters (j_decompress_ptr /*srcinfo*/,
                                                j_compress_ptr dstinfo,
                                                jvirt_barray_ptr *src_coef_arrays,
                                                jpeg_transform_info *info)
{
    /* If force-to-grayscale is requested, adjust destination parameters */
    if (info->force_grayscale) {
        /* We use jpeg_set_colorspace to make sure subsidiary settings get fixed
         * properly.  Among other things, the target h_samp_factor & v_samp_factor
         * will get set to 1, which typically won't match the source.
         * In fact we do this even if the source is already grayscale; that
         * provides an easy way of coercing a grayscale JPEG with funny sampling
         * factors to the customary 1,1.  (Some decoders fail on other factors.)
         */
        if ((dstinfo->jpeg_color_space == JCS_YCbCr &&
             dstinfo->num_components == 3) ||
            (dstinfo->jpeg_color_space == JCS_GRAYSCALE &&
             dstinfo->num_components == 1)) {
            /* We have to preserve the source's quantization table number. */
            int sv_quant_tbl_no = dstinfo->comp_info[0].quant_tbl_no;
            jpeg_set_colorspace(dstinfo, JCS_GRAYSCALE);
            dstinfo->comp_info[0].quant_tbl_no = sv_quant_tbl_no;
        } else {
            /* Sorry, can't do it */
            ERREXIT(dstinfo, JERR_CONVERSION_NOTIMPL);
        }
    }

    /* Correct the destination's image dimensions etc if necessary */
    switch (info->transform) {
    case JXFORM_NONE:
        /* Nothing to do */
        break;
    case JXFORM_FLIP_H:
        if (info->trim)
            trim_right_edge(dstinfo);
        break;
    case JXFORM_FLIP_V:
        if (info->trim)
            trim_bottom_edge(dstinfo);
        break;
    case JXFORM_TRANSPOSE:
        transpose_critical_parameters(dstinfo);
        /* transpose does NOT have to trim anything */
        break;
    case JXFORM_TRANSVERSE:
        transpose_critical_parameters(dstinfo);
        if (info->trim) {
            trim_right_edge(dstinfo);
            trim_bottom_edge(dstinfo);
        }
        break;
    case JXFORM_ROT_90:
        transpose_critical_parameters(dstinfo);
        if (info->trim)
            trim_right_edge(dstinfo);
        break;
    case JXFORM_ROT_180:
        if (info->trim) {
            trim_right_edge(dstinfo);
            trim_bottom_edge(dstinfo);
        }
        break;
    case JXFORM_ROT_270:
        transpose_critical_parameters(dstinfo);
        if (info->trim)
            trim_bottom_edge(dstinfo);
        break;
    }

    /* Return the appropriate output data set */
    if (info->workspace_coef_arrays != NULL)
        return info->workspace_coef_arrays;
    return src_coef_arrays;
}


/* Execute the actual transformation, if any.
 *
 * This must be called *after* jpeg_write_coefficients, because it depends
 * on jpeg_write_coefficients to have computed subsidiary values such as
 * the per-component width and height fields in the destination object.
 *
 * Note that some transformations will modify the source data arrays!
 */

void jtransform_execute_transformation (j_decompress_ptr srcinfo,
                                        j_compress_ptr dstinfo,
                                        jvirt_barray_ptr *src_coef_arrays,
                                        jpeg_transform_info *info)
{
    jvirt_barray_ptr *dst_coef_arrays = info->workspace_coef_arrays;

    switch (info->transform) {
    case JXFORM_NONE:
        break;
    case JXFORM_FLIP_H:
        do_flip_h(srcinfo, dstinfo, src_coef_arrays);
        break;
    case JXFORM_FLIP_V:
        do_flip_v(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
        break;
    case JXFORM_TRANSPOSE:
        do_transpose(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
        break;
    case JXFORM_TRANSVERSE:
        do_transverse(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
        break;
    case JXFORM_ROT_90:
        do_rot_90(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
        break;
    case JXFORM_ROT_180:
        do_rot_180(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
        break;
    case JXFORM_ROT_270:
        do_rot_270(srcinfo, dstinfo, src_coef_arrays, dst_coef_arrays);
        break;
    }
}

/* Setup decompression object to save desired markers in memory.
 * This must be called before jpeg_read_header() to have the desired effect.
 */

void jcopy_markers_setup (j_decompress_ptr srcinfo, JCOPY_OPTION option)
{
    int m;

    /* Save comments except under NONE option */
    if (option != JCOPYOPT_NONE) {
        jpeg_save_markers(srcinfo, JPEG_COM, 0xFFFF);
    }
    /* Save all types of APPn markers iff ALL option */
    if (option == JCOPYOPT_ALL) {
        for (m = 0; m < 16; m++)
            jpeg_save_markers(srcinfo, JPEG_APP0 + m, 0xFFFF);
    }
}

/* Copy markers saved in the given source object to the destination object.
 * This should be called just after jpeg_start_compress() or
 * jpeg_write_coefficients().
 * Note that those routines will have written the SOI, and also the
 * JFIF APP0 or Adobe APP14 markers if selected.
 */

void jcopy_markers_execute (j_decompress_ptr srcinfo, j_compress_ptr dstinfo,
                            JCOPY_OPTION /*option*/)
{
    jpeg_saved_marker_ptr marker;

    /* In the current implementation, we don't actually need to examine the
     * option flag here; we just copy everything that got saved.
     * But to avoid confusion, we do not output JFIF and Adobe APP14 markers
     * if the encoder library already wrote one.
     */
    for (marker = srcinfo->marker_list; marker != NULL; marker = marker->next) {
        if (dstinfo->write_JFIF_header &&
            marker->marker == JPEG_APP0 &&
            marker->data_length >= 5 &&
            GETJOCTET(marker->data[0]) == 0x4A &&
            GETJOCTET(marker->data[1]) == 0x46 &&
            GETJOCTET(marker->data[2]) == 0x49 &&
            GETJOCTET(marker->data[3]) == 0x46 &&
            GETJOCTET(marker->data[4]) == 0)
            continue;			/* reject duplicate JFIF */
        if (dstinfo->write_Adobe_marker &&
            marker->marker == JPEG_APP0+14 &&
            marker->data_length >= 5 &&
            GETJOCTET(marker->data[0]) == 0x41 &&
            GETJOCTET(marker->data[1]) == 0x64 &&
            GETJOCTET(marker->data[2]) == 0x6F &&
            GETJOCTET(marker->data[3]) == 0x62 &&
            GETJOCTET(marker->data[4]) == 0x65)
            continue;			/* reject duplicate Adobe */
        jpeg_write_marker(dstinfo, marker->marker,
                          marker->data, marker->data_length);
    }
}

long jdiv_round_up (long a, long b)
/* Compute a/b rounded up to next integer, ie, ceil(a/b) */
/* Assumes a >= 0, b > 0 */
{
  return (a + b - 1L) / b;
}


long jround_up (long a, long b)
/* Compute a rounded up to next multiple of b, ie, ceil(a/b)*b */
/* Assumes a >= 0, b > 0 */
{
  a += b - 1L;
  return a - (a % b);
}

void jcopy_sample_rows (JSAMPARRAY input_array, int source_row,
                        JSAMPARRAY output_array, int dest_row,
                        int num_rows, JDIMENSION num_cols)
/* Copy some rows of samples from one place to another.
 * num_rows rows are copied from input_array[source_row++]
 * to output_array[dest_row++]; these areas may overlap for duplication.
 * The source and destination arrays must be at least as wide as num_cols.
 */
{
    register JSAMPROW inptr, outptr;
    register JDIMENSION count;
    register int row;

    input_array += source_row;
    output_array += dest_row;

    for (row = num_rows; row > 0; row--) {
        inptr = *input_array++;
        outptr = *output_array++;
        for (count = num_cols; count > 0; count--)
            *outptr++ = *inptr++;	/* needn't bother with GETJSAMPLE() here */
    }
}


void jcopy_block_row (JBLOCKROW input_row, JBLOCKROW output_row,
		 JDIMENSION num_blocks)
/* Copy a row of coefficient blocks from one place to another. */
{
    register JCOEFPTR inptr, outptr;
    register long count;

    inptr = (JCOEFPTR) input_row;
    outptr = (JCOEFPTR) output_row;
    for (count = (long) num_blocks * DCTSIZE2; count > 0; count--) {
        *outptr++ = *inptr++;
    }
}


void jzero_far (void FAR * target, size_t bytestozero)
/* Zero out a chunk of FAR memory. */
/* This might be sample-array data, block-array data, or alloc_large data. */
{
    register char FAR * ptr = (char FAR *) target;
    register size_t count;

    for (count = bytestozero; count > 0; count--) {
        *ptr++ = 0;
    }
}

} // extern "C"

/**
 *
 * This is the actual stuff from jpegtran
 *
 */

bool transformJPEG(const char *filename, QBuffer *buffer,
                   JPEGTransform transform)
{
    struct jpeg_decompress_struct srcinfo;
    struct jpeg_compress_struct dstinfo;
    struct jpeg_error_mgr jsrcerr, jdsterr;
    jvirt_barray_ptr *src_coef_arrays;
    jvirt_barray_ptr *dst_coef_arrays;
    bool simple_progressive;

    JCOPY_OPTION copyoption;
    jpeg_transform_info transformoption;
    FILE *input_file;

    srcinfo.err = jpeg_std_error(&jsrcerr);
    jpeg_create_decompress(&srcinfo);
    dstinfo.err = jpeg_std_error(&jdsterr);
    jpeg_create_compress(&dstinfo);

    struct mosfet_jpeg_destination_mgr *dest_mgr =
        new mosfet_jpeg_destination_mgr(buffer);

    simple_progressive = false;
    copyoption = JCOPYOPT_COMMENTS;
    transformoption.transform = JXFORM_NONE;
    transformoption.trim = true;
    transformoption.force_grayscale = false;
    dstinfo.err->trace_level = 0;
    switch(transform){
    case JPEGProgressive:
        simple_progressive = true;
        break;
    case JPEGFlipH:
        transformoption.transform = JXFORM_FLIP_H;
        break;
    case JPEGFlipV:
        transformoption.transform = JXFORM_FLIP_V;
        break;
    case JPEGRot90:
        transformoption.transform = JXFORM_ROT_90;
        break;
    case JPEGRot180:
        transformoption.transform = JXFORM_ROT_180;
        break;
    case JPEGRot270:
        transformoption.transform = JXFORM_ROT_270;
        break;
    default:
        break;
    }
    if(simple_progressive)  
        jpeg_simple_progression(&dstinfo);

    if((input_file = fopen(filename, "rb")) == NULL){
        qWarning("Unable to open %s!", filename);
        // fixme - destroy stuff
        return(false);
    }

    jpeg_stdio_src(&srcinfo, input_file);
    jcopy_markers_setup(&srcinfo, copyoption);
    (void)jpeg_read_header(&srcinfo, true);
    jtransform_request_workspace(&srcinfo, &transformoption);
    src_coef_arrays = jpeg_read_coefficients(&srcinfo);
    jpeg_copy_critical_parameters(&srcinfo, &dstinfo);
    dst_coef_arrays = jtransform_adjust_parameters(&srcinfo, &dstinfo,
                                                   src_coef_arrays,
                                                   &transformoption);
    dstinfo.dest = dest_mgr;
    //jpeg_stdio_dest(&dstinfo, output_file);
    jpeg_write_coefficients(&dstinfo, dst_coef_arrays);
    jcopy_markers_execute(&srcinfo, &dstinfo, copyoption);
    jtransform_execute_transformation(&srcinfo, &dstinfo,
                                      src_coef_arrays,
                                      &transformoption);
    jpeg_finish_compress(&dstinfo);
    jpeg_destroy_compress(&dstinfo);
    (void)jpeg_finish_decompress(&srcinfo);
    jpeg_destroy_decompress(&srcinfo);
    fclose(input_file);
    delete dest_mgr;
    return(jsrcerr.num_warnings + jdsterr.num_warnings ? false : true);
}

