1 //******************************************************************************
2 ///
3 /// @file base/image/bmp.cpp
4 ///
5 /// Implementation of Windows Bitmap (BMP) image file handling.
6 ///
7 /// @author Wlodzimierz ABX Skiba (abx@abx.art.pl)
8 ///
9 /// @copyright
10 /// @parblock
11 ///
12 /// Persistence of Vision Ray Tracer ('POV-Ray') version 3.8.
13 /// Copyright 1991-2018 Persistence of Vision Raytracer Pty. Ltd.
14 ///
15 /// POV-Ray is free software: you can redistribute it and/or modify
16 /// it under the terms of the GNU Affero General Public License as
17 /// published by the Free Software Foundation, either version 3 of the
18 /// License, or (at your option) any later version.
19 ///
20 /// POV-Ray is distributed in the hope that it will be useful,
21 /// but WITHOUT ANY WARRANTY; without even the implied warranty of
22 /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 /// GNU Affero General Public License for more details.
24 ///
25 /// You should have received a copy of the GNU Affero General Public License
26 /// along with this program. If not, see <http://www.gnu.org/licenses/>.
27 ///
28 /// ----------------------------------------------------------------------------
29 ///
30 /// POV-Ray is based on the popular DKB raytracer version 2.12.
31 /// DKBTrace was originally written by David K. Buck.
32 /// DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
33 ///
34 /// @endparblock
35 ///
36 //******************************************************************************
37
38 // Unit header file must be the first file included within POV-Ray *.cpp files (pulls in config)
39 #include "base/image/bmp.h"
40
41 // Standard C++ header files
42 #include <vector>
43
44 // POV-Ray base header files
45 #include "base/types.h"
46
47 // this must be the last file included
48 #include "base/povdebug.h"
49
50
51 /*****************************************************************************
52 * Local preprocessor defines
53 ******************************************************************************/
54
55 #define WIN_NEW 40
56 #define WIN_OS2_OLD 12
57 #define BI_RGB 0L
58 #define BI_RLE8 1L
59 #define BI_RLE4 2L
60
61
62 namespace pov_base
63 {
64
65 namespace Bmp
66 {
67
68 /*****************************************************************************
69 *
70 * FUNCTION
71 *
72 * Read_Safe_Char
73 *
74 * INPUT
75 *
76 * *filePUT
77 *
78 * RETURNS
79 *
80 * AUTHOR
81 *
82 * Wlodzimierz ABX Skiba
83 *
84 * DESCRIPTION
85 *
86 * -
87 *
88 * CHANGES
89 *
90 * Aug 2003 : Creation.
91 * Jan 2004 : Added exception to allow cleanup on error [CJC]
92 *
93 ******************************************************************************/
94
Read_Safe_Char(IStream & in)95 static inline unsigned char Read_Safe_Char (IStream& in)
96 {
97 unsigned char ch;
98
99 ch = in.Read_Byte();
100 if (!in)
101 throw POV_EXCEPTION(kFileDataErr, "Error reading data from BMP image.") ;
102
103 return (ch) ;
104 }
105
106 // skip forward without using seekg
Skip(IStream * file,int bytes)107 static bool Skip (IStream *file, int bytes)
108 {
109 while (*file && bytes--)
110 file->Read_Byte () ;
111 return (*file) ;
112 }
113
114 // skip forward without using seekg
Skip(OStream * file,int bytes)115 static bool Skip (OStream *file, int bytes)
116 {
117 while (*file && bytes--)
118 file->Write_Byte (0) ;
119 return (*file) ;
120 }
121
122 // write a long to a stream in little-endian (e.g. x86) format
Write_Long(OStream * file,unsigned long val)123 static void Write_Long (OStream *file, unsigned long val)
124 {
125 file->Write_Byte (val & 0xff) ;
126 file->Write_Byte ((val >> 8) & 0xff) ;
127 file->Write_Byte ((val >> 16) & 0xff) ;
128 file->Write_Byte ((val >> 24) & 0xff) ;
129 }
130
131 // write a short to a stream in little-endian (e.g. x86) format
Write_Short(OStream * file,unsigned short val)132 static void Write_Short (OStream *file, unsigned short val)
133 {
134 file->Write_Byte (val & 0xff) ;
135 file->Write_Byte ((val >> 8) & 0xff) ;
136 }
137
138 // read a long from a stream in little-endian (e.g. x86) format
Read_Long(IStream * file)139 static unsigned long Read_Long (IStream *file)
140 {
141 unsigned long ch1 = Read_Safe_Char (*file) ;
142 unsigned long ch2 = Read_Safe_Char (*file) ;
143 unsigned long ch3 = Read_Safe_Char (*file) ;
144 unsigned long ch4 = Read_Safe_Char (*file) ;
145 return ((ch4 << 24) | (ch3 << 16) | (ch2 << 8) | ch1) ;
146 }
147
148 // read a short from a stream in little-endian (e.g. x86) format
Read_Short(IStream * file)149 static unsigned short Read_Short (IStream *file)
150 {
151 unsigned short ch1 = Read_Safe_Char (*file) ;
152 unsigned short ch2 = Read_Safe_Char (*file) ;
153 return ((ch2 << 8) | ch1) ;
154 }
155
156 /*****************************************************************************
157 *
158 * FUNCTION
159 *
160 * Read_BMP_1b
161 *
162 * INPUT
163 *
164 * *filePUT
165 *
166 * RETURNS
167 *
168 * AUTHOR
169 *
170 * Wlodzimierz ABX Skiba
171 *
172 * DESCRIPTION
173 *
174 * -
175 *
176 * CHANGES
177 *
178 * Aug 2003 : Creation.
179 *
180 ******************************************************************************/
181
Read_BMP_1b(Image * image,IStream & in,unsigned width,unsigned height)182 static void Read_BMP_1b(Image *image, IStream& in, unsigned width, unsigned height)
183 {
184 int c = 0;
185 unsigned pwidth = ((width+31)>>5)<<5; /* clear bits to get 4 byte boundary */
186
187 for (int y=height - 1; y >= 0; y--)
188 {
189 for (int x=0; x<pwidth; x++)
190 {
191 if ((x&7) == 0)
192 c = Read_Safe_Char (in);
193 if (x<width)
194 {
195 image->SetBitValue (x, y, (c & 0x80) ? 1 : 0);
196 c <<= 1;
197 }
198 }
199 }
200 }
201
202 /*****************************************************************************
203 *
204 * FUNCTION
205 *
206 * Read_BMP_4b_RGB
207 *
208 * INPUT
209 *
210 * *filePUT
211 *
212 * RETURNS
213 *
214 * AUTHOR
215 *
216 * Wlodzimierz ABX Skiba
217 *
218 * DESCRIPTION
219 *
220 * -
221 *
222 * CHANGES
223 *
224 * Aug 2003 : Creation.
225 *
226 ******************************************************************************/
227
Read_BMP_4b_RGB(Image * image,IStream & in,unsigned width,unsigned height)228 static void Read_BMP_4b_RGB(Image *image, IStream& in, unsigned width, unsigned height)
229 {
230 int c = 0;
231 unsigned pwidth = ((width+7)>>3)<<3;
232
233 for (int y=height - 1; y >= 0; y--)
234 {
235 for (int x=0; x<pwidth; x++)
236 {
237 if ((x&1)==0)
238 c = Read_Safe_Char(in);
239 if (x<width)
240 {
241 image->SetIndexedValue (x, y, (c&0xf0)>>4) ;
242 c <<= 4;
243 }
244 }
245 }
246 }
247
248 /*****************************************************************************
249 *
250 * FUNCTION
251 *
252 * Read_BMP_4b_RLE
253 *
254 * INPUT
255 *
256 * *filePUT
257 *
258 * RETURNS
259 *
260 * AUTHOR
261 *
262 * Wlodzimierz ABX Skiba
263 *
264 * DESCRIPTION
265 *
266 * -
267 *
268 * CHANGES
269 *
270 * Aug 2003 : Creation.
271 *
272 ******************************************************************************/
273
Read_BMP_4b_RLE(Image * image,IStream & in,unsigned width,unsigned height)274 static void Read_BMP_4b_RLE(Image *image, IStream& in, unsigned width, unsigned height)
275 {
276 int c, cc = 0;
277 unsigned x = 0;
278 unsigned y = height-1;
279
280 while (1)
281 {
282 c = Read_Safe_Char (in);
283 if (c)
284 {
285 cc = Read_Safe_Char (in);
286 for (int i=0; i<c; i++, x++)
287 if ((y<height) && (x<width))
288 image->SetIndexedValue (x, y, (i&1) ? (cc &0x0f) : ((cc>>4)&0x0f));
289 }
290 else
291 {
292 c = Read_Safe_Char (in);
293 if (c==0)
294 {
295 x=0;
296 y--;
297 }
298 else if (c==1)
299 return;
300 else if (c==2)
301 {
302 x += Read_Safe_Char (in);
303 y -= Read_Safe_Char (in);
304 }
305 else
306 {
307 for (int i=0; i<c; i++, x++)
308 {
309 if ((i&1)==0)
310 cc = Read_Safe_Char (in);
311 if ((y<height) && (x<width))
312 image->SetIndexedValue (x, y, ((i&1)?cc:(cc>>4))&0x0f) ;
313 }
314 if (((c&3)==1) || ((c&3)==2))
315 Read_Safe_Char (in);
316 }
317 }
318 }
319 }
320
321 /*****************************************************************************
322 *
323 * FUNCTION
324 *
325 * Read_BMP_8b_RGB
326 *
327 * INPUT
328 *
329 * *filePUT
330 *
331 * RETURNS
332 *
333 * AUTHOR
334 *
335 * Wlodzimierz ABX Skiba
336 *
337 * DESCRIPTION
338 *
339 * -
340 *
341 * CHANGES
342 *
343 * Aug 2003 : Creation.
344 *
345 ******************************************************************************/
346
Read_BMP_8b_RGB(Image * image,IStream & in,unsigned width,unsigned height)347 static void Read_BMP_8b_RGB(Image *image, IStream& in, unsigned width, unsigned height)
348 {
349 int c;
350 unsigned pwidth = ((width+3)>>2)<<2;
351
352 for (unsigned y=height; (--y)<height;)
353 for (unsigned x=0; x<pwidth; x++)
354 {
355 c = Read_Safe_Char (in);
356 if (x<width)
357 image->SetIndexedValue (x, y, c);
358 }
359 }
360
361 /*****************************************************************************
362 *
363 * FUNCTION
364 *
365 * Read_BMP_8b_RLE
366 *
367 * INPUT
368 *
369 * *filePUT
370 *
371 * RETURNS
372 *
373 * AUTHOR
374 *
375 * Wlodzimierz ABX Skiba
376 *
377 * DESCRIPTION
378 *
379 * -
380 *
381 * CHANGES
382 *
383 * Aug 2003 : Creation.
384 *
385 ******************************************************************************/
386
Read_BMP_8b_RLE(Image * image,IStream & in,unsigned width,unsigned height)387 static void Read_BMP_8b_RLE(Image *image, IStream& in, unsigned width, unsigned height)
388 {
389 int c, cc;
390 unsigned x = 0;
391 unsigned y = height-1;
392
393 while (1)
394 {
395 c = Read_Safe_Char (in);
396 if (c)
397 {
398 cc = Read_Safe_Char (in);
399 for (int i=0; i<c; i++, x++)
400 if ((y<height) && (x<width))
401 image->SetIndexedValue (x, y, cc);
402 }
403 else
404 {
405 c = Read_Safe_Char (in);
406 switch(c)
407 {
408 case 0:
409 x = 0;
410 y--;
411 break;
412 case 1:
413 return;
414 break;
415 case 2:
416 x += Read_Safe_Char (in);
417 y -= Read_Safe_Char (in);
418 break;
419 default:
420 for (int i=0; i<c; i++, x++)
421 if ((y<height) && (x<width))
422 image->SetIndexedValue (x, y, Read_Safe_Char (in));
423 if (c & 1)
424 Read_Safe_Char (in); /* "absolute mode" runs are word-aligned */
425 }
426 }
427 }
428 }
429
430 /*****************************************************************************
431 *
432 * FUNCTION
433 *
434 * Open_BMP_File
435 *
436 * INPUT
437 *
438 * *filePUT
439 *
440 * RETURNS
441 *
442 * AUTHOR
443 *
444 * Wlodzimierz ABX Skiba
445 *
446 * DESCRIPTION
447 *
448 * -
449 *
450 * CHANGES
451 *
452 * Aug 2003 : Creation.
453 * Jan 2004 : Added exception handling to allow cleanup on error [CJC]
454 *
455 ******************************************************************************/
456
Write(OStream * file,const Image * image,const Image::WriteOptions & options)457 void Write (OStream *file, const Image *image, const Image::WriteOptions& options)
458 {
459 int width = image->GetWidth();
460 int height = image->GetHeight();
461 int pad = (4 - ((width * 3) % 4)) & 0x03 ;
462 bool alpha = image->HasTransparency() && options.AlphaIsEnabled();
463 unsigned int r ;
464 unsigned int g ;
465 unsigned int b ;
466 unsigned int a ;
467 GammaCurvePtr gamma;
468 DitherStrategy& dither = *options.ditherStrategy;
469
470 // BMP files used to have no clearly defined gamma by default, but a Microsoft recommendation exists to assume sRGB.
471 gamma = options.GetTranscodingGammaCurve(SRGBGammaCurve::Get());
472
473 // TODO ALPHA - check if BMP should really keep presuming non-premultiplied alpha
474 // We presume non-premultiplied alpha, unless the user overrides
475 // (e.g. to handle a non-compliant file).
476 bool premul = options.AlphaIsPremultiplied(false);
477
478 int count = (width * (alpha ? 32 : 24) + 31) / 32 * 4 * height;
479
480 file->Write_Byte('B');
481 file->Write_Byte('M');
482 Write_Long (file, 14 + 40 + count) ;
483 Write_Short (file, 0) ;
484 Write_Short (file, 0) ;
485 Write_Long (file, 14 + 40) ;
486 Write_Long (file, 40) ;
487 Write_Long (file, width) ;
488 Write_Long (file, height) ;
489 Write_Short (file, 1) ;
490 Write_Short (file, alpha ? 32 : 24) ;
491 Write_Long (file, BI_RGB) ;
492 Write_Long (file, count) ;
493 Write_Long (file, 0) ;
494 Write_Long (file, 0) ;
495 Write_Long (file, 0) ;
496 Write_Long (file, 0) ;
497
498 for (int y = height - 1 ; y >= 0 ; y--)
499 {
500 for (int x = 0 ; x < width ; x++)
501 {
502 if (alpha)
503 GetEncodedRGBAValue (image, x, y, gamma, 255, r, g, b, a, dither, premul);
504 else
505 GetEncodedRGBValue (image, x, y, gamma, 255, r, g, b, dither) ;
506 file->Write_Byte((unsigned char) b);
507 file->Write_Byte((unsigned char) g);
508 file->Write_Byte((unsigned char) r);
509 if (alpha)
510 file->Write_Byte((unsigned char) a);
511 }
512 if (!alpha)
513 for (int i = 0 ; i < pad; i++)
514 file->Write_Byte((unsigned char) 0);
515 }
516
517 if (!*file)
518 throw POV_EXCEPTION(kFileDataErr, "Error writing to BMP file") ;
519 }
520
Read(IStream * file,const Image::ReadOptions & options)521 Image *Read (IStream *file, const Image::ReadOptions& options)
522 {
523 unsigned file_width, file_height;
524 unsigned file_depth, file_colors;
525 unsigned data_location, planes, compression;
526 unsigned info;
527 Image *image = nullptr;
528
529 // BMP files used to have no clearly defined gamma by default, but a Microsoft recommendation exists to assume sRGB.
530 // Since ~1995, a header extension (BITMAPV4HEADER) with gamma metadata exists, which could be used if present.
531 // However, as of now (2009), such information seems to be rarely included, if at all. (Same goes for BITMAPV5HEADER,
532 // which could include a full colorimetric profile.)
533 GammaCurvePtr gamma;
534 if (options.gammacorrect)
535 {
536 if (options.defaultGamma)
537 gamma = TranscodingGammaCurve::Get(options.workingGamma, options.defaultGamma);
538 else
539 gamma = TranscodingGammaCurve::Get(options.workingGamma, SRGBGammaCurve::Get());
540 }
541
542 // TODO ALPHA - check if BMP should really keep presuming non-premultiplied alpha
543 // We presume non-premultiplied alpha, so that's the preferred mode to use for the image container unless the user overrides
544 // (e.g. to handle a non-compliant file).
545 bool premul = false;
546 if (options.premultipliedOverride)
547 premul = options.premultiplied;
548
549 if ((file->Read_Byte () != 'B') || (file->Read_Byte () !='M'))
550 throw POV_EXCEPTION(kFileDataErr, "Error reading magic number of BMP image");
551
552 // skip file size and reserved fields
553 Skip (file, 8) ;
554 data_location = Read_Long (file) ;
555
556 // read properties
557 if ((info = Read_Long (file)) != WIN_OS2_OLD)
558 {
559 file_width = Read_Long (file) ;
560 file_height = Read_Long (file) ;
561 planes = Read_Short (file) ;
562 file_depth = Read_Short (file) ;
563 compression = Read_Long (file) ;
564 Skip (file, 12) ; // skip image size in bytes, H&V pixels per meter
565 file_colors = Read_Long (file) ;
566 Skip (file, 4) ; // skip needed colors
567 }
568 else /* info == WIN_OS2_OLD */
569 {
570 file_width = Read_Short (file) ;
571 file_height = Read_Short (file) ;
572 planes = Read_Short (file) ;
573 file_depth = Read_Short (file) ;
574 compression = BI_RGB ;
575 file_colors = 0 ;
576 }
577
578 bool has_alpha = file_depth == 32 ;
579
580 /* do not allow other subtypes */
581 if (((file_depth!=1) && (file_depth!=4) && (file_depth!=8) && (file_depth!=24) && (file_depth!=32)) ||
582 (planes!=1) || (compression>BI_RLE4) ||
583 (((file_depth==1) || (file_depth==24) || (file_depth==32)) && (compression!=BI_RGB)) ||
584 ((file_depth==4) && (compression==BI_RLE8)) ||
585 ((file_depth==8) && (compression==BI_RLE4)))
586 throw POV_EXCEPTION(kFileDataErr, "Invalid or unsupported BMP file");
587
588 /* seek to colormap */
589 if (info != WIN_OS2_OLD)
590 Skip (file, info - 40) ;
591
592 if (file_depth < 24)
593 {
594 int color_map_length = file_colors ? file_colors : 1<<file_depth ;
595 vector<Image::RGBMapEntry> colormap ;
596 Image::RGBMapEntry entry;
597
598 for (int i=0; i<color_map_length; i++)
599 {
600 entry.blue = IntDecode(gamma, file->Read_Byte (), 255);
601 entry.green = IntDecode(gamma, file->Read_Byte (), 255);
602 entry.red = IntDecode(gamma, file->Read_Byte (), 255);
603 if (info != WIN_OS2_OLD)
604 file->Read_Byte() ;
605 colormap.push_back (entry) ;
606 }
607 gamma.reset(); // gamma has been taken care of by transforming the color table.
608
609 if (file->eof ())
610 throw POV_EXCEPTION(kFileDataErr, "Unexpected EOF while reading BMP file");
611
612 image = Image::Create (file_width, file_height, Image::Colour_Map, colormap) ;
613 image->SetPremultiplied(premul); // specify whether the color map data has premultiplied alpha
614
615 switch (file_depth)
616 {
617 case 1:
618 Read_BMP_1b(image, *file, file_width, file_height);
619 break;
620 case 4:
621 switch(compression)
622 {
623 case BI_RGB:
624 Read_BMP_4b_RGB(image, *file, file_width, file_height);
625 break;
626 case BI_RLE4:
627 Read_BMP_4b_RLE(image, *file, file_width, file_height);
628 break;
629 default:
630 throw POV_EXCEPTION(kFileDataErr, "Unknown compression scheme in BMP file");
631 }
632 break;
633 case 8:
634 switch(compression)
635 {
636 case BI_RGB:
637 Read_BMP_8b_RGB(image, *file, file_width, file_height);
638 break;
639 case BI_RLE8:
640 Read_BMP_8b_RLE(image, *file, file_width, file_height);
641 break;
642 default:
643 throw POV_EXCEPTION(kFileDataErr, "Unknown compression scheme in BMP file");
644 }
645 break;
646 default:
647 throw POV_EXCEPTION(kFileDataErr, "Unknown depth in BMP file");
648 }
649 }
650 else
651 {
652 /* includes update from stefan maierhofer for 32bit */
653 Image::ImageDataType imagetype = options.itype ;
654 if (imagetype == Image::Undefined)
655 {
656 if (GammaCurve::IsNeutral(gamma))
657 // No gamma correction required, raw values can be stored "as is".
658 imagetype = has_alpha ? Image::RGBA_Int8 : Image::RGB_Int8 ;
659 else
660 // Gamma correction required; use an image container that will take care of that.
661 imagetype = has_alpha ? Image::RGBA_Gamma8 : Image::RGB_Gamma8 ;
662 }
663 image = Image::Create (file_width, file_height, imagetype) ;
664 image->SetPremultiplied(premul); // set desired storage mode regarding alpha premultiplication
665 image->TryDeferDecoding(gamma, 255); // try to have gamma adjustment being deferred until image evaluation.
666
667 int pad = has_alpha ? 0 : (4 - ((file_width * 3) % 4)) & 0x03 ;
668 unsigned int a = 255 ; // value to use for files that don't have an alpha channel (full opacity)
669
670 for (int y = file_height - 1 ; y >= 0 ; y--)
671 {
672 for (int x = 0 ; x < file_width ; x++)
673 {
674 unsigned int b = Read_Safe_Char (*file);
675 unsigned int g = Read_Safe_Char (*file);
676 unsigned int r = Read_Safe_Char (*file);
677 if (has_alpha)
678 a = Read_Safe_Char (*file);
679 SetEncodedRGBAValue (image, x, y, gamma, 255, r, g, b, a, premul);
680 }
681 if (pad && !Skip (file, pad))
682 throw POV_EXCEPTION(kFileDataErr, "Error reading data from BMP image.") ;
683 }
684 }
685
686 return (image) ;
687
688 }
689
690 } // end of namespace Bmp
691
692 }
693