/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DIBCOREHEADERSIZE ( 12UL ) #define DIBINFOHEADERSIZE ( sizeof(DIBInfoHeader) ) #define DIBV5HEADERSIZE ( sizeof(DIBV5Header) ) // - DIBInfoHeader and DIBV5Header typedef sal_Int32 FXPT2DOT30; namespace { struct CIEXYZ { FXPT2DOT30 aXyzX; FXPT2DOT30 aXyzY; FXPT2DOT30 aXyzZ; CIEXYZ() : aXyzX(0), aXyzY(0), aXyzZ(0) {} }; struct CIEXYZTriple { CIEXYZ aXyzRed; CIEXYZ aXyzGreen; CIEXYZ aXyzBlue; CIEXYZTriple() : aXyzRed(), aXyzGreen(), aXyzBlue() {} }; struct DIBInfoHeader { sal_uInt32 nSize; sal_Int32 nWidth; sal_Int32 nHeight; sal_uInt16 nPlanes; sal_uInt16 nBitCount; sal_uInt32 nCompression; sal_uInt32 nSizeImage; sal_Int32 nXPelsPerMeter; sal_Int32 nYPelsPerMeter; sal_uInt32 nColsUsed; sal_uInt32 nColsImportant; DIBInfoHeader() : nSize(0), nWidth(0), nHeight(0), nPlanes(0), nBitCount(0), nCompression(0), nSizeImage(0), nXPelsPerMeter(0), nYPelsPerMeter(0), nColsUsed(0), nColsImportant(0) {} }; struct DIBV5Header : public DIBInfoHeader { sal_uInt32 nV5RedMask; sal_uInt32 nV5GreenMask; sal_uInt32 nV5BlueMask; sal_uInt32 nV5AlphaMask; sal_uInt32 nV5CSType; CIEXYZTriple aV5Endpoints; sal_uInt32 nV5GammaRed; sal_uInt32 nV5GammaGreen; sal_uInt32 nV5GammaBlue; sal_uInt32 nV5Intent; sal_uInt32 nV5ProfileData; sal_uInt32 nV5ProfileSize; sal_uInt32 nV5Reserved; DIBV5Header() : DIBInfoHeader(), nV5RedMask(0), nV5GreenMask(0), nV5BlueMask(0), nV5AlphaMask(0), nV5CSType(0), aV5Endpoints(), nV5GammaRed(0), nV5GammaGreen(0), nV5GammaBlue(0), nV5Intent(0), nV5ProfileData(0), nV5ProfileSize(0), nV5Reserved(0) {} }; sal_uInt16 discretizeBitcount( sal_uInt16 nInputCount ) { return ( nInputCount <= 1 ) ? 1 : ( nInputCount <= 4 ) ? 4 : ( nInputCount <= 8 ) ? 8 : 24; } bool isBitfieldCompression( ScanlineFormat nScanlineFormat ) { return ScanlineFormat::N32BitTcMask == nScanlineFormat; } bool ImplReadDIBInfoHeader(SvStream& rIStm, DIBV5Header& rHeader, bool& bTopDown, bool bMSOFormat) { // BITMAPINFOHEADER or BITMAPCOREHEADER or BITMAPV5HEADER sal_uInt64 const aStartPos(rIStm.Tell()); rIStm.ReadUInt32( rHeader.nSize ); // BITMAPCOREHEADER if ( rHeader.nSize == DIBCOREHEADERSIZE ) { sal_Int16 nTmp16; rIStm.ReadInt16( nTmp16 ); rHeader.nWidth = nTmp16; rIStm.ReadInt16( nTmp16 ); rHeader.nHeight = nTmp16; rIStm.ReadUInt16( rHeader.nPlanes ); rIStm.ReadUInt16( rHeader.nBitCount ); } else if ( bMSOFormat && rHeader.nSize == DIBINFOHEADERSIZE ) { sal_Int16 nTmp16(0); rIStm.ReadInt16(nTmp16); rHeader.nWidth = nTmp16; rIStm.ReadInt16(nTmp16); rHeader.nHeight = nTmp16; sal_uInt8 nTmp8(0); rIStm.ReadUChar(nTmp8); rHeader.nPlanes = nTmp8; rIStm.ReadUChar(nTmp8); rHeader.nBitCount = nTmp8; rIStm.ReadInt16(nTmp16); rHeader.nSizeImage = nTmp16; rIStm.ReadInt16(nTmp16); rHeader.nCompression = nTmp16; if ( !rHeader.nSizeImage ) // uncompressed? rHeader.nSizeImage = ((rHeader.nWidth * rHeader.nBitCount + 31) & ~31) / 8 * rHeader.nHeight; rIStm.ReadInt32( rHeader.nXPelsPerMeter ); rIStm.ReadInt32( rHeader.nYPelsPerMeter ); rIStm.ReadUInt32( rHeader.nColsUsed ); rIStm.ReadUInt32( rHeader.nColsImportant ); } else { // BITMAPCOREHEADER, BITMAPV5HEADER or unknown. Read as far as possible std::size_t nUsed(sizeof(rHeader.nSize)); auto readUInt16 = [&nUsed, &rHeader, &rIStm](sal_uInt16 & v) { if (nUsed < rHeader.nSize) { rIStm.ReadUInt16(v); nUsed += sizeof(v); } }; auto readInt32 = [&nUsed, &rHeader, &rIStm](sal_Int32 & v) { if (nUsed < rHeader.nSize) { rIStm.ReadInt32(v); nUsed += sizeof(v); } }; auto readUInt32 = [&nUsed, &rHeader, &rIStm](sal_uInt32 & v) { if (nUsed < rHeader.nSize) { rIStm.ReadUInt32(v); nUsed += sizeof(v); } }; // read DIBInfoHeader entries readInt32( rHeader.nWidth ); readInt32( rHeader.nHeight ); readUInt16( rHeader.nPlanes ); readUInt16( rHeader.nBitCount ); readUInt32( rHeader.nCompression ); readUInt32( rHeader.nSizeImage ); readInt32( rHeader.nXPelsPerMeter ); readInt32( rHeader.nYPelsPerMeter ); readUInt32( rHeader.nColsUsed ); readUInt32( rHeader.nColsImportant ); // read DIBV5HEADER members readUInt32( rHeader.nV5RedMask ); readUInt32( rHeader.nV5GreenMask ); readUInt32( rHeader.nV5BlueMask ); readUInt32( rHeader.nV5AlphaMask ); readUInt32( rHeader.nV5CSType ); // read contained CIEXYZTriple's readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzX ); readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzY ); readInt32( rHeader.aV5Endpoints.aXyzRed.aXyzZ ); readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzX ); readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzY ); readInt32( rHeader.aV5Endpoints.aXyzGreen.aXyzZ ); readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzX ); readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzY ); readInt32( rHeader.aV5Endpoints.aXyzBlue.aXyzZ ); readUInt32( rHeader.nV5GammaRed ); readUInt32( rHeader.nV5GammaGreen ); readUInt32( rHeader.nV5GammaBlue ); readUInt32( rHeader.nV5Intent ); readUInt32( rHeader.nV5ProfileData ); readUInt32( rHeader.nV5ProfileSize ); readUInt32( rHeader.nV5Reserved ); // seek to EndPos if (!checkSeek(rIStm, aStartPos + rHeader.nSize)) return false; } if (rHeader.nHeight == SAL_MIN_INT32) return false; if ( rHeader.nHeight < 0 ) { bTopDown = true; rHeader.nHeight *= -1; } else { bTopDown = false; } if ( rHeader.nWidth < 0 || rHeader.nXPelsPerMeter < 0 || rHeader.nYPelsPerMeter < 0 ) { rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR ); } // #144105# protect a little against damaged files assert(rHeader.nHeight >= 0); if (rHeader.nHeight != 0 && rHeader.nWidth >= 0 && (rHeader.nSizeImage / 16 / static_cast(rHeader.nHeight) > o3tl::make_unsigned(rHeader.nWidth))) { rHeader.nSizeImage = 0; } if (rHeader.nPlanes != 1) return false; if (rHeader.nBitCount != 0 && rHeader.nBitCount != 1 && rHeader.nBitCount != 4 && rHeader.nBitCount != 8 && rHeader.nBitCount != 16 && rHeader.nBitCount != 24 && rHeader.nBitCount != 32) { return false; } return rIStm.good(); } bool ImplReadDIBPalette(SvStream& rIStm, BitmapPalette& rPal, bool bQuad) { const sal_uInt16 nColors = rPal.GetEntryCount(); const sal_uLong nPalSize = nColors * ( bQuad ? 4UL : 3UL ); BitmapColor aPalColor; std::unique_ptr pEntries(new sal_uInt8[ nPalSize ]); if (rIStm.ReadBytes(pEntries.get(), nPalSize) != nPalSize) { return false; } sal_uInt8* pTmpEntry = pEntries.get(); for( sal_uInt16 i = 0; i < nColors; i++ ) { aPalColor.SetBlue( *pTmpEntry++ ); aPalColor.SetGreen( *pTmpEntry++ ); aPalColor.SetRed( *pTmpEntry++ ); if( bQuad ) pTmpEntry++; rPal[i] = aPalColor; } return rIStm.GetError() == ERRCODE_NONE; } BitmapColor SanitizePaletteIndex(sal_uInt8 nIndex, BitmapPalette& rPalette, bool bForceToMonoWhileReading) { const sal_uInt16 nPaletteEntryCount = rPalette.GetEntryCount(); if (nPaletteEntryCount && nIndex >= nPaletteEntryCount) { auto nSanitizedIndex = nIndex % nPaletteEntryCount; SAL_WARN_IF(nIndex != nSanitizedIndex, "vcl", "invalid colormap index: " << static_cast(nIndex) << ", colormap len is: " << nPaletteEntryCount); nIndex = nSanitizedIndex; } if (nPaletteEntryCount && bForceToMonoWhileReading) { return BitmapColor(static_cast(rPalette[nIndex].GetLuminance() >= 255)); } return BitmapColor(nIndex); } BitmapColor SanitizeColor(const BitmapColor &rColor, bool bForceToMonoWhileReading) { if (!bForceToMonoWhileReading) return rColor; return BitmapColor(static_cast(rColor.GetLuminance() >= 255)); } bool ImplDecodeRLE(sal_uInt8* pBuffer, DIBV5Header const & rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, bool bForceToMonoWhileReading, bool bRLE4) { Scanline pRLE = pBuffer; Scanline pEndRLE = pBuffer + rHeader.nSizeImage; long nY = rHeader.nHeight - 1; const sal_uLong nWidth = rAcc.Width(); sal_uLong nCountByte; sal_uLong nRunByte; sal_uLong nX = 0; sal_uInt8 cTmp; bool bEndDecoding = false; do { if (pRLE == pEndRLE) return false; if( ( nCountByte = *pRLE++ ) == 0 ) { if (pRLE == pEndRLE) return false; nRunByte = *pRLE++; if( nRunByte > 2 ) { Scanline pScanline = rAcc.GetScanline(nY); if( bRLE4 ) { nCountByte = nRunByte >> 1; for( sal_uLong i = 0; i < nCountByte; i++ ) { if (pRLE == pEndRLE) return false; cTmp = *pRLE++; if( nX < nWidth ) rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading)); if( nX < nWidth ) rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette, bForceToMonoWhileReading)); } if( nRunByte & 1 ) { if (pRLE == pEndRLE) return false; if( nX < nWidth ) rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE >> 4, rPalette, bForceToMonoWhileReading)); pRLE++; } if( ( ( nRunByte + 1 ) >> 1 ) & 1 ) { if (pRLE == pEndRLE) return false; pRLE++; } } else { for( sal_uLong i = 0; i < nRunByte; i++ ) { if (pRLE == pEndRLE) return false; if( nX < nWidth ) rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(*pRLE, rPalette, bForceToMonoWhileReading)); pRLE++; } if( nRunByte & 1 ) { if (pRLE == pEndRLE) return false; pRLE++; } } } else if( !nRunByte ) { nY--; nX = 0; } else if( nRunByte == 1 ) bEndDecoding = true; else { if (pRLE == pEndRLE) return false; nX += *pRLE++; if (pRLE == pEndRLE) return false; nY -= *pRLE++; } } else { if (pRLE == pEndRLE) return false; cTmp = *pRLE++; Scanline pScanline = rAcc.GetScanline(nY); if( bRLE4 ) { nRunByte = nCountByte >> 1; for (sal_uLong i = 0; i < nRunByte && nX < nWidth; ++i) { rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading)); if( nX < nWidth ) rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp & 0x0f, rPalette, bForceToMonoWhileReading)); } if( ( nCountByte & 1 ) && ( nX < nWidth ) ) rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp >> 4, rPalette, bForceToMonoWhileReading)); } else { for (sal_uLong i = 0; i < nCountByte && nX < nWidth; ++i) rAcc.SetPixelOnData(pScanline, nX++, SanitizePaletteIndex(cTmp, rPalette, bForceToMonoWhileReading)); } } } while (!bEndDecoding && (nY >= 0)); return true; } bool ImplReadDIBBits(SvStream& rIStm, DIBV5Header& rHeader, BitmapWriteAccess& rAcc, BitmapPalette& rPalette, BitmapWriteAccess* pAccAlpha, bool bTopDown, bool& rAlphaUsed, const sal_uInt64 nAlignedWidth, const bool bForceToMonoWhileReading) { sal_uInt32 nRMask(( rHeader.nBitCount == 16 ) ? 0x00007c00UL : 0x00ff0000UL); sal_uInt32 nGMask(( rHeader.nBitCount == 16 ) ? 0x000003e0UL : 0x0000ff00UL); sal_uInt32 nBMask(( rHeader.nBitCount == 16 ) ? 0x0000001fUL : 0x000000ffUL); bool bNative(false); bool bTCMask(!pAccAlpha && ((16 == rHeader.nBitCount) || (32 == rHeader.nBitCount))); bool bRLE((RLE_8 == rHeader.nCompression && 8 == rHeader.nBitCount) || (RLE_4 == rHeader.nCompression && 4 == rHeader.nBitCount)); // Is native format? switch(rAcc.GetScanlineFormat()) { case ScanlineFormat::N1BitMsbPal: case ScanlineFormat::N24BitTcBgr: { // we can't trust arbitrary-sourced index based formats to have correct indexes, so we exclude the pal formats // from raw read and force checking their colormap indexes bNative = ( ( rAcc.IsBottomUp() != bTopDown ) && !bRLE && !bTCMask && ( rAcc.GetScanlineSize() == nAlignedWidth ) ); break; } default: { break; } } // Read data if (bNative) { if (nAlignedWidth > std::numeric_limits::max() / rHeader.nHeight) { return false; } std::size_t n = nAlignedWidth * rHeader.nHeight; if (rIStm.ReadBytes(rAcc.GetBuffer(), n) != n) { return false; } } else { // Read color mask if(bTCMask && BITFIELDS == rHeader.nCompression) { rIStm.SeekRel( -12 ); rIStm.ReadUInt32( nRMask ); rIStm.ReadUInt32( nGMask ); rIStm.ReadUInt32( nBMask ); } const long nWidth(rHeader.nWidth); const long nHeight(rHeader.nHeight); long nResult = 0; if (utl::ConfigManager::IsFuzzing() && (o3tl::checked_multiply(nWidth, nHeight, nResult) || nResult > 4000000)) return false; if (bRLE) { if(!rHeader.nSizeImage) { rHeader.nSizeImage = rIStm.remainingSize(); } if (rHeader.nSizeImage > rIStm.remainingSize()) return false; std::vector aBuffer(rHeader.nSizeImage); if (rIStm.ReadBytes(aBuffer.data(), rHeader.nSizeImage) != rHeader.nSizeImage) return false; if (!ImplDecodeRLE(aBuffer.data(), rHeader, rAcc, rPalette, bForceToMonoWhileReading, RLE_4 == rHeader.nCompression)) return false; } else { if (nAlignedWidth > rIStm.remainingSize()) { // ofz#11188 avoid timeout // all following paths will enter a case statement, and nCount // is always at least 1, so we can check here before allocation // if at least one row can be read return false; } std::vector aBuf(nAlignedWidth); const long nI(bTopDown ? 1 : -1); long nY(bTopDown ? 0 : nHeight - 1); long nCount(nHeight); switch(rHeader.nBitCount) { case 1: { for( ; nCount--; nY += nI ) { sal_uInt8 * pTmp = aBuf.data(); if (rIStm.ReadBytes(pTmp, nAlignedWidth) != nAlignedWidth) { return false; } sal_uInt8 cTmp = *pTmp++; Scanline pScanline = rAcc.GetScanline(nY); for( long nX = 0, nShift = 8; nX < nWidth; nX++ ) { if( !nShift ) { nShift = 8; cTmp = *pTmp++; } auto nIndex = (cTmp >> --nShift) & 1; rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading)); } } } break; case 4: { for( ; nCount--; nY += nI ) { sal_uInt8 * pTmp = aBuf.data(); if (rIStm.ReadBytes(pTmp, nAlignedWidth) != nAlignedWidth) { return false; } sal_uInt8 cTmp = *pTmp++; Scanline pScanline = rAcc.GetScanline(nY); for( long nX = 0, nShift = 2; nX < nWidth; nX++ ) { if( !nShift ) { nShift = 2; cTmp = *pTmp++; } auto nIndex = (cTmp >> ( --nShift << 2 ) ) & 0x0f; rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading)); } } } break; case 8: { for( ; nCount--; nY += nI ) { sal_uInt8 * pTmp = aBuf.data(); if (rIStm.ReadBytes(pTmp, nAlignedWidth) != nAlignedWidth) { return false; } Scanline pScanline = rAcc.GetScanline(nY); for( long nX = 0; nX < nWidth; nX++ ) { auto nIndex = *pTmp++; rAcc.SetPixelOnData(pScanline, nX, SanitizePaletteIndex(nIndex, rPalette, bForceToMonoWhileReading)); } } } break; case 16: { ColorMaskElement aRedMask(nRMask); if (!aRedMask.CalcMaskShift()) return false; ColorMaskElement aGreenMask(nGMask); if (!aGreenMask.CalcMaskShift()) return false; ColorMaskElement aBlueMask(nBMask); if (!aBlueMask.CalcMaskShift()) return false; ColorMask aMask(aRedMask, aGreenMask, aBlueMask); BitmapColor aColor; for( ; nCount--; nY += nI ) { sal_uInt16 * pTmp16 = reinterpret_cast(aBuf.data()); if (rIStm.ReadBytes(pTmp16, nAlignedWidth) != nAlignedWidth) { return false; } Scanline pScanline = rAcc.GetScanline(nY); for( long nX = 0; nX < nWidth; nX++ ) { aMask.GetColorFor16BitLSB( aColor, reinterpret_cast(pTmp16++) ); rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading)); } } } break; case 24: { BitmapColor aPixelColor; for( ; nCount--; nY += nI ) { sal_uInt8* pTmp = aBuf.data(); if (rIStm.ReadBytes(pTmp, nAlignedWidth) != nAlignedWidth) { return false; } Scanline pScanline = rAcc.GetScanline(nY); for( long nX = 0; nX < nWidth; nX++ ) { aPixelColor.SetBlue( *pTmp++ ); aPixelColor.SetGreen( *pTmp++ ); aPixelColor.SetRed( *pTmp++ ); rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aPixelColor, bForceToMonoWhileReading)); } } } break; case 32: { ColorMaskElement aRedMask(nRMask); if (!aRedMask.CalcMaskShift()) return false; ColorMaskElement aGreenMask(nGMask); if (!aGreenMask.CalcMaskShift()) return false; ColorMaskElement aBlueMask(nBMask); if (!aBlueMask.CalcMaskShift()) return false; ColorMask aMask(aRedMask, aGreenMask, aBlueMask); BitmapColor aColor; sal_uInt32* pTmp32; if(pAccAlpha) { sal_uInt8 aAlpha; for( ; nCount--; nY += nI ) { pTmp32 = reinterpret_cast(aBuf.data()); if (rIStm.ReadBytes(pTmp32, nAlignedWidth) != nAlignedWidth) { return false; } Scanline pScanline = rAcc.GetScanline(nY); Scanline pAlphaScanline = pAccAlpha->GetScanline(nY); for( long nX = 0; nX < nWidth; nX++ ) { aMask.GetColorAndAlphaFor32Bit( aColor, aAlpha, reinterpret_cast(pTmp32++) ); rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading)); pAccAlpha->SetPixelOnData(pAlphaScanline, nX, BitmapColor(sal_uInt8(0xff) - aAlpha)); rAlphaUsed |= 0xff != aAlpha; } } } else { for( ; nCount--; nY += nI ) { pTmp32 = reinterpret_cast(aBuf.data()); if (rIStm.ReadBytes(pTmp32, nAlignedWidth) != nAlignedWidth) { return false; } Scanline pScanline = rAcc.GetScanline(nY); for( long nX = 0; nX < nWidth; nX++ ) { aMask.GetColorFor32Bit( aColor, reinterpret_cast(pTmp32++) ); rAcc.SetPixelOnData(pScanline, nX, SanitizeColor(aColor, bForceToMonoWhileReading)); } } } } } } } return rIStm.GetError() == ERRCODE_NONE; } bool ImplReadDIBBody(SvStream& rIStm, Bitmap& rBmp, AlphaMask* pBmpAlpha, sal_uLong nOffset, bool bIsMask, bool bMSOFormat) { DIBV5Header aHeader; const sal_uLong nStmPos = rIStm.Tell(); bool bTopDown(false); if (!ImplReadDIBInfoHeader(rIStm, aHeader, bTopDown, bMSOFormat)) return false; //BI_BITCOUNT_0 jpeg/png is unsupported if (aHeader.nBitCount == 0) return false; if (aHeader.nWidth <= 0 || aHeader.nHeight <= 0) return false; // In case ImplReadDIB() didn't call ImplReadDIBFileHeader() before // this method, nOffset is 0, that's OK. if (nOffset && aHeader.nSize > nOffset) { // Header size claims to extend into the image data. // Looks like an error. return false; } sal_uInt16 nColors(0); SvStream* pIStm; std::unique_ptr pMemStm; std::vector aData; if (aHeader.nBitCount <= 8) { if(aHeader.nColsUsed) { nColors = static_cast(aHeader.nColsUsed); } else { nColors = ( 1 << aHeader.nBitCount ); } } if (ZCOMPRESS == aHeader.nCompression) { sal_uInt32 nCodedSize(0); sal_uInt32 nUncodedSize(0); // read coding information rIStm.ReadUInt32( nCodedSize ).ReadUInt32( nUncodedSize ).ReadUInt32( aHeader.nCompression ); if (nCodedSize > rIStm.remainingSize()) nCodedSize = sal_uInt32(rIStm.remainingSize()); pMemStm.reset(new SvMemoryStream); // There may be bytes left over or the codec might read more than // necessary. So to preserve the correctness of the source stream copy // the encoded block pMemStm->WriteStream(rIStm, nCodedSize); pMemStm->Seek(0); size_t nSizeInc(4 * pMemStm->remainingSize()); if (nUncodedSize < nSizeInc) nSizeInc = nUncodedSize; if (nSizeInc > 0) { // decode buffer ZCodec aCodec; aCodec.BeginCompression(); aData.resize(nSizeInc); size_t nDataPos(0); while (nUncodedSize > nDataPos) { assert(aData.size() > nDataPos); const size_t nToRead(std::min(nUncodedSize - nDataPos, aData.size() - nDataPos)); assert(nToRead > 0); assert(!aData.empty()); const long nRead = aCodec.Read(*pMemStm, aData.data() + nDataPos, sal_uInt32(nToRead)); if (nRead > 0) { nDataPos += static_cast(nRead); // we haven't read everything yet: resize buffer and continue if (nDataPos < nUncodedSize) aData.resize(aData.size() + nSizeInc); } else { break; } } // truncate the data buffer to actually read size aData.resize(nDataPos); // set the real uncoded size nUncodedSize = sal_uInt32(aData.size()); aCodec.EndCompression(); } if (aData.empty()) { // add something so we can take address of the first element aData.resize(1); nUncodedSize = 0; } // set decoded bytes to memory stream, // from which we will read the bitmap data pMemStm.reset(new SvMemoryStream); pIStm = pMemStm.get(); assert(!aData.empty()); pMemStm->SetBuffer(aData.data(), nUncodedSize, nUncodedSize); nOffset = 0; } else { pIStm = &rIStm; } // read palette BitmapPalette aPalette; if (nColors) { aPalette.SetEntryCount(nColors); ImplReadDIBPalette(*pIStm, aPalette, aHeader.nSize != DIBCOREHEADERSIZE); } if (pIStm->GetError()) return false; if (nOffset) { pIStm->SeekRel(nOffset - (pIStm->Tell() - nStmPos)); } const sal_Int64 nBitsPerLine (static_cast(aHeader.nWidth) * static_cast(aHeader.nBitCount)); if (nBitsPerLine > SAL_MAX_UINT32) return false; const sal_uInt64 nAlignedWidth(AlignedWidth4Bytes(static_cast(nBitsPerLine))); switch (aHeader.nCompression) { case RLE_8: { if (aHeader.nBitCount != 8) return false; // (partially) check the image dimensions to avoid potential large bitmap allocation if the input is damaged sal_uInt64 nMaxWidth = pIStm->remainingSize(); nMaxWidth *= 256; //assume generous compression ratio nMaxWidth /= aHeader.nHeight; if (nMaxWidth < o3tl::make_unsigned(aHeader.nWidth)) return false; break; } case RLE_4: { if (aHeader.nBitCount != 4) return false; sal_uInt64 nMaxWidth = pIStm->remainingSize(); nMaxWidth *= 512; //assume generous compression ratio nMaxWidth /= aHeader.nHeight; if (nMaxWidth < o3tl::make_unsigned(aHeader.nWidth)) return false; break; } default: // tdf#122958 invalid compression value used if (aHeader.nCompression & 0x000F) { // lets assume that there was an error in the generating application // and allow through as COMPRESS_NONE if the bottom byte is 0 SAL_WARN( "vcl", "bad bmp compression scheme: " << aHeader.nCompression << ", rejecting bmp"); return false; } else SAL_WARN( "vcl", "bad bmp compression scheme: " << aHeader.nCompression << ", assuming meant to be COMPRESS_NONE"); [[fallthrough]]; case BITFIELDS: case ZCOMPRESS: case COMPRESS_NONE: { // (partially) check the image dimensions to avoid potential large bitmap allocation if the input is damaged sal_uInt64 nMaxWidth = pIStm->remainingSize(); nMaxWidth /= aHeader.nHeight; if (nMaxWidth < nAlignedWidth) return false; break; } } const Size aSizePixel(aHeader.nWidth, aHeader.nHeight); AlphaMask aNewBmpAlpha; AlphaScopedWriteAccess pAccAlpha; bool bAlphaPossible(pBmpAlpha && aHeader.nBitCount == 32); if (bAlphaPossible) { const bool bRedSet(0 != aHeader.nV5RedMask); const bool bGreenSet(0 != aHeader.nV5GreenMask); const bool bBlueSet(0 != aHeader.nV5BlueMask); // some clipboard entries have alpha mask on zero to say that there is // no alpha; do only use this when the other masks are set. The MS docu // says that masks are only to be set when bV5Compression is set to // BI_BITFIELDS, but there seem to exist a wild variety of usages... if((bRedSet || bGreenSet || bBlueSet) && (0 == aHeader.nV5AlphaMask)) { bAlphaPossible = false; } } if (bAlphaPossible) { aNewBmpAlpha = AlphaMask(aSizePixel); pAccAlpha = AlphaScopedWriteAccess(aNewBmpAlpha); } sal_uInt16 nBitCount(discretizeBitcount(aHeader.nBitCount)); const BitmapPalette* pPal = &aPalette; //ofz#948 match the surrounding logic of case TransparentType::Bitmap of //ReadDIBBitmapEx but do it while reading for performance const bool bIsAlpha = (nBitCount == 8 && !!aPalette && aPalette.IsGreyPalette()); const bool bForceToMonoWhileReading = (bIsMask && !bIsAlpha && nBitCount != 1); if (bForceToMonoWhileReading) { pPal = nullptr; nBitCount = 1; SAL_WARN( "vcl", "forcing mask to monochrome"); } Bitmap aNewBmp(aSizePixel, nBitCount, pPal); BitmapScopedWriteAccess pAcc(aNewBmp); if (!pAcc) return false; if (pAcc->Width() != aHeader.nWidth || pAcc->Height() != aHeader.nHeight) { return false; } // read bits bool bAlphaUsed(false); bool bRet = ImplReadDIBBits(*pIStm, aHeader, *pAcc, aPalette, pAccAlpha.get(), bTopDown, bAlphaUsed, nAlignedWidth, bForceToMonoWhileReading); if (bRet && aHeader.nXPelsPerMeter && aHeader.nYPelsPerMeter) { MapMode aMapMode( MapUnit::MapMM, Point(), Fraction(1000, aHeader.nXPelsPerMeter), Fraction(1000, aHeader.nYPelsPerMeter)); aNewBmp.SetPrefMapMode(aMapMode); aNewBmp.SetPrefSize(Size(aHeader.nWidth, aHeader.nHeight)); } pAcc.reset(); if (bAlphaPossible) { pAccAlpha.reset(); if(!bAlphaUsed) { bAlphaPossible = false; } } if (bRet) { rBmp = aNewBmp; if(bAlphaPossible) { *pBmpAlpha = aNewBmpAlpha; } } return bRet; } bool ImplReadDIBFileHeader( SvStream& rIStm, sal_uLong& rOffset ) { bool bRet = false; const sal_uInt64 nStreamLength = rIStm.TellEnd(); sal_uInt16 nTmp16 = 0; rIStm.ReadUInt16( nTmp16 ); if ( ( 0x4D42 == nTmp16 ) || ( 0x4142 == nTmp16 ) ) { sal_uInt32 nTmp32(0); if ( 0x4142 == nTmp16 ) { rIStm.SeekRel( 12 ); rIStm.ReadUInt16( nTmp16 ); rIStm.SeekRel( 8 ); rIStm.ReadUInt32( nTmp32 ); rOffset = nTmp32 - 28; bRet = ( 0x4D42 == nTmp16 ); } else // 0x4D42 == nTmp16, 'MB' from BITMAPFILEHEADER { rIStm.SeekRel( 8 ); // we are on bfSize member of BITMAPFILEHEADER, forward to bfOffBits rIStm.ReadUInt32( nTmp32 ); // read bfOffBits rOffset = nTmp32 - 14; // adapt offset by sizeof(BITMAPFILEHEADER) bRet = rIStm.GetError() == ERRCODE_NONE; } if ( rOffset >= nStreamLength ) { // Offset claims that image starts past the end of the // stream. Unlikely. rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR ); bRet = false; } } else rIStm.SetError( SVSTREAM_FILEFORMAT_ERROR ); return bRet; } bool ImplWriteDIBPalette( SvStream& rOStm, BitmapReadAccess const & rAcc ) { const sal_uInt16 nColors = rAcc.GetPaletteEntryCount(); const sal_uLong nPalSize = nColors * 4UL; std::unique_ptr pEntries(new sal_uInt8[ nPalSize ]); sal_uInt8* pTmpEntry = pEntries.get(); for( sal_uInt16 i = 0; i < nColors; i++ ) { const BitmapColor& rPalColor = rAcc.GetPaletteColor( i ); *pTmpEntry++ = rPalColor.GetBlue(); *pTmpEntry++ = rPalColor.GetGreen(); *pTmpEntry++ = rPalColor.GetRed(); *pTmpEntry++ = 0; } rOStm.WriteBytes( pEntries.get(), nPalSize ); return rOStm.GetError() == ERRCODE_NONE; } bool ImplWriteRLE( SvStream& rOStm, BitmapReadAccess const & rAcc, bool bRLE4 ) { const sal_uLong nWidth = rAcc.Width(); const sal_uLong nHeight = rAcc.Height(); sal_uLong nX; sal_uLong nSaveIndex; sal_uLong nCount; sal_uLong nBufCount; std::vector aBuf(( nWidth << 1 ) + 2); sal_uInt8 cPix; sal_uInt8 cLast; bool bFound; for ( long nY = nHeight - 1; nY >= 0; nY-- ) { sal_uInt8* pTmp = aBuf.data(); nX = nBufCount = 0; Scanline pScanline = rAcc.GetScanline( nY ); while( nX < nWidth ) { nCount = 1; cPix = rAcc.GetIndexFromData( pScanline, nX++ ); while( ( nX < nWidth ) && ( nCount < 255 ) && ( cPix == rAcc.GetIndexFromData( pScanline, nX ) ) ) { nX++; nCount++; } if ( nCount > 1 ) { *pTmp++ = static_cast(nCount); *pTmp++ = ( bRLE4 ? ( ( cPix << 4 ) | cPix ) : cPix ); nBufCount += 2; } else { cLast = cPix; nSaveIndex = nX - 1; bFound = false; while( ( nX < nWidth ) && ( nCount < 256 ) && ( cPix = rAcc.GetIndexFromData( pScanline, nX ) ) != cLast ) { nX++; nCount++; cLast = cPix; bFound = true; } if ( bFound ) nX--; if ( nCount > 3 ) { *pTmp++ = 0; *pTmp++ = static_cast(--nCount); if( bRLE4 ) { for ( sal_uLong i = 0; i < nCount; i++, pTmp++ ) { *pTmp = rAcc.GetIndexFromData( pScanline, nSaveIndex++ ) << 4; if ( ++i < nCount ) *pTmp |= rAcc.GetIndexFromData( pScanline, nSaveIndex++ ); } nCount = ( nCount + 1 ) >> 1; } else { for( sal_uLong i = 0; i < nCount; i++ ) *pTmp++ = rAcc.GetIndexFromData( pScanline, nSaveIndex++ ); } if ( nCount & 1 ) { *pTmp++ = 0; nBufCount += ( nCount + 3 ); } else nBufCount += ( nCount + 2 ); } else { *pTmp++ = 1; *pTmp++ = rAcc.GetIndexFromData( pScanline, nSaveIndex ) << (bRLE4 ? 4 : 0); if ( nCount == 3 ) { *pTmp++ = 1; *pTmp++ = rAcc.GetIndexFromData( pScanline, ++nSaveIndex ) << ( bRLE4 ? 4 : 0 ); nBufCount += 4; } else nBufCount += 2; } } } aBuf[ nBufCount++ ] = 0; aBuf[ nBufCount++ ] = 0; rOStm.WriteBytes(aBuf.data(), nBufCount); } rOStm.WriteUChar( 0 ); rOStm.WriteUChar( 1 ); return rOStm.GetError() == ERRCODE_NONE; } bool ImplWriteDIBBits(SvStream& rOStm, BitmapReadAccess const & rAcc, BitmapReadAccess const * pAccAlpha, sal_uLong nCompression, sal_uInt32& rImageSize) { if(!pAccAlpha && BITFIELDS == nCompression) { const ColorMask& rMask = rAcc.GetColorMask(); SVBT32 aVal32; UInt32ToSVBT32( rMask.GetRedMask(), aVal32 ); rOStm.WriteBytes( aVal32, 4UL ); UInt32ToSVBT32( rMask.GetGreenMask(), aVal32 ); rOStm.WriteBytes( aVal32, 4UL ); UInt32ToSVBT32( rMask.GetBlueMask(), aVal32 ); rOStm.WriteBytes( aVal32, 4UL ); rImageSize = rOStm.Tell(); if( rAcc.IsBottomUp() ) rOStm.WriteBytes(rAcc.GetBuffer(), rAcc.Height() * rAcc.GetScanlineSize()); else { for( long nY = rAcc.Height() - 1, nScanlineSize = rAcc.GetScanlineSize(); nY >= 0; nY-- ) rOStm.WriteBytes( rAcc.GetScanline(nY), nScanlineSize ); } } else if(!pAccAlpha && ((RLE_4 == nCompression) || (RLE_8 == nCompression))) { rImageSize = rOStm.Tell(); ImplWriteRLE( rOStm, rAcc, RLE_4 == nCompression ); } else if(!nCompression) { // #i5xxx# Limit bitcount to 24bit, the 32 bit cases are not // handled properly below (would have to set color masks, and // nCompression=BITFIELDS - but color mask is not set for // formats != *_TC_*). Note that this very problem might cause // trouble at other places - the introduction of 32 bit RGBA // bitmaps is relatively recent. // #i59239# discretize bitcount for aligned width to 1,4,8,24 // (other cases are not written below) const sal_uInt16 nBitCount(pAccAlpha ? 32 : discretizeBitcount(rAcc.GetBitCount())); const sal_uLong nAlignedWidth(AlignedWidth4Bytes(rAcc.Width() * nBitCount)); bool bNative(false); switch(rAcc.GetScanlineFormat()) { case ScanlineFormat::N1BitMsbPal: case ScanlineFormat::N4BitMsnPal: case ScanlineFormat::N8BitPal: case ScanlineFormat::N24BitTcBgr: { if(!pAccAlpha && rAcc.IsBottomUp() && (rAcc.GetScanlineSize() == nAlignedWidth)) { bNative = true; } break; } default: { break; } } rImageSize = rOStm.Tell(); if(bNative) { rOStm.WriteBytes(rAcc.GetBuffer(), nAlignedWidth * rAcc.Height()); } else { const long nWidth(rAcc.Width()); const long nHeight(rAcc.Height()); std::vector aBuf(nAlignedWidth); switch( nBitCount ) { case 1: { //valgrind, zero out the trailing unused alignment bytes size_t nUnusedBytes = nAlignedWidth - ((nWidth+7) / 8); memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes); for( long nY = nHeight - 1; nY >= 0; nY-- ) { sal_uInt8* pTmp = aBuf.data(); sal_uInt8 cTmp = 0; Scanline pScanline = rAcc.GetScanline( nY ); for( long nX = 0, nShift = 8; nX < nWidth; nX++ ) { if( !nShift ) { nShift = 8; *pTmp++ = cTmp; cTmp = 0; } cTmp |= rAcc.GetIndexFromData( pScanline, nX ) << --nShift; } *pTmp = cTmp; rOStm.WriteBytes(aBuf.data(), nAlignedWidth); } } break; case 4: { //valgrind, zero out the trailing unused alignment bytes size_t nUnusedBytes = nAlignedWidth - ((nWidth+1) / 2); memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes); for( long nY = nHeight - 1; nY >= 0; nY-- ) { sal_uInt8* pTmp = aBuf.data(); sal_uInt8 cTmp = 0; Scanline pScanline = rAcc.GetScanline( nY ); for( long nX = 0, nShift = 2; nX < nWidth; nX++ ) { if( !nShift ) { nShift = 2; *pTmp++ = cTmp; cTmp = 0; } cTmp |= rAcc.GetIndexFromData( pScanline, nX ) << ( --nShift << 2 ); } *pTmp = cTmp; rOStm.WriteBytes(aBuf.data(), nAlignedWidth); } } break; case 8: { for( long nY = nHeight - 1; nY >= 0; nY-- ) { sal_uInt8* pTmp = aBuf.data(); Scanline pScanline = rAcc.GetScanline( nY ); for( long nX = 0; nX < nWidth; nX++ ) *pTmp++ = rAcc.GetIndexFromData( pScanline, nX ); rOStm.WriteBytes(aBuf.data(), nAlignedWidth); } } break; case 24: { //valgrind, zero out the trailing unused alignment bytes size_t nUnusedBytes = nAlignedWidth - nWidth * 3; memset(aBuf.data() + nAlignedWidth - nUnusedBytes, 0, nUnusedBytes); } [[fallthrough]]; // #i59239# fallback to 24 bit format, if bitcount is non-default default: { BitmapColor aPixelColor; const bool bWriteAlpha(32 == nBitCount && pAccAlpha); for( long nY = nHeight - 1; nY >= 0; nY-- ) { sal_uInt8* pTmp = aBuf.data(); Scanline pScanlineAlpha = bWriteAlpha ? pAccAlpha->GetScanline( nY ) : nullptr; for( long nX = 0; nX < nWidth; nX++ ) { // when alpha is used, this may be non-24bit main bitmap, so use GetColor // instead of GetPixel to ensure RGB value aPixelColor = rAcc.GetColor( nY, nX ); *pTmp++ = aPixelColor.GetBlue(); *pTmp++ = aPixelColor.GetGreen(); *pTmp++ = aPixelColor.GetRed(); if(bWriteAlpha) { *pTmp++ = sal_uInt8(0xff) - pAccAlpha->GetIndexFromData( pScanlineAlpha, nX ); } } rOStm.WriteBytes(aBuf.data(), nAlignedWidth); } } break; } } } rImageSize = rOStm.Tell() - rImageSize; return (!rOStm.GetError()); } bool ImplWriteDIBBody(const Bitmap& rBitmap, SvStream& rOStm, BitmapReadAccess const & rAcc, BitmapReadAccess const * pAccAlpha, bool bCompressed) { const MapMode aMapPixel(MapUnit::MapPixel); DIBV5Header aHeader; sal_uLong nImageSizePos(0); sal_uLong nEndPos(0); sal_uInt32 nCompression(COMPRESS_NONE); bool bRet(false); aHeader.nSize = pAccAlpha ? DIBV5HEADERSIZE : DIBINFOHEADERSIZE; // size dependent on CF_DIB type to use aHeader.nWidth = rAcc.Width(); aHeader.nHeight = rAcc.Height(); aHeader.nPlanes = 1; if(!pAccAlpha && isBitfieldCompression(rAcc.GetScanlineFormat())) { aHeader.nBitCount = 32; aHeader.nSizeImage = rAcc.Height() * rAcc.GetScanlineSize(); nCompression = BITFIELDS; } else { // #i5xxx# Limit bitcount to 24bit, the 32 bit cases are // not handled properly below (would have to set color // masks, and nCompression=BITFIELDS - but color mask is // not set for formats != *_TC_*). Note that this very // problem might cause trouble at other places - the // introduction of 32 bit RGBA bitmaps is relatively // recent. // #i59239# discretize bitcount to 1,4,8,24 (other cases // are not written below) const sal_uInt16 nBitCount(pAccAlpha ? 32 : discretizeBitcount(rAcc.GetBitCount())); aHeader.nBitCount = nBitCount; aHeader.nSizeImage = rAcc.Height() * AlignedWidth4Bytes(rAcc.Width() * aHeader.nBitCount); if(bCompressed) { if(4 == nBitCount) { nCompression = RLE_4; } else if(8 == nBitCount) { nCompression = RLE_8; } } } if((rOStm.GetCompressMode() & SvStreamCompressFlags::ZBITMAP) && (rOStm.GetVersion() >= SOFFICE_FILEFORMAT_40)) { aHeader.nCompression = ZCOMPRESS; } else { aHeader.nCompression = nCompression; } if(rBitmap.GetPrefSize().Width() && rBitmap.GetPrefSize().Height() && (rBitmap.GetPrefMapMode() != aMapPixel)) { // #i48108# Try to recover xpels/ypels as previously stored on // disk. The problem with just converting maPrefSize to 100th // mm and then relating that to the bitmap pixel size is that // MapMode is integer-based, and suffers from roundoffs, // especially if maPrefSize is small. Trying to circumvent // that by performing part of the math in floating point. const Size aScale100000(OutputDevice::LogicToLogic(Size(100000, 100000), MapMode(MapUnit::Map100thMM), rBitmap.GetPrefMapMode())); const double fBmpWidthM(static_cast(rBitmap.GetPrefSize().Width()) / aScale100000.Width()); const double fBmpHeightM(static_cast(rBitmap.GetPrefSize().Height()) / aScale100000.Height()); if(!basegfx::fTools::equalZero(fBmpWidthM) && !basegfx::fTools::equalZero(fBmpHeightM)) { aHeader.nXPelsPerMeter = basegfx::fround(rAcc.Width() / fabs(fBmpWidthM)); aHeader.nYPelsPerMeter = basegfx::fround(rAcc.Height() / fabs(fBmpHeightM)); } } aHeader.nColsUsed = ((!pAccAlpha && aHeader.nBitCount <= 8) ? rAcc.GetPaletteEntryCount() : 0); aHeader.nColsImportant = 0; rOStm.WriteUInt32( aHeader.nSize ); rOStm.WriteInt32( aHeader.nWidth ); rOStm.WriteInt32( aHeader.nHeight ); rOStm.WriteUInt16( aHeader.nPlanes ); rOStm.WriteUInt16( aHeader.nBitCount ); rOStm.WriteUInt32( aHeader.nCompression ); nImageSizePos = rOStm.Tell(); rOStm.SeekRel( sizeof( aHeader.nSizeImage ) ); rOStm.WriteInt32( aHeader.nXPelsPerMeter ); rOStm.WriteInt32( aHeader.nYPelsPerMeter ); rOStm.WriteUInt32( aHeader.nColsUsed ); rOStm.WriteUInt32( aHeader.nColsImportant ); if(pAccAlpha) // only write DIBV5 when asked to do so { aHeader.nV5CSType = 0x57696E20; // LCS_WINDOWS_COLOR_SPACE aHeader.nV5Intent = 0x00000004; // LCS_GM_IMAGES rOStm.WriteUInt32( aHeader.nV5RedMask ); rOStm.WriteUInt32( aHeader.nV5GreenMask ); rOStm.WriteUInt32( aHeader.nV5BlueMask ); rOStm.WriteUInt32( aHeader.nV5AlphaMask ); rOStm.WriteUInt32( aHeader.nV5CSType ); rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzX ); rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzY ); rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzRed.aXyzZ ); rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzX ); rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzY ); rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzGreen.aXyzZ ); rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzX ); rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzY ); rOStm.WriteInt32( aHeader.aV5Endpoints.aXyzBlue.aXyzZ ); rOStm.WriteUInt32( aHeader.nV5GammaRed ); rOStm.WriteUInt32( aHeader.nV5GammaGreen ); rOStm.WriteUInt32( aHeader.nV5GammaBlue ); rOStm.WriteUInt32( aHeader.nV5Intent ); rOStm.WriteUInt32( aHeader.nV5ProfileData ); rOStm.WriteUInt32( aHeader.nV5ProfileSize ); rOStm.WriteUInt32( aHeader.nV5Reserved ); } if(ZCOMPRESS == aHeader.nCompression) { ZCodec aCodec; SvMemoryStream aMemStm(aHeader.nSizeImage + 4096, 65535); sal_uLong nCodedPos(rOStm.Tell()); sal_uLong nLastPos(0); sal_uInt32 nCodedSize(0); sal_uInt32 nUncodedSize(0); // write uncoded data palette if(aHeader.nColsUsed) { ImplWriteDIBPalette(aMemStm, rAcc); } // write uncoded bits bRet = ImplWriteDIBBits(aMemStm, rAcc, pAccAlpha, nCompression, aHeader.nSizeImage); // get uncoded size nUncodedSize = aMemStm.Tell(); // seek over compress info rOStm.SeekRel(12); // write compressed data aCodec.BeginCompression(3); aCodec.Write(rOStm, static_cast(aMemStm.GetData()), nUncodedSize); aCodec.EndCompression(); // update compress info ( coded size, uncoded size, uncoded compression ) nLastPos = rOStm.Tell(); nCodedSize = nLastPos - nCodedPos - 12; rOStm.Seek(nCodedPos); rOStm.WriteUInt32( nCodedSize ).WriteUInt32( nUncodedSize ).WriteUInt32( nCompression ); rOStm.Seek(nLastPos); if(bRet) { bRet = (ERRCODE_NONE == rOStm.GetError()); } } else { if(aHeader.nColsUsed) { ImplWriteDIBPalette(rOStm, rAcc); } bRet = ImplWriteDIBBits(rOStm, rAcc, pAccAlpha, aHeader.nCompression, aHeader.nSizeImage); } nEndPos = rOStm.Tell(); rOStm.Seek(nImageSizePos); rOStm.WriteUInt32( aHeader.nSizeImage ); rOStm.Seek(nEndPos); return bRet; } bool ImplWriteDIBFileHeader(SvStream& rOStm, BitmapReadAccess const & rAcc) { const sal_uInt32 nPalCount((rAcc.HasPalette() ? rAcc.GetPaletteEntryCount() : isBitfieldCompression(rAcc.GetScanlineFormat()) ? 3UL : 0UL)); const sal_uInt32 nOffset(14 + DIBINFOHEADERSIZE + nPalCount * 4UL); rOStm.WriteUInt16( 0x4D42 ); // 'MB' from BITMAPFILEHEADER rOStm.WriteUInt32( nOffset + (rAcc.Height() * rAcc.GetScanlineSize()) ); rOStm.WriteUInt16( 0 ); rOStm.WriteUInt16( 0 ); rOStm.WriteUInt32( nOffset ); return rOStm.GetError() == ERRCODE_NONE; } bool ImplReadDIB( Bitmap& rTarget, AlphaMask* pTargetAlpha, SvStream& rIStm, bool bFileHeader, bool bIsMask=false, bool bMSOFormat=false) { const SvStreamEndian nOldFormat(rIStm.GetEndian()); const sal_uLong nOldPos(rIStm.Tell()); sal_uLong nOffset(0); bool bRet(false); rIStm.SetEndian(SvStreamEndian::LITTLE); if(bFileHeader) { if(ImplReadDIBFileHeader(rIStm, nOffset)) { bRet = ImplReadDIBBody(rIStm, rTarget, nOffset >= DIBV5HEADERSIZE ? pTargetAlpha : nullptr, nOffset, bIsMask, bMSOFormat); } } else { bRet = ImplReadDIBBody(rIStm, rTarget, nullptr, nOffset, bIsMask, bMSOFormat); } if(!bRet) { if(!rIStm.GetError()) { rIStm.SetError(SVSTREAM_GENERALERROR); } rIStm.Seek(nOldPos); } rIStm.SetEndian(nOldFormat); return bRet; } bool ImplWriteDIB( const Bitmap& rSource, SvStream& rOStm, bool bCompressed, bool bFileHeader) { const Size aSizePix(rSource.GetSizePixel()); bool bRet(false); if(aSizePix.Width() && aSizePix.Height()) { Bitmap::ScopedReadAccess pAcc(const_cast< Bitmap& >(rSource)); Bitmap::ScopedReadAccess pAccAlpha; const SvStreamEndian nOldFormat(rOStm.GetEndian()); const sal_uLong nOldPos(rOStm.Tell()); rOStm.SetEndian(SvStreamEndian::LITTLE); if (pAcc) { if(bFileHeader) { if(ImplWriteDIBFileHeader(rOStm, *pAcc)) { bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, pAccAlpha.get(), bCompressed); } } else { bRet = ImplWriteDIBBody(rSource, rOStm, *pAcc, pAccAlpha.get(), bCompressed); } pAcc.reset(); } pAccAlpha.reset(); if(!bRet) { rOStm.SetError(SVSTREAM_GENERALERROR); rOStm.Seek(nOldPos); } rOStm.SetEndian(nOldFormat); } return bRet; } } // unnamed namespace bool ReadDIB( Bitmap& rTarget, SvStream& rIStm, bool bFileHeader, bool bMSOFormat) { return ImplReadDIB(rTarget, nullptr, rIStm, bFileHeader, false, bMSOFormat); } bool ReadDIBBitmapEx( BitmapEx& rTarget, SvStream& rIStm, bool bFileHeader, bool bMSOFormat) { Bitmap aBmp; bool bRetval(ImplReadDIB(aBmp, nullptr, rIStm, bFileHeader, /*bMask*/false, bMSOFormat) && !rIStm.GetError()); if(bRetval) { // base bitmap was read, set as return value and try to read alpha extra-data const sal_uLong nStmPos(rIStm.Tell()); sal_uInt32 nMagic1(0); sal_uInt32 nMagic2(0); rTarget = BitmapEx(aBmp); rIStm.ReadUInt32( nMagic1 ).ReadUInt32( nMagic2 ); bRetval = (0x25091962 == nMagic1) && (0xACB20201 == nMagic2) && !rIStm.GetError(); if(bRetval) { sal_uInt8 tmp = 0; rIStm.ReadUChar( tmp ); TransparentType transparent = static_cast(tmp); bRetval = !rIStm.GetError(); if(bRetval) { switch (transparent) { case TransparentType::Bitmap: { Bitmap aMask; bRetval = ImplReadDIB(aMask, nullptr, rIStm, true, true); if(bRetval) { if(!!aMask) { // do we have an alpha mask? if((8 == aMask.GetBitCount()) && aMask.HasGreyPalette()) { AlphaMask aAlpha; // create alpha mask quickly (without greyscale conversion) aAlpha.ImplSetBitmap(aMask); rTarget = BitmapEx(aBmp, aAlpha); } else { rTarget = BitmapEx(aBmp, aMask); } } } break; } case TransparentType::Color: { Color aTransparentColor; tools::GenericTypeSerializer aSerializer(rIStm); aSerializer.readColor(aTransparentColor); bRetval = !rIStm.GetError(); if(bRetval) { rTarget = BitmapEx(aBmp, aTransparentColor); } break; } default: break; } } } if(!bRetval) { // alpha extra data could not be read; reset, but use base bitmap as result rIStm.ResetError(); rIStm.Seek(nStmPos); bRetval = true; } } return bRetval; } bool ReadDIBV5( Bitmap& rTarget, AlphaMask& rTargetAlpha, SvStream& rIStm) { return ImplReadDIB(rTarget, &rTargetAlpha, rIStm, true); } bool ReadRawDIB( BitmapEx& rTarget, const unsigned char* pBuf, const ScanlineFormat nFormat, const int nHeight, const int nStride) { BitmapScopedWriteAccess pWriteAccess(rTarget.maBitmap.AcquireWriteAccess(), rTarget.maBitmap); for (int nRow = 0; nRow < nHeight; ++nRow) { pWriteAccess->CopyScanline(nRow, pBuf + (nStride * nRow), nFormat, nStride); } return true; } bool WriteDIB( const Bitmap& rSource, SvStream& rOStm, bool bCompressed, bool bFileHeader) { return ImplWriteDIB(rSource, rOStm, bCompressed, bFileHeader); } bool WriteDIB( const BitmapEx& rSource, SvStream& rOStm, bool bCompressed) { return ImplWriteDIB(rSource.GetBitmap(), rOStm, bCompressed, /*bFileHeader*/true); } bool WriteDIBBitmapEx( const BitmapEx& rSource, SvStream& rOStm) { if(ImplWriteDIB(rSource.GetBitmap(), rOStm, true, true)) { rOStm.WriteUInt32( 0x25091962 ); rOStm.WriteUInt32( 0xACB20201 ); rOStm.WriteUChar( static_cast(rSource.meTransparent) ); if(TransparentType::Bitmap == rSource.meTransparent) { return ImplWriteDIB(rSource.maMask, rOStm, true, true); } else if(TransparentType::Color == rSource.meTransparent) { tools::GenericTypeSerializer aSerializer(rOStm); aSerializer.writeColor(rSource.maTransparentColor); return true; } } return false; } sal_uInt32 getDIBV5HeaderSize() { return DIBV5HEADERSIZE; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */