/src/graphicsmagick/coders/xcf.c
Line | Count | Source |
1 | | /* |
2 | | % Copyright (C) 2003-2026 GraphicsMagick Group |
3 | | % Copyright (C) 2002 ImageMagick Studio |
4 | | % |
5 | | % This program is covered by multiple licenses, which are described in |
6 | | % Copyright.txt. You should have received a copy of Copyright.txt with this |
7 | | % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html. |
8 | | % |
9 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
10 | | % % |
11 | | % % |
12 | | % % |
13 | | % X X CCCC FFFFF % |
14 | | % X X C F % |
15 | | % X C FFF % |
16 | | % X X C F % |
17 | | % X X CCCC F % |
18 | | % % |
19 | | % % |
20 | | % Read GIMP XCF Image Format. % |
21 | | % % |
22 | | % % |
23 | | % Software Design % |
24 | | % Leonard Rosenthol % |
25 | | % November 2001 % |
26 | | % % |
27 | | % % |
28 | | % % |
29 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
30 | | % |
31 | | % https://testing.developer.gimp.org/core/standards/xcf |
32 | | */ |
33 | | |
34 | | /* |
35 | | Include declarations. |
36 | | */ |
37 | | #include "magick/studio.h" |
38 | | #include "magick/blob.h" |
39 | | #include "magick/pixel_cache.h" |
40 | | #include "magick/composite.h" |
41 | | #include "magick/log.h" |
42 | | #include "magick/magick.h" |
43 | | #include "magick/monitor.h" |
44 | | #include "magick/quantize.h" |
45 | | #include "magick/utility.h" |
46 | | #include "magick/static.h" |
47 | | |
48 | | /* |
49 | | Typedef declarations. |
50 | | */ |
51 | | typedef enum |
52 | | { |
53 | | GIMP_RGB, |
54 | | GIMP_GRAY, |
55 | | GIMP_INDEXED |
56 | | } GimpImageBaseType; |
57 | | |
58 | | typedef enum |
59 | | { |
60 | | PROP_END = 0, |
61 | | PROP_COLORMAP = 1, |
62 | | PROP_ACTIVE_LAYER = 2, |
63 | | PROP_ACTIVE_CHANNEL = 3, |
64 | | PROP_SELECTION = 4, |
65 | | PROP_FLOATING_SELECTION = 5, |
66 | | PROP_OPACITY = 6, |
67 | | PROP_MODE = 7, |
68 | | PROP_VISIBLE = 8, |
69 | | PROP_LINKED = 9, |
70 | | PROP_PRESERVE_TRANSPARENCY = 10, |
71 | | PROP_APPLY_MASK = 11, |
72 | | PROP_EDIT_MASK = 12, |
73 | | PROP_SHOW_MASK = 13, |
74 | | PROP_SHOW_MASKED = 14, |
75 | | PROP_OFFSETS = 15, |
76 | | PROP_COLOR = 16, |
77 | | PROP_COMPRESSION = 17, |
78 | | PROP_GUIDES = 18, |
79 | | PROP_RESOLUTION = 19, |
80 | | PROP_TATTOO = 20, |
81 | | PROP_PARASITES = 21, |
82 | | PROP_UNIT = 22, |
83 | | PROP_PATHS = 23, |
84 | | PROP_USER_UNIT = 24 |
85 | | } PropType; |
86 | | |
87 | | typedef enum |
88 | | { |
89 | | COMPRESS_NONE = 0, |
90 | | COMPRESS_RLE = 1, |
91 | | COMPRESS_ZLIB = 2, /* unused */ |
92 | | COMPRESS_FRACTAL = 3 /* unused */ |
93 | | } XcfCompressionType; |
94 | | |
95 | | typedef struct { |
96 | | magick_uint32_t |
97 | | width, |
98 | | height, |
99 | | image_type, |
100 | | bpp; /* BYTES per pixel!! */ |
101 | | |
102 | | int |
103 | | compression; |
104 | | |
105 | | /* not really part of the doc, but makes it easy to pass around! */ |
106 | | ExceptionInfo |
107 | | *exception; |
108 | | |
109 | | /* File size */ |
110 | | magick_off_t |
111 | | file_size; |
112 | | } XCFDocInfo; |
113 | | |
114 | | typedef struct { |
115 | | char |
116 | | name[1024]; |
117 | | |
118 | | unsigned int |
119 | | active; |
120 | | |
121 | | magick_uint32_t |
122 | | width, |
123 | | height, |
124 | | type, |
125 | | opacity, |
126 | | visible, |
127 | | linked, |
128 | | preserve_trans, |
129 | | apply_mask, |
130 | | show_mask, |
131 | | edit_mask, |
132 | | floating_offset; |
133 | | |
134 | | magick_int32_t |
135 | | offset_x, |
136 | | offset_y; |
137 | | |
138 | | magick_uint32_t |
139 | | mode, |
140 | | tattoo; |
141 | | |
142 | | Image |
143 | | *image; |
144 | | } XCFLayerInfo; |
145 | | |
146 | 130k | #define TILE_WIDTH 64 |
147 | 130k | #define TILE_HEIGHT 64 |
148 | | |
149 | | typedef struct { |
150 | | unsigned char |
151 | | red, |
152 | | green, |
153 | | blue, |
154 | | opacity; /* NOTE: reversed from IM! */ |
155 | | } XCFPixelPacket; |
156 | | |
157 | | /* |
158 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
159 | | % % |
160 | | % % |
161 | | % % |
162 | | % I s X C F % |
163 | | % % |
164 | | % % |
165 | | % % |
166 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
167 | | % |
168 | | % Method IsXCF returns True if the image format type, identified by the |
169 | | % magick string, is XCF (GIMP native format). |
170 | | % |
171 | | % The format of the IsXCF method is: |
172 | | % |
173 | | % unsigned int IsXCF(const unsigned char *magick,const size_t length) |
174 | | % |
175 | | % A description of each parameter follows: |
176 | | % |
177 | | % o status: Method IsXCF returns True if the image format type is XCF. |
178 | | % |
179 | | % o magick: This string is generally the first few bytes of an image file |
180 | | % or blob. |
181 | | % |
182 | | % o length: Specifies the length of the magick string. |
183 | | % |
184 | | % |
185 | | */ |
186 | | static unsigned int IsXCF(const unsigned char *magick,const size_t length) |
187 | 0 | { |
188 | 0 | if (length < 8) |
189 | 0 | return(False); |
190 | 0 | if (LocaleNCompare((char *) magick,"gimp xcf",8) == 0) |
191 | 0 | return(True); |
192 | 0 | return(False); |
193 | 0 | } |
194 | | |
195 | | |
196 | | typedef enum |
197 | | { |
198 | | GIMP_NORMAL_MODE, |
199 | | GIMP_DISSOLVE_MODE, |
200 | | GIMP_BEHIND_MODE, |
201 | | GIMP_MULTIPLY_MODE, |
202 | | GIMP_SCREEN_MODE, |
203 | | GIMP_OVERLAY_MODE, |
204 | | GIMP_DIFFERENCE_MODE, |
205 | | GIMP_ADDITION_MODE, |
206 | | GIMP_SUBTRACT_MODE, |
207 | | GIMP_DARKEN_ONLY_MODE, |
208 | | GIMP_LIGHTEN_ONLY_MODE, |
209 | | GIMP_HUE_MODE, |
210 | | GIMP_SATURATION_MODE, |
211 | | GIMP_COLOR_MODE, |
212 | | GIMP_VALUE_MODE, |
213 | | GIMP_DIVIDE_MODE, |
214 | | GIMP_DODGE_MODE, |
215 | | GIMP_BURN_MODE, |
216 | | GIMP_HARDLIGHT_MODE |
217 | | } GimpLayerModeEffects; |
218 | | |
219 | | /* |
220 | | Simple utility routine to convert between PSD blending modes and |
221 | | GraphicsMagick compositing operators |
222 | | */ |
223 | | static CompositeOperator GIMPBlendModeToCompositeOperator( unsigned int blendMode ) |
224 | 5.31k | { |
225 | 5.31k | switch ( blendMode ) |
226 | 5.31k | { |
227 | 5.26k | case GIMP_NORMAL_MODE: return( OverCompositeOp ); |
228 | 1 | case GIMP_DISSOLVE_MODE: return( DissolveCompositeOp ); |
229 | 0 | case GIMP_MULTIPLY_MODE: return( MultiplyCompositeOp ); |
230 | 1 | case GIMP_SCREEN_MODE: return( ScreenCompositeOp ); |
231 | 1 | case GIMP_OVERLAY_MODE: return( OverlayCompositeOp ); |
232 | 0 | case GIMP_DIFFERENCE_MODE: return( DifferenceCompositeOp ); |
233 | 0 | case GIMP_ADDITION_MODE: return( AddCompositeOp ); |
234 | 0 | case GIMP_SUBTRACT_MODE: return( SubtractCompositeOp ); |
235 | 0 | case GIMP_DARKEN_ONLY_MODE: return( DarkenCompositeOp ); |
236 | 0 | case GIMP_LIGHTEN_ONLY_MODE:return( LightenCompositeOp ); |
237 | 1 | case GIMP_HUE_MODE: return( HueCompositeOp ); |
238 | 0 | case GIMP_SATURATION_MODE: return( SaturateCompositeOp ); |
239 | 1 | case GIMP_COLOR_MODE: return( ColorizeCompositeOp ); |
240 | 0 | case GIMP_DIVIDE_MODE: return( DivideCompositeOp ); |
241 | 1 | case GIMP_HARDLIGHT_MODE: return( HardLightCompositeOp ); |
242 | 0 | case GIMP_DODGE_MODE: return( ColorDodgeCompositeOp ); |
243 | 1 | case GIMP_BURN_MODE: return( ColorBurnCompositeOp ); |
244 | | /* these are the ones we don't support...yet */ |
245 | 0 | case GIMP_BEHIND_MODE: return( OverCompositeOp ); |
246 | 1 | case GIMP_VALUE_MODE: return( OverCompositeOp ); |
247 | 44 | default: return( OverCompositeOp ); |
248 | 5.31k | } |
249 | 5.31k | } |
250 | | |
251 | | |
252 | | /* |
253 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
254 | | % % |
255 | | % % |
256 | | % % |
257 | | + R e a d B l o b S t r i n g W i t h L o n g S i z e % |
258 | | % % |
259 | | % % |
260 | | % % |
261 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
262 | | % |
263 | | % Method ReadBlobStringWithLongSize reads characters from a blob or file |
264 | | % starting with a long length byte and then characters to that length |
265 | | % |
266 | | % The format of the ReadBlobStringWithLongSize method is: |
267 | | % |
268 | | % char *ReadBlobStringWithLongSize(Image *image,char *string) |
269 | | % |
270 | | % A description of each parameter follows: |
271 | | % |
272 | | % o status: Method ReadBlobString returns the string on success, otherwise, |
273 | | % a null is returned. |
274 | | % |
275 | | % o image: The image. |
276 | | % |
277 | | % o string: The address of a character buffer. |
278 | | % |
279 | | % o max: Length of 'string' array. |
280 | | % |
281 | | % |
282 | | */ |
283 | | static char *ReadBlobStringWithLongSize(Image *image,char *string,size_t max) |
284 | 14.5k | { |
285 | 14.5k | int |
286 | 14.5k | c; |
287 | | |
288 | 14.5k | register unsigned long |
289 | 14.5k | i; |
290 | | |
291 | 14.5k | unsigned long |
292 | 14.5k | length; |
293 | | |
294 | 14.5k | assert(image != (Image *) NULL); |
295 | 14.5k | assert(image->signature == MagickSignature); |
296 | 14.5k | assert(max != 0); |
297 | 14.5k | length = ReadBlobMSBLong(image); |
298 | 191k | for (i=0; i < Min(length,max-1); i++) |
299 | 179k | { |
300 | 179k | c=ReadBlobByte(image); |
301 | 179k | if (c == EOF) |
302 | 2.71k | return((char *) NULL); |
303 | 176k | string[i]=c; |
304 | 176k | } |
305 | 11.8k | string[i]='\0'; |
306 | 11.8k | (void) SeekBlob(image, length-i, SEEK_CUR); |
307 | 11.8k | return(string); |
308 | 14.5k | } |
309 | | |
310 | | |
311 | | static MagickPassFail load_tile (Image* image, Image* tile_image, XCFDocInfo* inDocInfo, |
312 | | XCFLayerInfo* inLayerInfo, size_t data_length) |
313 | 30.7k | { |
314 | 30.7k | size_t |
315 | 30.7k | nmemb_read_successfully; |
316 | | |
317 | 30.7k | unsigned long |
318 | 30.7k | x, |
319 | 30.7k | y; |
320 | | |
321 | 30.7k | PixelPacket |
322 | 30.7k | *q; |
323 | | |
324 | 30.7k | XCFPixelPacket |
325 | 30.7k | *xcfdata, |
326 | 30.7k | *xcfodata; |
327 | | |
328 | 30.7k | unsigned char |
329 | 30.7k | *graydata; |
330 | | |
331 | | /* |
332 | | Validate that claimed data length is sufficient for tile. |
333 | | */ |
334 | 30.7k | { |
335 | 30.7k | size_t |
336 | 30.7k | expected_data_length=0; |
337 | | |
338 | 30.7k | if (inDocInfo->image_type == GIMP_GRAY) |
339 | 30.0k | { |
340 | 30.0k | expected_data_length=MagickArraySize(tile_image->columns,tile_image->rows)* |
341 | 30.0k | sizeof(unsigned char); |
342 | 30.0k | } |
343 | 692 | else if (inDocInfo->image_type == GIMP_RGB) |
344 | 692 | { |
345 | 692 | expected_data_length=MagickArraySize(tile_image->columns,tile_image->rows)* |
346 | 692 | sizeof(XCFPixelPacket); |
347 | 692 | } |
348 | 30.7k | if (expected_data_length && (expected_data_length > data_length)) |
349 | 42 | { |
350 | 42 | ThrowException(&image->exception,CorruptImageError,CorruptImage, |
351 | 42 | "Claimed tile data length is insufficient for tile data"); |
352 | 42 | return MagickFail; |
353 | 42 | } |
354 | 30.7k | } |
355 | | |
356 | 30.7k | xcfdata = xcfodata = MagickAllocateResourceLimitedMemory(XCFPixelPacket *,data_length); |
357 | 30.7k | graydata = (unsigned char *) xcfdata; /* used by gray and indexed */ |
358 | | |
359 | 30.7k | if (xcfdata == (XCFPixelPacket *) NULL) |
360 | 0 | { |
361 | 0 | ThrowException(&image->exception,ResourceLimitError,MemoryAllocationFailed,NULL); |
362 | 0 | return MagickFail; |
363 | 0 | } |
364 | | |
365 | 30.7k | nmemb_read_successfully = ReadBlob(image, data_length, xcfdata); |
366 | 30.7k | if (nmemb_read_successfully != data_length) |
367 | 232 | { |
368 | 232 | MagickFreeResourceLimitedMemory(XCFPixelPacket *,xcfodata); |
369 | 232 | ThrowBinaryException(CorruptImageError,UnexpectedEndOfFile,image->filename); |
370 | 0 | } |
371 | | |
372 | 30.4k | q=SetImagePixels(tile_image,0,0,tile_image->columns,tile_image->rows); |
373 | 30.4k | if (q == (PixelPacket *) NULL) |
374 | 2 | { |
375 | 2 | CopyException(&image->exception,&tile_image->exception); |
376 | 2 | MagickFreeResourceLimitedMemory(XCFPixelPacket *,xcfodata); |
377 | 2 | return MagickFail; |
378 | 2 | } |
379 | | |
380 | 266k | for (x=0; x < tile_image->columns; x++) |
381 | 235k | { |
382 | 235k | if (inDocInfo->image_type == GIMP_GRAY) |
383 | 231k | { |
384 | 5.22M | for (y=tile_image->rows; y != 0; y--) |
385 | 4.98M | { |
386 | 4.98M | q->red =q->green=q->blue=ScaleCharToQuantum(*graydata); |
387 | 4.98M | q->opacity = ScaleCharToQuantum(255U-inLayerInfo->opacity); |
388 | 4.98M | graydata++; |
389 | 4.98M | q++; |
390 | 4.98M | } |
391 | 231k | } |
392 | 4.17k | else if (inDocInfo->image_type == GIMP_RGB) |
393 | 4.17k | { |
394 | 115k | for (y=tile_image->rows; y != 0; y--) |
395 | 111k | { |
396 | 111k | q->red = ScaleCharToQuantum(xcfdata->red); |
397 | 111k | q->green = ScaleCharToQuantum(xcfdata->green); |
398 | 111k | q->blue = ScaleCharToQuantum(xcfdata->blue); |
399 | 111k | q->opacity = (Quantum) (xcfdata->opacity==0U ? TransparentOpacity : |
400 | 111k | ScaleCharToQuantum(255U-inLayerInfo->opacity)); |
401 | 111k | xcfdata++; |
402 | 111k | q++; |
403 | 111k | } |
404 | 4.17k | } |
405 | 235k | } |
406 | | |
407 | 30.4k | MagickFreeResourceLimitedMemory(XCFPixelPacket *,xcfodata); |
408 | 30.4k | return MagickPass; |
409 | 30.4k | } |
410 | | |
411 | | static MagickPassFail load_tile_rle (Image* image, |
412 | | Image* tile_image, |
413 | | XCFDocInfo* inDocInfo, |
414 | | XCFLayerInfo* inLayerInfo, |
415 | | size_t data_length) |
416 | 33.2k | { |
417 | 33.2k | unsigned char |
418 | 33.2k | data, |
419 | 33.2k | val; |
420 | | |
421 | 33.2k | magick_int64_t |
422 | 33.2k | size; |
423 | | |
424 | 33.2k | size_t |
425 | 33.2k | nmemb_read_successfully; |
426 | | |
427 | 33.2k | int |
428 | | /* count, */ |
429 | 33.2k | length, |
430 | 33.2k | bpp, /* BYTES per pixel! */ |
431 | 33.2k | i, |
432 | 33.2k | j; |
433 | | |
434 | 33.2k | unsigned char |
435 | 33.2k | *xcfdata, |
436 | 33.2k | *xcfodata, |
437 | 33.2k | *xcfdatalimit; |
438 | | |
439 | 33.2k | PixelPacket |
440 | 33.2k | *q; |
441 | | |
442 | 33.2k | bpp = (int) inDocInfo->bpp; |
443 | | |
444 | 33.2k | xcfdata = xcfodata = MagickAllocateResourceLimitedMemory(unsigned char *,data_length); |
445 | 33.2k | if (xcfdata == (unsigned char *) NULL) |
446 | 0 | { |
447 | 0 | ThrowException(&image->exception,ResourceLimitError,MemoryAllocationFailed,NULL); |
448 | 0 | return MagickFail; |
449 | 0 | } |
450 | | |
451 | 33.2k | nmemb_read_successfully = ReadBlob(image, data_length, xcfdata); |
452 | 33.2k | if (nmemb_read_successfully != data_length) |
453 | 10 | { |
454 | 10 | if (image->logging) |
455 | 10 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
456 | 10 | "Read %lu bytes, expected %lu bytes", |
457 | 10 | (unsigned long) nmemb_read_successfully, |
458 | 10 | (unsigned long) data_length); |
459 | 10 | MagickFreeResourceLimitedMemory(unsigned char *,xcfodata); |
460 | 10 | ThrowBinaryException(CorruptImageError,UnexpectedEndOfFile,image->filename); |
461 | 0 | } |
462 | | |
463 | 33.2k | xcfdatalimit = &xcfodata[nmemb_read_successfully - 1]; |
464 | | |
465 | 34.3k | for (i = 0; i < bpp; i++) |
466 | 1.32k | { |
467 | 1.32k | q=SetImagePixels(tile_image,0,0,tile_image->columns,tile_image->rows); |
468 | 1.32k | if (q == (PixelPacket *) NULL) |
469 | 1 | { |
470 | 1 | CopyException(&image->exception,&tile_image->exception); |
471 | 1 | goto bogus_rle; |
472 | 1 | } |
473 | 1.32k | size = MagickArraySize(tile_image->rows,tile_image->columns); |
474 | | /* count = 0; */ |
475 | | |
476 | 53.4k | while (size > 0) |
477 | 52.3k | { |
478 | 52.3k | if (xcfdata > xcfdatalimit) |
479 | 102 | { |
480 | 102 | goto bogus_rle; |
481 | 102 | } |
482 | | |
483 | 52.2k | val = *xcfdata++; |
484 | | |
485 | 52.2k | length = val; |
486 | | |
487 | 52.2k | if (length >= 128) |
488 | 3.19k | { |
489 | 3.19k | length = 255 - (length - 1); |
490 | | |
491 | 3.19k | if (length == 128) |
492 | 452 | { |
493 | 452 | if (xcfdata >= xcfdatalimit) |
494 | 1 | { |
495 | 1 | goto bogus_rle; |
496 | 1 | } |
497 | | |
498 | 451 | length = ((*xcfdata << 8) + xcfdata[1]) & 0xFFFF; |
499 | 451 | xcfdata += 2; |
500 | 451 | } |
501 | | |
502 | | /* count += length; */ |
503 | 3.19k | size -= length; |
504 | | |
505 | 3.19k | if (size < 0) |
506 | 20 | { |
507 | 20 | goto bogus_rle; |
508 | 20 | } |
509 | | |
510 | 3.17k | if (&xcfdata[length-1] > xcfdatalimit) |
511 | 16 | { |
512 | 16 | goto bogus_rle; |
513 | 16 | } |
514 | | |
515 | 78.3k | while (length-- > 0) |
516 | 75.1k | { |
517 | 75.1k | data = *xcfdata++; |
518 | 75.1k | switch (i) |
519 | 75.1k | { |
520 | 52.8k | case 0: |
521 | 52.8k | { |
522 | 52.8k | q->red = ScaleCharToQuantum(data); |
523 | 52.8k | if ( inDocInfo->image_type == GIMP_GRAY ) |
524 | 34.5k | { |
525 | 34.5k | q->green = ScaleCharToQuantum(data); |
526 | 34.5k | q->blue = ScaleCharToQuantum(data); |
527 | 34.5k | q->opacity = ScaleCharToQuantum(255-inLayerInfo->opacity); |
528 | 34.5k | } |
529 | 18.2k | else |
530 | 18.2k | { |
531 | 18.2k | q->green = q->red; |
532 | 18.2k | q->blue = q->red; |
533 | 18.2k | q->opacity = ScaleCharToQuantum(255-inLayerInfo->opacity); |
534 | 18.2k | } |
535 | 52.8k | break; |
536 | 0 | } |
537 | 12.5k | case 1: |
538 | 12.5k | { |
539 | 12.5k | q->green = ScaleCharToQuantum(data); |
540 | 12.5k | break; |
541 | 0 | } |
542 | 2.72k | case 2: |
543 | 2.72k | { |
544 | 2.72k | q->blue = ScaleCharToQuantum(data); |
545 | 2.72k | break; |
546 | 0 | } |
547 | 6.74k | case 3: |
548 | 6.74k | { |
549 | 6.74k | q->opacity = (Quantum) (data==0 ? TransparentOpacity : |
550 | 6.74k | ScaleCharToQuantum(255-inLayerInfo->opacity)); |
551 | 6.74k | break; |
552 | 0 | } |
553 | 75.1k | } |
554 | 75.1k | q++; |
555 | 75.1k | } |
556 | 3.16k | } |
557 | 49.0k | else |
558 | 49.0k | { |
559 | 49.0k | length += 1; |
560 | 49.0k | if (length == 128) |
561 | 262 | { |
562 | 262 | if (xcfdata >= xcfdatalimit) |
563 | 1 | { |
564 | 1 | goto bogus_rle; |
565 | 1 | } |
566 | | |
567 | 261 | length = ((*xcfdata << 8) + xcfdata[1]) & 0xFFFF; |
568 | 261 | xcfdata += 2; |
569 | 261 | } |
570 | | |
571 | | /* count += length; */ |
572 | 49.0k | size -= length; |
573 | | |
574 | 49.0k | if (size < 0) |
575 | 38 | { |
576 | 38 | goto bogus_rle; |
577 | 38 | } |
578 | | |
579 | 49.0k | if (xcfdata > xcfdatalimit) |
580 | 23 | { |
581 | 23 | goto bogus_rle; |
582 | 23 | } |
583 | | |
584 | 48.9k | val = *xcfdata++; |
585 | | |
586 | 473k | for (j = 0; j < length; j++) |
587 | 424k | { |
588 | 424k | data = val; |
589 | 424k | switch (i) |
590 | 424k | { |
591 | 244k | case 0: |
592 | 244k | { |
593 | 244k | q->red = ScaleCharToQuantum(data); |
594 | 244k | if ( inDocInfo->image_type == GIMP_GRAY ) |
595 | 112k | { |
596 | 112k | q->green = ScaleCharToQuantum(data); |
597 | 112k | q->blue = ScaleCharToQuantum(data); |
598 | 112k | q->opacity = ScaleCharToQuantum(255-inLayerInfo->opacity); |
599 | 112k | } |
600 | 132k | else |
601 | 132k | { |
602 | 132k | q->green = q->red; |
603 | 132k | q->blue = q->red; |
604 | 132k | q->opacity = ScaleCharToQuantum(255-inLayerInfo->opacity); |
605 | 132k | } |
606 | 244k | break; |
607 | 0 | } |
608 | 83.3k | case 1: |
609 | 83.3k | { |
610 | 83.3k | q->green = ScaleCharToQuantum(data); |
611 | 83.3k | break; |
612 | 0 | } |
613 | 63.9k | case 2: |
614 | 63.9k | { |
615 | 63.9k | q->blue = ScaleCharToQuantum(data); |
616 | 63.9k | break; |
617 | 0 | } |
618 | 27.0k | case 3: |
619 | 27.0k | { |
620 | 27.0k | q->opacity = (Quantum) (data==0 ? TransparentOpacity : |
621 | 27.0k | ScaleCharToQuantum(255-inLayerInfo->opacity)); |
622 | 27.0k | break; |
623 | 0 | } |
624 | 424k | } |
625 | 424k | q++; |
626 | 424k | } |
627 | 48.9k | } |
628 | 52.2k | } |
629 | 1.12k | if (SyncImagePixelsEx(tile_image,&tile_image->exception) == MagickFail) |
630 | 0 | break; |
631 | 1.12k | } |
632 | 33.0k | MagickFreeResourceLimitedMemory(unsigned char *,xcfodata); |
633 | 33.0k | return MagickPass; |
634 | | |
635 | 202 | bogus_rle: |
636 | 202 | if (xcfodata) |
637 | 202 | MagickFreeResourceLimitedMemory(unsigned char *,xcfodata); |
638 | | |
639 | 202 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), "Failed to RLE-decode tile"); |
640 | 202 | ThrowBinaryException(CorruptImageError,CorruptImage,image->filename); |
641 | 0 | return MagickFail; |
642 | 202 | } |
643 | | |
644 | | |
645 | | static MagickPassFail load_level (Image* image, |
646 | | XCFDocInfo* inDocInfo, |
647 | | XCFLayerInfo* |
648 | | inLayerInfo) |
649 | 3.20k | { |
650 | 3.20k | magick_off_t |
651 | 3.20k | saved_pos, |
652 | 3.20k | offset, |
653 | 3.20k | offset2; |
654 | | |
655 | 3.20k | unsigned long |
656 | 3.20k | width, |
657 | 3.20k | height; |
658 | | |
659 | 3.20k | unsigned long |
660 | 3.20k | ntiles, |
661 | 3.20k | ntile_rows, |
662 | 3.20k | ntile_cols; |
663 | | |
664 | 3.20k | size_t |
665 | 3.20k | tile_data_size; |
666 | | |
667 | 3.20k | int |
668 | 3.20k | i; |
669 | | |
670 | 3.20k | Image* |
671 | 3.20k | tile_image; |
672 | | |
673 | 3.20k | int |
674 | 3.20k | destLeft = 0, |
675 | 3.20k | destTop = 0, |
676 | 3.20k | tile_image_width, |
677 | 3.20k | tile_image_height; |
678 | | |
679 | 3.20k | MagickPassFail |
680 | 3.20k | status = MagickPass; |
681 | | |
682 | 3.20k | ExceptionInfo |
683 | 3.20k | *exception = inDocInfo->exception; |
684 | | |
685 | | /* start reading the data */ |
686 | 3.20k | width = ReadBlobMSBLong(image); /* width */ |
687 | 3.20k | height = ReadBlobMSBLong(image); /* height */ |
688 | | |
689 | | /* read in the first tile offset. |
690 | | * if it is '0', then this tile level is empty |
691 | | * and we can simply return. |
692 | | */ |
693 | 3.20k | offset = ReadBlobMSBLong(image); |
694 | | |
695 | 3.20k | if (EOFBlob(image)) |
696 | 3.19k | ThrowBinaryException(CorruptImageError,UnexpectedEndOfFile,image->filename); |
697 | | |
698 | 3.19k | if (offset == 0) |
699 | 1.76k | return MagickPass; |
700 | | |
701 | 1.43k | if (offset >= inDocInfo->file_size) |
702 | 178 | { |
703 | 178 | if (image->logging) |
704 | 178 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
705 | 178 | "Tile offset %ld (file size %lu)!", |
706 | 178 | (long) offset, (unsigned long) inDocInfo->file_size); |
707 | 178 | ThrowBinaryException(CorruptImageError,CorruptImage,image->filename); |
708 | 0 | } |
709 | | |
710 | 1.25k | if (image->logging) |
711 | 1.25k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
712 | 1.25k | "load_level: dimensions %lux%lu, bpp %lu, offset %ld", |
713 | 1.25k | width,height,(unsigned long) inDocInfo->bpp, |
714 | 1.25k | (long) offset); |
715 | | |
716 | | /* |
717 | | Initialise the reference for the in-memory tile-compression |
718 | | */ |
719 | 1.25k | ntile_rows=(height+TILE_HEIGHT-1)/TILE_HEIGHT; |
720 | 1.25k | ntile_cols=(width+TILE_WIDTH-1)/TILE_WIDTH; |
721 | 1.25k | ntiles=ntile_rows*ntile_cols; |
722 | | |
723 | 1.25k | if (image->logging) |
724 | 1.25k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
725 | 1.25k | "Tile: dimensions %lux%lu, number of tiles %lu", |
726 | 1.25k | ntile_cols,ntile_rows,ntiles); |
727 | | |
728 | 64.7k | for (i = 0; i < (long) ntiles; i++) |
729 | 64.6k | { |
730 | 64.6k | status = MagickPass; |
731 | | |
732 | 64.6k | if (offset == 0) |
733 | 248 | { |
734 | 248 | if (image->logging) |
735 | 248 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
736 | 248 | "Tile: offset %ld!", (long) offset); |
737 | 248 | ThrowBinaryException(CorruptImageError,NotEnoughTiles,image->filename); |
738 | 0 | } |
739 | | |
740 | | /* |
741 | | save the current position as it is where the next tile offset |
742 | | is stored. |
743 | | */ |
744 | 64.3k | saved_pos = TellBlob(image); |
745 | 64.3k | if (saved_pos < 0) |
746 | 64.3k | ThrowBinaryException(BlobError,UnableToObtainOffset,image->filename); |
747 | | |
748 | | /* |
749 | | read in the offset of the next tile so we can calculate the |
750 | | amount of data needed for this tile |
751 | | */ |
752 | 64.3k | offset2 = ReadBlobMSBLong(image); |
753 | 64.3k | if (EOFBlob(image)) |
754 | 64.2k | ThrowBinaryException(CorruptImageError,UnexpectedEndOfFile,image->filename); |
755 | 64.2k | if (offset2 >= inDocInfo->file_size) |
756 | 232 | { |
757 | 232 | if (image->logging) |
758 | 232 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
759 | 232 | "Tile: offset %ld (file size %lu)!", |
760 | 232 | (long) offset2, (unsigned long) inDocInfo->file_size); |
761 | 232 | ThrowBinaryException(CorruptImageError,CorruptImage,image->filename); |
762 | 0 | } |
763 | | |
764 | | /* verify that seek position is in file */ |
765 | 64.0k | if ((magick_off_t) offset >= GetBlobSize(image)) |
766 | 0 | { |
767 | 0 | if (image->logging) |
768 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
769 | 0 | "Tile offset %" MAGICK_OFF_F "d is outside file bounds", |
770 | 0 | (magick_off_t) offset); |
771 | 0 | ThrowBinaryException(CorruptImageError,InsufficientImageDataInFile,image->filename); |
772 | 0 | } |
773 | | |
774 | | /* seek to the tile offset */ |
775 | 64.0k | if (SeekBlob(image, offset, SEEK_SET) != offset) |
776 | 64.0k | ThrowBinaryException(CorruptImageError,InsufficientImageDataInFile,image->filename); |
777 | | |
778 | | /* allocate the image for the tile |
779 | | NOTE: the last tile in a row or column may not be a full tile! |
780 | | */ |
781 | 64.0k | tile_image_width=(size_t) (destLeft == (int) ntile_cols-1 ? |
782 | 33.7k | (int) width % TILE_WIDTH : TILE_WIDTH); |
783 | 64.0k | if (tile_image_width == 0) tile_image_width=TILE_WIDTH; |
784 | 64.0k | tile_image_height = (size_t) (destTop == (int) ntile_rows-1 ? |
785 | 55.5k | (int) height % TILE_HEIGHT : TILE_HEIGHT); |
786 | 64.0k | if (tile_image_height == 0) tile_image_height=TILE_HEIGHT; |
787 | 64.0k | tile_image=CloneImage(inLayerInfo->image, tile_image_width, |
788 | 64.0k | tile_image_height,True,exception); |
789 | | |
790 | 64.0k | if (tile_image == (Image *) NULL) |
791 | 0 | return MagickFail; |
792 | | |
793 | | /* |
794 | | Compute the tile data size. |
795 | | */ |
796 | 64.0k | if (offset2 > offset) |
797 | 2.50k | { |
798 | | /* |
799 | | Tile data size is simply the difference in file offsets. |
800 | | */ |
801 | 2.50k | tile_data_size=offset2-offset; |
802 | 2.50k | if (image->logging) |
803 | 2.50k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
804 | 2.50k | "Tile: start offset=%ld, end offset=%ld, data size=%lu", |
805 | 2.50k | (long) offset,(long) offset2, |
806 | 2.50k | (unsigned long) tile_data_size); |
807 | 2.50k | } |
808 | 61.5k | else |
809 | 61.5k | { |
810 | 61.5k | size_t |
811 | 61.5k | packet_size=4; |
812 | | |
813 | 61.5k | if (inDocInfo->image_type == GIMP_GRAY) |
814 | 35.6k | packet_size=1; |
815 | | |
816 | 61.5k | if (COMPRESS_NONE == inDocInfo->compression) |
817 | 30.4k | { |
818 | | /* |
819 | | If compression is not used then enforce expected tile size. |
820 | | */ |
821 | 30.4k | tile_data_size = MagickArraySize(MagickArraySize(tile_image->columns,tile_image->rows),packet_size); |
822 | 30.4k | } |
823 | 31.0k | else |
824 | 31.0k | { |
825 | | /* |
826 | | Estimate the tile size. First we estimate the tile |
827 | | size, allowing for a possible expansion factor of 1.5. |
828 | | Then we truncate to the file length, whichever is |
829 | | smallest. |
830 | | */ |
831 | 31.0k | offset2 = (magick_off_t) ((double) offset + (double) tile_image->columns*tile_image->rows * packet_size * 1.5); |
832 | 31.0k | tile_data_size = (size_t) offset2-offset; |
833 | 31.0k | if (image->logging) |
834 | 31.0k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
835 | 31.0k | "We estimated tile data size: %lu", |
836 | 31.0k | (unsigned long) tile_data_size); |
837 | 31.0k | offset2 = Min(offset2,GetBlobSize(image)); |
838 | 31.0k | tile_data_size = (size_t) offset2-offset; |
839 | 31.0k | if (image->logging) |
840 | 31.0k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
841 | 31.0k | "Final tile data size: %lu", |
842 | 31.0k | (unsigned long) tile_data_size); |
843 | 31.0k | if (offset2 <= offset) |
844 | 0 | { |
845 | 0 | DestroyImage(tile_image); |
846 | 0 | ThrowBinaryException(CorruptImageError,UnexpectedEndOfFile,image->filename); |
847 | 0 | } |
848 | 31.0k | } |
849 | 61.5k | } |
850 | | |
851 | | /* read in the tile */ |
852 | 64.0k | switch (inDocInfo->compression) |
853 | 64.0k | { |
854 | 30.7k | case COMPRESS_NONE: |
855 | 30.7k | status=load_tile(image,tile_image,inDocInfo,inLayerInfo, |
856 | 30.7k | tile_data_size); |
857 | 30.7k | break; |
858 | 33.2k | case COMPRESS_RLE: |
859 | 33.2k | status=load_tile_rle(image,tile_image,inDocInfo,inLayerInfo, |
860 | 33.2k | tile_data_size); |
861 | 33.2k | break; |
862 | 1 | case COMPRESS_ZLIB: |
863 | 1 | DestroyImage(tile_image); |
864 | 1 | ThrowBinaryException(CoderError,ZipCompressionNotSupported, |
865 | 1 | image->filename); |
866 | 3 | case COMPRESS_FRACTAL: |
867 | 3 | DestroyImage(tile_image); |
868 | 3 | ThrowBinaryException(CoderError,FractalCompressionNotSupported, |
869 | 3 | image->filename); |
870 | 64.0k | } |
871 | | |
872 | 64.0k | if (MagickPass == status) |
873 | 63.5k | { |
874 | | /* |
875 | | Composite the tile onto the layer's image, and then |
876 | | destroy it. We temporarily disable the progress monitor |
877 | | so that the user does not see composition of individual |
878 | | tiles. |
879 | | */ |
880 | | #if 0 |
881 | | const PixelPacket |
882 | | *p; |
883 | | |
884 | | PixelPacket |
885 | | *q; |
886 | | |
887 | | long |
888 | | canvas_x, |
889 | | canvas_y, |
890 | | y; |
891 | | |
892 | | unsigned long |
893 | | tile_width; |
894 | | |
895 | | canvas_x=destLeft*TILE_WIDTH; |
896 | | tile_width=tile_image->columns; |
897 | | for (y=0; y < (long) tile_image->columns; y++) |
898 | | { |
899 | | canvas_y=destTop*TILE_HEIGHT+y; |
900 | | p=AcquireImagePixels(tile_image,0,y,tile_image->columns,1, |
901 | | &inLayerInfo->image->exception); |
902 | | q=GetImagePixels(inLayerInfo->image,canvas_x,canvas_y, |
903 | | tile_image->columns,1); |
904 | | if ((p != (const PixelPacket *) NULL) && (q != (PixelPacket *) NULL)) |
905 | | (void) memcpy(q,p,tile_image->columns*sizeof(PixelPacket)); |
906 | | else |
907 | | printf("null pointer canvas: %lux%lu tile: %lux%lu+%ld+%ld !\n", |
908 | | inLayerInfo->image->columns,inLayerInfo->image->rows, |
909 | | tile_width,1LU,canvas_x,canvas_y); |
910 | | } |
911 | | #else |
912 | 63.5k | MonitorHandler |
913 | 63.5k | handler; |
914 | | |
915 | 63.5k | long |
916 | 63.5k | canvas_x, |
917 | 63.5k | canvas_y; |
918 | | |
919 | 63.5k | canvas_x=destLeft*TILE_WIDTH; |
920 | 63.5k | canvas_y=destTop*TILE_HEIGHT; |
921 | 63.5k | handler=SetMonitorHandler((MonitorHandler) NULL); |
922 | 63.5k | (void) CompositeImageRegion(CopyCompositeOp,NULL,tile_image->columns, |
923 | 63.5k | tile_image->rows,tile_image,0,0, |
924 | 63.5k | inLayerInfo->image,canvas_x, |
925 | 63.5k | canvas_y,&inLayerInfo->image->exception); |
926 | 63.5k | (void) SetMonitorHandler(handler); |
927 | 63.5k | #endif |
928 | 63.5k | } |
929 | 64.0k | DestroyImage(tile_image); |
930 | 64.0k | #if !defined(__COVERITY__) /* 384797 Unused value */ |
931 | 64.0k | tile_image = (Image *) NULL; |
932 | 64.0k | #endif /* if !defined(__COVERITY__) */ |
933 | | |
934 | | /* adjust tile position */ |
935 | 64.0k | destLeft++; |
936 | 64.0k | if (destLeft >= (int) ntile_cols) |
937 | 33.7k | { |
938 | 33.7k | destLeft = 0; |
939 | 33.7k | destTop++; |
940 | 33.7k | } |
941 | | |
942 | 64.0k | if (MagickPass != status) |
943 | 488 | return status; |
944 | | |
945 | | /* restore the saved position so we'll be ready to |
946 | | * read the next offset. |
947 | | */ |
948 | 63.5k | (void) SeekBlob(image, saved_pos, SEEK_SET); |
949 | | |
950 | | /* read in the offset of the next tile */ |
951 | 63.5k | offset = ReadBlobMSBLong(image); |
952 | 63.5k | if (EOFBlob(image)) |
953 | 63.5k | ThrowBinaryException(CorruptImageError,UnexpectedEndOfFile,image->filename); |
954 | 63.5k | if (offset != 0) |
955 | 63.1k | if (!MagickMonitorFormatted(offset,inDocInfo->file_size, |
956 | 63.1k | &image->exception,LoadImageText, |
957 | 63.1k | image->filename, |
958 | 63.1k | image->columns,image->rows)) |
959 | 0 | break; |
960 | 63.5k | } |
961 | | |
962 | 133 | if (offset != 0) |
963 | 52 | { |
964 | 52 | ThrowBinaryException(CorruptImageError,CorruptImage,image->filename); |
965 | 0 | } |
966 | 81 | else |
967 | 81 | { |
968 | 81 | (void) MagickMonitorFormatted(inDocInfo->file_size, |
969 | 81 | inDocInfo->file_size+1,&image->exception, |
970 | 81 | LoadImageText,image->filename, |
971 | 81 | image->columns,image->rows); |
972 | 81 | } |
973 | | |
974 | 81 | return MagickPass; |
975 | 133 | } |
976 | | |
977 | | static MagickPassFail load_hierarchy (Image *image, XCFDocInfo* inDocInfo, XCFLayerInfo* |
978 | | inLayer) |
979 | 4.41k | { |
980 | 4.41k | unsigned long |
981 | 4.41k | width, |
982 | 4.41k | height; |
983 | | |
984 | 4.41k | magick_off_t |
985 | 4.41k | saved_pos, |
986 | 4.41k | offset; |
987 | | |
988 | 4.41k | unsigned long |
989 | 4.41k | junk; |
990 | | |
991 | 4.41k | width=ReadBlobMSBLong(image); /* width */ |
992 | 4.41k | height=ReadBlobMSBLong(image); /* height */ |
993 | 4.41k | inDocInfo->bpp = ReadBlobMSBLong(image); /* bpp */ |
994 | | |
995 | | /* load in the levels...we make sure that the number of levels |
996 | | * calculated when the TileManager was created is the same |
997 | | * as the number of levels found in the file. |
998 | | */ |
999 | 4.41k | offset = ReadBlobMSBLong(image); /* top level */ |
1000 | | |
1001 | 4.41k | if (EOFBlob(image)) |
1002 | 4.40k | ThrowBinaryException(CorruptImageError,UnexpectedEndOfFile,image->filename); |
1003 | | |
1004 | 4.40k | if (image->logging) |
1005 | 4.40k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1006 | 4.40k | "load_hierarchy: dimensions %lux%lu, bpp=%lu," |
1007 | 4.40k | " offset=%" MAGICK_OFF_F "d", |
1008 | 4.40k | width,height,(unsigned long) inDocInfo->bpp, |
1009 | 4.40k | (magick_off_t) offset); |
1010 | | |
1011 | | /* verify that seek position is in file */ |
1012 | 4.40k | if ((magick_off_t) offset >= GetBlobSize(image)) |
1013 | 189 | { |
1014 | 189 | if (image->logging) |
1015 | 189 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1016 | 189 | "Hierarchy offset %" MAGICK_OFF_F "d is outside file bounds", |
1017 | 189 | (magick_off_t) offset); |
1018 | 189 | ThrowBinaryException(CorruptImageError,InsufficientImageDataInFile,image->filename); |
1019 | 0 | } |
1020 | | |
1021 | | /* discard offsets for layers below first, if any. |
1022 | | */ |
1023 | 4.21k | do |
1024 | 171k | { |
1025 | 171k | junk = ReadBlobMSBLong(image); |
1026 | 171k | } |
1027 | 171k | while ((junk != 0) && (!EOFBlob(image))); |
1028 | | |
1029 | 4.21k | if (EOFBlob(image)) |
1030 | 3.20k | ThrowBinaryException(CorruptImageError,UnexpectedEndOfFile,image->filename); |
1031 | | |
1032 | | /* save the current position as it is where the |
1033 | | * next level offset is stored. |
1034 | | */ |
1035 | 3.20k | saved_pos = TellBlob(image); |
1036 | 3.20k | if (saved_pos < 0) |
1037 | 3.20k | ThrowBinaryException(BlobError,UnableToObtainOffset,image->filename); |
1038 | | |
1039 | | /* verify that seek position is in file */ |
1040 | 3.20k | if ((magick_off_t) offset >= GetBlobSize(image)) |
1041 | 0 | { |
1042 | 0 | if (image->logging) |
1043 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1044 | 0 | "Level offset %" MAGICK_OFF_F "d is outside file bounds", |
1045 | 0 | (magick_off_t) offset); |
1046 | 0 | ThrowBinaryException(CorruptImageError,InsufficientImageDataInFile,image->filename); |
1047 | 0 | } |
1048 | | |
1049 | | /* seek to the level offset */ |
1050 | 3.20k | if (SeekBlob(image, offset, SEEK_SET) != offset) |
1051 | 3.20k | ThrowBinaryException(CorruptImageError,InsufficientImageDataInFile,image->filename); |
1052 | | |
1053 | | /* read in the level */ |
1054 | 3.20k | if (load_level (image, inDocInfo, inLayer) == MagickFail) |
1055 | 1.35k | return MagickFail; |
1056 | | |
1057 | | /* restore the saved position so we'll be ready to |
1058 | | * read the next offset. |
1059 | | */ |
1060 | 1.84k | if (SeekBlob(image, saved_pos, SEEK_SET) != saved_pos) |
1061 | 1.84k | ThrowBinaryException(BlobError,UnableToSeekToOffset,image->filename); |
1062 | | |
1063 | 1.84k | return MagickPass; |
1064 | 1.84k | } |
1065 | | |
1066 | | |
1067 | | static MagickPassFail ReadOneLayer( Image* image, XCFDocInfo* inDocInfo, XCFLayerInfo* |
1068 | | outLayer ) |
1069 | 9.86k | { |
1070 | 9.86k | unsigned int |
1071 | 9.86k | i; |
1072 | | |
1073 | 9.86k | unsigned int |
1074 | 9.86k | foundPropEnd = 0; |
1075 | | |
1076 | 9.86k | unsigned long |
1077 | 9.86k | hierarchy_offset, |
1078 | 9.86k | layer_mask_offset; |
1079 | | |
1080 | 9.86k | magick_off_t start_offset; |
1081 | | |
1082 | 9.86k | start_offset = TellBlob(image); |
1083 | | |
1084 | | /* clear the block! */ |
1085 | 9.86k | (void) memset( outLayer, 0, sizeof( XCFLayerInfo ) ); |
1086 | | |
1087 | | /* read in the layer width, height, type and name */ |
1088 | 9.86k | outLayer->width = ReadBlobMSBLong(image); |
1089 | 9.86k | outLayer->height = ReadBlobMSBLong(image); |
1090 | 9.86k | outLayer->type = ReadBlobMSBLong(image); |
1091 | 9.86k | (void) ReadBlobStringWithLongSize(image, outLayer->name, |
1092 | 9.86k | sizeof(outLayer->name)); |
1093 | | |
1094 | 9.86k | if (EOFBlob(image)) |
1095 | 7.22k | ThrowBinaryException(CorruptImageError,UnexpectedEndOfFile,image->filename); |
1096 | | |
1097 | 7.22k | if (image->logging) |
1098 | 7.22k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1099 | 7.22k | "Loading layer \"%s\", dimensions %lux%lu, type %lu", |
1100 | 7.22k | outLayer->name, |
1101 | 7.22k | (unsigned long) outLayer->width, |
1102 | 7.22k | (unsigned long) outLayer->height, |
1103 | 7.22k | (unsigned long) outLayer->type); |
1104 | | |
1105 | 7.22k | if ((outLayer->width == 0) || (outLayer->height == 0)) |
1106 | 6.87k | ThrowBinaryException(CorruptImageError,ImproperImageHeader,image->filename); |
1107 | | |
1108 | | /* allocate the image for this layer */ |
1109 | 6.87k | outLayer->image=CloneImage(image,outLayer->width, outLayer->height,True, |
1110 | 6.87k | &image->exception); |
1111 | 6.87k | if (outLayer->image == (Image *) NULL) |
1112 | 0 | return MagickFail; |
1113 | | |
1114 | | /* read the layer properties! */ |
1115 | 6.87k | foundPropEnd = 0; |
1116 | 23.3k | while ( !foundPropEnd && !EOFBlob(image) ) |
1117 | 16.5k | { |
1118 | 16.5k | PropType prop_type = (PropType) ReadBlobMSBLong(image); |
1119 | 16.5k | size_t prop_size = ReadBlobMSBLong(image); |
1120 | | |
1121 | 16.5k | switch (prop_type) |
1122 | 16.5k | { |
1123 | 6.46k | case PROP_END: |
1124 | 6.46k | foundPropEnd = 1; |
1125 | 6.46k | break; |
1126 | 499 | case PROP_ACTIVE_LAYER: |
1127 | 499 | outLayer->active = 1; |
1128 | 499 | break; |
1129 | 402 | case PROP_FLOATING_SELECTION: |
1130 | 402 | outLayer->floating_offset = ReadBlobMSBLong(image); |
1131 | 402 | break; |
1132 | 836 | case PROP_OPACITY: |
1133 | 836 | outLayer->opacity = ReadBlobMSBLong(image); |
1134 | 836 | if (outLayer->opacity > 255) |
1135 | 574 | outLayer->opacity = 255; |
1136 | 836 | break; |
1137 | 270 | case PROP_VISIBLE: |
1138 | 270 | outLayer->visible = ReadBlobMSBLong(image); |
1139 | 270 | break; |
1140 | 224 | case PROP_LINKED: |
1141 | 224 | outLayer->linked = ReadBlobMSBLong(image); |
1142 | 224 | break; |
1143 | 264 | case PROP_PRESERVE_TRANSPARENCY: |
1144 | 264 | outLayer->preserve_trans = ReadBlobMSBLong(image); |
1145 | 264 | break; |
1146 | 226 | case PROP_APPLY_MASK: |
1147 | 226 | outLayer->apply_mask = ReadBlobMSBLong(image); |
1148 | 226 | break; |
1149 | 320 | case PROP_EDIT_MASK: |
1150 | 320 | outLayer->edit_mask = ReadBlobMSBLong(image); |
1151 | 320 | break; |
1152 | 218 | case PROP_SHOW_MASK: |
1153 | 218 | outLayer->show_mask = ReadBlobMSBLong(image); |
1154 | 218 | break; |
1155 | 523 | case PROP_OFFSETS: |
1156 | 523 | outLayer->offset_x = (magick_int32_t) ReadBlobMSBLong(image); |
1157 | 523 | outLayer->offset_y = (magick_int32_t) ReadBlobMSBLong(image); |
1158 | 523 | break; |
1159 | 279 | case PROP_MODE: |
1160 | 279 | outLayer->mode = ReadBlobMSBLong(image); |
1161 | 279 | break; |
1162 | 247 | case PROP_TATTOO: |
1163 | 247 | outLayer->preserve_trans = ReadBlobMSBLong(image); |
1164 | 247 | break; |
1165 | 578 | case PROP_PARASITES: |
1166 | 578 | { |
1167 | 1.65M | for (i=0; i < prop_size; i++ ) |
1168 | 1.64M | if (ReadBlobByte(image) == EOF) |
1169 | 79 | break; |
1170 | | |
1171 | | /* |
1172 | | long base = info->cp; |
1173 | | GimpParasite *p; |
1174 | | while (info->cp - base < prop_size) |
1175 | | { |
1176 | | p = xcf_load_parasite(info); |
1177 | | gimp_drawable_parasite_attach(GIMP_DRAWABLE(layer), p); |
1178 | | gimp_parasite_free(p); |
1179 | | } |
1180 | | if (info->cp - base != prop_size) |
1181 | | g_message ("Error detected while loading a layer's parasites"); |
1182 | | */ |
1183 | 578 | } |
1184 | 578 | break; |
1185 | 5.14k | default: |
1186 | | /* g_message ("unexpected/unknown layer property: %d (skipping)", |
1187 | | prop_type); */ |
1188 | | |
1189 | 5.14k | { |
1190 | 5.14k | int buf[16]; |
1191 | 5.14k | size_t amount; |
1192 | | |
1193 | | /* read over it... */ |
1194 | 22.2k | while (prop_size > 0 && !EOFBlob(image)) |
1195 | 17.1k | { |
1196 | 17.1k | amount = Min (16, prop_size); |
1197 | 231k | for (i=0; i < amount; i++) |
1198 | 214k | if (ReadBlob(image, amount, &buf) != amount) |
1199 | 124 | break; |
1200 | 17.1k | prop_size -= Min (16, amount); |
1201 | 17.1k | } |
1202 | 5.14k | } |
1203 | 5.14k | break; |
1204 | 16.5k | } |
1205 | 16.5k | } |
1206 | 6.87k | if (EOFBlob(image)) |
1207 | 5.85k | ThrowBinaryException(CorruptImageError,UnexpectedEndOfFile,image->filename); |
1208 | | |
1209 | 5.85k | if (!foundPropEnd) |
1210 | 0 | return MagickFail; |
1211 | | |
1212 | | /* clear the image based on the layer opacity */ |
1213 | 5.85k | if (SetImage(outLayer->image,(Quantum)(255-outLayer->opacity)) != MagickPass) |
1214 | 541 | return MagickFail; |
1215 | | |
1216 | | /* set the compositing mode */ |
1217 | 5.31k | outLayer->image->compose = GIMPBlendModeToCompositeOperator( outLayer->mode ); |
1218 | 5.31k | if ( outLayer->visible == False ) |
1219 | 5.27k | { |
1220 | | /* BOGUS: should really be separate member var! */ |
1221 | 5.27k | outLayer->image->compose = NoCompositeOp; |
1222 | 5.27k | } |
1223 | | |
1224 | | /* read the hierarchy and layer mask offsets */ |
1225 | 5.31k | hierarchy_offset = ReadBlobMSBLong(image); |
1226 | 5.31k | layer_mask_offset = ReadBlobMSBLong(image); |
1227 | | |
1228 | | /* verify that seek position is in file */ |
1229 | 5.31k | if ((magick_off_t) hierarchy_offset >= GetBlobSize(image)) |
1230 | 760 | { |
1231 | 760 | if (image->logging) |
1232 | 760 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1233 | 760 | "Hierarchy offset %" MAGICK_OFF_F "d is outside file bounds", |
1234 | 760 | (magick_off_t) hierarchy_offset); |
1235 | 760 | ThrowBinaryException(CorruptImageError,InsufficientImageDataInFile,image->filename); |
1236 | 0 | } |
1237 | | /* |
1238 | | Verify that seek position is not too small. |
1239 | | Seek position provides ample opportunity for abuse. |
1240 | | */ |
1241 | 4.55k | if ((magick_off_t) hierarchy_offset <= start_offset) |
1242 | 136 | { |
1243 | 136 | if (image->logging) |
1244 | 136 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1245 | 136 | "Hierarchy offset %" MAGICK_OFF_F "d is unreasonable", |
1246 | 136 | (magick_off_t) hierarchy_offset); |
1247 | 136 | ThrowBinaryException(CorruptImageError,ImproperImageHeader,image->filename); |
1248 | 0 | } |
1249 | | |
1250 | | /* read in the hierarchy */ |
1251 | 4.41k | if (SeekBlob(image, hierarchy_offset, SEEK_SET) != (magick_off_t) hierarchy_offset) |
1252 | 4.41k | ThrowBinaryException(CorruptImageError,InsufficientImageDataInFile,image->filename); |
1253 | 4.41k | if (image->logging) |
1254 | 4.41k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1255 | 4.41k | "Hierarchy offset %" MAGICK_OFF_F "d", |
1256 | 4.41k | (magick_off_t) hierarchy_offset); |
1257 | 4.41k | if (load_hierarchy (image, inDocInfo, outLayer) == MagickFail) |
1258 | 2.57k | return MagickFail; |
1259 | | |
1260 | | /* read in the layer mask */ |
1261 | 1.84k | if (layer_mask_offset != 0) |
1262 | 1.02k | { |
1263 | | /* verify that seek position is in file */ |
1264 | 1.02k | if ((magick_off_t) layer_mask_offset >= GetBlobSize(image)) |
1265 | 362 | { |
1266 | 362 | if (image->logging) |
1267 | 362 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1268 | 362 | "Layer mask offset %" MAGICK_OFF_F "d is outside file bounds", |
1269 | 362 | (magick_off_t) layer_mask_offset); |
1270 | 362 | ThrowBinaryException(CorruptImageError,InsufficientImageDataInFile,image->filename); |
1271 | 0 | } |
1272 | | /* |
1273 | | Verify that seek position is not too small. |
1274 | | Seek position provides ample opportunity for abuse. |
1275 | | */ |
1276 | 662 | if ((magick_off_t) layer_mask_offset <= start_offset) |
1277 | 51 | { |
1278 | 51 | if (image->logging) |
1279 | 51 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1280 | 51 | "Layer mask offset %" MAGICK_OFF_F "d is unreasonable", |
1281 | 51 | (magick_off_t) hierarchy_offset); |
1282 | 51 | ThrowBinaryException(CorruptImageError,ImproperImageHeader,image->filename); |
1283 | 0 | } |
1284 | 611 | if (SeekBlob(image, layer_mask_offset, SEEK_SET) != (magick_off_t) layer_mask_offset) |
1285 | 611 | ThrowBinaryException(CorruptImageError,InsufficientImageDataInFile,image->filename); |
1286 | | |
1287 | | #if 0 /* BOGUS: support layer masks! */ |
1288 | | layer_mask = xcf_load_layer_mask (info, gimage); |
1289 | | if (!layer_mask) |
1290 | | goto error; |
1291 | | |
1292 | | /* set the offsets of the layer_mask */ |
1293 | | GIMP_DRAWABLE (layer_mask)->offset_x = GIMP_DRAWABLE (layer)->offset_x; |
1294 | | GIMP_DRAWABLE (layer_mask)->offset_y = GIMP_DRAWABLE (layer)->offset_y; |
1295 | | |
1296 | | gimp_layer_add_mask (layer, layer_mask, False); |
1297 | | |
1298 | | layer->mask->apply_mask = apply_mask; |
1299 | | layer->mask->edit_mask = edit_mask; |
1300 | | layer->mask->show_mask = show_mask; |
1301 | | #endif |
1302 | 611 | } |
1303 | | |
1304 | | /* attach the floating selection... */ |
1305 | | #if 0 /* BOGUS: we may need to read this, even if we don't support it! */ |
1306 | | if (add_floating_sel) |
1307 | | { |
1308 | | GimpLayer *floating_sel; |
1309 | | |
1310 | | floating_sel = info->floating_sel; |
1311 | | floating_sel_attach (floating_sel, GIMP_DRAWABLE (layer)); |
1312 | | } |
1313 | | #endif |
1314 | | |
1315 | 1.43k | return MagickPass; |
1316 | 1.84k | } |
1317 | | |
1318 | | /* |
1319 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1320 | | % % |
1321 | | % % |
1322 | | % % |
1323 | | % R e a d X C F I m a g e % |
1324 | | % % |
1325 | | % % |
1326 | | % % |
1327 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1328 | | % |
1329 | | % Method ReadXCFImage reads a GIMP (GNU Image Manipulation Program) image |
1330 | | % file and returns it. It allocates the memory necessary for the new Image |
1331 | | % structure and returns a pointer to the new image. |
1332 | | % |
1333 | | % The format of the ReadXCFImage method is: |
1334 | | % |
1335 | | % image=ReadXCFImage(image_info) |
1336 | | % |
1337 | | % A description of each parameter follows: |
1338 | | % |
1339 | | % o image: Method ReadXCFImage returns a pointer to the image after |
1340 | | % reading. A null image is returned if there is a memory shortage or |
1341 | | % if the image cannot be read. |
1342 | | % |
1343 | | % o image_info: Specifies a pointer to a ImageInfo structure. |
1344 | | % |
1345 | | % o exception: return any errors or warnings in this structure. |
1346 | | % |
1347 | | % |
1348 | | */ |
1349 | | #define DestroyLayerInfo(number_layers,layer_info) \ |
1350 | 8.43k | do { \ |
1351 | 8.43k | size_t \ |
1352 | 8.43k | j; \ |
1353 | 8.43k | \ |
1354 | 8.43k | if (layer_info != (XCFLayerInfo *) NULL) \ |
1355 | 8.43k | { \ |
1356 | 16.8k | for (j=0; j < number_layers; j++) \ |
1357 | 8.43k | { \ |
1358 | 8.43k | if (layer_info[j].image != (Image *) NULL) \ |
1359 | 8.43k | { \ |
1360 | 5.44k | DestroyImage(layer_info[j].image); \ |
1361 | 5.44k | layer_info[j].image = (Image *) NULL; \ |
1362 | 5.44k | } \ |
1363 | 8.43k | } \ |
1364 | 8.43k | } \ |
1365 | 8.43k | MagickFreeResourceLimitedMemory(XCFLayerInfo *,layer_info); \ |
1366 | 8.43k | } while (0); |
1367 | | static Image *ReadXCFImage(const ImageInfo *image_info,ExceptionInfo *exception) |
1368 | 163k | { |
1369 | 163k | char |
1370 | 163k | magick[14]; |
1371 | | |
1372 | 163k | Image |
1373 | 163k | *image; |
1374 | | |
1375 | 163k | unsigned int |
1376 | 163k | status; |
1377 | | |
1378 | 163k | unsigned long |
1379 | 163k | i, |
1380 | 163k | image_type; |
1381 | | |
1382 | 163k | int |
1383 | 163k | foundPropEnd = 0; |
1384 | | |
1385 | 163k | size_t |
1386 | 163k | count; |
1387 | | |
1388 | 163k | XCFDocInfo |
1389 | 163k | doc_info; |
1390 | | |
1391 | | /* |
1392 | | Open image file. |
1393 | | */ |
1394 | 163k | assert(image_info != (const ImageInfo *) NULL); |
1395 | 163k | assert(image_info->signature == MagickSignature); |
1396 | 163k | assert(exception != (ExceptionInfo *) NULL); |
1397 | 163k | assert(exception->signature == MagickSignature); |
1398 | 163k | image=AllocateImage(image_info); |
1399 | 163k | status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); |
1400 | 163k | if (status == MagickFail) |
1401 | 163k | ThrowReaderException(FileOpenError,UnableToOpenFile,image); |
1402 | 163k | count=ReadBlob(image,14,(char *) magick); |
1403 | 163k | if ((count != 14) || |
1404 | 159k | (LocaleNCompare((char *) magick,"gimp xcf",8) != 0)) |
1405 | 159k | ThrowReaderException(CorruptImageError,ImproperImageHeader,image); |
1406 | | /* clear the docinfo stuff */ |
1407 | 159k | (void) memset( &doc_info, 0, sizeof(XCFDocInfo)); |
1408 | 159k | doc_info.exception = exception; |
1409 | | |
1410 | | /* read the three simple values */ |
1411 | 159k | image->columns = doc_info.width = ReadBlobMSBLong(image); |
1412 | 159k | image->rows = doc_info.height = ReadBlobMSBLong(image); |
1413 | 159k | image_type = doc_info.image_type = ReadBlobMSBLong(image); |
1414 | | |
1415 | | /* |
1416 | | Get file size to use for validation later. |
1417 | | */ |
1418 | 159k | doc_info.file_size=GetBlobSize(image); |
1419 | | |
1420 | 159k | if (image->logging) |
1421 | 159k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1422 | 159k | "XCF dimensions %lux%lu, type %s", |
1423 | 159k | image->columns,image->rows, |
1424 | 159k | (image_type == GIMP_RGB ? "RGB" : |
1425 | 159k | (image_type == GIMP_GRAY ? "GRAY" : |
1426 | 35.3k | (image_type == GIMP_INDEXED ? "INDEXED" : |
1427 | 26.3k | "unknown")))); |
1428 | | |
1429 | 159k | if ((image->columns == 0) || (image->rows == 0)) |
1430 | 148k | ThrowReaderException(CorruptImageError,NegativeOrZeroImageSize,image); |
1431 | | |
1432 | | /* setup some things about the image...*/ |
1433 | 148k | image->compression=NoCompression; |
1434 | 148k | image->depth = 8; |
1435 | 148k | if ( image_type == GIMP_RGB ) |
1436 | 118k | { |
1437 | 118k | image->colorspace=RGBColorspace; |
1438 | 118k | } |
1439 | 30.8k | else if ( image_type == GIMP_GRAY ) |
1440 | 9.03k | { |
1441 | 9.03k | image->colorspace=GRAYColorspace; |
1442 | 9.03k | } |
1443 | 21.8k | else if ( image_type == GIMP_INDEXED ) |
1444 | 2.30k | { |
1445 | 2.30k | ThrowReaderException(CoderError,ColormapTypeNotSupported,image); |
1446 | 0 | } |
1447 | 19.5k | else |
1448 | 19.5k | { |
1449 | 19.5k | ThrowReaderException(CorruptImageError,ImageTypeNotSupported,image); |
1450 | 0 | } |
1451 | | |
1452 | 127k | if (CheckImagePixelLimits(image, exception) != MagickPass) |
1453 | 106k | ThrowReaderException(ResourceLimitError,ImagePixelLimitExceeded,image); |
1454 | | |
1455 | | /* |
1456 | | SetImage can be very expensive but we do it here because it is |
1457 | | expected that the canvas is initialized to opaque-black and |
1458 | | operations may be done using uninitialized pixels if we don't |
1459 | | initialize here. |
1460 | | */ |
1461 | 106k | SetRedSample(&image->background_color,0); |
1462 | 106k | SetGreenSample(&image->background_color,0); |
1463 | 106k | SetBlueSample(&image->background_color,0); |
1464 | 106k | SetOpacitySample(&image->background_color,OpaqueOpacity); |
1465 | 106k | if (SetImage(image,OpaqueOpacity) != MagickPass) |
1466 | 55.3k | ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image); /* ??? */ |
1467 | | |
1468 | 51.4k | image->matte=True; /* XCF always has a matte! */ |
1469 | | |
1470 | | /* read properties */ |
1471 | 104k | while ( !foundPropEnd && !EOFBlob(image) ) |
1472 | 79.0k | { |
1473 | 79.0k | PropType prop_type = (PropType) ReadBlobMSBLong(image); |
1474 | 79.0k | size_t prop_size = ReadBlobMSBLong(image); |
1475 | | |
1476 | 79.0k | switch ( prop_type ) |
1477 | 79.0k | { |
1478 | 21.2k | case PROP_END: |
1479 | 21.2k | foundPropEnd = 1; |
1480 | 21.2k | break; |
1481 | | |
1482 | 4.51k | case PROP_COLORMAP: |
1483 | | /* BOGUS: just skip it for now */ |
1484 | 1.04M | for (i=0; i <prop_size; i++ ) |
1485 | 1.03M | if (ReadBlobByte(image) == EOF) |
1486 | 3.74k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
1487 | | /* |
1488 | | if (info->file_version == 0) |
1489 | | { |
1490 | | gint i; |
1491 | | |
1492 | | g_message (_("XCF warning: version 0 of XCF file format\n" |
1493 | | "did not save indexed colormaps correctly.\n" |
1494 | | "Substituting grayscale map.")); |
1495 | | info->cp += |
1496 | | xcf_read_int32 (info->fp, (guint32*) &gimage->num_cols, 1); |
1497 | | gimage->cmap = g_new (guchar, gimage->num_cols*3); |
1498 | | xcf_seek_pos (info, info->cp + gimage->num_cols); |
1499 | | for (i = 0; i<gimage->num_cols; i++) |
1500 | | { |
1501 | | gimage->cmap[i*3+0] = i; |
1502 | | gimage->cmap[i*3+1] = i; |
1503 | | gimage->cmap[i*3+2] = i; |
1504 | | } |
1505 | | } |
1506 | | else |
1507 | | { |
1508 | | info->cp += |
1509 | | xcf_read_int32 (info->fp, (guint32*) &gimage->num_cols, 1); |
1510 | | gimage->cmap = g_new (guchar, gimage->num_cols*3); |
1511 | | info->cp += |
1512 | | xcf_read_int8 (info->fp, |
1513 | | (guint8*) gimage->cmap, gimage->num_cols*3); |
1514 | | } |
1515 | | */ |
1516 | 3.74k | break; |
1517 | | |
1518 | 8.05k | case PROP_COMPRESSION: |
1519 | 8.05k | { |
1520 | 8.05k | int c; |
1521 | 8.05k | c = ReadBlobByte(image); |
1522 | 8.05k | if (c == EOF) |
1523 | 1.53k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile, |
1524 | 8.05k | image); |
1525 | 6.52k | doc_info.compression = c; |
1526 | 6.52k | if ((doc_info.compression != COMPRESS_NONE) && |
1527 | 5.31k | (doc_info.compression != COMPRESS_RLE) && |
1528 | 4.30k | (doc_info.compression != COMPRESS_ZLIB) && |
1529 | 3.99k | (doc_info.compression != COMPRESS_FRACTAL)) |
1530 | 3.78k | ThrowReaderException(CorruptImageError,CompressionNotValid, |
1531 | 6.52k | image); |
1532 | 2.73k | } |
1533 | 0 | break; |
1534 | | |
1535 | 6.09k | case PROP_GUIDES: |
1536 | 6.09k | { |
1537 | | /* just skip it - we don't care about guides */ |
1538 | 348k | for (i=0; i < prop_size; i++ ) |
1539 | 347k | if (ReadBlobByte(image) == EOF) |
1540 | 5.45k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile, |
1541 | 6.09k | image); |
1542 | 636 | } |
1543 | 0 | break; |
1544 | | |
1545 | 280 | case PROP_RESOLUTION: |
1546 | 280 | { |
1547 | 280 | /* float xres = (float) */ (void) ReadBlobMSBLong(image); |
1548 | 280 | /* float yres = (float) */ (void) ReadBlobMSBLong(image); |
1549 | | |
1550 | | /* |
1551 | | if (xres < GIMP_MIN_RESOLUTION || xres > GIMP_MAX_RESOLUTION || |
1552 | | yres < GIMP_MIN_RESOLUTION || yres > GIMP_MAX_RESOLUTION) |
1553 | | { |
1554 | | g_message ("Warning, resolution out of range in XCF file"); |
1555 | | xres = gimage->gimp->config->default_xresolution; |
1556 | | yres = gimage->gimp->config->default_yresolution; |
1557 | | } |
1558 | | */ |
1559 | | |
1560 | | |
1561 | | /* BOGUS: we don't write these yet because we aren't |
1562 | | reading them properly yet :( */ |
1563 | | /* image->x_resolution = xres; */ |
1564 | | /* image->y_resolution = yres; */ |
1565 | 280 | } |
1566 | 280 | break; |
1567 | | |
1568 | 316 | case PROP_TATTOO: |
1569 | 316 | { |
1570 | | /* we need to read it, even if we ignore it */ |
1571 | 316 | /*unsigned long tattoo_state = */ (void) ReadBlobMSBLong(image); |
1572 | 316 | } |
1573 | 316 | break; |
1574 | | |
1575 | 7.12k | case PROP_PARASITES: |
1576 | 7.12k | { |
1577 | | /* BOGUS: we may need these for IPTC stuff */ |
1578 | 67.8k | for (i=0; i < prop_size; i++ ) |
1579 | 62.7k | if (ReadBlobByte(image) == EOF) |
1580 | 5.13k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
1581 | | |
1582 | | /* |
1583 | | glong base = info->cp; |
1584 | | GimpParasite *p; |
1585 | | |
1586 | | while (info->cp - base < prop_size) |
1587 | | { |
1588 | | p = xcf_load_parasite (info); |
1589 | | gimp_image_parasite_attach (gimage, p); |
1590 | | gimp_parasite_free (p); |
1591 | | } |
1592 | | if (info->cp - base != prop_size) |
1593 | | g_message ("Error detected while loading an image's parasites"); |
1594 | | */ |
1595 | 5.13k | } |
1596 | 0 | break; |
1597 | | |
1598 | 685 | case PROP_UNIT: |
1599 | 685 | { |
1600 | | /* BOGUS: ignore for now... */ |
1601 | 685 | /*unsigned long unit = */ (void) ReadBlobMSBLong(image); |
1602 | 685 | } |
1603 | 685 | break; |
1604 | | |
1605 | 2.22k | case PROP_PATHS: |
1606 | 2.22k | { |
1607 | | /* BOGUS: just skip it for now */ |
1608 | 614k | for (i=0; i < prop_size; i++ ) |
1609 | 613k | if (ReadBlobByte(image) == EOF) |
1610 | 1.27k | ThrowReaderException(CorruptImageError,UnexpectedEndOfFile,image); |
1611 | | |
1612 | | /* |
1613 | | PathList *paths = xcf_load_bzpaths (gimage, info); |
1614 | | gimp_image_set_paths (gimage, paths); |
1615 | | */ |
1616 | 951 | } |
1617 | 0 | break; |
1618 | | |
1619 | 940 | case PROP_USER_UNIT: |
1620 | 940 | { |
1621 | 940 | char unit_string[1000]; |
1622 | | /*BOGUS: ignored for now */ |
1623 | 940 | /*float factor = (float) */ (void) ReadBlobMSBLong(image); |
1624 | 940 | /* unsigned long digits = */ (void) ReadBlobMSBLong(image); |
1625 | 5.64k | for (i=0; i < 5; i++) |
1626 | 4.70k | (void) ReadBlobStringWithLongSize(image, unit_string, |
1627 | 4.70k | sizeof(unit_string)); |
1628 | 940 | } |
1629 | 940 | break; |
1630 | | |
1631 | 27.5k | default: |
1632 | | /* g_message ("unexpected/unknown image property: %d (skipping)", |
1633 | | prop_type); */ |
1634 | | |
1635 | 27.5k | { |
1636 | 27.5k | int buf[16]; |
1637 | 27.5k | size_t amount; |
1638 | | |
1639 | | /* read over it... */ |
1640 | 38.2k | while (prop_size > 0) /* size_t prop_size, amount */ |
1641 | 21.6k | { |
1642 | 21.6k | amount = Min (sizeof(buf), prop_size); |
1643 | 97.0k | for (i=0; i < amount; i++) |
1644 | 86.4k | { |
1645 | 86.4k | amount = ReadBlob(image, amount, &buf); |
1646 | 86.4k | if (amount == 0U) |
1647 | 11.0k | ThrowReaderException(CorruptImageError, |
1648 | 86.4k | UnexpectedEndOfFile,image); |
1649 | 75.4k | } |
1650 | 10.6k | prop_size -= Min (16U, amount); |
1651 | 10.6k | } |
1652 | 27.5k | } |
1653 | 16.5k | break; |
1654 | 79.0k | } |
1655 | 79.0k | } |
1656 | | |
1657 | 25.5k | if (!foundPropEnd) |
1658 | 21.2k | ThrowReaderException(CorruptImageError,ImproperImageHeader,image); |
1659 | | |
1660 | 21.2k | if (image_info->ping && (image_info->subrange != 0)) |
1661 | 0 | { |
1662 | 0 | ; /* do nothing, we were just pinging! */ |
1663 | 0 | } |
1664 | 21.2k | else |
1665 | 21.2k | { |
1666 | 21.2k | XCFLayerInfo |
1667 | 21.2k | *layer_info; |
1668 | | |
1669 | 21.2k | unsigned long |
1670 | 21.2k | number_layers = 0, |
1671 | 21.2k | num_layers = 0; |
1672 | | |
1673 | 21.2k | long |
1674 | 21.2k | current_layer = 0, |
1675 | 21.2k | first_layer = 0, |
1676 | 21.2k | last_layer = 0, |
1677 | 21.2k | T = 0; |
1678 | | |
1679 | 21.2k | MagickBool |
1680 | 21.2k | foundAllLayers = MagickFalse; |
1681 | | |
1682 | 21.2k | magick_off_t |
1683 | 21.2k | oldPos; |
1684 | | |
1685 | 21.2k | unsigned int |
1686 | 21.2k | previous_offset; |
1687 | | |
1688 | 21.2k | if (CheckImagePixelLimits(image, exception) != MagickPass) |
1689 | 21.2k | ThrowReaderException(ResourceLimitError,ImagePixelLimitExceeded,image); |
1690 | | |
1691 | | /* BIG HACK |
1692 | | because XCF doesn't include the layer count, and we |
1693 | | want to know it in advance in order to allocate memory, |
1694 | | we have to scan the layer offset list, and then reposition |
1695 | | the read pointer |
1696 | | */ |
1697 | 21.2k | oldPos = TellBlob(image); |
1698 | 21.2k | if (oldPos < 0) |
1699 | 21.2k | ThrowReaderException(BlobError,UnableToObtainOffset,image); |
1700 | | |
1701 | 21.2k | previous_offset = 0; |
1702 | 21.2k | do |
1703 | 37.4k | { |
1704 | 37.4k | magick_uint32_t |
1705 | 37.4k | offset = ReadBlobMSBLong(image); |
1706 | | |
1707 | 37.4k | if (image->logging) |
1708 | 37.4k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1709 | 37.4k | "Layer Offset[%lu] = %u", |
1710 | 37.4k | number_layers, offset); |
1711 | | |
1712 | 37.4k | if (offset >= doc_info.file_size) |
1713 | 3.49k | { |
1714 | 3.49k | if (image->logging) |
1715 | 3.49k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1716 | 3.49k | "Layer Offset %u" |
1717 | 3.49k | " is outside of file bounds", |
1718 | 3.49k | offset); |
1719 | 3.49k | ThrowReaderException(CorruptImageError,ImproperImageHeader,image); |
1720 | 0 | } |
1721 | | /* |
1722 | | Are layer offsets assured to be ascending? |
1723 | | */ |
1724 | 34.0k | if ((offset != 0) && (offset <= previous_offset)) |
1725 | 1.74k | { |
1726 | 1.74k | if (image->logging) |
1727 | 1.74k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1728 | 1.74k | "Layer Offset %u" |
1729 | 1.74k | " is not ascending", |
1730 | 1.74k | offset); |
1731 | 1.74k | ThrowReaderException(CorruptImageError,ImproperImageHeader,image); |
1732 | 0 | } |
1733 | | |
1734 | 32.2k | if ( offset == 0 ) |
1735 | 16.0k | foundAllLayers = MagickTrue; |
1736 | 16.2k | else |
1737 | 16.2k | number_layers++; |
1738 | | |
1739 | | /* Check for too many layers */ |
1740 | 32.2k | if (number_layers == (unsigned long) LONG_MAX) |
1741 | 32.2k | ThrowReaderException(CorruptImageError,CorruptImage,image); |
1742 | | |
1743 | 32.2k | previous_offset=offset; |
1744 | 32.2k | } while ( !foundAllLayers ); |
1745 | | |
1746 | 16.0k | if (SeekBlob(image, oldPos, SEEK_SET) != oldPos) /* restore the position! */ |
1747 | 16.0k | ThrowReaderException(BlobError,UnableToSeekToOffset,image); |
1748 | | |
1749 | 16.0k | first_layer = image_info->subimage; |
1750 | 16.0k | num_layers = number_layers; |
1751 | | |
1752 | 16.0k | if (number_layers == 0) |
1753 | 9.86k | ThrowReaderException(CorruptImageError,ImproperImageHeader,image); |
1754 | | |
1755 | | /* subrange==0 means read all the images */ |
1756 | 9.86k | if( image_info->subrange > 0UL && image_info->subrange < number_layers ) |
1757 | 4.26k | num_layers = image_info->subrange; |
1758 | 9.86k | last_layer = first_layer + num_layers-1; |
1759 | | |
1760 | 9.86k | if (image->logging) |
1761 | 9.86k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1762 | 9.86k | "XCF number_layers=%lu first_layer=%ld last_layer=%ld", |
1763 | 9.86k | number_layers, first_layer, last_layer); |
1764 | | |
1765 | | /* XCF has layers backwards, so this gets a bit complicated */ |
1766 | 9.86k | T = last_layer; |
1767 | 9.86k | last_layer = number_layers - first_layer - 1; |
1768 | 9.86k | first_layer = number_layers - T - 1; |
1769 | 9.86k | number_layers = num_layers; |
1770 | | |
1771 | 9.86k | if (image->logging) |
1772 | 9.86k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1773 | 9.86k | "XCF reading layers %ld to %ld inclusive", first_layer, |
1774 | 9.86k | last_layer); |
1775 | | |
1776 | | /* allocate our array of layer info blocks */ |
1777 | 9.86k | layer_info=MagickAllocateResourceLimitedArray(XCFLayerInfo *, |
1778 | 9.86k | number_layers, |
1779 | 9.86k | sizeof(XCFLayerInfo)); |
1780 | 9.86k | if (layer_info == (XCFLayerInfo *) NULL) |
1781 | 9.86k | ThrowReaderException(ResourceLimitError,MemoryAllocationFailed,image); |
1782 | 9.86k | (void) memset(layer_info,0,number_layers*sizeof(XCFLayerInfo)); |
1783 | | |
1784 | 9.86k | for ( ; ; ) |
1785 | 15.7k | { |
1786 | 15.7k | magick_off_t |
1787 | 15.7k | offset, |
1788 | 15.7k | saved_pos; |
1789 | | |
1790 | 15.7k | MagickPassFail |
1791 | 15.7k | layer_ok; |
1792 | | |
1793 | | /* read in the offset of the next layer */ |
1794 | 15.7k | offset = ReadBlobMSBLong(image); |
1795 | | |
1796 | | /* if the offset is 0 then we are at the end |
1797 | | * of the layer list. |
1798 | | */ |
1799 | 15.7k | if (offset == 0) |
1800 | 1.43k | break; |
1801 | | |
1802 | | /* save the current position as it is where the |
1803 | | * next layer offset is stored. |
1804 | | */ |
1805 | 14.2k | saved_pos = TellBlob(image); |
1806 | 14.2k | if (saved_pos < 0) |
1807 | 0 | { |
1808 | 0 | MagickFreeResourceLimitedMemory(XCFLayerInfo *,layer_info); |
1809 | 0 | ThrowReaderException(BlobError,UnableToObtainOffset,image); |
1810 | 0 | } |
1811 | | |
1812 | 14.2k | if ( first_layer <= current_layer && current_layer <= last_layer ) |
1813 | 9.86k | { |
1814 | | /* verify that seek position is in file */ |
1815 | 9.86k | if ((magick_off_t) offset >= GetBlobSize(image)) |
1816 | 0 | { |
1817 | 0 | if (image->logging) |
1818 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1819 | 0 | "Layer offset %" MAGICK_OFF_F "d is outside file bounds", |
1820 | 0 | (magick_off_t) offset); |
1821 | 0 | DestroyLayerInfo(number_layers,layer_info); |
1822 | 0 | ThrowReaderException(CorruptImageError,InsufficientImageDataInFile,image); |
1823 | 0 | } |
1824 | | |
1825 | | /* seek to the layer offset */ |
1826 | 9.86k | if (SeekBlob(image, offset, SEEK_SET) != offset) |
1827 | 0 | { |
1828 | | /* FIXME: CID 64064: leaks layer_info */ |
1829 | 0 | DestroyLayerInfo(number_layers,layer_info); |
1830 | 0 | ThrowReaderException(CorruptImageError,InsufficientImageDataInFile,image); |
1831 | 0 | } |
1832 | | |
1833 | | /* read in the layer */ |
1834 | 9.86k | layer_ok = ReadOneLayer( image, &doc_info, &layer_info[current_layer-first_layer] ); |
1835 | 9.86k | if (!layer_ok) |
1836 | 8.43k | { |
1837 | | #if 0 |
1838 | | int |
1839 | | j; |
1840 | | |
1841 | | for (j=0; j <= (current_layer-first_layer); j++) |
1842 | | { |
1843 | | if (layer_info[j].image) |
1844 | | { |
1845 | | DestroyImage(layer_info[j].image); |
1846 | | layer_info[j].image = (Image *) NULL; |
1847 | | } |
1848 | | } |
1849 | | MagickFreeResourceLimitedMemory(XCFLayerInfo *,layer_info); |
1850 | | #endif |
1851 | 8.43k | DestroyLayerInfo(number_layers,layer_info); |
1852 | 8.43k | CopyException(exception,&image->exception); |
1853 | 8.43k | CloseBlob(image); |
1854 | 8.43k | DestroyImageList(image); |
1855 | 8.43k | return (Image *) NULL; |
1856 | 8.43k | } |
1857 | | /* restore the saved position so we'll be ready to |
1858 | | * read the next offset. |
1859 | | */ |
1860 | 1.43k | if (SeekBlob(image, saved_pos, SEEK_SET) != saved_pos) |
1861 | 0 | { |
1862 | | /* FIXME: CID 64064: leaks layer_info */ |
1863 | 0 | DestroyLayerInfo(number_layers,layer_info); |
1864 | 0 | ThrowReaderException(BlobError,UnableToSeekToOffset,image); |
1865 | 0 | } |
1866 | 1.43k | } |
1867 | | |
1868 | 5.84k | current_layer++; |
1869 | 5.84k | } |
1870 | | |
1871 | 1.43k | if ( number_layers == 1 ) |
1872 | 1.43k | { |
1873 | | /* composite the layer data onto the main image & then dispose the layer */ |
1874 | 1.43k | if (image->logging) |
1875 | 1.43k | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1876 | 1.43k | "Composite Layer[0]: %lux%lu%+d%+d", |
1877 | 1.43k | layer_info[0].image->columns, |
1878 | 1.43k | layer_info[0].image->rows, |
1879 | 1.43k | layer_info[0].offset_x, |
1880 | 1.43k | layer_info[0].offset_y); |
1881 | 1.43k | (void) CompositeImage(image, OverCompositeOp, layer_info[0].image, |
1882 | 1.43k | layer_info[0].offset_x, layer_info[0].offset_y ); |
1883 | 1.43k | DestroyImage( layer_info[0].image ); |
1884 | 1.43k | layer_info[0].image = (Image *) NULL; |
1885 | 1.43k | } |
1886 | 0 | else |
1887 | 0 | { |
1888 | | #if 0 |
1889 | | { |
1890 | | /* NOTE: XCF layers are REVERSED from composite order! */ |
1891 | | long |
1892 | | j; |
1893 | | |
1894 | | for (j=(long) (number_layers-1); j>=0; j--) |
1895 | | { |
1896 | | /* BOGUS: need to consider layer blending modes!! */ |
1897 | | if ( layer_info[j].visible ) /* only visible ones, please! */ |
1898 | | { |
1899 | | if (image->logging) |
1900 | | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1901 | | "Composite Layer[%lu]: %lux%lu%+d%+d", |
1902 | | j, |
1903 | | layer_info[j].image->columns, |
1904 | | layer_info[j].image->rows, |
1905 | | layer_info[j].offset_x, |
1906 | | layer_info[j].offset_y); |
1907 | | CompositeImage(image, OverCompositeOp, layer_info[j].image, |
1908 | | layer_info[j].offset_x, layer_info[j].offset_y ); |
1909 | | DestroyImage( layer_info[j].image ); |
1910 | | layer_info[j].image = (Image *) NULL; |
1911 | | } |
1912 | | } |
1913 | | } |
1914 | | #else |
1915 | 0 | { |
1916 | | /* NOTE: XCF layers are REVERSED from composite order! */ |
1917 | 0 | long |
1918 | 0 | j; |
1919 | | |
1920 | | /* first we copy the last layer on top of the main image */ |
1921 | 0 | if (image->logging) |
1922 | 0 | (void) LogMagickEvent(CoderEvent,GetMagickModule(), |
1923 | 0 | "Composite Layer[%lu]: %lux%lu%+d%+d", |
1924 | 0 | number_layers-1, |
1925 | 0 | layer_info[number_layers-1].image->columns, |
1926 | 0 | layer_info[number_layers-1].image->rows, |
1927 | 0 | layer_info[number_layers-1].offset_x, |
1928 | 0 | layer_info[number_layers-1].offset_y); |
1929 | 0 | (void) CompositeImage(image, CopyCompositeOp, layer_info[number_layers-1].image, |
1930 | 0 | layer_info[number_layers-1].offset_x, |
1931 | 0 | layer_info[number_layers-1].offset_y ); |
1932 | 0 | DestroyImage( layer_info[number_layers-1].image ); |
1933 | 0 | layer_info[number_layers-1].image = (Image *) NULL; |
1934 | | |
1935 | | /* now reverse the order of the layers as they are put |
1936 | | into subimages |
1937 | | */ |
1938 | 0 | image->next=layer_info[number_layers-2].image; |
1939 | 0 | layer_info[number_layers-2].image->previous=image; |
1940 | 0 | for (j=(long) ((number_layers-2)); j >= 0; j--) |
1941 | 0 | { |
1942 | 0 | if (j > 0) |
1943 | 0 | layer_info[j].image->next=layer_info[j-1].image; |
1944 | 0 | if (j < ((long) (number_layers-1))) |
1945 | 0 | layer_info[j].image->previous=layer_info[j+1].image; |
1946 | 0 | layer_info[j].image->page.x = layer_info[j].offset_x; |
1947 | 0 | layer_info[j].image->page.y = layer_info[j].offset_y; |
1948 | 0 | layer_info[j].image->page.width = layer_info[j].width; |
1949 | 0 | layer_info[j].image->page.height = layer_info[j].height; |
1950 | 0 | } |
1951 | 0 | } |
1952 | 0 | #endif |
1953 | 0 | } |
1954 | | |
1955 | 1.43k | MagickFreeResourceLimitedMemory(XCFLayerInfo *,layer_info); |
1956 | | |
1957 | | #if 0 /* BOGUS: do we need the channels?? */ |
1958 | | while (True) |
1959 | | { |
1960 | | /* read in the offset of the next channel */ |
1961 | | info->cp += xcf_read_int32 (info->fp, &offset, 1); |
1962 | | |
1963 | | /* if the offset is 0 then we are at the end |
1964 | | * of the channel list. |
1965 | | */ |
1966 | | if (offset == 0) |
1967 | | break; |
1968 | | |
1969 | | /* save the current position as it is where the |
1970 | | * next channel offset is stored. |
1971 | | */ |
1972 | | saved_pos = info->cp; |
1973 | | |
1974 | | /* seek to the channel offset */ |
1975 | | xcf_seek_pos (info, offset); |
1976 | | |
1977 | | /* read in the layer */ |
1978 | | channel = xcf_load_channel (info, gimage); |
1979 | | if (!channel) |
1980 | | goto error; |
1981 | | |
1982 | | num_successful_elements++; |
1983 | | |
1984 | | /* add the channel to the image if its not the selection */ |
1985 | | if (channel != gimage->selection_mask) |
1986 | | gimp_image_add_channel (gimage, channel, -1); |
1987 | | |
1988 | | /* restore the saved position so we'll be ready to |
1989 | | * read the next offset. |
1990 | | */ |
1991 | | xcf_seek_pos (info, saved_pos); |
1992 | | } |
1993 | | #endif |
1994 | 1.43k | } |
1995 | | |
1996 | 1.43k | CloseBlob(image); |
1997 | 1.43k | if ( image_type == GIMP_GRAY ) |
1998 | 176 | image->is_grayscale=MagickTrue; |
1999 | 1.43k | StopTimer(&image->timer); |
2000 | 1.43k | return(image); |
2001 | 21.2k | } |
2002 | | |
2003 | | /* |
2004 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2005 | | % % |
2006 | | % % |
2007 | | % % |
2008 | | % R e g i s t e r X C F I m a g e % |
2009 | | % % |
2010 | | % % |
2011 | | % % |
2012 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2013 | | % |
2014 | | % Method RegisterXCFImage adds attributes for the XCF image format to |
2015 | | % the list of supported formats. The attributes include the image format |
2016 | | % tag, a method to read and/or write the format, whether the format |
2017 | | % supports the saving of more than one frame to the same file or blob, |
2018 | | % whether the format supports native in-memory I/O, and a brief |
2019 | | % description of the format. |
2020 | | % |
2021 | | % The format of the RegisterXCFImage method is: |
2022 | | % |
2023 | | % RegisterXCFImage(void) |
2024 | | % |
2025 | | */ |
2026 | | ModuleExport void RegisterXCFImage(void) |
2027 | 4 | { |
2028 | 4 | MagickInfo |
2029 | 4 | *entry; |
2030 | | |
2031 | 4 | entry=SetMagickInfo("XCF"); |
2032 | 4 | entry->decoder=(DecoderHandler) ReadXCFImage; |
2033 | 4 | entry->magick=(MagickHandler) IsXCF; |
2034 | 4 | entry->description="GIMP image"; |
2035 | 4 | entry->module="XCF"; |
2036 | 4 | entry->seekable_stream=True; |
2037 | 4 | (void) RegisterMagickInfo(entry); |
2038 | 4 | } |
2039 | | |
2040 | | /* |
2041 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2042 | | % % |
2043 | | % % |
2044 | | % % |
2045 | | % U n r e g i s t e r X C F I m a g e % |
2046 | | % % |
2047 | | % % |
2048 | | % % |
2049 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2050 | | % |
2051 | | % Method UnregisterXCFImage removes format registrations made by the |
2052 | | % XCF module from the list of supported formats. |
2053 | | % |
2054 | | % The format of the UnregisterXCFImage method is: |
2055 | | % |
2056 | | % UnregisterXCFImage(void) |
2057 | | % |
2058 | | */ |
2059 | | ModuleExport void UnregisterXCFImage(void) |
2060 | 0 | { |
2061 | 0 | (void) UnregisterMagickInfo("XCF"); |
2062 | 0 | } |