+/*
+=head1 NAME
+
+jpeg.c - implement saving and loading JPEG images
+
+=head1 SYNOPSIS
+
+ io_glue *ig;
+ if (!i_writejpeg_wiol(im, ig, quality)) {
+ .. error ..
+ }
+ im = i_readjpeg_wiol(ig, length, iptc_text, itlength);
+
+=head1 DESCRIPTION
+
+Reads and writes JPEG images
+
+=over
+
+=cut
+*/
+
#include <stdio.h>
#include <sys/stat.h>
#ifndef _MSC_VER
#include <setjmp.h>
#include "iolayer.h"
-#include "image.h"
+#include "imageri.h"
#include "jpeglib.h"
+#include "jerror.h"
+#include <errno.h>
+#ifdef IMEXIF_ENABLE
+#include "imexif.h"
+#endif
+#define JPEG_APP13 0xED /* APP13 marker code */
+#define JPEG_APP1 (JPEG_APP0 + 1)
+#define JPGS 16384
-unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI};
+static unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI};
-/* Handlers to read from memory */
+/* Bad design right here */
+
+static int tlength=0;
+static char **iptc_text=NULL;
-typedef struct {
- struct jpeg_source_mgr pub; /* public fields */
- char *data;
- int length;
- JOCTET * buffer; /* start of buffer */
- boolean start_of_file; /* have we gotten any data yet? */
-} scalar_source_mgr;
-typedef scalar_source_mgr *scalar_src_ptr;
+/* Source and Destination managers */
+typedef struct {
+ struct jpeg_source_mgr pub; /* public fields */
+ io_glue *data;
+ JOCTET *buffer; /* start of buffer */
+ int length; /* Do I need this? */
+ boolean start_of_file; /* have we gotten any data yet? */
+} wiol_source_mgr;
+typedef struct {
+ struct jpeg_destination_mgr pub; /* public fields */
+ io_glue *data;
+ JOCTET *buffer; /* start of buffer */
+ boolean start_of_file; /* have we gotten any data yet? */
+} wiol_destination_mgr;
+typedef wiol_source_mgr *wiol_src_ptr;
+typedef wiol_destination_mgr *wiol_dest_ptr;
+/*
+ * Methods for io manager objects
+ *
+ * Methods for source managers:
+ *
+ * init_source (j_decompress_ptr cinfo);
+ * skip_input_data (j_decompress_ptr cinfo, long num_bytes);
+ * fill_input_buffer (j_decompress_ptr cinfo);
+ * term_source (j_decompress_ptr cinfo);
+ */
static void
-scalar_init_source (j_decompress_ptr cinfo) {
- scalar_src_ptr src = (scalar_src_ptr) cinfo->src;
+wiol_init_source (j_decompress_ptr cinfo) {
+ wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
- /* We reset the empty-input-file flag for each image,
- * but we don't clear the input buffer.
- * This is correct behavior for reading a series of images from one source.
+ /* We reset the empty-input-file flag for each image, but we don't clear
+ * the input buffer. This is correct behavior for reading a series of
+ * images from one source.
*/
src->start_of_file = TRUE;
}
+
+
static boolean
-scalar_fill_input_buffer (j_decompress_ptr cinfo) {
- scalar_src_ptr src = (scalar_src_ptr) cinfo->src;
- size_t nbytes;
+wiol_fill_input_buffer(j_decompress_ptr cinfo) {
+ wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
+ ssize_t nbytes; /* We assume that reads are "small" */
+
+ mm_log((1,"wiol_fill_input_buffer(cinfo 0x%p)\n", cinfo));
- if (src->start_of_file) {
- nbytes=src->length;
- src->buffer=src->data;
+ nbytes = src->data->readcb(src->data, src->buffer, JPGS);
+
+ if (nbytes <= 0) { /* Insert a fake EOI marker */
+ src->pub.next_input_byte = fake_eoi;
+ src->pub.bytes_in_buffer = 2;
} else {
- /* Insert a fake EOI marker */
- src->buffer = fake_eoi;
- nbytes = 2;
+ src->pub.next_input_byte = src->buffer;
+ src->pub.bytes_in_buffer = nbytes;
}
-
- src->pub.next_input_byte = src->buffer;
- src->pub.bytes_in_buffer = nbytes;
src->start_of_file = FALSE;
return TRUE;
}
+
static void
-scalar_skip_input_data (j_decompress_ptr cinfo, long num_bytes) {
- scalar_src_ptr src = (scalar_src_ptr) cinfo->src;
+wiol_skip_input_data (j_decompress_ptr cinfo, long num_bytes) {
+ wiol_src_ptr src = (wiol_src_ptr) 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
if (num_bytes > 0) {
while (num_bytes > (long) src->pub.bytes_in_buffer) {
num_bytes -= (long) src->pub.bytes_in_buffer;
- (void) scalar_fill_input_buffer(cinfo);
+ (void) wiol_fill_input_buffer(cinfo);
/* note we assume that fill_input_buffer will never return FALSE,
* so suspension need not be handled.
*/
}
static void
-scalar_term_source (j_decompress_ptr cinfo) { /* no work necessary here */ }
+wiol_term_source (j_decompress_ptr cinfo) {
+ /* no work necessary here */
+ wiol_src_ptr src;
+ if (cinfo->src != NULL) {
+ src = (wiol_src_ptr) cinfo->src;
+ myfree(src->buffer);
+ }
+}
+
+
+/* Source manager Constructor */
static void
-jpeg_scalar_src(j_decompress_ptr cinfo, char *data, int length) {
- scalar_src_ptr src;
+jpeg_wiol_src(j_decompress_ptr cinfo, io_glue *ig, int length) {
+ wiol_src_ptr src;
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(scalar_source_mgr));
- src = (scalar_src_ptr) cinfo->src;
+ cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small)
+ ((j_common_ptr) cinfo, JPOOL_PERMANENT,sizeof(wiol_source_mgr));
+ src = (wiol_src_ptr) cinfo->src;
}
+
+ /* put the request method call in here later */
+ io_glue_commit_types(ig);
- src = (scalar_src_ptr) cinfo->src;
- src->data = data;
+ src = (wiol_src_ptr) cinfo->src;
+ src->data = ig;
+ src->buffer = mymalloc( JPGS );
src->length = length;
- src->pub.init_source = scalar_init_source;
- src->pub.fill_input_buffer = scalar_fill_input_buffer;
- src->pub.skip_input_data = scalar_skip_input_data;
+
+ src->pub.init_source = wiol_init_source;
+ src->pub.fill_input_buffer = wiol_fill_input_buffer;
+ src->pub.skip_input_data = wiol_skip_input_data;
src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
- src->pub.term_source = scalar_term_source;
- src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
- src->pub.next_input_byte = NULL; /* until buffer loaded */
+ src->pub.term_source = wiol_term_source;
+ src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
+ src->pub.next_input_byte = NULL; /* until buffer loaded */
}
-undef_int
-i_writejpeg(i_img *im,int fd,int qfactor) {
- struct stat stbuf;
- JSAMPLE *image_buffer;
- int quality;
-
- struct jpeg_compress_struct cinfo;
- struct jpeg_error_mgr jerr;
-
- FILE *outfile; /* target file */
- JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
- int row_stride; /* physical row width in image buffer */
+/*
+ * Methods for destination managers:
+ *
+ * init_destination (j_compress_ptr cinfo);
+ * empty_output_buffer (j_compress_ptr cinfo);
+ * term_destination (j_compress_ptr cinfo);
+ *
+ */
- mm_log((1,"i_writejpeg(0x%x,fd %d,qfactor %d)\n",im,fd,qfactor));
-
- if (!(im->channels==1 || im->channels==3)) { fprintf(stderr,"Unable to write JPEG, improper colorspace.\n"); exit(3); }
- quality = qfactor;
-
- image_buffer=im->data;
-
- /* Step 1: allocate and initialize JPEG compression object */
-
- /* We have to set up the error handler first, in case the initialization
- * step fails. (Unlikely, but it could happen if you are out of memory.)
- * This routine fills in the contents of struct jerr, and returns jerr's
- * address which we place into the link field in cinfo.
- */
- cinfo.err = jpeg_std_error(&jerr);
- /* Now we can initialize the JPEG compression object. */
- jpeg_create_compress(&cinfo);
-
- /* Step 2: specify data destination (eg, a file) */
- /* Note: steps 2 and 3 can be done in either order. */
-
- /* Here we use the library-supplied code to send compressed data to a
- * stdio stream. You can also write your own code to do something else.
- * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
- * requires it in order to write binary files.
- */
-
- if (fstat(fd,&stbuf)<0) { fprintf(stderr,"Unable to stat fd.\n"); exit(1); }
+static void
+wiol_init_destination (j_compress_ptr cinfo) {
+ wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
- if ((outfile = fdopen(fd,"w")) == NULL) {
- fprintf(stderr, "can't fdopen.\n");
- exit(1);
- }
-
- jpeg_stdio_dest(&cinfo, outfile);
-
- /* Step 3: set parameters for compression */
-
- /* First we supply a description of the input image.
- * Four fields of the cinfo struct must be filled in:
+ /* We reset the empty-input-file flag for each image, but we don't clear
+ * the input buffer. This is correct behavior for reading a series of
+ * images from one source.
*/
- cinfo.image_width = im->xsize; /* image width and height, in pixels */
- cinfo.image_height = im->ysize;
+ dest->start_of_file = TRUE;
+}
- if (im->channels==3) {
- cinfo.input_components = 3; /* # of color components per pixel */
- cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
+static boolean
+wiol_empty_output_buffer(j_compress_ptr cinfo) {
+ wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
+ ssize_t rc;
+ /*
+ Previously this code was checking free_in_buffer to see how much
+ needed to be written. This does not follow the documentation:
+
+ "In typical applications, it should write out the
+ *entire* buffer (use the saved start address and buffer length;
+ ignore the current state of next_output_byte and free_in_buffer)."
+
+ ssize_t nbytes = JPGS - dest->pub.free_in_buffer;
+ */
+
+ mm_log((1,"wiol_empty_output_buffer(cinfo 0x%p)\n", cinfo));
+ rc = dest->data->writecb(dest->data, dest->buffer, JPGS);
+
+ if (rc != JPGS) { /* XXX: Should raise some jpeg error */
+ myfree(dest->buffer);
+ mm_log((1, "wiol_empty_output_buffer: Error: nbytes = %d != rc = %d\n", JPGS, rc));
+ ERREXIT(cinfo, JERR_FILE_WRITE);
}
+ dest->pub.free_in_buffer = JPGS;
+ dest->pub.next_output_byte = dest->buffer;
+ return TRUE;
+}
- if (im->channels==1) {
- cinfo.input_components = 1; /* # of color components per pixel */
- cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */
+static void
+wiol_term_destination (j_compress_ptr cinfo) {
+ wiol_dest_ptr dest = (wiol_dest_ptr) cinfo->dest;
+ size_t nbytes = JPGS - dest->pub.free_in_buffer;
+ /* yes, this needs to flush the buffer */
+ /* needs error handling */
+
+ if (dest->data->writecb(dest->data, dest->buffer, nbytes) != nbytes) {
+ myfree(dest->buffer);
+ ERREXIT(cinfo, JERR_FILE_WRITE);
}
-
- /* Now use the library's routine to set default compression parameters.
- * (You must set at least cinfo.in_color_space before calling this,
- * since the defaults depend on the source color space.)
- */
- jpeg_set_defaults(&cinfo);
- /* Now you can set any non-default parameters you wish to.
- * Here we just illustrate the use of quality (quantization table) scaling:
- */
-
- jpeg_set_quality(&cinfo, quality, TRUE); /* limit to baseline-JPEG values */
-
- /* Step 4: Start compressor */
- /* TRUE ensures that we will write a complete interchange-JPEG file.
- * Pass TRUE unless you are very sure of what you're doing.
- */
- jpeg_start_compress(&cinfo, TRUE);
+ if (dest != NULL) myfree(dest->buffer);
+}
- /* Step 5: while (scan lines remain to be written) */
- /* jpeg_write_scanlines(...); */
- /* Here we use the library's state variable cinfo.next_scanline as the
- * loop counter, so that we don't have to keep track ourselves.
- * To keep things simple, we pass one scanline per call; you can pass
- * more if you wish, though.
- */
- row_stride = im->xsize * im->channels; /* JSAMPLEs per row in image_buffer */
+/* Destination manager Constructor */
- while (cinfo.next_scanline < cinfo.image_height) {
- /* jpeg_write_scanlines expects an array of pointers to scanlines.
- * Here the array is only one element long, but you could pass
- * more than one scanline at a time if that's more convenient.
- */
- row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
- (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+static void
+jpeg_wiol_dest(j_compress_ptr cinfo, io_glue *ig) {
+ wiol_dest_ptr dest;
+
+ if (cinfo->dest == NULL) { /* first time for this JPEG object? */
+ cinfo->dest =
+ (struct jpeg_destination_mgr *)
+ (*cinfo->mem->alloc_small)
+ ((j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(wiol_destination_mgr));
}
-
- /* Step 6: Finish compression */
-
- jpeg_finish_compress(&cinfo);
- /* After finish_compress, we can close the output file. */
- fclose(outfile);
-
- /* Step 7: release JPEG compression object */
-
- /* This is an important step since it will release a good deal of memory. */
- jpeg_destroy_compress(&cinfo);
-
- return(1);
+
+ dest = (wiol_dest_ptr) cinfo->dest;
+ dest->data = ig;
+ dest->buffer = mymalloc( JPGS );
+
+ dest->pub.init_destination = wiol_init_destination;
+ dest->pub.empty_output_buffer = wiol_empty_output_buffer;
+ dest->pub.term_destination = wiol_term_destination;
+ dest->pub.free_in_buffer = JPGS;
+ dest->pub.next_output_byte = dest->buffer;
}
-
-static int tlength=0;
-static char **iptc_text=NULL;
-
-#define JPEG_APP13 0xED /* APP13 marker code */
-
LOCAL(unsigned int)
jpeg_getc (j_decompress_ptr cinfo)
/* Read next byte */
return TRUE;
}
-
-
-
-
-
-
-
-
-
-
-
METHODDEF(void)
-my_output_message (j_common_ptr cinfo)
-{
+my_output_message (j_common_ptr cinfo) {
char buffer[JMSG_LENGTH_MAX];
/* Create the message */
(*cinfo->err->format_message) (cinfo, buffer);
+ i_push_error(0, buffer);
+
/* Send it to stderr, adding a newline */
mm_log((1, "%s\n", buffer));
}
-
-
-
-
-
struct my_error_mgr {
struct jpeg_error_mgr pub; /* "public" fields */
jmp_buf setjmp_buffer; /* for return to caller */
/* Always display the message. */
/* We could postpone this until after returning, if we chose. */
(*cinfo->err->output_message) (cinfo);
-
+
/* Return control to the setjmp point */
longjmp(myerr->setjmp_buffer, 1);
}
+static void
+transfer_cmyk_inverted(i_color *out, JSAMPARRAY in, int width) {
+ JSAMPROW inrow = *in;
+ while (width--) {
+ /* extract and convert to real CMYK */
+ /* horribly enough this is correct given cmyk values are inverted */
+ int c = *inrow++;
+ int m = *inrow++;
+ int y = *inrow++;
+ int k = *inrow++;
+ out->rgba.r = (c * k) / MAXJSAMPLE;
+ out->rgba.g = (m * k) / MAXJSAMPLE;
+ out->rgba.b = (y * k) / MAXJSAMPLE;
+ ++out;
+ }
+}
+static void
+transfer_rgb(i_color *out, JSAMPARRAY in, int width) {
+ JSAMPROW inrow = *in;
+ while (width--) {
+ out->rgba.r = *inrow++;
+ out->rgba.g = *inrow++;
+ out->rgba.b = *inrow++;
+ ++out;
+ }
+}
+static void
+transfer_gray(i_color *out, JSAMPARRAY in, int width) {
+ JSAMPROW inrow = *in;
+ while (width--) {
+ out->gray.gray_color = *inrow++;
+ ++out;
+ }
+}
+typedef void (*transfer_function_t)(i_color *out, JSAMPARRAY in, int width);
+/*
+=item i_readjpeg_wiol(data, length, iptc_itext, itlength)
+=cut
+*/
i_img*
-i_readjpeg(int fd,char** iptc_itext,int *itlength) {
- i_img *im;
-
+i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
+ i_img * volatile im = NULL;
+#ifdef IMEXIF_ENABLE
+ int seen_exif = 0;
+#endif
+ i_color * volatile line_buffer = NULL;
struct jpeg_decompress_struct cinfo;
- /* We use our private extension JPEG error handler.
- * Note that this struct must live as long as the main JPEG parameter
- * struct, to avoid dangling-pointer problems.
- */
-
- /* struct jpeg_error_mgr jerr;*/
struct my_error_mgr jerr;
- FILE * infile; /* source file */
JSAMPARRAY buffer; /* Output row buffer */
int row_stride; /* physical row width in output buffer */
+ jpeg_saved_marker_ptr markerp;
+ transfer_function_t transfer_f;
+ int channels;
+ volatile int src_set = 0;
- mm_log((1,"i_readjpeg(fd %d,iptc_itext 0x%x)\n",fd,iptc_itext));
-
- iptc_text=iptc_itext;
+ mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, length, iptc_itext));
- if ((infile = fdopen(fd,"r")) == NULL) {
- fprintf(stderr, "can't fdopen.\n");
- exit(1);
- }
-
- /* Step 1: allocate and initialize JPEG decompression object */
+ i_clear_error();
- /* We set up the normal JPEG error routines, then override error_exit. */
+ iptc_text = iptc_itext;
cinfo.err = jpeg_std_error(&jerr.pub);
- jerr.pub.error_exit = my_error_exit;
+ jerr.pub.error_exit = my_error_exit;
jerr.pub.output_message = my_output_message;
- /* Establish the setjmp return context for my_error_exit to use. */
+
+ /* Set error handler */
if (setjmp(jerr.setjmp_buffer)) {
- /* If we get here, the JPEG code has signaled an error.
- * We need to clean up the JPEG object, close the input file, and return.
- */
+ if (src_set)
+ wiol_term_source(&cinfo);
jpeg_destroy_decompress(&cinfo);
- fclose(infile);
*iptc_itext=NULL;
*itlength=0;
+ if (line_buffer)
+ myfree(line_buffer);
+ if (im)
+ i_img_destroy(im);
return NULL;
}
- /* Now we can initialize the JPEG decompression object. */
jpeg_create_decompress(&cinfo);
jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
- /* Step 2: specify data source (eg, a file) */
-
- jpeg_stdio_src(&cinfo, infile);
-
- /* Step 3: read file parameters with jpeg_read_header() */
+ jpeg_save_markers(&cinfo, JPEG_APP1, 0xFFFF);
+ jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF);
+ jpeg_wiol_src(&cinfo, data, length);
+ src_set = 1;
(void) jpeg_read_header(&cinfo, TRUE);
-
- /* We can ignore the return value from jpeg_read_header since
- * (a) suspension is not possible with the stdio data source, and
- * (b) we passed TRUE to reject a tables-only JPEG file as an error.
- * See libjpeg.doc for more info.
- */
-
- /* Step 4: set parameters for decompression */
+ (void) jpeg_start_decompress(&cinfo);
- /* In this example, we don't need to change any of the defaults set by
- * jpeg_read_header(), so we do nothing here.
- */
+ channels = cinfo.output_components;
+ switch (cinfo.out_color_space) {
+ case JCS_GRAYSCALE:
+ if (cinfo.output_components != 1) {
+ mm_log((1, "i_readjpeg: grayscale image with %d channels\n", cinfo.output_components));
+ i_push_errorf(0, "grayscale image with invalid components %d", cinfo.output_components);
+ wiol_term_source(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ return NULL;
+ }
+ transfer_f = transfer_gray;
+ break;
- /* Step 5: Start decompressor */
+ case JCS_RGB:
+ transfer_f = transfer_rgb;
+ if (cinfo.output_components != 3) {
+ mm_log((1, "i_readjpeg: RGB image with %d channels\n", cinfo.output_components));
+ i_push_errorf(0, "RGB image with invalid components %d", cinfo.output_components);
+ wiol_term_source(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ return NULL;
+ }
+ break;
- (void) jpeg_start_decompress(&cinfo);
- /* We can ignore the return value since suspension is not possible
- * with the stdio data source.
- */
+ case JCS_CMYK:
+ if (cinfo.output_components == 4) {
+ /* we treat the CMYK values as inverted, because that's what that
+ buggy photoshop does, and everyone has to follow the gorilla.
- /* We may need to do some setup of our own at this point before reading
- * the data. After jpeg_start_decompress() we have the correct scaled
- * output image dimensions available, as well as the output colormap
- * if we asked for color quantization.
- * In this example, we need to make an output work buffer of the right size.
- */
+ Is there any app that still produces correct CMYK JPEGs?
+ */
+ transfer_f = transfer_cmyk_inverted;
+ channels = 3;
+ }
+ else {
+ mm_log((1, "i_readjpeg: cmyk image with %d channels\n", cinfo.output_components));
+ i_push_errorf(0, "CMYK image with invalid components %d", cinfo.output_components);
+ wiol_term_source(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ return NULL;
+ }
+ break;
- im=i_img_empty_ch(NULL,cinfo.output_width,cinfo.output_height,cinfo.output_components);
+ default:
+ mm_log((1, "i_readjpeg: unknown color space %d\n", cinfo.out_color_space));
+ i_push_errorf(0, "Unknown color space %d", cinfo.out_color_space);
+ wiol_term_source(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ return NULL;
+ }
- /* fprintf(stderr,"JPEG info:\n xsize:%d\n ysize:%d\n channels:%d.\n",xsize,ysize,channels);*/
+ if (!i_int_check_image_file_limits(cinfo.output_width, cinfo.output_height,
+ channels, sizeof(i_sample_t))) {
+ mm_log((1, "i_readjpeg: image size exceeds limits\n"));
+ wiol_term_source(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ return NULL;
+ }
- /* JSAMPLEs per row in output buffer */
+ im=i_img_empty_ch(NULL, cinfo.output_width, cinfo.output_height, channels);
+ if (!im) {
+ wiol_term_source(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ return NULL;
+ }
row_stride = cinfo.output_width * cinfo.output_components;
- /* Make a one-row-high sample array that will go away when done with image */
buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
-
- /* Step 6: while (scan lines remain to be read) */
- /* jpeg_read_scanlines(...); */
-
- /* Here we use the library's state variable cinfo.output_scanline as the
- * loop counter, so that we don't have to keep track ourselves.
- */
-
+ line_buffer = mymalloc(sizeof(i_color) * cinfo.output_width);
while (cinfo.output_scanline < cinfo.output_height) {
- /* jpeg_read_scanlines expects an array of pointers to scanlines.
- * Here the array is only one element long, but you could ask for
- * more than one scanline at a time if that's more convenient.
- */
(void) jpeg_read_scanlines(&cinfo, buffer, 1);
- /* Assume put_scanline_someplace wants a pointer and sample count. */
- memcpy(im->data+im->channels*im->xsize*(cinfo.output_scanline-1),buffer[0],row_stride);
+ transfer_f(line_buffer, buffer, cinfo.output_width);
+ i_plin(im, 0, cinfo.output_width, cinfo.output_scanline-1, line_buffer);
}
+ myfree(line_buffer);
+ line_buffer = NULL;
+
+ /* check for APP1 marker and save */
+ markerp = cinfo.marker_list;
+ while (markerp != NULL) {
+ if (markerp->marker == JPEG_COM) {
+ i_tags_add(&im->tags, "jpeg_comment", 0, (const char *)markerp->data,
+ markerp->data_length, 0);
+ }
+#ifdef IMEXIF_ENABLE
+ else if (markerp->marker == JPEG_APP1 && !seen_exif) {
+ seen_exif = i_int_decode_exif(im, markerp->data, markerp->data_length);
+ }
+#endif
- /* Step 7: Finish decompression */
-
- (void) jpeg_finish_decompress(&cinfo);
- /* We can ignore the return value since suspension is not possible
- * with the stdio data source.
- */
+ markerp = markerp->next;
+ }
- /* Step 8: Release JPEG decompression object */
+ i_tags_addn(&im->tags, "jpeg_out_color_space", 0, cinfo.out_color_space);
+ i_tags_addn(&im->tags, "jpeg_color_space", 0, cinfo.jpeg_color_space);
+
+ if (cinfo.saw_JFIF_marker) {
+ double xres = cinfo.X_density;
+ double yres = cinfo.Y_density;
+
+ i_tags_addn(&im->tags, "jpeg_density_unit", 0, cinfo.density_unit);
+ switch (cinfo.density_unit) {
+ case 0: /* values are just the aspect ratio */
+ i_tags_addn(&im->tags, "i_aspect_only", 0, 1);
+ i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "none", -1, 0);
+ break;
+
+ case 1: /* per inch */
+ i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "inch", -1, 0);
+ break;
+
+ case 2: /* per cm */
+ i_tags_add(&im->tags, "jpeg_density_unit_name", 0, "centimeter", -1, 0);
+ xres *= 2.54;
+ yres *= 2.54;
+ break;
+ }
+ i_tags_set_float2(&im->tags, "i_xres", 0, xres, 6);
+ i_tags_set_float2(&im->tags, "i_yres", 0, yres, 6);
+ }
- /* This is an important step since it will release a good deal of memory. */
+ (void) jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
+ *itlength=tlength;
- /* After finish_decompress, we can close the input file.
- * Here we postpone it until after no more JPEG errors are possible,
- * so as to simplify the setjmp error logic above. (Actually, I don't
- * think that jpeg_destroy can do an error exit, but why assume anything...)
- */
-
-/* fclose(infile); DO NOT fclose() BECAUSE THEN close() WILL FAIL*/
-
- /* At this point you may want to check to see whether any corrupt-data
- * warnings occurred (test whether jerr.pub.num_warnings is nonzero).
- */
-
- /* And we're done! */
+ i_tags_add(&im->tags, "i_format", 0, "jpeg", 4, 0);
- *itlength=tlength;
- mm_log((1,"i_readjpeg -> (0x%x)\n",im));
+ mm_log((1,"i_readjpeg_wiol -> (0x%x)\n",im));
return im;
}
+/*
+=item i_writejpeg_wiol(im, ig, qfactor)
+=cut
+*/
-i_img*
-i_readjpeg_scalar(char *data, int length,char** iptc_itext,int *itlength) {
- i_img *im;
+undef_int
+i_writejpeg_wiol(i_img *im, io_glue *ig, int qfactor) {
+ JSAMPLE *image_buffer;
+ int quality;
+ int got_xres, got_yres, aspect_only, resunit;
+ double xres, yres;
+ int comment_entry;
+ int want_channels = im->channels;
- struct jpeg_decompress_struct cinfo;
+ struct jpeg_compress_struct cinfo;
struct my_error_mgr jerr;
- JSAMPARRAY buffer; /* Output row buffer */
- int row_stride; /* physical row width in output buffer */
- mm_log((1,"i_readjpeg_scalar(data 0x%08x, length %d,iptc_itext 0x%x)\n",data,length,iptc_itext));
- iptc_text=iptc_itext;
+ JSAMPROW row_pointer[1]; /* pointer to JSAMPLE row[s] */
+ int row_stride; /* physical row width in image buffer */
+ unsigned char * data = NULL;
+ i_color *line_buf = NULL;
+
+ mm_log((1,"i_writejpeg(im %p, ig %p, qfactor %d)\n", im, ig, qfactor));
+
+ i_clear_error();
+ io_glue_commit_types(ig);
+
+ if (!(im->channels==1 || im->channels==3)) {
+ want_channels = im->channels - 1;
+ }
+ quality = qfactor;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
jerr.pub.output_message = my_output_message;
- if (setjmp(jerr.setjmp_buffer)) {
- jpeg_destroy_decompress(&cinfo);
- *iptc_itext=NULL;
- *itlength=0;
- return NULL;
- }
- jpeg_create_decompress(&cinfo);
- jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
- jpeg_scalar_src(&cinfo, data, length );
- (void) jpeg_read_header(&cinfo, TRUE);
- (void) jpeg_start_decompress(&cinfo);
- im=i_img_empty_ch(NULL,cinfo.output_width,cinfo.output_height,cinfo.output_components);
- row_stride = cinfo.output_width * cinfo.output_components;
- buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
- while (cinfo.output_scanline < cinfo.output_height) {
- (void) jpeg_read_scanlines(&cinfo, buffer, 1);
- memcpy(im->data+im->channels*im->xsize*(cinfo.output_scanline-1),buffer[0],row_stride);
- }
- (void) jpeg_finish_decompress(&cinfo);
- jpeg_destroy_decompress(&cinfo);
- *itlength=tlength;
- mm_log((1,"i_readjpeg_scalar -> (0x%x)\n",im));
- return im;
-}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ jpeg_create_compress(&cinfo);
+ if (setjmp(jerr.setjmp_buffer)) {
+ jpeg_destroy_compress(&cinfo);
+ if (data)
+ myfree(data);
+ if (line_buf)
+ myfree(line_buf);
+ return 0;
+ }
+ jpeg_wiol_dest(&cinfo, ig);
+ cinfo.image_width = im -> xsize; /* image width and height, in pixels */
+ cinfo.image_height = im -> ysize;
+ if (want_channels==3) {
+ cinfo.input_components = 3; /* # of color components per pixel */
+ cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
+ }
+ if (want_channels==1) {
+ cinfo.input_components = 1; /* # of color components per pixel */
+ cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */
+ }
-#define JPGS 1024
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, quality, TRUE); /* limit to baseline-JPEG values */
+ got_xres = i_tags_get_float(&im->tags, "i_xres", 0, &xres);
+ got_yres = i_tags_get_float(&im->tags, "i_yres", 0, &yres);
+ if (!i_tags_get_int(&im->tags, "i_aspect_only", 0,&aspect_only))
+ aspect_only = 0;
+ if (!i_tags_get_int(&im->tags, "jpeg_density_unit", 0, &resunit))
+ resunit = 1; /* per inch */
+ if (resunit < 0 || resunit > 2) /* default to inch if invalid */
+ resunit = 1;
+ if (got_xres || got_yres) {
+ if (!got_xres)
+ xres = yres;
+ else if (!got_yres)
+ yres = xres;
+ if (aspect_only)
+ resunit = 0; /* standard tags override format tags */
+ if (resunit == 2) {
+ /* convert to per cm */
+ xres /= 2.54;
+ yres /= 2.54;
+ }
+ cinfo.density_unit = resunit;
+ cinfo.X_density = (int)(xres + 0.5);
+ cinfo.Y_density = (int)(yres + 0.5);
+ }
+ jpeg_start_compress(&cinfo, TRUE);
-typedef struct {
- struct jpeg_source_mgr pub; /* public fields */
- io_glue *data;
- JOCTET *buffer; /* start of buffer */
- int length; /* Do I need this? */
- boolean start_of_file; /* have we gotten any data yet? */
-} wiol_source_mgr;
+ if (i_tags_find(&im->tags, "jpeg_comment", 0, &comment_entry)) {
+ jpeg_write_marker(&cinfo, JPEG_COM,
+ (const JOCTET *)im->tags.tags[comment_entry].data,
+ im->tags.tags[comment_entry].size);
+ }
-typedef wiol_source_mgr *wiol_src_ptr;
+ row_stride = im->xsize * im->channels; /* JSAMPLEs per row in image_buffer */
-static void
-wiol_init_source (j_decompress_ptr cinfo) {
- wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
-
- /* We reset the empty-input-file flag for each image,
- * but we don't clear the input buffer.
- * This is correct behavior for reading a series of images from one source.
- */
- src->start_of_file = TRUE;
-}
+ if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits
+ && im->channels == want_channels) {
+ image_buffer=im->idata;
-static boolean
-wiol_fill_input_buffer(j_decompress_ptr cinfo) {
- wiol_src_ptr src = (wiol_src_ptr) cinfo->src;
- ssize_t nbytes; /* We assume that reads are "small" */
-
- mm_log((1,"wiol_fill_input_buffer(cinfo 0x%p)\n"));
-
- nbytes = src->data->readcb(src->data, src->buffer, JPGS);
-
- if (nbytes <= 0) { /* Insert a fake EOI marker */
- src->pub.next_input_byte = fake_eoi;
- src->pub.bytes_in_buffer = 2;
- } else {
- src->pub.next_input_byte = src->buffer;
- src->pub.bytes_in_buffer = nbytes;
- }
- src->start_of_file = FALSE;
- return TRUE;
-}
-
-static void
-wiol_skip_input_data (j_decompress_ptr cinfo, long num_bytes) {
- wiol_src_ptr src = (wiol_src_ptr) 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->pub.bytes_in_buffer) {
- num_bytes -= (long) src->pub.bytes_in_buffer;
- (void) wiol_fill_input_buffer(cinfo);
- /* note we assume that fill_input_buffer will never return FALSE,
- * so suspension need not be handled.
+ while (cinfo.next_scanline < cinfo.image_height) {
+ /* jpeg_write_scanlines expects an array of pointers to scanlines.
+ * Here the array is only one element long, but you could pass
+ * more than one scanline at a time if that's more convenient.
*/
+ row_pointer[0] = & image_buffer[cinfo.next_scanline * row_stride];
+ (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
- src->pub.next_input_byte += (size_t) num_bytes;
- src->pub.bytes_in_buffer -= (size_t) num_bytes;
}
-}
-
-static void
-wiol_term_source (j_decompress_ptr cinfo) {
- /* no work necessary here */
- wiol_src_ptr src;
- if (cinfo->src != NULL) {
- src = (wiol_src_ptr) cinfo->src;
- myfree(src->buffer);
- }
-}
-
-static void
-jpeg_wiol_src(j_decompress_ptr cinfo, io_glue *ig, int length) {
- wiol_src_ptr src;
-
- 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(wiol_source_mgr));
- src = (wiol_src_ptr) cinfo->src;
+ else {
+ i_color bg;
+
+ i_get_file_background(im, &bg);
+ data = mymalloc(im->xsize * im->channels);
+ if (data) {
+ while (cinfo.next_scanline < cinfo.image_height) {
+ /* jpeg_write_scanlines expects an array of pointers to scanlines.
+ * Here the array is only one element long, but you could pass
+ * more than one scanline at a time if that's more convenient.
+ */
+ i_gsamp_bg(im, 0, im->xsize, cinfo.next_scanline, data,
+ want_channels, &bg);
+ row_pointer[0] = data;
+ (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
+ }
+ myfree(data);
+ }
+ else {
+ jpeg_destroy_compress(&cinfo);
+ i_push_error(0, "out of memory");
+ return 0; /* out of memory? */
+ }
}
- /* put the request method call in here later */
- io_glue_commit_types(ig);
-
- src = (wiol_src_ptr) cinfo->src;
- src->data = ig;
- src->buffer = mymalloc( JPGS );
- src->length = length;
-
- src->pub.init_source = wiol_init_source;
- src->pub.fill_input_buffer = wiol_fill_input_buffer;
- src->pub.skip_input_data = wiol_skip_input_data;
- src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
- src->pub.term_source = wiol_term_source;
- src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
- src->pub.next_input_byte = NULL; /* until buffer loaded */
-}
-
-
-
-
-
-
-
+ /* Step 6: Finish compression */
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+ ig->closecb(ig);
+ return(1);
+}
-i_img*
-i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
- i_img *im;
+/*
+=back
- struct jpeg_decompress_struct cinfo;
- struct my_error_mgr jerr;
- JSAMPARRAY buffer; /* Output row buffer */
- int row_stride; /* physical row width in output buffer */
+=head1 AUTHOR
- mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, iptc_itext));
+Arnar M. Hrafnkelsson, addi@umich.edu
- iptc_text = iptc_itext;
- cinfo.err = jpeg_std_error(&jerr.pub);
- jerr.pub.error_exit = my_error_exit;
- jerr.pub.output_message = my_output_message;
-
- /* Set error handler */
- if (setjmp(jerr.setjmp_buffer)) {
- jpeg_destroy_decompress(&cinfo);
- *iptc_itext=NULL;
- *itlength=0;
- return NULL;
- }
-
- jpeg_create_decompress(&cinfo);
- jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
- jpeg_wiol_src(&cinfo, data, length);
-
- (void) jpeg_read_header(&cinfo, TRUE);
- (void) jpeg_start_decompress(&cinfo);
- im=i_img_empty_ch(NULL,cinfo.output_width,cinfo.output_height,cinfo.output_components);
- row_stride = cinfo.output_width * cinfo.output_components;
- buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
- while (cinfo.output_scanline < cinfo.output_height) {
- (void) jpeg_read_scanlines(&cinfo, buffer, 1);
- memcpy(im->data+im->channels*im->xsize*(cinfo.output_scanline-1),buffer[0],row_stride);
- }
- (void) jpeg_finish_decompress(&cinfo);
- jpeg_destroy_decompress(&cinfo);
- *itlength=tlength;
- mm_log((1,"i_readjpeg_wiol -> (0x%x)\n",im));
- return im;
-}
+=head1 SEE ALSO
+Imager(3)
+=cut
+*/