diff options
author | Tomaž Vajngerl <tomaz.vajngerl@collabora.com> | 2016-10-03 18:39:04 +0200 |
---|---|---|
committer | Tomaž Vajngerl <quikee@gmail.com> | 2016-10-04 12:15:24 +0000 |
commit | 8a04fac29da8ae902bd5f0aac559129013274304 (patch) | |
tree | 0615500e9bc8b1b736ee2ed7922167b31199cfbd | |
parent | d954f856a9909a582b089fd9339b0930d57914d7 (diff) |
vcl: read image from JPEG one scanline at a time
When reading an image from JPEG file we allocate a large buffer
to hold the whole raw image before we read it into SalBitmap.
If the image is large this step will spike the memory usage.
This changes reading so that we allocate only the buffer for
a scanline and read one scanline at a time.
Change-Id: I405f4a14601e271c0293a2b75b4c9ec6c458d8a9
Reviewed-on: https://gerrit.libreoffice.org/29504
Tested-by: Jenkins <ci@libreoffice.org>
Reviewed-by: Tomaž Vajngerl <quikee@gmail.com>
-rw-r--r-- | vcl/source/filter/jpeg/JpegReader.cxx | 214 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/JpegReader.hxx | 23 | ||||
-rw-r--r-- | vcl/source/filter/jpeg/jpegc.cxx | 149 |
3 files changed, 139 insertions, 247 deletions
diff --git a/vcl/source/filter/jpeg/JpegReader.cxx b/vcl/source/filter/jpeg/JpegReader.cxx index 8959e4e8bd99..cd00518dcf44 100644 --- a/vcl/source/filter/jpeg/JpegReader.cxx +++ b/vcl/source/filter/jpeg/JpegReader.cxx @@ -178,9 +178,6 @@ void jpeg_svstream_src (j_decompress_ptr cinfo, void* input) JPEGReader::JPEGReader( SvStream& rStream, void* /*pCallData*/, bool bSetLogSize ) : mrStream ( rStream ), - mpAcc ( nullptr ), - mpAcc1 ( nullptr ), - mpBuffer ( nullptr ), mnLastPos ( rStream.Tell() ), mnLastLines ( 0 ), mbSetLogSize ( bSetLogSize ) @@ -191,46 +188,32 @@ JPEGReader::JPEGReader( SvStream& rStream, void* /*pCallData*/, bool bSetLogSize JPEGReader::~JPEGReader() { - delete[] mpBuffer; - - if( mpAcc ) - Bitmap::ReleaseAccess( mpAcc ); - - if( mpAcc1 ) - Bitmap::ReleaseAccess( mpAcc1 ); } -unsigned char * JPEGReader::CreateBitmap(JPEGCreateBitmapParam& rParam) +bool JPEGReader::CreateBitmap(JPEGCreateBitmapParam& rParam) { if (rParam.nWidth > SAL_MAX_INT32 / 8 || rParam.nHeight > SAL_MAX_INT32 / 8) - return nullptr; // avoid overflows later + return false; // avoid overflows later if (rParam.nWidth == 0 || rParam.nHeight == 0) - return nullptr; + return false; - Size aSize(rParam.nWidth, rParam.nHeight ); - bool bGray = rParam.bGray != 0; + Size aSize(rParam.nWidth, rParam.nHeight); + bool bGray = rParam.bGray; - unsigned char * pBmpBuf = nullptr; + maBitmap = Bitmap(); - if( mpAcc ) - { - Bitmap::ReleaseAccess( mpAcc ); - maBmp = Bitmap(); - mpAcc = nullptr; - } + sal_uInt64 nSize = aSize.Width() * aSize.Height(); - sal_uInt64 nSize = aSize.Width(); - nSize *= aSize.Height(); if (nSize > SAL_MAX_INT32 / (bGray?1:3)) - return nullptr; + return false; // Check if the bitmap is atypically large. if (nSize*(bGray?1:3) > MAX_BITMAP_BYTE_SIZE) { // Do not try to acquire resources for the large bitmap or to // read the bitmap into memory. - return nullptr; + return false; } if( bGray ) @@ -243,18 +226,18 @@ unsigned char * JPEGReader::CreateBitmap(JPEGCreateBitmapParam& rParam) aGrayPal[ n ] = BitmapColor( cGray, cGray, cGray ); } - maBmp = Bitmap( aSize, 8, &aGrayPal ); + maBitmap = Bitmap(aSize, 8, &aGrayPal); } else { - maBmp = Bitmap( aSize, 24 ); + maBitmap = Bitmap(aSize, 24); } - if ( mbSetLogSize ) + if (mbSetLogSize) { unsigned long nUnit = rParam.density_unit; - if( ( ( 1 == nUnit ) || ( 2 == nUnit ) ) && rParam.X_density && rParam.Y_density ) + if (((1 == nUnit) || (2 == nUnit)) && rParam.X_density && rParam.Y_density ) { Point aEmptyPoint; Fraction aFractX( 1, rParam.X_density ); @@ -262,161 +245,48 @@ unsigned char * JPEGReader::CreateBitmap(JPEGCreateBitmapParam& rParam) MapMode aMapMode( nUnit == 1 ? MAP_INCH : MAP_CM, aEmptyPoint, aFractX, aFractY ); Size aPrefSize = OutputDevice::LogicToLogic( aSize, aMapMode, MAP_100TH_MM ); - maBmp.SetPrefSize( aPrefSize ); - maBmp.SetPrefMapMode( MapMode( MAP_100TH_MM ) ); + maBitmap.SetPrefSize(aPrefSize); + maBitmap.SetPrefMapMode(MapMode(MAP_100TH_MM)); } } - mpAcc = maBmp.AcquireWriteAccess(); - - if( mpAcc ) - { - const ScanlineFormat nFormat = mpAcc->GetScanlineFormat(); - - if( - ( bGray && ( ScanlineFormat::N8BitPal == nFormat ) ) || - ( !bGray && ( ScanlineFormat::N24BitTcRgb == nFormat ) ) - ) - { - pBmpBuf = mpAcc->GetBuffer(); - rParam.nAlignedWidth = mpAcc->GetScanlineSize(); - rParam.bTopDown = mpAcc->IsTopDown(); - } - else - { - rParam.nAlignedWidth = AlignedWidth4Bytes( aSize.Width() * ( bGray ? 8 : 24 ) ); - rParam.bTopDown = true; - pBmpBuf = mpBuffer = new unsigned char[rParam.nAlignedWidth * aSize.Height()]; - } - } - - // clean up, if no Bitmap buffer can be provided. - if ( !pBmpBuf ) - { - Bitmap::ReleaseAccess( mpAcc ); - maBmp = Bitmap(); - mpAcc = nullptr; - } - - return pBmpBuf; -} - -void JPEGReader::FillBitmap() -{ - if( mpBuffer && mpAcc ) - { - unsigned char * pTmp; - BitmapColor aColor; - long nAlignedWidth; - long nWidth = mpAcc->Width(); - long nHeight = mpAcc->Height(); - - if( mpAcc->GetBitCount() == 8 ) - { - std::unique_ptr<BitmapColor[]> pCols(new BitmapColor[ 256 ]); - - for( sal_uInt16 n = 0; n < 256; n++ ) - { - const sal_uInt8 cGray = (sal_uInt8) n; - pCols[ n ] = mpAcc->GetBestMatchingColor( BitmapColor( cGray, cGray, cGray ) ); - } - - nAlignedWidth = AlignedWidth4Bytes( mpAcc->Width() * 8L ); - - for( long nY = 0L; nY < nHeight; nY++ ) - { - pTmp = mpBuffer + nY * nAlignedWidth; - - for( long nX = 0L; nX < nWidth; nX++ ) - { - mpAcc->SetPixel( nY, nX, pCols[ *pTmp++ ] ); - } - } - } - else - { - nAlignedWidth = AlignedWidth4Bytes( mpAcc->Width() * 24L ); - - for( long nY = 0L; nY < nHeight; nY++ ) - { - // #i122985# Added fast-lane implementations using CopyScanline with direct supported mem formats - static bool bCheckOwnReader(true); - - if(bCheckOwnReader) - { - // #i122985# Trying to copy the RGB data from jpeg import to make things faster. Unfortunately - // it has no GBR format, so RGB three-byte groups need to be 'flipped' to GBR first, - // then CopyScanline can use a memcpy to do the data transport. CopyScanline can also - // do the needed conversion from ScanlineFormat::N24BitTcRgb (and it works well), but this - // is not faster that the old loop below using SetPixel. - sal_uInt8* aSource(mpBuffer + nY * nAlignedWidth); - sal_uInt8* aEnd(aSource + (nWidth * 3)); - - for(sal_uInt8* aTmp(aSource); aTmp < aEnd; aTmp += 3) - { - ::std::swap(*aTmp, *(aTmp + 2)); - } - - mpAcc->CopyScanline(nY, aSource, ScanlineFormat::N24BitTcBgr, nWidth * 3); - } - else - { - // old version: WritePixel - pTmp = mpBuffer + nY * nAlignedWidth; - - for( long nX = 0L; nX < nWidth; nX++ ) - { - aColor.SetRed( *pTmp++ ); - aColor.SetGreen( *pTmp++ ); - aColor.SetBlue( *pTmp++ ); - mpAcc->SetPixel( nY, nX, aColor ); - } - } - } - } - } + return true; } -Graphic JPEGReader::CreateIntermediateGraphic( const Bitmap& rBitmap, long nLines ) +Graphic JPEGReader::CreateIntermediateGraphic(long nLines) { - Graphic aGraphic; - const Size aSizePixel( rBitmap.GetSizePixel() ); + Graphic aGraphic; + const Size aSizePixel(maBitmap.GetSizePixel()); - if( !mnLastLines ) + if (!mnLastLines) { - if( mpAcc1 ) - { - Bitmap::ReleaseAccess( mpAcc1 ); - } - - maBmp1 = Bitmap( rBitmap.GetSizePixel(), 1 ); - maBmp1.Erase( Color( COL_WHITE ) ); - mpAcc1 = maBmp1.AcquireWriteAccess(); + maIncompleteAlpha = Bitmap(aSizePixel, 1); + maIncompleteAlpha.Erase(Color(COL_WHITE)); } - if( nLines && ( nLines < aSizePixel.Height() ) ) + if (nLines && (nLines < aSizePixel.Height())) { - if( mpAcc1 ) - { - const long nNewLines = nLines - mnLastLines; + const long nNewLines = nLines - mnLastLines; - if( nNewLines ) + if (nNewLines > 0) + { { - mpAcc1->SetFillColor( Color( COL_BLACK ) ); - mpAcc1->FillRect( Rectangle( Point( 0, mnLastLines ), Size( mpAcc1->Width(), nNewLines ) ) ); + Bitmap::ScopedWriteAccess pAccess(maIncompleteAlpha); + pAccess->SetFillColor(Color(COL_BLACK)); + pAccess->FillRect(Rectangle(Point(0, mnLastLines), Size(pAccess->Width(), nNewLines))); } - Bitmap::ReleaseAccess( mpAcc1 ); - aGraphic = BitmapEx( rBitmap, maBmp1 ); - mpAcc1 = maBmp1.AcquireWriteAccess(); + aGraphic = BitmapEx(maBitmap, maIncompleteAlpha); } else { - aGraphic = rBitmap; + aGraphic = maBitmap; } } else - aGraphic = rBitmap; + { + aGraphic = maBitmap; + } mnLastLines = nLines; @@ -454,25 +324,15 @@ ReadState JPEGReader::Read( Graphic& rGraphic ) // read the (partial) image ReadJPEG( this, &mrStream, &nLines, GetPreviewSize() ); - if( mpAcc ) + if (!maBitmap.IsEmpty()) { - if( mpBuffer ) - { - FillBitmap(); - delete[] mpBuffer; - mpBuffer = nullptr; - } - - Bitmap::ReleaseAccess( mpAcc ); - mpAcc = nullptr; - if( mrStream.GetError() == ERRCODE_IO_PENDING ) { - rGraphic = CreateIntermediateGraphic( maBmp, nLines ); + rGraphic = CreateIntermediateGraphic(nLines); } else { - rGraphic = maBmp; + rGraphic = maBitmap; } bRet = true; diff --git a/vcl/source/filter/jpeg/JpegReader.hxx b/vcl/source/filter/jpeg/JpegReader.hxx index 71f8b9ae37e2..c3b52fd2abf9 100644 --- a/vcl/source/filter/jpeg/JpegReader.hxx +++ b/vcl/source/filter/jpeg/JpegReader.hxx @@ -39,34 +39,33 @@ struct JPEGCreateBitmapParam unsigned long density_unit; unsigned long X_density; unsigned long Y_density; - long bGray; - long nAlignedWidth; // these members will be filled by the - bool bTopDown; // CreateBitmap method in svtools + bool bGray; + bool bTopDown; // CreateBitmap method in svtools }; class JPEGReader : public GraphicReader { SvStream& mrStream; - Bitmap maBmp; - Bitmap maBmp1; - BitmapWriteAccess* mpAcc; - BitmapWriteAccess* mpAcc1; - unsigned char * mpBuffer; + Bitmap maBitmap; + Bitmap maIncompleteAlpha; + long mnLastPos; long mnFormerPos; long mnLastLines; bool mbSetLogSize; - Graphic CreateIntermediateGraphic( const Bitmap& rBitmap, long nLines ); - void FillBitmap(); + Graphic CreateIntermediateGraphic(long nLines); public: JPEGReader( SvStream& rStream, void* pCallData, bool bSetLogSize ); virtual ~JPEGReader() override; - ReadState Read( Graphic& rGraphic ); - unsigned char * CreateBitmap( JPEGCreateBitmapParam& param ); + ReadState Read(Graphic& rGraphic); + + bool CreateBitmap(JPEGCreateBitmapParam& param); + + Bitmap& GetBitmap() { return maBitmap; } }; #endif // INCLUDED_VCL_SOURCE_FILTER_JPEG_JPEGREADER_HXX diff --git a/vcl/source/filter/jpeg/jpegc.cxx b/vcl/source/filter/jpeg/jpegc.cxx index 74be061644c1..28af8c915d4b 100644 --- a/vcl/source/filter/jpeg/jpegc.cxx +++ b/vcl/source/filter/jpeg/jpegc.cxx @@ -36,6 +36,7 @@ extern "C" { #include <JpegReader.hxx> #include <JpegWriter.hxx> #include <memory> +#include <vcl/bitmapaccess.hxx> #ifdef _MSC_VER #pragma warning(push, 1) /* disable to __declspec(align()) aligned warning */ @@ -68,16 +69,8 @@ extern "C" void outputMessage (j_common_ptr cinfo) void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, long* pLines, Size const & previewSize ) { - jpeg_decompress_struct cinfo; - ErrorManagerStruct jerr; - JPEGCreateBitmapParam aCreateBitmapParam; - unsigned char * pDIB; - unsigned char * pTmp; - long nWidth; - long nHeight; - long nAlignedWidth; - JSAMPLE* aRangeLimit; - std::unique_ptr<unsigned char[]> pScanLineBuffer; + jpeg_decompress_struct cinfo; + ErrorManagerStruct jerr; if ( setjmp( jerr.setjmp_buffer ) ) { @@ -151,72 +144,114 @@ void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, long* pLines, jpeg_start_decompress( &cinfo ); - nWidth = cinfo.output_width; - nHeight = cinfo.output_height; + long nWidth = cinfo.output_width; + long nHeight = cinfo.output_height; + + bool bGray = (cinfo.output_components == 1); + + JPEGCreateBitmapParam aCreateBitmapParam; + aCreateBitmapParam.nWidth = nWidth; aCreateBitmapParam.nHeight = nHeight; aCreateBitmapParam.density_unit = cinfo.density_unit; aCreateBitmapParam.X_density = cinfo.X_density; aCreateBitmapParam.Y_density = cinfo.Y_density; - aCreateBitmapParam.bGray = long(cinfo.output_components == 1); - pDIB = pJPEGReader->CreateBitmap(aCreateBitmapParam); - nAlignedWidth = aCreateBitmapParam.nAlignedWidth; - aRangeLimit = cinfo.sample_range_limit; + aCreateBitmapParam.bGray = bGray; - long nScanLineBufferComponents = 0; - if ( cinfo.out_color_space == JCS_CMYK ) - { - nScanLineBufferComponents = cinfo.output_width * 4; - pScanLineBuffer.reset(new unsigned char[nScanLineBufferComponents]); - } + bool bBitmapCreated = pJPEGReader->CreateBitmap(aCreateBitmapParam); - if( pDIB ) + if (bBitmapCreated) { - if( aCreateBitmapParam.bTopDown ) - { - pTmp = pDIB; - } - else - { - pTmp = pDIB + ( nHeight - 1 ) * nAlignedWidth; - nAlignedWidth = -nAlignedWidth; - } + Bitmap::ScopedWriteAccess pAccess(pJPEGReader->GetBitmap()); - for ( *pLines = 0; *pLines < nHeight; (*pLines)++ ) + if (pAccess) { - if (pScanLineBuffer) - { // in other words cinfo.out_color_space == JCS_CMYK - int i; - int j; - unsigned char *pSLB = pScanLineBuffer.get(); - jpeg_read_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pSLB), 1 ); - // convert CMYK to RGB - for( i=0, j=0; i < nScanLineBufferComponents; i+=4, j+=3 ) + JSAMPLE* aRangeLimit = cinfo.sample_range_limit; + + std::vector<sal_uInt8> pScanLineBuffer(nWidth * (bGray ? 1 : 3)); + std::vector<sal_uInt8> pCYMKBuffer; + + if (cinfo.out_color_space == JCS_CMYK) + { + pCYMKBuffer.resize(nWidth * 4); + } + + const ScanlineFormat nFormat = pAccess->GetScanlineFormat(); + + bool bTopDown = true; + + if (( bGray && nFormat == ScanlineFormat::N8BitPal) || + (!bGray && nFormat == ScanlineFormat::N24BitTcRgb)) + { + bTopDown = pAccess->IsTopDown(); + } + + std::unique_ptr<BitmapColor[]> pCols; + + if (bGray) + { + pCols.reset(new BitmapColor[256]); + + for (sal_uInt16 n = 0; n < 256; n++) { - int color_C = 255 - pScanLineBuffer[i+0]; - int color_M = 255 - pScanLineBuffer[i+1]; - int color_Y = 255 - pScanLineBuffer[i+2]; - int color_K = 255 - pScanLineBuffer[i+3]; - pTmp[j+0] = aRangeLimit[ 255L - ( color_C + color_K ) ]; - pTmp[j+1] = aRangeLimit[ 255L - ( color_M + color_K ) ]; - pTmp[j+2] = aRangeLimit[ 255L - ( color_Y + color_K ) ]; + const sal_uInt8 cGray = n; + pCols[n] = pAccess->GetBestMatchingColor(BitmapColor(cGray, cGray, cGray)); } } - else + + for (*pLines = 0; *pLines < nHeight; (*pLines)++) { - jpeg_read_scanlines( &cinfo, reinterpret_cast<JSAMPARRAY>(&pTmp), 1 ); - } + size_t yIndex = *pLines; - /* PENDING ??? */ - if ( cinfo.err->msg_code == 113 ) - break; + if (cinfo.out_color_space == JCS_CMYK) + { + sal_uInt8* p = pCYMKBuffer.data(); + jpeg_read_scanlines(&cinfo, reinterpret_cast<JSAMPARRAY>(&p), 1); + + // convert CMYK to RGB + for (int cmyk = 0, rgb = 0; cmyk < nWidth * 4; cmyk += 4, rgb += 3) + { + int color_C = 255 - pCYMKBuffer[cmyk + 0]; + int color_M = 255 - pCYMKBuffer[cmyk + 1]; + int color_Y = 255 - pCYMKBuffer[cmyk + 2]; + int color_K = 255 - pCYMKBuffer[cmyk + 3]; + + pScanLineBuffer[rgb + 0] = aRangeLimit[255L - (color_C + color_K)]; + pScanLineBuffer[rgb + 1] = aRangeLimit[255L - (color_M + color_K)]; + pScanLineBuffer[rgb + 2] = aRangeLimit[255L - (color_Y + color_K)]; + } + } + else + { + sal_uInt8* p = pScanLineBuffer.data(); + jpeg_read_scanlines(&cinfo, reinterpret_cast<JSAMPARRAY>(&p), 1); + } + + if (!bTopDown) + yIndex = nHeight - 1 - yIndex; - pTmp += nAlignedWidth; + if (bGray) + { + for (long x = 0; x < nWidth; ++x) + { + sal_uInt8 nColorGray = pScanLineBuffer[x]; + pAccess->SetPixel(yIndex, x, pCols[nColorGray]); + } + } + else + { + pAccess->CopyScanline(yIndex, pScanLineBuffer.data(), ScanlineFormat::N24BitTcRgb, nWidth * 3); + } + + /* PENDING ??? */ + if (cinfo.err->msg_code == 113) + break; + } } } - if ( pDIB ) + if (bBitmapCreated) { jpeg_finish_decompress( &cinfo ); } @@ -225,8 +260,6 @@ void ReadJPEG( JPEGReader* pJPEGReader, void* pInputStream, long* pLines, jpeg_abort_decompress( &cinfo ); } - pScanLineBuffer.reset(); - jpeg_destroy_decompress( &cinfo ); } |