#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
static unsigned char fake_eoi[]={(JOCTET) 0xFF,(JOCTET) JPEG_EOI};
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"));
+ mm_log((1,"wiol_fill_input_buffer(cinfo 0x%p)\n", cinfo));
nbytes = src->data->readcb(src->data, src->buffer, JPGS);
ssize_t nbytes = JPGS - dest->pub.free_in_buffer;
*/
- mm_log((1,"wiol_empty_output_buffer(cinfo 0x%p)\n"));
+ 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 */
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)
*/
i_img*
i_readjpeg_wiol(io_glue *data, int length, char** iptc_itext, int *itlength) {
- i_img *im;
-
+ i_img * volatile im = NULL;
+#ifdef IMEXIF_ENABLE
+ int seen_exif = 0;
+#endif
+ i_color * volatile line_buffer = NULL;
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
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_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, iptc_itext));
+ mm_log((1,"i_readjpeg_wiol(data 0x%p, length %d,iptc_itext 0x%p)\n", data, length, iptc_itext));
i_clear_error();
/* Set error handler */
if (setjmp(jerr.setjmp_buffer)) {
+ if (src_set)
+ wiol_term_source(&cinfo);
jpeg_destroy_decompress(&cinfo);
*iptc_itext=NULL;
*itlength=0;
+ if (line_buffer)
+ myfree(line_buffer);
+ if (im)
+ i_img_destroy(im);
return NULL;
}
jpeg_create_decompress(&cinfo);
jpeg_set_marker_processor(&cinfo, JPEG_APP13, APP13_handler);
+ 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);
(void) jpeg_start_decompress(&cinfo);
- im=i_img_empty_ch(NULL,cinfo.output_width,cinfo.output_height,cinfo.output_components);
+
+ 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;
+
+ 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;
+
+ 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.
+
+ 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;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
buffer = (*cinfo.mem->alloc_sarray) ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
+ line_buffer = mymalloc(sizeof(i_color) * cinfo.output_width);
while (cinfo.output_scanline < cinfo.output_height) {
(void) jpeg_read_scanlines(&cinfo, buffer, 1);
- memcpy(im->idata+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
+
+ markerp = markerp->next;
+ }
+
+ 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);
+ }
+
(void) jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
*itlength=tlength;
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_compress_struct cinfo;
struct my_error_mgr jerr;
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)) {
- i_push_error(0, "only 1 or 3 channels images can be saved as JPEG");
- return 0;
+ want_channels = im->channels - 1;
}
quality = qfactor;
jpeg_destroy_compress(&cinfo);
if (data)
myfree(data);
+ if (line_buf)
+ myfree(line_buf);
return 0;
}
- io_glue_commit_types(ig);
jpeg_wiol_dest(&cinfo, ig);
cinfo.image_width = im -> xsize; /* image width and height, in pixels */
cinfo.image_height = im -> ysize;
- if (im->channels==3) {
+ if (want_channels==3) {
cinfo.input_components = 3; /* # of color components per pixel */
cinfo.in_color_space = JCS_RGB; /* colorspace of input image */
}
- if (im->channels==1) {
+ if (want_channels==1) {
cinfo.input_components = 1; /* # of color components per pixel */
cinfo.in_color_space = JCS_GRAYSCALE; /* colorspace of input image */
}
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);
+ 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);
+ }
+
row_stride = im->xsize * im->channels; /* JSAMPLEs per row in image_buffer */
- if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits) {
+ if (!im->virtual && im->type == i_direct_type && im->bits == i_8_bits
+ && im->channels == want_channels) {
image_buffer=im->idata;
while (cinfo.next_scanline < cinfo.image_height) {
}
}
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) {
* 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(im, 0, im->xsize, cinfo.next_scanline, data,
- NULL, im->channels);
+ 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);