1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % EEEEE X X RRRR %
7 % E X X R R %
8 % EEE X RRRR %
9 % E X X R R %
10 % EEEEE X X R R %
11 % %
12 % %
13 % Read/Write High Dynamic-Range (HDR) Image File Format %
14 % %
15 % Software Design %
16 % Cristy %
17 % April 2007 %
18 % %
19 % %
20 % Copyright 1999-2019 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
22 % %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
25 % %
26 % https://imagemagick.org/script/license.php %
27 % %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
33 % %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 %
36 %
37 */
38
39 /*
40 Include declarations.
41 */
42 #include "MagickCore/studio.h"
43 #include "MagickCore/blob.h"
44 #include "MagickCore/blob-private.h"
45 #include "MagickCore/cache.h"
46 #include "MagickCore/exception.h"
47 #include "MagickCore/exception-private.h"
48 #include "MagickCore/image.h"
49 #include "MagickCore/image-private.h"
50 #include "MagickCore/list.h"
51 #include "MagickCore/magick.h"
52 #include "MagickCore/memory_.h"
53 #include "MagickCore/option.h"
54 #include "MagickCore/pixel-accessor.h"
55 #include "MagickCore/property.h"
56 #include "MagickCore/quantum-private.h"
57 #include "MagickCore/static.h"
58 #include "MagickCore/string_.h"
59 #include "MagickCore/module.h"
60 #include "MagickCore/resource_.h"
61 #include "MagickCore/utility.h"
62 #if defined(MAGICKCORE_OPENEXR_DELEGATE)
63 #include <ImfCRgbaFile.h>
64
65 /*
66 Typedef declaractions.
67 */
68 typedef struct _ExrWindow
69 {
70 int
71 max_x,
72 max_y,
73 min_x,
74 min_y;
75 } ExrWindow;
76
77 /*
78 Forward declarations.
79 */
80 static MagickBooleanType
81 WriteEXRImage(const ImageInfo *,Image *,ExceptionInfo *);
82 #endif
83
84 /*
85 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86 % %
87 % %
88 % %
89 % I s E X R %
90 % %
91 % %
92 % %
93 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94 %
95 % IsEXR() returns MagickTrue if the image format type, identified by the
96 % magick string, is EXR.
97 %
98 % The format of the IsEXR method is:
99 %
100 % MagickBooleanType IsEXR(const unsigned char *magick,const size_t length)
101 %
102 % A description of each parameter follows:
103 %
104 % o magick: compare image format pattern against these bytes.
105 %
106 % o length: Specifies the length of the magick string.
107 %
108 */
IsEXR(const unsigned char * magick,const size_t length)109 static MagickBooleanType IsEXR(const unsigned char *magick,const size_t length)
110 {
111 if (length < 4)
112 return(MagickFalse);
113 if (memcmp(magick,"\166\057\061\001",4) == 0)
114 return(MagickTrue);
115 return(MagickFalse);
116 }
117
118 #if defined(MAGICKCORE_OPENEXR_DELEGATE)
119 /*
120 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
121 % %
122 % %
123 % %
124 % R e a d E X R I m a g e %
125 % %
126 % %
127 % %
128 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
129 %
130 % ReadEXRImage reads an image in the high dynamic-range (HDR) file format
131 % developed by Industrial Light & Magic. It allocates the memory necessary
132 % for the new Image structure and returns a pointer to the new image.
133 %
134 % The format of the ReadEXRImage method is:
135 %
136 % Image *ReadEXRImage(const ImageInfo *image_info,ExceptionInfo *exception)
137 %
138 % A description of each parameter follows:
139 %
140 % o image_info: the image info.
141 %
142 % o exception: return any errors or warnings in this structure.
143 %
144 */
ReadEXRImage(const ImageInfo * image_info,ExceptionInfo * exception)145 static Image *ReadEXRImage(const ImageInfo *image_info,ExceptionInfo *exception)
146 {
147 ExrWindow
148 data_window,
149 display_window;
150
151 const ImfHeader
152 *hdr_info;
153
154 Image
155 *image;
156
157 ImageInfo
158 *read_info;
159
160 ImfInputFile
161 *file;
162
163 ImfRgba
164 *scanline;
165
166 MagickBooleanType
167 status;
168
169 register ssize_t
170 x;
171
172 register Quantum
173 *q;
174
175 ssize_t
176 columns,
177 y;
178
179 /*
180 Open image.
181 */
182 assert(image_info != (const ImageInfo *) NULL);
183 assert(image_info->signature == MagickCoreSignature);
184 if (image_info->debug != MagickFalse)
185 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
186 image_info->filename);
187 assert(exception != (ExceptionInfo *) NULL);
188 assert(exception->signature == MagickCoreSignature);
189 image=AcquireImage(image_info,exception);
190 status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
191 if (status == MagickFalse)
192 {
193 image=DestroyImageList(image);
194 return((Image *) NULL);
195 }
196 read_info=CloneImageInfo(image_info);
197 if (IsPathAccessible(read_info->filename) == MagickFalse)
198 {
199 (void) AcquireUniqueFilename(read_info->filename);
200 (void) ImageToFile(image,read_info->filename,exception);
201 }
202 file=ImfOpenInputFile(read_info->filename);
203 if (file == (ImfInputFile *) NULL)
204 {
205 ThrowFileException(exception,BlobError,"UnableToOpenBlob",
206 ImfErrorMessage());
207 if (LocaleCompare(image_info->filename,read_info->filename) != 0)
208 (void) RelinquishUniqueFileResource(read_info->filename);
209 read_info=DestroyImageInfo(read_info);
210 image=DestroyImageList(image);
211 return((Image *) NULL);
212 }
213 hdr_info=ImfInputHeader(file);
214 ImfHeaderDisplayWindow(hdr_info,&display_window.min_x,&display_window.min_y,
215 &display_window.max_x,&display_window.max_y);
216 image->columns=display_window.max_x-display_window.min_x+1UL;
217 image->rows=display_window.max_y-display_window.min_y+1UL;
218 image->alpha_trait=BlendPixelTrait;
219 SetImageColorspace(image,RGBColorspace,exception);
220 image->gamma=1.0;
221 if (image_info->ping != MagickFalse)
222 {
223 (void) ImfCloseInputFile(file);
224 if (LocaleCompare(image_info->filename,read_info->filename) != 0)
225 (void) RelinquishUniqueFileResource(read_info->filename);
226 read_info=DestroyImageInfo(read_info);
227 (void) CloseBlob(image);
228 return(GetFirstImageInList(image));
229 }
230 status=SetImageExtent(image,image->columns,image->rows,exception);
231 if (status == MagickFalse)
232 return(DestroyImageList(image));
233 ImfHeaderDataWindow(hdr_info,&data_window.min_x,&data_window.min_y,
234 &data_window.max_x,&data_window.max_y);
235 columns=(ssize_t) data_window.max_x-data_window.min_x+1UL;
236 if ((display_window.min_x > data_window.max_x) ||
237 (display_window.min_x+(int) image->columns <= data_window.min_x))
238 scanline=(ImfRgba *) NULL;
239 else
240 {
241 scanline=(ImfRgba *) AcquireQuantumMemory(columns,sizeof(*scanline));
242 if (scanline == (ImfRgba *) NULL)
243 {
244 (void) ImfCloseInputFile(file);
245 if (LocaleCompare(image_info->filename,read_info->filename) != 0)
246 (void) RelinquishUniqueFileResource(read_info->filename);
247 read_info=DestroyImageInfo(read_info);
248 image=DestroyImageList(image);
249 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
250 }
251 }
252 for (y=0; y < (ssize_t) image->rows; y++)
253 {
254 int
255 yy;
256
257 q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
258 if (q == (Quantum *) NULL)
259 break;
260 yy=display_window.min_y+y;
261 if ((yy < data_window.min_y) || (yy > data_window.max_y) ||
262 (scanline == (ImfRgba *) NULL))
263 {
264 for (x=0; x < (ssize_t) image->columns; x++)
265 {
266 SetPixelViaPixelInfo(image,&image->background_color,q);
267 q+=GetPixelChannels(image);
268 }
269 continue;
270 }
271 memset(scanline,0,columns*sizeof(*scanline));
272 ImfInputSetFrameBuffer(file,scanline-data_window.min_x-columns*yy,1,
273 columns);
274 ImfInputReadPixels(file,yy,yy);
275 for (x=0; x < (ssize_t) image->columns; x++)
276 {
277 int
278 xx;
279
280 xx=display_window.min_x+((int) x-data_window.min_x);
281 if ((xx < 0) || (display_window.min_x+(int) x > data_window.max_x))
282 SetPixelViaPixelInfo(image,&image->background_color,q);
283 else
284 {
285 SetPixelRed(image,ClampToQuantum((MagickRealType) QuantumRange*
286 ImfHalfToFloat(scanline[xx].r)),q);
287 SetPixelGreen(image,ClampToQuantum((MagickRealType) QuantumRange*
288 ImfHalfToFloat(scanline[xx].g)),q);
289 SetPixelBlue(image,ClampToQuantum((MagickRealType) QuantumRange*
290 ImfHalfToFloat(scanline[xx].b)),q);
291 SetPixelAlpha(image,ClampToQuantum((MagickRealType) QuantumRange*
292 ImfHalfToFloat(scanline[xx].a)),q);
293 }
294 q+=GetPixelChannels(image);
295 }
296 if (SyncAuthenticPixels(image,exception) == MagickFalse)
297 break;
298 }
299 scanline=(ImfRgba *) RelinquishMagickMemory(scanline);
300 (void) ImfCloseInputFile(file);
301 if (LocaleCompare(image_info->filename,read_info->filename) != 0)
302 (void) RelinquishUniqueFileResource(read_info->filename);
303 read_info=DestroyImageInfo(read_info);
304 (void) CloseBlob(image);
305 return(GetFirstImageInList(image));
306 }
307 #endif
308
309 /*
310 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
311 % %
312 % %
313 % %
314 % R e g i s t e r E X R I m a g e %
315 % %
316 % %
317 % %
318 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
319 %
320 % RegisterEXRImage() adds properties for the EXR image format
321 % to the list of supported formats. The properties include the image format
322 % tag, a method to read and/or write the format, whether the format
323 % supports the saving of more than one frame to the same file or blob,
324 % whether the format supports native in-memory I/O, and a brief
325 % description of the format.
326 %
327 % The format of the RegisterEXRImage method is:
328 %
329 % size_t RegisterEXRImage(void)
330 %
331 */
RegisterEXRImage(void)332 ModuleExport size_t RegisterEXRImage(void)
333 {
334 MagickInfo
335 *entry;
336
337 entry=AcquireMagickInfo("EXR","EXR","High Dynamic-range (HDR)");
338 #if defined(MAGICKCORE_OPENEXR_DELEGATE)
339 entry->decoder=(DecodeImageHandler *) ReadEXRImage;
340 entry->encoder=(EncodeImageHandler *) WriteEXRImage;
341 #endif
342 entry->magick=(IsImageFormatHandler *) IsEXR;
343 entry->flags^=CoderAdjoinFlag;
344 entry->flags^=CoderBlobSupportFlag;
345 (void) RegisterMagickInfo(entry);
346 return(MagickImageCoderSignature);
347 }
348
349 /*
350 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
351 % %
352 % %
353 % %
354 % U n r e g i s t e r E X R I m a g e %
355 % %
356 % %
357 % %
358 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
359 %
360 % UnregisterEXRImage() removes format registrations made by the
361 % EXR module from the list of supported formats.
362 %
363 % The format of the UnregisterEXRImage method is:
364 %
365 % UnregisterEXRImage(void)
366 %
367 */
UnregisterEXRImage(void)368 ModuleExport void UnregisterEXRImage(void)
369 {
370 (void) UnregisterMagickInfo("EXR");
371 }
372
373 #if defined(MAGICKCORE_OPENEXR_DELEGATE)
374 /*
375 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
376 % %
377 % %
378 % %
379 % W r i t e E X R I m a g e %
380 % %
381 % %
382 % %
383 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
384 %
385 % WriteEXRImage() writes an image to a file the in the high dynamic-range
386 % (HDR) file format developed by Industrial Light & Magic.
387 %
388 % The format of the WriteEXRImage method is:
389 %
390 % MagickBooleanType WriteEXRImage(const ImageInfo *image_info,
391 % Image *image,ExceptionInfo *exception)
392 %
393 % A description of each parameter follows.
394 %
395 % o image_info: the image info.
396 %
397 % o image: The image.
398 %
399 % o exception: return any errors or warnings in this structure.
400 %
401 */
WriteEXRImage(const ImageInfo * image_info,Image * image,ExceptionInfo * exception)402 static MagickBooleanType WriteEXRImage(const ImageInfo *image_info,Image *image,
403 ExceptionInfo *exception)
404 {
405 const char
406 *sampling_factor,
407 *value;
408
409 ImageInfo
410 *write_info;
411
412 ImfHalf
413 half_quantum;
414
415 ImfHeader
416 *hdr_info;
417
418 ImfOutputFile
419 *file;
420
421 ImfRgba
422 *scanline;
423
424 int
425 channels,
426 compression,
427 factors[3];
428
429 MagickBooleanType
430 status;
431
432 register const Quantum
433 *p;
434
435 register ssize_t
436 x;
437
438 ssize_t
439 y;
440
441 /*
442 Open output image file.
443 */
444 assert(image_info != (const ImageInfo *) NULL);
445 assert(image_info->signature == MagickCoreSignature);
446 assert(image != (Image *) NULL);
447 assert(image->signature == MagickCoreSignature);
448 if (image->debug != MagickFalse)
449 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
450 assert(exception != (ExceptionInfo *) NULL);
451 assert(exception->signature == MagickCoreSignature);
452 status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
453 if (status == MagickFalse)
454 return(status);
455 (void) SetImageColorspace(image,RGBColorspace,exception);
456 write_info=CloneImageInfo(image_info);
457 (void) AcquireUniqueFilename(write_info->filename);
458 hdr_info=ImfNewHeader();
459 ImfHeaderSetDataWindow(hdr_info,0,0,(int) image->columns-1,(int)
460 image->rows-1);
461 ImfHeaderSetDisplayWindow(hdr_info,0,0,(int) image->columns-1,(int)
462 image->rows-1);
463 compression=IMF_NO_COMPRESSION;
464 if (write_info->compression == ZipSCompression)
465 compression=IMF_ZIPS_COMPRESSION;
466 if (write_info->compression == ZipCompression)
467 compression=IMF_ZIP_COMPRESSION;
468 if (write_info->compression == PizCompression)
469 compression=IMF_PIZ_COMPRESSION;
470 if (write_info->compression == Pxr24Compression)
471 compression=IMF_PXR24_COMPRESSION;
472 #if defined(B44Compression)
473 if (write_info->compression == B44Compression)
474 compression=IMF_B44_COMPRESSION;
475 #endif
476 #if defined(B44ACompression)
477 if (write_info->compression == B44ACompression)
478 compression=IMF_B44A_COMPRESSION;
479 #endif
480 channels=0;
481 value=GetImageOption(image_info,"exr:color-type");
482 if (value != (const char *) NULL)
483 {
484 if (LocaleCompare(value,"RGB") == 0)
485 channels=IMF_WRITE_RGB;
486 else if (LocaleCompare(value,"RGBA") == 0)
487 channels=IMF_WRITE_RGBA;
488 else if (LocaleCompare(value,"YC") == 0)
489 channels=IMF_WRITE_YC;
490 else if (LocaleCompare(value,"YCA") == 0)
491 channels=IMF_WRITE_YCA;
492 else if (LocaleCompare(value,"Y") == 0)
493 channels=IMF_WRITE_Y;
494 else if (LocaleCompare(value,"YA") == 0)
495 channels=IMF_WRITE_YA;
496 else if (LocaleCompare(value,"R") == 0)
497 channels=IMF_WRITE_R;
498 else if (LocaleCompare(value,"G") == 0)
499 channels=IMF_WRITE_G;
500 else if (LocaleCompare(value,"B") == 0)
501 channels=IMF_WRITE_B;
502 else if (LocaleCompare(value,"A") == 0)
503 channels=IMF_WRITE_A;
504 else
505 (void) ThrowMagickException(exception,GetMagickModule(),CoderWarning,
506 "ignoring invalid defined exr:color-type","=%s",value);
507 }
508 sampling_factor=(const char *) NULL;
509 factors[0]=0;
510 if (image_info->sampling_factor != (char *) NULL)
511 sampling_factor=image_info->sampling_factor;
512 if (sampling_factor != NULL)
513 {
514 /*
515 Sampling factors, valid values are 1x1 or 2x2.
516 */
517 if (sscanf(sampling_factor,"%d:%d:%d",factors,factors+1,factors+2) == 3)
518 {
519 if ((factors[0] == factors[1]) && (factors[1] == factors[2]))
520 factors[0]=1;
521 else
522 if ((factors[0] == (2*factors[1])) && (factors[2] == 0))
523 factors[0]=2;
524 }
525 else
526 if (sscanf(sampling_factor,"%dx%d",factors,factors+1) == 2)
527 {
528 if (factors[0] != factors[1])
529 factors[0]=0;
530 }
531 if ((factors[0] != 1) && (factors[0] != 2))
532 (void) ThrowMagickException(exception,GetMagickModule(),CoderWarning,
533 "ignoring sampling-factor","=%s",sampling_factor);
534 else if (channels != 0)
535 {
536 /*
537 Cross check given color type and subsampling.
538 */
539 factors[1]=((channels == IMF_WRITE_YCA) ||
540 (channels == IMF_WRITE_YC)) ? 2 : 1;
541 if (factors[0] != factors[1])
542 (void) ThrowMagickException(exception,GetMagickModule(),
543 CoderWarning,"sampling-factor and color type mismatch","=%s",
544 sampling_factor);
545 }
546 }
547 if (channels == 0)
548 {
549 /*
550 If no color type given, select it now.
551 */
552 if (factors[0] == 2)
553 channels=image->alpha_trait != UndefinedPixelTrait ? IMF_WRITE_YCA :
554 IMF_WRITE_YC;
555 else
556 channels=image->alpha_trait != UndefinedPixelTrait ? IMF_WRITE_RGBA :
557 IMF_WRITE_RGB;
558 }
559 ImfHeaderSetCompression(hdr_info,compression);
560 ImfHeaderSetLineOrder(hdr_info,IMF_INCREASING_Y);
561 file=ImfOpenOutputFile(write_info->filename,hdr_info,channels);
562 ImfDeleteHeader(hdr_info);
563 if (file == (ImfOutputFile *) NULL)
564 {
565 (void) RelinquishUniqueFileResource(write_info->filename);
566 write_info=DestroyImageInfo(write_info);
567 ThrowFileException(exception,BlobError,"UnableToOpenBlob",
568 ImfErrorMessage());
569 return(MagickFalse);
570 }
571 scanline=(ImfRgba *) AcquireQuantumMemory(image->columns,sizeof(*scanline));
572 if (scanline == (ImfRgba *) NULL)
573 {
574 (void) ImfCloseOutputFile(file);
575 (void) RelinquishUniqueFileResource(write_info->filename);
576 write_info=DestroyImageInfo(write_info);
577 ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
578 }
579 memset(scanline,0,image->columns*sizeof(*scanline));
580 for (y=0; y < (ssize_t) image->rows; y++)
581 {
582 p=GetVirtualPixels(image,0,y,image->columns,1,exception);
583 if (p == (const Quantum *) NULL)
584 break;
585 for (x=0; x < (ssize_t) image->columns; x++)
586 {
587 ImfFloatToHalf(QuantumScale*GetPixelRed(image,p),&half_quantum);
588 scanline[x].r=half_quantum;
589 ImfFloatToHalf(QuantumScale*GetPixelGreen(image,p),&half_quantum);
590 scanline[x].g=half_quantum;
591 ImfFloatToHalf(QuantumScale*GetPixelBlue(image,p),&half_quantum);
592 scanline[x].b=half_quantum;
593 if (image->alpha_trait == UndefinedPixelTrait)
594 ImfFloatToHalf(1.0,&half_quantum);
595 else
596 ImfFloatToHalf(QuantumScale*GetPixelAlpha(image,p),&half_quantum);
597 scanline[x].a=half_quantum;
598 p+=GetPixelChannels(image);
599 }
600 ImfOutputSetFrameBuffer(file,scanline-(y*image->columns),1,image->columns);
601 ImfOutputWritePixels(file,1);
602 }
603 (void) ImfCloseOutputFile(file);
604 scanline=(ImfRgba *) RelinquishMagickMemory(scanline);
605 (void) FileToImage(image,write_info->filename,exception);
606 (void) RelinquishUniqueFileResource(write_info->filename);
607 write_info=DestroyImageInfo(write_info);
608 (void) CloseBlob(image);
609 return(MagickTrue);
610 }
611 #endif
612