Logo Search packages:      
Sourcecode: ziproxy version File versions  Download package

image.c

/* image.c
 * Part of ziproxy package
 *
 * Copyright (c)2002-2004 Juraj Variny<variny@naex.sk>
 * Copyright (c)2005-2009 Daniel Mealha Cabrita
 *
 * Released subject to GNU General Public License v2 or later version.
 *
 * Image reading/writing wrappers.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include <gif_lib.h>

#include <jpeglib.h>
//remove some leaks from jpeglib headers:
#undef METHODDEF
#undef EXTERN
#undef LOCAL
#undef GLOBAL

#include <png.h>

#ifdef JP2K
#include <jasper/jasper.h>
#include "jp2tools.h"
#endif

#define SRC_IMAGE_C
#include "image.h"
#include "cfgfile.h"
#include "log.h"
#include "cvtables.h"

// minimal image file size to _bother_ trying to recompress
// this is for code documentation purposes
// TODO: later we may add an option like "MinSize"
int min_insize = 800;   // was 600 (ziproxy <= 2.4.3) but jp2 compression destroys images which are too small

static int outlen;
static char *outbuf;

//Forwards. There are more utility functions, but they're used only once.
static raw_bitmap *new_raw_bitmap();

static int png2bitmap(char *inbuf, int insize, raw_bitmap **out, long long int max_raw_size);
static int gif2bitmap(char *inbuf, int insize, raw_bitmap **out, long long int max_raw_size);
static int jpg2bitmap(char *inbuf, int insize, raw_bitmap **out, long long int max_raw_size);

static int bitmap2jpg(raw_bitmap  * bmp, int quality, char ** outb, int * outl);

static int bitmap2png(raw_bitmap  * bmp, char ** outb, int * outl);

static int rgb2gray(raw_bitmap * bmp);
static int depalettize(raw_bitmap * bmp);

#ifdef JP2K
int calculate_jp2_rawsize (raw_bitmap *bmp, const t_color_space target_clrspc, const int *bitlenYA, const int *bitlenRGBA, const int*bitlenYUVA, const int *csamplingYA, const int *csamplingRGBA, const int *csamplingYUVA, int discard_alpha);
float estimate_jp2rate_from_quality (raw_bitmap *bmp, int quality, const t_color_space target_clrspc, const int *bitlenYA, const int *bitlenRGBA, const int *bitlenYUVA, const int *csamplingYA, const int *csamplingRGBA, const int *csamplingYUVA);
static int bitmap2jp2 (raw_bitmap *bmp, float rate, char **outb, int *outl, const t_color_space target_clrspc, const int *bitlenYA, const int *bitlenRGBA, const int *bitlenYUVA, const int *csamplingYA, const int *csamplingRGBA, const int *csamplingYUVA);
static int jp22bitmap (char *inbuf, int insize, raw_bitmap **out, long long int max_raw_size);
#endif

int detect_type(char * line, int len){
      int image = OTHER_CONTENT;
      if(len <  4) return OTHER_CONTENT;
    // Check for image signatures
      if ((line[0] == -1) && (line[1] == -40) &&
            (line[2] == -1) && (line[3] == -32))
            image = IMG_JPEG;
      else if ((line[0] == 'G') && (line[1] == 'I') &&
                 (line[2] == 'F') && (line[3] == '8'))
            image = IMG_GIF;
      else if ((line[0] == -119) && (line[1] == 'P') &&
                 (line[2] == 'N') && (line[3] == 'G'))
            image = IMG_PNG;
#ifdef JP2K
      // TODO: improve JP2K detection
      else if ((line[4] == 'j') && (line[5] == 'P'))
            image = IMG_JP2K;
#endif
      return image;
}

/* What will be done here with images:
 *
 * Compiled without JP2K, regardless of HTML modification
 * Input -> Output
 * GIF      -> PNG or JPEG, whatever is smaller
 * PNG      -> JPEG
 * JPEG     -> JPEG
 *
 * Compiled with JP2K, HTML modification on
 * Input -> Output if in OBJECT tag : Output if outside OBJECT tag
 * GIF      -> JP2K : PNG or JPEG, whatever is smaller
 * PNG      -> JP2K : JPEG
 * JPEG     -> JP2K : JPEG
 * 
 * Compiled with JP2K, HTML modification off
 * Input -> Output
 * GIF      -> PNG or JP2K, whatever is smaller
 * PNG      -> JP2K
 * JPEG     -> JP2K
 *
 * Compression GIF -> PNG is tried even if relevant ImageQuality or JP2Rate 
 * option is set to zero.
 */

