1 /* Eye Of MATE -- PNG Metadata Reader
2 *
3 * Copyright (C) 2008 The Free Software Foundation
4 *
5 * Author: Felix Riemann <friemann@svn.gnome.org>
6 *
7 * Based on the old EomMetadataReader code.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <math.h>
29 #include <string.h>
30 #include <zlib.h>
31 #include <gdk/gdkx.h>
32
33 #include "eom-metadata-reader.h"
34 #include "eom-metadata-reader-png.h"
35 #include "eom-debug.h"
36
37 typedef enum {
38 EMR_READ_MAGIC,
39 EMR_READ_SIZE_HIGH_HIGH_BYTE,
40 EMR_READ_SIZE_HIGH_LOW_BYTE,
41 EMR_READ_SIZE_LOW_HIGH_BYTE,
42 EMR_READ_SIZE_LOW_LOW_BYTE,
43 EMR_READ_CHUNK_NAME,
44 EMR_SKIP_BYTES,
45 EMR_CHECK_CRC,
46 EMR_SKIP_CRC,
47 EMR_READ_XMP_ITXT,
48 EMR_READ_ICCP,
49 EMR_READ_SRGB,
50 EMR_READ_CHRM,
51 EMR_READ_GAMA,
52 EMR_FINISHED
53 } EomMetadataReaderPngState;
54
55 #if 0
56 #define IS_FINISHED(priv) (priv->icc_chunk != NULL && \
57 priv->xmp_chunk != NULL)
58 #endif
59
60 struct _EomMetadataReaderPngPrivate {
61 EomMetadataReaderPngState state;
62
63 /* data fields */
64 guint32 icc_len;
65 gpointer icc_chunk;
66
67 gpointer xmp_chunk;
68 guint32 xmp_len;
69
70 guint32 sRGB_len;
71 gpointer sRGB_chunk;
72
73 gpointer cHRM_chunk;
74 guint32 cHRM_len;
75
76 guint32 gAMA_len;
77 gpointer gAMA_chunk;
78
79 /* management fields */
80 gsize size;
81 gsize bytes_read;
82 guint sub_step;
83 guchar chunk_name[4];
84 gpointer *crc_chunk;
85 guint32 *crc_len;
86 guint32 target_crc;
87 gboolean hasIHDR;
88 };
89
90 static void
91 eom_metadata_reader_png_init_emr_iface (gpointer g_iface, gpointer iface_data);
92
G_DEFINE_TYPE_WITH_CODE(EomMetadataReaderPng,eom_metadata_reader_png,G_TYPE_OBJECT,G_IMPLEMENT_INTERFACE (EOM_TYPE_METADATA_READER,eom_metadata_reader_png_init_emr_iface)G_ADD_PRIVATE (EomMetadataReaderPng))93 G_DEFINE_TYPE_WITH_CODE (EomMetadataReaderPng, eom_metadata_reader_png,
94 G_TYPE_OBJECT,
95 G_IMPLEMENT_INTERFACE (EOM_TYPE_METADATA_READER,
96 eom_metadata_reader_png_init_emr_iface) \
97 G_ADD_PRIVATE(EomMetadataReaderPng))
98
99 static void
100 eom_metadata_reader_png_dispose (GObject *object)
101 {
102 EomMetadataReaderPng *emr = EOM_METADATA_READER_PNG (object);
103 EomMetadataReaderPngPrivate *priv = emr->priv;
104
105 g_free (priv->xmp_chunk);
106 priv->xmp_chunk = NULL;
107
108 g_free (priv->icc_chunk);
109 priv->icc_chunk = NULL;
110
111 g_free (priv->sRGB_chunk);
112 priv->sRGB_chunk = NULL;
113
114 g_free (priv->cHRM_chunk);
115 priv->cHRM_chunk = NULL;
116
117 g_free (priv->gAMA_chunk);
118 priv->gAMA_chunk = NULL;
119
120 G_OBJECT_CLASS (eom_metadata_reader_png_parent_class)->dispose (object);
121 }
122
123 static void
eom_metadata_reader_png_init(EomMetadataReaderPng * emr)124 eom_metadata_reader_png_init (EomMetadataReaderPng *emr)
125 {
126 EomMetadataReaderPngPrivate *priv;
127
128 priv = emr->priv = eom_metadata_reader_png_get_instance_private (emr);
129 priv->icc_chunk = NULL;
130 priv->icc_len = 0;
131 priv->xmp_chunk = NULL;
132 priv->xmp_len = 0;
133 priv->sRGB_chunk = NULL;
134 priv->sRGB_len = 0;
135 priv->cHRM_chunk = NULL;
136 priv->cHRM_len = 0;
137 priv->gAMA_chunk = NULL;
138 priv->gAMA_len = 0;
139
140 priv->sub_step = 0;
141 priv->state = EMR_READ_MAGIC;
142 priv->hasIHDR = FALSE;
143 }
144
145 static void
eom_metadata_reader_png_class_init(EomMetadataReaderPngClass * klass)146 eom_metadata_reader_png_class_init (EomMetadataReaderPngClass *klass)
147 {
148 GObjectClass *object_class = (GObjectClass*) klass;
149
150 object_class->dispose = eom_metadata_reader_png_dispose;
151 }
152
153 static gboolean
eom_metadata_reader_png_finished(EomMetadataReaderPng * emr)154 eom_metadata_reader_png_finished (EomMetadataReaderPng *emr)
155 {
156 g_return_val_if_fail (EOM_IS_METADATA_READER_PNG (emr), TRUE);
157
158 return (emr->priv->state == EMR_FINISHED);
159 }
160
161
162 static void
eom_metadata_reader_png_get_next_block(EomMetadataReaderPngPrivate * priv,guchar * chunk,int * i,const guchar * buf,int len,EomMetadataReaderPngState state)163 eom_metadata_reader_png_get_next_block (EomMetadataReaderPngPrivate* priv,
164 guchar *chunk,
165 int* i,
166 const guchar *buf,
167 int len,
168 EomMetadataReaderPngState state)
169 {
170 if (*i + priv->size < len) {
171 /* read data in one block */
172 memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], priv->size);
173 priv->state = EMR_CHECK_CRC;
174 *i = *i + priv->size - 1; /* the for-loop consumes the other byte */
175 priv->size = 0;
176 } else {
177 int chunk_len = len - *i;
178 memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], chunk_len);
179 priv->bytes_read += chunk_len; /* bytes already read */
180 priv->size = (*i + priv->size) - len; /* remaining data to read */
181 *i = len - 1;
182 priv->state = state;
183 }
184 }
185
186 static void
eom_metadata_reader_png_consume(EomMetadataReaderPng * emr,const guchar * buf,guint len)187 eom_metadata_reader_png_consume (EomMetadataReaderPng *emr, const guchar *buf, guint len)
188 {
189 EomMetadataReaderPngPrivate *priv;
190 int i;
191 guint32 chunk_crc;
192 static const gchar PNGMAGIC[8] = "\x89PNG\x0D\x0A\x1a\x0A";
193
194 g_return_if_fail (EOM_IS_METADATA_READER_PNG (emr));
195
196 priv = emr->priv;
197
198 if (priv->state == EMR_FINISHED) return;
199
200 for (i = 0; (i < len) && (priv->state != EMR_FINISHED); i++) {
201
202 switch (priv->state) {
203 case EMR_READ_MAGIC:
204 /* Check PNG magic string */
205 if (priv->sub_step < 8 &&
206 (gchar)buf[i] == PNGMAGIC[priv->sub_step]) {
207 if (priv->sub_step == 7)
208 priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE;
209 priv->sub_step++;
210 } else {
211 priv->state = EMR_FINISHED;
212 }
213 break;
214 case EMR_READ_SIZE_HIGH_HIGH_BYTE:
215 /* Read the high byte of the size's high word */
216 priv->size |= (buf[i] & 0xFF) << 24;
217 priv->state = EMR_READ_SIZE_HIGH_LOW_BYTE;
218 break;
219 case EMR_READ_SIZE_HIGH_LOW_BYTE:
220 /* Read the low byte of the size's high word */
221 priv->size |= (buf[i] & 0xFF) << 16;
222 priv->state = EMR_READ_SIZE_LOW_HIGH_BYTE;
223 break;
224 case EMR_READ_SIZE_LOW_HIGH_BYTE:
225 /* Read the high byte of the size's low word */
226 priv->size |= (buf [i] & 0xff) << 8;
227 priv->state = EMR_READ_SIZE_LOW_LOW_BYTE;
228 break;
229 case EMR_READ_SIZE_LOW_LOW_BYTE:
230 /* Read the high byte of the size's low word */
231 priv->size |= (buf [i] & 0xff);
232 /* The maximum chunk length is 2^31-1 */
233 if (G_LIKELY (priv->size <= (guint32) 0x7fffffff)) {
234 priv->state = EMR_READ_CHUNK_NAME;
235 /* Make sure sub_step is 0 before next step */
236 priv->sub_step = 0;
237 } else {
238 priv->state = EMR_FINISHED;
239 eom_debug_message (DEBUG_IMAGE_DATA,
240 "chunk size larger than "
241 "2^31-1; stopping parser");
242 }
243
244 break;
245 case EMR_READ_CHUNK_NAME:
246 /* Read the 4-byte chunk name */
247 if (priv->sub_step > 3)
248 g_assert_not_reached ();
249
250 priv->chunk_name[priv->sub_step] = buf[i];
251
252 if (priv->sub_step++ != 3)
253 break;
254
255 if (G_UNLIKELY (!priv->hasIHDR)) {
256 /* IHDR should be the first chunk in a PNG */
257 if (priv->size == 13
258 && memcmp (priv->chunk_name, "IHDR", 4) == 0){
259 priv->hasIHDR = TRUE;
260 } else {
261 /* Stop parsing if it is not */
262 priv->state = EMR_FINISHED;
263 }
264 }
265
266 /* Try to identify the chunk by its name.
267 * Already do some sanity checks where possible */
268 if (memcmp (priv->chunk_name, "iTXt", 4) == 0 &&
269 priv->size > (22 + 54) && priv->xmp_chunk == NULL) {
270 priv->state = EMR_READ_XMP_ITXT;
271 } else if (memcmp (priv->chunk_name, "iCCP", 4) == 0 &&
272 priv->icc_chunk == NULL) {
273 priv->state = EMR_READ_ICCP;
274 } else if (memcmp (priv->chunk_name, "sRGB", 4) == 0 &&
275 priv->sRGB_chunk == NULL && priv->size == 1) {
276 priv->state = EMR_READ_SRGB;
277 } else if (memcmp (priv->chunk_name, "cHRM", 4) == 0 &&
278 priv->cHRM_chunk == NULL && priv->size == 32) {
279 priv->state = EMR_READ_CHRM;
280 } else if (memcmp (priv->chunk_name, "gAMA", 4) == 0 &&
281 priv->gAMA_chunk == NULL && priv->size == 4) {
282 priv->state = EMR_READ_GAMA;
283 } else if (memcmp (priv->chunk_name, "IEND", 4) == 0) {
284 priv->state = EMR_FINISHED;
285 } else {
286 /* Skip chunk + 4-byte CRC32 value */
287 priv->size += 4;
288 priv->state = EMR_SKIP_BYTES;
289 }
290 priv->sub_step = 0;
291 break;
292 case EMR_SKIP_CRC:
293 /* Skip the 4-byte CRC32 value following every chunk */
294 priv->size = 4;
295 case EMR_SKIP_BYTES:
296 /* Skip chunk and start reading the size of the next one */
297 eom_debug_message (DEBUG_IMAGE_DATA,
298 "Skip bytes: %" G_GSIZE_FORMAT,
299 priv->size);
300
301 if (i + priv->size < len) {
302 i = i + priv->size - 1; /* the for-loop consumes the other byte */
303 priv->size = 0;
304 priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE;
305 }
306 else {
307 priv->size = (i + priv->size) - len;
308 i = len - 1;
309 }
310 break;
311 case EMR_CHECK_CRC:
312 /* Read the chunks CRC32 value from the file,... */
313 if (priv->sub_step == 0)
314 priv->target_crc = 0;
315
316 priv->target_crc |= buf[i] << ((3 - priv->sub_step) * 8);
317
318 if (priv->sub_step++ != 3)
319 break;
320
321 /* ...generate the chunks CRC32,... */
322 chunk_crc = crc32 (crc32 (0L, Z_NULL, 0), priv->chunk_name, 4);
323 chunk_crc = crc32 (chunk_crc, *priv->crc_chunk, *priv->crc_len);
324
325 eom_debug_message (DEBUG_IMAGE_DATA, "Checking CRC: Chunk: 0x%X - Target: 0x%X", chunk_crc, priv->target_crc);
326
327 /* ...and check if they match. If they don't throw
328 * the chunk away and stop parsing. */
329 if (priv->target_crc == chunk_crc) {
330 priv->state = EMR_READ_SIZE_HIGH_HIGH_BYTE;
331 } else {
332 g_free (*priv->crc_chunk);
333 *priv->crc_chunk = NULL;
334 *priv->crc_len = 0;
335 /* Stop parsing for security reasons */
336 priv->state = EMR_FINISHED;
337 }
338 priv->sub_step = 0;
339 break;
340 case EMR_READ_XMP_ITXT:
341 /* Extract an iTXt chunk possibly containing
342 * an XMP packet */
343 eom_debug_message (DEBUG_IMAGE_DATA,
344 "Read XMP Chunk - size: %"
345 G_GSIZE_FORMAT, priv->size);
346
347 if (priv->xmp_chunk == NULL) {
348 priv->xmp_chunk = g_new0 (guchar, priv->size);
349 priv->xmp_len = priv->size;
350 priv->crc_len = &priv->xmp_len;
351 priv->bytes_read = 0;
352 priv->crc_chunk = &priv->xmp_chunk;
353 }
354 eom_metadata_reader_png_get_next_block (priv,
355 priv->xmp_chunk,
356 &i, buf, len,
357 EMR_READ_XMP_ITXT);
358
359 if (priv->state == EMR_CHECK_CRC) {
360 /* Check if it is actually an XMP chunk.
361 * Throw it away if not.
362 * The check has 4 extra \0's to check
363 * if the chunk is configured correctly. */
364 if (memcmp (priv->xmp_chunk, "XML:com.adobe.xmp\0\0\0\0\0", 22) != 0) {
365 priv->state = EMR_SKIP_CRC;
366 g_free (priv->xmp_chunk);
367 priv->xmp_chunk = NULL;
368 priv->xmp_len = 0;
369 }
370 }
371 break;
372 case EMR_READ_ICCP:
373 /* Extract an iCCP chunk containing a
374 * deflated ICC profile. */
375 eom_debug_message (DEBUG_IMAGE_DATA,
376 "Read ICC Chunk - size: %"
377 G_GSIZE_FORMAT, priv->size);
378
379 if (priv->icc_chunk == NULL) {
380 priv->icc_chunk = g_new0 (guchar, priv->size);
381 priv->icc_len = priv->size;
382 priv->crc_len = &priv->icc_len;
383 priv->bytes_read = 0;
384 priv->crc_chunk = &priv->icc_chunk;
385 }
386
387 eom_metadata_reader_png_get_next_block (priv,
388 priv->icc_chunk,
389 &i, buf, len,
390 EMR_READ_ICCP);
391 break;
392 case EMR_READ_SRGB:
393 /* Extract the sRGB chunk. Marks the image data as
394 * being in sRGB colorspace. */
395 eom_debug_message (DEBUG_IMAGE_DATA,
396 "Read sRGB Chunk - value: %u", *(buf+i));
397
398 if (priv->sRGB_chunk == NULL) {
399 priv->sRGB_chunk = g_new0 (guchar, priv->size);
400 priv->sRGB_len = priv->size;
401 priv->crc_len = &priv->sRGB_len;
402 priv->bytes_read = 0;
403 priv->crc_chunk = &priv->sRGB_chunk;
404 }
405
406 eom_metadata_reader_png_get_next_block (priv,
407 priv->sRGB_chunk,
408 &i, buf, len,
409 EMR_READ_SRGB);
410 break;
411 case EMR_READ_CHRM:
412 /* Extract the cHRM chunk. Contains the coordinates of
413 * the image's whitepoint and primary chromacities. */
414 eom_debug_message (DEBUG_IMAGE_DATA,
415 "Read cHRM Chunk - size: %"
416 G_GSIZE_FORMAT, priv->size);
417
418 if (priv->cHRM_chunk == NULL) {
419 priv->cHRM_chunk = g_new0 (guchar, priv->size);
420 priv->cHRM_len = priv->size;
421 priv->crc_len = &priv->cHRM_len;
422 priv->bytes_read = 0;
423 priv->crc_chunk = &priv->cHRM_chunk;
424 }
425
426 eom_metadata_reader_png_get_next_block (priv,
427 priv->cHRM_chunk,
428 &i, buf, len,
429 EMR_READ_ICCP);
430 break;
431 case EMR_READ_GAMA:
432 /* Extract the gAMA chunk containing the
433 * image's gamma value */
434 eom_debug_message (DEBUG_IMAGE_DATA,
435 "Read gAMA-Chunk - size: %"
436 G_GSIZE_FORMAT, priv->size);
437
438 if (priv->gAMA_chunk == NULL) {
439 priv->gAMA_chunk = g_new0 (guchar, priv->size);
440 priv->gAMA_len = priv->size;
441 priv->crc_len = &priv->gAMA_len;
442 priv->bytes_read = 0;
443 priv->crc_chunk = &priv->gAMA_chunk;
444 }
445
446 eom_metadata_reader_png_get_next_block (priv,
447 priv->gAMA_chunk,
448 &i, buf, len,
449 EMR_READ_ICCP);
450 break;
451 default:
452 g_assert_not_reached ();
453 }
454 }
455 }
456
457 #ifdef HAVE_EXEMPI
458
459 /* skip the chunk ID */
460 #define EOM_XMP_OFFSET (22)
461
462 static gpointer
eom_metadata_reader_png_get_xmp_data(EomMetadataReaderPng * emr)463 eom_metadata_reader_png_get_xmp_data (EomMetadataReaderPng *emr )
464 {
465 EomMetadataReaderPngPrivate *priv;
466 XmpPtr xmp = NULL;
467
468 g_return_val_if_fail (EOM_IS_METADATA_READER_PNG (emr), NULL);
469
470 priv = emr->priv;
471
472 if (priv->xmp_chunk != NULL) {
473 xmp = xmp_new (priv->xmp_chunk+EOM_XMP_OFFSET,
474 priv->xmp_len-EOM_XMP_OFFSET);
475 }
476
477 return (gpointer) xmp;
478 }
479 #endif
480
481 #if defined(HAVE_LCMS) && defined(GDK_WINDOWING_X11)
482
483 #define EXTRACT_DOUBLE_UINT_BLOCK_OFFSET(chunk,offset,divider) \
484 (double)(GUINT32_FROM_BE(*((guint32*)((chunk)+((offset)*4))))/(double)(divider))
485
486 /* This is the amount of memory the inflate output buffer gets increased by
487 * while decompressing the ICC profile */
488 #define EOM_ICC_INFLATE_BUFFER_STEP 1024
489
490 /* I haven't seen ICC profiles larger than 1MB yet.
491 * A maximum output buffer of 5MB should be enough. */
492 #define EOM_ICC_INFLATE_BUFFER_LIMIT (1024*1024*5)
493
494 /* Apparently an sRGB profile saved in cHRM and gAMA chunks does not compute
495 * a profile that exactly matches the built-in sRGB profile and thus could
496 * cause a slight color deviation. Try catching this case to allow fallback
497 * to the built-in profile instead.
498 */
499 static gboolean
_chrm_matches_srgb(const cmsCIExyY * whitepoint,const cmsCIExyYTRIPLE * primaries,gdouble gammaValue)500 _chrm_matches_srgb(const cmsCIExyY *whitepoint,
501 const cmsCIExyYTRIPLE *primaries,
502 gdouble gammaValue)
503 {
504 /* PNGs gAMA value for 2.2 is only accurate to the 4th decimal point */
505 #define DOUBLE_EQUAL_MAX_DIFF 1e-4
506 #define DOUBLE_EQUAL(a,b) (fabs (a - b) < DOUBLE_EQUAL_MAX_DIFF)
507
508 return (DOUBLE_EQUAL(gammaValue, 2.2)
509 && DOUBLE_EQUAL(whitepoint->x, 0.3127)
510 && DOUBLE_EQUAL(whitepoint->y, 0.329)
511 && DOUBLE_EQUAL(primaries->Red.x, 0.64)
512 && DOUBLE_EQUAL(primaries->Red.y, 0.33)
513 && DOUBLE_EQUAL(primaries->Green.x, 0.3)
514 && DOUBLE_EQUAL(primaries->Green.y, 0.6)
515 && DOUBLE_EQUAL(primaries->Blue.x, 0.15)
516 && DOUBLE_EQUAL(primaries->Blue.y, 0.06));
517 }
518
519 static gpointer
eom_metadata_reader_png_get_icc_profile(EomMetadataReaderPng * emr)520 eom_metadata_reader_png_get_icc_profile (EomMetadataReaderPng *emr)
521 {
522 EomMetadataReaderPngPrivate *priv;
523 cmsHPROFILE profile = NULL;
524
525 g_return_val_if_fail (EOM_IS_METADATA_READER_PNG (emr), NULL);
526
527 priv = emr->priv;
528
529 if (priv->icc_chunk) {
530 gpointer outbuf;
531 gsize offset = 0;
532 z_stream zstr;
533 int z_ret;
534
535 /* Use default allocation functions */
536 zstr.zalloc = Z_NULL;
537 zstr.zfree = Z_NULL;
538 zstr.opaque = Z_NULL;
539
540 /* Skip the name of the ICC profile */
541 while (*((guchar*)priv->icc_chunk+offset) != '\0')
542 offset++;
543 /* Ensure the compression method (deflate) */
544 if (*((guchar*)priv->icc_chunk+(++offset)) != '\0')
545 return NULL;
546 ++offset; //offset now points to the start of the deflated data
547
548 /* Prepare the zlib data structure for decompression */
549 zstr.next_in = priv->icc_chunk + offset;
550 zstr.avail_in = priv->icc_len - offset;
551 if (inflateInit (&zstr) != Z_OK) {
552 return NULL;
553 }
554
555 /* Prepare output buffer and make zlib aware of it */
556 outbuf = g_malloc (EOM_ICC_INFLATE_BUFFER_STEP);
557 zstr.next_out = outbuf;
558 zstr.avail_out = EOM_ICC_INFLATE_BUFFER_STEP;
559
560 do {
561 if (zstr.avail_out == 0) {
562 /* The output buffer was not large enough to
563 * hold all the decompressed data. Increase its
564 * size and continue decompression. */
565 gsize new_size = zstr.total_out + EOM_ICC_INFLATE_BUFFER_STEP;
566
567 if (G_UNLIKELY (new_size > EOM_ICC_INFLATE_BUFFER_LIMIT)) {
568 /* Enforce a memory limit for the output
569 * buffer to avoid possible OOM cases */
570 inflateEnd (&zstr);
571 g_free (outbuf);
572 eom_debug_message (DEBUG_IMAGE_DATA, "ICC profile is too large. Ignoring.");
573 return NULL;
574 }
575 outbuf = g_realloc(outbuf, new_size);
576 zstr.avail_out = EOM_ICC_INFLATE_BUFFER_STEP;
577 zstr.next_out = outbuf + zstr.total_out;
578 }
579 z_ret = inflate (&zstr, Z_SYNC_FLUSH);
580 } while (z_ret == Z_OK);
581
582 if (G_UNLIKELY (z_ret != Z_STREAM_END)) {
583 eom_debug_message (DEBUG_IMAGE_DATA, "Error while inflating ICC profile: %s (%d)", zstr.msg, z_ret);
584 inflateEnd (&zstr);
585 g_free (outbuf);
586 return NULL;
587 }
588
589 if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
590 profile = cmsOpenProfileFromMem(outbuf, zstr.total_out);
591 }
592 inflateEnd (&zstr);
593 g_free (outbuf);
594
595 eom_debug_message (DEBUG_LCMS, "PNG has %s ICC profile", profile ? "valid" : "invalid");
596 }
597
598 if (!profile && priv->sRGB_chunk) {
599 eom_debug_message (DEBUG_LCMS, "PNG is sRGB");
600 /* If the file has an sRGB chunk the image data is in the sRGB
601 * colorspace. lcms has a built-in sRGB profile. */
602
603 if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
604 profile = cmsCreate_sRGBProfile ();
605 }
606 }
607
608 if (!profile && priv->cHRM_chunk && priv->gAMA_chunk) {
609 cmsCIExyY whitepoint;
610 cmsCIExyYTRIPLE primaries;
611 cmsToneCurve *gamma[3];
612 double gammaValue;
613
614 /* This uglyness extracts the chromacity and whitepoint values
615 * from a PNG's cHRM chunk. These can be accurate up to the
616 * 5th decimal point.
617 * They are saved as integer values multiplied by 100000. */
618
619 eom_debug_message (DEBUG_LCMS, "Trying to calculate color profile");
620
621 whitepoint.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 0, 100000);
622 whitepoint.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 1, 100000);
623
624 primaries.Red.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 2, 100000);
625 primaries.Red.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 3, 100000);
626 primaries.Green.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 4, 100000);
627 primaries.Green.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 5, 100000);
628 primaries.Blue.x = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 6, 100000);
629 primaries.Blue.y = EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->cHRM_chunk, 7, 100000);
630
631 whitepoint.Y = primaries.Red.Y = primaries.Green.Y = primaries.Blue.Y = 1.0;
632
633 gammaValue = (double) 1.0/EXTRACT_DOUBLE_UINT_BLOCK_OFFSET (priv->gAMA_chunk, 0, 100000);
634 eom_debug_message (DEBUG_LCMS, "Gamma %.5lf", gammaValue);
635
636 /* Catch SRGB in cHRM/gAMA chunks and use accurate built-in
637 * profile instead of computing one that "gets close". */
638 if(_chrm_matches_srgb (&whitepoint, &primaries, gammaValue)) {
639 eom_debug_message (DEBUG_LCMS, "gAMA and cHRM match sRGB");
640 if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
641 profile = cmsCreate_sRGBProfile ();
642 }
643 } else {
644 gamma[0] = gamma[1] = gamma[2] =
645 cmsBuildGamma (NULL, gammaValue);
646 if (GDK_IS_X11_DISPLAY (gdk_display_get_default ())) {
647 profile = cmsCreateRGBProfile (&whitepoint, &primaries,
648 gamma);
649 cmsFreeToneCurve(gamma[0]);
650 }
651 }
652 }
653
654 return profile;
655 }
656 #endif
657
658 static void
eom_metadata_reader_png_init_emr_iface(gpointer g_iface,gpointer iface_data)659 eom_metadata_reader_png_init_emr_iface (gpointer g_iface, gpointer iface_data)
660 {
661 EomMetadataReaderInterface *iface;
662
663 iface = (EomMetadataReaderInterface*) g_iface;
664
665 iface->consume =
666 (void (*) (EomMetadataReader *self, const guchar *buf, guint len))
667 eom_metadata_reader_png_consume;
668 iface->finished =
669 (gboolean (*) (EomMetadataReader *self))
670 eom_metadata_reader_png_finished;
671 #if defined(HAVE_LCMS) && defined(GDK_WINDOWING_X11)
672 iface->get_icc_profile =
673 (cmsHPROFILE (*) (EomMetadataReader *self))
674 eom_metadata_reader_png_get_icc_profile;
675 #endif
676 #ifdef HAVE_EXEMPI
677 iface->get_xmp_ptr =
678 (gpointer (*) (EomMetadataReader *self))
679 eom_metadata_reader_png_get_xmp_data;
680 #endif
681 }
682