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