int compress_image(http_headers *serv_hdr, http_headers *client_hdr, char *inbuf, int insize, char ** outb, int *outl){ 
      int st = IMG_RET_ERR_OTHER, pngstatus = IMG_RET_ERR_OTHER, jp2status = IMG_RET_ERR_OTHER, jpegstatus = IMG_RET_ERR_OTHER;
      int outtype = OTHER_CONTENT;
      int q, quality = -1;
      int try, TryPNG, TryJPEG, TryJP2;
      char *tempp;
      raw_bitmap *bmp;
      long long int max_raw_size;

      const int *j2bitlenYA, *j2bitlenRGBA, *j2bitlenYUVA, *j2csamplingYA, *j2csamplingRGBA, *j2csamplingYUVA;
      // "rate" below: JP2 rate, the native compression setting of JP2
      // ziproxy tries to emulate JPEG's quality setting to JP2, and this
      // var represents the 'real thing' which is hidden from the user.
      float rate = -1.0;
      int jp2_q, jp2_quality = 0; // this var doesn't have the quirks as 'quality'

      try = detect_type(inbuf, insize);

      if (try != OTHER_CONTENT) serv_hdr->type = try;

      max_raw_size = insize * MaxUncompressedImageRatio;

      switch (serv_hdr->type) {
            case IMG_PNG:if(insize >= min_insize) st = png2bitmap(inbuf, insize, &bmp, max_raw_size);
                             else st = IMG_RET_TOO_SMALL;
                   quality = 128;
                   break;
            case IMG_GIF:if(insize >= min_insize) st = gif2bitmap(inbuf, insize, &bmp, max_raw_size);
                             else st = IMG_RET_TOO_SMALL;
                   quality = 128;
                   break;
            case IMG_JPEG:if(insize >= min_insize) st = jpg2bitmap(inbuf, insize, &bmp, max_raw_size);
                              else st = IMG_RET_TOO_SMALL;
                   quality = 0;
                   break;
#ifdef JP2K
            case IMG_JP2K:
                  if (ForceOutputNoJP2)
                        min_insize = 0;
                  if (insize >= min_insize)
                        st = jp22bitmap (inbuf, insize, &bmp, max_raw_size);
                  else
                        st = IMG_RET_TOO_SMALL;
                  quality = 0;
                  break;
#endif
      }

      // error, forward unchanged
      if (st != IMG_RET_OK) {
            *outb = inbuf;
            *outl = insize;
            return st;
      }

      // TODO: make it configurable
      // the point of this size limitation is to avoid losing noticeable quality
      // (remember we're doing lossy compression over already lossy data) for little gain.
      // this is specially relevant dealing with highly compressed and/or small pictures.
      //
      //worsest compression ratio allowed 85%(large images) - 90% (small)
      outlen = insize * 0.85 + 30;
      //What about this? Or make this configurable?
      //outlen = insize * 0.7;

#ifdef JP2K
      // the size limitation does not apply when _forcing_ conversion from JP2K
      // (chances are the final size will be bigger anyway, we do that for compatibility, not for compression)
      if ((serv_hdr->type == IMG_JP2K) && (ForceOutputNoJP2))
            outlen = (bmp->width * bmp->height * bmp->bpp) + 500; // up to 100%+500bytes of uncompressed bitmap, otherwise it's an abnomaly
#endif

      outbuf = (char *)malloc(outlen);

      tempp = outbuf;
      try = outlen;

      // get the image quality specifically for this image (based on image dimensions)
      q = getImageQuality(bmp->width, bmp->height);
      
#ifdef JP2K
      // FIXME: should this be inside JP2K restrictions or is that a bug? Verify this later.
      TryPNG = (IMG_GIF == serv_hdr->type);

      jp2_q = getJP2ImageQuality(bmp->width, bmp->height);
      
      // get the components' bit depth specifically for this image (based on image dimensions)
      j2bitlenYA = getJP2KBitLenYA (bmp->width, bmp->height);
      j2bitlenRGBA = getJP2KBitLenRGBA (bmp->width, bmp->height);
      j2bitlenYUVA = getJP2KBitLenYUVA (bmp->width, bmp->height);

      // get the components' sampling (scaling) parameters specifically for this image (based on image dimensions)
      j2csamplingYA = getJP2KCSamplingYA (bmp->width, bmp->height);
      j2csamplingRGBA = getJP2KCSamplingRGBA (bmp->width, bmp->height);
      j2csamplingYUVA = getJP2KCSamplingYUVA (bmp->width, bmp->height);

      if (ProcessToJP2 && (! ForceOutputNoJP2) && ((! JP2OutRequiresExpCap) || (JP2OutRequiresExpCap && client_hdr->client_explicity_accepts_jp2)))
            TryJP2 = ((jp2_q != 0) && (insize >= min_insize));
      else
            TryJP2 = 0; // we don't want to recompress to/back_to JP2K

      TryJPEG = ((q != 0) && (insize >= min_insize));
#else
      TryPNG = ((IMG_GIF == serv_hdr->type));
      TryJP2 = 0;
      TryJPEG = ((q != 0) && (insize >= min_insize));
#endif
/*    logprintf("Will try compress image to: %s %s %s", 
                  TryPNG ? "PNG" : "",
                  TryJPEG ? "JPEG" : "",
                  TryJP2 ? "JP2" : "");*/
      
      //Go compressing the image.

      // for testing purposes: enabled to force to JP2 output always
      //if (TryJP2)
      //    TryJPEG = 0;

#ifdef JP2K
      if (TryJP2) {
            if(bmp->bitmap == NULL) depalettize(bmp);
            if (jp2_q < 0){
                  jp2_q = -jp2_q;
                  rgb2gray (bmp);
            }
            jp2_quality += jp2_q;

            rate = estimate_jp2rate_from_quality (bmp, jp2_quality, JP2Colorspace, j2bitlenYA, j2bitlenRGBA, j2bitlenYUVA, j2csamplingYA, j2csamplingRGBA, j2csamplingYUVA);

            if (rate * (float) calculate_jp2_rawsize (bmp, JP2Colorspace, j2bitlenYA, j2bitlenRGBA, j2bitlenYUVA, j2csamplingYA, j2csamplingRGBA, j2csamplingYUVA, 0) <= (float) outlen) {
                  jp2status = bitmap2jp2 (bmp, rate, outb, outl, JP2Colorspace, j2bitlenYA, j2bitlenRGBA, j2bitlenYUVA, j2csamplingYA, j2csamplingRGBA, j2csamplingYUVA);
            } else {
                  jp2status = 1;
            }
      } else {
            jp2status = 1;
      }
#endif

      if(TryJPEG){
            if(bmp->bitmap == NULL) depalettize(bmp);
            if (q < 0){
                  q = -q;
                  rgb2gray(bmp);
            }
            quality += q;

            jpegstatus = bitmap2jpg(bmp, quality, &outbuf, &outlen);
      }

      //Try PNG only if JPEG/JP2 didn't compressed it enough.
      try=0;
      if(TryPNG && (jp2status || (try=*outl) > insize/2) && 
                  (jpegstatus || (try=outlen) > insize/2)){
            if(!try) try = insize * 0.85 + 30;
            tempp = (char*)malloc(try);
            pngstatus = bitmap2png(bmp, &tempp, &try);
      }

      //See what happened.
      if(!TryJP2 && (TryPNG || TryJPEG)){
            if(!jpegstatus && pngstatus){
                  *outb = outbuf;
                  *outl = outlen;
                  outtype = IMG_JPEG;
            }else if(!pngstatus){
                  *outb = tempp;
                  *outl = try;
                  outtype = IMG_PNG;
            }else{
                  *outb = inbuf;
                  *outl = insize;
                  outtype = OTHER_CONTENT;
            }
      
      }else if (TryPNG || TryJP2){
            if( !jp2status && pngstatus){
                  outtype = IMG_JP2K;
            } else if(!pngstatus){
                  *outb = tempp;
                  *outl = try;
                  outtype = IMG_PNG;
            } else {
                  *outb = inbuf;
                  *outl = insize;
                  outtype = OTHER_CONTENT;
            }
      
      }else assert(1);
      
      if (serv_hdr->where_content_type > 0){ 
            if(outtype != OTHER_CONTENT)
                  switch(outtype){
                        case IMG_JP2K:
                              serv_hdr->hdr[serv_hdr->where_content_type] =
                                    "Content-Type: image/jp2";
                              break;
                        case IMG_JPEG:          
                              serv_hdr->hdr[serv_hdr->where_content_type] = 
                                    "Content-Type: image/jpeg";
                              break;
                        case IMG_PNG:
                              serv_hdr->hdr[serv_hdr->where_content_type] = 
                                    "Content-Type: image/png";
                              break;
                  }
      }
      return IMG_RET_OK; //status??
}

00326 typedef struct {
            char* buf;
            int size;
            union {
                  int pos;
                  char jpeg_unexpected_end;
            }x;
}IODesc;


raw_bitmap *new_raw_bitmap(){
      raw_bitmap * bmp = (raw_bitmap*)malloc(sizeof(raw_bitmap));
      bmp->bitmap = bmp->bitmap_yuv = bmp->raster = bmp->palette = NULL;
      bmp->height = bmp->width = bmp->bpp = bmp->pal_bpp = -1;
      bmp->pal_entries = -1;
      return bmp;
}


static void mem_to_png(png_structp png_ptr,
        png_bytep data, png_size_t length)
{
            IODesc *desc=(IODesc*)png_get_io_ptr(png_ptr);
            
            if(desc->x.pos + length >= desc->size) png_error(png_ptr, "Reading past input buffer\n");
            
      memcpy(data, desc->buf + desc->x.pos,length);
      desc->x.pos += length;
}

static void png_warn_still(png_structp png_ptr, png_const_charp msg){}

static void png_err_still(png_structp png_ptr, png_const_charp msg)
{
      longjmp(png_jmpbuf(png_ptr), IMG_RET_ERR_UNKNOWN + IMG_RET_FLG_WHILE_DECOMP);
}

int png2bitmap(char *inbuf, int insize, raw_bitmap **out, long long int max_raw_size)
{
      png_structp png_ptr;
      png_infop info_ptr, end_info = NULL;
      int color_type, grayscale, bit_depth;
      // int num_palette;
      // png_color *palette;
      png_bytepp row_pointers;
      png_bytep onerow;
      int i;
      IODesc desc;
      raw_bitmap *bmp;
      long long int raw_size;

   /* Compare the first 8 bytes of the signature.
      Return with error if they don't match. */

      if((insize < 8) || png_sig_cmp(inbuf, (png_size_t)0, 8))
            return IMG_RET_ERR_BAD_DATA_FORMAT + IMG_RET_FLG_WHILE_DECOMP;
   
      png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
      NULL, NULL, NULL);
      if (png_ptr == NULL) {
                  return IMG_RET_ERR_OUT_OF_MEM + IMG_RET_FLG_WHILE_DECOMP;
      }

      
      info_ptr = png_create_info_struct(png_ptr);
      if (info_ptr == NULL) {
                  return IMG_RET_ERR_OUT_OF_MEM + IMG_RET_FLG_WHILE_DECOMP;
      }
      if ((i = setjmp(png_jmpbuf(png_ptr)))) {
      png_destroy_read_struct(&png_ptr, &info_ptr,
         &end_info);
            return i;
      }

      png_set_error_fn(png_ptr,NULL, 
                  png_err_still, png_warn_still);     

      desc.buf=inbuf;
      desc.size=insize;
      desc.x.pos=0;

      bmp = new_raw_bitmap();
      *out = bmp;

      png_set_read_fn(png_ptr,(voidp)&desc,mem_to_png);
      
      png_read_info(png_ptr,info_ptr);
      png_get_IHDR(png_ptr,info_ptr,(png_uint_32*)&(bmp->width),
                  (png_uint_32*)&(bmp->height),
                  &bit_depth,&color_type,NULL,NULL,NULL);

      /* MaxUncompressedImageRatio checking */
      /* this raw_size represents the real raw size of the bitmap,
         which may (and probably will) be smaller than the
         bitmap allocated in memory */
      raw_size = (bmp->width * bmp->height * bit_depth) / 8;
      if ((max_raw_size > 0) && (raw_size > max_raw_size))
            return (IMG_RET_TOO_EXPANSIVE);

      grayscale = 1;
      
      if (color_type & PNG_COLOR_MASK_PALETTE)
      {
            //TODO better palette->grayscale expansion
            //Detects grayscale image (scans all colors in palette)
/*                png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette);
            for(i=0;i<num_palette;i++)
                        if(palette[i].red != palette[i].green ||
                                          palette[i].green != palette[i].blue)
                        {
                                    grayscale=0;
                                    break;
                        }*/
            png_set_palette_to_rgb(png_ptr);
            // A palette image may or may not have alpha
            // transparency. I'd be safe and remove it anyway.
            png_set_strip_alpha(png_ptr); 

            if(!AllowLookCh){
            //this option needs explicit check for transparency 
                  png_bytep trans;
                  int num_trans;
                  png_color_16p trans_values;
                  
                  png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values);
                  if(num_trans > 0)
                        return IMG_RET_ERR_POSSIBLE_LOOK_CHANGE + IMG_RET_FLG_WHILE_DECOMP;
            }
      }
      
      if (color_type == PNG_COLOR_TYPE_GRAY || 
                        color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
      {
            grayscale=1;      
        if(bit_depth < 8) png_set_gray_1_2_4_to_8(png_ptr);
      }else grayscale=0;
      
      if (bit_depth == 16)
        png_set_strip_16(png_ptr);
      
       if (color_type & PNG_COLOR_MASK_ALPHA){
             if(!AllowLookCh) return IMG_RET_ERR_POSSIBLE_LOOK_CHANGE + IMG_RET_FLG_WHILE_DECOMP;
              png_set_strip_alpha(png_ptr);
       }
       

      if (grayscale) bmp->bpp = 1;
            else bmp->bpp = 3;

      row_pointers = (png_bytepp)malloc(bmp->height * sizeof(png_bytep));
      
      bmp->bitmap=(unsigned char*)malloc(
                  (bmp->width) * (bmp->height) * (bmp->bpp));
      onerow=(png_bytep)bmp->bitmap;

      for (i=0; i < bmp->height; i++) {
      row_pointers[i]=onerow;
            onerow += (bmp->width) * (bmp->bpp);
      }
      png_read_image(png_ptr,row_pointers);

      free(row_pointers);

      png_destroy_read_struct(&png_ptr, &info_ptr,NULL);

      return IMG_RET_OK;
}

