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 | |
43 | typedef struct |
44 | { |
45 | GBytes **out_bytes; |
46 | gchar *data; |
47 | gsize size; |
48 | gsize position; |
49 | } TiffIO; |
50 | |
51 | static void |
52 | tiff_io_warning (const char *module, |
53 | const char *fmt, |
54 | va_list ap) G_GNUC_PRINTF(2, 0); |
55 | static void |
56 | tiff_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 | |
63 | static void |
64 | tiff_io_error (const char *module, |
65 | const char *fmt, |
66 | va_list ap) G_GNUC_PRINTF(2, 0); |
67 | static void |
68 | tiff_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 | |
75 | static tsize_t |
76 | tiff_io_no_read (thandle_t handle, |
77 | tdata_t buffer, |
78 | tsize_t size) |
79 | { |
80 | return (tsize_t) -1; |
81 | } |
82 | |
83 | static tsize_t |
84 | tiff_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 | |
102 | static tsize_t |
103 | tiff_io_no_write (thandle_t handle, |
104 | tdata_t buffer, |
105 | tsize_t size) |
106 | { |
107 | return (tsize_t) -1; |
108 | } |
109 | |
110 | static tsize_t |
111 | tiff_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 | |
130 | static toff_t |
131 | tiff_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 | |
158 | static int |
159 | tiff_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 | |
171 | static toff_t |
172 | tiff_io_get_file_size (thandle_t handle) |
173 | { |
174 | TiffIO *io = (TiffIO *) handle; |
175 | |
176 | return io->size; |
177 | } |
178 | |
179 | static TIFF * |
180 | tiff_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 | |
201 | static TIFF * |
202 | tiff_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 | |
226 | typedef struct _FormatData FormatData; |
227 | struct _FormatData { |
228 | GdkMemoryFormat format; |
229 | guint16 bits_per_sample; |
230 | guint16 samples_per_pixel; |
231 | guint16 sample_format; |
232 | guint16 alpha_samples; |
233 | }; |
234 | |
235 | static 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 */ |
257 | G_STATIC_ASSERT (G_N_ELEMENTS (format_data) == GDK_MEMORY_N_FORMATS); |
258 | |
259 | GBytes * |
260 | gdk_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 | |
322 | static GdkTexture * |
323 | load_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 | |
357 | GdkTexture * |
358 | gdk_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 ; |
401 | guint16 *; |
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 | |