/******************************************************************************
 * $Id$
 *
 * Project:  MapServer
 * Purpose:  MapCache tile caching support file: JPEG format
 * Author:   Thomas Bonfort and the MapServer team.
 *
 ******************************************************************************
 * Copyright (c) 1996-2011 Regents of the University of Minnesota.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of this Software or works derived from this Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *****************************************************************************/

#include "mapcache.h"
#include <apr_strings.h>
#include <jpeglib.h>

/**\addtogroup imageio_jpg */
/** @{ */

typedef struct {
  struct jpeg_destination_mgr pub;
  unsigned char *data;
  mapcache_buffer *buffer;
} mapcache_jpeg_destination_mgr;


#define OUTPUT_BUF_SIZE 4096

static void _mapcache_imageio_jpeg_init_source(j_decompress_ptr cinfo)
{
  /* nothing to do */
}

static int _mapcache_imageio_jpeg_fill_input_buffer(j_decompress_ptr cinfo)
{
  static JOCTET mybuffer[4];

  /* The whole JPEG data is expected to reside in the supplied memory
   * buffer, so any request for more data beyond the given buffer size
   * is treated as an error.
   */
  /* Insert a fake EOI marker */
  mybuffer[0] = (JOCTET) 0xFF;
  mybuffer[1] = (JOCTET) JPEG_EOI;

  cinfo->src->next_input_byte = mybuffer;
  cinfo->src->bytes_in_buffer = 2;

  return TRUE;
}

static void _mapcache_imageio_jpeg_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
{
  struct jpeg_source_mgr * src = cinfo->src;

  /* Just a dumb implementation for now.  Could use fseek() except
   * it doesn't work on pipes.  Not clear that being smart is worth
   * any trouble anyway --- large skips are infrequent.
   */
  if (num_bytes > 0) {
    while (num_bytes > (long) src->bytes_in_buffer) {
      num_bytes -= (long) src->bytes_in_buffer;
      (void) (*src->fill_input_buffer) (cinfo);
      /* note we assume that fill_input_buffer will never return FALSE,
       * so suspension need not be handled.
       */
    }
    src->next_input_byte += (size_t) num_bytes;
    src->bytes_in_buffer -= (size_t) num_bytes;
  }
}

static void _mapcache_imageio_jpeg_term_source(j_decompress_ptr cinfo)
{
}



int _mapcache_imageio_jpeg_mem_src (j_decompress_ptr cinfo, unsigned char * inbuffer, unsigned long insize)
{
  struct jpeg_source_mgr * src;

  if (inbuffer == NULL || insize == 0) /* Treat empty input as fatal error */
    return MAPCACHE_FAILURE;

  /* The source object is made permanent so that a series of JPEG images
   * can be read from the same buffer by calling jpeg_mem_src only before
   * the first one.
   */
  if (cinfo->src == NULL) {   /* first time for this JPEG object? */
    cinfo->src = (struct jpeg_source_mgr *)
                 (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
                     sizeof(struct jpeg_source_mgr));
  }

  src = cinfo->src;
  src->init_source = _mapcache_imageio_jpeg_init_source;
  src->fill_input_buffer = _mapcache_imageio_jpeg_fill_input_buffer;
  src->skip_input_data = _mapcache_imageio_jpeg_skip_input_data;
  src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
  src->term_source = _mapcache_imageio_jpeg_term_source;
  src->bytes_in_buffer = (size_t) insize;
  src->next_input_byte = (JOCTET *) inbuffer;
  return MAPCACHE_SUCCESS;
}



void _mapcache_imageio_jpeg_init_destination (j_compress_ptr cinfo)
{
  mapcache_jpeg_destination_mgr *dest = (mapcache_jpeg_destination_mgr*) cinfo->dest;

  /* Allocate the output buffer --- it will be released when done with image */
  dest->data = (unsigned char *)(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo,
               JPOOL_IMAGE,OUTPUT_BUF_SIZE * sizeof (unsigned char));

  dest->pub.next_output_byte = dest->data;
  dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
}

void _mapcache_imageio_jpeg_buffer_term_destination (j_compress_ptr cinfo)
{
  mapcache_jpeg_destination_mgr *dest = (mapcache_jpeg_destination_mgr*) cinfo->dest;
  mapcache_buffer_append(dest->buffer,OUTPUT_BUF_SIZE-dest->pub.free_in_buffer, dest->data);
  dest->pub.next_output_byte = dest->data;
  dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
}