static void png_to_mem(png_structp png_ptr,
        png_bytep data, png_size_t length){
            IODesc *desc=(IODesc*)png_get_io_ptr(png_ptr);
            if(length + desc->x.pos > desc->size)
                  png_error(png_ptr, "Writing past output buffer\n");
            
      memcpy(desc->buf + desc->x.pos, data ,length);
      desc->x.pos += length;
}

static void png_flush_mem(png_structp png_ptr){};

static int bitmap2png(raw_bitmap  * bmp, char ** outb, int * outl){
      int i, ctype, bits = 8;
      IODesc desc;
      png_bytepp row_pointers;
      png_bytep onerow;
      png_color_16 transcol;
      char *tr = NULL;
      png_infop info_ptr;
      
      png_structp png_ptr = png_create_write_struct
      (PNG_LIBPNG_VER_STRING, NULL,
      NULL, NULL);
      
      if (png_ptr == NULL)
      return (IMG_RET_ERR_UNKNOWN + IMG_RET_FLG_WHILE_COMPRESS);

      info_ptr = png_create_info_struct(png_ptr);
      if (info_ptr == NULL) {
            png_destroy_write_struct(&png_ptr,
             (png_infopp)NULL);
            return (IMG_RET_ERR_UNKNOWN + IMG_RET_FLG_WHILE_COMPRESS);
      }
      
      if ((i = setjmp(png_jmpbuf(png_ptr)))) {
            png_destroy_write_struct(&png_ptr,
             &info_ptr);
            return (i);
      }

      png_set_error_fn(png_ptr,NULL, 
                  png_err_still, png_warn_still);     

      if(bmp->pal_entries > 0){
            png_color *palette;
            
            palette = (png_colorp)png_malloc(png_ptr, bmp->pal_entries
                 * sizeof (png_color));
                 
            if(bmp->pal_bpp == 2 || bmp->pal_bpp == 4){
                  tr = (char*)malloc(bmp->pal_entries);
            }
            
            palette = malloc(bmp->pal_entries * sizeof(png_color));
            for(i=0;i < bmp->pal_entries;i++){
                  if(tr){
                        if(0 == bmp->palette[(i+1)*bmp->pal_bpp -1]){
                              transcol.index = i;
                              tr[i] = 0;
                        }else tr[i] = 255;
                  }
                        
                  if(bmp->pal_bpp < 3)
                  palette[i].red = palette[i].green = palette[i].blue = bmp->palette[i * bmp->pal_bpp];
                  else{
                  palette[i].red = bmp->palette[i*bmp->pal_bpp];
                  palette[i].green = bmp->palette[i*bmp->pal_bpp + 1];
                  palette[i].blue = bmp->palette[i*bmp->pal_bpp + 2];
                  }
            }
            ctype = PNG_COLOR_TYPE_PALETTE;
            png_set_PLTE(png_ptr, info_ptr, palette, bmp->pal_entries);

            if (tr) {
                  png_set_tRNS(png_ptr, info_ptr, tr, bmp->pal_entries,NULL);
                  //png_set_bKGD(png_ptr, info_ptr, &transcol);
                  
            }
            if(bmp->pal_entries <= 2) bits = 1;
            else if(bmp->pal_entries <= 4) bits = 2;
            else if(bmp->pal_entries <= 16) bits = 4;
            
            onerow=(png_bytep)bmp->raster;
      }else{
            switch(bmp->bpp){
                  case 1: ctype = PNG_COLOR_TYPE_GRAY;
                        break;
                  case 2: ctype = PNG_COLOR_TYPE_GRAY_ALPHA;
                        break;
                  case 3: ctype = PNG_COLOR_TYPE_RGB;
                        break;
                  case 4: ctype = PNG_COLOR_TYPE_RGB_ALPHA;
                        break;
            }
/*          color_bpps.red = 8;
            color_bpps.green = 8;
            color_bpps.blue = 8;
            color_bpps.gray = 8;
            color_bpps.alpha = 8;*/

            png_set_filter(png_ptr, 0,PNG_FILTER_SUB);

//          png_set_sBIT(png_ptr, info_ptr, &color_bpps);
            onerow=(png_bytep)bmp->bitmap;
      }

      png_set_IHDR(png_ptr, info_ptr, bmp->width, bmp->height, bits,
                  ctype,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT,
                  PNG_FILTER_TYPE_DEFAULT);
      
      png_set_compression_level(png_ptr, Z_BEST_COMPRESSION);
      
      row_pointers = (png_bytepp)malloc(bmp->height * sizeof(png_bytep));
      

      for (i=0; i < bmp->height; i++) {
            row_pointers[i]=onerow;
            onerow += (bmp->width) * 
                  (ctype == PNG_COLOR_TYPE_PALETTE ? 1 : bmp->bpp);
      }
      
      png_set_rows(png_ptr, info_ptr, row_pointers);
      
      desc.buf = *outb;
      desc.size = *outl;
      desc.x.pos=0;
      png_set_write_fn(png_ptr, (void*)&desc, png_to_mem,
        png_flush_mem);

      png_write_png(png_ptr, info_ptr,PNG_TRANSFORM_PACKING, NULL);

      *outl = desc.x.pos;
      
      return IMG_RET_OK;
}

static int gif_mem_input(GifFileType *GifFile, GifByteType* data, int bytes)
{
      IODesc *desc = (IODesc*) GifFile->UserData;
      
      int copied;
      
      if(bytes + desc->x.pos >= desc->size) copied = desc->size - desc->x.pos;
      else copied=bytes;
      
      
      memcpy(data,desc->buf + desc->x.pos,copied);

      desc->x.pos+=copied;

      return copied;
}

/**
 * Get transparency color from graphic extension block
 *
 * Return: transparency color or -1
 */
static int getTransparentColor(GifFileType * file)
{
  int i;
  ExtensionBlock * ext = file->SavedImages[0].ExtensionBlocks;
 
  for (i=0; i < file->SavedImages[0].ExtensionBlockCount; i++, ext++) {
  
    if (ext->Function == GRAPHICS_EXT_FUNC_CODE) {
      if (ext->Bytes[0] & 1)/* there is a transparent color */
        return (unsigned char) ext->Bytes[3];/* here it is */
    }
  }

  return -1;
}


