| 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 | |