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;
}