int gif2bitmap(char *inbuf, int insize, raw_bitmap **out, long long int max_raw_size)
{
      GifFileType *GifFile;
      SavedImage* LastImg = NULL;
      GifImageDesc *LastImgDesc = NULL;
      ColorMapObject *Colors;
      GifColorType *Pixel;
      IODesc desc;
      int i, j, k, c, offset, transColor;
      unsigned char* BufferP, *Raster;
      const int    InterlacedOffset[] = { 0, 4, 2, 1 }; /* The way Interlaced image should. */
      const int    InterlacedJumps[] = { 8, 8, 4, 2 };    /* be read - offsets and jumps... */
      raw_bitmap *bmp;
      unsigned char *MyColorMap = NULL;
      int MyColorCount = -1;
      long long int raw_size;

      desc.buf=inbuf;
      desc.size=insize;
      desc.x.pos=0;

      if ((GifFile = DGifOpen((void*)&desc, &gif_mem_input)) == NULL) 
            return( IMG_RET_ERR_UNKNOWN + IMG_RET_FLG_WHILE_DECOMP);//more possible reasons

      bmp = new_raw_bitmap();
      *out = bmp;
      bmp->width=GifFile->SWidth;
      bmp->height=GifFile->SHeight;

      /* MaxUncompressedImageRatio checking */
      /* this is an estimation of the bitmap size used in memory,
         it assumes 8 bits per pixel.
         there's no transparency information at this point. */
      raw_size = bmp->width * bmp->height;
      if ((max_raw_size > 0) && (raw_size > max_raw_size))
            return (IMG_RET_TOO_EXPANSIVE);

      if(DGifSlurp(GifFile)!=GIF_OK)
      {
                  fprintf(stderr,"GIF reading failed\n");
                  return(IMG_RET_ERR_BAD_DATA_FORMAT + IMG_RET_FLG_WHILE_DECOMP);
      }

      transColor = getTransparentColor(GifFile);

      //We're interested in last frame. Searching for last complete redraw
      //of image.
      i = GifFile->ImageCount - 1;
      if(i > 0 && !AllowLookCh) return IMG_RET_ERR_POSSIBLE_LOOK_CHANGE + IMG_RET_FLG_WHILE_DECOMP;
      for(; i>=0; i--)
            if(GifFile->SavedImages[i].ImageDesc.Left == 0 &&
                  GifFile->SavedImages[i].ImageDesc.Top == 0 &&
                  GifFile->SavedImages[i].ImageDesc.Width == bmp->width &&
                  GifFile->SavedImages[i].ImageDesc.Height == bmp->height
                  )
                        LastImg = GifFile->SavedImages + i;

      if (LastImg != NULL) {
            
            Raster = (unsigned char*)malloc(bmp->width*bmp->height);
            memcpy(Raster, LastImg->RasterBits, bmp->width*bmp->height);
            
            LastImgDesc = &LastImg->ImageDesc;
      
            Colors = (LastImgDesc->ColorMap
                  ? LastImgDesc->ColorMap
                  : GifFile->SColorMap);

      }else{

             if(!AllowLookCh) return IMG_RET_ERR_POSSIBLE_LOOK_CHANGE + IMG_RET_FLG_WHILE_DECOMP;
            //no complete image found - Make overlay of all images
            Raster = (unsigned char*)malloc(bmp->width*bmp->height);
            
            for(i=0;i<bmp->height;i++)
                  for(j=0;j<bmp->width;j++)
                        Raster[i*bmp->width + j] = GifFile->SBackGroundColor;
            
            for(i=0;i<bmp->height;i++)
                  for(j=0;j<bmp->width;j++)
                        for(k = 0;k < GifFile->ImageCount; k++){
                              int left = GifFile->SavedImages[k].ImageDesc.Left;
                              int right = left + GifFile->SavedImages[k].ImageDesc.Width;
                              int top = GifFile->SavedImages[i].ImageDesc.Top;
                              int bottom = top + GifFile->SavedImages[i].ImageDesc.Height == bmp->height;

                              if(i >= top && i <= bottom && j >= left && j <= right){
                                    Raster[(i - top)*bmp->width + (j-left)] =
                                          GifFile->SavedImages[k].RasterBits[(i - top)*(left-right) + (j-left)];
                                    break;
                              }
                              
                        }
            Colors = GifFile->SColorMap;
      }
      
      //Often there are unused colors in the palette.
      MyColorMap = (unsigned char*)malloc(Colors->ColorCount);
      for(i=0; i< Colors->ColorCount;i++)
            MyColorMap[i] = 0;
      
      MyColorCount = 0;
      for(i=0; i< bmp->width*bmp->height;i++){
            if(!MyColorMap[Raster[i]]){
                  MyColorCount++; 
                  //We're numbering the colors here from 1 upwards. But in 
                  //the final raster they'd be from 0.
                  MyColorMap[Raster[i]] = MyColorCount;
                  if((255 == MyColorCount) || (MyColorCount == Colors->ColorCount)){
                        free(MyColorMap);//we're using full palette
                        MyColorMap = NULL;
                        MyColorCount = Colors->ColorCount;
                        break;
                  }
            }
      }
      
      bmp->bpp=1;
      for(i = 0; i < Colors->ColorCount; i++)
      {
            if((i != transColor) && (!MyColorMap || MyColorMap[i]) &&
                  (( Colors->Colors[i].Red !=  Colors->Colors[i].Green ) ||
                  ( Colors->Colors[i].Green != Colors->Colors[i].Blue)))
            {
                  bmp->bpp=3;
                  break;
            }
      }
      if (MyColorMap && !MyColorMap[transColor])
                  transColor = -1;

      if (transColor >= 0 ){
            if(!AllowLookCh) return IMG_RET_ERR_POSSIBLE_LOOK_CHANGE + IMG_RET_FLG_WHILE_DECOMP;
            bmp->bpp++; //add alpha channel
      }
      
      assert(bmp->bpp > 0 && bmp->bpp <= 4);
      
      bmp->pal_entries = MyColorCount;
      bmp->palette = (unsigned char*)malloc(bmp->pal_entries*bmp->bpp);
      
      BufferP = bmp->palette;
      for(i = 0; i < Colors->ColorCount; i++){
            if(MyColorMap) {
                  if(!MyColorMap[i]) continue;
                  BufferP = bmp->palette + bmp->bpp*(MyColorMap[i] - 1);
            }
            Pixel = Colors->Colors + i;
            for(k=0;k<bmp->bpp;k++)
                  switch(k){
                        case 0: *BufferP++ = Pixel->Red;
                              break;
                        case 1: if( bmp->bpp > 2)
                                    *BufferP++ = Pixel->Green;
                              else 
                                    *BufferP++ = 
                                    ( i == transColor ?
                                    0 : 255);

                              break;
                        case 2: *BufferP++ = Pixel->Blue;
                              break;
                        case 3: *BufferP++ = 
                              ( i == transColor ?
                              0 : 255);
                              break;
                  }

      }
      
      
      if(GifFile->Image.Interlace){
            bmp->raster=(unsigned char*)malloc(bmp->width * bmp->height);
            BufferP=bmp->raster;
            offset = -1;
                  
          for (c = 0; c < 4; c++)
            for (i = InterlacedOffset[c]; i < bmp->height;
                               i += InterlacedJumps[c]){
                  BufferP = bmp->raster + i*bmp->width;
                  for(j=0; j < bmp->width; j++){
                        offset++;
                        if(MyColorMap)
                              *BufferP++ = MyColorMap[Raster[offset]] - 1;
                        else
                              *BufferP++ = Raster[offset];
                  }
            }
          free(Raster);

      }else{
            if(MyColorMap) for(i =0; i< bmp->width*bmp->height; i++){
                  Raster[i]=MyColorMap[Raster[i]] - 1;
            }
            
            bmp->raster = Raster;
      }
      bmp->pal_bpp = bmp->bpp;
      
      DGifCloseFile(GifFile);
      return IMG_RET_OK;
}

IODesc * get_src (IODesc* ptr)
{
      static IODesc *myptr;

      if (ptr != NULL) myptr = ptr;
      return myptr;
}

static void jpeg_src_noop(j_decompress_ptr dinfo){}

static void jpeg_src_init(j_decompress_ptr dinfo)
{
      IODesc * desc = get_src(NULL);

      dinfo-> src ->next_input_byte = desc->buf;
      dinfo-> src -> bytes_in_buffer = desc->size;
}

static boolean jpeg_src_fill_input(j_decompress_ptr dinfo)
{
      return FALSE;
}

static void jpeg_src_skip_input (j_decompress_ptr dinfo, long num_bytes)
{
      IODesc * desc = get_src(NULL);

      if ((char*)(dinfo->src->next_input_byte + num_bytes) > desc->buf + desc->size || 
            (char*)dinfo->src->next_input_byte == &desc->x.jpeg_unexpected_end )
            { //silly behavior - recommended by jpeglib docs
                  dinfo->src->next_input_byte = &desc->x.jpeg_unexpected_end;
                  dinfo->src->bytes_in_buffer = 1; 
            }
            else
            {
                  dinfo->src->next_input_byte += num_bytes;
                  dinfo->src->bytes_in_buffer -= num_bytes;
            }
}

jmp_buf errjmp;

static void jpeg_error_exit (j_common_ptr cinfo)
{
      longjmp(errjmp,1);
}

static void jpeg_emit_message (j_common_ptr cinfo, int msg_level)
{}



