/src/freeimage-svn/FreeImage/trunk/Source/FreeImage/PluginHDR.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // ========================================================== |
2 | | // HDR Loader and writer |
3 | | // |
4 | | // Design and implementation by |
5 | | // - Hervé Drolon (drolon@infonie.fr) |
6 | | // |
7 | | // This file is part of FreeImage 3 |
8 | | // |
9 | | // COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, WITHOUT WARRANTY |
10 | | // OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES |
11 | | // THAT THE COVERED CODE IS FREE OF DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE |
12 | | // OR NON-INFRINGING. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED |
13 | | // CODE IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, YOU (NOT |
14 | | // THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE COST OF ANY NECESSARY |
15 | | // SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER OF WARRANTY CONSTITUTES AN ESSENTIAL |
16 | | // PART OF THIS LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER |
17 | | // THIS DISCLAIMER. |
18 | | // |
19 | | // Use at your own risk! |
20 | | // ========================================================== |
21 | | |
22 | | #include "FreeImage.h" |
23 | | #include "Utilities.h" |
24 | | |
25 | | // ========================================================== |
26 | | // Plugin Interface |
27 | | // ========================================================== |
28 | | |
29 | | static int s_format_id; |
30 | | |
31 | | // ========================================================== |
32 | | // RGBE library |
33 | | // ========================================================== |
34 | | |
35 | | // ---------------------------------------------------------- |
36 | | |
37 | | // maximum size of a line in the header |
38 | 0 | #define HDR_MAXLINE 256 |
39 | | |
40 | | // flags indicating which fields in an rgbeHeaderInfo are valid |
41 | 0 | #define RGBE_VALID_PROGRAMTYPE 0x01 |
42 | 0 | #define RGBE_VALID_COMMENT 0x02 |
43 | 0 | #define RGBE_VALID_GAMMA 0x04 |
44 | 0 | #define RGBE_VALID_EXPOSURE 0x08 |
45 | | |
46 | | // offsets to red, green, and blue components in a data (float) pixel |
47 | | #define RGBE_DATA_RED 0 |
48 | | #define RGBE_DATA_GREEN 1 |
49 | | #define RGBE_DATA_BLUE 2 |
50 | | |
51 | | // ---------------------------------------------------------- |
52 | | #ifdef _WIN32 |
53 | | #pragma pack(push, 1) |
54 | | #else |
55 | | #pragma pack(1) |
56 | | #endif |
57 | | |
58 | | typedef struct tagHeaderInfo { |
59 | | int valid; // indicate which fields are valid |
60 | | char programtype[16]; // listed at beginning of file to identify it after "#?". defaults to "RGBE" |
61 | | char comment[HDR_MAXLINE]; // comment beginning with "# " |
62 | | float gamma; // image has already been gamma corrected with given gamma. defaults to 1.0 (no correction) |
63 | | float exposure; // a value of 1.0 in an image corresponds to <exposure> watts/steradian/m^2. defaults to 1.0 |
64 | | } rgbeHeaderInfo; |
65 | | |
66 | | #ifdef _WIN32 |
67 | | #pragma pack(pop) |
68 | | #else |
69 | | #pragma pack() |
70 | | #endif |
71 | | |
72 | | typedef enum { |
73 | | rgbe_read_error, |
74 | | rgbe_write_error, |
75 | | rgbe_format_error, |
76 | | rgbe_memory_error |
77 | | } rgbe_error_code; |
78 | | |
79 | | // ---------------------------------------------------------- |
80 | | // Prototypes |
81 | | // ---------------------------------------------------------- |
82 | | |
83 | | static BOOL rgbe_Error(rgbe_error_code error_code, const char *msg); |
84 | | static BOOL rgbe_GetLine(FreeImageIO *io, fi_handle handle, char *buffer, int length); |
85 | | static inline void rgbe_FloatToRGBE(BYTE rgbe[4], FIRGBF *rgbf); |
86 | | static inline void rgbe_RGBEToFloat(FIRGBF *rgbf, BYTE rgbe[4]); |
87 | | static BOOL rgbe_ReadHeader(FreeImageIO *io, fi_handle handle, unsigned *width, unsigned *height, rgbeHeaderInfo *header_info); |
88 | | static BOOL rgbe_WriteHeader(FreeImageIO *io, fi_handle handle, unsigned width, unsigned height, rgbeHeaderInfo *info); |
89 | | static BOOL rgbe_ReadPixels(FreeImageIO *io, fi_handle handle, FIRGBF *data, unsigned numpixels); |
90 | | static BOOL rgbe_WritePixels(FreeImageIO *io, fi_handle handle, FIRGBF *data, unsigned numpixels); |
91 | | static BOOL rgbe_ReadPixels_RLE(FreeImageIO *io, fi_handle handle, FIRGBF *data, int scanline_width, unsigned num_scanlines); |
92 | | static BOOL rgbe_WriteBytes_RLE(FreeImageIO *io, fi_handle handle, BYTE *data, int numbytes); |
93 | | static BOOL rgbe_WritePixels_RLE(FreeImageIO *io, fi_handle handle, FIRGBF *data, unsigned scanline_width, unsigned num_scanlines); |
94 | | static BOOL rgbe_ReadMetadata(FIBITMAP *dib, rgbeHeaderInfo *header_info); |
95 | | static BOOL rgbe_WriteMetadata(FIBITMAP *dib, rgbeHeaderInfo *header_info); |
96 | | |
97 | | // ---------------------------------------------------------- |
98 | | |
99 | | /** |
100 | | Default error routine. change this to change error handling |
101 | | */ |
102 | | static BOOL |
103 | 0 | rgbe_Error(rgbe_error_code error_code, const char *msg) { |
104 | 0 | switch (error_code) { |
105 | 0 | case rgbe_read_error: |
106 | 0 | FreeImage_OutputMessageProc(s_format_id, "RGBE read error"); |
107 | 0 | break; |
108 | 0 | case rgbe_write_error: |
109 | 0 | FreeImage_OutputMessageProc(s_format_id, "RGBE write error"); |
110 | 0 | break; |
111 | 0 | case rgbe_format_error: |
112 | 0 | FreeImage_OutputMessageProc(s_format_id, "RGBE bad file format: %s\n", msg); |
113 | 0 | break; |
114 | 0 | case rgbe_memory_error: |
115 | 0 | default: |
116 | 0 | FreeImage_OutputMessageProc(s_format_id, "RGBE error: %s\n",msg); |
117 | 0 | } |
118 | | |
119 | 0 | return FALSE; |
120 | 0 | } |
121 | | |
122 | | /** |
123 | | Get a line from a ASCII io stream |
124 | | */ |
125 | | static BOOL |
126 | 0 | rgbe_GetLine(FreeImageIO *io, fi_handle handle, char *buffer, int length) { |
127 | 0 | int i; |
128 | 0 | memset(buffer, 0, length); |
129 | 0 | for(i = 0; i < length; i++) { |
130 | 0 | if (!io->read_proc(&buffer[i], 1, 1, handle)) { |
131 | 0 | return FALSE; |
132 | 0 | } |
133 | 0 | if (buffer[i] == 0x0A) { |
134 | 0 | break; |
135 | 0 | } |
136 | 0 | } |
137 | | |
138 | 0 | return (i < length) ? TRUE : FALSE; |
139 | 0 | } |
140 | | |
141 | | /** |
142 | | Standard conversion from float pixels to rgbe pixels. |
143 | | Note: you can remove the "inline"s if your compiler complains about it |
144 | | */ |
145 | | static inline void |
146 | 0 | rgbe_FloatToRGBE(BYTE rgbe[4], FIRGBF *rgbf) { |
147 | 0 | float v = rgbf->red; |
148 | 0 | if (rgbf->green > v) { |
149 | 0 | v = rgbf->green; |
150 | 0 | } |
151 | 0 | if (rgbf->blue > v) { |
152 | 0 | v = rgbf->blue; |
153 | 0 | } |
154 | 0 | if (v < 1e-32) { |
155 | 0 | rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; |
156 | 0 | } |
157 | 0 | else { |
158 | 0 | int e; |
159 | 0 | v = (float)(frexp(v, &e) * 256.0 / v); |
160 | 0 | rgbe[0] = (BYTE) (rgbf->red * v); |
161 | 0 | rgbe[1] = (BYTE) (rgbf->green * v); |
162 | 0 | rgbe[2] = (BYTE) (rgbf->blue * v); |
163 | 0 | rgbe[3] = (BYTE) (e + 128); |
164 | 0 | } |
165 | 0 | } |
166 | | |
167 | | /** |
168 | | Standard conversion from rgbe to float pixels. |
169 | | Note: Ward uses ldexp(col+0.5,exp-(128+8)). |
170 | | However we wanted pixels in the range [0,1] to map back into the range [0,1]. |
171 | | */ |
172 | | static inline void |
173 | 0 | rgbe_RGBEToFloat(FIRGBF *rgbf, BYTE rgbe[4]) { |
174 | 0 | if (rgbe[3]) { // nonzero pixel |
175 | 0 | const float f = (float)(ldexp(1.0, rgbe[3] - (int)(128+8))); |
176 | 0 | rgbf->red = rgbe[0] * f; |
177 | 0 | rgbf->green = rgbe[1] * f; |
178 | 0 | rgbf->blue = rgbe[2] * f; |
179 | 0 | } |
180 | 0 | else { |
181 | 0 | rgbf->red = rgbf->green = rgbf->blue = 0; |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | | /** |
186 | | Minimal header reading. Modify if you want to parse more information |
187 | | */ |
188 | | static BOOL |
189 | 0 | rgbe_ReadHeader(FreeImageIO *io, fi_handle handle, unsigned *width, unsigned *height, rgbeHeaderInfo *header_info) { |
190 | 0 | char buf[HDR_MAXLINE]; |
191 | 0 | float tempf; |
192 | 0 | int i; |
193 | 0 | BOOL bFormatFound = FALSE; |
194 | 0 | BOOL bHeaderFound = FALSE; |
195 | |
|
196 | 0 | header_info->valid = 0; |
197 | 0 | header_info->programtype[0] = 0; |
198 | 0 | header_info->gamma = 1.0; |
199 | 0 | header_info->exposure = 1.0; |
200 | | |
201 | | // get the first line |
202 | 0 | if (!rgbe_GetLine(io, handle, buf, HDR_MAXLINE)) { |
203 | 0 | return rgbe_Error(rgbe_read_error, NULL); |
204 | 0 | } |
205 | | |
206 | | // check the signature |
207 | | |
208 | 0 | if ((buf[0] != '#')||(buf[1] != '?')) { |
209 | | // if you don't want to require the magic token then comment the next line |
210 | 0 | return rgbe_Error(rgbe_format_error,"bad initial token"); |
211 | 0 | } |
212 | 0 | else { |
213 | 0 | header_info->valid |= RGBE_VALID_PROGRAMTYPE; |
214 | 0 | for(i = 0; i < sizeof(header_info->programtype) - 1; i++) { |
215 | 0 | if ((buf[i + 2] == 0) || isspace(buf[i + 2])) { |
216 | 0 | break; |
217 | 0 | } |
218 | 0 | header_info->programtype[i] = buf[i+2]; |
219 | 0 | } |
220 | 0 | header_info->programtype[i] = 0; |
221 | 0 | } |
222 | | |
223 | 0 | for(;;) { |
224 | | // get next line |
225 | 0 | if (!rgbe_GetLine(io, handle, buf, HDR_MAXLINE)) { |
226 | 0 | return rgbe_Error(rgbe_read_error, NULL); |
227 | 0 | } |
228 | | |
229 | 0 | if((buf[0] == 0) || (buf[0] == '\n')) { |
230 | | // end of header so break out of loop |
231 | 0 | bHeaderFound = TRUE; |
232 | 0 | break; |
233 | 0 | } |
234 | 0 | else if(strcmp(buf,"FORMAT=32-bit_rle_rgbe\n") == 0) { |
235 | 0 | bFormatFound = TRUE; |
236 | 0 | } |
237 | 0 | else if(sscanf(buf, "GAMMA=%g", &tempf) == 1) { |
238 | 0 | header_info->gamma = tempf; |
239 | 0 | header_info->valid |= RGBE_VALID_GAMMA; |
240 | 0 | } |
241 | 0 | else if(sscanf(buf,"EXPOSURE=%g",&tempf) == 1) { |
242 | 0 | header_info->exposure = tempf; |
243 | 0 | header_info->valid |= RGBE_VALID_EXPOSURE; |
244 | 0 | } |
245 | 0 | else if((buf[0] == '#') && (buf[1] == 0x20)) { |
246 | 0 | header_info->valid |= RGBE_VALID_COMMENT; |
247 | 0 | strncpy(header_info->comment, buf, HDR_MAXLINE - 1); |
248 | 0 | header_info->comment[HDR_MAXLINE - 1] = '\0'; |
249 | 0 | } |
250 | 0 | } |
251 | 0 | if(!bHeaderFound || !bFormatFound) { |
252 | 0 | return rgbe_Error(rgbe_format_error, "invalid header"); |
253 | 0 | } |
254 | | |
255 | | // get next line |
256 | 0 | if (!rgbe_GetLine(io, handle, buf, HDR_MAXLINE)) { |
257 | 0 | return rgbe_Error(rgbe_read_error, NULL); |
258 | 0 | } |
259 | | |
260 | | // get the image width & height |
261 | 0 | if(sscanf(buf,"-Y %d +X %d", height, width) < 2) { |
262 | 0 | if(sscanf(buf,"+X %d +Y %d", height, width) < 2) { |
263 | 0 | return rgbe_Error(rgbe_format_error, "missing image size specifier"); |
264 | 0 | } |
265 | 0 | } |
266 | | |
267 | 0 | return TRUE; |
268 | 0 | } |
269 | | |
270 | | /** |
271 | | default minimal header. modify if you want more information in header |
272 | | */ |
273 | | static BOOL |
274 | 0 | rgbe_WriteHeader(FreeImageIO *io, fi_handle handle, unsigned width, unsigned height, rgbeHeaderInfo *info) { |
275 | 0 | char buffer[HDR_MAXLINE + 1]; |
276 | |
|
277 | 0 | const char *programtype = "RADIANCE"; |
278 | |
|
279 | 0 | if (info && (info->valid & RGBE_VALID_PROGRAMTYPE)) { |
280 | 0 | programtype = info->programtype; |
281 | 0 | } |
282 | | // The #? is to identify file type, the programtype is optional |
283 | 0 | snprintf(buffer, HDR_MAXLINE, "#?%s\n", programtype); |
284 | 0 | if (io->write_proc(buffer, 1, (unsigned int)strlen(buffer), handle) < 1) { |
285 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
286 | 0 | } |
287 | 0 | snprintf(buffer, HDR_MAXLINE, "%s\n", info->comment); |
288 | 0 | if (io->write_proc(buffer, 1, (unsigned int)strlen(buffer), handle) < 1) { |
289 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
290 | 0 | } |
291 | 0 | snprintf(buffer, HDR_MAXLINE, "FORMAT=32-bit_rle_rgbe\n"); |
292 | 0 | if (io->write_proc(buffer, 1, (unsigned int)strlen(buffer), handle) < 1) { |
293 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
294 | 0 | } |
295 | 0 | if (info && (info->valid & RGBE_VALID_GAMMA)) { |
296 | 0 | snprintf(buffer, HDR_MAXLINE, "GAMMA=%g\n", info->gamma); |
297 | 0 | if (io->write_proc(buffer, 1, (unsigned int)strlen(buffer), handle) < 1) { |
298 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
299 | 0 | } |
300 | 0 | } |
301 | 0 | if (info && (info->valid & RGBE_VALID_EXPOSURE)) { |
302 | 0 | snprintf(buffer, HDR_MAXLINE, "EXPOSURE=%g\n", info->exposure); |
303 | 0 | if (io->write_proc(buffer, 1, (unsigned int)strlen(buffer), handle) < 1) { |
304 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
305 | 0 | } |
306 | 0 | } |
307 | 0 | snprintf(buffer, HDR_MAXLINE, "\n-Y %d +X %d\n", height, width); |
308 | 0 | if (io->write_proc(buffer, 1, (unsigned int)strlen(buffer), handle) < 1) { |
309 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
310 | 0 | } |
311 | | |
312 | 0 | return TRUE; |
313 | 0 | } |
314 | | |
315 | | static BOOL |
316 | 0 | rgbe_ReadMetadata(FIBITMAP *dib, rgbeHeaderInfo *header_info) { |
317 | 0 | return TRUE; |
318 | 0 | } |
319 | | static BOOL |
320 | 0 | rgbe_WriteMetadata(FIBITMAP *dib, rgbeHeaderInfo *header_info) { |
321 | 0 | header_info->gamma = 1; |
322 | 0 | header_info->valid |= RGBE_VALID_GAMMA; |
323 | 0 | header_info->exposure = 1; // default value |
324 | 0 | header_info->valid |= RGBE_VALID_EXPOSURE; |
325 | |
|
326 | 0 | return TRUE; |
327 | 0 | } |
328 | | |
329 | | /** |
330 | | Simple read routine. Will not correctly handle run length encoding |
331 | | */ |
332 | | static BOOL |
333 | 0 | rgbe_ReadPixels(FreeImageIO *io, fi_handle handle, FIRGBF *data, unsigned numpixels) { |
334 | 0 | BYTE rgbe[4]; |
335 | |
|
336 | 0 | for(unsigned x = 0; x < numpixels; x++) { |
337 | 0 | if(io->read_proc(rgbe, 1, sizeof(rgbe), handle) < 1) { |
338 | 0 | return rgbe_Error(rgbe_read_error, NULL); |
339 | 0 | } |
340 | 0 | rgbe_RGBEToFloat(&data[x], rgbe); |
341 | 0 | } |
342 | | |
343 | 0 | return TRUE; |
344 | 0 | } |
345 | | |
346 | | /** |
347 | | Simple write routine that does not use run length encoding. |
348 | | These routines can be made faster by allocating a larger buffer and |
349 | | fread-ing and fwrite-ing the data in larger chunks. |
350 | | */ |
351 | | static BOOL |
352 | 0 | rgbe_WritePixels(FreeImageIO *io, fi_handle handle, FIRGBF *data, unsigned numpixels) { |
353 | 0 | BYTE rgbe[4]; |
354 | |
|
355 | 0 | for(unsigned x = 0; x < numpixels; x++) { |
356 | 0 | rgbe_FloatToRGBE(rgbe, &data[x]); |
357 | 0 | if (io->write_proc(rgbe, sizeof(rgbe), 1, handle) < 1) { |
358 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
359 | 0 | } |
360 | 0 | } |
361 | | |
362 | 0 | return TRUE; |
363 | 0 | } |
364 | | |
365 | | static BOOL |
366 | 0 | rgbe_ReadPixels_RLE(FreeImageIO *io, fi_handle handle, FIRGBF *data, int scanline_width, unsigned num_scanlines) { |
367 | 0 | BYTE rgbe[4], *scanline_buffer, *ptr, *ptr_end; |
368 | 0 | int i, count; |
369 | 0 | BYTE buf[2]; |
370 | | |
371 | 0 | if ((scanline_width < 8)||(scanline_width > 0x7fff)) { |
372 | | // run length encoding is not allowed so read flat |
373 | 0 | return rgbe_ReadPixels(io, handle, data, scanline_width * num_scanlines); |
374 | 0 | } |
375 | 0 | scanline_buffer = NULL; |
376 | | // read in each successive scanline |
377 | 0 | while(num_scanlines > 0) { |
378 | 0 | if(io->read_proc(rgbe, 1, sizeof(rgbe), handle) < 1) { |
379 | 0 | free(scanline_buffer); |
380 | 0 | return rgbe_Error(rgbe_read_error,NULL); |
381 | 0 | } |
382 | 0 | if((rgbe[0] != 2) || (rgbe[1] != 2) || (rgbe[2] & 0x80)) { |
383 | | // this file is not run length encoded |
384 | 0 | rgbe_RGBEToFloat(data, rgbe); |
385 | 0 | data ++; |
386 | 0 | free(scanline_buffer); |
387 | 0 | return rgbe_ReadPixels(io, handle, data, scanline_width * num_scanlines - 1); |
388 | 0 | } |
389 | 0 | if((((int)rgbe[2]) << 8 | rgbe[3]) != scanline_width) { |
390 | 0 | free(scanline_buffer); |
391 | 0 | return rgbe_Error(rgbe_format_error,"wrong scanline width"); |
392 | 0 | } |
393 | 0 | if(scanline_buffer == NULL) { |
394 | 0 | scanline_buffer = (BYTE*)malloc(sizeof(BYTE) * 4 * scanline_width); |
395 | 0 | if(scanline_buffer == NULL) { |
396 | 0 | return rgbe_Error(rgbe_memory_error, "unable to allocate buffer space"); |
397 | 0 | } |
398 | 0 | } |
399 | | |
400 | 0 | ptr = &scanline_buffer[0]; |
401 | | // read each of the four channels for the scanline into the buffer |
402 | 0 | for(i = 0; i < 4; i++) { |
403 | 0 | ptr_end = &scanline_buffer[(i+1)*scanline_width]; |
404 | 0 | while(ptr < ptr_end) { |
405 | 0 | if(io->read_proc(buf, 1, 2 * sizeof(BYTE), handle) < 1) { |
406 | 0 | free(scanline_buffer); |
407 | 0 | return rgbe_Error(rgbe_read_error, NULL); |
408 | 0 | } |
409 | 0 | if(buf[0] > 128) { |
410 | | // a run of the same value |
411 | 0 | count = buf[0] - 128; |
412 | 0 | if((count == 0) || (count > ptr_end - ptr)) { |
413 | 0 | free(scanline_buffer); |
414 | 0 | return rgbe_Error(rgbe_format_error, "bad scanline data"); |
415 | 0 | } |
416 | 0 | while (count-- > 0) { |
417 | 0 | *ptr++ = buf[1]; |
418 | 0 | } |
419 | 0 | } |
420 | 0 | else { |
421 | | // a non-run |
422 | 0 | count = buf[0]; |
423 | 0 | if((count == 0) || (count > ptr_end - ptr)) { |
424 | 0 | free(scanline_buffer); |
425 | 0 | return rgbe_Error(rgbe_format_error, "bad scanline data"); |
426 | 0 | } |
427 | 0 | *ptr++ = buf[1]; |
428 | 0 | if(--count > 0) { |
429 | 0 | if(io->read_proc(ptr, 1, sizeof(BYTE) * count, handle) < 1) { |
430 | 0 | free(scanline_buffer); |
431 | 0 | return rgbe_Error(rgbe_read_error, NULL); |
432 | 0 | } |
433 | 0 | ptr += count; |
434 | 0 | } |
435 | 0 | } |
436 | 0 | } |
437 | 0 | } |
438 | | // now convert data from buffer into floats |
439 | 0 | for(i = 0; i < scanline_width; i++) { |
440 | 0 | rgbe[0] = scanline_buffer[i]; |
441 | 0 | rgbe[1] = scanline_buffer[i+scanline_width]; |
442 | 0 | rgbe[2] = scanline_buffer[i+2*scanline_width]; |
443 | 0 | rgbe[3] = scanline_buffer[i+3*scanline_width]; |
444 | 0 | rgbe_RGBEToFloat(data, rgbe); |
445 | 0 | data ++; |
446 | 0 | } |
447 | |
|
448 | 0 | num_scanlines--; |
449 | 0 | } |
450 | | |
451 | 0 | free(scanline_buffer); |
452 | | |
453 | 0 | return TRUE; |
454 | 0 | } |
455 | | |
456 | | /** |
457 | | The code below is only needed for the run-length encoded files. |
458 | | Run length encoding adds considerable complexity but does |
459 | | save some space. For each scanline, each channel (r,g,b,e) is |
460 | | encoded separately for better compression. |
461 | | @return Returns TRUE if successful, returns FALSE otherwise |
462 | | */ |
463 | | static BOOL |
464 | 0 | rgbe_WriteBytes_RLE(FreeImageIO *io, fi_handle handle, BYTE *data, int numbytes) { |
465 | 0 | static const int MINRUNLENGTH = 4; |
466 | 0 | int cur, beg_run, run_count, old_run_count, nonrun_count; |
467 | 0 | BYTE buf[2]; |
468 | | |
469 | 0 | cur = 0; |
470 | 0 | while(cur < numbytes) { |
471 | 0 | beg_run = cur; |
472 | | // find next run of length at least 4 if one exists |
473 | 0 | run_count = old_run_count = 0; |
474 | 0 | while((run_count < MINRUNLENGTH) && (beg_run < numbytes)) { |
475 | 0 | beg_run += run_count; |
476 | 0 | old_run_count = run_count; |
477 | 0 | run_count = 1; |
478 | 0 | while((beg_run + run_count < numbytes) && (run_count < 127) && (data[beg_run] == data[beg_run + run_count])) { |
479 | 0 | run_count++; |
480 | 0 | } |
481 | 0 | } |
482 | | // if data before next big run is a short run then write it as such |
483 | 0 | if ((old_run_count > 1)&&(old_run_count == beg_run - cur)) { |
484 | 0 | buf[0] = (BYTE)(128 + old_run_count); // write short run |
485 | 0 | buf[1] = data[cur]; |
486 | 0 | if (io->write_proc(buf, 2 * sizeof(BYTE), 1, handle) < 1) { |
487 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
488 | 0 | } |
489 | 0 | cur = beg_run; |
490 | 0 | } |
491 | | // write out bytes until we reach the start of the next run |
492 | 0 | while(cur < beg_run) { |
493 | 0 | nonrun_count = beg_run - cur; |
494 | 0 | if (nonrun_count > 128) { |
495 | 0 | nonrun_count = 128; |
496 | 0 | } |
497 | 0 | buf[0] = (BYTE)nonrun_count; |
498 | 0 | if (io->write_proc(buf, sizeof(buf[0]), 1, handle) < 1) { |
499 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
500 | 0 | } |
501 | 0 | if (io->write_proc(&data[cur], sizeof(data[0]) * nonrun_count, 1, handle) < 1) { |
502 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
503 | 0 | } |
504 | 0 | cur += nonrun_count; |
505 | 0 | } |
506 | | // write out next run if one was found |
507 | 0 | if (run_count >= MINRUNLENGTH) { |
508 | 0 | buf[0] = (BYTE)(128 + run_count); |
509 | 0 | buf[1] = data[beg_run]; |
510 | 0 | if (io->write_proc(buf, sizeof(buf[0]) * 2, 1, handle) < 1) { |
511 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
512 | 0 | } |
513 | 0 | cur += run_count; |
514 | 0 | } |
515 | 0 | } |
516 | | |
517 | 0 | return TRUE; |
518 | 0 | } |
519 | | |
520 | | static BOOL |
521 | 0 | rgbe_WritePixels_RLE(FreeImageIO *io, fi_handle handle, FIRGBF *data, unsigned scanline_width, unsigned num_scanlines) { |
522 | 0 | BYTE rgbe[4]; |
523 | 0 | BYTE *buffer; |
524 | | |
525 | 0 | if ((scanline_width < 8)||(scanline_width > 0x7fff)) { |
526 | | // run length encoding is not allowed so write flat |
527 | 0 | return rgbe_WritePixels(io, handle, data, scanline_width * num_scanlines); |
528 | 0 | } |
529 | 0 | buffer = (BYTE*)malloc(sizeof(BYTE) * 4 * scanline_width); |
530 | 0 | if (buffer == NULL) { |
531 | | // no buffer space so write flat |
532 | 0 | return rgbe_WritePixels(io, handle, data, scanline_width * num_scanlines); |
533 | 0 | } |
534 | 0 | while(num_scanlines-- > 0) { |
535 | 0 | rgbe[0] = (BYTE)2; |
536 | 0 | rgbe[1] = (BYTE)2; |
537 | 0 | rgbe[2] = (BYTE)(scanline_width >> 8); |
538 | 0 | rgbe[3] = (BYTE)(scanline_width & 0xFF); |
539 | 0 | if(io->write_proc(rgbe, sizeof(rgbe), 1, handle) < 1) { |
540 | 0 | free(buffer); |
541 | 0 | return rgbe_Error(rgbe_write_error, NULL); |
542 | 0 | } |
543 | 0 | for(unsigned x = 0; x < scanline_width; x++) { |
544 | 0 | rgbe_FloatToRGBE(rgbe, data); |
545 | 0 | buffer[x] = rgbe[0]; |
546 | 0 | buffer[x+scanline_width] = rgbe[1]; |
547 | 0 | buffer[x+2*scanline_width] = rgbe[2]; |
548 | 0 | buffer[x+3*scanline_width] = rgbe[3]; |
549 | 0 | data ++; |
550 | 0 | } |
551 | | // write out each of the four channels separately run length encoded |
552 | | // first red, then green, then blue, then exponent |
553 | 0 | for(int i = 0; i < 4; i++) { |
554 | 0 | BOOL bOK = rgbe_WriteBytes_RLE(io, handle, &buffer[i*scanline_width], scanline_width); |
555 | 0 | if(!bOK) { |
556 | 0 | free(buffer); |
557 | 0 | return bOK; |
558 | 0 | } |
559 | 0 | } |
560 | 0 | } |
561 | 0 | free(buffer); |
562 | | |
563 | 0 | return TRUE; |
564 | 0 | } |
565 | | |
566 | | |
567 | | // ---------------------------------------------------------- |
568 | | |
569 | | |
570 | | |
571 | | // ========================================================== |
572 | | // Plugin Implementation |
573 | | // ========================================================== |
574 | | |
575 | | static const char * DLL_CALLCONV |
576 | 2 | Format() { |
577 | 2 | return "HDR"; |
578 | 2 | } |
579 | | |
580 | | static const char * DLL_CALLCONV |
581 | 0 | Description() { |
582 | 0 | return "High Dynamic Range Image"; |
583 | 0 | } |
584 | | |
585 | | static const char * DLL_CALLCONV |
586 | 0 | Extension() { |
587 | 0 | return "hdr"; |
588 | 0 | } |
589 | | |
590 | | static const char * DLL_CALLCONV |
591 | 0 | RegExpr() { |
592 | 0 | return NULL; |
593 | 0 | } |
594 | | |
595 | | static const char * DLL_CALLCONV |
596 | 0 | MimeType() { |
597 | 0 | return "image/vnd.radiance"; |
598 | 0 | } |
599 | | |
600 | | static BOOL DLL_CALLCONV |
601 | 24.0k | Validate(FreeImageIO *io, fi_handle handle) { |
602 | 24.0k | BYTE hdr_signature[] = { '#', '?' }; |
603 | 24.0k | BYTE signature[] = { 0, 0 }; |
604 | | |
605 | 24.0k | io->read_proc(signature, 1, 2, handle); |
606 | | |
607 | 24.0k | return (memcmp(hdr_signature, signature, 2) == 0); |
608 | 24.0k | } |
609 | | |
610 | | static BOOL DLL_CALLCONV |
611 | 0 | SupportsExportDepth(int depth) { |
612 | 0 | return FALSE; |
613 | 0 | } |
614 | | |
615 | | static BOOL DLL_CALLCONV |
616 | 0 | SupportsExportType(FREE_IMAGE_TYPE type) { |
617 | 0 | return (type == FIT_RGBF) ? TRUE : FALSE; |
618 | 0 | } |
619 | | |
620 | | static BOOL DLL_CALLCONV |
621 | 0 | SupportsNoPixels() { |
622 | 0 | return TRUE; |
623 | 0 | } |
624 | | |
625 | | // -------------------------------------------------------------------------- |
626 | | |
627 | | static FIBITMAP * DLL_CALLCONV |
628 | 0 | Load(FreeImageIO *io, fi_handle handle, int page, int flags, void *data) { |
629 | 0 | FIBITMAP *dib = NULL; |
630 | |
|
631 | 0 | if(!handle) { |
632 | 0 | return NULL; |
633 | 0 | } |
634 | | |
635 | 0 | BOOL header_only = (flags & FIF_LOAD_NOPIXELS) == FIF_LOAD_NOPIXELS; |
636 | |
|
637 | 0 | try { |
638 | |
|
639 | 0 | rgbeHeaderInfo header_info; |
640 | 0 | unsigned width, height; |
641 | | |
642 | | // Read the header |
643 | 0 | if(rgbe_ReadHeader(io, handle, &width, &height, &header_info) == FALSE) { |
644 | 0 | return NULL; |
645 | 0 | } |
646 | | |
647 | | // allocate a RGBF image |
648 | 0 | dib = FreeImage_AllocateHeaderT(header_only, FIT_RGBF, width, height); |
649 | 0 | if(!dib) { |
650 | 0 | throw FI_MSG_ERROR_MEMORY; |
651 | 0 | } |
652 | | |
653 | | // set the metadata as comments |
654 | 0 | rgbe_ReadMetadata(dib, &header_info); |
655 | |
|
656 | 0 | if(header_only) { |
657 | | // header only mode |
658 | 0 | return dib; |
659 | 0 | } |
660 | | |
661 | | // read the image pixels and fill the dib |
662 | | |
663 | 0 | for(unsigned y = 0; y < height; y++) { |
664 | 0 | FIRGBF *scanline = (FIRGBF*)FreeImage_GetScanLine(dib, height - 1 - y); |
665 | 0 | if(!rgbe_ReadPixels_RLE(io, handle, scanline, width, 1)) { |
666 | 0 | FreeImage_Unload(dib); |
667 | 0 | return NULL; |
668 | 0 | } |
669 | 0 | } |
670 | |
|
671 | 0 | } |
672 | 0 | catch(const char *text) { |
673 | 0 | if(dib != NULL) { |
674 | 0 | FreeImage_Unload(dib); |
675 | 0 | } |
676 | 0 | FreeImage_OutputMessageProc(s_format_id, text); |
677 | 0 | } |
678 | | |
679 | 0 | return dib; |
680 | 0 | } |
681 | | |
682 | | static BOOL DLL_CALLCONV |
683 | 0 | Save(FreeImageIO *io, FIBITMAP *dib, fi_handle handle, int page, int flags, void *data) { |
684 | 0 | if(!dib) return FALSE; |
685 | | |
686 | 0 | FREE_IMAGE_TYPE src_type = FreeImage_GetImageType(dib); |
687 | 0 | if(src_type != FIT_RGBF) { |
688 | 0 | FreeImage_OutputMessageProc(s_format_id, "FREE_IMAGE_TYPE: Unable to convert from type %d to type %d.\n No such conversion exists.", src_type, FIT_RGBF); |
689 | 0 | return FALSE; |
690 | 0 | } |
691 | | |
692 | 0 | unsigned width = FreeImage_GetWidth(dib); |
693 | 0 | unsigned height = FreeImage_GetHeight(dib); |
694 | | |
695 | | // write the header |
696 | |
|
697 | 0 | rgbeHeaderInfo header_info; |
698 | 0 | memset(&header_info, 0, sizeof(rgbeHeaderInfo)); |
699 | | // fill the header with correct gamma and exposure |
700 | 0 | rgbe_WriteMetadata(dib, &header_info); |
701 | | // fill a comment |
702 | 0 | sprintf(header_info.comment, "# Made with FreeImage %s", FreeImage_GetVersion()); |
703 | 0 | if(!rgbe_WriteHeader(io, handle, width, height, &header_info)) { |
704 | 0 | return FALSE; |
705 | 0 | } |
706 | | |
707 | | // write each scanline |
708 | | |
709 | 0 | for(unsigned y = 0; y < height; y++) { |
710 | 0 | FIRGBF *scanline = (FIRGBF*)FreeImage_GetScanLine(dib, height - 1 - y); |
711 | 0 | if(!rgbe_WritePixels_RLE(io, handle, scanline, width, 1)) { |
712 | 0 | return FALSE; |
713 | 0 | } |
714 | 0 | } |
715 | | |
716 | 0 | return TRUE; |
717 | 0 | } |
718 | | |
719 | | // ========================================================== |
720 | | // Init |
721 | | // ========================================================== |
722 | | |
723 | | void DLL_CALLCONV |
724 | 2 | InitHDR(Plugin *plugin, int format_id) { |
725 | 2 | s_format_id = format_id; |
726 | | |
727 | 2 | plugin->format_proc = Format; |
728 | 2 | plugin->description_proc = Description; |
729 | 2 | plugin->extension_proc = Extension; |
730 | 2 | plugin->regexpr_proc = RegExpr; |
731 | 2 | plugin->open_proc = NULL; |
732 | 2 | plugin->close_proc = NULL; |
733 | 2 | plugin->pagecount_proc = NULL; |
734 | 2 | plugin->pagecapability_proc = NULL; |
735 | 2 | plugin->load_proc = Load; |
736 | 2 | plugin->save_proc = Save; |
737 | 2 | plugin->validate_proc = Validate; |
738 | 2 | plugin->mime_proc = MimeType; |
739 | 2 | plugin->supports_export_bpp_proc = SupportsExportDepth; |
740 | 2 | plugin->supports_export_type_proc = SupportsExportType; |
741 | 2 | plugin->supports_icc_profiles_proc = NULL; |
742 | 2 | plugin->supports_no_pixels_proc = SupportsNoPixels; |
743 | 2 | } |