File image.c of Package arachne

/*
image.c
Author: Hayden Walles
Date: 17 September 2007
A little image wrapper implementation.
*/

/*
    Copyright (C) 2007, 2008 Hayden Walles

    This file is part of Arachne.

    Arachne is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    Arachne is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <tiffio.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <setjmp.h>
#include <jpeglib.h>
#include "image.h"

#ifndef MIN
#define MIN(a,b) (((a)<(b)) ? (a): (b))
#endif

/*This is used to store an error string, but only for the JPEG library at the moment.*/
static char *LastJpegErrorString=NULL;

IMAGE *newImage(int width,int height,int *error){
  IMAGE *new;

  if(LastJpegErrorString!=NULL){
    free(LastJpegErrorString);
    LastJpegErrorString=NULL;
  }

  if((new=malloc(sizeof(IMAGE)+width*height*4))==NULL){
    if(error!=NULL)
      *error=IMAGE_E_MEMORY;
    return NULL;
  }

  new->width=width;
  new->height=height;
  new->pixels=((unsigned char *)new)+sizeof(IMAGE);
  
  return new;
}

void deleteImage(IMAGE *image){
  free(image);
}

void setPixel(IMAGE *image,int row, int col, int r, int g, int b){

  image->pixels[(row*image->width+col)*4]=r;
  image->pixels[(row*image->width+col)*4+1]=g;
  image->pixels[(row*image->width+col)*4+2]=b;

}




struct custom_error_mgr {
  struct jpeg_error_mgr lib;
  jmp_buf jump_buffer;
};

void customJpegExit(j_common_ptr cinfo){
  struct custom_error_mgr *cerr=(struct  custom_error_mgr *) cinfo->err;
  char *msg;

  if(LastJpegErrorString!=NULL){
    free(LastJpegErrorString);
    LastJpegErrorString=NULL;
  }

  msg=malloc(JMSG_LENGTH_MAX);
  if(msg!=NULL){
    (*cerr->lib.format_message)(cinfo,msg);
    LastJpegErrorString=msg;
  }

  longjmp(cerr->jump_buffer,1);
}



int saveJPEGImage(char *filename,IMAGE *image, int *error){
  struct jpeg_compress_struct cinfo;
  struct custom_error_mgr cerr;
  FILE *file;
  JSAMPLE *scanline;
  int i,j;

  /*NB If we knew that the input image was greyscale we could write a greyscale image here.  Something to consider later.*/
  
  file=fopen(filename,"wb");
  if(file==NULL){
    *error=IMAGE_E_FILE;
    return 0;
  }

  cinfo.err=jpeg_std_error((struct jpeg_error_mgr *)&cerr);
  cerr.lib.error_exit=&customJpegExit;
  if(setjmp(cerr.jump_buffer)!=0){
    if(scanline!=NULL)
      free(scanline);
    fclose(file);
    jpeg_abort_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    *error=IMAGE_E_DECODE;
    return 0;
  }

  jpeg_create_compress(&cinfo);

  jpeg_stdio_dest(&cinfo,file);

  cinfo.image_width=cinfo.image_width=imageWidth(image);
  cinfo.image_height=cinfo.image_height=imageHeight(image);
  cinfo.input_components=3;
  cinfo.in_color_space=JCS_RGB;
  jpeg_set_defaults(&cinfo);

  jpeg_start_compress(&cinfo,TRUE);

  scanline=malloc(sizeof(JSAMPLE)*3*imageWidth(image));
  if(scanline==NULL){
    //    deleteImage(image);
    jpeg_abort_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
    fclose(file);
    *error=IMAGE_E_MEMORY;
    return 0;
  }

  
  while(cinfo.next_scanline<cinfo.image_height){
    /*Transcode from 32 bit to 24bit.*/
#if 0
    if(cinfo.output_components==1){
      for(j=0;j<cinfo.image_width;j++) /*This feels quite slow.*/
	setPixel(image,i,j,scanline[j],scanline[j],scanline[j]);
    }
    else {
#endif
      /*Assume rgb.*/
      for(j=0;j<cinfo.image_width;j++){ /*This feels quite slow.*/
	scanline[j*3]=getRed(image,cinfo.next_scanline,j);
	scanline[j*3+1]=getGreen(image,cinfo.next_scanline,j);
	scanline[j*3+2]=getBlue(image,cinfo.next_scanline,j);
      }
#if 0
    }
#endif
    jpeg_write_scanlines(&cinfo,&scanline,1);
    
    
  }
    

  
  jpeg_finish_compress(&cinfo);

  jpeg_destroy_compress(&cinfo);

  fclose(file);
  free(scanline);

  *error=IMAGE_E_OK;
  return 1;

}