int jpg2bitmap(char *inbuf, int insize, raw_bitmap **out, long long int max_raw_size)
{
            
        struct jpeg_decompress_struct dinfo;
      char *BufferP;
        struct jpeg_error_mgr jerr;
      struct jpeg_source_mgr jsrc;
      IODesc desc;
      int imgsize,row_stride;
      raw_bitmap *bmp;
      long long int raw_size;

      desc.buf = inbuf;
      desc.size = insize;
      desc.x.jpeg_unexpected_end = JPEG_EOI;

      get_src(&desc);

      dinfo.err = jpeg_std_error(&jerr);
      dinfo.err->error_exit = jpeg_error_exit;
      dinfo.err->emit_message = jpeg_emit_message;
      
        jpeg_create_decompress(&dinfo);

      jsrc.init_source = jpeg_src_init;
      jsrc.term_source = jpeg_src_noop; 
      jsrc.fill_input_buffer = jpeg_src_fill_input;
      jsrc.skip_input_data = jpeg_src_skip_input;
      dinfo.src = &jsrc;
      
      if (setjmp(errjmp))
                  return IMG_RET_ERR_UNKNOWN + IMG_RET_FLG_WHILE_DECOMP;

      bmp = new_raw_bitmap();
      *out = bmp;
      jpeg_read_header(&dinfo, TRUE);
      jpeg_start_decompress(&dinfo);
      
      imgsize=dinfo.output_width*dinfo.output_height*dinfo.output_components;
      row_stride=dinfo.output_width*dinfo.output_components;
      
      bmp->width=dinfo.output_width;
      bmp->height=dinfo.output_height;
      bmp->bpp=dinfo.output_components;

      /* MaxUncompressedImageRatio checking */
      /* this is not the real raw_size (libjpeg may downscale certain
         components internally) but, instead, the bitmap size to be
         allocated in memory */
      raw_size = bmp->width * bmp->height * bmp->bpp;
      if ((max_raw_size > 0) && (raw_size > max_raw_size))
            return (IMG_RET_TOO_EXPANSIVE);

      bmp->bitmap = (unsigned char*)malloc (imgsize);
      BufferP=bmp->bitmap;
      while (dinfo.output_scanline < dinfo.output_height)
      {
                  if (jpeg_read_scanlines(&dinfo,(JSAMPARRAY)(&BufferP),1) == 0)
                        break;
                  BufferP+=row_stride;
      }
      
      jpeg_finish_decompress(&dinfo);
      jpeg_destroy_decompress(&dinfo);
      return IMG_RET_OK;
}

static void jpeg_dest_init(j_compress_ptr cinfo)
{
      outbuf = cinfo->dest->next_output_byte = malloc(outlen);
      cinfo->dest->free_in_buffer=outlen;
}

static boolean jpeg_dest_empty_output_buffer()
{
            return FALSE; //output file larger than original..
}

static void jpeg_dest_term(j_compress_ptr cinfo)
{
            outlen = outlen - cinfo->dest->free_in_buffer;
}

int bitmap2jpg(raw_bitmap  * bmp, int quality, char ** outb, int * outl)
{
      struct jpeg_compress_struct cinfo;
        struct jpeg_error_mgr jerr;
      struct jpeg_destination_mgr memdest;
      int row_stride = bmp->width*bmp->bpp;
      int i,j;
      unsigned char *BufferP, *noA;

      cinfo.err = jpeg_std_error(&jerr);
      cinfo.err->error_exit = jpeg_error_exit;
      cinfo.err->emit_message = jpeg_emit_message;


      //translate i -> error code ?
      if((i = setjmp(errjmp)))
            return IMG_RET_ERR_UNKNOWN + IMG_RET_FLG_WHILE_COMPRESS;

      jpeg_create_compress (&cinfo);
      
      memdest.init_destination = jpeg_dest_init;
      memdest.empty_output_buffer = jpeg_dest_empty_output_buffer;
      memdest.term_destination = jpeg_dest_term;
      cinfo.dest = &memdest;

      cinfo.image_width = bmp->width;     /* image width and height, in pixels */
      cinfo.image_height = bmp->height;

      if(bmp->bpp <= 2){
            cinfo.input_components = 1;
            cinfo.in_color_space = JCS_GRAYSCALE;
      }else{
            cinfo.input_components = 3;   
            cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
      }
            
      jpeg_set_defaults(&cinfo);

      if(quality > 100){
            quality -=128;
            cinfo.smoothing_factor = 10;
      }

      cinfo.dct_method = JDCT_FLOAT;      
      cinfo.optimize_coding = TRUE;
      jpeg_set_quality(&cinfo, quality, FALSE );

      jpeg_start_compress(&cinfo, TRUE);
      BufferP = bmp->bitmap;

      if(bmp->bpp != cinfo.input_components)
            noA = (unsigned char *)malloc(bmp->width * cinfo.input_components);
      
      for(i=0; (i == cinfo.next_scanline) && (cinfo.next_scanline < cinfo.image_height); i++) {
            //remove alpha channel if any
            if(bmp->bpp != cinfo.input_components){
                  unsigned char * p = noA;
                  for(j=0;j < bmp->width * bmp->bpp;j++)
                        if(bmp->bpp - 1 != (j % bmp->bpp)){
                              *p = BufferP[j];
                              p++;
                        }
            }else noA = BufferP;
            
            jpeg_write_scanlines (&cinfo,(JSAMPARRAY)(&noA),1);
            BufferP += row_stride;
      }
            
      if (i < cinfo.image_height) {
            jpeg_abort_compress(&cinfo);
            outlen = -1;
            free(outbuf);
            return IMG_RET_ERR_OUT_OF_MEM + IMG_RET_FLG_WHILE_COMPRESS;
      }

      jpeg_finish_compress(&cinfo);
      jpeg_destroy_compress(&cinfo);
      *outb = outbuf;
      *outl = outlen;
      return IMG_RET_OK;
}

//what about gamma?
int rgb2gray(raw_bitmap * bmp) {
      int i, Y, size = bmp->height * bmp->width;
      
      if(bmp->bpp != 3 || !bmp->bitmap) return IMG_RET_FLG_WHILE_TRANSFORM + IMG_RET_ERR_BAD_DATA_FORMAT;

      bmp->pal_bpp = bmp->bpp;
      
      for(i=0; i < size;i++){
      //approx. formula from libpng
      Y = (6969 * bmp->bitmap[3*i] + 
                  23434 * bmp->bitmap[3*i+1] + 
                  2365 * bmp->bitmap[3*i+2] )/32768;
            if(Y > 255)
                  bmp->bitmap[i]=255;
            else
                  bmp->bitmap[i]=Y;
      }
      bmp->bpp = 1;
      return IMG_RET_OK;
}

static int depalettize(raw_bitmap * bmp){
      
      int i, j, size = bmp->height * bmp->width;
      unsigned char *Buffer, *BufferP;
      
      if(bmp->pal_entries <= 0) return IMG_RET_FLG_WHILE_TRANSFORM + IMG_RET_ERR_BAD_DATA_FORMAT;

      Buffer = BufferP = (unsigned char*)malloc(size*bmp->pal_bpp);
      
      for(i=0;i<size;i++)
            for(j=0;j<bmp->pal_bpp;j++)
                  *BufferP++ = bmp->palette[bmp->raster[i]*bmp->pal_bpp + j];
      
      bmp->bitmap = Buffer;
      
      return IMG_RET_OK;      
}

#ifdef JP2K

/*
 * R = Y + (1.4075 * (V - 128));
 * G = Y - (0.3455 * (U - 128) - (0.7169 * (V - 128));
 * B = Y + (1.7790 * (U - 128);
 *
 * Y = 0.299*R + 0.587*G + 0.114*B
 * U = -0.169*R - 0.331*G + 0.500*B + 128.0
 * V = 0.500*R - 0.419*G - 0.081*B + 128.0
 */                          

/* convert image from RGB/RGBA to YUV/YUVA */
// TODO: optimize this
int rgb2yuv (raw_bitmap * bmp) {
      float srcR, srcG, srcB;
      float dstY, dstU, dstV;
      int imgsize;
      int step = bmp->bpp;
      int rpos;
      unsigned char *read_base, *write_base;

      imgsize = bmp->width * bmp->height * bmp->bpp;
      
      /* allocate YUV bitmap buffer if needed */
      if (bmp->bitmap_yuv == NULL)
            bmp->bitmap_yuv = (unsigned char *) malloc (imgsize);

      read_base = bmp->bitmap;
      write_base = bmp->bitmap_yuv;

      for (rpos = 0; rpos < imgsize; rpos += step) {
            srcR = *(read_base + rpos);
            srcG = *(read_base + rpos + 1);
            srcB = *(read_base + rpos + 2);
            
            dstY = (0.299 * srcR) + (0.587 * srcG) + (0.114 * srcB);
            dstU = (-0.169 * srcR) - (0.331 * srcG) + (0.500 * srcB) + 128;
            dstV = (0.500 * srcR) - (0.419 * srcG) - (0.081 * srcB) + 128;

            if (dstU > 255)
                  dstU = 255;
            if (dstV > 255)
                  dstV = 255;
            if (dstU < 0)
                  dstU = 0;
            if (dstV < 0)
                  dstV = 0;
            
            *(write_base + rpos) = dstY;
            *(write_base + rpos + 1) = dstU;
            *(write_base + rpos + 2) = dstV;
      }

      return IMG_RET_OK;
}

