]> mj.ucw.cz Git - libucw.git/blob - images/io-libmagick.c
slow image compression via GraphicsMagick
[libucw.git] / images / io-libmagick.c
1 /*
2  *      Image Library -- GraphicsMagick (slow fallback library)
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 #define 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 <sys/types.h>
17 #include <stdlib.h>
18 #include <stdio.h>
19 #include <magick/api.h>
20
21 #define MAX_FILE_SIZE (1 << 30)
22 #define QUANTUM_SCALE (QuantumDepth - 8)
23 #define QUANTUM_TO_BYTE(x) ((uns)(x) >> QUANTUM_SCALE)
24 #define BYTE_TO_QUANTUM(x) ((uns)(x) << QUANTUM_SCALE)
25 #define OPACITY_MAX ((1 << QuantumDepth) - 1)
26
27 struct magick_read_data {
28   ExceptionInfo exception;
29   ImageInfo *info;
30   Image *image;
31 };
32
33 static inline void
34 libmagick_destroy_read_data(struct magick_read_data *rd)
35 {
36   if (rd->image)
37     DestroyImage(rd->image);
38   DestroyImageInfo(rd->info);
39   DestroyExceptionInfo(&rd->exception);
40   DestroyMagick();
41 }
42
43 static void
44 libmagick_read_cancel(struct image_io *io)
45 {
46   DBG("libmagick_read_cancel()");
47
48   struct magick_read_data *rd = io->read_data;
49
50   DestroyImage(rd->image);
51   libmagick_destroy_read_data(rd);
52 }
53
54 int
55 libmagick_read_header(struct image_io *io)
56 {
57   DBG("libmagick_read_header()");
58
59   /* Read entire stream */
60   sh_off_t file_size = bfilesize(io->fastbuf) - btell(io->fastbuf);
61   if (unlikely(file_size > MAX_FILE_SIZE))
62     {
63       image_thread_err(io->thread, IMAGE_ERR_READ_FAILED, "Too long stream.");
64       return 0;
65     }
66   uns buf_size = file_size;
67   byte *buf = xmalloc(buf_size);
68   bread(io->fastbuf, buf, buf_size);
69
70   /* Allocate read structure */
71   struct magick_read_data *rd = io->read_data = mp_alloc(io->internal_pool, sizeof(*rd));
72
73   /* Initialize GraphicsMagick */
74   InitializeMagick(NULL);
75   GetExceptionInfo(&rd->exception);
76   rd->info = CloneImageInfo(NULL);
77   rd->info->subrange = 1;
78
79   /* Read the image */
80   rd->image = BlobToImage(rd->info, buf, buf_size, &rd->exception);
81   xfree(buf);
82   if (unlikely(!rd->image))
83     {
84       image_thread_err(io->thread, IMAGE_ERR_READ_FAILED, "GraphicsMagick failed to read the image.");
85       goto err;
86     }
87   if (unlikely(rd->image->columns > IMAGE_MAX_SIZE || rd->image->rows > IMAGE_MAX_SIZE))
88     {
89       image_thread_err(io->thread, IMAGE_ERR_INVALID_DIMENSIONS, "Image too large.");
90       goto err;
91     }
92
93   /* Fill image parameters */
94   if (!io->cols)
95     io->cols = rd->image->columns;
96   if (!io->rows)
97     io->rows = rd->image->rows;
98   if (!(io->flags & IMAGE_CHANNELS_FORMAT))
99     {
100       switch (rd->image->colorspace)
101         {
102           case GRAYColorspace:
103             io->flags |= COLOR_SPACE_GRAYSCALE | IMAGE_ALPHA;
104             break;
105           default:
106             io->flags |= COLOR_SPACE_RGB | IMAGE_ALPHA;
107             break;
108         }
109     }
110
111   io->read_cancel = libmagick_read_cancel;
112   return 1;
113
114 err:
115   libmagick_destroy_read_data(rd);
116   return 0;
117 }
118
119 static inline byte
120 libmagick_pixel_to_gray(PixelPacket *pixel)
121 {
122   return ((uns)pixel->red * 19660 + (uns)pixel->green * 38666 + (uns)pixel->blue * 7210) >> (16 + QUANTUM_SCALE);
123 }
124
125 int
126 libmagick_read_data(struct image_io *io)
127 {
128   DBG("libmagick_read_data()");
129
130   struct magick_read_data *rd = io->read_data;
131
132   /* Quantize image */
133   switch (rd->image->colorspace)
134     {
135       case RGBColorspace:
136       case GRAYColorspace:
137         break;
138       default: ;
139         QuantizeInfo quantize;
140         GetQuantizeInfo(&quantize);
141         quantize.colorspace = RGBColorspace;
142         QuantizeImage(&quantize, rd->image);
143         break;
144     }
145
146   /* Allocate image for conversion */
147   int need_scale = io->cols != rd->image->columns || io->rows != rd->image->rows;
148   int need_destroy = need_scale || !io->pool;
149   struct image *img = need_scale ?
150     image_new(io->thread, rd->image->columns, rd->image->rows, io->flags & IMAGE_CHANNELS_FORMAT, NULL) :
151     image_new(io->thread, io->cols, io->rows, io->flags, io->pool);
152   if (unlikely(!img))
153     goto err;
154
155   /* Acquire pixels */
156   PixelPacket *src = (PixelPacket *)AcquireImagePixels(rd->image, 0, 0, rd->image->columns, rd->image->rows, &rd->exception);
157   if (unlikely(!src))
158     {
159       image_thread_err(io->thread, IMAGE_ERR_READ_FAILED, "Cannot acquire image pixels.");
160       goto err;
161     }
162
163   /* Convert pixels */
164   switch (img->pixel_size)
165     {
166       case 1:
167 #       define IMAGE_WALK_INLINE
168 #       define IMAGE_WALK_UNROLL 4
169 #       define IMAGE_WALK_COL_STEP 1
170 #       define IMAGE_WALK_DO_STEP do{ \
171           pos[0] = libmagick_pixel_to_gray(src); \
172           src++; }while(0)
173 #       include "images/image-walk.h"
174         break;
175
176       case 2:
177 #       define IMAGE_WALK_INLINE
178 #       define IMAGE_WALK_UNROLL 4
179 #       define IMAGE_WALK_COL_STEP 2
180 #       define IMAGE_WALK_DO_STEP do{ \
181           pos[0] = libmagick_pixel_to_gray(src); \
182           pos[1] = QUANTUM_TO_BYTE(src->opacity); \
183           src++; }while(0)
184 #       include "images/image-walk.h"
185         break;
186
187       case 3:
188 #       define IMAGE_WALK_INLINE
189 #       define IMAGE_WALK_UNROLL 4
190 #       define IMAGE_WALK_COL_STEP 3
191 #       define IMAGE_WALK_DO_STEP do{ \
192           pos[0] = QUANTUM_TO_BYTE(src->red); \
193           pos[1] = QUANTUM_TO_BYTE(src->green); \
194           pos[2] = QUANTUM_TO_BYTE(src->blue); \
195           src++; }while(0)
196 #       include "images/image-walk.h"
197         break;
198
199       case 4:
200 #       define IMAGE_WALK_INLINE
201 #       define IMAGE_WALK_UNROLL 4
202 #       define IMAGE_WALK_COL_STEP 4
203 #       define IMAGE_WALK_DO_STEP do{ \
204           pos[0] = QUANTUM_TO_BYTE(src->red); \
205           pos[1] = QUANTUM_TO_BYTE(src->green); \
206           pos[2] = QUANTUM_TO_BYTE(src->blue); \
207           pos[3] = QUANTUM_TO_BYTE(src->opacity); \
208           src++; }while(0)
209 #       include "images/image-walk.h"
210         break;
211
212       default:
213         ASSERT(0);
214     }
215
216   /* Free GraphicsMagick structures */
217   libmagick_destroy_read_data(rd);
218
219   /* Scale image */
220   if (need_scale)
221     {
222       struct image *img2 = image_new(io->thread, io->cols, io->rows, io->flags, io->pool);
223       if (unlikely(!img2))
224         goto err2;
225       int result = image_scale(io->thread, img2, img);
226       image_destroy(io->thread, img);
227       img = img2;
228       need_destroy = !io->pool;
229       if (unlikely(!result))
230         goto err2;
231     }
232
233   /* Success */
234   io->image = img;
235   io->image_destroy = need_destroy;
236   return 1;
237
238   /* Free structures */
239 err:
240   libmagick_destroy_read_data(rd);
241 err2:
242   if (need_destroy)
243     image_destroy(io->thread, img);
244   return 0;
245 }
246
247 int
248 libmagick_write(struct image_io *io)
249 {
250   DBG("libmagick_write()");
251
252   /* Initialize GraphicsMagick */
253   int result = 0;
254   ExceptionInfo exception;
255   ImageInfo *info;
256   InitializeMagick(NULL);
257   GetExceptionInfo(&exception);
258   info = CloneImageInfo(NULL);
259
260   /* Setup image parameters and allocate the image*/
261   switch (io->flags & IMAGE_COLOR_SPACE)
262     {
263       case COLOR_SPACE_GRAYSCALE:
264         info->colorspace = GRAYColorspace;
265         break;
266       case COLOR_SPACE_RGB:
267         info->colorspace = RGBColorspace;
268         break;
269       default:
270         ASSERT(0);
271     }
272   switch (io->format)
273     {
274       case IMAGE_FORMAT_JPEG:
275         strcpy(info->magick, "JPEG");
276         break;
277       case IMAGE_FORMAT_PNG:
278         strcpy(info->magick, "PNG");
279         break;
280       case IMAGE_FORMAT_GIF:
281         strcpy(info->magick, "GIF");
282         break;
283       default:
284         ASSERT(0);
285     }
286   Image *image = AllocateImage(info);
287   if (unlikely(!image))
288     {
289       image_thread_err(io->thread, IMAGE_ERR_WRITE_FAILED, "GraphicsMagick failed to allocate the image.");
290       goto err;
291     }
292   image->columns = io->cols;
293   image->rows = io->rows;
294
295   /* Get pixels */
296   PixelPacket *pixels = SetImagePixels(image, 0, 0, io->cols, io->rows), *dest = pixels;
297   if (unlikely(!pixels))
298     {
299       image_thread_err(io->thread, IMAGE_ERR_WRITE_FAILED, "Cannot get GraphicsMagick pixels.");
300       goto err2;
301     }
302
303   /* Convert pixels */
304   struct image *img = io->image;
305   switch (img->pixel_size)
306     {
307       case 1:
308 #       define IMAGE_WALK_INLINE
309 #       define IMAGE_WALK_UNROLL 4
310 #       define IMAGE_WALK_COL_STEP 1
311 #       define IMAGE_WALK_DO_STEP do{ \
312           dest->red = BYTE_TO_QUANTUM(pos[0]); \
313           dest->green = BYTE_TO_QUANTUM(pos[0]); \
314           dest->blue = BYTE_TO_QUANTUM(pos[0]); \
315           dest->opacity = OPACITY_MAX; \
316           dest++; }while(0)
317 #       include "images/image-walk.h"
318         break;
319       case 2:
320 #       define IMAGE_WALK_INLINE
321 #       define IMAGE_WALK_UNROLL 4
322 #       define IMAGE_WALK_COL_STEP 2
323 #       define IMAGE_WALK_DO_STEP do{ \
324           dest->red = BYTE_TO_QUANTUM(pos[0]); \
325           dest->green = BYTE_TO_QUANTUM(pos[0]); \
326           dest->blue = BYTE_TO_QUANTUM(pos[0]); \
327           dest->opacity = BYTE_TO_QUANTUM(pos[1]); \
328           dest++; }while(0)
329 #       include "images/image-walk.h"
330         break;
331       case 3:
332 #       define IMAGE_WALK_INLINE
333 #       define IMAGE_WALK_UNROLL 4
334 #       define IMAGE_WALK_COL_STEP 3
335 #       define IMAGE_WALK_DO_STEP do{ \
336           dest->red = BYTE_TO_QUANTUM(pos[0]); \
337           dest->green = BYTE_TO_QUANTUM(pos[1]); \
338           dest->blue = BYTE_TO_QUANTUM(pos[2]); \
339           dest->opacity = OPACITY_MAX; \
340           dest++; }while(0)
341 #       include "images/image-walk.h"
342         break;
343       case 4:
344 #       define IMAGE_WALK_INLINE
345 #       define IMAGE_WALK_UNROLL 4
346 #       define IMAGE_WALK_COL_STEP 4
347 #       define IMAGE_WALK_DO_STEP do{ \
348           dest->red = BYTE_TO_QUANTUM(pos[0]); \
349           dest->green = BYTE_TO_QUANTUM(pos[1]); \
350           dest->blue = BYTE_TO_QUANTUM(pos[2]); \
351           dest->opacity = BYTE_TO_QUANTUM(pos[3]); \
352           dest++; }while(0)
353 #       include "images/image-walk.h"
354         break;
355     }
356
357   /* Store pixels */
358   if (unlikely(!SyncImagePixels(image)))
359     {
360       image_thread_err(io->thread, IMAGE_ERR_WRITE_FAILED, "Cannot sync GraphicsMagick pixels.");
361       goto err2;
362     }
363
364   /* Write image */
365   size_t buf_len = 0;
366   void *buf = ImageToBlob(info, image, &buf_len, &exception);
367   if (unlikely(!buf))
368     {
369       image_thread_err(io->thread, IMAGE_ERR_WRITE_FAILED, "GraphicsMagick failed to compress the image.");
370       goto err2;
371     }
372   if (unlikely(buf_len > MAX_FILE_SIZE))
373     {
374       image_thread_err(io->thread, IMAGE_ERR_WRITE_FAILED, "Image too large.");
375       goto err2;
376     }
377
378   /* Write to stream */
379   bwrite(io->fastbuf, buf, buf_len);
380
381   /* Success */
382   result = 1;
383
384 err2:
385   DestroyImage(image);
386 err:
387   DestroyImageInfo(info);
388   DestroyExceptionInfo(&exception);
389   DestroyMagick();
390   return result;
391 }