blob: 1e7feb54c1a1a031026637adeb73206d99d9eb3a [file] [log] [blame]
cristy579bc8f2011-03-06 17:27:05 +00001/*
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% %
cristy03533f22011-03-06 23:30:17 +000013% Read/Write Radiance RGBE Image Format %
cristy579bc8f2011-03-06 17:27:05 +000014% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
20% Copyright 1999-2011 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% http://www.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*/
cristy4c08aed2011-07-01 19:47:50 +000042#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/exception.h"
48#include "MagickCore/exception-private.h"
49#include "MagickCore/image.h"
50#include "MagickCore/image-private.h"
51#include "MagickCore/list.h"
52#include "MagickCore/magick.h"
53#include "MagickCore/memory_.h"
54#include "MagickCore/monitor.h"
55#include "MagickCore/monitor-private.h"
56#include "MagickCore/pixel-accessor.h"
57#include "MagickCore/property.h"
58#include "MagickCore/quantum-private.h"
59#include "MagickCore/static.h"
60#include "MagickCore/string_.h"
61#include "MagickCore/string-private.h"
62#include "MagickCore/module.h"
cristy579bc8f2011-03-06 17:27:05 +000063
64/*
cristy84c3d052011-03-07 19:22:02 +000065 Forward declarations.
66*/
67static MagickBooleanType
68 WriteHDRImage(const ImageInfo *,Image *);
69
70/*
cristy579bc8f2011-03-06 17:27:05 +000071%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
72% %
73% %
74% %
cristy03533f22011-03-06 23:30:17 +000075% I s H D R %
76% %
77% %
78% %
79%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
80%
81% IsHDR() returns MagickTrue if the image format type, identified by the
82% magick string, is Radiance RGBE image format.
83%
84% The format of the IsHDR method is:
85%
86% MagickBooleanType IsHDR(const unsigned char *magick,
87% const size_t length)
88%
89% A description of each parameter follows:
90%
91% o magick: compare image format pattern against these bytes.
92%
93% o length: Specifies the length of the magick string.
94%
95*/
96static MagickBooleanType IsHDR(const unsigned char *magick,
97 const size_t length)
98{
99 if (length < 10)
100 return(MagickFalse);
101 if (LocaleNCompare((const char *) magick,"#?RADIANCE",10) == 0)
102 return(MagickTrue);
cristy84c3d052011-03-07 19:22:02 +0000103 if (LocaleNCompare((const char *) magick,"#?RGBE",6) == 0)
104 return(MagickTrue);
cristy03533f22011-03-06 23:30:17 +0000105 return(MagickFalse);
106}
107
108/*
109%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
110% %
111% %
112% %
cristy579bc8f2011-03-06 17:27:05 +0000113% R e a d H D R I m a g e %
114% %
115% %
116% %
117%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
118%
cristy03533f22011-03-06 23:30:17 +0000119% ReadHDRImage() reads the Radiance RGBE image format and returns it. It
cristy21384232011-03-06 17:31:08 +0000120% allocates the memory necessary for the new Image structure and returns a
121% pointer to the new image.
cristy579bc8f2011-03-06 17:27:05 +0000122%
123% The format of the ReadHDRImage method is:
124%
125% Image *ReadHDRImage(const ImageInfo *image_info,ExceptionInfo *exception)
126%
127% A description of each parameter follows:
128%
129% o image_info: the image info.
130%
131% o exception: return any errors or warnings in this structure.
132%
133*/
134static Image *ReadHDRImage(const ImageInfo *image_info,ExceptionInfo *exception)
135{
cristy03533f22011-03-06 23:30:17 +0000136 char
137 format[MaxTextExtent],
138 keyword[MaxTextExtent],
139 tag[MaxTextExtent],
140 value[MaxTextExtent];
141
142 double
143 gamma;
144
cristy579bc8f2011-03-06 17:27:05 +0000145 Image
146 *image;
147
cristy03533f22011-03-06 23:30:17 +0000148 int
149 c;
cristy579bc8f2011-03-06 17:27:05 +0000150
cristy03533f22011-03-06 23:30:17 +0000151 MagickBooleanType
152 status,
153 value_expected;
cristy579bc8f2011-03-06 17:27:05 +0000154
cristy4c08aed2011-07-01 19:47:50 +0000155 register Quantum
cristy579bc8f2011-03-06 17:27:05 +0000156 *q;
157
158 register unsigned char
159 *p;
160
cristy03533f22011-03-06 23:30:17 +0000161 register ssize_t
162 i,
163 x;
cristy579bc8f2011-03-06 17:27:05 +0000164
165 ssize_t
166 count,
167 y;
168
169 unsigned char
cristy03533f22011-03-06 23:30:17 +0000170 *end,
171 pixel[4],
cristy579bc8f2011-03-06 17:27:05 +0000172 *pixels;
173
174 /*
175 Open image file.
176 */
177 assert(image_info != (const ImageInfo *) NULL);
178 assert(image_info->signature == MagickSignature);
179 if (image_info->debug != MagickFalse)
180 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
181 image_info->filename);
182 assert(exception != (ExceptionInfo *) NULL);
183 assert(exception->signature == MagickSignature);
184 image=AcquireImage(image_info);
185 status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
186 if (status == MagickFalse)
187 {
188 image=DestroyImageList(image);
189 return((Image *) NULL);
190 }
191 /*
cristy03533f22011-03-06 23:30:17 +0000192 Decode image header.
cristy579bc8f2011-03-06 17:27:05 +0000193 */
cristy03533f22011-03-06 23:30:17 +0000194 image->columns=0;
195 image->rows=0;
196 *format='\0';
197 c=ReadBlobByte(image);
198 if (c == EOF)
cristy579bc8f2011-03-06 17:27:05 +0000199 {
cristy03533f22011-03-06 23:30:17 +0000200 image=DestroyImage(image);
201 return((Image *) NULL);
cristy579bc8f2011-03-06 17:27:05 +0000202 }
cristy03533f22011-03-06 23:30:17 +0000203 while (isgraph(c) && (image->columns == 0) && (image->rows == 0))
204 {
cristy97bd7c32011-03-08 02:52:04 +0000205 if (c == (int) '#')
cristy579bc8f2011-03-06 17:27:05 +0000206 {
cristy97bd7c32011-03-08 02:52:04 +0000207 char
208 *comment;
209
cristy03533f22011-03-06 23:30:17 +0000210 register char
211 *p;
212
cristy97bd7c32011-03-08 02:52:04 +0000213 size_t
214 length;
cristy03533f22011-03-06 23:30:17 +0000215
cristy97bd7c32011-03-08 02:52:04 +0000216 /*
217 Read comment-- any text between # and end-of-line.
218 */
219 length=MaxTextExtent;
220 comment=AcquireString((char *) NULL);
221 for (p=comment; comment != (char *) NULL; p++)
222 {
223 c=ReadBlobByte(image);
224 if ((c == EOF) || (c == (int) '\n'))
225 break;
226 if ((size_t) (p-comment+1) >= length)
227 {
228 *p='\0';
229 length<<=1;
230 comment=(char *) ResizeQuantumMemory(comment,length+
231 MaxTextExtent,sizeof(*comment));
232 if (comment == (char *) NULL)
cristy03533f22011-03-06 23:30:17 +0000233 break;
cristy97bd7c32011-03-08 02:52:04 +0000234 p=comment+strlen(comment);
235 }
236 *p=(char) c;
cristy03533f22011-03-06 23:30:17 +0000237 }
cristy97bd7c32011-03-08 02:52:04 +0000238 if (comment == (char *) NULL)
239 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
240 *p='\0';
241 (void) SetImageProperty(image,"comment",comment);
242 comment=DestroyString(comment);
243 c=ReadBlobByte(image);
cristy579bc8f2011-03-06 17:27:05 +0000244 }
cristy97bd7c32011-03-08 02:52:04 +0000245 else
246 if (isalnum(c) == MagickFalse)
247 c=ReadBlobByte(image);
248 else
249 {
250 register char
251 *p;
252
253 /*
254 Determine a keyword and its value.
255 */
256 p=keyword;
257 do
258 {
259 if ((size_t) (p-keyword) < (MaxTextExtent-1))
260 *p++=c;
261 c=ReadBlobByte(image);
262 } while (isalnum(c) || (c == '_'));
263 *p='\0';
264 value_expected=MagickFalse;
265 while ((isspace((int) ((unsigned char) c)) != 0) || (c == '='))
266 {
267 if (c == '=')
268 value_expected=MagickTrue;
269 c=ReadBlobByte(image);
270 }
271 if (LocaleCompare(keyword,"Y") == 0)
272 value_expected=MagickTrue;
273 if (value_expected == MagickFalse)
274 continue;
275 p=value;
276 while ((c != '\n') && (c != '\0'))
277 {
278 if ((size_t) (p-value) < (MaxTextExtent-1))
279 *p++=c;
280 c=ReadBlobByte(image);
281 }
282 *p='\0';
283 /*
284 Assign a value to the specified keyword.
285 */
286 switch (*keyword)
287 {
288 case 'F':
289 case 'f':
290 {
291 if (LocaleCompare(keyword,"format") == 0)
292 {
293 (void) CopyMagickString(format,value,MaxTextExtent);
294 break;
295 }
cristyb51dff52011-05-19 16:55:47 +0000296 (void) FormatLocaleString(tag,MaxTextExtent,"hdr:%s",keyword);
cristy97bd7c32011-03-08 02:52:04 +0000297 (void) SetImageProperty(image,tag,value);
298 break;
299 }
300 case 'G':
301 case 'g':
302 {
303 if (LocaleCompare(keyword,"gamma") == 0)
304 {
cristyc1acd842011-05-19 23:05:47 +0000305 image->gamma=InterpretLocaleValue(value,(char **) NULL);
cristy97bd7c32011-03-08 02:52:04 +0000306 break;
307 }
cristyb51dff52011-05-19 16:55:47 +0000308 (void) FormatLocaleString(tag,MaxTextExtent,"hdr:%s",keyword);
cristy97bd7c32011-03-08 02:52:04 +0000309 (void) SetImageProperty(image,tag,value);
310 break;
311 }
312 case 'P':
313 case 'p':
314 {
315 if (LocaleCompare(keyword,"primaries") == 0)
316 {
317 float
318 chromaticity[6],
319 white_point[2];
320
cristyb84ca112011-03-30 14:16:18 +0000321 (void) sscanf(value,"%g %g %g %g %g %g %g %g",
322 &chromaticity[0],&chromaticity[1],&chromaticity[2],
323 &chromaticity[3],&chromaticity[4],&chromaticity[5],
324 &white_point[0],&white_point[1]);
cristy97bd7c32011-03-08 02:52:04 +0000325 image->chromaticity.red_primary.x=chromaticity[0];
326 image->chromaticity.red_primary.y=chromaticity[1];
327 image->chromaticity.green_primary.x=chromaticity[2];
328 image->chromaticity.green_primary.y=chromaticity[3];
329 image->chromaticity.blue_primary.x=chromaticity[4];
330 image->chromaticity.blue_primary.y=chromaticity[5];
331 image->chromaticity.white_point.x=white_point[0],
332 image->chromaticity.white_point.y=white_point[1];
333 break;
334 }
cristyb51dff52011-05-19 16:55:47 +0000335 (void) FormatLocaleString(tag,MaxTextExtent,"hdr:%s",keyword);
cristy97bd7c32011-03-08 02:52:04 +0000336 (void) SetImageProperty(image,tag,value);
337 break;
338 }
339 case 'Y':
340 case 'y':
341 {
342 if (strcmp(keyword,"Y") == 0)
343 {
344 int
345 height,
346 width;
347
348 (void) sscanf(value,"%d +X %d",&height,&width);
349 image->columns=(size_t) width;
350 image->rows=(size_t) height;
351 break;
352 }
cristyb51dff52011-05-19 16:55:47 +0000353 (void) FormatLocaleString(tag,MaxTextExtent,"hdr:%s",keyword);
cristy97bd7c32011-03-08 02:52:04 +0000354 (void) SetImageProperty(image,tag,value);
355 break;
356 }
357 default:
358 {
cristyb51dff52011-05-19 16:55:47 +0000359 (void) FormatLocaleString(tag,MaxTextExtent,"hdr:%s",keyword);
cristy97bd7c32011-03-08 02:52:04 +0000360 (void) SetImageProperty(image,tag,value);
361 break;
362 }
363 }
364 }
cristy03533f22011-03-06 23:30:17 +0000365 if ((image->columns == 0) && (image->rows == 0))
366 while (isspace((int) ((unsigned char) c)) != 0)
367 c=ReadBlobByte(image);
368 }
cristya74b77f2011-04-17 01:59:47 +0000369 if ((LocaleCompare(format,"32-bit_rle_rgbe") != 0) &&
370 (LocaleCompare(format,"32-bit_rle_xyze") != 0))
cristy03533f22011-03-06 23:30:17 +0000371 ThrowReaderException(CorruptImageError,"ImproperImageHeader");
372 if ((image->columns == 0) || (image->rows == 0))
373 ThrowReaderException(CorruptImageError,"NegativeOrZeroImageSize");
cristyf0e4a5a2011-04-17 02:15:18 +0000374 if (LocaleCompare(format,"32-bit_rle_rgbe") == 0)
375 image->colorspace=XYZColorspace;
376 image->compression=(image->columns < 8) || (image->columns > 0x7ffff) ?
377 NoCompression : RLECompression;
cristy03533f22011-03-06 23:30:17 +0000378 if (image_info->ping != MagickFalse)
379 {
380 (void) CloseBlob(image);
381 return(GetFirstImageInList(image));
382 }
383 /*
cristya8ff9422011-03-06 23:58:56 +0000384 Read RGBE (red+green+blue+exponent) pixels.
cristy03533f22011-03-06 23:30:17 +0000385 */
cristyb84ca112011-03-30 14:16:18 +0000386 pixels=(unsigned char *) AcquireQuantumMemory(image->columns,4*
387 sizeof(*pixels));
cristy03533f22011-03-06 23:30:17 +0000388 if (pixels == (unsigned char *) NULL)
389 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
390 for (y=0; y < (ssize_t) image->rows; y++)
391 {
cristyf0e4a5a2011-04-17 02:15:18 +0000392 if (image->compression != RLECompression)
cristy03533f22011-03-06 23:30:17 +0000393 {
cristya74b77f2011-04-17 01:59:47 +0000394 count=ReadBlob(image,4*image->columns*sizeof(*pixels),pixels);
395 if (count != (ssize_t) (4*image->columns*sizeof(*pixels)))
cristy03533f22011-03-06 23:30:17 +0000396 break;
cristy97bd7c32011-03-08 02:52:04 +0000397 }
398 else
399 {
400 count=ReadBlob(image,4*sizeof(*pixel),pixel);
401 if (count != 4)
402 break;
403 if ((size_t) ((((size_t) pixel[2]) << 8) | pixel[3]) != image->columns)
cristy03533f22011-03-06 23:30:17 +0000404 {
cristyf0e4a5a2011-04-17 02:15:18 +0000405 (void) memcpy(pixels,pixel,4*sizeof(*pixel));
406 count=ReadBlob(image,4*(image->columns-1)*sizeof(*pixels),pixels+4);
407 image->compression=NoCompression;
408 }
409 else
410 {
411 p=pixels;
412 for (i=0; i < 4; i++)
413 {
414 end=&pixels[(i+1)*image->columns];
415 while (p < end)
cristy03533f22011-03-06 23:30:17 +0000416 {
cristyf0e4a5a2011-04-17 02:15:18 +0000417 count=ReadBlob(image,2*sizeof(*pixel),pixel);
418 if (count < 1)
cristy03533f22011-03-06 23:30:17 +0000419 break;
cristyf0e4a5a2011-04-17 02:15:18 +0000420 if (pixel[0] > 128)
cristy97bd7c32011-03-08 02:52:04 +0000421 {
cristyf0e4a5a2011-04-17 02:15:18 +0000422 count=(ssize_t) pixel[0]-128;
423 if ((count == 0) || (count > (ssize_t) (end-p)))
cristy97bd7c32011-03-08 02:52:04 +0000424 break;
cristyf0e4a5a2011-04-17 02:15:18 +0000425 while (count-- > 0)
426 *p++=pixel[1];
427 }
428 else
429 {
430 count=(ssize_t) pixel[0];
431 if ((count == 0) || (count > (ssize_t) (end-p)))
432 break;
433 *p++=pixel[1];
434 if (--count > 0)
435 {
436 count=ReadBlob(image,(size_t) count*sizeof(*p),p);
437 if (count < 1)
438 break;
439 p+=count;
440 }
cristy97bd7c32011-03-08 02:52:04 +0000441 }
cristy03533f22011-03-06 23:30:17 +0000442 }
cristyf0e4a5a2011-04-17 02:15:18 +0000443 }
cristy03533f22011-03-06 23:30:17 +0000444 }
445 }
cristy03533f22011-03-06 23:30:17 +0000446 q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +0000447 if (q == (const Quantum *) NULL)
cristy03533f22011-03-06 23:30:17 +0000448 break;
cristy97bd7c32011-03-08 02:52:04 +0000449 i=0;
cristy03533f22011-03-06 23:30:17 +0000450 for (x=0; x < (ssize_t) image->columns; x++)
451 {
cristyf0e4a5a2011-04-17 02:15:18 +0000452 if (image->compression == RLECompression)
cristy97bd7c32011-03-08 02:52:04 +0000453 {
454 pixel[0]=pixels[x];
455 pixel[1]=pixels[x+image->columns];
456 pixel[2]=pixels[x+2*image->columns];
457 pixel[3]=pixels[x+3*image->columns];
458 }
459 else
460 {
461 pixel[0]=pixels[i++];
462 pixel[1]=pixels[i++];
463 pixel[2]=pixels[i++];
464 pixel[3]=pixels[i++];
465 }
cristy4c08aed2011-07-01 19:47:50 +0000466 SetPixelRed(image,0,q);
467 SetPixelGreen(image,0,q);
468 SetPixelBlue(image,0,q);
cristy03533f22011-03-06 23:30:17 +0000469 if (pixel[3] != 0)
470 {
471 gamma=pow(2.0,pixel[3]-(128.0+8.0));
cristy4c08aed2011-07-01 19:47:50 +0000472 SetPixelRed(image,ClampToQuantum(QuantumRange*gamma*pixel[0]),q);
473 SetPixelGreen(image,ClampToQuantum(QuantumRange*gamma*pixel[1]),q);
474 SetPixelBlue(image,ClampToQuantum(QuantumRange*gamma*pixel[2]),q);
cristy03533f22011-03-06 23:30:17 +0000475 }
cristy4c08aed2011-07-01 19:47:50 +0000476 q+=GetPixelChannels(image);
cristy03533f22011-03-06 23:30:17 +0000477 }
478 if (SyncAuthenticPixels(image,exception) == MagickFalse)
479 break;
480 status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
481 image->rows);
482 if (status == MagickFalse)
483 break;
484 }
485 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
486 if (EOFBlob(image) != MagickFalse)
487 ThrowFileException(exception,CorruptImageError,"UnexpectedEndOfFile",
488 image->filename);
cristy579bc8f2011-03-06 17:27:05 +0000489 (void) CloseBlob(image);
490 return(GetFirstImageInList(image));
491}
492
493/*
494%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
495% %
496% %
497% %
498% R e g i s t e r H D R I m a g e %
499% %
500% %
501% %
502%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
503%
cristy03533f22011-03-06 23:30:17 +0000504% RegisterHDRImage() adds attributes for the Radiance RGBE image format to the
cristy21384232011-03-06 17:31:08 +0000505% list of supported formats. The attributes include the image format tag, a
cristy579bc8f2011-03-06 17:27:05 +0000506% method to read and/or write the format, whether the format supports the
507% saving of more than one frame to the same file or blob, whether the format
508% supports native in-memory I/O, and a brief description of the format.
509%
510% The format of the RegisterHDRImage method is:
511%
512% size_t RegisterHDRImage(void)
513%
514*/
515ModuleExport size_t RegisterHDRImage(void)
516{
517 MagickInfo
518 *entry;
519
520 entry=SetMagickInfo("HDR");
521 entry->decoder=(DecodeImageHandler *) ReadHDRImage;
cristy84c3d052011-03-07 19:22:02 +0000522 entry->encoder=(EncodeImageHandler *) WriteHDRImage;
cristy03533f22011-03-06 23:30:17 +0000523 entry->description=ConstantString("Radiance RGBE image format");
cristy579bc8f2011-03-06 17:27:05 +0000524 entry->module=ConstantString("HDR");
cristy03533f22011-03-06 23:30:17 +0000525 entry->magick=(IsImageFormatHandler *) IsHDR;
cristy579bc8f2011-03-06 17:27:05 +0000526 (void) RegisterMagickInfo(entry);
527 return(MagickImageCoderSignature);
528}
529
530/*
531%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
532% %
533% %
534% %
535% U n r e g i s t e r H D R I m a g e %
536% %
537% %
538% %
539%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
540%
541% UnregisterHDRImage() removes format registrations made by the
542% HDR module from the list of supported formats.
543%
544% The format of the UnregisterHDRImage method is:
545%
546% UnregisterHDRImage(void)
547%
548*/
549ModuleExport void UnregisterHDRImage(void)
550{
551 (void) UnregisterMagickInfo("HDR");
552}
cristy84c3d052011-03-07 19:22:02 +0000553
554/*
555%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
556% %
557% %
558% %
559% W r i t e H D R I m a g e %
560% %
561% %
562% %
563%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
564%
565% WriteHDRImage() writes an image in the Radience RGBE image format.
566%
567% The format of the WriteHDRImage method is:
568%
569% MagickBooleanType WriteHDRImage(const ImageInfo *image_info,
570% Image *image)
571%
572% A description of each parameter follows.
573%
574% o image_info: the image info.
575%
576% o image: The image.
577%
578*/
cristy97bd7c32011-03-08 02:52:04 +0000579
580static size_t HDRWriteRunlengthPixels(Image *image,unsigned char *pixels)
581{
582#define MinimumRunlength 4
583
584 register size_t
585 p,
586 q;
587
588 size_t
589 runlength;
590
591 ssize_t
592 count,
593 previous_count;
594
595 unsigned char
596 pixel[2];
597
598 for (p=0; p < image->columns; )
599 {
600 q=p;
601 runlength=0;
602 previous_count=0;
603 while ((runlength < MinimumRunlength) && (q < image->columns))
604 {
605 q+=runlength;
606 previous_count=(ssize_t) runlength;
607 runlength=1;
608 while ((pixels[q] == pixels[q+runlength]) &&
609 ((q+runlength) < image->columns) && (runlength < 127))
610 runlength++;
611 }
612 if ((previous_count > 1) && (previous_count == (ssize_t) (q-p)))
613 {
614 pixel[0]=(unsigned char) (128+previous_count);
615 pixel[1]=pixels[p];
616 if (WriteBlob(image,2*sizeof(*pixel),pixel) < 1)
617 break;
618 p=q;
619 }
620 while (p < q)
621 {
622 count=(ssize_t) (q-p);
623 if (count > 128)
624 count=128;
625 pixel[0]=(unsigned char) count;
626 if (WriteBlob(image,sizeof(*pixel),pixel) < 1)
627 break;
628 if (WriteBlob(image,(size_t) count*sizeof(*pixel),&pixels[p]) < 1)
629 break;
630 p+=count;
631 }
632 if (runlength >= MinimumRunlength)
633 {
634 pixel[0]=(unsigned char) (128+runlength);
635 pixel[1]=pixels[q];
636 if (WriteBlob(image,2*sizeof(*pixel),pixel) < 1)
637 break;
638 p+=runlength;
639 }
640 }
641 return(p);
642}
643
cristy84c3d052011-03-07 19:22:02 +0000644static MagickBooleanType WriteHDRImage(const ImageInfo *image_info,Image *image)
645{
646 char
647 header[MaxTextExtent];
648
cristy97bd7c32011-03-08 02:52:04 +0000649 const char
650 *property;
cristy84c3d052011-03-07 19:22:02 +0000651
652 MagickBooleanType
653 status;
654
cristy4c08aed2011-07-01 19:47:50 +0000655 register const Quantum
cristy84c3d052011-03-07 19:22:02 +0000656 *p;
657
cristy97bd7c32011-03-08 02:52:04 +0000658 register ssize_t
659 i,
660 x;
cristy84c3d052011-03-07 19:22:02 +0000661
662 size_t
663 length;
664
cristy97bd7c32011-03-08 02:52:04 +0000665 ssize_t
666 count,
667 y;
668
cristy84c3d052011-03-07 19:22:02 +0000669 unsigned char
cristy97bd7c32011-03-08 02:52:04 +0000670 pixel[4],
cristy84c3d052011-03-07 19:22:02 +0000671 *pixels;
672
673 /*
674 Open output image file.
675 */
676 assert(image_info != (const ImageInfo *) NULL);
677 assert(image_info->signature == MagickSignature);
678 assert(image != (Image *) NULL);
679 assert(image->signature == MagickSignature);
680 if (image->debug != MagickFalse)
681 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
682 status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
683 if (status == MagickFalse)
684 return(status);
685 if (image->colorspace != RGBColorspace)
686 (void) TransformImageColorspace(image,RGBColorspace);
687 /*
688 Write header.
689 */
690 (void) ResetMagickMemory(header,' ',MaxTextExtent);
cristy97bd7c32011-03-08 02:52:04 +0000691 length=CopyMagickString(header,"#?RGBE\n",MaxTextExtent);
692 (void) WriteBlob(image,length,(unsigned char *) header);
693 property=GetImageProperty(image,"comment");
694 if ((property != (const char *) NULL) &&
695 (strchr(property,'\n') == (char *) NULL))
696 {
cristyb51dff52011-05-19 16:55:47 +0000697 count=FormatLocaleString(header,MaxTextExtent,"#%s\n",property);
cristy97bd7c32011-03-08 02:52:04 +0000698 (void) WriteBlob(image,(size_t) count,(unsigned char *) header);
699 }
700 property=GetImageProperty(image,"hdr:exposure");
701 if (property != (const char *) NULL)
702 {
cristyb51dff52011-05-19 16:55:47 +0000703 count=FormatLocaleString(header,MaxTextExtent,"EXPOSURE=%g\n",
cristy97bd7c32011-03-08 02:52:04 +0000704 atof(property));
705 (void) WriteBlob(image,(size_t) count,(unsigned char *) header);
706 }
707 if (image->gamma != 0.0)
708 {
cristyb51dff52011-05-19 16:55:47 +0000709 count=FormatLocaleString(header,MaxTextExtent,"GAMMA=%g\n",image->gamma);
cristy97bd7c32011-03-08 02:52:04 +0000710 (void) WriteBlob(image,(size_t) count,(unsigned char *) header);
711 }
cristyb51dff52011-05-19 16:55:47 +0000712 count=FormatLocaleString(header,MaxTextExtent,
cristy97bd7c32011-03-08 02:52:04 +0000713 "PRIMARIES=%g %g %g %g %g %g %g %g\n",
714 image->chromaticity.red_primary.x,image->chromaticity.red_primary.y,
715 image->chromaticity.green_primary.x,image->chromaticity.green_primary.y,
716 image->chromaticity.blue_primary.x,image->chromaticity.blue_primary.y,
717 image->chromaticity.white_point.x,image->chromaticity.white_point.y);
718 (void) WriteBlob(image,(size_t) count,(unsigned char *) header);
719 length=CopyMagickString(header,"FORMAT=32-bit_rle_rgbe\n\n",MaxTextExtent);
720 (void) WriteBlob(image,length,(unsigned char *) header);
cristyb51dff52011-05-19 16:55:47 +0000721 count=FormatLocaleString(header,MaxTextExtent,"-Y %.20g +X %.20g\n",
cristy84c3d052011-03-07 19:22:02 +0000722 (double) image->rows,(double) image->columns);
cristy97bd7c32011-03-08 02:52:04 +0000723 (void) WriteBlob(image,(size_t) count,(unsigned char *) header);
cristy84c3d052011-03-07 19:22:02 +0000724 /*
725 Write HDR pixels.
726 */
cristyebc891a2011-04-24 23:04:16 +0000727 pixels=(unsigned char *) AcquireQuantumMemory(image->columns,4*
728 sizeof(*pixels));
cristy97bd7c32011-03-08 02:52:04 +0000729 if (pixels == (unsigned char *) NULL)
cristy84c3d052011-03-07 19:22:02 +0000730 ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
cristy84c3d052011-03-07 19:22:02 +0000731 for (y=0; y < (ssize_t) image->rows; y++)
732 {
733 p=GetVirtualPixels(image,0,y,image->columns,1,&image->exception);
cristy4c08aed2011-07-01 19:47:50 +0000734 if (p == (const Quantum *) NULL)
cristy84c3d052011-03-07 19:22:02 +0000735 break;
cristy97bd7c32011-03-08 02:52:04 +0000736 if ((image->columns >= 8) && (image->columns <= 0x7ffff))
737 {
738 pixel[0]=2;
739 pixel[1]=2;
740 pixel[2]=(unsigned char) (image->columns >> 8);
741 pixel[3]=(unsigned char) (image->columns & 0xff);
742 count=WriteBlob(image,4*sizeof(*pixel),pixel);
743 if (count != (ssize_t) (4*sizeof(*pixel)))
744 break;
745 }
746 i=0;
747 for (x=0; x < (ssize_t) image->columns; x++)
748 {
749 double
750 gamma;
751
752 pixel[0]=0;
753 pixel[1]=0;
754 pixel[2]=0;
755 pixel[3]=0;
cristy4c08aed2011-07-01 19:47:50 +0000756 gamma=QuantumScale*GetPixelRed(image,p);
757 if ((QuantumScale*GetPixelGreen(image,p)) > gamma)
758 gamma=QuantumScale*GetPixelGreen(image,p);
759 if ((QuantumScale*GetPixelBlue(image,p)) > gamma)
760 gamma=QuantumScale*GetPixelBlue(image,p);
cristy97bd7c32011-03-08 02:52:04 +0000761 if (gamma > MagickEpsilon)
762 {
763 int
764 exponent;
765
766 gamma=frexp(gamma,&exponent)*256.0/gamma;
cristy4c08aed2011-07-01 19:47:50 +0000767 pixel[0]=(unsigned char) (gamma*QuantumScale*GetPixelRed(image,p));
768 pixel[1]=(unsigned char) (gamma*QuantumScale*GetPixelGreen(image,p));
769 pixel[2]=(unsigned char) (gamma*QuantumScale*GetPixelBlue(image,p));
cristy97bd7c32011-03-08 02:52:04 +0000770 pixel[3]=(unsigned char) (exponent+128);
771 }
772 if ((image->columns >= 8) && (image->columns <= 0x7ffff))
773 {
774 pixels[x]=pixel[0];
775 pixels[x+image->columns]=pixel[1];
776 pixels[x+2*image->columns]=pixel[2];
777 pixels[x+3*image->columns]=pixel[3];
778 }
779 else
780 {
781 pixels[i++]=pixel[0];
782 pixels[i++]=pixel[1];
783 pixels[i++]=pixel[2];
784 pixels[i++]=pixel[3];
785 }
cristy4c08aed2011-07-01 19:47:50 +0000786 p+=GetPixelChannels(image);
cristy97bd7c32011-03-08 02:52:04 +0000787 }
788 if ((image->columns >= 8) && (image->columns <= 0x7ffff))
789 {
790 for (i=0; i < 4; i++)
791 length=HDRWriteRunlengthPixels(image,&pixels[i*image->columns]);
792 }
793 else
794 {
795 count=WriteBlob(image,4*image->columns*sizeof(*pixel),pixel);
796 if (count != (ssize_t) (4*image->columns*sizeof(*pixel)))
797 break;
798 }
cristy84c3d052011-03-07 19:22:02 +0000799 status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
800 image->rows);
801 if (status == MagickFalse)
802 break;
803 }
cristy97bd7c32011-03-08 02:52:04 +0000804 pixels=(unsigned char *) RelinquishMagickMemory(pixels);
cristy84c3d052011-03-07 19:22:02 +0000805 (void) CloseBlob(image);
806 return(MagickTrue);
807}