// Copyright (C) 2002-2012 Nikolaus Gebhardt // This file is part of the "Irrlicht Engine". // For conditions of distribution and use, see copyright notice in irrlicht.h #include "CImageLoaderBMP.h" #ifdef _IRR_COMPILE_WITH_BMP_LOADER_ #include "IReadFile.h" #include "SColor.h" #include "CColorConverter.h" #include "CImage.h" #include "os.h" namespace irr { namespace video { //! constructor CImageLoaderBMP::CImageLoaderBMP() { #ifdef _DEBUG setDebugName("CImageLoaderBMP"); #endif } //! returns true if the file maybe is able to be loaded by this class //! based on the file extension (e.g. ".tga") bool CImageLoaderBMP::isALoadableFileExtension(const io::path& filename) const { return core::hasFileExtension ( filename, "bmp" ); } //! returns true if the file maybe is able to be loaded by this class bool CImageLoaderBMP::isALoadableFileFormat(io::IReadFile* file) const { u16 headerID; file->read(&headerID, sizeof(u16)); #ifdef __BIG_ENDIAN__ headerID = os::Byteswap::byteswap(headerID); #endif return headerID == 0x4d42; } // UB-safe overflow check static inline bool doesOverflow(const void *base, size_t numElements, const void *end) { // TODO: uintptr_t (as in original patch from sfan5) would be nicer than size_t, use once we allow c++11 size_t baseI = reinterpret_cast(base); size_t endI = reinterpret_cast(end); return baseI > endI || numElements >= (endI - baseI); } // check whether &p[0] to &p[_off - 1] can be accessed #define EXIT_P_OVERFLOW(_off) if (doesOverflow(p, _off, pEnd)) goto exit // same for d #define EXIT_D_OVERFLOW(_off) if (doesOverflow(d, _off, destEnd)) goto exit void CImageLoaderBMP::decompress8BitRLE(u8*& bmpData, s32 size, s32 width, s32 height, s32 pitch) const { u8* p = bmpData; const u8* pEnd = bmpData + size; u8* newBmp = new u8[(width+pitch)*height]; u8* d = newBmp; const u8* destEnd = newBmp + (width+pitch)*height; s32 line = 0; while (p < pEnd && d < destEnd) { if (*p == 0) { ++p; EXIT_P_OVERFLOW(1); switch(*p) { case 0: // end of line ++p; ++line; d = newBmp + (line*(width+pitch)); break; case 1: // end of bmp goto exit; case 2: ++p; EXIT_P_OVERFLOW(2); d += (u8)*p; // delta ++p; d += ((u8)*p)*(width+pitch); ++p; break; default: { // absolute mode const s32 count = (u8)*p; ++p; EXIT_P_OVERFLOW(count); EXIT_D_OVERFLOW(count); for (s32 i=0; i= width ) \ { \ line += row/width; \ row %= width; \ } \ if ( line >= height ) \ { \ line = 0; /* bug anyway, this way more visible maybe */ \ } void CImageLoaderBMP::decompress4BitRLE(u8*& bmpData, s32 size, s32 width, s32 height, s32 pitch) const { const s32 lineWidth = (width+1)/2+pitch; u8* p = bmpData; const u8* pEnd = bmpData + size; u8* newBmp = new u8[lineWidth*height]; memset(newBmp, 0, lineWidth*height); // Extra cost, but otherwise we have to code pixel skipping stuff const u8* destEnd = newBmp + lineWidth*height; s32 line = 0; s32 row = 0; while (p < pEnd) { if (*p == 0) { ++p; EXIT_P_OVERFLOW(1); switch(*p) { case 0: // end of line ++p; ++line; row = 0; break; case 1: // end of bmp goto exit; case 2: // delta { ++p; EXIT_P_OVERFLOW(2); s32 x = (u8)*p; ++p; s32 y = (u8)*p; ++p; row += x; line += y; } break; default: { // absolute mode const u32 count = (u8)*p; ++p; s32 readShift = 4; KEEP_ROW_LINE_INSIDE; s32 shift = row%2 == 0 ? 4 : 0; u8* d = newBmp + (lineWidth*line + row/2); EXIT_P_OVERFLOW(shiftedCount(count, readShift)); EXIT_D_OVERFLOW(shiftedCount(count, shift)); for (u32 i=0; i> readShift) & 0x0f; readShift -= 4; if (readShift < 0) { ++p; readShift = 4; } u8 mask = 0x0f << shift; *d = (*d & (~mask)) | ((color << shift) & mask); shift -= 4; if (shift < 0) { shift = 4; ++d; } } row += count; // pixels always come in 2-byte packages with unused half-bytes filled with zeroes const u32 modCount = (count%4); const u32 readAdditional = modCount == 1 ? 2 : (modCount==0?0:1); // jump 2,1,1,0 for count 1,2,3,4 EXIT_P_OVERFLOW(readAdditional); for (u32 i=0; i> 4) & 0x0f; // hi bits (1st,3rd,... pixel) ++p; KEEP_ROW_LINE_INSIDE; s32 shift = row%2 == 0 ? 4 : 0; u8* d = newBmp + (lineWidth*line + row/2); EXIT_D_OVERFLOW(shiftedCount(count, shift)); for (s32 i=0; iread(&header, sizeof(header)); #ifdef __BIG_ENDIAN__ header.Id = os::Byteswap::byteswap(header.Id); header.FileSize = os::Byteswap::byteswap(header.FileSize); header.BitmapDataOffset = os::Byteswap::byteswap(header.BitmapDataOffset); header.BitmapHeaderSize = os::Byteswap::byteswap(header.BitmapHeaderSize); header.Width = os::Byteswap::byteswap(header.Width); header.Height = os::Byteswap::byteswap(header.Height); header.Planes = os::Byteswap::byteswap(header.Planes); header.BPP = os::Byteswap::byteswap(header.BPP); header.Compression = os::Byteswap::byteswap(header.Compression); header.BitmapDataSize = os::Byteswap::byteswap(header.BitmapDataSize); header.PixelPerMeterX = os::Byteswap::byteswap(header.PixelPerMeterX); header.PixelPerMeterY = os::Byteswap::byteswap(header.PixelPerMeterY); header.Colors = os::Byteswap::byteswap(header.Colors); header.ImportantColors = os::Byteswap::byteswap(header.ImportantColors); #endif s32 pitch = 0; //! return if the header is false if (header.Id != 0x4d42) return 0; if (header.Compression > 2) // we'll only handle RLE-Compression { os::Printer::log("Compression mode not supported.", ELL_ERROR); return 0; } // adjust bitmap data size to dword boundary header.BitmapDataSize += (4-(header.BitmapDataSize%4))%4; // read palette long pos = file->getPos(); s32 paletteSize = (header.BitmapDataOffset - pos) / 4; s32* paletteData = 0; if (paletteSize) { paletteData = new s32[paletteSize]; file->read(paletteData, paletteSize * sizeof(s32)); #ifdef __BIG_ENDIAN__ for (s32 i=0; i(file->getSize()) - header.BitmapDataOffset; } file->seek(header.BitmapDataOffset); const s32 widthInBytes = core::ceil32(header.Width * (header.BPP / 8.0f)); const s32 lineSize = widthInBytes + ((4-(widthInBytes%4)))%4; pitch = lineSize - widthInBytes; u8* bmpData = new u8[header.BitmapDataSize]; file->read(bmpData, header.BitmapDataSize); // decompress data if needed switch(header.Compression) { case 1: // 8 bit RLE decompress8BitRLE(bmpData, header.BitmapDataSize, header.Width, header.Height, pitch); header.BitmapDataSize = (header.Width + pitch) * header.Height; break; case 2: // 4 bit RLE decompress4BitRLE(bmpData, header.BitmapDataSize, header.Width, header.Height, pitch); header.BitmapDataSize = ((header.Width+1)/2 + pitch) * header.Height; break; } if (header.BitmapDataSize < lineSize * header.Height) { os::Printer::log("Bitmap data is cut off.", ELL_ERROR); delete [] paletteData; delete [] bmpData; return 0; } // create surface // no default constructor from packed area! ARM problem! core::dimension2d dim; dim.Width = header.Width; dim.Height = header.Height; IImage* image = 0; if ( !IImage::checkDataSizeLimit( (size_t)header.Width*header.Height*header.BPP) ) { os::Printer::log("Image dimensions too large for file", file->getFileName(), ELL_WARNING); goto cleanup; } switch(header.BPP) { case 1: image = new CImage(ECF_A1R5G5B5, dim); if (image) CColorConverter::convert1BitTo16Bit(bmpData, (s16*)image->getData(), header.Width, header.Height, pitch, true); break; case 4: image = new CImage(ECF_A1R5G5B5, dim); if (image) CColorConverter::convert4BitTo16Bit(bmpData, (s16*)image->getData(), header.Width, header.Height, paletteData, pitch, true); break; case 8: image = new CImage(ECF_A1R5G5B5, dim); if (image) CColorConverter::convert8BitTo16Bit(bmpData, (s16*)image->getData(), header.Width, header.Height, paletteData, pitch, true); break; case 16: image = new CImage(ECF_A1R5G5B5, dim); if (image) CColorConverter::convert16BitTo16Bit((s16*)bmpData, (s16*)image->getData(), header.Width, header.Height, pitch, true); break; case 24: image = new CImage(ECF_R8G8B8, dim); if (image) CColorConverter::convert24BitTo24Bit(bmpData, (u8*)image->getData(), header.Width, header.Height, pitch, true, true); break; case 32: // thx to Reinhard Ostermeier image = new CImage(ECF_A8R8G8B8, dim); if (image) CColorConverter::convert32BitTo32Bit((s32*)bmpData, (s32*)image->getData(), header.Width, header.Height, pitch, true); break; }; // clean up cleanup: delete [] paletteData; delete [] bmpData; return image; } //! creates a loader which is able to load windows bitmaps IImageLoader* createImageLoaderBMP() { return new CImageLoaderBMP; } } // end namespace video } // end namespace irr #endif