1/* GDK - The GIMP Drawing Kit
2 * Copyright (C) 2021 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include "config.h"
19
20#include "gdktiffprivate.h"
21
22#include "gdkintl.h"
23#include "gdkmemoryformatprivate.h"
24#include "gdkmemorytextureprivate.h"
25#include "gdkprofilerprivate.h"
26#include "gdktexture.h"
27#include "gdktextureprivate.h"
28
29#include <tiffio.h>
30
31/* Our main interest in tiff as an image format is that it is
32 * flexible enough to save all our texture formats without
33 * lossy conversions.
34 *
35 * The loader isn't meant to be a very versatile. It just aims
36 * to load the subset that we're saving ourselves. For anything
37 * else, we fall back to TIFFRGBAImage, which is the same api
38 * that gdk-pixbuf uses.
39 */
40
41/* {{{ IO handling */
42
43typedef struct
44{
45 GBytes **out_bytes;
46 gchar *data;
47 gsize size;
48 gsize position;
49} TiffIO;
50
51static void
52tiff_io_warning (const char *module,
53 const char *fmt,
54 va_list ap) G_GNUC_PRINTF(2, 0);
55static void
56tiff_io_warning (const char *module,
57 const char *fmt,
58 va_list ap)
59{
60 g_logv (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_MESSAGE, format: fmt, args: ap);
61}
62
63static void
64tiff_io_error (const char *module,
65 const char *fmt,
66 va_list ap) G_GNUC_PRINTF(2, 0);
67static void
68tiff_io_error (const char *module,
69 const char *fmt,
70 va_list ap)
71{
72 g_logv (G_LOG_DOMAIN, log_level: G_LOG_LEVEL_MESSAGE, format: fmt, args: ap);
73}
74
75static tsize_t
76tiff_io_no_read (thandle_t handle,
77 tdata_t buffer,
78 tsize_t size)
79{
80 return (tsize_t) -1;
81}
82
83static tsize_t
84tiff_io_read (thandle_t handle,
85 tdata_t buffer,
86 tsize_t size)
87{
88 TiffIO *io = (TiffIO *) handle;
89 gsize read;
90
91 if (io->position >= io->size)
92 return 0;
93
94 read = MIN (size, io->size - io->position);
95
96 memcpy (dest: buffer, src: io->data + io->position, n: read);
97 io->position += read;
98
99 return (tsize_t) read;
100}
101
102static tsize_t
103tiff_io_no_write (thandle_t handle,
104 tdata_t buffer,
105 tsize_t size)
106{
107 return (tsize_t) -1;
108}
109
110static tsize_t
111tiff_io_write (thandle_t handle,
112 tdata_t buffer,
113 tsize_t size)
114{
115 TiffIO *io = (TiffIO *) handle;
116
117 if (io->position > io->size ||
118 io->size - io->position < size)
119 {
120 io->size = io->position + size;
121 io->data = g_realloc (mem: io->data, n_bytes: io->size);
122 }
123
124 memcpy (dest: io->data + io->position, src: buffer, n: size);
125 io->position += size;
126
127 return (tsize_t) size;
128}
129
130static toff_t
131tiff_io_seek (thandle_t handle,
132 toff_t offset,
133 int whence)
134{
135 TiffIO *io = (TiffIO *) handle;
136
137 switch (whence)
138 {
139 default:
140 return -1;
141 case SEEK_SET:
142 break;
143 case SEEK_CUR:
144 offset += io->position;
145 break;
146 case SEEK_END:
147 offset += io->size;
148 break;
149 }
150 if (offset < 0)
151 return -1;
152
153 io->position = offset;
154
155 return offset;
156}
157
158static int
159tiff_io_close (thandle_t handle)
160{
161 TiffIO *io = (TiffIO *) handle;
162
163 if (io->out_bytes)
164 *io->out_bytes = g_bytes_new_take (data: io->data, size: io->size);
165
166 g_free (mem: io);
167
168 return 0;
169}
170
171static toff_t
172tiff_io_get_file_size (thandle_t handle)
173{
174 TiffIO *io = (TiffIO *) handle;
175
176 return io->size;
177}
178
179static TIFF *
180tiff_open_read (GBytes *bytes)
181{
182 TiffIO *io;
183
184 TIFFSetWarningHandler ((TIFFErrorHandler) tiff_io_warning);
185 TIFFSetErrorHandler ((TIFFErrorHandler) tiff_io_error);
186
187 io = g_new0 (TiffIO, 1);
188
189 io->data = (char *) g_bytes_get_data (bytes, size: &io->size);
190
191 return TIFFClientOpen ("GTK-read", "r",
192 (thandle_t) io,
193 tiff_io_read,
194 tiff_io_no_write,
195 tiff_io_seek,
196 tiff_io_close,
197 tiff_io_get_file_size,
198 NULL, NULL);
199}
200
201static TIFF *
202tiff_open_write (GBytes **result)
203{
204 TiffIO *io;
205
206 TIFFSetWarningHandler ((TIFFErrorHandler) tiff_io_warning);
207 TIFFSetErrorHandler ((TIFFErrorHandler) tiff_io_error);
208
209 io = g_new0 (TiffIO, 1);
210
211 io->out_bytes = result;
212
213 return TIFFClientOpen ("GTK-write", "w",
214 (thandle_t) io,
215 tiff_io_no_read,
216 tiff_io_write,
217 tiff_io_seek,
218 tiff_io_close,
219 tiff_io_get_file_size,
220 NULL, NULL);
221}
222
223/* }}} */
224/* {{{ Public API */
225
226typedef struct _FormatData FormatData;
227struct _FormatData {
228 GdkMemoryFormat format;
229 guint16 bits_per_sample;
230 guint16 samples_per_pixel;
231 guint16 sample_format;
232 guint16 alpha_samples;
233};
234
235static const FormatData format_data[] = {
236 [GDK_MEMORY_B8G8R8A8_PREMULTIPLIED] = { .format: GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, .bits_per_sample: 8, .samples_per_pixel: 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_ASSOCALPHA },
237 [GDK_MEMORY_A8R8G8B8_PREMULTIPLIED] = { .format: GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, .bits_per_sample: 8, .samples_per_pixel: 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_ASSOCALPHA },
238 [GDK_MEMORY_R8G8B8A8_PREMULTIPLIED] = { .format: GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, .bits_per_sample: 8, .samples_per_pixel: 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_ASSOCALPHA },
239 [GDK_MEMORY_B8G8R8A8] = { .format: GDK_MEMORY_R8G8B8A8, .bits_per_sample: 8, .samples_per_pixel: 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_UNASSALPHA },
240 [GDK_MEMORY_A8R8G8B8] = { .format: GDK_MEMORY_R8G8B8A8, .bits_per_sample: 8, .samples_per_pixel: 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_UNASSALPHA },
241 [GDK_MEMORY_R8G8B8A8] = { .format: GDK_MEMORY_R8G8B8A8, .bits_per_sample: 8, .samples_per_pixel: 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_UNASSALPHA },
242 [GDK_MEMORY_A8B8G8R8] = { .format: GDK_MEMORY_R8G8B8A8, .bits_per_sample: 8, .samples_per_pixel: 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_UNASSALPHA },
243 [GDK_MEMORY_R8G8B8] = { .format: GDK_MEMORY_R8G8B8, .bits_per_sample: 8, .samples_per_pixel: 3, SAMPLEFORMAT_UINT, .alpha_samples: 0 },
244 [GDK_MEMORY_B8G8R8] = { .format: GDK_MEMORY_R8G8B8, .bits_per_sample: 8, .samples_per_pixel: 3, SAMPLEFORMAT_UINT, .alpha_samples: 0 },
245 [GDK_MEMORY_R16G16B16] = { .format: GDK_MEMORY_R16G16B16, .bits_per_sample: 16, .samples_per_pixel: 3, SAMPLEFORMAT_UINT, .alpha_samples: 0 },
246 [GDK_MEMORY_R16G16B16A16_PREMULTIPLIED] = { .format: GDK_MEMORY_R16G16B16A16_PREMULTIPLIED, .bits_per_sample: 16, .samples_per_pixel: 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_ASSOCALPHA },
247 [GDK_MEMORY_R16G16B16A16] = { .format: GDK_MEMORY_R16G16B16A16, .bits_per_sample: 16, .samples_per_pixel: 4, SAMPLEFORMAT_UINT, EXTRASAMPLE_UNASSALPHA },
248 [GDK_MEMORY_R16G16B16_FLOAT] = { .format: GDK_MEMORY_R16G16B16_FLOAT, .bits_per_sample: 16, .samples_per_pixel: 3, SAMPLEFORMAT_IEEEFP, .alpha_samples: 0 },
249 [GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED] = { .format: GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED, .bits_per_sample: 16, .samples_per_pixel: 4, SAMPLEFORMAT_IEEEFP, EXTRASAMPLE_ASSOCALPHA },
250 [GDK_MEMORY_R16G16B16A16_FLOAT] = { .format: GDK_MEMORY_R16G16B16A16_FLOAT, .bits_per_sample: 16, .samples_per_pixel: 4, SAMPLEFORMAT_IEEEFP, EXTRASAMPLE_UNASSALPHA },
251 [GDK_MEMORY_R32G32B32_FLOAT] = { .format: GDK_MEMORY_R32G32B32_FLOAT, .bits_per_sample: 32, .samples_per_pixel: 3, SAMPLEFORMAT_IEEEFP, .alpha_samples: 0 },
252 [GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED] = { .format: GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED, .bits_per_sample: 32, .samples_per_pixel: 4, SAMPLEFORMAT_IEEEFP, EXTRASAMPLE_ASSOCALPHA },
253 [GDK_MEMORY_R32G32B32A32_FLOAT] = { .format: GDK_MEMORY_R32G32B32A32_FLOAT, .bits_per_sample: 32, .samples_per_pixel: 4, SAMPLEFORMAT_IEEEFP, EXTRASAMPLE_UNASSALPHA },
254};
255
256/* if this fails, somebody forgot to add formats above */
257G_STATIC_ASSERT (G_N_ELEMENTS (format_data) == GDK_MEMORY_N_FORMATS);
258
259GBytes *
260gdk_save_tiff (GdkTexture *texture)
261{
262 TIFF *tif;
263 int width, height, stride;
264 const guchar *line;
265 const guchar *data;
266 GBytes *result = NULL;
267 GdkMemoryTexture *memtex;
268 GdkMemoryFormat format;
269 const FormatData *fdata = NULL;
270
271 tif = tiff_open_write (result: &result);
272
273 width = gdk_texture_get_width (texture);
274 height = gdk_texture_get_height (texture);
275 format = gdk_texture_get_format (self: texture);
276 fdata = &format_data[format];
277
278 if (fdata == NULL)
279 fdata = &format_data[0];
280
281 TIFFSetField (tif, TIFFTAG_SOFTWARE, "GTK");
282 TIFFSetField (tif, TIFFTAG_IMAGEWIDTH, width);
283 TIFFSetField (tif, TIFFTAG_IMAGELENGTH, height);
284 TIFFSetField (tif, TIFFTAG_BITSPERSAMPLE, fdata->bits_per_sample);
285 TIFFSetField (tif, TIFFTAG_SAMPLESPERPIXEL, fdata->samples_per_pixel);
286 TIFFSetField (tif, TIFFTAG_SAMPLEFORMAT, fdata->sample_format);
287 TIFFSetField (tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
288 TIFFSetField (tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
289 if (fdata->alpha_samples)
290 TIFFSetField (tif, TIFFTAG_EXTRASAMPLES, 1, &fdata->alpha_samples);
291
292 TIFFSetField (tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
293 TIFFSetField (tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
294
295 memtex = gdk_memory_texture_from_texture (texture, format: fdata->format);
296 data = gdk_memory_texture_get_data (self: memtex);
297 stride = gdk_memory_texture_get_stride (self: memtex);
298
299 line = (const guchar *)data;
300 for (int y = 0; y < height; y++)
301 {
302 if (TIFFWriteScanline (tif, buf: (void *)line, row: y, sample: 0) == -1)
303 {
304 TIFFClose (tif);
305 g_object_unref (object: memtex);
306 return NULL;
307 }
308
309 line += stride;
310 }
311
312 TIFFFlushData (tif);
313 TIFFClose (tif);
314
315 g_assert (result);
316
317 g_object_unref (object: memtex);
318
319 return result;
320}
321
322static GdkTexture *
323load_fallback (TIFF *tif,
324 GError **error)
325{
326 int width, height;
327 guchar *data;
328 GBytes *bytes;
329 GdkTexture *texture;
330
331 TIFFGetField (tif, TIFFTAG_IMAGEWIDTH, &width);
332 TIFFGetField (tif, TIFFTAG_IMAGELENGTH, &height);
333
334 data = g_malloc (n_bytes: width * height * 4);
335
336 if (!TIFFReadRGBAImageOriented (tif, width, height, (guint32 *)data, ORIENTATION_TOPLEFT, 1))
337 {
338 g_set_error_literal (err: error,
339 GDK_TEXTURE_ERROR, code: GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
340 _("Failed to load RGB data from TIFF file"));
341 g_free (mem: data);
342 return NULL;
343 }
344
345 bytes = g_bytes_new_take (data, size: width * height * 4);
346
347 texture = gdk_memory_texture_new (width, height,
348 format: GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
349 bytes,
350 stride: width * 4);
351
352 g_bytes_unref (bytes);
353
354 return texture;
355}
356
357GdkTexture *
358gdk_load_tiff (GBytes *input_bytes,
359 GError **error)
360{
361 TIFF *tif;
362 guint16 samples_per_pixel;
363 guint16 bits_per_sample;
364 guint16 photometric;
365 guint16 planarconfig;
366 guint16 sample_format;
367 guint16 orientation;
368 guint32 width, height;
369 guint16 alpha_samples;
370 GdkMemoryFormat format;
371 guchar *data, *line;
372 gsize stride;
373 int bpp;
374 GBytes *bytes;
375 GdkTexture *texture;
376 G_GNUC_UNUSED gint64 before = GDK_PROFILER_CURRENT_TIME;
377
378 tif = tiff_open_read (bytes: input_bytes);
379 if (!tif)
380 {
381 g_set_error_literal (err: error,
382 GDK_TEXTURE_ERROR, code: GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
383 _("Could not load TIFF data"));
384 return NULL;
385 }
386
387 TIFFSetDirectory (tif, 0);
388
389 TIFFGetFieldDefaulted (tif, TIFFTAG_SAMPLESPERPIXEL, &samples_per_pixel);
390 TIFFGetFieldDefaulted (tif, TIFFTAG_BITSPERSAMPLE, &bits_per_sample);
391 TIFFGetFieldDefaulted (tif, TIFFTAG_SAMPLEFORMAT, &sample_format);
392 TIFFGetFieldDefaulted (tif, TIFFTAG_PHOTOMETRIC, &photometric);
393 TIFFGetFieldDefaulted (tif, TIFFTAG_PLANARCONFIG, &planarconfig);
394 TIFFGetFieldDefaulted (tif, TIFFTAG_ORIENTATION, &orientation);
395 TIFFGetFieldDefaulted (tif, TIFFTAG_IMAGEWIDTH, &width);
396 TIFFGetFieldDefaulted (tif, TIFFTAG_IMAGELENGTH, &height);
397
398 if (samples_per_pixel == 4)
399 {
400 guint16 extra;
401 guint16 *extra_types;
402
403 if (TIFFGetField (tif, TIFFTAG_EXTRASAMPLES, &extra, &extra_types))
404 alpha_samples = extra_types[0];
405 else
406 alpha_samples = 0;
407
408 if (alpha_samples != 0 && alpha_samples != EXTRASAMPLE_ASSOCALPHA && alpha_samples != EXTRASAMPLE_UNASSALPHA)
409 {
410 texture = load_fallback (tif, error);
411 TIFFClose (tif);
412 return texture;
413 }
414 }
415 else
416 alpha_samples = 0;
417
418 for (format = 0; format < G_N_ELEMENTS (format_data); format++)
419 {
420 /* not a native format */
421 if (format_data[format].format != format)
422 continue;
423
424 if (format_data[format].sample_format == sample_format &&
425 format_data[format].bits_per_sample == bits_per_sample &&
426 format_data[format].samples_per_pixel == samples_per_pixel &&
427 format_data[format].alpha_samples == alpha_samples)
428 {
429 break;
430 }
431 }
432
433 if (format == G_N_ELEMENTS(format_data) ||
434 photometric != PHOTOMETRIC_RGB ||
435 planarconfig != PLANARCONFIG_CONTIG ||
436 TIFFIsTiled (tif) ||
437 orientation != ORIENTATION_TOPLEFT)
438 {
439 texture = load_fallback (tif, error);
440 TIFFClose (tif);
441 return texture;
442 }
443
444 stride = width * gdk_memory_format_bytes_per_pixel (format);
445
446 g_assert (TIFFScanlineSize (tif) == stride);
447
448 data = g_try_malloc_n (n_blocks: height, n_block_bytes: stride);
449 if (!data)
450 {
451 g_set_error (err: error,
452 GDK_TEXTURE_ERROR, code: GDK_TEXTURE_ERROR_TOO_LARGE,
453 _("Not enough memory for image size %ux%u"), width, height);
454 TIFFClose (tif);
455 return NULL;
456 }
457
458 line = data;
459 for (int y = 0; y < height; y++)
460 {
461 if (TIFFReadScanline (tif, buf: line, row: y, sample: 0) == -1)
462 {
463 g_set_error (err: error,
464 GDK_TEXTURE_ERROR, code: GDK_TEXTURE_ERROR_CORRUPT_IMAGE,
465 _("Reading data failed at row %d"), y);
466 TIFFClose (tif);
467 g_free (mem: data);
468 return NULL;
469 }
470
471 line += stride;
472 }
473
474 bpp = gdk_memory_format_bytes_per_pixel (format);
475 bytes = g_bytes_new_take (data, size: width * height * bpp);
476
477 texture = gdk_memory_texture_new (width, height,
478 format,
479 bytes, stride: width * bpp);
480 g_bytes_unref (bytes);
481
482 TIFFClose (tif);
483
484 if (GDK_PROFILER_IS_RUNNING)
485 {
486 gint64 end = GDK_PROFILER_CURRENT_TIME;
487 if (end - before > 500000)
488 gdk_profiler_add_mark (before, end - before, "tiff load", NULL);
489 }
490
491 return texture;
492}
493
494/* }}} */
495
496/* vim:set foldmethod=marker expandtab: */
497

source code of gtk/gdk/loaders/gdktiff.c