summaryrefslogtreecommitdiff
path: root/Build/source/texk/dvipdfm-x/jpegimage.c
diff options
context:
space:
mode:
Diffstat (limited to 'Build/source/texk/dvipdfm-x/jpegimage.c')
-rw-r--r--Build/source/texk/dvipdfm-x/jpegimage.c1102
1 files changed, 1102 insertions, 0 deletions
diff --git a/Build/source/texk/dvipdfm-x/jpegimage.c b/Build/source/texk/dvipdfm-x/jpegimage.c
new file mode 100644
index 00000000000..b7a4a5275cc
--- /dev/null
+++ b/Build/source/texk/dvipdfm-x/jpegimage.c
@@ -0,0 +1,1102 @@
+/* This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.
+
+ Copyright (C) 2002-2020 by Jin-Hwan Cho and Shunsaku Hirata,
+ the dvipdfmx project team.
+
+ Copyright (C) 1998, 1999 by Mark A. Wicks <mwicks@kettering.edu>
+
+ This program 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.
+
+ This program 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.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*
+ * JPEG SUPPORT
+ *
+ * Accroding to Libjpeg document:
+ *
+ * CAUTION: it appears that Adobe Photoshop writes inverted data in CMYK
+ * JPEG files: 0 represents 100% ink coverage, rather than 0% ink as you'd
+ * expect....
+ *
+ * To wrok with this problem, we must detect whether CMYK JPEG file is
+ * created by Photoshop. But there are no reliable way to determine this.
+ *
+ * According to Adobe Technical Note #5516,
+ * "Supporting the DCT Filters in PostScript Level 2", Section 18, p.27.
+ *
+ * DCTDecode ignores and skips any APPE marker segment does not begin with
+ * the `Adobe' 5-character string.
+ *
+ * PDF Reference Manual 4th ed., p.61-62.
+ *
+ * The JPEG filter implementation in Adobe Acrobat products does not
+ * support features of the JPEG standard that are irrelevant to images.
+ * In addition, certain choices have been made regarding reserved marker
+ * codes and other optional features of the standard. For details, see
+ * Adobe Technical Note #5116, Supporting the DCT Filters in PostScript
+ * Level 2.
+ */
+
+#include "system.h"
+#include "error.h"
+#include "mem.h"
+
+#include "mfileio.h"
+#include "numbers.h"
+
+#include "dpxconf.h"
+
+#include "pdfobj.h"
+
+#include "jpegimage.h"
+#include "pdfcolor.h"
+
+#include "pdfximage.h"
+
+#define JPEG_DEBUG_STR "JPEG"
+#define JPEG_DEBUG 3
+
+#ifdef HAVE_LIBJPEG
+#include <jpeglib.h>
+#endif /* HAVE_LIBJPEG */
+
+/* JPEG Markers */
+typedef enum {
+ JM_SOF0 = 0xc0,
+ JM_SOF1 = 0xc1,
+ JM_SOF2 = 0xc2,
+ JM_SOF3 = 0xc3,
+ JM_SOF5 = 0xc5,
+ JM_DHT = 0xc4,
+ JM_SOF6 = 0xc6,
+ JM_SOF7 = 0xc7,
+ JM_SOF9 = 0xc9,
+ JM_SOF10 = 0xca,
+ JM_SOF11 = 0xcb,
+ JM_DAC = 0xcc,
+ JM_SOF13 = 0xcd,
+ JM_SOF14 = 0xce,
+ JM_SOF15 = 0xcf,
+
+ JM_RST0 = 0xd0,
+ JM_RST1 = 0xd1,
+ JM_RST2 = 0xd2,
+ JM_RST3 = 0xd3,
+ JM_RST4 = 0xd4,
+ JM_RST5 = 0xd5,
+ JM_RST6 = 0xd6,
+ JM_RST7 = 0xd7,
+
+ JM_SOI = 0xd8,
+ JM_EOI = 0xd9,
+ JM_SOS = 0xda,
+ JM_DQT = 0xdb,
+ JM_DNL = 0xdc,
+ JM_DRI = 0xdd,
+ JM_DHP = 0xde,
+ JM_EXP = 0xdf,
+
+ JM_APP0 = 0xe0,
+ JM_APP1 = 0xe1,
+ JM_APP2 = 0xe2,
+ JM_APP14 = 0xee,
+ JM_APP15 = 0xef,
+
+ JM_COM = 0xfe
+} JPEG_marker;
+
+typedef enum {
+ JS_APPn_JFIF,
+ JS_APPn_ADOBE,
+ JS_APPn_ICC,
+ JS_APPn_XMP
+} JPEG_APPn_sig;
+
+struct JPEG_APPn_JFIF /* APP0 */
+{
+ uint16_t version;
+ uint8_t units; /* 0: only aspect ratio
+ * 1: dots per inch
+ * 2: dots per cm
+ */
+ uint16_t Xdensity;
+ uint16_t Ydensity;
+ uint8_t Xthumbnail;
+ uint8_t Ythumbnail;
+ unsigned char *thumbnail; /* Thumbnail data. */
+};
+
+struct JPEG_APPn_ICC /* APP2 */
+{
+ uint8_t seq_id;
+ uint8_t num_chunks;
+ unsigned char *chunk;
+
+ /* Length of ICC profile data in this chunk. */
+ size_t length;
+};
+
+struct JPEG_APPn_Adobe /* APP14 */
+{
+ uint16_t version;
+ uint16_t flag0;
+ uint16_t flag1;
+ uint8_t transform; /* color transform code */
+};
+
+struct JPEG_APPn_XMP /* APP1 */
+{
+ unsigned char *packet; /* XMP packet */
+
+ /* Length of XMP packet data */
+ size_t length;
+};
+
+struct JPEG_ext
+{
+ JPEG_marker marker;
+ JPEG_APPn_sig app_sig;
+ void *app_data;
+};
+
+#define MAX_COUNT 1024
+struct JPEG_info
+{
+ uint16_t height;
+ uint16_t width;
+
+ uint8_t bits_per_component;
+ uint8_t num_components;
+
+ double xdpi;
+ double ydpi;
+
+ /* Application specific extensions */
+ int flags;
+ int num_appn, max_appn;
+ struct JPEG_ext *appn;
+
+ /* Skip chunks not necessary. */
+ char skipbits[MAX_COUNT / 8 + 1];
+};
+
+#define HAVE_APPn_JFIF (1 << 0)
+#define HAVE_APPn_ADOBE (1 << 1)
+#define HAVE_APPn_ICC (1 << 2)
+#define HAVE_APPn_Exif (1 << 3)
+#define HAVE_APPn_XMP (1 << 4)
+
+static int JPEG_scan_file (struct JPEG_info *j_info, FILE *fp);
+static int JPEG_copy_stream (struct JPEG_info *j_info, pdf_obj *stream, FILE *fp);
+
+static void JPEG_info_init (struct JPEG_info *j_info);
+static void JPEG_info_clear (struct JPEG_info *j_info);
+static pdf_obj *JPEG_get_XMP (struct JPEG_info *j_info);
+static pdf_obj *JPEG_get_iccp (struct JPEG_info *j_info);
+static void jpeg_get_density (struct JPEG_info *j_info, double *xdensity, double *ydensity);
+
+int
+check_for_jpeg (FILE *fp)
+{
+ unsigned char jpeg_sig[2];
+
+ rewind(fp);
+ if (fread(jpeg_sig, sizeof(unsigned char), 2, fp) != 2)
+ return 0;
+ else if (jpeg_sig[0] != 0xff || jpeg_sig[1] != JM_SOI)
+ return 0;
+
+ return 1;
+}
+
+int
+jpeg_include_image (pdf_ximage *ximage, FILE *fp)
+{
+ pdf_obj *stream;
+ pdf_obj *stream_dict;
+ pdf_obj *colorspace;
+ int colortype;
+ ximage_info info;
+ struct JPEG_info j_info;
+
+ if (!check_for_jpeg(fp)) {
+ WARN("%s: Not a JPEG file?", JPEG_DEBUG_STR);
+ rewind(fp);
+ return -1;
+ }
+ /* File position is 2 here... */
+
+ pdf_ximage_init_image_info(&info);
+
+ JPEG_info_init(&j_info);
+
+ if (JPEG_scan_file(&j_info, fp) < 0) {
+ WARN("%s: Not a JPEG file?", JPEG_DEBUG_STR);
+ JPEG_info_clear(&j_info);
+ return -1;
+ }
+
+ switch (j_info.num_components) {
+ case 1:
+ colortype = PDF_COLORSPACE_TYPE_GRAY;
+ break;
+ case 3:
+ colortype = PDF_COLORSPACE_TYPE_RGB;
+ break;
+ case 4:
+ colortype = PDF_COLORSPACE_TYPE_CMYK;
+ break;
+ default:
+ WARN("%s: Unknown color space (num components: %d)", JPEG_DEBUG_STR, info.num_components);
+ JPEG_info_clear(&j_info);
+ return -1;
+ }
+
+ /* JPEG image use DCTDecode. */
+ stream = pdf_new_stream (0);
+ stream_dict = pdf_stream_dict(stream);
+ pdf_add_dict(stream_dict, pdf_new_name("Filter"), pdf_new_name("DCTDecode"));
+
+ /* XMP Metadata */
+ if (pdf_check_version(1, 4) >= 0) {
+ if (j_info.flags & HAVE_APPn_XMP) {
+ pdf_obj *XMP_stream;
+
+ XMP_stream = JPEG_get_XMP(&j_info);
+ pdf_add_dict(stream_dict,
+ pdf_new_name("Metadata"), pdf_ref_obj(XMP_stream));
+ pdf_release_obj(XMP_stream);
+ }
+ }
+
+ /* Check embedded ICC Profile */
+ colorspace = NULL;
+ if (j_info.flags & HAVE_APPn_ICC) {
+ pdf_obj *icc_stream, *intent;
+ int cspc_id;
+
+ icc_stream = JPEG_get_iccp(&j_info);
+ if (!icc_stream)
+ colorspace = NULL;
+ else {
+ if (iccp_check_colorspace(colortype,
+ pdf_stream_dataptr(icc_stream), pdf_stream_length (icc_stream)) < 0)
+ colorspace = NULL;
+ else {
+ cspc_id = iccp_load_profile(NULL, /* noname */
+ pdf_stream_dataptr(icc_stream),
+ pdf_stream_length (icc_stream));
+ if (cspc_id < 0)
+ colorspace = NULL;
+ else {
+ colorspace = pdf_get_colorspace_reference(cspc_id);
+ intent = iccp_get_rendering_intent(pdf_stream_dataptr(icc_stream),
+ pdf_stream_length (icc_stream));
+ if (intent)
+ pdf_add_dict(stream_dict, pdf_new_name("Intent"), intent);
+ }
+ }
+ pdf_release_obj(icc_stream);
+ }
+ }
+ /* No ICC or invalid ICC profile. */
+ if (!colorspace) {
+ switch (colortype) {
+ case PDF_COLORSPACE_TYPE_GRAY:
+ colorspace = pdf_new_name("DeviceGray");
+ break;
+ case PDF_COLORSPACE_TYPE_RGB:
+ colorspace = pdf_new_name("DeviceRGB");
+ break;
+ case PDF_COLORSPACE_TYPE_CMYK:
+ colorspace = pdf_new_name("DeviceCMYK");
+ break;
+ }
+ }
+ pdf_add_dict(stream_dict, pdf_new_name("ColorSpace"), colorspace);
+
+#define IS_ADOBE_CMYK(j) (((j).flags & HAVE_APPn_ADOBE) && (j).num_components == 4)
+ if (IS_ADOBE_CMYK(j_info)) {
+ pdf_obj *decode;
+ int i;
+
+ WARN("Adobe CMYK JPEG: Inverted color assumed.");
+ decode = pdf_new_array();
+ for (i = 0; i < j_info.num_components; i++) {
+ pdf_add_array(decode, pdf_new_number(1.0));
+ pdf_add_array(decode, pdf_new_number(0.0));
+ }
+ pdf_add_dict(stream_dict, pdf_new_name("Decode"), decode);
+ }
+
+ /* Copy file */
+ JPEG_copy_stream(&j_info, stream, fp);
+
+ info.width = j_info.width;
+ info.height = j_info.height;
+ info.bits_per_component = j_info.bits_per_component;
+ info.num_components = j_info.num_components;
+
+ jpeg_get_density(&j_info, &info.xdensity, &info.ydensity);
+
+ pdf_ximage_set_image(ximage, &info, stream);
+ JPEG_info_clear(&j_info);
+
+ return 0;
+}
+
+static void
+jpeg_get_density (struct JPEG_info *j_info, double *xdensity, double *ydensity)
+{
+ if (dpx_conf.compat_mode == dpx_mode_compat_mode) {
+ *xdensity = *ydensity = 72.0 / 100.0;
+ return;
+ }
+
+/*
+ * j_info->xdpi and j_info->ydpi are determined in most cases
+ * in JPEG_scan_file(). FIXME: However, in some kinds of JPEG files,
+ * j_info->xdpi, and j_info->ydpi are not determined in
+ * JPEG_scan_file(). In this case we assume
+ * that j_info->xdpi = j_info->ydpi = 72.0.
+ */
+ if (j_info->xdpi < 0.1 && j_info->ydpi < 0.1) {
+ j_info->xdpi = j_info->ydpi = 72.0;
+ }
+ *xdensity = 72.0 / j_info->xdpi;
+ *ydensity = 72.0 / j_info->ydpi;
+
+ return;
+}
+
+static void
+JPEG_info_init (struct JPEG_info *j_info)
+{
+ j_info->width = 0;
+ j_info->height = 0;
+ j_info->bits_per_component = 0;
+ j_info->num_components = 0;
+
+ j_info->xdpi = 0.0;
+ j_info->ydpi = 0.0;
+
+ j_info->flags = 0;
+ j_info->num_appn = 0;
+ j_info->max_appn = 0;
+ j_info->appn = NULL;
+
+ memset(j_info->skipbits, 0, MAX_COUNT / 8 + 1);
+}
+
+static void
+JPEG_release_APPn_data (JPEG_marker marker, JPEG_APPn_sig app_sig, void *app_data)
+{
+ if (marker == JM_APP0 &&
+ app_sig == JS_APPn_JFIF) {
+ struct JPEG_APPn_JFIF *data;
+
+ data = (struct JPEG_APPn_JFIF *) app_data;
+ if (data->thumbnail)
+ RELEASE(data->thumbnail);
+ data->thumbnail = NULL;
+
+ RELEASE(data);
+ } else if (marker == JM_APP2 && app_sig == JS_APPn_ICC) {
+ struct JPEG_APPn_ICC *data;
+
+ data = (struct JPEG_APPn_ICC *) app_data;
+ if (data->chunk)
+ RELEASE(data->chunk);
+ data->chunk = NULL;
+
+ RELEASE(data);
+ } else if (marker == JM_APP14 && app_sig == JS_APPn_ADOBE) {
+ struct JPEG_APPn_Adobe *data;
+
+ data = (struct JPEG_APPn_Adobe *) app_data;
+
+ RELEASE(data);
+ } else if (marker == JM_APP1 && app_sig == JS_APPn_XMP) {
+ struct JPEG_APPn_XMP *data;
+
+ data = (struct JPEG_APPn_XMP *) app_data;
+ if (data->packet)
+ RELEASE(data->packet);
+
+ RELEASE(data);
+ }
+}
+
+static void
+JPEG_info_clear (struct JPEG_info *j_info)
+{
+ if (j_info->num_appn > 0 &&
+ j_info->appn != NULL) {
+ int i;
+
+ for (i = 0; i < j_info->num_appn; i++)
+ JPEG_release_APPn_data(j_info->appn[i].marker,
+ j_info->appn[i].app_sig, j_info->appn[i].app_data);
+ RELEASE(j_info->appn);
+ }
+ j_info->appn = NULL;
+ j_info->num_appn = 0;
+ j_info->max_appn = 0;
+ j_info->flags = 0;
+}
+
+static pdf_obj *
+JPEG_get_iccp (struct JPEG_info *j_info)
+{
+ pdf_obj *icc_stream;
+ struct JPEG_APPn_ICC *icc;
+ int i, prev_id = 0, num_icc_seg = -1;
+
+ icc_stream = pdf_new_stream(STREAM_COMPRESS);
+ for (i = 0; i < j_info->num_appn; i++) {
+ if (j_info->appn[i].marker != JM_APP2 ||
+ j_info->appn[i].app_sig != JS_APPn_ICC)
+ continue;
+ icc = (struct JPEG_APPn_ICC *) j_info->appn[i].app_data;
+ if (num_icc_seg < 0 && prev_id == 0) {
+ num_icc_seg = icc->num_chunks;
+ /* ICC chunks are sorted? */
+ } else if (icc->seq_id != prev_id + 1 ||
+ num_icc_seg != icc->num_chunks || icc->seq_id > icc->num_chunks) {
+ WARN("Invalid JPEG ICC chunk: %d (p:%d, n:%d)", icc->seq_id, prev_id, icc->num_chunks);
+ pdf_release_obj(icc_stream);
+ icc_stream = NULL;
+ break;
+ }
+ pdf_add_stream(icc_stream, icc->chunk, icc->length);
+ prev_id = icc->seq_id;
+ num_icc_seg = icc->num_chunks;
+ }
+
+ return icc_stream;
+}
+
+static pdf_obj *
+JPEG_get_XMP (struct JPEG_info *j_info)
+{
+ pdf_obj *XMP_stream, *stream_dict;
+ struct JPEG_APPn_XMP *XMP;
+ int i, count = 0;
+
+ /* I don't know if XMP Metadata should be compressed here.*/
+ XMP_stream = pdf_new_stream(STREAM_COMPRESS);
+ stream_dict = pdf_stream_dict(XMP_stream);
+ pdf_add_dict(stream_dict,
+ pdf_new_name("Type"), pdf_new_name("Metadata"));
+ pdf_add_dict(stream_dict,
+ pdf_new_name("Subtype"), pdf_new_name("XML"));
+ for (i = 0; i < j_info->num_appn; i++) {
+ /* Not sure for the case of multiple segments */
+ if (j_info->appn[i].marker != JM_APP1 ||
+ j_info->appn[i].app_sig != JS_APPn_XMP)
+ continue;
+ XMP = (struct JPEG_APPn_XMP *) j_info->appn[i].app_data;
+ pdf_add_stream(XMP_stream, XMP->packet, XMP->length);
+ count++;
+ }
+ if (count > 1)
+ WARN("%s: Multiple XMP segments found in JPEG file. (untested)", JPEG_DEBUG_STR);
+
+ return XMP_stream;
+}
+
+static JPEG_marker
+JPEG_get_marker (FILE *fp)
+{
+ int c;
+
+ c = fgetc(fp);
+ if (c != 255)
+ return -1;
+
+ for (;;) {
+ c = fgetc(fp);
+ if (c < 0)
+ return -1;
+ else if (c > 0 && c < 255) {
+ return c;
+ }
+ }
+}
+
+static int
+add_APPn_marker (struct JPEG_info *j_info, JPEG_marker marker, int app_sig, void *app_data)
+{
+ int n;
+
+ if (j_info->num_appn >= j_info->max_appn) {
+ j_info->max_appn += 16;
+ j_info->appn = RENEW(j_info->appn, j_info->max_appn, struct JPEG_ext);
+ }
+ n = j_info->num_appn;
+
+ j_info->appn[n].marker = marker;
+ j_info->appn[n].app_sig = app_sig;
+ j_info->appn[n].app_data = app_data;
+
+ j_info->num_appn += 1;
+
+ return n;
+}
+
+static unsigned short
+read_APP14_Adobe (struct JPEG_info *j_info, FILE *fp)
+{
+ struct JPEG_APPn_Adobe *app_data;
+
+ app_data = NEW(1, struct JPEG_APPn_Adobe);
+ app_data->version = get_unsigned_pair(fp);
+ app_data->flag0 = get_unsigned_pair(fp);
+ app_data->flag1 = get_unsigned_pair(fp);
+ app_data->transform = get_unsigned_byte(fp);
+
+ add_APPn_marker(j_info, JM_APP14, JS_APPn_ADOBE, app_data);
+
+ return 7;
+}
+
+#define JPEG_EXIF_BIGENDIAN 0
+#define JPEG_EXIF_LITTLEENDIAN 1
+static int
+read_exif_bytes (unsigned char **pp, int n, int endian)
+{
+ int rval = 0;
+ unsigned char *p = *pp;
+ int i;
+
+ switch (endian) {
+ case JPEG_EXIF_BIGENDIAN:
+ for (i = 0; i < n; i++) {
+ rval = (rval << 8) + p[i];
+ }
+ break;
+ case JPEG_EXIF_LITTLEENDIAN:
+ for (i = n - 1; i >= 0; i--) {
+ rval = (rval << 8) + p[i];
+ }
+ break;
+ }
+
+ *pp += n;
+ return rval;
+}
+
+#define JPEG_EXIF_TYPE_BYTE 1
+#define JPEG_EXIF_TYPE_ASCII 2
+#define JPEG_EXIF_TYPE_SHORT 3
+#define JPEG_EXIF_TYPE_LONG 4
+#define JPEG_EXIF_TYPE_RATIONAL 5
+#define JPEG_EXIF_TYPE_UNDEFINED 7
+#define JPEG_EXIF_TYPE_SLONG 9
+#define JPEG_EXIF_TYPE_SRATIONAL 10
+
+#define JPEG_EXIF_TAG_XRESOLUTION 282
+#define JPEG_EXIF_TAG_YRESOLUTION 283
+#define JPEG_EXIF_TAG_RESOLUTIONUNIT 296
+#define JPEG_EXIF_TAG_RESUNIT_MS 0x5110
+#define JPEG_EXIF_TAG_XRES_MS 0x5111
+#define JPEG_EXIF_TAG_YRES_MS 0x5112
+static size_t
+read_APP1_Exif (struct JPEG_info *j_info, FILE *fp, size_t length)
+{
+ /* this doesn't save the data, just reads the tags we need */
+ /* based on info from http://www.exif.org/Exif2-2.PDF */
+ unsigned char *buffer = NEW(length, unsigned char);
+ unsigned char *p, *endptr;
+ unsigned char *tiff_header;
+ int endian;
+ int num_fields;
+ int value = 0, offset;
+ double xres = 0.0, yres = 0.0;
+ double res_unit = 1.0;
+ unsigned int xres_ms = 0;
+ unsigned int yres_ms = 0;
+ double res_unit_ms = 0.0;
+ double exifxdpi = 0.0, exifydpi = 0.0;
+
+ fread(buffer, length, 1, fp);
+ if (length < 10)
+ goto err;
+
+ p = buffer; endptr = buffer + length;
+ while ((p < endptr) && (*p == 0))
+ p++;
+
+ if (p + 8 >= endptr)
+ goto err;
+ /* TIFF header */
+ tiff_header = p;
+ if ((p[0] == 'M') && (p[1] == 'M'))
+ endian = JPEG_EXIF_BIGENDIAN;
+ else if ((p[0] == 'I') && (p[1] == 'I'))
+ endian = JPEG_EXIF_LITTLEENDIAN;
+ else {
+ WARN("%s: Invalid value in Exif TIFF header.", JPEG_DEBUG_STR);
+ goto err;
+ }
+ p += 2;
+ value = read_exif_bytes(&p, 2, endian);
+ if (value != 42) {
+ WARN("%s: Invalid value in Exif TIFF header.", JPEG_DEBUG_STR);
+ goto err;
+ }
+ /* Offset to 0th IFD */
+ offset = read_exif_bytes(&p, 4, endian);
+
+ p = tiff_header + offset;
+ if (p + 2 >= endptr)
+ goto err;
+ num_fields = read_exif_bytes(&p, 2, endian);
+ while (num_fields-- > 0 && p < endptr) {
+ int tag, type;
+ int count;
+ unsigned int den, num;
+
+ if (p + 12 > endptr) {
+ WARN("%s: Truncated Exif data...", JPEG_DEBUG_STR);
+ goto err;
+ }
+ tag = (int) read_exif_bytes(&p, 2, endian);
+ type = (int) read_exif_bytes(&p, 2, endian);
+ count = read_exif_bytes(&p, 4, endian);
+ /* Exif data is redundant... */
+ switch (tag) {
+ case JPEG_EXIF_TAG_XRESOLUTION:
+ if (type != JPEG_EXIF_TYPE_RATIONAL || count != 1) {
+ WARN("%s: Invalid data for XResolution in Exif chunk.", JPEG_DEBUG_STR);
+ goto err;
+ }
+ offset = read_exif_bytes(&p, 4, endian);
+ if (tiff_header + offset + 8 > buffer + length) {
+ WARN("%s: Invalid offset value in Exif data.", JPEG_DEBUG_STR);
+ goto err;
+ } else {
+ unsigned char *vp = tiff_header + offset;
+ num = (unsigned int) read_exif_bytes(&vp, 4, endian);
+ den = (unsigned int) read_exif_bytes(&vp, 4, endian);
+ }
+ if (den > 0)
+ xres = (double) num / den;
+ break;
+ case JPEG_EXIF_TAG_YRESOLUTION:
+ if (type != JPEG_EXIF_TYPE_RATIONAL || count != 1) {
+ WARN("%s: Invalid data for XResolution in Exif chunk.", JPEG_DEBUG_STR);
+ goto err;
+ }
+ offset = read_exif_bytes(&p, 4, endian);
+ if (tiff_header + offset + 8 > buffer + length) {
+ WARN("%s: Invalid offset value in Exif data.", JPEG_DEBUG_STR);
+ goto err;
+ } else {
+ unsigned char *vp = tiff_header + offset;
+ num = (unsigned int) read_exif_bytes(&vp, 4, endian);
+ den = (unsigned int) read_exif_bytes(&vp, 4, endian);
+ }
+ if (den > 0)
+ yres = (double) num / den;
+ break;
+ case JPEG_EXIF_TAG_RESOLUTIONUNIT:
+ if (type != JPEG_EXIF_TYPE_SHORT || count != 1) {
+ WARN("%s: Invalid data for ResolutionUnit in Exif chunk.", JPEG_DEBUG_STR);
+ goto err;
+ }
+ value = read_exif_bytes(&p, 2, endian);
+ p += 2;
+ if (value == 2)
+ res_unit = 1.0 ; /* inch */
+ else if (value == 3)
+ res_unit = 2.54; /* cm */
+ break;
+ case JPEG_EXIF_TAG_RESUNIT_MS: /* PixelUnit */
+ if (type != JPEG_EXIF_TYPE_BYTE || count != 1) {
+ WARN("%s: Invalid data for ResolutionUnit in Exif chunk.", JPEG_DEBUG_STR);
+ goto err;
+ }
+ value = read_exif_bytes(&p, 1, endian);
+ p += 3;
+ if (value == 1)
+ res_unit_ms = 0.0254; /* Unit is meter */
+ else
+ res_unit_ms = 0.0;
+ break;
+ case JPEG_EXIF_TAG_XRES_MS: /* PixelPerUnitX */
+ if (type != JPEG_EXIF_TYPE_LONG || count != 1) {
+ WARN("%s: Invalid data for PixelPerUnitX in Exif chunk.", JPEG_DEBUG_STR);
+ goto err;
+ }
+ value = read_exif_bytes(&p, 4, endian);
+ xres_ms = value;
+ break;
+ case JPEG_EXIF_TAG_YRES_MS: /* PixelPerUnitY */
+ if (type != JPEG_EXIF_TYPE_LONG || count != 1) {
+ WARN("%s: Invalid data for PixelPerUnitY in Exif chunk.", JPEG_DEBUG_STR);
+ goto err;
+ }
+ value = read_exif_bytes(&p, 4, endian);
+ yres_ms = value;
+ break;
+ default:
+ /* 40901 ColorSpace and 42240 Gamma unsupported... */
+ p += 4;
+ break;
+ }
+ }
+ if (num_fields > 0) {
+ WARN("%s: Truncated Exif data...", JPEG_DEBUG_STR);
+ goto err;
+ }
+
+/* Calculate Exif resolution, if given.
+ */
+ if (xres > 0.0 && yres > 0.0) {
+ exifxdpi = xres * res_unit;
+ exifydpi = yres * res_unit;
+ } else if (xres_ms > 0 && yres_ms > 0 && res_unit_ms > 0.0) {
+ exifxdpi = xres_ms * res_unit_ms;
+ exifydpi = yres_ms * res_unit_ms;
+ } else {
+ exifxdpi = 72.0 * res_unit;
+ exifydpi = 72.0 * res_unit;
+ }
+
+/* Do not overwrite j_info->xdpi and j_info->ydpi if they are
+ * already determined in JFIF.
+ */
+ if (j_info->xdpi < 0.1 && j_info->ydpi < 0.1) {
+ j_info->xdpi = exifxdpi;
+ j_info->ydpi = exifydpi;
+ } else {
+ double xxx1 = floor(exifxdpi+0.5);
+ double xxx2 = floor(j_info->xdpi+0.5);
+ double yyy1 = floor(exifydpi+0.5);
+ double yyy2 = floor(j_info->ydpi+0.5);
+
+ if (xxx1 != xxx2 || yyy1 != yyy2) {
+ WARN("%s: Inconsistent resolution may have " \
+ "specified in Exif and JFIF: %gx%g - %gx%g", JPEG_DEBUG_STR,
+ xxx1, yyy1, xxx2, yyy2);
+ }
+ }
+
+err:
+ RELEASE(buffer);
+ return length;
+}
+
+static size_t
+read_APP0_JFIF (struct JPEG_info *j_info, FILE *fp)
+{
+ struct JPEG_APPn_JFIF *app_data;
+ size_t thumb_data_len;
+
+ app_data = NEW(1, struct JPEG_APPn_JFIF);
+ app_data->version = get_unsigned_pair(fp);
+ app_data->units = get_unsigned_byte(fp);
+ app_data->Xdensity = get_unsigned_pair(fp);
+ app_data->Ydensity = get_unsigned_pair(fp);
+ app_data->Xthumbnail = get_unsigned_byte(fp);
+ app_data->Ythumbnail = get_unsigned_byte(fp);
+ thumb_data_len = 3 * app_data->Xthumbnail * app_data->Ythumbnail;
+ if (thumb_data_len > 0) {
+ app_data->thumbnail = NEW(thumb_data_len, unsigned char);
+ fread(app_data->thumbnail, 1, thumb_data_len, fp);
+ } else {
+ app_data->thumbnail = NULL;
+ }
+
+ add_APPn_marker(j_info, JM_APP0, JS_APPn_JFIF, app_data);
+
+ switch (app_data->units) {
+ case 1:
+ j_info->xdpi = app_data->Xdensity;
+ j_info->ydpi = app_data->Ydensity;
+ break;
+ case 2: /* density is in pixels per cm */
+ j_info->xdpi = app_data->Xdensity * 2.54;
+ j_info->ydpi = app_data->Ydensity * 2.54;
+ break;
+ default: /* FIXME: not sure what to do with this.... */
+ j_info->xdpi = 72.0;
+ j_info->ydpi = 72.0;
+ break;
+ }
+
+ return (9 + thumb_data_len);
+}
+
+static size_t
+read_APP0_JFXX (FILE *fp, size_t length)
+{
+ get_unsigned_byte(fp);
+ /* Extension Code:
+ *
+ * 0x10: Thumbnail coded using JPEG
+ * 0x11: Thumbnail stored using 1 byte/pixel
+ * 0x13: Thumbnail stored using 3 bytes/pixel
+ */
+ seek_relative(fp, length - 1); /* Thunbnail image */
+
+ /* Ignore */
+
+ return length;
+}
+
+static size_t
+read_APP1_XMP (struct JPEG_info *j_info, FILE *fp, size_t length)
+{
+ struct JPEG_APPn_XMP *app_data;
+
+ app_data = NEW(1, struct JPEG_APPn_XMP);
+ app_data->length = length;
+ app_data->packet = NEW(app_data->length, unsigned char);
+ fread(app_data->packet, 1, app_data->length, fp);
+
+ add_APPn_marker(j_info, JM_APP1, JS_APPn_XMP, app_data);
+
+ return length;
+}
+
+static size_t
+read_APP2_ICC (struct JPEG_info *j_info, FILE *fp, size_t length)
+{
+ struct JPEG_APPn_ICC *app_data;
+
+ app_data = NEW(1, struct JPEG_APPn_ICC);
+ app_data->seq_id = get_unsigned_byte(fp); /* Starting at 1 */
+ app_data->num_chunks = get_unsigned_byte(fp);
+ app_data->length = length - 2;
+ app_data->chunk = NEW(app_data->length, unsigned char);
+ fread(app_data->chunk, 1, app_data->length, fp);
+
+ add_APPn_marker(j_info, JM_APP2, JS_APPn_ICC, app_data);
+
+ return length;
+}
+
+static int
+JPEG_copy_stream (struct JPEG_info *j_info, pdf_obj *stream, FILE *fp)
+{
+ JPEG_marker marker;
+ int length;
+ int found_SOFn, count;
+
+#define SKIP_CHUNK(j,c) ((j)->skipbits[(c) / 8] & (1 << (7 - (c) % 8)))
+#define COPY_CHUNK(f,s,l) while ((l) > 0) { \
+ int nb_read = fread(work_buffer, sizeof(char), MIN((l), WORK_BUFFER_SIZE), (f)); \
+ if (nb_read > 0) \
+ pdf_add_stream((s), work_buffer, nb_read); \
+ (l) -= nb_read; \
+}
+ rewind(fp);
+ count = 0;
+ found_SOFn = 0;
+ while (!found_SOFn && count < MAX_COUNT &&
+ (marker = JPEG_get_marker(fp)) != (JPEG_marker) - 1) {
+ if ( marker == JM_SOI ||
+ (marker >= JM_RST0 && marker <= JM_RST7)) {
+ work_buffer[0] = (char) 0xff;
+ work_buffer[1] = (char) marker;
+ pdf_add_stream(stream, work_buffer, 2);
+ } else {
+ length = get_unsigned_pair(fp) - 2;
+ switch (marker) {
+ case JM_SOF0: case JM_SOF1: case JM_SOF2: case JM_SOF3:
+ case JM_SOF5: case JM_SOF6: case JM_SOF7: case JM_SOF9:
+ case JM_SOF10: case JM_SOF11: case JM_SOF13: case JM_SOF14:
+ case JM_SOF15:
+ work_buffer[0] = (char) 0xff;
+ work_buffer[1] = (char) marker;
+ work_buffer[2] = ((length + 2) >> 8) & 0xff;
+ work_buffer[3] = (length + 2) & 0xff;
+ pdf_add_stream(stream, work_buffer, 4);
+ COPY_CHUNK(fp, stream, length);
+ found_SOFn = 1;
+ break;
+ default:
+ if (SKIP_CHUNK(j_info, count)) {
+ seek_relative(fp, length);
+ } else {
+ work_buffer[0] = (char) 0xff;
+ work_buffer[1] = (char) marker;
+ work_buffer[2] = ((length + 2) >> 8) & 0xff;
+ work_buffer[3] = (length + 2) & 0xff;
+ pdf_add_stream(stream, work_buffer, 4);
+ COPY_CHUNK(fp, stream, length);
+ }
+ }
+ }
+ count++;
+ }
+ while ((length = fread(work_buffer,
+ sizeof(char), WORK_BUFFER_SIZE, fp)) > 0) {
+ pdf_add_stream(stream, work_buffer, length);
+ }
+
+ return (found_SOFn ? 0 : -1);
+}
+
+/* Adobe Technical Note #5116 "Supporting the DCT Filters in PostScript Level 2"
+ * 6. "DCTDecode Filter Summary"
+ * ... APPn (application-specific) markers are skipped over harmlessly except
+ * for the Adobe reserved marker described later.
+ *
+ */
+#define SET_SKIP(j,c) if ((c) < MAX_COUNT) { \
+ (j)->skipbits[(c) / 8] |= (1 << (7 - ((c) % 8))); \
+}
+static int
+JPEG_scan_file (struct JPEG_info *j_info, FILE *fp)
+{
+ JPEG_marker marker;
+ int found_SOFn, count;
+ char app_sig[128];
+
+ rewind(fp);
+ count = 0;
+ found_SOFn = 0;
+ while (!found_SOFn &&
+ (marker = JPEG_get_marker(fp)) != (JPEG_marker) -1) {
+ if ( marker != JM_SOI &&
+ (marker < JM_RST0 || marker > JM_RST7)) {
+ int length = get_unsigned_pair(fp) - 2;
+ switch (marker) {
+ case JM_SOF0: case JM_SOF1: case JM_SOF2: case JM_SOF3:
+ case JM_SOF5: case JM_SOF6: case JM_SOF7: case JM_SOF9:
+ case JM_SOF10: case JM_SOF11: case JM_SOF13: case JM_SOF14:
+ case JM_SOF15:
+ j_info->bits_per_component = get_unsigned_byte(fp);
+ j_info->height = get_unsigned_pair(fp);
+ j_info->width = get_unsigned_pair(fp);
+ j_info->num_components = get_unsigned_byte(fp);
+ found_SOFn = 1;
+ break;
+ case JM_APP0:
+ if (length > 5) {
+ if (fread(app_sig, sizeof(char), 5, fp) != 5)
+ return -1;
+ length -= 5;
+ if (!memcmp(app_sig, "JFIF\000", 5)) {
+ /* APP0 JFIF marker preserved */
+ j_info->flags |= HAVE_APPn_JFIF;
+ length -= read_APP0_JFIF(j_info, fp);
+ } else if (!memcmp(app_sig, "JFXX", 5)) {
+ length -= read_APP0_JFXX(fp, length);
+ SET_SKIP(j_info, count);
+ } else {
+ SET_SKIP(j_info, count);
+ }
+ } else {
+ SET_SKIP(j_info, count);
+ }
+ seek_relative(fp, length);
+ break;
+ case JM_APP1:
+ if (length > 5) {
+ if (fread(app_sig, sizeof(char), 5, fp) != 5)
+ return -1;
+ length -= 5;
+ if (!memcmp(app_sig, "Exif\000", 5)) {
+ /* APP1 Exif marker preserved */
+ j_info->flags |= HAVE_APPn_Exif;
+ length -= read_APP1_Exif(j_info, fp, length);
+ } else if (!memcmp(app_sig, "http:", 5) && length > 24) {
+ if (fread(app_sig, sizeof(char), 24, fp) != 24)
+ return -1;
+ length -= 24;
+ if (!memcmp(app_sig, "//ns.adobe.com/xap/1.0/\000", 24)) {
+ j_info->flags |= HAVE_APPn_XMP;
+ length -= read_APP1_XMP(j_info, fp, length);
+ }
+ SET_SKIP(j_info, count);
+ }
+ } else {
+ SET_SKIP(j_info, count);
+ }
+ seek_relative(fp, length);
+ break;
+ case JM_APP2:
+ if (length >= 14) {
+ if (fread(app_sig, sizeof(char), 12, fp) != 12)
+ return -1;
+ length -= 12;
+ if (!memcmp(app_sig, "ICC_PROFILE\000", 12)) {
+ j_info->flags |= HAVE_APPn_ICC;
+ length -= read_APP2_ICC(j_info, fp, length);
+ }
+ }
+ seek_relative(fp, length);
+ SET_SKIP(j_info, count);
+ break;
+ case JM_APP14:
+ if (length > 5) {
+ if (fread(app_sig, sizeof(char), 5, fp) != 5)
+ return -1;
+ length -= 5;
+ if (!memcmp(app_sig, "Adobe", 5)) {
+ /* APP14 Adobe marker preserved */
+ j_info->flags |= HAVE_APPn_ADOBE;
+ length -= read_APP14_Adobe(j_info, fp);
+ } else {
+ SET_SKIP(j_info, count);
+ }
+ } else {
+ SET_SKIP(j_info, count);
+ }
+ seek_relative(fp, length);
+ break;
+ default:
+ seek_relative(fp, length);
+ if (marker >= JM_APP0 && marker <= JM_APP15) {
+ SET_SKIP(j_info, count);
+ }
+ break;
+ }
+ }
+ count++;
+ }
+
+ return (found_SOFn ? 0 : -1);
+}
+
+int
+jpeg_get_bbox (FILE *fp, int *width, int *height, double *xdensity, double *ydensity)
+{
+ struct JPEG_info j_info;
+
+ JPEG_info_init(&j_info);
+
+ if (JPEG_scan_file(&j_info, fp) < 0) {
+ WARN("%s: Not a JPEG file?", JPEG_DEBUG_STR);
+ JPEG_info_clear(&j_info);
+ return -1;
+ }
+
+ *width = j_info.width;
+ *height = j_info.height;
+
+ jpeg_get_density(&j_info, xdensity, ydensity);
+
+ JPEG_info_clear(&j_info);
+
+ return 0;
+}