IMAGE *loadJPEGImage(char *filename, int *error){
  IMAGE *image=NULL;
  struct jpeg_decompress_struct cinfo;
  struct custom_error_mgr cerr;
  FILE *file;
  JSAMPLE *scanline=NULL;
  int i,j;
  


  file=fopen(filename,"rb");
  if(file==NULL){
    *error=IMAGE_E_FILE;
    return NULL;
  }

  cinfo.err=jpeg_std_error((struct jpeg_error_mgr *)&cerr);
  cerr.lib.error_exit=&customJpegExit;
  if(setjmp(cerr.jump_buffer)!=0){
    if(image!=NULL)
      deleteImage(image);
    if(scanline!=NULL)
      free(scanline);
    fclose(file);
    jpeg_abort_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    *error=IMAGE_E_DECODE;
    return NULL;
  }

  jpeg_create_decompress(&cinfo);

  jpeg_stdio_src(&cinfo,file);

  jpeg_read_header(&cinfo,TRUE);

  image=newImage(cinfo.image_width,cinfo.image_height,error);
  if(image==NULL){
    jpeg_abort_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    fclose(file);
    return NULL;
  }
  

  jpeg_start_decompress(&cinfo);

  
  scanline=malloc(sizeof(JSAMPLE)*cinfo.image_width*cinfo.output_components);
  if(scanline==NULL){
    deleteImage(image);
    jpeg_abort_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    fclose(file);
    *error=IMAGE_E_MEMORY;
    return NULL;
  }

  i=0;
  do {
    if(jpeg_read_scanlines(&cinfo,&scanline,1)>0){
      /*Transcode from 24 bit to 32bit.*/
      if(cinfo.output_components==1){
	for(j=0;j<cinfo.image_width;j++) /*This feels quite slow.*/
	  setPixel(image,i,j,scanline[j],scanline[j],scanline[j]);
      }
      else {
	/*Assume rgb.*/
	for(j=0;j<cinfo.image_width;j++) /*This feels quite slow.*/
	  setPixel(image,i,j,scanline[j*3],scanline[j*3+1],scanline[j*3+2]);
      }
      i++;
    }
    
  } while(i<cinfo.output_height);


  
  jpeg_finish_decompress(&cinfo);

  jpeg_destroy_decompress(&cinfo);

  fclose(file);
  free(scanline);


  *error=IMAGE_E_OK;

  return image;

}


IMAGE *loadTIFFImage(char *filename, int *error){
  IMAGE *image;
  TIFF *tif;
  int width,height;
  int i,j,k,x;
  int z;
  FILE *temp;

  /*First try to open the file with the TIFF library*/
  tif=TIFFOpen(filename,"r");
  if(tif==NULL){
    if(error!=NULL)
      *error=IMAGE_E_FILE;
    return NULL;
  }

  /*Next, obtain the width and height*/
  TIFFGetField(tif,TIFFTAG_IMAGEWIDTH,&width);
  TIFFGetField(tif,TIFFTAG_IMAGELENGTH,&height);



  /*Grab an IMAGE ADT to hold the image.  Now I'm going to be a bit sneaky here: the TIFF library will read in a pixel map with each pixel represented by a single 32 bit number, divided into byte-size fields.  I will then want to unshuffle these values to create three planes of consecutive bytes.  This is a bit messy memory-wise, but it lets the ADTs work happily, and I don't feel like delving too far into the TIFF library at the moment. Lets go.*/

  image=newImage(width,height,error); /*4 bands, R, G, B and alpha*/ 
  if(image==NULL){
    TIFFClose(tif);
    return NULL;
  }


  /*And if all that went OK, we can try to load the image.  The TIFF library has a function to do this in one go.  This function might be only available in newer versions, though.  Perhaps it would be wise to do it using a row-oriented version sometime.*/
  
  if(!TIFFReadRGBAImageOriented(tif,width,height,(uint32 *)image->pixels,ORIENTATION_TOPLEFT,1)){
    deleteImage(image);
    TIFFClose(tif);
    if(error!=NULL)
      *error=IMAGE_E_DECODE;
    return NULL;
  }


  /*And we're done*/

  TIFFClose(tif);
  

 

  if(error!=NULL)
    *error=IMAGE_E_OK;

  return image;


}


/*This, and saveImage below, should probably also take an optional explicit type to override the guessing.  Or maybe I should just export the various loadXXXXImage functions for that purpose.  Something to think about.*/
IMAGE *loadImage(char *name,int *error){
  int len;
  char *dotptr;

  if(LastJpegErrorString!=NULL){
    free(LastJpegErrorString);
    LastJpegErrorString=NULL;
  }

  /*Guess the file type from the extension.  Perhaps in future we could also try magic number detection.*/
  dotptr=strrchr(name,'.');
  if((dotptr!=NULL)&&(strcasecmp(dotptr,".tif")==0))
    return loadTIFFImage(name,error);
  else if((dotptr!=NULL)&&((strcasecmp(dotptr,".jpg")==0)||(strcasecmp(dotptr,".jpeg")==0)))
    return loadJPEGImage(name,error);
  else {
    *error=IMAGE_E_DECODE; //could do with a better error code here.  More specific.
    return NULL;
  }

}




