1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2
3 /*
4 * GThumb
5 *
6 * Copyright (C) 2011-2015 Free Software Foundation, Inc.
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22
23 #include <config.h>
24 #include "jpeg-info.h"
25
26
27 void
_jpeg_info_data_init(JpegInfoData * data)28 _jpeg_info_data_init (JpegInfoData *data)
29 {
30 data->valid = _JPEG_INFO_NONE;
31 data->width = 0;
32 data->height = 0;
33 data->orientation = GTH_TRANSFORM_NONE;
34 data->icc_data = NULL;
35 data->icc_data_size = 0;
36 }
37
38
39 void
_jpeg_info_data_dispose(JpegInfoData * data)40 _jpeg_info_data_dispose (JpegInfoData *data)
41 {
42 if (data->valid & _JPEG_INFO_ICC_PROFILE)
43 g_free (data->icc_data);
44 }
45
46
47 static guchar
_g_input_stream_read_byte(GInputStream * stream,GCancellable * cancellable,GError ** error)48 _g_input_stream_read_byte (GInputStream *stream,
49 GCancellable *cancellable,
50 GError **error)
51 {
52 guchar v;
53 return (g_input_stream_read (stream, &v, 1, cancellable, error) > 0) ? v : 0;
54 }
55
56
57 static guchar
_jpeg_read_segment_marker(GInputStream * stream,GCancellable * cancellable,GError ** error)58 _jpeg_read_segment_marker (GInputStream *stream,
59 GCancellable *cancellable,
60 GError **error)
61 {
62 guchar marker_id;
63
64 if (_g_input_stream_read_byte (stream, cancellable, error) != 0xff)
65 return 0x00;
66
67 while ((marker_id = _g_input_stream_read_byte (stream, cancellable, error)) == 0xff)
68 /* skip padding */;
69
70 return marker_id;
71 }
72
73
74 static gboolean
_jpeg_skip_segment_data(GInputStream * stream,guchar marker_id,GCancellable * cancellable,GError ** error)75 _jpeg_skip_segment_data (GInputStream *stream,
76 guchar marker_id,
77 GCancellable *cancellable,
78 GError **error)
79 {
80 if (marker_id == 0xd9) /* EOI => end of image */
81 return FALSE;
82 if (marker_id == 0xda) /* SOS => end of header */
83 return FALSE;
84
85 if ((marker_id != 0xd0)
86 && (marker_id != 0xd1)
87 && (marker_id != 0xd2)
88 && (marker_id != 0xd3)
89 && (marker_id != 0xd4)
90 && (marker_id != 0xd5)
91 && (marker_id != 0xd6)
92 && (marker_id != 0xd7)
93 && (marker_id != 0xd8)
94 && (marker_id != 0x01))
95 {
96 guint h, l;
97 guint segment_size;
98
99 /* skip to the next segment */
100
101 h = _g_input_stream_read_byte (stream, cancellable, error);
102 l = _g_input_stream_read_byte (stream, cancellable, error);
103 segment_size = (h << 8) + l;
104
105 if (g_input_stream_skip (stream, segment_size - 2, cancellable, error) < 0)
106 return FALSE;
107 }
108
109 return TRUE;
110 }
111
112
113 static gboolean
_jpeg_exif_tags_from_app1_segment(guchar * in_buffer,gsize app1_segment_size,JpegInfoFlags flags,JpegInfoData * data)114 _jpeg_exif_tags_from_app1_segment (guchar *in_buffer,
115 gsize app1_segment_size,
116 JpegInfoFlags flags,
117 JpegInfoData *data)
118 {
119 int pos;
120 guint length;
121 gboolean big_endian;
122 guchar *exif_data;
123 guint offset, number_of_tags, tagnum;
124 int remaining_tags;
125
126 /* Length includes itself, so must be at least 2 */
127 /* Following Exif data length must be at least 6 */
128
129 length = app1_segment_size;
130 if (length < 6)
131 return FALSE;
132
133 pos = 0;
134
135 /* Read Exif head, check for "Exif" */
136
137 if ((in_buffer[pos++] != 'E')
138 || (in_buffer[pos++] != 'x')
139 || (in_buffer[pos++] != 'i')
140 || (in_buffer[pos++] != 'f')
141 || (in_buffer[pos++] != 0)
142 || (in_buffer[pos++] != 0))
143 {
144 return FALSE;
145 }
146
147 /* Length of an IFD entry */
148
149 if (length < 12)
150 return FALSE;
151
152 exif_data = in_buffer + pos;
153
154 /* Discover byte order */
155
156 if ((exif_data[0] == 0x49) && (exif_data[1] == 0x49))
157 big_endian = FALSE;
158 else if ((exif_data[0] == 0x4D) && (exif_data[1] == 0x4D))
159 big_endian = TRUE;
160 else
161 return FALSE;
162
163 /* Check Tag Mark */
164
165 if (big_endian) {
166 if (exif_data[2] != 0)
167 return FALSE;
168 if (exif_data[3] != 0x2A)
169 return FALSE;
170 }
171 else {
172 if (exif_data[3] != 0)
173 return FALSE;
174 if (exif_data[2] != 0x2A)
175 return FALSE;
176 }
177
178 /* Get first IFD offset (offset to IFD0) */
179
180 if (big_endian) {
181 if (exif_data[4] != 0)
182 return FALSE;
183 if (exif_data[5] != 0)
184 return FALSE;
185 offset = (exif_data[6] << 8) + exif_data[7];
186 }
187 else {
188 if (exif_data[7] != 0)
189 return FALSE;
190 if (exif_data[6] != 0)
191 return FALSE;
192 offset = (exif_data[5] << 8) + exif_data[4];
193 }
194
195 if (offset > length - 2) /* check end of data segment */
196 return FALSE;
197
198 /* Get the number of directory entries contained in this IFD */
199
200 if (big_endian)
201 number_of_tags = (exif_data[offset] << 8) + exif_data[offset+1];
202 else
203 number_of_tags = (exif_data[offset+1] << 8) + exif_data[offset];
204 if (number_of_tags == 0)
205 return FALSE;
206
207 offset += 2;
208
209 /* Search the tags in IFD0 */
210
211 remaining_tags = 0;
212 if (flags & _JPEG_INFO_EXIF_ORIENTATION)
213 remaining_tags += 1;
214 if (flags & _JPEG_INFO_EXIF_COLORIMETRY)
215 remaining_tags += 3;
216 if (flags & _JPEG_INFO_EXIF_COLOR_SPACE)
217 remaining_tags += 1;
218
219 for (;;) {
220 if (offset > length - 12) /* check end of data segment */
221 return FALSE;
222
223 /* Get Tag number */
224
225 if (big_endian)
226 tagnum = (exif_data[offset] << 8) + exif_data[offset+1];
227 else
228 tagnum = (exif_data[offset+1] << 8) + exif_data[offset];
229
230 if ((flags & _JPEG_INFO_EXIF_ORIENTATION) && (tagnum == 0x0112)) { /* Orientation */
231 int orientation;
232
233 if (big_endian) {
234 if (exif_data[offset + 8] != 0)
235 return FALSE;
236 orientation = exif_data[offset + 9];
237 }
238 else {
239 if (exif_data[offset + 9] != 0)
240 return FALSE;
241 orientation = exif_data[offset + 8];
242 }
243 if (orientation > 8)
244 orientation = 0;
245 data->orientation = orientation;
246 data->valid |= _JPEG_INFO_EXIF_ORIENTATION;
247
248 remaining_tags--;
249 }
250
251 if ((flags & _JPEG_INFO_EXIF_COLORIMETRY) && (tagnum == 0x012D)) { /* TransferFunction */
252 remaining_tags--;
253 }
254
255 if ((flags & _JPEG_INFO_EXIF_COLORIMETRY) && (tagnum == 0x013E)) { /* WhitePoint */
256 remaining_tags--;
257 }
258
259 if ((flags & _JPEG_INFO_EXIF_COLORIMETRY) && (tagnum == 0x013F)) { /* PrimaryChromaticities */
260 remaining_tags--;
261 }
262
263 if ((flags & _JPEG_INFO_EXIF_COLOR_SPACE) && (tagnum == 0xA001)) { /* ColorSpace */
264 int value;
265
266 if (big_endian) {
267 if (exif_data[offset + 8] != 0)
268 return FALSE;
269 value = exif_data[offset + 9];
270 }
271 else {
272 if (exif_data[offset + 9] != 0)
273 return FALSE;
274 value = exif_data[offset + 8];
275 }
276
277 if (value == 1)
278 data->color_space = GTH_COLOR_SPACE_SRGB;
279 else if (value == 0xFFFF)
280 data->color_space = GTH_COLOR_SPACE_UNCALIBRATED;
281 else
282 data->color_space = GTH_COLOR_SPACE_UNKNOWN;
283 data->valid |= _JPEG_INFO_EXIF_COLOR_SPACE;
284
285 remaining_tags--;
286 }
287
288 if (remaining_tags == 0)
289 break;
290
291 if (--number_of_tags == 0)
292 return FALSE;
293
294 offset += 12;
295 }
296
297 return TRUE;
298 }
299
300
301 /* -- _jpeg_get_icc_profile_chunk_from_app2_segment -- */
302
303
304 typedef struct {
305 int seq_n;
306 int tot;
307 guchar *in_buffer;
308 guchar *data;
309 gsize size;
310 } ICCProfileChunk;
311
312
313 static void
icc_profile_chunk_free(ICCProfileChunk * chunk)314 icc_profile_chunk_free (ICCProfileChunk *chunk)
315 {
316 g_free (chunk->in_buffer);
317 g_free (chunk);
318 }
319
320
321 static int
icc_chunk_compare(gconstpointer a,gconstpointer b)322 icc_chunk_compare (gconstpointer a,
323 gconstpointer b)
324 {
325 const ICCProfileChunk *chunk_a = a;
326 const ICCProfileChunk *chunk_b = b;
327
328 if (chunk_a->seq_n < chunk_b->seq_n)
329 return -1;
330 if (chunk_a->seq_n > chunk_b->seq_n)
331 return 1;
332 return 0;
333 }
334
335
336 static ICCProfileChunk *
_jpeg_get_icc_profile_chunk_from_app2_segment(guchar * in_buffer,gsize app2_segment_size)337 _jpeg_get_icc_profile_chunk_from_app2_segment (guchar *in_buffer,
338 gsize app2_segment_size)
339 {
340 int pos;
341 guint length;
342 ICCProfileChunk *chunk;
343
344 length = app2_segment_size;
345 if (length <= 14)
346 return NULL;
347
348 pos = 0;
349
350 /* check for "ICC_PROFILE" */
351
352 if ((in_buffer[pos++] != 'I')
353 || (in_buffer[pos++] != 'C')
354 || (in_buffer[pos++] != 'C')
355 || (in_buffer[pos++] != '_')
356 || (in_buffer[pos++] != 'P')
357 || (in_buffer[pos++] != 'R')
358 || (in_buffer[pos++] != 'O')
359 || (in_buffer[pos++] != 'F')
360 || (in_buffer[pos++] != 'I')
361 || (in_buffer[pos++] != 'L')
362 || (in_buffer[pos++] != 'E')
363 || (in_buffer[pos++] != 0))
364 {
365 return NULL;
366 }
367
368 chunk = g_new (ICCProfileChunk, 1);
369 chunk->in_buffer = in_buffer;
370 chunk->seq_n = in_buffer[pos++];
371 chunk->tot = in_buffer[pos++];
372 chunk->data = in_buffer + 14;
373 chunk->size = app2_segment_size - 14;
374
375 return chunk;
376 }
377
378
379 #define _JPEG_MARKER_SOF0 0xc0
380 #define _JPEG_MARKER_SOF1 0xc2
381 #define _JPEG_MARKER_APP1 0xe1
382 #define _JPEG_MARKER_APP2 0xe2
383
384
385 gboolean
_jpeg_info_get_from_stream(GInputStream * stream,JpegInfoFlags flags,JpegInfoData * data,GCancellable * cancellable,GError ** error)386 _jpeg_info_get_from_stream (GInputStream *stream,
387 JpegInfoFlags flags,
388 JpegInfoData *data,
389 GCancellable *cancellable,
390 GError **error)
391 {
392 GList *icc_chunks;
393 guchar marker_id;
394
395 g_return_val_if_fail (data->valid == _JPEG_INFO_NONE, FALSE);
396
397 icc_chunks = NULL;
398 while ((marker_id = _jpeg_read_segment_marker (stream, cancellable, error)) != 0x00) {
399 gboolean segment_data_consumed = FALSE;
400
401 if (((flags & _JPEG_INFO_IMAGE_SIZE) && ! (data->valid & _JPEG_INFO_IMAGE_SIZE))
402 && ((marker_id == _JPEG_MARKER_SOF0) || (marker_id == _JPEG_MARKER_SOF1)))
403 {
404 guint h, l;
405 guint size;
406
407 /* size */
408
409 h = _g_input_stream_read_byte (stream, cancellable, error);
410 l = _g_input_stream_read_byte (stream, cancellable, error);
411 size = (h << 8) + l;
412
413 /* data precision */
414
415 (void) _g_input_stream_read_byte (stream, cancellable, error);
416
417 /* height */
418
419 h = _g_input_stream_read_byte (stream, cancellable, error);
420 l = _g_input_stream_read_byte (stream, cancellable, error);
421 data->height = (h << 8) + l;
422
423 /* width */
424
425 h = _g_input_stream_read_byte (stream, cancellable, error);
426 l = _g_input_stream_read_byte (stream, cancellable, error);
427 data->width = (h << 8) + l;
428
429 g_input_stream_skip (stream, size - 7, cancellable, error);
430
431 segment_data_consumed = TRUE;
432 }
433
434 if (((flags & _JPEG_INFO_EXIF_ORIENTATION)
435 || (flags & _JPEG_INFO_EXIF_COLORIMETRY)
436 || (flags & _JPEG_INFO_EXIF_COLOR_SPACE))
437 && (marker_id == _JPEG_MARKER_APP1))
438 {
439 guint h, l;
440 guint app1_segment_size;
441 guchar *app1_segment;
442
443 h = _g_input_stream_read_byte (stream, cancellable, error);
444 l = _g_input_stream_read_byte (stream, cancellable, error);
445 app1_segment_size = (h << 8) + l - 2;
446
447 app1_segment = g_new (guchar, app1_segment_size);
448 if (g_input_stream_read_all (stream,
449 app1_segment,
450 app1_segment_size,
451 NULL,
452 cancellable,
453 error))
454 {
455 _jpeg_exif_tags_from_app1_segment (app1_segment, app1_segment_size, flags, data);
456 }
457
458 segment_data_consumed = TRUE;
459
460 g_free (app1_segment);
461 }
462
463 if ((flags & _JPEG_INFO_ICC_PROFILE) && (marker_id == _JPEG_MARKER_APP2)) {
464 guint h, l;
465 gsize app2_segment_size;
466 guchar *app2_segment;
467
468 /* size */
469
470 h = _g_input_stream_read_byte (stream, cancellable, error);
471 l = _g_input_stream_read_byte (stream, cancellable, error);
472 app2_segment_size = (h << 8) + l - 2;
473
474 app2_segment = g_new (guchar, app2_segment_size);
475 if (g_input_stream_read_all (stream,
476 app2_segment,
477 app2_segment_size,
478 NULL,
479 cancellable,
480 error))
481 {
482 ICCProfileChunk *chunk;
483
484 chunk = _jpeg_get_icc_profile_chunk_from_app2_segment (app2_segment, app2_segment_size);
485 if (chunk != NULL)
486 icc_chunks = g_list_prepend (icc_chunks, chunk);
487 }
488
489 segment_data_consumed = TRUE;
490 }
491
492 if (! segment_data_consumed && ! _jpeg_skip_segment_data (stream, marker_id, cancellable, error))
493 break;
494 }
495
496 if (flags & _JPEG_INFO_ICC_PROFILE) {
497 gboolean valid_icc = (icc_chunks != NULL);
498 GOutputStream *ostream;
499 GList *scan;
500 int seq_n;
501
502 ostream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
503 icc_chunks = g_list_sort (icc_chunks, icc_chunk_compare);
504 seq_n = 1;
505 for (scan = icc_chunks; scan; scan = scan->next) {
506 ICCProfileChunk *chunk = scan->data;
507
508 if (chunk->seq_n != seq_n) {
509 valid_icc = FALSE;
510 g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Invalid ICC data");
511 break;
512 }
513
514 g_output_stream_write_all (ostream, chunk->data, chunk->size, NULL, cancellable, error);
515 seq_n++;
516 }
517
518 if (valid_icc && g_output_stream_close (ostream, NULL, NULL)) {
519 data->valid |= _JPEG_INFO_ICC_PROFILE;
520 data->icc_data = g_memory_output_stream_steal_data (G_MEMORY_OUTPUT_STREAM (ostream));
521 data->icc_data_size = g_memory_output_stream_get_data_size (G_MEMORY_OUTPUT_STREAM (ostream));
522 }
523
524 g_object_unref (ostream);
525 }
526
527 g_list_free_full (icc_chunks, (GDestroyNotify) icc_profile_chunk_free);
528
529 return (flags == data->valid);
530 }
531
532
533 gboolean
_jpeg_info_get_from_buffer(guchar * in_buffer,gsize in_buffer_size,JpegInfoFlags flags,JpegInfoData * data)534 _jpeg_info_get_from_buffer (guchar *in_buffer,
535 gsize in_buffer_size,
536 JpegInfoFlags flags,
537 JpegInfoData *data)
538 {
539 GInputStream *stream;
540 gboolean result;
541
542 stream = g_memory_input_stream_new_from_data (in_buffer, in_buffer_size, NULL);
543 result = _jpeg_info_get_from_stream (stream, flags, data, NULL, NULL);
544
545 g_object_unref (stream);
546
547 return result;
548 }
549