/* convert image from YUV/YUVA to RGB/RGBA */
// TODO: optimize this
int yuv2rgb (raw_bitmap * bmp) {
      float srcY, srcU, srcV;
      float dstR, dstG, dstB;
      int imgsize;
      int step = bmp->bpp;
      int rpos;
      unsigned char *read_base, *write_base;

      imgsize = bmp->width * bmp->height * bmp->bpp;
      
      /* allocate RGB bitmap buffer if needed */
      if (bmp->bitmap_yuv == NULL)
            bmp->bitmap = (unsigned char *) malloc (imgsize);
      
      read_base = bmp->bitmap_yuv;
      write_base = bmp->bitmap;

      for (rpos = 0; rpos < imgsize; rpos += step) {

            srcY = *(read_base + rpos);
            srcU = *(read_base + rpos + 1);
            srcV = *(read_base + rpos + 2);
            
            dstR = srcY + (1.4075 * (srcV - 128));
            dstG = srcY - (0.3455 * (srcU - 128)) - (0.7169 * (srcV - 128));
            dstB = srcY + (1.7790 * (srcU - 128));

            if (dstR > 255)
                  dstR = 255;
            if (dstG > 255)
                  dstG = 255;
            if (dstB > 255)
                  dstB = 255;
            if (dstR < 0)
                  dstR = 0;
            if (dstG < 0)
                  dstG = 0;
            if (dstB < 0)
                  dstB = 0;

            *(write_base + rpos) = dstR;
            *(write_base + rpos + 1) = dstG;
            *(write_base + rpos + 2) = dstB;
      }

      return IMG_RET_OK;
}

// needed to satisfy libjpeg (see compress_to_jpg())
static void jpeg_dest_init_void (j_compress_ptr cinfo)
{
}

// needed to satisfy libjpeg (see compress_to_jpg())
static void jpeg_dest_term_void (j_compress_ptr cinfo)
{
}

/* compress a bitmap to jpeg.
 * we CANNOT use bitmap2jpg() because that function is promiscually making reference to global vars and who knows what else.
 * jpegout returns a pointer to a buffer containing a pointer to jpeg data (should be free'ed manually later)
 * needs: maxoutl = max size the compressed data may be
 * provides: *outl = size of the compressed data
 * returns: 0 if ok, !=0 error */
int compress_to_jpg (raw_bitmap *bmp, int quality, int maxoutl, int *outl, unsigned char **jpegout)
{
      unsigned char *outb;    // compressed data will be stored here
      struct jpeg_compress_struct cinfo;
      struct jpeg_error_mgr jerr;
      unsigned char *BufferP, *noA;
      int i, j;
      struct jpeg_destination_mgr dest;
      int outbufsize;
      int row_stride;             // physical row width in image buffer

      if (bmp->bitmap == NULL)
            depalettize (bmp);

      cinfo.err = jpeg_std_error(&jerr);

      jpeg_create_compress(&cinfo);

      // jpeg does not support alpha, so it doesn't count in this case (if present)
      if(bmp->bpp <= 2){
            cinfo.input_components = 1;   // Y only
            cinfo.in_color_space = JCS_GRAYSCALE;
      }else{
            cinfo.input_components = 3;   // R, G, B
            cinfo.in_color_space = JCS_RGB;
      }

      // we want output to memory, set things accordingly
      outbufsize = maxoutl;   // max compressed size and buffer size are limited by maxoutl
      outb = malloc (outbufsize);
      cinfo.dest = &dest;
      dest.next_output_byte = outb;
      dest.free_in_buffer = outbufsize;
      //
      dest.init_destination = jpeg_dest_init_void;
      dest.empty_output_buffer = jpeg_dest_empty_output_buffer;
      dest.term_destination = jpeg_dest_term_void;

      // dest.data_precision = XX; // hmm.. this may be interesting later
      cinfo.image_width = bmp->width;
      cinfo.image_height = bmp->height;

      // this command is picky on its position, related to other references
      // of cinfo (some must come before, other ones after this)
      jpeg_set_defaults (&cinfo);

      cinfo.dct_method = JDCT_FLOAT;
      cinfo.optimize_coding = TRUE;
      BufferP = bmp->bitmap;
      row_stride = cinfo.image_width * cinfo.input_components;
      jpeg_set_quality (&cinfo, quality, FALSE);
      jpeg_start_compress (&cinfo, TRUE);

      if (bmp->bpp != cinfo.input_components)
            noA = (unsigned char *) malloc (bmp->width * cinfo.input_components);

      for (i=0; (i == cinfo.next_scanline) && (cinfo.next_scanline < cinfo.image_height); i++) {
            //remove alpha channel if any
            if(bmp->bpp != cinfo.input_components){
                  unsigned char * p = noA;
                  for (j=0;j < bmp->width * bmp->bpp;j++)
                        if(bmp->bpp - 1 != (j % bmp->bpp)){
                              *p = BufferP [j];
                              p++;
                        }
            } else noA = BufferP;
            jpeg_write_scanlines (&cinfo, (JSAMPARRAY) (&noA), 1);
            BufferP += row_stride;
      }

      if (bmp->bpp != cinfo.input_components)
            free (noA);
      
      if (i < cinfo.image_height) {
            jpeg_abort_compress (&cinfo);
            outlen = -1;
            free (outb);
            return (IMG_RET_ERR_OUT_OF_MEM + IMG_RET_FLG_WHILE_COMPRESS);
      }

      jpeg_finish_compress(&cinfo);
      
      *outl = outbufsize - cinfo.dest->free_in_buffer;
      *jpegout = outb;
      
      jpeg_destroy_compress(&cinfo);

      return (IMG_RET_OK);
}

/* this calculates the _real_ jp2 rawsize (differently from the
   routine embbeded in estimate_jp2rate_from_quality()) */
int calculate_jp2_rawsize (raw_bitmap *bmp, const t_color_space target_clrspc, const int *bitlenYA, const int *bitlenRGBA, const int*bitlenYUVA, const int *csamplingYA, const int *csamplingRGBA, const int *csamplingYUVA, int discard_alpha)
{
      int rawsize = 0;
      int bpp;    // bpp minus alpha channel
      const int *cbitlen;     // 0:first component, 1:second component.. and so on
      const int *ucsampling; // 0:xpos0, 1:ypos0, 2:xstep0, 3:ystep0... xpos1, ypos1.. and so on
      const int *this_ucsampling;
      int cmptno;
      int cwidth, cheight;

      bpp = bmp->bpp;
      if (discard_alpha) {
            if (bmp->bpp == 4)
                  bpp = 3;
            else if (bmp->bpp == 2)
                  bpp = 1;
      }

      /* see which colorspace we'll really use and set things accordingly */
      if ((target_clrspc == CENC_YUV) && (bmp->bpp >= 3)) {
            cbitlen = bitlenYUVA;
            ucsampling = csamplingYUVA;
      } else {
            /* the other option is RGBA/YA (RGBA when color, YA when B&W) */
            if (bmp->bpp >= 3) {
                  cbitlen = bitlenRGBA;
                  ucsampling = csamplingRGBA;
            } else {
                  cbitlen = bitlenYA;
                  ucsampling = csamplingYA;
            }
      }

      /* calculate rawsize of the picture, taking into account
       * bit length and (under)sampling of each component while compressing to jp2 */
      for (cmptno = 0; cmptno < bpp; cmptno++) {
            this_ucsampling = ucsampling + (4 * cmptno);
            cwidth = return_downscaled_array_len (bmp->width, this_ucsampling [0], this_ucsampling [2]);    // xpos, xtep
            cheight = return_downscaled_array_len (bmp->height, this_ucsampling [1], this_ucsampling [3]);  // xpos, xtep
            rawsize += (cwidth * cheight * cbitlen [cmptno]) / 8;
      }

      /* preventive measure (no problems so far, but let's be safe):
         avoid downwards rounding of rawsize in cases
         one or more of the components is less than 8 bits */
      if ((cwidth > 0) && (cheight > 0))
            rawsize += bmp->bpp;

      return (rawsize);
}

/* returns the necessary jp2-rate to have results
 * roughly similar to jpeg-type quality.
 * requires pre-malloc'ed outbuf array sized: (bmp->width * bmp->height * bmp->bpp)
 * limitations: the alpha channel is not taken into account */