/*saveImage produces an image file on disk corresponding to the given image.  The image must have a known structure (eg RGB). The file will be saved in portable pixmap (PPM) format.  Returns nonzero on success, zero on error. If error is non-NULL then an error value is returned in *error.*/
int savePPMImage(char *name, IMAGE *image,int *error){
  int i,j;
  FILE *temp;
  temp=fopen(name,"w");
  if(temp==NULL){
    if(error!=NULL)
      *error=IMAGE_E_IO;
    return 0;
  }
  fprintf(temp,"P6\n%d\n%d\n255\n",imageCols(image),imageRows(image));
  
  
  for(i=0;i<imageRows(image);i++){
    for(j=0;j<imageCols(image);j++){
      fputc(getRed(image,i,j),temp);
      fputc(getGreen(image,i,j),temp);
      fputc(getBlue(image,i,j),temp);
    }
  }
  
  if(ferror(temp)){
    if(error!=NULL)
      *error=IMAGE_E_IO;
    fclose(temp);
    return 0;
  }
  
  fclose(temp);
  if(error!=NULL)
    *error=IMAGE_E_OK;
  
  return 1;
  
 
}



/*outputImage produces an image file on disk corresponding to the given image.  The image must have a known structure (eg RGB). The file will be saved in portable pixmap (PPM) format.  Returns nonzero on success, zero on error. If error is non-NULL then an error value is returned in *error.*/
int saveTIFFImage(char *name, IMAGE *image,int *error){
  int s,i,j,b;
  TIFF *temp;
  unsigned char *strip;
  
  strip=malloc(imageCols(image)*10*3);
  if(strip==NULL){
    if(error!=NULL)
      *error=IMAGE_E_MEMORY;
    return 0;
  }
  temp=TIFFOpen(name,"w");
  if(temp==NULL){
    free(strip);
    if(error!=NULL)
      *error=IMAGE_E_IO;
    return 0;
  }
  
  TIFFSetField(temp,TIFFTAG_PHOTOMETRIC,2);
  TIFFSetField(temp,TIFFTAG_COMPRESSION,32773);
  TIFFSetField(temp,TIFFTAG_PLANARCONFIG,1);
  TIFFSetField(temp,TIFFTAG_IMAGELENGTH,imageRows(image));
  TIFFSetField(temp,TIFFTAG_IMAGEWIDTH,imageCols(image));
  TIFFSetField(temp,TIFFTAG_ROWSPERSTRIP,10);
  TIFFSetField(temp,TIFFTAG_XRESOLUTION,1.0);
  TIFFSetField(temp,TIFFTAG_YRESOLUTION,1.0);
  TIFFSetField(temp,TIFFTAG_RESOLUTIONUNIT,1);
  TIFFSetField(temp,TIFFTAG_BITSPERSAMPLE,8,8,8);
  TIFFSetField(temp,TIFFTAG_SAMPLESPERPIXEL,3);
  
  /*That sets up the file.  Now go through and compose the
    strips as we go.*/
  
  for(s=0;s<imageRows(image);s+=10){
    b=0;
    for(i=0;i<MIN(10,imageRows(image)-s);i++){
      for(j=0;j<imageCols(image);j++){
	strip[b++]=getRed(image,s+i,j);
	strip[b++]=getGreen(image,s+i,j);
	strip[b++]=getBlue(image,s+i,j);
      }
    }
    TIFFWriteEncodedStrip(temp,s/10,strip,imageCols(image)*10*3);
    
  }
  

  TIFFClose(temp);
  free(strip);
  
  if(error!=NULL)
    *error=IMAGE_E_OK;
  return 1;
  
  
  }


int saveImage(char *name, IMAGE *image,int *error){
  char *dotptr;
  int len;

  if(LastJpegErrorString!=NULL){
    free(LastJpegErrorString);
    LastJpegErrorString=NULL;
  }

 /*Guess the file type from the extension.  Perhaps in future we could also try magic number detection.*/
  dotptr=strrchr(name,'.');
  if((dotptr!=NULL)&&(strcasecmp(dotptr,".tif")==0))
    return saveTIFFImage(name,image,error);
  else if((dotptr!=NULL)&&((strcasecmp(dotptr,".jpg")==0)||(strcasecmp(dotptr,".jpeg")==0)))
    return saveJPEGImage(name,image,error);
  else {
    *error=IMAGE_E_DECODE; //could do with a better error code here.  More specific.
    return 0;
  }

}




char *lastImageErrorString(void){
  return LastJpegErrorString;
}
openSUSE Build Service is sponsored by