1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 %                                                                             %
4 %                                                                             %
5 %                                                                             %
6 %                            H   H  DDDD   RRRR                               %
7 %                            H   H  D   D  R   R                              %
8 %                            HHHHH  D   D  RRRR                               %
9 %                            H   H  D   D  R R                                %
10 %                            H   H  DDDD   R  R                               %
11 %                                                                             %
12 %                                                                             %
13 %                   Read/Write Radiance RGBE Image Format                     %
14 %                                                                             %
15 %                              Software Design                                %
16 %                                   Cristy                                    %
17 %                                 July 1992                                   %
18 %                                                                             %
19 %                                                                             %
20 %  Copyright 1999-2021 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/colorspace.h"
47 #include "MagickCore/colorspace-private.h"
48 #include "MagickCore/exception.h"
49 #include "MagickCore/exception-private.h"
50 #include "MagickCore/image.h"
51 #include "MagickCore/image-private.h"
52 #include "MagickCore/list.h"
53 #include "MagickCore/magick.h"
54 #include "MagickCore/memory_.h"
55 #include "MagickCore/monitor.h"
56 #include "MagickCore/monitor-private.h"
57 #include "MagickCore/pixel-accessor.h"
58 #include "MagickCore/property.h"
59 #include "MagickCore/quantum-private.h"
60 #include "MagickCore/static.h"
61 #include "MagickCore/string_.h"
62 #include "MagickCore/string-private.h"
63 #include "MagickCore/module.h"
64 
65 /*
66   Forward declarations.
67 */
68 static MagickBooleanType
69   WriteHDRImage(const ImageInfo *,Image *,ExceptionInfo *);
70 
71 /*
72 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
73 %                                                                             %
74 %                                                                             %
75 %                                                                             %
76 %   I s H D R                                                                 %
77 %                                                                             %
78 %                                                                             %
79 %                                                                             %
80 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
81 %
82 %  IsHDR() returns MagickTrue if the image format type, identified by the
83 %  magick string, is Radiance RGBE image format.
84 %
85 %  The format of the IsHDR method is:
86 %
87 %      MagickBooleanType IsHDR(const unsigned char *magick,
88 %        const size_t length)
89 %
90 %  A description of each parameter follows:
91 %
92 %    o magick: compare image format pattern against these bytes.
93 %
94 %    o length: Specifies the length of the magick string.
95 %
96 */
IsHDR(const unsigned char * magick,const size_t length)97 static MagickBooleanType IsHDR(const unsigned char *magick,
98   const size_t length)
99 {
100   if (length < 10)
101     return(MagickFalse);
102   if (LocaleNCompare((const char *) magick,"#?RADIANCE",10) == 0)
103     return(MagickTrue);
104   if (LocaleNCompare((const char *) magick,"#?RGBE",6) == 0)
105     return(MagickTrue);
106   return(MagickFalse);
107 }
108 
109 /*
110 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
111 %                                                                             %
112 %                                                                             %
113 %                                                                             %
114 %   R e a d H D R I m a g e                                                   %
115 %                                                                             %
116 %                                                                             %
117 %                                                                             %
118 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
119 %
120 %  ReadHDRImage() reads the Radiance RGBE image format and returns it.  It
121 %  allocates the memory necessary for the new Image structure and returns a
122 %  pointer to the new image.
123 %
124 %  The format of the ReadHDRImage method is:
125 %
126 %      Image *ReadHDRImage(const ImageInfo *image_info,ExceptionInfo *exception)
127 %
128 %  A description of each parameter follows:
129 %
130 %    o image_info: the image info.
131 %
132 %    o exception: return any errors or warnings in this structure.
133 %
134 */
ReadHDRImage(const ImageInfo * image_info,ExceptionInfo * exception)135 static Image *ReadHDRImage(const ImageInfo *image_info,ExceptionInfo *exception)
136 {
137   char
138     format[MagickPathExtent],
139     keyword[MagickPathExtent],
140     tag[MagickPathExtent],
141     value[MagickPathExtent];
142 
143   double
144     gamma;
145 
146   Image
147     *image;
148 
149   int
150     c;
151 
152   MagickBooleanType
153     status,
154     value_expected;
155 
156   Quantum
157     *q;
158 
159   ssize_t
160     i,
161     x;
162 
163   ssize_t
164     y;
165 
166   unsigned char
167     *end,
168     pixel[4],
169     *pixels;
170 
171   /*
172     Open image file.
173   */
174   assert(image_info != (const ImageInfo *) NULL);
175   assert(image_info->signature == MagickCoreSignature);
176   if (image_info->debug != MagickFalse)
177     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
178       image_info->filename);
179   assert(exception != (ExceptionInfo *) NULL);
180   assert(exception->signature == MagickCoreSignature);
181   image=AcquireImage(image_info,exception);
182   status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
183   if (status == MagickFalse)
184     {
185       image=DestroyImageList(image);
186       return((Image *) NULL);
187     }
188   /*
189     Decode image header.
190   */
191   image->columns=0;
192   image->rows=0;
193   *format='\0';
194   c=ReadBlobByte(image);
195   if (c == EOF)
196     {
197       image=DestroyImage(image);
198       return((Image *) NULL);
199     }
200   while (isgraph((int) ((unsigned char) c)) && (image->columns == 0) && (image->rows == 0))
201   {
202     if (c == (int) '#')
203       {
204         char
205           *comment;
206 
207         char
208           *p;
209 
210         size_t
211           length;
212 
213         /*
214           Read comment-- any text between # and end-of-line.
215         */
216         length=MagickPathExtent;
217         comment=AcquireString((char *) NULL);
218         for (p=comment; comment != (char *) NULL; p++)
219         {
220           c=ReadBlobByte(image);
221           if ((c == EOF) || (c == (int) '\n'))
222             break;
223           if ((size_t) (p-comment+1) >= length)
224             {
225               *p='\0';
226               length<<=1;
227               comment=(char *) ResizeQuantumMemory(comment,length+
228                 MagickPathExtent,sizeof(*comment));
229               if (comment == (char *) NULL)
230                 break;
231               p=comment+strlen(comment);
232             }
233           *p=(char) c;
234         }
235         if (comment == (char *) NULL)
236           ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
237         *p='\0';
238         (void) SetImageProperty(image,"comment",comment,exception);
239         comment=DestroyString(comment);
240         c=ReadBlobByte(image);
241       }
242     else
243       if (isalnum((int) ((unsigned char) c)) == 0)
244         c=ReadBlobByte(image);
245       else
246         {
247           char
248             *p;
249 
250           /*
251             Determine a keyword and its value.
252           */
253           p=keyword;
254           do
255           {
256             if ((size_t) (p-keyword) < (MagickPathExtent-1))
257               *p++=c;
258             c=ReadBlobByte(image);
259           } while (isalnum((int) ((unsigned char) c)) || (c == '_'));
260           *p='\0';
261           value_expected=MagickFalse;
262           while ((isspace((int) ((unsigned char) c)) != 0) || (c == '='))
263           {
264             if (c == '=')
265               value_expected=MagickTrue;
266             c=ReadBlobByte(image);
267           }
268           if (LocaleCompare(keyword,"Y") == 0)
269             value_expected=MagickTrue;
270           if (value_expected == MagickFalse)
271             continue;
272           p=value;
273           while ((c != '\n') && (c != '\0') && (c != EOF))
274           {
275             if ((size_t) (p-value) < (MagickPathExtent-1))
276               *p++=c;
277             c=ReadBlobByte(image);
278           }
279           *p='\0';
280           /*
281             Assign a value to the specified keyword.
282           */
283           switch (*keyword)
284           {
285             case 'F':
286             case 'f':
287             {
288               if (LocaleCompare(keyword,"format") == 0)
289                 {
290                   (void) CopyMagickString(format,value,MagickPathExtent);
291                   break;
292                 }
293               (void) FormatLocaleString(tag,MagickPathExtent,"hdr:%s",keyword);
294               (void) SetImageProperty(image,tag,value,exception);
295               break;
296             }
297             case 'G':
298             case 'g':
299             {
300               if (LocaleCompare(keyword,"gamma") == 0)
301                 {
302                   image->gamma=StringToDouble(value,(char **) NULL);
303                   break;
304                 }
305               (void) FormatLocaleString(tag,MagickPathExtent,"hdr:%s",keyword);
306               (void) SetImageProperty(image,tag,value,exception);
307               break;
308             }
309             case 'P':
310             case 'p':
311             {
312               if (LocaleCompare(keyword,"primaries") == 0)
313                 {
314                   float
315                     chromaticity[6],
316                     white_point[2];
317 
318                   int
319                     count;
320 
321                   count=sscanf(value,"%g %g %g %g %g %g %g %g",&chromaticity[0],
322                     &chromaticity[1],&chromaticity[2],&chromaticity[3],
323                     &chromaticity[4],&chromaticity[5],&white_point[0],
324                     &white_point[1]);
325                   if (count == 8)
326                     {
327                       image->chromaticity.red_primary.x=chromaticity[0];
328                       image->chromaticity.red_primary.y=chromaticity[1];
329                       image->chromaticity.green_primary.x=chromaticity[2];
330                       image->chromaticity.green_primary.y=chromaticity[3];
331                       image->chromaticity.blue_primary.x=chromaticity[4];
332                       image->chromaticity.blue_primary.y=chromaticity[5];
333                       image->chromaticity.white_point.x=white_point[0],
334                       image->chromaticity.white_point.y=white_point[1];
335                     }
336                   break;
337                 }
338               (void) FormatLocaleString(tag,MagickPathExtent,"hdr:%s",keyword);
339               (void) SetImageProperty(image,tag,value,exception);
340               break;
341             }
342             case 'Y':
343             case 'y':
344             {
345               char
346                 target[] = "Y";
347 
348               if (strcmp(keyword,target) == 0)
349                 {
350                   int
351                     height,
352                     width;
353 
354                   if (sscanf(value,"%d +X %d",&height,&width) == 2)
355                     {
356                       image->columns=(size_t) width;
357                       image->rows=(size_t) height;
358                     }
359                   break;
360                 }
361               (void) FormatLocaleString(tag,MagickPathExtent,"hdr:%s",keyword);
362               (void) SetImageProperty(image,tag,value,exception);
363               break;
364             }
365             default:
366             {
367               (void) FormatLocaleString(tag,MagickPathExtent,"hdr:%s",keyword);
368               (void) SetImageProperty(image,tag,value,exception);
369               break;
370             }
371           }
372         }
373     if ((image->columns == 0) && (image->rows == 0))
374       while (isspace((int) ((unsigned char) c)) != 0)
375         c=ReadBlobByte(image);
376   }
377   if ((LocaleCompare(format,"32-bit_rle_rgbe") != 0) &&
378       (LocaleCompare(format,"32-bit_rle_xyze") != 0))
379     ThrowReaderException(CorruptImageError,"ImproperImageHeader");
380   if ((image->columns == 0) || (image->rows == 0))
381     ThrowReaderException(CorruptImageError,"NegativeOrZeroImageSize");
382   (void) SetImageColorspace(image,RGBColorspace,exception);
383   if (LocaleCompare(format,"32-bit_rle_xyze") == 0)
384     (void) SetImageColorspace(image,XYZColorspace,exception);
385   image->compression=(image->columns < 8) || (image->columns > 0x7ffff) ?
386     NoCompression : RLECompression;
387   if (image_info->ping != MagickFalse)
388     {
389       (void) CloseBlob(image);
390       return(GetFirstImageInList(image));
391     }
392   status=SetImageExtent(image,image->columns,image->rows,exception);
393   if (status == MagickFalse)
394     return(DestroyImageList(image));
395   /*
396     Read RGBE (red+green+blue+exponent) pixels.
397   */
398   pixels=(unsigned char *) AcquireQuantumMemory(image->columns,4*
399     sizeof(*pixels));
400   if (pixels == (unsigned char *) NULL)
401     ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
402   (void) memset(pixels,0,4*image->columns*sizeof(*pixels));
403   for (y=0; y < (ssize_t) image->rows; y++)
404   {
405     ssize_t
406       count;
407 
408     if (image->compression != RLECompression)
409       {
410         count=ReadBlob(image,4*image->columns*sizeof(*pixels),pixels);
411         if (count != (ssize_t) (4*image->columns*sizeof(*pixels)))
412           break;
413       }
414     else
415       {
416         count=ReadBlob(image,4*sizeof(*pixel),pixel);
417         if (count != 4)
418           break;
419         if ((size_t) ((((size_t) pixel[2]) << 8) | pixel[3]) != image->columns)
420           {
421             (void) memcpy(pixels,pixel,4*sizeof(*pixel));
422             count=ReadBlob(image,4*(image->columns-1)*sizeof(*pixels),pixels+4);
423             image->compression=NoCompression;
424           }
425         else
426           {
427             unsigned char
428               *p;
429 
430             p=pixels;
431             for (i=0; i < 4; i++)
432             {
433               end=&pixels[(i+1)*image->columns];
434               while (p < end)
435               {
436                 count=ReadBlob(image,2*sizeof(*pixel),pixel);
437                 if (count < 1)
438                   break;
439                 if (pixel[0] > 128)
440                   {
441                     count=(ssize_t) pixel[0]-128;
442                     if ((count == 0) || (count > (ssize_t) (end-p)))
443                       break;
444                     while (count-- > 0)
445                       *p++=pixel[1];
446                   }
447                 else
448                   {
449                     count=(ssize_t) pixel[0];
450                     if ((count == 0) || (count > (ssize_t) (end-p)))
451                       break;
452                     *p++=pixel[1];
453                     if (--count > 0)
454                       {
455                         count=ReadBlob(image,(size_t) count*sizeof(*p),p);
456                         if (count < 1)
457                           break;
458                         p+=count;
459                       }
460                   }
461               }
462             }
463           }
464       }
465     q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
466     if (q == (Quantum *) NULL)
467       break;
468     i=0;
469     for (x=0; x < (ssize_t) image->columns; x++)
470     {
471       if (image->compression == RLECompression)
472         {
473           pixel[0]=pixels[x];
474           pixel[1]=pixels[x+image->columns];
475           pixel[2]=pixels[x+2*image->columns];
476           pixel[3]=pixels[x+3*image->columns];
477         }
478       else
479         {
480           pixel[0]=pixels[i++];
481           pixel[1]=pixels[i++];
482           pixel[2]=pixels[i++];
483           pixel[3]=pixels[i++];
484         }
485       SetPixelRed(image,0,q);
486       SetPixelGreen(image,0,q);
487       SetPixelBlue(image,0,q);
488       if (pixel[3] != 0)
489         {
490           gamma=pow(2.0,pixel[3]-(128.0+8.0));
491           SetPixelRed(image,ClampToQuantum(QuantumRange*gamma*pixel[0]),q);
492           SetPixelGreen(image,ClampToQuantum(QuantumRange*gamma*pixel[1]),q);
493           SetPixelBlue(image,ClampToQuantum(QuantumRange*gamma*pixel[2]),q);
494         }
495       q+=GetPixelChannels(image);
496     }
497     if (SyncAuthenticPixels(image,exception) == MagickFalse)
498       break;
499     status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
500       image->rows);
501     if (status == MagickFalse)
502       break;
503   }
504   pixels=(unsigned char *) RelinquishMagickMemory(pixels);
505   if (EOFBlob(image) != MagickFalse)
506     ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
507       image->filename);
508   (void) CloseBlob(image);
509   return(GetFirstImageInList(image));
510 }
511 
512 /*
513 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
514 %                                                                             %
515 %                                                                             %
516 %                                                                             %
517 %   R e g i s t e r H D R I m a g e                                           %
518 %                                                                             %
519 %                                                                             %
520 %                                                                             %
521 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
522 %
523 %  RegisterHDRImage() adds attributes for the Radiance RGBE image format to the
524 %  list of supported formats.  The attributes include the image format tag, a
525 %  method to read and/or write the format, whether the format supports the
526 %  saving of more than one frame to the same file or blob, whether the format
527 %  supports native in-memory I/O, and a brief description of the format.
528 %
529 %  The format of the RegisterHDRImage method is:
530 %
531 %      size_t RegisterHDRImage(void)
532 %
533 */
RegisterHDRImage(void)534 ModuleExport size_t RegisterHDRImage(void)
535 {
536   MagickInfo
537     *entry;
538 
539   entry=AcquireMagickInfo("HDR","HDR","Radiance RGBE image format");
540   entry->decoder=(DecodeImageHandler *) ReadHDRImage;
541   entry->encoder=(EncodeImageHandler *) WriteHDRImage;
542   entry->magick=(IsImageFormatHandler *) IsHDR;
543   (void) RegisterMagickInfo(entry);
544   return(MagickImageCoderSignature);
545 }
546 
547 /*
548 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
549 %                                                                             %
550 %                                                                             %
551 %                                                                             %
552 %   U n r e g i s t e r H D R I m a g e                                       %
553 %                                                                             %
554 %                                                                             %
555 %                                                                             %
556 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
557 %
558 %  UnregisterHDRImage() removes format registrations made by the
559 %  HDR module from the list of supported formats.
560 %
561 %  The format of the UnregisterHDRImage method is:
562 %
563 %      UnregisterHDRImage(void)
564 %
565 */
UnregisterHDRImage(void)566 ModuleExport void UnregisterHDRImage(void)
567 {
568   (void) UnregisterMagickInfo("HDR");
569 }
570 
571 /*
572 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
573 %                                                                             %
574 %                                                                             %
575 %                                                                             %
576 %   W r i t e H D R I m a g e                                                 %
577 %                                                                             %
578 %                                                                             %
579 %                                                                             %
580 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
581 %
582 %  WriteHDRImage() writes an image in the Radience RGBE image format.
583 %
584 %  The format of the WriteHDRImage method is:
585 %
586 %      MagickBooleanType WriteHDRImage(const ImageInfo *image_info,
587 %        Image *image,ExceptionInfo *exception)
588 %
589 %  A description of each parameter follows.
590 %
591 %    o image_info: the image info.
592 %
593 %    o image:  The image.
594 %
595 */
596 
HDRWriteRunlengthPixels(Image * image,unsigned char * pixels)597 static size_t HDRWriteRunlengthPixels(Image *image,unsigned char *pixels)
598 {
599 #define MinimumRunlength 4
600 
601   size_t
602     p,
603     q;
604 
605   size_t
606     runlength;
607 
608   ssize_t
609     count,
610     previous_count;
611 
612   unsigned char
613     pixel[2];
614 
615   for (p=0; p < image->columns; )
616   {
617     q=p;
618     runlength=0;
619     previous_count=0;
620     while ((runlength < MinimumRunlength) && (q < image->columns))
621     {
622       q+=runlength;
623       previous_count=(ssize_t) runlength;
624       runlength=1;
625       while ((pixels[q] == pixels[q+runlength]) &&
626              ((q+runlength) < image->columns) && (runlength < 127))
627        runlength++;
628     }
629     if ((previous_count > 1) && (previous_count == (ssize_t) (q-p)))
630       {
631         pixel[0]=(unsigned char) (128+previous_count);
632         pixel[1]=pixels[p];
633         if (WriteBlob(image,2*sizeof(*pixel),pixel) < 1)
634           break;
635         p=q;
636       }
637     while (p < q)
638     {
639       count=(ssize_t) (q-p);
640       if (count > 128)
641         count=128;
642       pixel[0]=(unsigned char) count;
643       if (WriteBlob(image,sizeof(*pixel),pixel) < 1)
644         break;
645       if (WriteBlob(image,(size_t) count*sizeof(*pixel),&pixels[p]) < 1)
646         break;
647       p+=count;
648     }
649     if (runlength >= MinimumRunlength)
650       {
651         pixel[0]=(unsigned char) (128+runlength);
652         pixel[1]=pixels[q];
653         if (WriteBlob(image,2*sizeof(*pixel),pixel) < 1)
654           break;
655         p+=runlength;
656       }
657   }
658   return(p);
659 }
660 
WriteHDRImage(const ImageInfo * image_info,Image * image,ExceptionInfo * exception)661 static MagickBooleanType WriteHDRImage(const ImageInfo *image_info,Image *image,
662   ExceptionInfo *exception)
663 {
664   char
665     header[MagickPathExtent];
666 
667   const char
668     *property;
669 
670   MagickBooleanType
671     status;
672 
673   const Quantum
674     *p;
675 
676   ssize_t
677     i,
678     x;
679 
680   size_t
681     length;
682 
683   ssize_t
684     count,
685     y;
686 
687   unsigned char
688     pixel[4],
689     *pixels;
690 
691   /*
692     Open output image file.
693   */
694   assert(image_info != (const ImageInfo *) NULL);
695   assert(image_info->signature == MagickCoreSignature);
696   assert(image != (Image *) NULL);
697   assert(image->signature == MagickCoreSignature);
698   if (image->debug != MagickFalse)
699     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
700   assert(exception != (ExceptionInfo *) NULL);
701   assert(exception->signature == MagickCoreSignature);
702   status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
703   if (status == MagickFalse)
704     return(status);
705   if (IsRGBColorspace(image->colorspace) == MagickFalse)
706     (void) TransformImageColorspace(image,RGBColorspace,exception);
707   /*
708     Write header.
709   */
710   (void) memset(header,' ',MagickPathExtent);
711   length=CopyMagickString(header,"#?RADIANCE\n",MagickPathExtent);
712   (void) WriteBlob(image,length,(unsigned char *) header);
713   property=GetImageProperty(image,"comment",exception);
714   if ((property != (const char *) NULL) &&
715       (strchr(property,'\n') == (char *) NULL))
716     {
717       count=FormatLocaleString(header,MagickPathExtent,"#%.*s\n",
718         MagickPathExtent-3,property);
719       (void) WriteBlob(image,(size_t) count,(unsigned char *) header);
720     }
721   property=GetImageProperty(image,"hdr:exposure",exception);
722   if (property != (const char *) NULL)
723     {
724       count=FormatLocaleString(header,MagickPathExtent,"EXPOSURE=%g\n",
725         strtod(property,(char **) NULL));
726       (void) WriteBlob(image,(size_t) count,(unsigned char *) header);
727     }
728   if (image->gamma != 0.0)
729     {
730       count=FormatLocaleString(header,MagickPathExtent,"GAMMA=%g\n",
731         image->gamma);
732       (void) WriteBlob(image,(size_t) count,(unsigned char *) header);
733     }
734   count=FormatLocaleString(header,MagickPathExtent,
735     "PRIMARIES=%g %g %g %g %g %g %g %g\n",
736     image->chromaticity.red_primary.x,image->chromaticity.red_primary.y,
737     image->chromaticity.green_primary.x,image->chromaticity.green_primary.y,
738     image->chromaticity.blue_primary.x,image->chromaticity.blue_primary.y,
739     image->chromaticity.white_point.x,image->chromaticity.white_point.y);
740   (void) WriteBlob(image,(size_t) count,(unsigned char *) header);
741   length=CopyMagickString(header,"FORMAT=32-bit_rle_rgbe\n\n",MagickPathExtent);
742   (void) WriteBlob(image,length,(unsigned char *) header);
743   count=FormatLocaleString(header,MagickPathExtent,"-Y %.20g +X %.20g\n",
744     (double) image->rows,(double) image->columns);
745   (void) WriteBlob(image,(size_t) count,(unsigned char *) header);
746   /*
747     Write HDR pixels.
748   */
749   pixels=(unsigned char *) AcquireQuantumMemory(image->columns+128,4*
750     sizeof(*pixels));
751   if (pixels == (unsigned char *) NULL)
752     ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
753   (void) memset(pixels,0,4*(image->columns+128)*sizeof(*pixels));
754   for (y=0; y < (ssize_t) image->rows; y++)
755   {
756     p=GetVirtualPixels(image,0,y,image->columns,1,exception);
757     if (p == (const Quantum *) NULL)
758       break;
759     if ((image->columns >= 8) && (image->columns <= 0x7ffff))
760       {
761         pixel[0]=2;
762         pixel[1]=2;
763         pixel[2]=(unsigned char) (image->columns >> 8);
764         pixel[3]=(unsigned char) (image->columns & 0xff);
765         count=WriteBlob(image,4*sizeof(*pixel),pixel);
766         if (count != (ssize_t) (4*sizeof(*pixel)))
767           break;
768       }
769     i=0;
770     for (x=0; x < (ssize_t) image->columns; x++)
771     {
772       double
773         gamma;
774 
775       pixel[0]=0;
776       pixel[1]=0;
777       pixel[2]=0;
778       pixel[3]=0;
779       gamma=QuantumScale*GetPixelRed(image,p);
780       if ((QuantumScale*GetPixelGreen(image,p)) > gamma)
781         gamma=QuantumScale*GetPixelGreen(image,p);
782       if ((QuantumScale*GetPixelBlue(image,p)) > gamma)
783         gamma=QuantumScale*GetPixelBlue(image,p);
784       if (gamma > MagickEpsilon)
785         {
786           int
787             exponent;
788 
789           gamma=frexp(gamma,&exponent)*256.0/gamma;
790           if (GetPixelRed(image,p) > 0)
791             pixel[0]=(unsigned char) (gamma*QuantumScale*GetPixelRed(image,p));
792           if (GetPixelGreen(image,p) > 0)
793             pixel[1]=(unsigned char) (gamma*QuantumScale*
794               GetPixelGreen(image,p));
795           if (GetPixelBlue(image,p) > 0)
796             pixel[2]=(unsigned char) (gamma*QuantumScale*GetPixelBlue(image,p));
797           pixel[3]=(unsigned char) (exponent+128);
798         }
799       if ((image->columns >= 8) && (image->columns <= 0x7ffff))
800         {
801           pixels[x]=pixel[0];
802           pixels[x+image->columns]=pixel[1];
803           pixels[x+2*image->columns]=pixel[2];
804           pixels[x+3*image->columns]=pixel[3];
805         }
806       else
807         {
808           pixels[i++]=pixel[0];
809           pixels[i++]=pixel[1];
810           pixels[i++]=pixel[2];
811           pixels[i++]=pixel[3];
812         }
813       p+=GetPixelChannels(image);
814     }
815     if ((image->columns >= 8) && (image->columns <= 0x7ffff))
816       {
817         for (i=0; i < 4; i++)
818           length=HDRWriteRunlengthPixels(image,&pixels[i*image->columns]);
819       }
820     else
821       {
822         count=WriteBlob(image,4*image->columns*sizeof(*pixels),pixels);
823         if (count != (ssize_t) (4*image->columns*sizeof(*pixels)))
824           break;
825       }
826     status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
827       image->rows);
828     if (status == MagickFalse)
829       break;
830   }
831   pixels=(unsigned char *) RelinquishMagickMemory(pixels);
832   (void) CloseBlob(image);
833   return(MagickTrue);
834 }
835