/* returns < 0 if error */
float estimate_jp2rate_from_quality (raw_bitmap *bmp, int quality, const t_color_space target_clrspc, const int *bitlenYA, const int *bitlenRGBA, const int*bitlenYUVA, const int *csamplingYA, const int *csamplingRGBA, const int *csamplingYUVA)
{
      float rawsize;
      float rate;
      int outl;
      int maxoutl;
      unsigned char *jpegout;

      /* Calculate rawsize of the picture, taking into account
       * bit length and (under)sampling of each component while compressing to jp2.
       * Note: we cannot take alpha into account since jpeg does not support that.
       * that means innacurate compression rate when dealing with pictures containing such component. */
      rawsize = calculate_jp2_rawsize (bmp, target_clrspc, bitlenYA, bitlenRGBA, bitlenYUVA, csamplingYA, csamplingRGBA, csamplingYUVA, 1);

      maxoutl = rawsize + 1000;     // avoids a too-small buffer if the raw picture is minuscle (libjpeg recommends ~600 bytes for headers)
      if (compress_to_jpg (bmp, quality, maxoutl, &outl, &jpegout) != IMG_RET_OK) {
            logputs ("Error: unable to compress to jpeg (estimate_jp2rate_from_quality)");
            return (-1);
      }
      free (jpegout);   // the data itself is not needed in this routine, we just want to know its size
            
      rate = (outl + 200) / rawsize;      // estimate the JP2k rate

      return (rate);
}

static int bitmap2jp2 (raw_bitmap *bmp, float rate, char **outb, int *outl, const t_color_space target_clrspc, const int *bitlenYA, const int *bitlenRGBA, const int *bitlenYUVA, const int *csamplingYA, const int *csamplingRGBA, const int *csamplingYUVA)
{
      jas_image_t *jimg;
      jas_matrix_t *jmatrix [4];
      jas_image_cmptparm_t cmptparms [4];
      jas_image_cmptparm_t *cmptparm;
      int pos0 = -1, pos1 = -1, pos2 = -1, pos3 = -1;     // holds jp2-component number for each -- FIXME: this is pointless, we may use constants in this routine instead.
      // uint_fast16_t cmptno; // TODO: should i use this instead? check that later
      int x, y, cmptno;
      jas_stream_t *js;
      char mode[30], *cmode;
      int outfmt;
      unsigned char *pixel_base;
      const int *cbitlen;           // points to either bitlenYA/bitlenRGBA/bitlenYUVA
      int cshiftR [4];        // right shift for each component
      unsigned char * ubitmap;      // used bitmap while compressing, points to either bmp->bitmap or bmp->bitmap_yuv
      int csampling_effective [16]; /* usually a mirror of either bitlenYA/bitlenRGBA/bitlenYUVA, but it will change to proper values
                                 if original parameters are invalid _or_
                                 invalid for this specific image (image to small for the requested downsampling, for example) */
      int cwidth [4];               // component width, (may differ if each component have different sampling from each other)
      int cheight [4];        // analogous to cwidth
      const int *ucsampling;        // points to either bitlenYA/bitlenRGBA/bitlenYUVA

      if(jas_init()) return 5;

      if(bmp->pal_entries > 0) depalettize(bmp);
      
      /* see which colorspace we'll really use and set things accordingly */
      if ((target_clrspc == CENC_YUV) && (bmp->bpp >= 3)) {
            /* requested YUVA and it's a color bitmap */
            rgb2yuv (bmp);    // the default buffer is RGB(A), convert to YUV(A) -- this will allocate bmp->bitmap_yuv automatically, if needed
            ubitmap = bmp->bitmap_yuv;
            cbitlen = bitlenYUVA;
            ucsampling = csamplingYUVA;
      } else {
            /* the other option is RGBA/YA (RGBA when color, YA when B&W) */
            ubitmap = bmp->bitmap;
            if (bmp->bpp >= 3) {
                  cbitlen = bitlenRGBA;
                  ucsampling = csamplingRGBA;
            } else {
                  cbitlen = bitlenYA;
                  ucsampling = csamplingYA;
            }
      }

      // change the sampling rate for each component (scaling each component to a new (lower) resolution, in a way)
      jp2_downsample_image_components (ubitmap, bmp->width, bmp->height, bmp->bpp, ucsampling, csampling_effective, cwidth, cheight);

      // initialize components
      for (cmptno = 0, cmptparm = cmptparms; cmptno < bmp->bpp; ++cmptno, ++cmptparm) {
            cmptparm->tlx = csampling_effective [(cmptno * 4) + 0];     // 0 = max quality
            cmptparm->tly = csampling_effective [(cmptno * 4) + 1]; // 0 = max quality
            cmptparm->hstep = csampling_effective [(cmptno * 4) + 2]; // 1 = max quality
            cmptparm->vstep = csampling_effective [(cmptno * 4) + 3]; // 1 = max quality
            cmptparm->width = cwidth [cmptno];
            cmptparm->height = cheight [cmptno];
            cmptparm->prec = cbitlen [cmptno];
            cmptparm->sgnd = false; // we only generate unsigned components

            cshiftR [cmptno] = 8 - cbitlen [cmptno]; // calculate the resampling shift for this component
      }

      if ((jimg = jas_image_create (bmp->bpp, cmptparms, JAS_CLRSPC_UNKNOWN)) == NULL)
            return IMG_RET_ERR_UNKNOWN; // unable to create image

      // initialize image parameters
      // FIXME: this does not work, libjasper always 'guess' the width/height based on the components' dimensions
      //        unfortunately certain combinations of components' dimensions make libjasper output a slightly smaller picture
      /*
      jimg->tlx_ = 0;
      jimg->brx_ = bmp->width;
      jimg->tly_ = 0;
      jimg->bry_ = bmp->height;
      */
      
      // more components initialization
      switch (bmp->bpp) {
      case 1:
      case 2:
            jas_image_setclrspc (jimg, JAS_CLRSPC_SGRAY);
            jas_image_setcmpttype (jimg, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_GRAY_Y));
            pos0 = 0;

            if (bmp->bpp == 2) {
                  jas_image_setcmpttype (jimg, 1, JAS_IMAGE_CT_OPACITY);
                  pos1 = 1;
            }
            break;
      case 3:
      case 4:
            if (target_clrspc == CENC_YUV) {
                  /* YUV */
                  jas_image_setclrspc (jimg, JAS_CLRSPC_SYCBCR);
                  jas_image_setcmpttype (jimg, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_YCBCR_Y));
                  pos0 = 0;
                  jas_image_setcmpttype (jimg, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_YCBCR_CB));
                  pos1 = 1;
                  jas_image_setcmpttype (jimg, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_YCBCR_CR));
                  pos2 = 2;
            } else {
                  /* RGB */
                  jas_image_setclrspc (jimg, JAS_CLRSPC_SRGB);
                  jas_image_setcmpttype (jimg, 0, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_R));
                  pos0 = 0;
                  jas_image_setcmpttype (jimg, 1, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_G));
                  pos1 = 1;
                  jas_image_setcmpttype (jimg, 2, JAS_IMAGE_CT_COLOR(JAS_CLRSPC_CHANIND_RGB_B));
                  pos2 = 2;
            }
            
            if (bmp->bpp == 4) {
                  jas_image_setcmpttype (jimg, 3, JAS_IMAGE_CT_OPACITY);
                  pos3 = 3;
            }
            break;
      }

      // allocate intermediate line buffers
      // note: although we do not need parallel buffers for each component (see routines below), and theoretically
      //       we could use only one sized as the widest component,
      //       libjasper does not accept a line buffer with bigger width than the component's width
      //       (what will happen if we use component with different sampling steps, thus different width).
      for (cmptno = 0; cmptno < bmp->bpp; cmptno++) {
            if ((jmatrix [cmptno] = jas_matrix_create (1, cwidth [cmptno])) == NULL)
                  return IMG_RET_ERR_UNKNOWN; // memory allocation error
      }

      // transfer image to jimg, through intermediate buffer
      for (cmptno = 0; cmptno < bmp->bpp; cmptno++) {
            pixel_base = ubitmap + cmptno;
            
            for (y = 0; y < cheight [cmptno]; y++) {
                  // populate intermediate line buffer
                  for (x = 0; x < cwidth [cmptno]; x++) {
                        jas_matrix_setv (jmatrix [cmptno], x, *pixel_base >> cshiftR [cmptno]);
                        pixel_base += bmp->bpp;
                  }
            
                  // transfer line to jimg
                  if (jas_image_writecmpt (jimg, cmptno, 0, y, cwidth [cmptno], 1, jmatrix [cmptno]))
                        return IMG_RET_ERR_UNKNOWN; // unable to transfer line to jimg
            }
      }

      js = jas_stream_memopen (0, 0);
      if(js == NULL) return 0;
      
      if(rate > 0.9999 ){
            cmode= "int"; 
            rate = 1.0;
      }
      else cmode = "real";

      snprintf(mode, sizeof(mode), "mode=%s rate=%f", cmode, rate);
      outfmt = jas_image_strtofmt ("jp2");

      // encode intermediate buffer into jp2 data
      if (jas_image_encode (jimg, js, outfmt, mode)) {
            // unable to encode to jp2
            return 0; // is that an error?
      }

      jas_stream_flush(js);
      //May not be future compatible!
      jas_stream_memobj_t * jmem = (jas_stream_memobj_t *)js->obj_;
      *outl = jmem->len_;
      *outb = jmem->buf_;
      jmem->myalloc_ = 0; //don't delete buf_ on closing
      jas_stream_close(js);//close

      // TODO: archaic code already commented. check later if there's something useable from this, otherwise delete it.
      /*    
      outlen = jas_stream_tell(js);
      outbuf = malloc(outlen);
      jas_stream_rewind(js);
      jas_stream_read(js, outbuf, outlen);
      jas_stream_close(js);*/
      
      return IMG_RET_OK;
}

