/* -*- 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 namespace vcl { #define PNGCHUNK_IHDR 0x49484452 #define PNGCHUNK_PLTE 0x504c5445 #define PNGCHUNK_IDAT 0x49444154 #define PNGCHUNK_IEND 0x49454e44 #define PNGCHUNK_bKGD 0x624b4744 #define PNGCHUNK_gAMA 0x67414d41 #define PNGCHUNK_pHYs 0x70485973 #define PNGCHUNK_tRNS 0x74524e53 #define VIEWING_GAMMA 2.35 #define DISPLAY_GAMMA 1.0 static const sal_uInt8 mpDefaultColorTable[ 256 ] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff }; class PNGReaderImpl { private: SvStream& mrPNGStream; SvStreamEndian mnOrigStreamMode; std::vector maChunkSeq; std::vector::iterator maChunkIter; std::vector::iterator maDataIter; std::unique_ptr mpBmp; BitmapScopedWriteAccess mxAcc; std::unique_ptr mpMaskBmp; BitmapScopedWriteAccess mxMaskAcc; std::unique_ptr mpAlphaMask; AlphaScopedWriteAccess mxAlphaAcc; BitmapWriteAccess* mpMaskAcc; ZCodec mpZCodec; std::unique_ptr mpInflateInBuf; // as big as the size of a scanline + alphachannel + 1 std::unique_ptr mpScanPrior; // pointer to the latest scanline std::unique_ptr mpTransTab; // for transparency in images with palette colortype sal_uInt8* mpScanCurrent; // pointer into the current scanline sal_uInt8* mpColorTable; std::size_t mnStreamSize; // estimate of PNG file size sal_uInt32 mnChunkType; // Type of current PNG chunk sal_Int32 mnChunkLen; // Length of current PNG chunk Size maOrigSize; // pixel size of the full image Size maTargetSize; // pixel size of the result image Size maPhysSize; // preferred size in MapUnit::Map100thMM units sal_uInt32 mnBPP; // number of bytes per pixel sal_uInt32 mnScansize; // max size of scanline sal_uInt32 mnYpos; // latest y position in full image int mnPass; // if interlaced the latest pass ( 1..7 ) else 7 sal_uInt32 mnXStart; // the starting X for the current pass sal_uInt32 mnXAdd; // the increment for input images X coords for the current pass sal_uInt32 mnYAdd; // the increment for input images Y coords for the current pass int mnPreviewShift; // shift to convert orig image coords into preview image coords int mnPreviewMask; // == ((1 << mnPreviewShift) - 1) sal_uInt16 mnTargetDepth; // pixel depth of target bitmap sal_uInt8 mnTransRed; sal_uInt8 mnTransGreen; sal_uInt8 mnTransBlue; sal_uInt8 mnPngDepth; // pixel depth of PNG data sal_uInt8 mnColorType; sal_uInt8 mnCompressionType; sal_uInt8 mnFilterType; sal_uInt8 mnInterlaceType; const BitmapColor mcTranspColor; // transparency mask's transparency "color" const BitmapColor mcOpaqueColor; // transparency mask's opaque "color" bool mbTransparent : 1; // graphic includes a tRNS Chunk or an alpha Channel bool mbAlphaChannel : 1; // is true for ColorType 4 and 6 bool mbRGBTriple : 1; bool mbPalette : 1; // false if we need a Palette bool mbGrayScale : 1; bool mbzCodecInUse : 1; bool mbStatus : 1; bool mbIDATStarted : 1; // true if IDAT seen bool mbIDATComplete : 1; // true if finished with enough IDAT chunks bool mbpHYs : 1; // true if physical size of pixel available bool mbIgnoreGammaChunk : 1; bool const mbIgnoreCRC : 1; // skip checking CRCs while fuzzing #if OSL_DEBUG_LEVEL > 0 // do some checks in debug mode sal_Int32 mnAllocSizeScanline; sal_Int32 mnAllocSizeScanlineAlpha; #endif // the temporary Scanline (and alpha) for direct scanline copy to Bitmap std::unique_ptr mpScanline; std::unique_ptr mpScanlineAlpha; bool ReadNextChunk(); void ReadRemainingChunks(); void ImplSetPixel( sal_uInt32 y, sal_uInt32 x, const BitmapColor & ); void ImplSetPixel( sal_uInt32 y, sal_uInt32 x, sal_uInt8 nPalIndex ); void ImplSetTranspPixel( sal_uInt32 y, sal_uInt32 x, const BitmapColor &, bool bTrans ); void ImplSetAlphaPixel( sal_uInt32 y, sal_uInt32 x, sal_uInt8 nPalIndex, sal_uInt8 nAlpha ); void ImplSetAlphaPixel( sal_uInt32 y, sal_uInt32 x, const BitmapColor&, sal_uInt8 nAlpha ); void ImplReadIDAT(); bool ImplPreparePass(); void ImplApplyFilter(); void ImplDrawScanline( sal_uInt32 nXStart, sal_uInt32 nXAdd ); bool ImplReadTransparent(); void ImplGetGamma(); void ImplGetBackground(); sal_uInt8 ImplScaleColor(); bool ImplReadHeader( const Size& rPreviewSizeHint ); bool ImplReadPalette(); void ImplGetGrayPalette( sal_uInt16 ); sal_uInt32 ImplReadsal_uInt32(); public: explicit PNGReaderImpl( SvStream& ); ~PNGReaderImpl(); BitmapEx GetBitmapEx( const Size& rPreviewSizeHint ); const std::vector& GetAllChunks(); void SetIgnoreGammaChunk( bool bIgnore ){ mbIgnoreGammaChunk = bIgnore; }; }; PNGReaderImpl::PNGReaderImpl( SvStream& rPNGStream ) : mrPNGStream( rPNGStream ), mpMaskAcc ( nullptr ), mpScanCurrent ( nullptr ), mpColorTable ( const_cast(mpDefaultColorTable) ), mnChunkType ( 0 ), mnChunkLen ( 0 ), mnBPP ( 0 ), mnScansize ( 0 ), mnYpos ( 0 ), mnPass ( 0 ), mnXStart ( 0 ), mnXAdd ( 0 ), mnYAdd ( 0 ), mnTargetDepth ( 0 ), mnTransRed ( 0 ), mnTransGreen ( 0 ), mnTransBlue ( 0 ), mnPngDepth ( 0 ), mnColorType ( 0 ), mnCompressionType( 0 ), mnFilterType ( 0 ), mnInterlaceType ( 0 ), mcTranspColor ( BitmapColor( 0xFF )), mcOpaqueColor ( BitmapColor( 0x00 )), mbTransparent( false ), mbAlphaChannel( false ), mbRGBTriple( false ), mbPalette( false ), mbGrayScale( false ), mbzCodecInUse ( false ), mbStatus( true ), mbIDATStarted( false ), mbIDATComplete( false ), mbpHYs ( false ), mbIgnoreGammaChunk ( false ), mbIgnoreCRC( utl::ConfigManager::IsFuzzing() ) #if OSL_DEBUG_LEVEL > 0 ,mnAllocSizeScanline(0), mnAllocSizeScanlineAlpha(0) #endif { // prepare the PNG data stream mnOrigStreamMode = mrPNGStream.GetEndian(); mrPNGStream.SetEndian( SvStreamEndian::BIG ); // prepare the chunk reader maChunkSeq.reserve( 16 ); maChunkIter = maChunkSeq.begin(); // estimate PNG file size (to allow sanity checks) mnStreamSize = mrPNGStream.TellEnd(); // check the PNG header magic sal_uInt32 nDummy = 0; mrPNGStream.ReadUInt32( nDummy ); mbStatus = (nDummy == 0x89504e47); mrPNGStream.ReadUInt32( nDummy ); mbStatus = (nDummy == 0x0d0a1a0a) && mbStatus; mnPreviewShift = 0; mnPreviewMask = (1 << mnPreviewShift) - 1; } PNGReaderImpl::~PNGReaderImpl() { mrPNGStream.SetEndian( mnOrigStreamMode ); if ( mbzCodecInUse ) mpZCodec.EndCompression(); if( mpColorTable != mpDefaultColorTable ) delete[] mpColorTable; } bool PNGReaderImpl::ReadNextChunk() { if( maChunkIter == maChunkSeq.end() ) { // get the next chunk from the stream // unless we are at the end of the PNG stream if (!mrPNGStream.good()) return false; if( !maChunkSeq.empty() && (maChunkSeq.back().nType == PNGCHUNK_IEND) ) return false; PNGReader::ChunkData aDummyChunk; maChunkIter = maChunkSeq.insert( maChunkSeq.end(), aDummyChunk ); PNGReader::ChunkData& rChunkData = *maChunkIter; // read the chunk header mnChunkLen = 0; mnChunkType = 0; mrPNGStream.ReadInt32( mnChunkLen ).ReadUInt32( mnChunkType ); rChunkData.nType = mnChunkType; // fdo#61847 truncate over-long, trailing chunks const std::size_t nStreamPos = mrPNGStream.Tell(); if( mnChunkLen < 0 || nStreamPos + mnChunkLen >= mnStreamSize ) mnChunkLen = mnStreamSize - nStreamPos; // calculate chunktype CRC (swap it back to original byte order) sal_uInt32 nChunkType = mnChunkType; #if defined(__LITTLEENDIAN) || defined(OSL_LITENDIAN) nChunkType = OSL_SWAPDWORD( nChunkType ); #endif sal_uInt32 nCRC32 = rtl_crc32( 0, &nChunkType, 4 ); // read the chunk data and check the CRC if( mnChunkLen && !mrPNGStream.eof() ) { rChunkData.aData.resize( mnChunkLen ); sal_Int32 nBytesRead = 0; do { sal_uInt8& rPtr = rChunkData.aData[nBytesRead]; nBytesRead += mrPNGStream.ReadBytes(&rPtr, mnChunkLen - nBytesRead); } while (nBytesRead < mnChunkLen && mrPNGStream.good()); nCRC32 = rtl_crc32( nCRC32, rChunkData.aData.data(), mnChunkLen ); maDataIter = rChunkData.aData.begin(); } sal_uInt32 nCheck(0); mrPNGStream.ReadUInt32( nCheck ); if (!mbIgnoreCRC && nCRC32 != nCheck) return false; } else { // the next chunk was already read mnChunkType = (*maChunkIter).nType; mnChunkLen = (*maChunkIter).aData.size(); maDataIter = (*maChunkIter).aData.begin(); } ++maChunkIter; return mnChunkType != PNGCHUNK_IEND; } // read the remaining chunks from mrPNGStream void PNGReaderImpl::ReadRemainingChunks() { while( ReadNextChunk() ) ; } const std::vector< vcl::PNGReader::ChunkData >& PNGReaderImpl::GetAllChunks() { ReadRemainingChunks(); return maChunkSeq; } BitmapEx PNGReaderImpl::GetBitmapEx( const Size& rPreviewSizeHint ) { // reset to the first chunk maChunkIter = maChunkSeq.begin(); // first chunk must be IDHR if( mbStatus && ReadNextChunk() ) { if (mnChunkType == PNGCHUNK_IHDR) mbStatus = ImplReadHeader( rPreviewSizeHint ); else mbStatus = false; } // parse the remaining chunks while (mbStatus && !mbIDATComplete && ReadNextChunk()) { switch( mnChunkType ) { case PNGCHUNK_IHDR : { mbStatus = false; //IHDR should only appear as the first chunk } break; case PNGCHUNK_gAMA : // the gamma chunk must precede { // the 'IDAT' and also the 'PLTE'(if available ) if (!mbIgnoreGammaChunk && !mbIDATComplete) ImplGetGamma(); } break; case PNGCHUNK_PLTE : { if (!mbPalette && !mbIDATStarted) mbStatus = ImplReadPalette(); } break; case PNGCHUNK_tRNS : { if (!mbIDATComplete) // the tRNS chunk must precede the IDAT mbStatus = ImplReadTransparent(); } break; case PNGCHUNK_bKGD : // the background chunk must appear { if (!mbIDATComplete && mbPalette) // before the 'IDAT' and after the ImplGetBackground(); // PLTE(if available ) chunk. } break; case PNGCHUNK_IDAT : { if ( !mpInflateInBuf ) // taking care that the header has properly been read mbStatus = false; else if (!mbIDATComplete) // the gfx is finished, but there may be left a zlibCRC of about 4Bytes ImplReadIDAT(); } break; case PNGCHUNK_pHYs : { if (!mbIDATComplete && mnChunkLen == 9) { sal_uInt32 nXPixelPerMeter = ImplReadsal_uInt32(); sal_uInt32 nYPixelPerMeter = ImplReadsal_uInt32(); sal_uInt8 nUnitSpecifier = *maDataIter++; if( (nUnitSpecifier == 1) && nXPixelPerMeter && nYPixelPerMeter ) { mbpHYs = true; // convert into MapUnit::Map100thMM maPhysSize.setWidth( static_cast( (100000.0 * maOrigSize.Width()) / nXPixelPerMeter ) ); maPhysSize.setHeight( static_cast( (100000.0 * maOrigSize.Height()) / nYPixelPerMeter ) ); } } } break; case PNGCHUNK_IEND: mbStatus = mbIDATComplete; // there is a problem if the image is not complete yet break; } } // release write access of the bitmaps mxAcc.reset(); mxMaskAcc.reset(); mxAlphaAcc.reset(); mpMaskAcc = nullptr; // return the resulting BitmapEx BitmapEx aRet; if (!mbStatus || !mbIDATComplete) aRet.Clear(); else { if ( mpAlphaMask ) aRet = BitmapEx( *mpBmp, *mpAlphaMask ); else if ( mpMaskBmp ) aRet = BitmapEx( *mpBmp, *mpMaskBmp ); else aRet = *mpBmp; if ( mbpHYs && maPhysSize.Width() && maPhysSize.Height() ) { aRet.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); aRet.SetPrefSize( maPhysSize ); } } return aRet; } bool PNGReaderImpl::ImplReadHeader( const Size& rPreviewSizeHint ) { if( mnChunkLen < 13 ) return false; maOrigSize.setWidth( ImplReadsal_uInt32() ); maOrigSize.setHeight( ImplReadsal_uInt32() ); if (maOrigSize.Width() <= 0 || maOrigSize.Height() <= 0) return false; mnPngDepth = *(maDataIter++); mnColorType = *(maDataIter++); mnCompressionType = *(maDataIter++); if( mnCompressionType != 0 ) // unknown compression type return false; mnFilterType = *(maDataIter++); if( mnFilterType != 0 ) // unknown filter type return false; mnInterlaceType = *(maDataIter++); switch ( mnInterlaceType ) // filter type valid ? { case 0 : // progressive image mnPass = 7; break; case 1 : // Adam7-interlaced image mnPass = 0; break; default: return false; } mbPalette = true; mbIDATStarted = mbIDATComplete = mbAlphaChannel = mbTransparent = false; mbGrayScale = mbRGBTriple = false; mnTargetDepth = mnPngDepth; sal_uInt64 nScansize64 = ( ( static_cast< sal_uInt64 >( maOrigSize.Width() ) * mnPngDepth ) + 7 ) >> 3; // valid color types are 0,2,3,4 & 6 switch ( mnColorType ) { case 0 : // each pixel is a grayscale { switch ( mnPngDepth ) { case 2 : // 2bit target not available -> use four bits mnTargetDepth = 4; // we have to expand the bitmap mbGrayScale = true; break; case 16 : mnTargetDepth = 8; // we have to reduce the bitmap [[fallthrough]]; case 1 : case 4 : case 8 : mbGrayScale = true; break; default : return false; } } break; case 2 : // each pixel is an RGB triple { mbRGBTriple = true; nScansize64 *= 3; switch ( mnPngDepth ) { case 16 : // we have to reduce the bitmap case 8 : mnTargetDepth = 24; break; default : return false; } } break; case 3 : // each pixel is a palette index { switch ( mnPngDepth ) { case 2 : mnTargetDepth = 4; // we have to expand the bitmap mbPalette = false; break; case 1 : case 4 : case 8 : mbPalette = false; break; default : return false; } } break; case 4 : // each pixel is a grayscale sample followed by an alpha sample { nScansize64 *= 2; mbAlphaChannel = true; switch ( mnPngDepth ) { case 16 : mnTargetDepth = 8; // we have to reduce the bitmap [[fallthrough]]; case 8 : mbGrayScale = true; break; default : return false; } } break; case 6 : // each pixel is an RGB triple followed by an alpha sample { mbRGBTriple = true; nScansize64 *= 4; mbAlphaChannel = true; switch (mnPngDepth ) { case 16 : // we have to reduce the bitmap case 8 : mnTargetDepth = 24; break; default : return false; } } break; default : return false; } mnBPP = static_cast< sal_uInt32 >( nScansize64 / maOrigSize.Width() ); if ( !mnBPP ) mnBPP = 1; nScansize64++; // each scanline includes one filterbyte if ( nScansize64 > SAL_MAX_UINT32 ) return false; // assume max theoretical compression of 1:1032 sal_uInt64 nMinSizeRequired = (nScansize64 * maOrigSize.Height()) / 1032; if (nMinSizeRequired > mnStreamSize) { SAL_WARN("vcl.gdi", "overlarge png dimensions: " << maOrigSize.Width() << " x " << maOrigSize.Height() << " depth: " << static_cast(mnPngDepth) << " couldn't be supplied by file length " << mnStreamSize << " at least " << nMinSizeRequired << " needed "); return false; } mnScansize = static_cast< sal_uInt32 >( nScansize64 ); // calculate target size from original size and the preview hint if( rPreviewSizeHint.Width() || rPreviewSizeHint.Height() ) { Size aPreviewSize( rPreviewSizeHint.Width(), rPreviewSizeHint.Height() ); maTargetSize = maOrigSize; if( aPreviewSize.Width() == 0 ) { aPreviewSize.setWidth( ( maOrigSize.Width()*aPreviewSize.Height() )/maOrigSize.Height() ); if( aPreviewSize.Width() <= 0 ) aPreviewSize.setWidth( 1 ); } else if( aPreviewSize.Height() == 0 ) { aPreviewSize.setHeight( ( maOrigSize.Height()*aPreviewSize.Width() )/maOrigSize.Width() ); if( aPreviewSize.Height() <= 0 ) aPreviewSize.setHeight( 1 ); } if( aPreviewSize.Width() < maOrigSize.Width() && aPreviewSize.Height() < maOrigSize.Height() ) { SAL_INFO( "vcl.gdi", "preview size " << aPreviewSize.Width() << " " << aPreviewSize.Height() ); for( int i = 1; i < 5; ++i ) { if( (maTargetSize.Width() >> i) < aPreviewSize.Width() ) break; if( (maTargetSize.Height() >> i) < aPreviewSize.Height() ) break; mnPreviewShift = i; } mnPreviewMask = (1 << mnPreviewShift) - 1; } } maTargetSize.setWidth( (maOrigSize.Width() + mnPreviewMask) >> mnPreviewShift ); maTargetSize.setHeight( (maOrigSize.Height() + mnPreviewMask) >> mnPreviewShift ); //round bits up to nearest multiple of 8 and divide by 8 to get num of bytes per pixel int nBytesPerPixel = ((mnTargetDepth + 7) & ~7)/8; //stupidly big, forget about it if (maTargetSize.Width() >= SAL_MAX_INT32 / nBytesPerPixel / maTargetSize.Height()) { SAL_WARN( "vcl.gdi", "overlarge png dimensions: " << maTargetSize.Width() << " x " << maTargetSize.Height() << " depth: " << mnTargetDepth); return false; } // TODO: switch between both scanlines instead of copying mpInflateInBuf.reset( new (std::nothrow) sal_uInt8[ mnScansize ] ); mpScanCurrent = mpInflateInBuf.get(); mpScanPrior.reset( new (std::nothrow) sal_uInt8[ mnScansize ] ); if ( !mpInflateInBuf || !mpScanPrior ) return false; mpBmp = std::make_unique( maTargetSize, mnTargetDepth ); mxAcc = BitmapScopedWriteAccess(*mpBmp); if (!mxAcc) return false; if ( mbAlphaChannel ) { mpAlphaMask = std::make_unique( maTargetSize ); mpAlphaMask->Erase( 128 ); mxAlphaAcc = AlphaScopedWriteAccess(*mpAlphaMask); mpMaskAcc = mxAlphaAcc.get(); if (!mpMaskAcc) return false; } if ( mbGrayScale ) ImplGetGrayPalette( mnPngDepth ); ImplPreparePass(); return true; } void PNGReaderImpl::ImplGetGrayPalette( sal_uInt16 nBitDepth ) { if( nBitDepth > 8 ) nBitDepth = 8; sal_uInt16 nPaletteEntryCount = 1 << nBitDepth; sal_uInt32 nAdd = nBitDepth ? 256 / (nPaletteEntryCount - 1) : 0; // no bitdepth==2 available // but bitdepth==4 with two unused bits is close enough if( nBitDepth == 2 ) nPaletteEntryCount = 16; mxAcc->SetPaletteEntryCount( nPaletteEntryCount ); for ( sal_uInt32 i = 0, nStart = 0; nStart < 256; i++, nStart += nAdd ) mxAcc->SetPaletteColor( static_cast(i), BitmapColor( mpColorTable[ nStart ], mpColorTable[ nStart ], mpColorTable[ nStart ] ) ); } bool PNGReaderImpl::ImplReadPalette() { sal_uInt16 nCount = static_cast( mnChunkLen / 3 ); if ( ( ( mnChunkLen % 3 ) == 0 ) && ( ( 0 < nCount ) && ( nCount <= 256 ) ) && mxAcc ) { mbPalette = true; mxAcc->SetPaletteEntryCount( nCount ); for ( sal_uInt16 i = 0; i < nCount; i++ ) { sal_uInt8 nRed = mpColorTable[ *maDataIter++ ]; sal_uInt8 nGreen = mpColorTable[ *maDataIter++ ]; sal_uInt8 nBlue = mpColorTable[ *maDataIter++ ]; mxAcc->SetPaletteColor( i, Color( nRed, nGreen, nBlue ) ); } } else mbStatus = false; return mbStatus; } bool PNGReaderImpl::ImplReadTransparent() { bool bNeedAlpha = false; if ( mpTransTab == nullptr ) { switch ( mnColorType ) { case 0 : { if ( mnChunkLen == 2 ) { mpTransTab.reset( new sal_uInt8[ 256 ] ); memset( mpTransTab.get(), 0xff, 256); // color type 0 and 4 is always greyscale, // so the return value can be used as index sal_uInt8 nIndex = ImplScaleColor(); mpTransTab[ nIndex ] = 0; mbTransparent = true; } } break; case 2 : { if ( mnChunkLen == 6 ) { mnTransRed = ImplScaleColor(); mnTransGreen = ImplScaleColor(); mnTransBlue = ImplScaleColor(); mbTransparent = true; } } break; case 3 : { if ( mnChunkLen <= 256 ) { mbTransparent = true; mpTransTab.reset( new sal_uInt8 [ 256 ] ); memset( mpTransTab.get(), 0xff, 256 ); if (mnChunkLen > 0) { memcpy( mpTransTab.get(), &(*maDataIter), mnChunkLen ); maDataIter += mnChunkLen; // need alpha transparency if not on/off masking for( int i = 0; i < mnChunkLen; ++i ) bNeedAlpha |= (mpTransTab[i]!=0x00) && (mpTransTab[i]!=0xFF); } } } break; } } if( mbTransparent && !mbAlphaChannel && !mpMaskBmp ) { if( bNeedAlpha) { mpAlphaMask = std::make_unique( maTargetSize ); mxAlphaAcc = AlphaScopedWriteAccess(*mpAlphaMask); mpMaskAcc = mxAlphaAcc.get(); } else { mpMaskBmp = std::make_unique( maTargetSize, 1 ); mxMaskAcc = BitmapScopedWriteAccess(*mpMaskBmp); mpMaskAcc = mxMaskAcc.get(); } mbTransparent = (mpMaskAcc != nullptr); if( !mbTransparent ) return false; mpMaskAcc->Erase( Color(0,0,0) ); } return true; } void PNGReaderImpl::ImplGetGamma() { if( mnChunkLen < 4 ) return; sal_uInt32 nGammaValue = ImplReadsal_uInt32(); double fGamma = ( VIEWING_GAMMA / DISPLAY_GAMMA ) * ( static_cast(nGammaValue) / 100000 ); double fInvGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma ); if ( fInvGamma != 1.0 ) { if ( mpColorTable == mpDefaultColorTable ) mpColorTable = new sal_uInt8[ 256 ]; for ( sal_Int32 i = 0; i < 256; i++ ) mpColorTable[ i ] = static_cast(pow(static_cast(i)/255.0, fInvGamma) * 255.0 + 0.5); if ( mbGrayScale ) ImplGetGrayPalette( mnPngDepth ); } } void PNGReaderImpl::ImplGetBackground() { switch (mnColorType) { case 3: { if (mnChunkLen == 1) { sal_uInt16 nCol = *maDataIter++; if (nCol < mxAcc->GetPaletteEntryCount()) { mxAcc->Erase(mxAcc->GetPaletteColor(static_cast(nCol))); break; } } } break; case 0: case 4: { if (mnChunkLen == 2) { // the color type 0 and 4 is always greyscale, // so the return value can be used as index mxAcc->Erase(mxAcc->GetPaletteColor(ImplScaleColor())); } } break; case 2: case 6: { if (mnChunkLen == 6) { sal_uInt8 nRed = ImplScaleColor(); sal_uInt8 nGreen = ImplScaleColor(); sal_uInt8 nBlue = ImplScaleColor(); // ofz#18653 slow and uninteresting if (!utl::ConfigManager::IsFuzzing()) return; mxAcc->Erase(Color(nRed, nGreen, nBlue)); } } break; } } // for color type 0 and 4 (greyscale) the return value is always index to the color // 2 and 6 (RGB) the return value is always the 8 bit color component sal_uInt8 PNGReaderImpl::ImplScaleColor() { sal_uInt32 nMask = ( 1 << mnPngDepth ) - 1; sal_uInt16 nCol = ( *maDataIter++ << 8 ); nCol += *maDataIter++ & static_cast(nMask); if ( mnPngDepth > 8 ) // convert 16bit graphics to 8 nCol >>= 8; return static_cast(nCol); } // ImplReadIDAT reads as much image data as needed void PNGReaderImpl::ImplReadIDAT() { //when fuzzing with a max len set, max decompress to 250 times that limit static size_t nMaxAllowedDecompression = [](const char* pEnv) { size_t nRet = pEnv ? std::atoi(pEnv) : 0; return nRet * 250; }(std::getenv("FUZZ_MAX_INPUT_LEN")); size_t nTotalDataRead = 0; if( mnChunkLen > 0 ) { mbIDATStarted = true; if ( !mbzCodecInUse ) { mbzCodecInUse = true; mpZCodec.BeginCompression( ZCODEC_NO_COMPRESSION ); } mpZCodec.SetBreak( mnChunkLen ); SvMemoryStream aIStrm( &(*maDataIter), mnChunkLen, StreamMode::READ ); while ( mpZCodec.GetBreak() ) { // get bytes needed to fill the current scanline sal_Int32 nToRead = mnScansize - (mpScanCurrent - mpInflateInBuf.get()); sal_Int32 nRead = mpZCodec.ReadAsynchron( aIStrm, mpScanCurrent, nToRead ); if ( nRead < 0 ) { mbStatus = false; break; } nTotalDataRead += nRead; if (nMaxAllowedDecompression && nTotalDataRead > nMaxAllowedDecompression) { mbStatus = false; break; } if ( nRead < nToRead ) { mpScanCurrent += nRead; // more ZStream data in the next IDAT chunk break; } else // this scanline is Finished { mpScanCurrent = mpInflateInBuf.get(); ImplApplyFilter(); ImplDrawScanline( mnXStart, mnXAdd ); mnYpos += mnYAdd; } if ( mnYpos >= static_cast(maOrigSize.Height()) ) { if( (mnPass < 7) && mnInterlaceType ) if( ImplPreparePass() ) continue; mbIDATComplete = true; break; } } } if (mbIDATComplete) { mpZCodec.EndCompression(); mbzCodecInUse = false; } } bool PNGReaderImpl::ImplPreparePass() { struct InterlaceParams{ int mnXStart, mnYStart, mnXAdd, mnYAdd; }; static const InterlaceParams aInterlaceParams[8] = { // non-interlaced { 0, 0, 1, 1 }, // Adam7-interlaced { 0, 0, 8, 8 }, // pass 1 { 4, 0, 8, 8 }, // pass 2 { 0, 4, 4, 8 }, // pass 3 { 2, 0, 4, 4 }, // pass 4 { 0, 2, 2, 4 }, // pass 5 { 1, 0, 2, 2 }, // pass 6 { 0, 1, 1, 2 } // pass 7 }; const InterlaceParams* pParam = &aInterlaceParams[ 0 ]; if( mnInterlaceType ) { while( ++mnPass <= 7 ) { pParam = &aInterlaceParams[ mnPass ]; // skip this pass if the original image is too small for it if( (pParam->mnXStart < maOrigSize.Width()) && (pParam->mnYStart < maOrigSize.Height()) ) break; } if( mnPass > 7 ) return false; // skip the last passes if possible (for scaled down target images) if( mnPreviewMask & (pParam->mnXStart | pParam->mnYStart) ) return false; } mnYpos = pParam->mnYStart; mnXStart = pParam->mnXStart; mnXAdd = pParam->mnXAdd; mnYAdd = pParam->mnYAdd; // in Interlace mode the size of scanline is not constant // so first we calculate the number of entries long nScanWidth = (maOrigSize.Width() - mnXStart + mnXAdd - 1) / mnXAdd; mnScansize = nScanWidth; if( mbRGBTriple ) mnScansize = 3 * nScanWidth; if( mbAlphaChannel ) mnScansize += nScanWidth; // convert to width in bytes mnScansize = ( mnScansize*mnPngDepth + 7 ) >> 3; ++mnScansize; // scan size also needs room for the filtertype byte memset( mpScanPrior.get(), 0, mnScansize ); return true; } // ImplApplyFilter writes the complete Scanline (nY) // in interlace mode the parameter nXStart and nXAdd are non-zero void PNGReaderImpl::ImplApplyFilter() { OSL_ASSERT( mnScansize >= mnBPP + 1 ); const sal_uInt8* const pScanEnd = mpInflateInBuf.get() + mnScansize; sal_uInt8 nFilterType = mpInflateInBuf[0]; // the filter type may change each scanline switch ( nFilterType ) { default: // unknown Scanline Filter Type case 0: // Filter Type "None" // we let the pixels pass and display the data unfiltered break; case 1: // Scanline Filter Type "Sub" { sal_uInt8* p1 = mpInflateInBuf.get() + 1; const sal_uInt8* p2 = p1; p1 += mnBPP; // use left pixels while (p1 < pScanEnd) { *p1 = static_cast( *p1 + *(p2++) ); ++p1; } } break; case 2: // Scanline Filter Type "Up" { sal_uInt8* p1 = mpInflateInBuf.get() + 1; const sal_uInt8* p2 = mpScanPrior.get() + 1; // use pixels from prior line while( p1 < pScanEnd ) { *p1 = static_cast( *p1 + *(p2++) ); ++p1; } } break; case 3: // Scanline Filter Type "Average" { sal_uInt8* p1 = mpInflateInBuf.get() + 1; const sal_uInt8* p2 = mpScanPrior.get() + 1; const sal_uInt8* p3 = p1; // use one pixel from prior line for( int n = mnBPP; --n >= 0; ++p1, ++p2) *p1 = static_cast( *p1 + (*p2 >> 1) ); // predict by averaging the left and prior line pixels while( p1 < pScanEnd ) { *p1 = static_cast( *p1 + ((*(p2++) + *(p3++)) >> 1) ); ++p1; } } break; case 4: // Scanline Filter Type "PathPredictor" { sal_uInt8* p1 = mpInflateInBuf.get() + 1; const sal_uInt8* p2 = mpScanPrior.get() + 1; const sal_uInt8* p3 = p1; const sal_uInt8* p4 = p2; // use one pixel from prior line for( int n = mnBPP; --n >= 0; ++p1) *p1 = static_cast( *p1 + *(p2++) ); // predict by using the left and the prior line pixels while( p1 < pScanEnd ) { int na = *(p2++); int nb = *(p3++); int nc = *(p4++); int npa = nb - nc; int npb = na - nc; int npc = npa + npb; if( npa < 0 ) npa =-npa; if( npb < 0 ) npb =-npb; if( npc < 0 ) npc =-npc; if( npa > npb ) { na = nb; npa = npb; } if( npa > npc ) na = nc; *p1 = static_cast( *p1 + na ); ++p1; } } break; } memcpy( mpScanPrior.get(), mpInflateInBuf.get(), mnScansize ); } namespace { sal_uInt8 SanitizePaletteIndex(sal_uInt8 nIndex, sal_uInt16 nPaletteEntryCount) { if (nIndex >= nPaletteEntryCount) { auto nSanitizedIndex = nIndex % nPaletteEntryCount; SAL_WARN_IF(nIndex != nSanitizedIndex, "vcl", "invalid colormap index: " << static_cast(nIndex) << ", colormap len is: " << nPaletteEntryCount); nIndex = nSanitizedIndex; } return nIndex; } void SanitizePaletteIndexes(sal_uInt8* pEntries, int nLen, const BitmapScopedWriteAccess& rAcc) { sal_uInt16 nPaletteEntryCount = rAcc->GetPaletteEntryCount(); for (int nX = 0; nX < nLen; ++nX) { if (pEntries[nX] >= nPaletteEntryCount) { SAL_WARN("vcl.gdi", "invalid colormap index: " << static_cast(pEntries[nX]) << ", colormap len is: " << nPaletteEntryCount); pEntries[nX] = pEntries[nX] % nPaletteEntryCount; } } } } // ImplDrawScanlines draws the complete Scanline (nY) into the target bitmap // In interlace mode the parameter nXStart and nXAdd append to the currently used pass void PNGReaderImpl::ImplDrawScanline( sal_uInt32 nXStart, sal_uInt32 nXAdd ) { // optimization for downscaling if( mnYpos & mnPreviewMask ) return; if( nXStart & mnPreviewMask ) return; // convert nY to pixel units in the target image // => TODO; also do this for nX here instead of in the ImplSet*Pixel() methods const sal_uInt32 nY = mnYpos >> mnPreviewShift; sal_uInt8* pTmp = mpInflateInBuf.get() + 1; if ( mxAcc->HasPalette() ) // alphachannel is not allowed by pictures including palette entries { switch ( mxAcc->GetBitCount() ) { case 1 : { if ( mbTransparent ) { for ( long nX = nXStart, nShift = 0; nX < maOrigSize.Width(); nX += nXAdd ) { sal_uInt8 nCol; nShift = (nShift - 1) & 7; if ( nShift == 0 ) nCol = *(pTmp++); else nCol = static_cast( *pTmp >> nShift ); nCol &= 1; ImplSetAlphaPixel( nY, nX, nCol, mpTransTab[ nCol ] ); } } else { // ScanlineFormat::N1BitMsbPal for ( long nX = nXStart, nShift = 0; nX < maOrigSize.Width(); nX += nXAdd ) { nShift = (nShift - 1) & 7; sal_uInt8 nCol; if ( nShift == 0 ) nCol = *(pTmp++); else nCol = static_cast( *pTmp >> nShift ); nCol &= 1; ImplSetPixel( nY, nX, nCol ); } } } break; case 4 : { if ( mbTransparent ) { if ( mnPngDepth == 4 ) // check if source has a two bit pixel format { for ( long nX = nXStart, nXIndex = 0; nX < maOrigSize.Width(); nX += nXAdd, ++nXIndex ) { if( nXIndex & 1 ) { ImplSetAlphaPixel( nY, nX, *pTmp & 0x0f, mpTransTab[ *pTmp & 0x0f ] ); pTmp++; } else { ImplSetAlphaPixel( nY, nX, ( *pTmp >> 4 ) & 0x0f, mpTransTab[ *pTmp >> 4 ] ); } } } else // if ( mnPngDepth == 2 ) { for ( long nX = nXStart, nXIndex = 0; nX < maOrigSize.Width(); nX += nXAdd, nXIndex++ ) { sal_uInt8 nCol; switch( nXIndex & 3 ) { case 0 : nCol = *pTmp >> 6; break; case 1 : nCol = ( *pTmp >> 4 ) & 0x03 ; break; case 2 : nCol = ( *pTmp >> 2 ) & 0x03; break; case 3 : nCol = ( *pTmp++ ) & 0x03; break; default: // get rid of nCol uninitialized warning nCol = 0; break; } ImplSetAlphaPixel( nY, nX, nCol, mpTransTab[ nCol ] ); } } } else { if ( mnPngDepth == 4 ) // maybe the source is a two bitmap graphic { // ScanlineFormat::N4BitLsnPal for ( long nX = nXStart, nXIndex = 0; nX < maOrigSize.Width(); nX += nXAdd, nXIndex++ ) { if( nXIndex & 1 ) ImplSetPixel( nY, nX, *pTmp++ & 0x0f ); else ImplSetPixel( nY, nX, ( *pTmp >> 4 ) & 0x0f ); } } else // if ( mnPngDepth == 2 ) { for ( long nX = nXStart, nXIndex = 0; nX < maOrigSize.Width(); nX += nXAdd, nXIndex++ ) { switch( nXIndex & 3 ) { case 0 : ImplSetPixel( nY, nX, *pTmp >> 6 ); break; case 1 : ImplSetPixel( nY, nX, ( *pTmp >> 4 ) & 0x03 ); break; case 2 : ImplSetPixel( nY, nX, ( *pTmp >> 2 ) & 0x03 ); break; case 3 : ImplSetPixel( nY, nX, *pTmp++ & 0x03 ); break; } } } } } break; case 8 : { if ( mbAlphaChannel ) { if ( mnPngDepth == 8 ) // maybe the source is a 16 bit grayscale { for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 2 ) ImplSetAlphaPixel( nY, nX, pTmp[ 0 ], pTmp[ 1 ] ); } else { assert(mnPngDepth == 16); for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 4 ) ImplSetAlphaPixel( nY, nX, pTmp[ 0 ], pTmp[ 2 ] ); } } else if ( mbTransparent ) { if ( mnPngDepth == 8 ) // maybe the source is a 16 bit grayscale { for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp++ ) ImplSetAlphaPixel( nY, nX, *pTmp, mpTransTab[ *pTmp ] ); } else if (mnPngDepth == 1 ) { for ( long nX = nXStart, nShift = 0; nX < maOrigSize.Width(); nX += nXAdd ) { nShift = (nShift - 1) & 7; sal_uInt8 nCol; if ( nShift == 0 ) nCol = *(pTmp++); else nCol = static_cast( *pTmp >> nShift ); nCol &= 1; ImplSetAlphaPixel( nY, nX, nCol, mpTransTab[ nCol ] ); } } else { for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 2 ) ImplSetAlphaPixel( nY, nX, *pTmp, mpTransTab[ *pTmp ] ); } } else // neither alpha nor transparency { if ( mnPngDepth == 8 ) // maybe the source is a 16 bit grayscale or 1 bit indexed { if( nXAdd == 1 && mnPreviewShift == 0 ) // copy raw line data if possible { int nLineBytes = maOrigSize.Width(); if (mbPalette) SanitizePaletteIndexes(pTmp, nLineBytes, mxAcc); mxAcc->CopyScanline( nY, pTmp, ScanlineFormat::N8BitPal, nLineBytes ); } else { for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd ) ImplSetPixel( nY, nX, *pTmp++ ); } } else if (mnPngDepth == 1 ) { for ( long nX = nXStart, nShift = 0; nX < maOrigSize.Width(); nX += nXAdd ) { nShift = (nShift - 1) & 7; sal_uInt8 nCol; if ( nShift == 0 ) nCol = *(pTmp++); else nCol = static_cast( *pTmp >> nShift ); nCol &= 1; ImplSetPixel( nY, nX, nCol ); } } else { for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 2 ) ImplSetPixel( nY, nX, *pTmp ); } } } break; default : mbStatus = false; break; } } else // no palette => truecolor { if( mbAlphaChannel ) { // has RGB + alpha if ( mnPngDepth == 8 ) // maybe the source has 16 bit per sample { // ScanlineFormat::N32BitTcRgba // only use DirectScanline when we have no preview shifting stuff and accesses to content and alpha const bool bDoDirectScanline( !nXStart && 1 == nXAdd && !mnPreviewShift && mpMaskAcc); const bool bCustomColorTable(mpColorTable != mpDefaultColorTable); if(bDoDirectScanline) { // allocate scanlines on demand, reused for next line if(!mpScanline) { #if OSL_DEBUG_LEVEL > 0 mnAllocSizeScanline = maOrigSize.Width() * 3; #endif mpScanline.reset( new sal_uInt8[maOrigSize.Width() * 3] ); } if(!mpScanlineAlpha) { #if OSL_DEBUG_LEVEL > 0 mnAllocSizeScanlineAlpha = maOrigSize.Width(); #endif mpScanlineAlpha.reset( new sal_uInt8[maOrigSize.Width()] ); } } if(bDoDirectScanline) { OSL_ENSURE(mpScanline, "No Scanline allocated (!)"); OSL_ENSURE(mpScanlineAlpha, "No ScanlineAlpha allocated (!)"); #if OSL_DEBUG_LEVEL > 0 OSL_ENSURE(mnAllocSizeScanline >= maOrigSize.Width() * 3, "Allocated Scanline too small (!)"); OSL_ENSURE(mnAllocSizeScanlineAlpha >= maOrigSize.Width(), "Allocated ScanlineAlpha too small (!)"); #endif sal_uInt8* pScanline(mpScanline.get()); sal_uInt8* pScanlineAlpha(mpScanlineAlpha.get()); for (long nX(0); nX < maOrigSize.Width(); nX++, pTmp += 4) { // prepare content line as BGR by reordering when copying // do not forget to invert alpha (source is alpha, target is opacity) if(bCustomColorTable) { *pScanline++ = mpColorTable[pTmp[2]]; *pScanline++ = mpColorTable[pTmp[1]]; *pScanline++ = mpColorTable[pTmp[0]]; *pScanlineAlpha++ = ~pTmp[3]; } else { *pScanline++ = pTmp[2]; *pScanline++ = pTmp[1]; *pScanline++ = pTmp[0]; *pScanlineAlpha++ = ~pTmp[3]; } } // copy scanlines directly to bitmaps for content and alpha; use the formats which // are able to copy directly to BitmapBuffer mxAcc->CopyScanline(nY, mpScanline.get(), ScanlineFormat::N24BitTcBgr, maOrigSize.Width() * 3); mpMaskAcc->CopyScanline(nY, mpScanlineAlpha.get(), ScanlineFormat::N8BitPal, maOrigSize.Width()); } else { for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 4 ) { if(bCustomColorTable) { ImplSetAlphaPixel( nY, nX, BitmapColor( mpColorTable[ pTmp[ 0 ] ], mpColorTable[ pTmp[ 1 ] ], mpColorTable[ pTmp[ 2 ] ]), pTmp[ 3 ]); } else { ImplSetAlphaPixel( nY, nX, BitmapColor( pTmp[0], pTmp[1], pTmp[2]), pTmp[3]); } } } } else { // BMP_FORMAT_64BIT_TC_RGBA for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 8 ) { ImplSetAlphaPixel( nY, nX, BitmapColor( mpColorTable[ pTmp[ 0 ] ], mpColorTable[ pTmp[ 2 ] ], mpColorTable[ pTmp[ 4 ] ]), pTmp[6]); } } } else if( mbTransparent ) // has RGB + transparency { // ScanlineFormat::N24BitTcRgb // no support currently for DirectScanline, found no real usages in current PNGs, may be added on demand if ( mnPngDepth == 8 ) // maybe the source has 16 bit per sample { for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 3 ) { sal_uInt8 nRed = pTmp[ 0 ]; sal_uInt8 nGreen = pTmp[ 1 ]; sal_uInt8 nBlue = pTmp[ 2 ]; bool bTransparent = ( ( nRed == mnTransRed ) && ( nGreen == mnTransGreen ) && ( nBlue == mnTransBlue ) ); ImplSetTranspPixel( nY, nX, BitmapColor( mpColorTable[ nRed ], mpColorTable[ nGreen ], mpColorTable[ nBlue ] ), bTransparent ); } } else { // BMP_FORMAT_48BIT_TC_RGB for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 6 ) { sal_uInt8 nRed = pTmp[ 0 ]; sal_uInt8 nGreen = pTmp[ 2 ]; sal_uInt8 nBlue = pTmp[ 4 ]; bool bTransparent = ( ( nRed == mnTransRed ) && ( nGreen == mnTransGreen ) && ( nBlue == mnTransBlue ) ); ImplSetTranspPixel( nY, nX, BitmapColor( mpColorTable[ nRed ], mpColorTable[ nGreen ], mpColorTable[ nBlue ] ), bTransparent ); } } } else // has RGB but neither alpha nor transparency { // ScanlineFormat::N24BitTcRgb // only use DirectScanline when we have no preview shifting stuff and access to content const bool bDoDirectScanline( !nXStart && 1 == nXAdd && !mnPreviewShift); const bool bCustomColorTable(mpColorTable != mpDefaultColorTable); if(bDoDirectScanline && !mpScanline) { // allocate scanlines on demand, reused for next line #if OSL_DEBUG_LEVEL > 0 mnAllocSizeScanline = maOrigSize.Width() * 3; #endif mpScanline.reset( new sal_uInt8[maOrigSize.Width() * 3] ); } if ( mnPngDepth == 8 ) // maybe the source has 16 bit per sample { if(bDoDirectScanline) { OSL_ENSURE(mpScanline, "No Scanline allocated (!)"); #if OSL_DEBUG_LEVEL > 0 OSL_ENSURE(mnAllocSizeScanline >= maOrigSize.Width() * 3, "Allocated Scanline too small (!)"); #endif sal_uInt8* pScanline(mpScanline.get()); for (long nX(0); nX < maOrigSize.Width(); nX++, pTmp += 3) { // prepare content line as BGR by reordering when copying if(bCustomColorTable) { *pScanline++ = mpColorTable[pTmp[2]]; *pScanline++ = mpColorTable[pTmp[1]]; *pScanline++ = mpColorTable[pTmp[0]]; } else { *pScanline++ = pTmp[2]; *pScanline++ = pTmp[1]; *pScanline++ = pTmp[0]; } } // copy scanline directly to bitmap for content; use the format which is able to // copy directly to BitmapBuffer mxAcc->CopyScanline(nY, mpScanline.get(), ScanlineFormat::N24BitTcBgr, maOrigSize.Width() * 3); } else { for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 3 ) { if(bCustomColorTable) { ImplSetPixel( nY, nX, BitmapColor( mpColorTable[ pTmp[ 0 ] ], mpColorTable[ pTmp[ 1 ] ], mpColorTable[ pTmp[ 2 ] ])); } else { ImplSetPixel( nY, nX, BitmapColor( pTmp[0], pTmp[1], pTmp[2])); } } } } else { // BMP_FORMAT_48BIT_TC_RGB // no support currently for DirectScanline, found no real usages in current PNGs, may be added on demand for ( long nX = nXStart; nX < maOrigSize.Width(); nX += nXAdd, pTmp += 6 ) { ImplSetPixel( nY, nX, BitmapColor( mpColorTable[ pTmp[ 0 ] ], mpColorTable[ pTmp[ 2 ] ], mpColorTable[ pTmp[ 4 ] ])); } } } } } void PNGReaderImpl::ImplSetPixel( sal_uInt32 nY, sal_uInt32 nX, const BitmapColor& rBitmapColor ) { // TODO: get preview mode checks out of inner loop if( nX & mnPreviewMask ) return; nX >>= mnPreviewShift; mxAcc->SetPixel( nY, nX, rBitmapColor ); } void PNGReaderImpl::ImplSetPixel( sal_uInt32 nY, sal_uInt32 nX, sal_uInt8 nPalIndex ) { // TODO: get preview mode checks out of inner loop if( nX & mnPreviewMask ) return; nX >>= mnPreviewShift; mxAcc->SetPixelIndex(nY, nX, SanitizePaletteIndex(nPalIndex, mxAcc->GetPaletteEntryCount())); } void PNGReaderImpl::ImplSetTranspPixel( sal_uInt32 nY, sal_uInt32 nX, const BitmapColor& rBitmapColor, bool bTrans ) { // TODO: get preview mode checks out of inner loop if( nX & mnPreviewMask ) return; nX >>= mnPreviewShift; mxAcc->SetPixel( nY, nX, rBitmapColor ); if ( bTrans ) mpMaskAcc->SetPixel( nY, nX, mcTranspColor ); else mpMaskAcc->SetPixel( nY, nX, mcOpaqueColor ); } void PNGReaderImpl::ImplSetAlphaPixel( sal_uInt32 nY, sal_uInt32 nX, sal_uInt8 nPalIndex, sal_uInt8 nAlpha ) { // TODO: get preview mode checks out of inner loop if( nX & mnPreviewMask ) return; nX >>= mnPreviewShift; mxAcc->SetPixelIndex(nY, nX, SanitizePaletteIndex(nPalIndex, mxAcc->GetPaletteEntryCount())); mpMaskAcc->SetPixel(nY, nX, BitmapColor(~nAlpha)); } void PNGReaderImpl::ImplSetAlphaPixel( sal_uInt32 nY, sal_uInt32 nX, const BitmapColor& rBitmapColor, sal_uInt8 nAlpha ) { // TODO: get preview mode checks out of inner loop if( nX & mnPreviewMask ) return; nX >>= mnPreviewShift; mxAcc->SetPixel( nY, nX, rBitmapColor ); if (!mpMaskAcc) return; mpMaskAcc->SetPixel(nY, nX, BitmapColor(~nAlpha)); } sal_uInt32 PNGReaderImpl::ImplReadsal_uInt32() { sal_uInt32 nRet; nRet = *maDataIter++; nRet <<= 8; nRet |= *maDataIter++; nRet <<= 8; nRet |= *maDataIter++; nRet <<= 8; nRet |= *maDataIter++; return nRet; } PNGReader::PNGReader(SvStream& rIStream) : mpImpl(new vcl::PNGReaderImpl(rIStream)) { } PNGReader::~PNGReader() { } BitmapEx PNGReader::Read( const Size& i_rPreviewSizeHint ) { return mpImpl->GetBitmapEx( i_rPreviewSizeHint ); } const std::vector< vcl::PNGReader::ChunkData >& PNGReader::GetChunks() const { return mpImpl->GetAllChunks(); } void PNGReader::SetIgnoreGammaChunk(bool bIgnoreGammaChunk) { mpImpl->SetIgnoreGammaChunk(bIgnoreGammaChunk); } } // namespace vcl /* vim:set shiftwidth=4 softtabstop=4 expandtab: */