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