imagecore/formats/internal/bmp.cpp (394 lines of code) (raw):

/* * MIT License * * Copyright (c) 2017 Twitter * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "bmp.h" #include "imagecore/utils/endianutils.h" #include "imagecore/utils/mathutils.h" #include "imagecore/utils/securemath.h" #include "imagecore/image/rgba.h" namespace imagecore { REGISTER_IMAGE_READER(ImageReaderBMP); // BMP loading code based on http://www.kalytta.com/bitmap.h - Benjamin Kalytta #ifndef __LITTLE_ENDIAN__ #ifndef __BIG_ENDIAN__ #define __LITTLE_ENDIAN__ #endif #endif #ifdef __LITTLE_ENDIAN__ #define BITMAP_SIGNATURE 0x4d42 #else #define BITMAP_SIGNATURE 0x424d #endif #pragma pack(push, 1) struct BGRA { uint8_t Blue; uint8_t Green; uint8_t Red; uint8_t Alpha; }; static inline unsigned int BitCountByMask(unsigned int Mask) { unsigned int BitCount = 0; while (Mask) { Mask &= Mask - 1; BitCount++; } return BitCount; } static inline unsigned int BitPositionByMask(unsigned int Mask) { return BitCountByMask((Mask & (~Mask + 1)) - 1); } static inline unsigned int ComponentByMask(unsigned int Color, unsigned int Mask) { unsigned int Component = Color & Mask; return Component >> BitPositionByMask(Mask); } static inline unsigned int BitCountToMask(unsigned int BitCount) { return (BitCount == 32) ? 0xFFFFFFFF : (1 << BitCount) - 1; } static unsigned int Convert(unsigned int Color, unsigned int FromBitCount, unsigned int ToBitCount) { if (ToBitCount < FromBitCount) { Color >>= (FromBitCount - ToBitCount); } else { Color <<= (ToBitCount - FromBitCount); if (Color > 0) { Color |= BitCountToMask(ToBitCount - FromBitCount); } } return Color; } bool ImageReaderBMP::Factory::matchesSignature(const uint8_t* sig, unsigned int sigLen) { if( sigLen >= 2 && sig[0] == 0x42 && sig[1] == 0x4D ) { return true; } return false; } ImageReaderBMP::ImageReaderBMP() : m_BitmapHeader(NULL) , m_Source(NULL) , m_Width(0) , m_Height(0) { } ImageReaderBMP::~ImageReaderBMP() { if( m_BitmapHeader != NULL ) { free(m_BitmapHeader); m_BitmapHeader = NULL; } } bool ImageReaderBMP::initWithStorage(ImageReader::Storage* source) { m_Source = source; return true; } bool ImageReaderBMP::readHeader() { if( m_Source->read((char*)&m_BitmapFileHeader, sizeof(BITMAP_FILEHEADER)) != sizeof(BITMAP_FILEHEADER) ) { return false; } if( m_BitmapFileHeader.Signature != BITMAP_SIGNATURE ) { return false; } // Don't read more of the header structure than we have to, to avoid having to seek back later. unsigned int headerSize = 0; if( m_Source->read((char*)&headerSize, sizeof(unsigned int)) != sizeof(unsigned int) ) { return false; } if (headerSize < sizeof(BITMAP_HEADER) || headerSize > 64 * 1024) { return false; } m_BitmapHeader = (BITMAP_HEADER*)malloc(headerSize); if( m_BitmapHeader == NULL ) { return false; } // Read the rest of the header, minus the size field. unsigned int headerReadSize = SafeUSub(headerSize, 4U); if( m_Source->read((char*)m_BitmapHeader + 4U, headerReadSize) != headerReadSize ) { return false; } m_BitmapHeader->HeaderSize = headerSize; // Signed -> Unsigned conversion, don't let it underflow. m_Width = m_BitmapHeader->Width >= 0 ? m_BitmapHeader->Width : 0; m_Height = m_BitmapHeader->Height >= 0 ? m_BitmapHeader->Height : 0; m_NativeColorModel = kColorModel_RGBX; return true; } bool ImageReaderBMP::readImage(Image* dest) { if( !supportsOutputColorModel(dest->getColorModel()) ) { return false; } ImageRGBA* destImage = dest->asRGBA(); // Security checks - make sure we don't exceed the dest capacity, and that nothing has somehow changed since we read the header. SECURE_ASSERT(destImage->getWidth() == m_Width && destImage->getHeight() == m_Height); unsigned int destPitch = 0; uint8_t* destBuffer = destImage->lockRect(m_Width, m_Height, destPitch); unsigned int destCapacity = destImage->getImageSize(); SECURE_ASSERT(SafeUMul(m_Width, 4U) <= destPitch); SECURE_ASSERT(destBuffer && destPitch); /* Load Color Table */ unsigned int ColorTableSize = 0; if (m_BitmapHeader->BitCount == 1) { ColorTableSize = 2; } else if (m_BitmapHeader->BitCount == 4) { ColorTableSize = 16; } else if (m_BitmapHeader->BitCount == 8) { ColorTableSize = 256; } BGRA* ColorTable = NULL; if( ColorTableSize > 0 ) { ColorTable = new BGRA[ColorTableSize]; if( ColorTable == NULL ) { return false; } if( m_BitmapHeader->ClrUsed == 0 ) { m_BitmapHeader->ClrUsed = ColorTableSize; } if (m_BitmapHeader->ClrUsed < ColorTableSize) { memset(ColorTable + m_BitmapHeader->ClrUsed, 0, sizeof(BGRA) * (ColorTableSize - m_BitmapHeader->ClrUsed)); } if( m_BitmapHeader->ClrUsed > ColorTableSize || m_Source->read((char*)ColorTable, SafeUMul((unsigned int)sizeof(BGRA), m_BitmapHeader->ClrUsed)) != SafeUMul((unsigned int)sizeof(BGRA), m_BitmapHeader->ClrUsed) ) { delete[] ColorTable; return false; } } unsigned int LineWidth = align(m_Width * m_BitmapHeader->BitCount, 32) / 8; uint8_t* Line = new uint8_t[LineWidth]; if( Line == NULL ) { delete [] ColorTable; return false; } // Read and discard everything up to the image data, can't seek. if( m_BitmapFileHeader.BitsOffset > m_Source->tell() ) { uint64_t remainingBytes = SafeUSub((uint64_t)m_BitmapFileHeader.BitsOffset, m_Source->tell()); uint8_t discardBuffer[1024]; while( remainingBytes > 0 ) { uint64_t bytesToRead = remainingBytes < 1024 ? remainingBytes : 1024; uint64_t bytesRead = m_Source->read(discardBuffer, bytesToRead); if( bytesRead == 0 ) { return false; } remainingBytes = SafeUSub(remainingBytes, bytesRead); } } else if( m_BitmapFileHeader.BitsOffset < m_Source->tell() ) { // Malformed BMP. return false; } bool result = true; if (m_BitmapHeader->Compression == 0) { for (unsigned int i = 0; i < m_Height; i++) { if( m_Source->read((char*) Line, LineWidth) != LineWidth ) { result = false; goto failedRead; } uint8_t* LinePtr = Line; SECURE_ASSERT(LinePtr < Line + LineWidth); uint8_t* DestPtr = destBuffer + destPitch * (m_Height - i - 1); SECURE_ASSERT(DestPtr >= destBuffer); for (unsigned int j = 0; j < m_Width; j++) { int OutIndex = j * 4; if (m_BitmapHeader->BitCount == 1) { unsigned int Color = *((uint8_t*) LinePtr); for (int k = 0; k < 8; k++) { DestPtr[OutIndex + 0] = ColorTable[Color & 0x80 ? 1 : 0].Red; DestPtr[OutIndex + 1] = ColorTable[Color & 0x80 ? 1 : 0].Green; DestPtr[OutIndex + 2] = ColorTable[Color & 0x80 ? 1 : 0].Blue; DestPtr[OutIndex + 3] = ColorTable[Color & 0x80 ? 1 : 0].Alpha; OutIndex += 4; if(OutIndex > destPitch - 4) { // handle widths that are not multiples of 8 break; } Color <<= 1; } LinePtr++; j += 7; } else if (m_BitmapHeader->BitCount == 4) { unsigned int Color = *((uint8_t*) LinePtr); DestPtr[OutIndex + 0] = ColorTable[(Color >> 4) & 0x0f].Red; DestPtr[OutIndex + 1] = ColorTable[(Color >> 4) & 0x0f].Green; DestPtr[OutIndex + 2] = ColorTable[(Color >> 4) & 0x0f].Blue; DestPtr[OutIndex + 3] = ColorTable[(Color >> 4) & 0x0f].Alpha; j++; LinePtr++; if (j < m_Width) { OutIndex = j * 4; DestPtr[OutIndex + 0] = ColorTable[Color & 0x0f].Red; DestPtr[OutIndex + 1] = ColorTable[Color & 0x0f].Green; DestPtr[OutIndex + 2] = ColorTable[Color & 0x0f].Blue; DestPtr[OutIndex + 3] = ColorTable[Color & 0x0f].Alpha; } } else if (m_BitmapHeader->BitCount == 8) { unsigned int Color = *((uint8_t*) LinePtr); DestPtr[OutIndex + 0] = ColorTable[Color].Red; DestPtr[OutIndex + 1] = ColorTable[Color].Green; DestPtr[OutIndex + 2] = ColorTable[Color].Blue; DestPtr[OutIndex + 3] = ColorTable[Color].Alpha; LinePtr++; } else if (m_BitmapHeader->BitCount == 16) { uint16_t Color = letoh16(*((uint16_t*) LinePtr)); DestPtr[OutIndex + 0] = ((Color >> 10) & 0x1f) << 3; DestPtr[OutIndex + 1] = ((Color >> 5) & 0x1f) << 3; DestPtr[OutIndex + 2] = (Color & 0x1f) << 3; DestPtr[OutIndex + 3] = 255; LinePtr += 2; } else if (m_BitmapHeader->BitCount == 24) { DestPtr[OutIndex + 2] = LinePtr[0]; DestPtr[OutIndex + 1] = LinePtr[1]; DestPtr[OutIndex + 0] = LinePtr[2]; DestPtr[OutIndex + 3] = 255; LinePtr += 3; } else if (m_BitmapHeader->BitCount == 32) { DestPtr[OutIndex + 2] = LinePtr[0]; DestPtr[OutIndex + 1] = LinePtr[1]; DestPtr[OutIndex + 0] = LinePtr[2]; DestPtr[OutIndex + 3] = 255; LinePtr += 4; } else { result = false; goto failedRead; } } } } else if (m_BitmapHeader->Compression == 1) { // RLE 8 // Just memset the whole buffer, don't want to keep track of which regions actually get written to by the RLE decoder. memset(destBuffer, 0, destCapacity); uint8_t Count = 0; uint8_t ColorIndex = 0; int x = 0, y = 0; if( ColorTable == NULL || ColorTableSize == 0 ) { result = false; goto failedRead; } while( true ) { if( m_Source->read((char*) &Count, sizeof(uint8_t)) != sizeof(uint8_t) ) { result = false; goto failedRead; } if( m_Source->read((char*) &ColorIndex, sizeof(uint8_t)) != sizeof(uint8_t) ) { result = false; goto failedRead; } if( ColorIndex >= ColorTableSize ) { result = false; goto failedRead; } if (Count > 0) { int OutIndex = x * 4 + (m_Height - y - 1) * destPitch; if( OutIndex < 0 || OutIndex + Count * 4 > (int)destCapacity ) { result = false; goto failedRead; } for (int k = 0; k < Count; k++) { destBuffer[OutIndex + k * 4 + 0] = ColorTable[ColorIndex].Red; destBuffer[OutIndex + k * 4 + 1] = ColorTable[ColorIndex].Green; destBuffer[OutIndex + k * 4 + 2] = ColorTable[ColorIndex].Blue; destBuffer[OutIndex + k * 4 + 3] = ColorTable[ColorIndex].Alpha; } x += Count; } else if (Count == 0) { int Flag = ColorIndex; if (Flag == 0) { x = 0; y++; } else if (Flag == 1) { break; } else if (Flag == 2) { char rx = 0; char ry = 0; if( m_Source->read((char*) &rx, sizeof(char)) != (int)sizeof(char) ) { result = false; goto failedRead; } if( m_Source->read((char*) &ry, sizeof(char)) != (int)sizeof(char) ) { result = false; goto failedRead; } x += rx; y += ry; } else { Count = Flag; int OutIndex = x * 4 + (m_Height - y - 1) * destPitch; if( OutIndex < 0 || OutIndex + Count * 4 > (int)destCapacity ) { result = false; goto failedRead; } for (int k = 0; k < Count; k++) { if( m_Source->read((char*) &ColorIndex, sizeof(uint8_t)) != (int)sizeof(uint8_t) ) { result = false; goto failedRead; } if( ColorIndex >= ColorTableSize ) { result = false; goto failedRead; } destBuffer[OutIndex + k * 4 + 0] = ColorTable[ColorIndex].Red; destBuffer[OutIndex + k * 4 + 1] = ColorTable[ColorIndex].Green; destBuffer[OutIndex + k * 4 + 2] = ColorTable[ColorIndex].Blue; destBuffer[OutIndex + k * 4 + 3] = ColorTable[ColorIndex].Alpha; } x += Count; uint64_t filePos = m_Source->tell(); if (filePos & 1) { uint8_t skip; m_Source->read(&skip, 1); } } } } } else if (m_BitmapHeader->Compression == 2) { // RLE 4 result = false; goto failedRead; } else if (m_BitmapHeader->Compression == 3) { // BITFIELDS if( m_BitmapHeader->HeaderSize < sizeof(BITMAP_HEADER_EXTENDED) ||( m_BitmapHeader->BitCount != 16 && m_BitmapHeader->BitCount != 32) ) { result = false; goto failedRead; } BITMAP_HEADER_EXTENDED* extendedHeader = (BITMAP_HEADER_EXTENDED*)m_BitmapHeader; unsigned int BitCountRed = BitCountByMask(extendedHeader->RedMask); unsigned int BitCountGreen = BitCountByMask(extendedHeader->GreenMask); unsigned int BitCountBlue = BitCountByMask(extendedHeader->BlueMask); unsigned int BitCountAlpha = BitCountByMask(extendedHeader->AlphaMask); for (unsigned int i = 0; i < m_Height; i++) { if( m_Source->read((char*) Line, LineWidth) != LineWidth ) { result = false; goto failedRead; } uint8_t* LinePtr = Line; uint8_t* DestPtr = destBuffer + destPitch * (m_Height - i - 1); for (unsigned int j = 0; j < m_Width; j++) { unsigned int Color = 0; int OutIndex = j * 4; if (m_BitmapHeader->BitCount == 16) { Color = *((uint16_t*) LinePtr); LinePtr += 2; } else if (m_BitmapHeader->BitCount == 32) { Color = *((uint32_t*) LinePtr); LinePtr += 4; } else { // Other formats are not valid } DestPtr[OutIndex + 0] = Convert(ComponentByMask(Color, extendedHeader->RedMask), BitCountRed, 8); DestPtr[OutIndex + 1] = Convert(ComponentByMask(Color, extendedHeader->GreenMask), BitCountGreen, 8); DestPtr[OutIndex + 2] = Convert(ComponentByMask(Color, extendedHeader->BlueMask), BitCountBlue, 8); DestPtr[OutIndex + 3] = Convert(ComponentByMask(Color, extendedHeader->AlphaMask), BitCountAlpha, 8); } } } else { result = false; goto failedRead; } failedRead: delete [] ColorTable; delete [] Line; destImage->unlockRect(); return result; } EImageFormat ImageReaderBMP::getFormat() { return kImageFormat_BMP; } const char* ImageReaderBMP::getFormatName() { return "BMP"; } unsigned int ImageReaderBMP::getWidth() { return m_Width; } unsigned int ImageReaderBMP::getHeight() { return m_Height; } EImageColorModel ImageReaderBMP::getNativeColorModel() { return m_NativeColorModel; } }