added custom tiff loader
[geeqie.git] / src / image_load_tiff.c
1 /*
2  * Geeqie
3  * (C) 2004 John Ellis
4  * Copyright (C) 2008 - 2011 The Geeqie Team
5  *
6  * Author: Vladimir Nadvornik
7  *
8  * This software is released under the GNU General Public License (GNU GPL).
9  * Please read the included file COPYING for more information.
10  * This software comes with no warranty of any kind, use at your own risk!
11  */
12
13
14 #include "main.h"
15
16 #include "image-load.h"
17 #include "image_load_tiff.h"
18
19 #ifdef HAVE_TIFF
20
21 #include <tiffio.h>
22
23 typedef struct _ImageLoaderTiff ImageLoaderTiff;
24 struct _ImageLoaderTiff {
25         ImageLoaderBackendCbAreaUpdated area_updated_cb;
26         ImageLoaderBackendCbSize size_cb;
27         ImageLoaderBackendCbAreaPrepared area_prepared_cb;
28         
29         gpointer data;
30
31         GdkPixbuf *pixbuf;
32         guint requested_width;
33         guint requested_height;
34         
35         gboolean abort;
36
37         const guchar *buffer;
38         toff_t used;
39         toff_t pos;
40 };
41
42 static void free_buffer (guchar *pixels, gpointer data)
43 {
44         g_free (pixels);
45 }
46
47 static tsize_t
48 tiff_load_read (thandle_t handle, tdata_t buf, tsize_t size)
49 {
50         ImageLoaderTiff *context = (ImageLoaderTiff *)handle;
51         
52         if (context->pos + size > context->used)
53                 return 0;
54         
55         memcpy (buf, context->buffer + context->pos, size);
56         context->pos += size;
57         return size;
58 }
59
60 static tsize_t
61 tiff_load_write (thandle_t handle, tdata_t buf, tsize_t size)
62 {
63         return -1;
64 }
65
66 static toff_t
67 tiff_load_seek (thandle_t handle, toff_t offset, int whence)
68 {
69         ImageLoaderTiff *context = (ImageLoaderTiff *)handle;
70         
71         switch (whence) {
72         case SEEK_SET:
73                 if (offset > context->used)
74                         return -1;
75                 context->pos = offset;
76                 break;
77         case SEEK_CUR:
78                 if (offset + context->pos >= context->used)
79                         return -1;
80                 context->pos += offset;
81                 break;
82         case SEEK_END:
83                 if (offset + context->used > context->used)
84                         return -1;
85                 context->pos = context->used + offset;
86                 break;
87         default:
88                 return -1;
89         }
90         return context->pos;
91 }
92
93 static int
94 tiff_load_close (thandle_t context)
95 {
96         return 0;
97 }
98
99 static toff_t
100 tiff_load_size (thandle_t handle)
101 {
102         ImageLoaderTiff *context = (ImageLoaderTiff *)handle;
103         
104         return context->used;
105 }
106
107 static int
108 tiff_load_map_file (thandle_t handle, tdata_t *buf, toff_t *size)
109 {
110         ImageLoaderTiff *context = (ImageLoaderTiff *)handle;
111         
112         *buf = (tdata_t *) context->buffer;
113         *size = context->used;
114         
115         return 0;
116 }
117
118 static void
119 tiff_load_unmap_file (thandle_t handle, tdata_t data, toff_t offset)
120 {
121 }
122
123 static gboolean image_loader_tiff_load (gpointer loader, const guchar *buf, gsize count, GError **error)
124 {
125         ImageLoaderTiff *lt = (ImageLoaderTiff *) loader;
126
127         TIFF *tiff;
128         guchar *pixels = NULL;
129         gint width, height, rowstride, bytes;
130         uint16 orientation = 0;
131         uint16 transform = 0;
132         uint32 rowsperstrip;
133
134         lt->buffer = buf;
135         lt->used = count;
136         lt->pos = 0;
137
138         TIFFSetWarningHandler(NULL);
139
140         tiff = TIFFClientOpen ("libtiff-geeqie", "r", lt, 
141                                tiff_load_read, tiff_load_write, 
142                                tiff_load_seek, tiff_load_close, 
143                                tiff_load_size, 
144                                tiff_load_map_file, tiff_load_unmap_file);
145         if (!tiff) 
146                 {
147                 DEBUG_1("Failed to open TIFF image");
148                 return FALSE;
149                 }
150
151
152
153         if (!TIFFGetField (tiff, TIFFTAG_IMAGEWIDTH, &width)) {
154                 DEBUG_1("Could not get image width (bad TIFF file)");
155                 TIFFClose(tiff);
156                 return FALSE;
157         }
158         
159         if (!TIFFGetField (tiff, TIFFTAG_IMAGELENGTH, &height)) {
160                 DEBUG_1("Could not get image height (bad TIFF file)");
161                 TIFFClose(tiff);
162                 return FALSE;
163         }
164
165         if (width <= 0 || height <= 0) {
166                 DEBUG_1("Width or height of TIFF image is zero");
167                 TIFFClose(tiff);
168                 return FALSE;                
169         }
170         
171         rowstride = width * 4;
172         if (rowstride / 4 != width) { /* overflow */
173                 DEBUG_1("Dimensions of TIFF image too large");
174                 TIFFClose(tiff);
175                 return FALSE;                
176         }
177         
178         bytes = height * rowstride;
179         if (bytes / rowstride != height) { /* overflow */
180                 DEBUG_1("Dimensions of TIFF image too large");
181                 TIFFClose(tiff);
182                 return FALSE;                
183         }
184
185         lt->requested_width = width;
186         lt->requested_height = height;
187         lt->size_cb(loader, lt->requested_width, lt->requested_height, lt->data);
188
189         pixels = g_try_malloc (bytes);
190
191         if (!pixels) {
192                 DEBUG_1("Insufficient memory to open TIFF file");
193                 TIFFClose(tiff);
194                 return FALSE;
195         }
196
197         lt->pixbuf = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8, 
198                                            width, height, rowstride,
199                                            free_buffer, NULL);
200         if (!lt->pixbuf) {
201                 g_free (pixels);
202                 DEBUG_1("Insufficient memory to open TIFF file");
203                 TIFFClose(tiff);
204                 return FALSE;
205         }
206
207         /* Set the "orientation" key associated with this image. libtiff 
208            orientation handling is odd, so further processing is required
209            by higher-level functions based on this tag. If the embedded
210            orientation tag is 1-4, libtiff flips/mirrors the image as
211            required, and no client processing is required - so we report 
212            no orientation. Orientations 5-8 require rotations which would 
213            swap the width and height of the image. libtiff does not do this. 
214            Instead it interprets orientations 5-8 the same as 1-4. 
215            See http://bugzilla.remotesensing.org/show_bug.cgi?id=1548.
216            To correct for this, the client must apply the transform normally
217            used for orientation 5 to both orientations 5 and 7, and apply
218            the transform normally used for orientation 7 for both
219            orientations 6 and 8. Then everythings works out OK! */
220         
221         TIFFGetField (tiff, TIFFTAG_ORIENTATION, &orientation);
222
223         switch (orientation) {
224                 case 5:
225                 case 7:
226                         transform = 5;
227                         break;
228                 case 6:
229                 case 8:
230                         transform = 7;
231                         break;
232                 default:
233                         transform = 0;
234                         break;
235         }
236
237
238         lt->area_prepared_cb(loader, lt->data);
239
240
241
242         if( TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rowsperstrip) ) 
243                 {
244                 /* read by strip */
245                 int row;
246                 guchar *wrk_line = (guchar *)g_malloc(width * sizeof (uint32));
247
248
249                 for( row = 0; row < height; row += rowsperstrip )
250                         {
251                         int rows_to_write, i_row;
252                         
253                         if (lt->abort) {
254                                 break;
255                         }
256
257                         /* Read the strip into an RGBA array */
258                         if (!TIFFReadRGBAStrip(tiff, row, (uint32 *)(pixels + row * rowstride))) {
259                                 break;
260                         }
261
262                         /*
263                          * Figure out the number of scanlines actually in this strip.
264                         */
265                         if( row + (int)rowsperstrip > height )
266                                 rows_to_write = height - row;
267                         else
268                                 rows_to_write = rowsperstrip;
269
270
271
272                         /*
273                          * For some reason the TIFFReadRGBAStrip() function chooses the
274                          * lower left corner as the origin.  Vertically mirror scanlines.
275                          */
276                         for( i_row = 0; i_row < rows_to_write / 2; i_row++ )
277                                 {
278                                 guchar *top_line, *bottom_line;
279
280                                 top_line = pixels + (row + i_row) * rowstride;
281                                 bottom_line = pixels + (row + rows_to_write - i_row - 1) * rowstride; 
282
283                                 memcpy(wrk_line, top_line, 4*width);
284                                 memcpy(top_line, bottom_line, 4*width);
285                                 memcpy(bottom_line, wrk_line, 4*width);
286                         }
287                         lt->area_updated_cb(loader, 0, row, width, rows_to_write, lt->data);
288                         }
289                 g_free(wrk_line);
290                 }
291         else
292                 {
293                 /* fallback, tiled tiff */
294                 if (!TIFFReadRGBAImageOriented (tiff, width, height, (uint32 *)pixels, ORIENTATION_TOPLEFT, 1)) 
295                         {
296                         TIFFClose(tiff);
297                         return FALSE;
298                         }
299
300 #if G_BYTE_ORDER == G_BIG_ENDIAN
301                 /* Turns out that the packing used by TIFFRGBAImage depends on 
302                  * the host byte order... 
303                  */ 
304                 while (pixels < pixbuf->pixels + bytes) 
305                         {
306                         uint32 pixel = *(uint32 *)pixels;
307                         int r = TIFFGetR(pixel);
308                         int g = TIFFGetG(pixel);
309                         int b = TIFFGetB(pixel);
310                         int a = TIFFGetA(pixel);
311                         *pixels++ = r;
312                         *pixels++ = g;
313                         *pixels++ = b;
314                         *pixels++ = a;
315                         }
316 #endif
317
318                 lt->area_updated_cb(loader, 0, 0, width, height, lt->data);
319                 }
320         TIFFClose(tiff);
321
322         return TRUE;
323 }
324
325
326 static gpointer image_loader_tiff_new(ImageLoaderBackendCbAreaUpdated area_updated_cb, ImageLoaderBackendCbSize size_cb, ImageLoaderBackendCbAreaPrepared area_prepared_cb, gpointer data)
327 {
328         ImageLoaderTiff *loader = g_new0(ImageLoaderTiff, 1);
329         
330         loader->area_updated_cb = area_updated_cb;
331         loader->size_cb = size_cb;
332         loader->area_prepared_cb = area_prepared_cb;
333         loader->data = data;
334         return (gpointer) loader;
335 }
336
337
338 static void image_loader_tiff_set_size(gpointer loader, int width, int height)
339 {
340         ImageLoaderTiff *lt = (ImageLoaderTiff *) loader;
341         lt->requested_width = width;
342         lt->requested_height = height;
343 }
344
345 static GdkPixbuf* image_loader_tiff_get_pixbuf(gpointer loader)
346 {
347         ImageLoaderTiff *lt = (ImageLoaderTiff *) loader;
348         return lt->pixbuf;
349 }
350
351 static gchar* image_loader_tiff_get_format_name(gpointer loader)
352 {
353         return g_strdup("tiff");
354 }
355 static gchar** image_loader_tiff_get_format_mime_types(gpointer loader)
356 {
357         static gchar *mime[] = {"image/tiff", NULL};
358         return g_strdupv(mime);
359 }
360
361 static gboolean image_loader_tiff_close(gpointer loader, GError **error)
362 {
363         return TRUE;
364 }
365
366 static void image_loader_tiff_abort(gpointer loader)
367 {
368         ImageLoaderTiff *lt = (ImageLoaderTiff *) loader;
369         lt->abort = TRUE;
370 }
371
372 static void image_loader_tiff_free(gpointer loader)
373 {
374         ImageLoaderTiff *lt = (ImageLoaderTiff *) loader;
375         if (lt->pixbuf) g_object_unref(lt->pixbuf);
376         g_free(lt);
377 }
378
379
380 void image_loader_backend_set_tiff(ImageLoaderBackend *funcs)
381 {
382         funcs->loader_new = image_loader_tiff_new;
383         funcs->set_size = image_loader_tiff_set_size;
384         funcs->load = image_loader_tiff_load;
385         funcs->write = NULL;
386         funcs->get_pixbuf = image_loader_tiff_get_pixbuf;
387         funcs->close = image_loader_tiff_close;
388         funcs->abort = image_loader_tiff_abort;
389         funcs->free = image_loader_tiff_free;
390         
391         funcs->get_format_name = image_loader_tiff_get_format_name;
392         funcs->get_format_mime_types = image_loader_tiff_get_format_mime_types;
393 }
394
395
396
397 #endif