/******************************************************************************* * bmp.cpp * * This module contains the code to read and write the BMP file format. * * Author: Wlodzimierz ABX Skiba (abx@abx.art.pl) * * --------------------------------------------------------------------------- * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7. * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd. * * POV-Ray is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * POV-Ray is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * --------------------------------------------------------------------------- * POV-Ray is based on the popular DKB raytracer version 2.12. * DKBTrace was originally written by David K. Buck. * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins. * --------------------------------------------------------------------------- * $File: //depot/public/povray/3.x/source/base/image/bmp.cpp $ * $Revision: #1 $ * $Change: 6069 $ * $DateTime: 2013/11/06 11:59:40 $ * $Author: chrisc $ *******************************************************************************/ /***************************************************************************** * Local preprocessor defines ******************************************************************************/ #define WIN_NEW 40 #define WIN_OS2_OLD 12 #define BI_RGB 0L #define BI_RLE8 1L #define BI_RLE4 2L #include // configbase.h must always be the first POV file included within base *.cpp files #include "base/configbase.h" #include "base/image/image.h" #include "base/image/bmp.h" #include "base/types.h" // this must be the last file included #include "base/povdebug.h" namespace pov_base { namespace Bmp { /***************************************************************************** * * FUNCTION * * Read_Safe_Char * * INPUT * * *filePUT * * RETURNS * * AUTHOR * * Wlodzimierz ABX Skiba * * DESCRIPTION * * - * * CHANGES * * Aug 2003 : Creation. * Jan 2004 : Added exception to allow cleanup on error [CJC] * ******************************************************************************/ static inline unsigned char Read_Safe_Char (IStream& in) { unsigned char ch; in >> ch; if (!in) throw POV_EXCEPTION(kFileDataErr, "Error reading data from BMP image.") ; return (ch) ; } // skip forward without using seekg static bool Skip (IStream *file, int bytes) { while (*file && bytes--) file->Read_Byte () ; return (*file) ; } // skip forward without using seekg static bool Skip (OStream *file, int bytes) { while (*file && bytes--) file->Write_Byte (0) ; return (*file) ; } // write a long to a stream in little-endian (e.g. x86) format static void Write_Long (OStream *file, unsigned long val) { file->Write_Byte (val & 0xff) ; file->Write_Byte ((val >> 8) & 0xff) ; file->Write_Byte ((val >> 16) & 0xff) ; file->Write_Byte ((val >> 24) & 0xff) ; } // write a short to a stream in little-endian (e.g. x86) format static void Write_Short (OStream *file, unsigned short val) { file->Write_Byte (val & 0xff) ; file->Write_Byte ((val >> 8) & 0xff) ; } // read a long from a stream in little-endian (e.g. x86) format static unsigned long Read_Long (IStream *file) { unsigned long ch1 = Read_Safe_Char (*file) ; unsigned long ch2 = Read_Safe_Char (*file) ; unsigned long ch3 = Read_Safe_Char (*file) ; unsigned long ch4 = Read_Safe_Char (*file) ; return ((ch4 << 24) | (ch3 << 16) | (ch2 << 8) | ch1) ; } // read a short from a stream in little-endian (e.g. x86) format static unsigned short Read_Short (IStream *file) { unsigned short ch1 = Read_Safe_Char (*file) ; unsigned short ch2 = Read_Safe_Char (*file) ; return ((ch2 << 8) | ch1) ; } /***************************************************************************** * * FUNCTION * * Read_BMP_1b * * INPUT * * *filePUT * * RETURNS * * AUTHOR * * Wlodzimierz ABX Skiba * * DESCRIPTION * * - * * CHANGES * * Aug 2003 : Creation. * ******************************************************************************/ static void Read_BMP_1b(Image *image, IStream& in, unsigned width, unsigned height) { int c = 0; unsigned pwidth = ((width+31)>>5)<<5; /* clear bits to get 4 byte boundary */ for (int y=height - 1; y >= 0; y--) { for (int x=0; xSetBitValue (x, y, (c & 0x80) ? 1 : 0); c <<= 1; } } } } /***************************************************************************** * * FUNCTION * * Read_BMP_4b_RGB * * INPUT * * *filePUT * * RETURNS * * AUTHOR * * Wlodzimierz ABX Skiba * * DESCRIPTION * * - * * CHANGES * * Aug 2003 : Creation. * ******************************************************************************/ static void Read_BMP_4b_RGB(Image *image, IStream& in, unsigned width, unsigned height) { int c = 0; unsigned pwidth = ((width+7)>>3)<<3; for (int y=height - 1; y >= 0; y--) { for (int x=0; xSetIndexedValue (x, y, (c&0xf0)>>4) ; c <<= 4; } } } } /***************************************************************************** * * FUNCTION * * Read_BMP_4b_RLE * * INPUT * * *filePUT * * RETURNS * * AUTHOR * * Wlodzimierz ABX Skiba * * DESCRIPTION * * - * * CHANGES * * Aug 2003 : Creation. * ******************************************************************************/ static void Read_BMP_4b_RLE(Image *image, IStream& in, unsigned width, unsigned height) { int c, cc = 0; unsigned x = 0; unsigned y = height-1; while (1) { c = Read_Safe_Char (in); if (c) { cc = Read_Safe_Char (in); for (int i=0; iSetIndexedValue (x, y, (i&1) ? (cc &0x0f) : ((cc>>4)&0x0f)); } else { c = Read_Safe_Char (in); if (c==0) { x=0; y--; } else if (c==1) return; else if (c==2) { x += Read_Safe_Char (in); y -= Read_Safe_Char (in); } else { for (int i=0; iSetIndexedValue (x, y, ((i&1)?cc:(cc>>4))&0x0f) ; } if (((c&3)==1) || ((c&3)==2)) Read_Safe_Char (in); } } } } /***************************************************************************** * * FUNCTION * * Read_BMP_8b_RGB * * INPUT * * *filePUT * * RETURNS * * AUTHOR * * Wlodzimierz ABX Skiba * * DESCRIPTION * * - * * CHANGES * * Aug 2003 : Creation. * ******************************************************************************/ static void Read_BMP_8b_RGB(Image *image, IStream& in, unsigned width, unsigned height) { int c; unsigned pwidth = ((width+3)>>2)<<2; for (unsigned y=height; (--y)SetIndexedValue (x, y, c); } } /***************************************************************************** * * FUNCTION * * Read_BMP_8b_RLE * * INPUT * * *filePUT * * RETURNS * * AUTHOR * * Wlodzimierz ABX Skiba * * DESCRIPTION * * - * * CHANGES * * Aug 2003 : Creation. * ******************************************************************************/ static void Read_BMP_8b_RLE(Image *image, IStream& in, unsigned width, unsigned height) { int c, cc; unsigned x = 0; unsigned y = height-1; while (1) { c = Read_Safe_Char (in); if (c) { cc = Read_Safe_Char (in); for (int i=0; iSetIndexedValue (x, y, cc); } else { c = Read_Safe_Char (in); switch(c) { case 0: x = 0; y--; break; case 1: return; break; case 2: x += Read_Safe_Char (in); y -= Read_Safe_Char (in); break; default: for (int i=0; iSetIndexedValue (x, y, Read_Safe_Char (in)); if (c & 1) Read_Safe_Char (in); /* "absolute mode" runs are word-aligned */ } } } } /***************************************************************************** * * FUNCTION * * Open_BMP_File * * INPUT * * *filePUT * * RETURNS * * AUTHOR * * Wlodzimierz ABX Skiba * * DESCRIPTION * * - * * CHANGES * * Aug 2003 : Creation. * Jan 2004 : Added exception handling to allow cleanup on error [CJC] * ******************************************************************************/ void Write (OStream *file, const Image *image, const Image::WriteOptions& options) { int width = image->GetWidth(); int height = image->GetHeight(); int pad = (4 - ((width * 3) % 4)) & 0x03 ; bool alpha = image->HasTransparency() && options.alphachannel; unsigned int r ; unsigned int g ; unsigned int b ; unsigned int a ; GammaCurvePtr gamma; DitherHandler* dither = options.dither.get(); if (options.encodingGamma) gamma = TranscodingGammaCurve::Get(options.workingGamma, options.encodingGamma); else // BMP files used to have no clearly defined gamma by default, but a Microsoft recommendation exists to assume sRGB. gamma = TranscodingGammaCurve::Get(options.workingGamma, SRGBGammaCurve::Get()); // TODO ALPHA - check if BMP should really keep presuming non-premultiplied alpha // We presume non-premultiplied alpha, unless the user overrides // (e.g. to handle a non-compliant file). bool premul = false; if (options.premultiplyOverride) premul = options.premultiply; int count = (width * (alpha ? 32 : 24) + 31) / 32 * 4 * height; *file << 'B' << 'M' ; Write_Long (file, 14 + 40 + count) ; Write_Short (file, 0) ; Write_Short (file, 0) ; Write_Long (file, 14 + 40) ; Write_Long (file, 40) ; Write_Long (file, width) ; Write_Long (file, height) ; Write_Short (file, 1) ; Write_Short (file, alpha ? 32 : 24) ; Write_Long (file, BI_RGB) ; Write_Long (file, count) ; Write_Long (file, 0) ; Write_Long (file, 0) ; Write_Long (file, 0) ; Write_Long (file, 0) ; for (int y = height - 1 ; y >= 0 ; y--) { for (int x = 0 ; x < width ; x++) { if (alpha) GetEncodedRGBAValue (image, x, y, gamma, 255, r, g, b, a, *dither, premul); else GetEncodedRGBValue (image, x, y, gamma, 255, r, g, b, *dither) ; *file << (unsigned char) b; *file << (unsigned char) g; *file << (unsigned char) r; if (alpha) *file << (unsigned char) a; } if (!alpha) for (int i = 0 ; i < pad; i++) *file << (unsigned char) 0 ; } if (!*file) throw POV_EXCEPTION(kFileDataErr, "Error writing to BMP file") ; } Image *Read (IStream *file, const Image::ReadOptions& options) { unsigned file_width, file_height; unsigned file_depth, file_colors; unsigned data_location, planes, compression; unsigned info; Image *image = NULL ; // BMP files used to have no clearly defined gamma by default, but a Microsoft recommendation exists to assume sRGB. // Since ~1995, a header extension (BITMAPV4HEADER) with gamma metadata exists, which could be used if present. // However, as of now (2009), such information seems to be rarely included, if at all. (Same goes for BITMAPV5HEADER, // which could include a full colorimetric profile.) GammaCurvePtr gamma; if (options.gammacorrect) { if (options.defaultGamma) gamma = TranscodingGammaCurve::Get(options.workingGamma, options.defaultGamma); else gamma = TranscodingGammaCurve::Get(options.workingGamma, SRGBGammaCurve::Get()); } // TODO ALPHA - check if BMP should really keep presuming non-premultiplied alpha // We presume non-premultiplied alpha, so that's the preferred mode to use for the image container unless the user overrides // (e.g. to handle a non-compliant file). bool premul = false; if (options.premultiplyOverride) premul = options.premultiply; if ((file->Read_Byte () != 'B') || (file->Read_Byte () !='M')) throw POV_EXCEPTION(kFileDataErr, "Error reading magic number of BMP image"); // skip file size and reserved fields Skip (file, 8) ; data_location = Read_Long (file) ; // read properties if ((info = Read_Long (file)) != WIN_OS2_OLD) { file_width = Read_Long (file) ; file_height = Read_Long (file) ; planes = Read_Short (file) ; file_depth = Read_Short (file) ; compression = Read_Long (file) ; Skip (file, 12) ; // skip image size in bytes, H&V pixels per meter file_colors = Read_Long (file) ; Skip (file, 4) ; // skip needed colors } else /* info == WIN_OS2_OLD */ { file_width = Read_Short (file) ; file_height = Read_Short (file) ; planes = Read_Short (file) ; file_depth = Read_Short (file) ; compression = BI_RGB ; file_colors = 0 ; } bool has_alpha = file_depth == 32 ; /* do not allow other subtypes */ if (((file_depth!=1) && (file_depth!=4) && (file_depth!=8) && (file_depth!=24) && (file_depth!=32)) || (planes!=1) || (compression>BI_RLE4) || (((file_depth==1) || (file_depth==24) || (file_depth==32)) && (compression!=BI_RGB)) || ((file_depth==4) && (compression==BI_RLE8)) || ((file_depth==8) && (compression==BI_RLE4))) throw POV_EXCEPTION(kFileDataErr, "Invalid or unsupported BMP file"); /* seek to colormap */ if (info != WIN_OS2_OLD) Skip (file, info - 40) ; if (file_depth < 24) { int color_map_length = file_colors ? file_colors : 1< colormap ; Image::RGBMapEntry entry; for (int i=0; iRead_Byte (), 255); entry.green = IntDecode(gamma, file->Read_Byte (), 255); entry.red = IntDecode(gamma, file->Read_Byte (), 255); if (info != WIN_OS2_OLD) file->Read_Byte() ; colormap.push_back (entry) ; } gamma.reset(); // gamma has been taken care of by transforming the color table. if (file->eof ()) throw POV_EXCEPTION(kFileDataErr, "Unexpected EOF while reading BMP file"); image = Image::Create (file_width, file_height, Image::Colour_Map, colormap) ; image->SetPremultiplied(premul); // specify whether the color map data has premultiplied alpha switch (file_depth) { case 1: Read_BMP_1b(image, *file, file_width, file_height); break; case 4: switch(compression) { case BI_RGB: Read_BMP_4b_RGB(image, *file, file_width, file_height); break; case BI_RLE4: Read_BMP_4b_RLE(image, *file, file_width, file_height); break; default: throw POV_EXCEPTION(kFileDataErr, "Unknown compression scheme in BMP file"); } break; case 8: switch(compression) { case BI_RGB: Read_BMP_8b_RGB(image, *file, file_width, file_height); break; case BI_RLE8: Read_BMP_8b_RLE(image, *file, file_width, file_height); break; default: throw POV_EXCEPTION(kFileDataErr, "Unknown compression scheme in BMP file"); } break; default: throw POV_EXCEPTION(kFileDataErr, "Unknown depth in BMP file"); } } else { /* includes update from stefan maierhofer for 32bit */ Image::ImageDataType imagetype = options.itype ; if (imagetype == Image::Undefined) { if (GammaCurve::IsNeutral(gamma)) // No gamma correction required, raw values can be stored "as is". imagetype = has_alpha ? Image::RGBA_Int8 : Image::RGB_Int8 ; else // Gamma correction required; use an image container that will take care of that. imagetype = has_alpha ? Image::RGBA_Gamma8 : Image::RGB_Gamma8 ; } image = Image::Create (file_width, file_height, imagetype) ; image->SetPremultiplied(premul); // set desired storage mode regarding alpha premultiplication image->TryDeferDecoding(gamma, 255); // try to have gamma adjustment being deferred until image evaluation. int pad = has_alpha ? 0 : (4 - ((file_width * 3) % 4)) & 0x03 ; unsigned int a = 255 ; // value to use for files that don't have an alpha channel (full opacity) for (int y = file_height - 1 ; y >= 0 ; y--) { for (int x = 0 ; x < file_width ; x++) { unsigned int b = Read_Safe_Char (*file); unsigned int g = Read_Safe_Char (*file); unsigned int r = Read_Safe_Char (*file); if (has_alpha) a = Read_Safe_Char (*file); SetEncodedRGBAValue (image, x, y, gamma, 255, r, g, b, a, premul); } if (pad && !Skip (file, pad)) throw POV_EXCEPTION(kFileDataErr, "Error reading data from BMP image.") ; } } return (image) ; } } // end of namespace Bmp }