/* this routine decodes a JP2K file within what is supported by the bitmap2jp2() function */
int jp22bitmap(char *inbuf, int insize, raw_bitmap **out, long long int max_raw_size)
{
      jas_stream_t *js;
      jas_image_t *jimg;
      jas_matrix_t *jmatrix [4];
      int components, colorspace;
      int pos0 = -1, pos1 = -1, pos2 = -1, pos3 = -1; // posX where 'X' is the position in the internal buffer, holds the jp2-component number equivalent - its meanings depends on the color model (Y, RGBA, YUV etc)
      int imgsize;
      int x, y, i, cmptno;
      raw_bitmap *bmp;
      unsigned char *pixel_base;
      int cbitlen [4];        // (R, G, B, [alpha]) || (Y, U, V [alpha]) || (Y, [alpha])
      int cwidth [4];
      int cheight [4];
      int csampling [16];           // max_components * 4
      unsigned char * ubitmap;      // used bitmap while decompressing, points to either bmp->bitmap or bmp->bitmap_yuv
      long long int raw_size;

      if(jas_init()) return 5;

      js = jas_stream_memopen (inbuf, insize);
      if ((jimg = jas_image_decode (js, -1, 0)) == NULL) {
            logputs ("ERROR: JP2K - unable to decode data.");
            return IMG_RET_ERR_UNKNOWN;
      }

      bmp = new_raw_bitmap ();
      *out = bmp;

      /* get image information */
      components = jas_image_numcmpts (jimg);
      bmp->width = jas_image_width (jimg);
      bmp->height = jas_image_height (jimg);
      colorspace = jas_image_clrspc (jimg);

      // TODO: component signedness is not supported (assumes: unsigned)

      i = 0;
      while (i < components) {
            int comptype = jas_image_cmpttype (jimg, i);

            /* the following looks kludgy because
             * libjasper defines some things in a peculiar way, like
             * both JAS_CLRSPC_CHANIND_RGB_R and JAS_CLRSPC_CHANIND_GRAY_Y as 0 */ 

            switch (colorspace) {
            case JAS_CLRSPC_SGRAY:
                  switch (comptype) {
                  case JAS_CLRSPC_CHANIND_GRAY_Y:
                        pos0 = i;
                        break;
                  /* bug workaround: libjasper renumerates JAS_IMAGE_CT_OPACITY to 1 */
                  case 1:
                  case JAS_IMAGE_CT_OPACITY:
                        pos1 = i;
                        break;
                  default:
                        logprintf ("ERROR: JP2K - component type not supported: %x\n", comptype);
                        jas_stream_close (js);
                        return IMG_RET_ERR_NOT_IMPL_YET;
                        break;
                  }
                  break;
            case JAS_CLRSPC_SYCBCR:
                  switch (comptype) {
                  case JAS_CLRSPC_CHANIND_YCBCR_Y:
                        pos0 = i;
                        break;
                  case JAS_CLRSPC_CHANIND_YCBCR_CB:
                        pos1 = i;
                        break;
                  case JAS_CLRSPC_CHANIND_YCBCR_CR:
                        pos2 = i;
                        break;
                  /* bug workaround: libjasper renumerates JAS_IMAGE_CT_OPACITY to 3 */
                  case 3:
                  case JAS_IMAGE_CT_OPACITY:
                        pos3 = i;
                        break;
                  default:
                        logprintf ("ERROR: JP2K - component type not supported: %x\n", comptype);
                        jas_stream_close (js);
                        return IMG_RET_ERR_NOT_IMPL_YET;
                        break;
                  }
                  break;
            case JAS_CLRSPC_SRGB:
                  switch (comptype) {
                  case JAS_CLRSPC_CHANIND_RGB_R:
                        pos0 = i;
                        break;
                  case JAS_CLRSPC_CHANIND_RGB_G:
                        pos1 = i;
                        break;
                  case JAS_CLRSPC_CHANIND_RGB_B:
                        pos2 = i;
                        break;
                  /* bug workaround: libjasper renumerates JAS_IMAGE_CT_OPACITY to 3 */
                  case 3:
                  case JAS_IMAGE_CT_OPACITY:
                        pos3 = i;
                        break;
                  default:
                        logprintf ("ERROR: JP2K - component type not supported: %x\n", comptype);
                        jas_stream_close (js);
                        return IMG_RET_ERR_NOT_IMPL_YET;
                        break;
                  }
                  break;
            }

            cbitlen [i] = jas_image_cmptprec (jimg, i);
            cwidth [i] = jas_image_cmptwidth (jimg, i);
            cheight [i] = jas_image_cmptheight (jimg, i);
            csampling [(i * 4) + 0] = jas_image_cmpttlx(jimg, i);
            csampling [(i * 4) + 1] = jas_image_cmpttly(jimg, i);
            csampling [(i * 4) + 2] = jas_image_cmpthstep(jimg, i);
            csampling [(i * 4) + 3] = jas_image_cmptvstep(jimg, i);
                  
            i++;
      }
      
      // see what kind of bitmap we're dealing with
      switch (colorspace) {
      case JAS_CLRSPC_SGRAY:
            // checks alpha channel presence
            if (pos1 >= 0)
                  bmp->bpp = 2;
            else
                  bmp->bpp = 1;
            break;
      case JAS_CLRSPC_SYCBCR:
      case JAS_CLRSPC_SRGB:
            // checks alpha channel presence
            if (pos3 >= 0)
                  bmp->bpp = 4;
            else
                  bmp->bpp = 3;
            break;
      default:
            logprintf ("ERROR: JP2K - colorspace not supported: %x\n", colorspace);
            jas_stream_close (js);
            return IMG_RET_ERR_NOT_IMPL_YET;
            break;
      }
      // TODO: check if there are no components missing from what is expected from the colorspace

      /* MaxUncompressedImageRatio checking */
      /* this is not the real raw_size
         (it does not take into consideration bit length per component etc)
         but, instead, the bitmap size to be allocated in memory */
      raw_size = bmp->width * bmp->height * bmp->bpp;
      if ((max_raw_size > 0) && (raw_size > max_raw_size))
            return (IMG_RET_TOO_EXPANSIVE);

      // calculate bitmap size and allocate its buffer
      imgsize = bmp->width * bmp->height * bmp->bpp;
      bmp->bitmap = (unsigned char *) malloc (imgsize);
      // allocate YUV buffer too, if image comes in that format
      if (colorspace == JAS_CLRSPC_SYCBCR) {
            bmp->bitmap_yuv = (unsigned char *) malloc (imgsize);
            ubitmap = bmp->bitmap_yuv;
      } else {
            ubitmap = bmp->bitmap;
      }
      
      // allocate intermediate line buffers
      // note: although we do not need parallel buffers for each component (see routines below), and theoretically
      //       we could use only one sized as the widest component,
      //       libjasper does not accept a line buffer with bigger width than the component's width
      //       (what will happen if we use component with different sampling steps, thus different width).
      for (cmptno = 0; cmptno < bmp->bpp; cmptno++) {
            if ((jmatrix [cmptno] = jas_matrix_create (1, cwidth [cmptno])) == NULL)
                  return IMG_RET_ERR_UNKNOWN; // memory allocation error
      }

      // transfer image to jimg, through intermediate buffer
      for (cmptno = 0; cmptno < bmp->bpp; cmptno++) {
            pixel_base = ubitmap + cmptno;
            
            for (y = 0; y < cheight [cmptno]; y++) {
                  // transfer decompressed image line, to line buffer
                  jas_image_readcmpt (jimg, cmptno, 0, y, cwidth [cmptno], 1, jmatrix [cmptno]);

                  // transfer data from line buffer to final bitmap
                  for (x = 0; x < cwidth [cmptno]; x++) {
                        *pixel_base = UPSAMPLE_TO_U8BIT(jas_matrix_getv (jmatrix [cmptno], x), cbitlen [cmptno]);
                        pixel_base += bmp->bpp;
                  }
            }
      }

      // are any of the image components scaled-down? if so, rescale them to the intended display size.
      jp2_upsample_image_components (ubitmap, bmp->width, bmp->height, bmp->bpp, csampling, cwidth, cheight);
      
      /* if YUV, convert back to RGB */
      if (colorspace == JAS_CLRSPC_SYCBCR)
            yuv2rgb (bmp);

      jas_stream_close (js);
      return IMG_RET_OK;
}

#endif

Generated by  Doxygen 1.6.0   Back to index