int _mapcache_imageio_jpeg_buffer_empty_output_buffer (j_compress_ptr cinfo)
{
  mapcache_jpeg_destination_mgr *dest = (mapcache_jpeg_destination_mgr*) cinfo->dest;
  mapcache_buffer_append(dest->buffer,OUTPUT_BUF_SIZE, dest->data);
  dest->pub.next_output_byte = dest->data;
  dest->pub.free_in_buffer = OUTPUT_BUF_SIZE;
  return TRUE;
}

mapcache_buffer* _mapcache_imageio_jpeg_encode(mapcache_context *ctx, mapcache_image *img, mapcache_image_format *format)
{
  struct jpeg_compress_struct cinfo;
  struct jpeg_error_mgr jerr;
  mapcache_jpeg_destination_mgr *dest;
  JSAMPLE *rowdata;
  unsigned int row;
  mapcache_buffer *buffer = mapcache_buffer_create(5000, ctx->pool);
  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_compress(&cinfo);

  cinfo.dest = (struct jpeg_destination_mgr *)(*cinfo.mem->alloc_small) (
                 (j_common_ptr) &cinfo, JPOOL_PERMANENT,
                 sizeof (mapcache_jpeg_destination_mgr));
  ((mapcache_jpeg_destination_mgr*)cinfo.dest)->pub.empty_output_buffer = _mapcache_imageio_jpeg_buffer_empty_output_buffer;
  ((mapcache_jpeg_destination_mgr*)cinfo.dest)->pub.term_destination = _mapcache_imageio_jpeg_buffer_term_destination;
  ((mapcache_jpeg_destination_mgr*)cinfo.dest)->buffer = buffer;

  dest = (mapcache_jpeg_destination_mgr*) cinfo.dest;
  dest->pub.init_destination = _mapcache_imageio_jpeg_init_destination;

  cinfo.image_width = img->w;
  cinfo.image_height = img->h;
  cinfo.input_components = 3;
  cinfo.in_color_space = JCS_RGB;
  jpeg_set_defaults(&cinfo);
  jpeg_set_quality(&cinfo, ((mapcache_image_format_jpeg*)format)->quality, TRUE);
  switch(((mapcache_image_format_jpeg*)format)->photometric) {
    case MAPCACHE_PHOTOMETRIC_RGB:
      jpeg_set_colorspace(&cinfo, JCS_RGB);
      break;
    case MAPCACHE_PHOTOMETRIC_YCBCR:
    default:
      jpeg_set_colorspace(&cinfo, JCS_YCbCr);
  }
  switch(((mapcache_image_format_jpeg*)format)->optimize) {
    case MAPCACHE_OPTIMIZE_NO:
      cinfo.optimize_coding = FALSE;
      break;
    case MAPCACHE_OPTIMIZE_ARITHMETIC:
      cinfo.optimize_coding = FALSE;
      cinfo.arith_code = TRUE;
      break;
    case MAPCACHE_OPTIMIZE_YES:
    default:
      cinfo.optimize_coding = TRUE;
  }
  jpeg_start_compress(&cinfo, TRUE);

  rowdata = (JSAMPLE*)malloc(img->w*cinfo.input_components*sizeof(JSAMPLE));
  for(row=0; row<img->h; row++) {
    JSAMPLE *pixptr = rowdata;
    int col;
    unsigned char *r,*g,*b;
    r=&(img->data[2])+row*img->stride;
    g=&(img->data[1])+row*img->stride;
    b=&(img->data[0])+row*img->stride;
    for(col=0; col<img->w; col++) {
      *(pixptr++) = *r;
      *(pixptr++) = *g;
      *(pixptr++) = *b;
      r+=4;
      g+=4;
      b+=4;
    }
    (void) jpeg_write_scanlines(&cinfo, &rowdata, 1);
  }

  /* Step 6: Finish compression */

  jpeg_finish_compress(&cinfo);
  jpeg_destroy_compress(&cinfo);
  free(rowdata);
  return buffer;
}

