]> mj.ucw.cz Git - libucw.git/blob - images/io-libpng.c
added image compression via libpng
[libucw.git] / images / io-libpng.c
1 /*
2  *      Image Library -- libpng
3  *
4  *      (c) 2006 Pavel Charvat <pchar@ucw.cz>
5  *
6  *      This software may be freely distributed and used according to the terms
7  *      of the GNU Lesser General Public License.
8  */
9
10 #undef LOCAL_DEBUG
11
12 #include "lib/lib.h"
13 #include "lib/mempool.h"
14 #include "lib/fastbuf.h"
15 #include "images/images.h"
16 #include <png.h>
17 #include <setjmp.h>
18
19 struct libpng_read_data {
20   png_structp png_ptr;
21   png_infop info_ptr;
22   png_infop end_ptr;
23   png_uint_32 cols;
24   png_uint_32 rows;
25   int bit_depth;
26   int color_type;
27 };
28
29 static png_voidp
30 libpng_malloc(png_structp png_ptr, png_size_t size)
31 {
32   DBG("libpng_malloc(size=%u)", (uns)size);
33   return mp_alloc(png_get_mem_ptr(png_ptr), size);
34 }
35
36 static void
37 libpng_free(png_structp png_ptr UNUSED, png_voidp ptr UNUSED)
38 {
39   DBG("libpng_free()");
40 }
41
42 static void NONRET
43 libpng_read_error(png_structp png_ptr, png_const_charp msg)
44 {
45   DBG("libpng_read_error()");
46   image_thread_err(png_get_error_ptr(png_ptr), IMAGE_ERR_READ_FAILED, (byte *)msg);
47   longjmp(png_jmpbuf(png_ptr), 1);
48 }
49
50 static void NONRET
51 libpng_write_error(png_structp png_ptr, png_const_charp msg)
52 {
53   DBG("libpng_write_error()");
54   image_thread_err(png_get_error_ptr(png_ptr), IMAGE_ERR_WRITE_FAILED, (byte *)msg);
55   longjmp(png_jmpbuf(png_ptr), 1);
56 }
57
58 static void
59 libpng_warning(png_structp png_ptr UNUSED, png_const_charp msg UNUSED)
60 {
61   DBG("libpng_warning(): %s", (byte *)msg);
62 }
63
64 static void
65 libpng_read_fn(png_structp png_ptr, png_bytep data, png_size_t length)
66 {
67   DBG("libpng_read_fn(len=%u)", (uns)length);
68   if (unlikely(bread((struct fastbuf *)png_get_io_ptr(png_ptr), (byte *)data, length) < length))
69     png_error(png_ptr, "Incomplete data");
70 }
71
72 static void
73 libpng_write_fn(png_structp png_ptr, png_bytep data, png_size_t length)
74 {
75   DBG("libpng_write_fn(len=%u)", (uns)length);
76   bwrite((struct fastbuf *)png_get_io_ptr(png_ptr), (byte *)data, length);
77 }
78
79 static void
80 libpng_flush_fn(png_structp png_ptr UNUSED)
81 {
82   DBG("libpng_flush_fn()");
83 }
84
85 static void
86 libpng_read_cancel(struct image_io *io)
87 {
88   DBG("libpng_read_cancel()");
89
90   struct libpng_read_data *rd = io->read_data;
91   png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
92 }
93
94 int
95 libpng_read_header(struct image_io *io)
96 {
97   DBG("libpng_read_header()");
98   
99   /* Create libpng structures */
100   struct libpng_read_data *rd = io->read_data = mp_alloc(io->internal_pool, sizeof(*rd));
101   rd->png_ptr = png_create_read_struct_2(PNG_LIBPNG_VER_STRING,
102       io->thread, libpng_read_error, libpng_warning,
103       io->internal_pool, libpng_malloc, libpng_free);
104   if (unlikely(!rd->png_ptr))
105     {
106       image_thread_err(io->thread, IMAGE_ERR_READ_FAILED, "Cannot create libpng read structure.");
107       return 0;
108     }
109   rd->info_ptr = png_create_info_struct(rd->png_ptr);
110   if (unlikely(!rd->info_ptr))
111     {
112       image_thread_err(io->thread, IMAGE_ERR_READ_FAILED, "Cannot create libpng info structure.");
113       png_destroy_read_struct(&rd->png_ptr, NULL, NULL);
114       return 0;
115     }
116   rd->end_ptr = png_create_info_struct(rd->png_ptr);
117   if (unlikely(!rd->end_ptr))
118     {
119       image_thread_err(io->thread, IMAGE_ERR_READ_FAILED, "Cannot create libpng info structure.");
120       png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, NULL);
121       return 0;
122     }
123   
124   /* Setup libpng longjump */
125   if (unlikely(setjmp(png_jmpbuf(rd->png_ptr))))
126     {
127       DBG("Libpng failed to read the image, longjump saved us");
128       png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
129       return 0;
130     }
131   
132   /* Setup libpng IO */
133   png_set_read_fn(rd->png_ptr, io->fastbuf, libpng_read_fn);
134   png_set_user_limits(rd->png_ptr, IMAGE_MAX_SIZE, IMAGE_MAX_SIZE);
135
136   /* Read header */
137   png_read_info(rd->png_ptr, rd->info_ptr);
138   png_get_IHDR(rd->png_ptr, rd->info_ptr, &rd->cols, &rd->rows, &rd->bit_depth, &rd->color_type, NULL, NULL, NULL);
139
140   /* Fill image_io values */
141   if (!io->cols)
142     io->cols = rd->cols;
143   if (!io->rows)
144     io->rows = rd->rows;
145   if (!(io->flags & IMAGE_CHANNELS_FORMAT))
146     switch (rd->color_type)
147       {
148         case PNG_COLOR_TYPE_GRAY:
149           io->flags |= COLOR_SPACE_GRAYSCALE;
150           break;
151         case PNG_COLOR_TYPE_GRAY_ALPHA:
152           io->flags |= COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA;
153           break;
154         case PNG_COLOR_TYPE_RGB:
155           io->flags |= COLOR_SPACE_RGB;
156           break;
157         case PNG_COLOR_TYPE_RGB_ALPHA:
158         case PNG_COLOR_TYPE_PALETTE:
159           io->flags |= COLOR_SPACE_RGB | IMAGE_ALPHA;
160           break;
161         default:
162           png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
163           image_thread_err(io->thread, IMAGE_ERR_READ_FAILED, "Unknown color type");
164           break;
165       }
166
167   /* Success */
168   io->read_cancel = libpng_read_cancel;
169   return 1;
170 }
171
172 int
173 libpng_read_data(struct image_io *io)
174 {
175   DBG("libpng_read_data()");
176
177   struct libpng_read_data *rd = io->read_data;
178
179   /* Test supported pixel formats */
180   switch (io->flags & IMAGE_COLOR_SPACE)
181     {
182       case COLOR_SPACE_GRAYSCALE:
183       case COLOR_SPACE_RGB:
184         break;
185       default:
186         png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
187         image_thread_err(io->thread, IMAGE_ERR_INVALID_PIXEL_FORMAT, "Unsupported color space.");
188         return 0;
189     }
190
191   volatile int need_scale = io->cols != rd->cols || io->rows != rd->rows;
192   struct image * volatile img = need_scale ? 
193     image_new(io->thread, rd->cols, rd->rows, io->flags & IMAGE_PIXEL_FORMAT, NULL) :
194     image_new(io->thread, rd->cols, rd->rows, io->flags, io->pool);
195   if (!img)
196     {
197       png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
198       return 0;
199     }
200
201   if (setjmp(png_jmpbuf(rd->png_ptr)))
202     {
203       DBG("Libpng failed to read the image, longjump saved us");
204       png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
205       if (need_scale || !io->pool)
206         image_destroy(io->thread, img);
207       return 0;
208     }
209
210   /* Apply transformations */
211   if (rd->bit_depth == 16)
212     png_set_strip_16(rd->png_ptr);
213   switch (rd->color_type)
214     {
215       case PNG_COLOR_TYPE_PALETTE:
216         if ((io->flags & IMAGE_COLOR_SPACE) == COLOR_SPACE_GRAYSCALE)
217           {
218             png_set_palette_to_rgb(rd->png_ptr);
219             png_set_rgb_to_gray_fixed(rd->png_ptr, 1, 21267, 71514);
220           }
221         else
222           png_set_palette_to_rgb(rd->png_ptr);
223         if ((io->flags & IMAGE_ALPHA) || (io->flags & IMAGE_PIXEL_FORMAT) == (COLOR_SPACE_RGB | IMAGE_PIXELS_ALIGNED))
224           png_set_add_alpha(rd->png_ptr, 255, PNG_FILLER_AFTER);
225         else
226           png_set_strip_alpha(rd->png_ptr);
227         break;
228       case PNG_COLOR_TYPE_GRAY:
229         if ((io->flags & IMAGE_COLOR_SPACE) == COLOR_SPACE_RGB)
230           png_set_gray_to_rgb(rd->png_ptr);
231         if (io->flags & IMAGE_ALPHA)
232           png_set_add_alpha(rd->png_ptr, 255, PNG_FILLER_AFTER);
233         break;
234       case PNG_COLOR_TYPE_GRAY_ALPHA:
235         if ((io->flags & IMAGE_COLOR_SPACE) == COLOR_SPACE_RGB)
236           png_set_gray_to_rgb(rd->png_ptr);
237         if (!(io->flags & IMAGE_ALPHA))
238           png_set_strip_alpha(rd->png_ptr);
239         break;
240       case PNG_COLOR_TYPE_RGB:
241         if ((io->flags & IMAGE_COLOR_SPACE) == COLOR_SPACE_GRAYSCALE)
242           png_set_rgb_to_gray_fixed(rd->png_ptr, 1, 21267, 71514);
243         if ((io->flags & IMAGE_ALPHA) || (io->flags & IMAGE_PIXEL_FORMAT) == (COLOR_SPACE_RGB | IMAGE_PIXELS_ALIGNED))
244           png_set_add_alpha(rd->png_ptr, 255, PNG_FILLER_AFTER);
245         break;
246       case PNG_COLOR_TYPE_RGB_ALPHA:
247         if ((io->flags & IMAGE_COLOR_SPACE) == COLOR_SPACE_GRAYSCALE)
248           png_set_rgb_to_gray_fixed(rd->png_ptr, 1, 21267, 71514);
249         if (!(io->flags & IMAGE_ALPHA) && (io->flags & IMAGE_PIXEL_FORMAT) != (COLOR_SPACE_RGB | IMAGE_PIXELS_ALIGNED))
250           png_set_strip_alpha(rd->png_ptr);
251         break;
252       default:
253         ASSERT(0);
254     }
255   png_read_update_info(rd->png_ptr, rd->info_ptr);
256
257   /* Read image data */
258   DBG("Reading image data");
259   byte *pixels = img->pixels;
260   png_bytep rows[img->rows];
261   for (uns r = 0; r < img->rows; r++, pixels += img->row_size)
262     rows[r] = (png_bytep)pixels;
263   png_read_image(rd->png_ptr, rows);
264   png_read_end(rd->png_ptr, rd->end_ptr);
265
266   /* Destroy libpng read structure */
267   png_destroy_read_struct(&rd->png_ptr, &rd->info_ptr, &rd->end_ptr);
268
269   /* Scale and store the resulting image */
270   if (need_scale)
271     {
272       struct image *dest = image_new(io->thread, io->cols, io->rows, io->flags, io->pool);
273       if (!dest)
274         {
275           image_destroy(io->thread, img);
276           return 0;
277         }
278       if (!image_scale(io->thread, dest, img))
279         {
280           image_destroy(io->thread, img);
281           if (!io->pool)
282             image_destroy(io->thread, dest);
283           return 0;
284         }
285       io->image = dest;
286     }
287   else
288     io->image = img;
289   io->image_destroy = !io->pool;
290   
291   return 1;
292 }
293
294 int
295 libpng_write(struct image_io *io)
296 {
297   DBG("libpng_write()");
298
299   /* Create libpng structures */
300   png_structp png_ptr = png_create_write_struct_2(PNG_LIBPNG_VER_STRING,
301       io->thread, libpng_write_error, libpng_warning,
302       io->internal_pool, libpng_malloc, libpng_free);
303   if (unlikely(!png_ptr))
304     {
305       image_thread_err(io->thread, IMAGE_ERR_WRITE_FAILED, "Cannot create libpng write structure.");
306       return 0;
307     }
308   png_infop info_ptr = png_create_info_struct(png_ptr);
309   if (unlikely(!info_ptr))
310     {
311       image_thread_err(io->thread, IMAGE_ERR_WRITE_FAILED, "Cannot create libpng info structure.");
312       png_destroy_write_struct(&png_ptr, NULL);
313       return 0;
314     }
315  
316   /* Setup libpng longjump */
317   if (unlikely(setjmp(png_jmpbuf(png_ptr))))
318     {
319       DBG("Libpng failed to write the image, longjump saved us.");
320       png_destroy_write_struct(&png_ptr, &info_ptr);
321       return 0;
322     }
323
324   /* Setup libpng IO */
325   png_set_write_fn(png_ptr, io->fastbuf, libpng_write_fn, libpng_flush_fn);
326   
327   /* Setup PNG parameters */
328   struct image *img = io->image;
329   switch (img->flags & IMAGE_PIXEL_FORMAT)
330     {
331       case COLOR_SPACE_GRAYSCALE | IMAGE_PIXELS_ALIGNED:
332         png_set_IHDR(png_ptr, info_ptr, img->cols, img->rows, 8, PNG_COLOR_TYPE_GRAY,
333           PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
334         break;
335       case COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA | IMAGE_PIXELS_ALIGNED:
336         png_set_IHDR(png_ptr, info_ptr, img->cols, img->rows, 8, PNG_COLOR_TYPE_GRAY_ALPHA,
337           PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
338         break;
339       case COLOR_SPACE_RGB:
340         png_set_IHDR(png_ptr, info_ptr, img->cols, img->rows, 8, PNG_COLOR_TYPE_RGB,
341           PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
342         break;
343       case COLOR_SPACE_RGB | IMAGE_ALPHA | IMAGE_PIXELS_ALIGNED:
344         png_set_IHDR(png_ptr, info_ptr, img->cols, img->rows, 8, PNG_COLOR_TYPE_RGB_ALPHA,
345           PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
346         break;
347       case COLOR_SPACE_RGB | IMAGE_PIXELS_ALIGNED:
348         png_set_IHDR(png_ptr, info_ptr, img->cols, img->rows, 8, PNG_COLOR_TYPE_RGB,
349           PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
350         png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
351         break;
352       default:
353         ASSERT(0);      
354     }
355   png_write_info(png_ptr, info_ptr);
356
357   /* Write pixels */
358   byte *pixels = img->pixels;
359   png_bytep rows[img->rows];
360   for (uns r = 0; r < img->rows; r++, pixels += img->row_size)
361     rows[r] = (png_bytep)pixels;
362   png_write_image(png_ptr, rows);
363   png_write_end(png_ptr, info_ptr);
364
365   /* Free libpng structure */
366   png_destroy_write_struct(&png_ptr, &info_ptr);
367   return 1;
368 }