void _mapcache_imageio_jpeg_decode_to_image(mapcache_context *r, mapcache_buffer *buffer,
    mapcache_image *img)
{
  int s;
  struct jpeg_decompress_struct cinfo = {NULL};
  struct jpeg_error_mgr jerr;
  unsigned char *temp;
  jpeg_create_decompress(&cinfo);
  cinfo.err = jpeg_std_error(&jerr);
  if (_mapcache_imageio_jpeg_mem_src(&cinfo,buffer->buf, buffer->size) != MAPCACHE_SUCCESS) {
    r->set_error(r,500,"failed to allocate jpeg decoding struct");
    return;
  }

  img->has_alpha = MC_ALPHA_NO;
  jpeg_read_header(&cinfo, TRUE);
  jpeg_start_decompress(&cinfo);
  img->w = cinfo.output_width;
  img->h = cinfo.output_height;
  s = cinfo.output_components;
  if(!img->data) {
    img->data = calloc(1,img->w*img->h*4*sizeof(unsigned char));
    apr_pool_cleanup_register(r->pool, img->data, (void*)free, apr_pool_cleanup_null) ;
    img->stride = img->w * 4;
  }

  temp = malloc(img->w*s);
  apr_pool_cleanup_register(r->pool, temp, (void*)free, apr_pool_cleanup_null) ;
  while ((int)cinfo.output_scanline < img->h) {
    int i;
    unsigned char *rowptr = &img->data[cinfo.output_scanline * img->stride];
    unsigned char *tempptr = temp;
    jpeg_read_scanlines(&cinfo, &tempptr, 1);
    if (s == 1) {
      for (i = 0; i < img->w; i++) {
        *rowptr++ = *tempptr;
        *rowptr++ = *tempptr;
        *rowptr++ = *tempptr;
        *rowptr++ = 255;
        tempptr++;
      }
    } else if (s == 3) {
      for (i = 0; i < img->w; i++) {
        rowptr[0] = tempptr[2];
        rowptr[1] = tempptr[1];
        rowptr[2] = tempptr[0];
        rowptr[3] = 255;
        rowptr+=4;
        tempptr+=3;
      }
    } else {
      r->set_error(r, 500, "unsupported jpeg format");
      jpeg_destroy_decompress(&cinfo);
      return;
    }
  }
  jpeg_finish_decompress(&cinfo);
  jpeg_destroy_decompress(&cinfo);
}

mapcache_image* _mapcache_imageio_jpeg_decode(mapcache_context *r, mapcache_buffer *buffer)
{
  mapcache_image *img = mapcache_image_create(r);
  _mapcache_imageio_jpeg_decode_to_image(r, buffer,img);
  if(GC_HAS_ERROR(r)) {
    return NULL;
  }
  return img;

}

static mapcache_buffer* _mapcache_imageio_jpg_create_empty(mapcache_context *ctx, mapcache_image_format *format,
    size_t width, size_t height, unsigned int color)
{
  mapcache_image *empty;
  mapcache_buffer *buf;
  int i;
  apr_pool_t *pool = NULL;
  if(apr_pool_create(&pool,ctx->pool) != APR_SUCCESS) {
    ctx->set_error(ctx,500,"png create empty: failed to create temp memory pool");
    return NULL;
  }
  empty = mapcache_image_create(ctx);
  if(GC_HAS_ERROR(ctx)) {
    return NULL;
  }
  empty->data = malloc(width*height*4*sizeof(unsigned char));
  for(i=0; i<width*height; i++) {
    ((unsigned int*)empty->data)[i] = color;
  }
  empty->w = width;
  empty->h = height;
  empty->stride = width * 4;

  buf = format->write(ctx,empty,format);
  apr_pool_destroy(pool);
  free(empty->data);
  return buf;
}

mapcache_image_format* mapcache_imageio_create_jpeg_format(apr_pool_t *pool, char *name, int quality,
    mapcache_photometric photometric, mapcache_optimization optimize)
{
  mapcache_image_format_jpeg *format = apr_pcalloc(pool, sizeof(mapcache_image_format_jpeg));
  format->format.name = name;
  format->format.extension = apr_pstrdup(pool,"jpg");
  format->format.mime_type = apr_pstrdup(pool,"image/jpeg");
  format->format.metadata = apr_table_make(pool,3);
  format->format.create_empty_image = _mapcache_imageio_jpg_create_empty;
  format->format.write = _mapcache_imageio_jpeg_encode;
  format->quality = quality;
  format->optimize = optimize;
  format->photometric = photometric;
  format->format.type = GC_JPEG;
  return (mapcache_image_format*)format;
}

/** @} */



/* vim: ts=2 sts=2 et sw=2
*/
