/* -*- 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 namespace { #define MAP_PRECISION 7 typedef sal_Int32 BilinearWeightType; constexpr BilinearWeightType lclMaxWeight() { return BilinearWeightType(1) << MAP_PRECISION; } constexpr sal_uInt8 MAP(sal_uInt8 cVal0, sal_uInt8 cVal1, BilinearWeightType nFrac) { return sal_uInt8(((BilinearWeightType(cVal0) << MAP_PRECISION) + nFrac * (BilinearWeightType(cVal1) - BilinearWeightType(cVal0))) >> MAP_PRECISION); } struct ScaleContext { BitmapReadAccess* mpSrc; BitmapWriteAccess* mpDest; long mnDestW; bool mbHMirr; bool mbVMirr; std::vector maMapIX; std::vector maMapIY; std::vector maMapFX; std::vector maMapFY; ScaleContext( BitmapReadAccess *pSrc, BitmapWriteAccess *pDest, long nSrcW, long nDestW, long nSrcH, long nDestH, bool bHMirr, bool bVMirr) : mpSrc(pSrc) , mpDest(pDest) , mnDestW(nDestW) , mbHMirr(bHMirr) , mbVMirr(bVMirr) , maMapIX(nDestW) , maMapIY(nDestH) , maMapFX(nDestW) , maMapFY(nDestH) { generateMap(nSrcW, nDestW, bHMirr, maMapIX, maMapFX); generateMap(nSrcH, nDestH, bVMirr, maMapIY, maMapFY); } static void generateMap(long nSourceLength, long nDestinationLength, bool bMirrored, std::vector & rMapIX, std::vector & rMapFX) { const double fRevScale = (nDestinationLength > 1) ? double(nSourceLength - 1) / (nDestinationLength - 1) : 0.0; long nTemp = nSourceLength - 2; long nTempX = nSourceLength - 1; for (long i = 0; i < nDestinationLength; i++) { double fTemp = i * fRevScale; if (bMirrored) fTemp = nTempX - fTemp; rMapIX[i] = MinMax(long(fTemp), 0, nTemp); rMapFX[i] = BilinearWeightType((fTemp - rMapIX[i]) * (BilinearWeightType(1) << MAP_PRECISION)); } } }; constexpr long constScaleThreadStrip = 32; typedef void (*ScaleRangeFn)(ScaleContext &rContext, long nStartY, long nEndY); class ScaleTask : public comphelper::ThreadTask { ScaleRangeFn mpScaleRangeFunction; ScaleContext& mrContext; long mnStartY; long mnEndY; public: explicit ScaleTask(const std::shared_ptr& pTag, ScaleRangeFn pScaleRangeFunction, ScaleContext& rContext, long nStartY, long nEndY) : comphelper::ThreadTask(pTag) , mpScaleRangeFunction(pScaleRangeFunction) , mrContext(rContext) , mnStartY(nStartY) , mnEndY(nEndY) {} virtual void doWork() override { mpScaleRangeFunction(mrContext, mnStartY, mnEndY); } }; void scaleUp32bit(ScaleContext &rCtx, long nStartY, long nEndY) { const int nColorComponents = 4; const long nStartX = 0; const long nEndX = rCtx.mnDestW - 1; for (long nY = nStartY; nY <= nEndY; nY++) { long nTempY = rCtx.maMapIY[nY]; BilinearWeightType nTempFY = rCtx.maMapFY[nY]; Scanline pLine0 = rCtx.mpSrc->GetScanline(nTempY+0); Scanline pLine1 = rCtx.mpSrc->GetScanline(nTempY+1); Scanline pScanDest = rCtx.mpDest->GetScanline(nY); sal_uInt8 nComponent1[nColorComponents]; sal_uInt8 nComponent2[nColorComponents]; Scanline pColorPtr0; Scanline pColorPtr1; for (long nX = nStartX; nX <= nEndX; nX++) { long nTempX = rCtx.maMapIX[nX]; BilinearWeightType nTempFX = rCtx.maMapFX[nX]; pColorPtr0 = pLine0 + nTempX * nColorComponents; pColorPtr1 = pColorPtr0 + nColorComponents; nComponent1[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0++; pColorPtr1++; nComponent1[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0++; pColorPtr1++; nComponent1[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0++; pColorPtr1++; nComponent1[3] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0 = pLine1 + nTempX * nColorComponents; pColorPtr1 = pColorPtr0 + nColorComponents; nComponent2[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0++; pColorPtr1++; nComponent2[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0++; pColorPtr1++; nComponent2[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0++; pColorPtr1++; nComponent2[3] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); *pScanDest = MAP(nComponent1[0], nComponent2[0], nTempFY); pScanDest++; *pScanDest = MAP(nComponent1[1], nComponent2[1], nTempFY); pScanDest++; *pScanDest = MAP(nComponent1[2], nComponent2[2], nTempFY); pScanDest++; *pScanDest = MAP(nComponent1[3], nComponent2[3], nTempFY); pScanDest++; } } } void scaleUpPalette8bit(ScaleContext &rCtx, long nStartY, long nEndY) { const long nStartX = 0, nEndX = rCtx.mnDestW - 1; for( long nY = nStartY; nY <= nEndY; nY++ ) { long nTempY = rCtx.maMapIY[ nY ]; BilinearWeightType nTempFY = rCtx.maMapFY[ nY ]; Scanline pLine0 = rCtx.mpSrc->GetScanline( nTempY ); Scanline pLine1 = rCtx.mpSrc->GetScanline( ++nTempY ); Scanline pScanDest = rCtx.mpDest->GetScanline( nY ); for(long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ ) { long nTempX = rCtx.maMapIX[ nX ]; BilinearWeightType nTempFX = rCtx.maMapFX[ nX ]; const BitmapColor& rCol0 = rCtx.mpSrc->GetPaletteColor( pLine0[ nTempX ] ); const BitmapColor& rCol2 = rCtx.mpSrc->GetPaletteColor( pLine1[ nTempX ] ); const BitmapColor& rCol1 = rCtx.mpSrc->GetPaletteColor( pLine0[ ++nTempX ] ); const BitmapColor& rCol3 = rCtx.mpSrc->GetPaletteColor( pLine1[ nTempX ] ); sal_uInt8 cR0 = MAP( rCol0.GetRed(), rCol1.GetRed(), nTempFX ); sal_uInt8 cG0 = MAP( rCol0.GetGreen(), rCol1.GetGreen(), nTempFX ); sal_uInt8 cB0 = MAP( rCol0.GetBlue(), rCol1.GetBlue(), nTempFX ); sal_uInt8 cR1 = MAP( rCol2.GetRed(), rCol3.GetRed(), nTempFX ); sal_uInt8 cG1 = MAP( rCol2.GetGreen(), rCol3.GetGreen(), nTempFX ); sal_uInt8 cB1 = MAP( rCol2.GetBlue(), rCol3.GetBlue(), nTempFX ); BitmapColor aColRes( MAP( cR0, cR1, nTempFY ), MAP( cG0, cG1, nTempFY ), MAP( cB0, cB1, nTempFY ) ); rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes ); } } } void scaleUpPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY) { const long nStartX = 0, nEndX = rCtx.mnDestW - 1; for( long nY = nStartY; nY <= nEndY; nY++ ) { long nTempY = rCtx.maMapIY[ nY ]; BilinearWeightType nTempFY = rCtx.maMapFY[ nY ]; Scanline pScanline = rCtx.mpDest->GetScanline( nY ); for( long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ ) { long nTempX = rCtx.maMapIX[ nX ]; BilinearWeightType nTempFX = rCtx.maMapFX[ nX ]; BitmapColor aCol0 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY, nTempX ) ); BitmapColor aCol1 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY, ++nTempX ) ); sal_uInt8 cR0 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX ); sal_uInt8 cG0 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX ); sal_uInt8 cB0 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX ); aCol1 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( ++nTempY, nTempX ) ); aCol0 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY--, --nTempX ) ); sal_uInt8 cR1 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX ); sal_uInt8 cG1 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX ); sal_uInt8 cB1 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX ); BitmapColor aColRes( MAP( cR0, cR1, nTempFY ), MAP( cG0, cG1, nTempFY ), MAP( cB0, cB1, nTempFY ) ); rCtx.mpDest->SetPixelOnData( pScanline, nXDst++, aColRes ); } } } void scaleUp24bit(ScaleContext &rCtx, long nStartY, long nEndY) { const int nColorComponents = 3; const long nStartX = 0; const long nEndX = rCtx.mnDestW - 1; for (long nY = nStartY; nY <= nEndY; nY++) { long nTempY = rCtx.maMapIY[nY]; BilinearWeightType nTempFY = rCtx.maMapFY[nY]; Scanline pLine0 = rCtx.mpSrc->GetScanline(nTempY+0); Scanline pLine1 = rCtx.mpSrc->GetScanline(nTempY+1); Scanline pScanDest = rCtx.mpDest->GetScanline(nY); sal_uInt8 nComponent1[nColorComponents]; sal_uInt8 nComponent2[nColorComponents]; Scanline pColorPtr0; Scanline pColorPtr1; for (long nX = nStartX; nX <= nEndX; nX++) { long nTempX = rCtx.maMapIX[nX]; BilinearWeightType nTempFX = rCtx.maMapFX[nX]; pColorPtr0 = pLine0 + nTempX * nColorComponents; pColorPtr1 = pColorPtr0 + nColorComponents; nComponent1[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0++; pColorPtr1++; nComponent1[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0++; pColorPtr1++; nComponent1[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0 = pLine1 + nTempX * nColorComponents; pColorPtr1 = pColorPtr0 + nColorComponents; nComponent2[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0++; pColorPtr1++; nComponent2[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); pColorPtr0++; pColorPtr1++; nComponent2[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX); *pScanDest = MAP(nComponent1[0], nComponent2[0], nTempFY); pScanDest++; *pScanDest = MAP(nComponent1[1], nComponent2[1], nTempFY); pScanDest++; *pScanDest = MAP(nComponent1[2], nComponent2[2], nTempFY); pScanDest++; } } } void scaleUpNonPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY) { const long nStartX = 0, nEndX = rCtx.mnDestW - 1; for( long nY = nStartY; nY <= nEndY; nY++ ) { long nTempY = rCtx.maMapIY[ nY ]; BilinearWeightType nTempFY = rCtx.maMapFY[ nY ]; Scanline pScanDest = rCtx.mpDest->GetScanline( nY ); for( long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ ) { long nTempX = rCtx.maMapIX[ nX ]; BilinearWeightType nTempFX = rCtx.maMapFX[ nX ]; BitmapColor aCol0 = rCtx.mpSrc->GetPixel( nTempY, nTempX ); BitmapColor aCol1 = rCtx.mpSrc->GetPixel( nTempY, ++nTempX ); sal_uInt8 cR0 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX ); sal_uInt8 cG0 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX ); sal_uInt8 cB0 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX ); aCol1 = rCtx.mpSrc->GetPixel( ++nTempY, nTempX ); aCol0 = rCtx.mpSrc->GetPixel( nTempY--, --nTempX ); sal_uInt8 cR1 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX ); sal_uInt8 cG1 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX ); sal_uInt8 cB1 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX ); BitmapColor aColRes( MAP( cR0, cR1, nTempFY ), MAP( cG0, cG1, nTempFY ), MAP( cB0, cB1, nTempFY ) ); rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes ); } } } void scaleDown32bit(ScaleContext &rCtx, long nStartY, long nEndY) { const int constColorComponents = 4; const long nStartX = 0; const long nEndX = rCtx.mnDestW - 1; for (long nY = nStartY; nY <= nEndY; nY++) { long nTop = rCtx.mbVMirr ? (nY + 1) : nY; long nBottom = rCtx.mbVMirr ? nY : (nY + 1); long nLineStart; long nLineRange; if (nY == nEndY) { nLineStart = rCtx.maMapIY[nY]; nLineRange = 0; } else { nLineStart = rCtx.maMapIY[nTop]; nLineRange = (rCtx.maMapIY[nBottom] == rCtx.maMapIY[nTop]) ? 1 : (rCtx.maMapIY[nBottom] - rCtx.maMapIY[nTop]); } Scanline pScanDest = rCtx.mpDest->GetScanline(nY); for (long nX = nStartX; nX <= nEndX; nX++) { long nLeft = rCtx.mbHMirr ? (nX + 1) : nX; long nRight = rCtx.mbHMirr ? nX : (nX + 1); long nRowStart; long nRowRange; if (nX == nEndX) { nRowStart = rCtx.maMapIX[nX]; nRowRange = 0; } else { nRowStart = rCtx.maMapIX[nLeft]; nRowRange = (rCtx.maMapIX[nRight] == rCtx.maMapIX[nLeft]) ? 1 : (rCtx.maMapIX[nRight] - rCtx.maMapIX[nLeft]); } int nSum1 = 0; int nSum2 = 0; int nSum3 = 0; int nSum4 = 0; BilinearWeightType nTotalWeightY = 0; for (long i = 0; i<= nLineRange; i++) { Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i); Scanline pTmpX = pTmpY + constColorComponents * nRowStart; int nSumRow1 = 0; int nSumRow2 = 0; int nSumRow3 = 0; int nSumRow4 = 0; BilinearWeightType nTotalWeightX = 0; for (long j = 0; j <= nRowRange; j++) { if (nX == nEndX) { nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++; nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++; nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++; nSumRow4 += (*pTmpX) << MAP_PRECISION; pTmpX++; nTotalWeightX += lclMaxWeight(); } else if(j == 0) { BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft]; nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++; nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++; nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++; nSumRow4 += (nWeightX * (*pTmpX)); pTmpX++; nTotalWeightX += nWeightX; } else if ( nRowRange == j ) { BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ; nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++; nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++; nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++; nSumRow4 += (nWeightX * (*pTmpX)); pTmpX++; nTotalWeightX += nWeightX; } else { nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++; nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++; nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++; nSumRow4 += (*pTmpX) << MAP_PRECISION; pTmpX++; nTotalWeightX += lclMaxWeight(); } } BilinearWeightType nWeightY = lclMaxWeight(); if (nY == nEndY) nWeightY = lclMaxWeight(); else if (i == 0) nWeightY = lclMaxWeight() - rCtx.maMapFY[nTop]; else if (nLineRange == 1) nWeightY = rCtx.maMapFY[nTop]; else if (nLineRange == i) nWeightY = rCtx.maMapFY[nBottom]; if (nTotalWeightX) { nSumRow1 /= nTotalWeightX; nSumRow2 /= nTotalWeightX; nSumRow3 /= nTotalWeightX; nSumRow4 /= nTotalWeightX; } nSum1 += nWeightY * nSumRow1; nSum2 += nWeightY * nSumRow2; nSum3 += nWeightY * nSumRow3; nSum4 += nWeightY * nSumRow4; nTotalWeightY += nWeightY; } if (nTotalWeightY) { nSum1 /= nTotalWeightY; nSum2 /= nTotalWeightY; nSum3 /= nTotalWeightY; nSum4 /= nTotalWeightY; } // Write the calculated color components to the destination *pScanDest = nSum1; pScanDest++; *pScanDest = nSum2; pScanDest++; *pScanDest = nSum3; pScanDest++; *pScanDest = nSum4; pScanDest++; } } } void scaleDownPalette8bit(ScaleContext &rCtx, long nStartY, long nEndY) { const long nStartX = 0, nEndX = rCtx.mnDestW - 1; for( long nY = nStartY; nY <= nEndY; nY++ ) { long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY; long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ; long nLineStart, nLineRange; if( nY == nEndY ) { nLineStart = rCtx.maMapIY[ nY ]; nLineRange = 0; } else { nLineStart = rCtx.maMapIY[ nTop ] ; nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] ); } Scanline pScanDest = rCtx.mpDest->GetScanline( nY ); for( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ ) { long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX; long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ; long nRowStart; long nRowRange; if( nX == nEndX ) { nRowStart = rCtx.maMapIX[ nX ]; nRowRange = 0; } else { nRowStart = rCtx.maMapIX[ nLeft ]; nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] ); } int nSumR = 0; int nSumG = 0; int nSumB = 0; BilinearWeightType nTotalWeightY = 0; for(long i = 0; i<= nLineRange; i++) { Scanline pTmpY = rCtx.mpSrc->GetScanline( nLineStart + i ); int nSumRowR = 0; int nSumRowG = 0; int nSumRowB = 0; BilinearWeightType nTotalWeightX = 0; for(long j = 0; j <= nRowRange; j++) { const BitmapColor& rCol = rCtx.mpSrc->GetPaletteColor( pTmpY[ nRowStart + j ] ); if(nX == nEndX ) { nSumRowB += rCol.GetBlue() << MAP_PRECISION; nSumRowG += rCol.GetGreen() << MAP_PRECISION; nSumRowR += rCol.GetRed() << MAP_PRECISION; nTotalWeightX += lclMaxWeight(); } else if( j == 0 ) { BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ]; nSumRowB += ( nWeightX *rCol.GetBlue()) ; nSumRowG += ( nWeightX *rCol.GetGreen()) ; nSumRowR += ( nWeightX *rCol.GetRed()) ; nTotalWeightX += nWeightX; } else if ( nRowRange == j ) { BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ; nSumRowB += ( nWeightX *rCol.GetBlue() ); nSumRowG += ( nWeightX *rCol.GetGreen() ); nSumRowR += ( nWeightX *rCol.GetRed() ); nTotalWeightX += nWeightX; } else { nSumRowB += rCol.GetBlue() << MAP_PRECISION; nSumRowG += rCol.GetGreen() << MAP_PRECISION; nSumRowR += rCol.GetRed() << MAP_PRECISION; nTotalWeightX += lclMaxWeight(); } } BilinearWeightType nWeightY = lclMaxWeight(); if( nY == nEndY ) nWeightY = lclMaxWeight(); else if( i == 0 ) nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ]; else if( nLineRange == 1 ) nWeightY = rCtx.maMapFY[ nTop ]; else if ( nLineRange == i ) nWeightY = rCtx.maMapFY[ nBottom ]; if (nTotalWeightX) { nSumRowB /= nTotalWeightX; nSumRowG /= nTotalWeightX; nSumRowR /= nTotalWeightX; } nSumB += nWeightY * nSumRowB; nSumG += nWeightY * nSumRowG; nSumR += nWeightY * nSumRowR; nTotalWeightY += nWeightY; } if (nTotalWeightY) { nSumR /= nTotalWeightY; nSumG /= nTotalWeightY; nSumB /= nTotalWeightY; } BitmapColor aColRes(static_cast(nSumR), static_cast(nSumG), static_cast(nSumB)); rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes ); } } } void scaleDownPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY) { const long nStartX = 0, nEndX = rCtx.mnDestW - 1; for( long nY = nStartY; nY <= nEndY; nY++ ) { long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY; long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ; long nLineStart, nLineRange; if( nY ==nEndY ) { nLineStart = rCtx.maMapIY[ nY ]; nLineRange = 0; } else { nLineStart = rCtx.maMapIY[ nTop ] ; nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] ); } Scanline pScanDest = rCtx.mpDest->GetScanline( nY ); for( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ ) { long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX; long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ; long nRowStart, nRowRange; if( nX == nEndX ) { nRowStart = rCtx.maMapIX[ nX ]; nRowRange = 0; } else { nRowStart = rCtx.maMapIX[ nLeft ]; nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] ); } int nSumR = 0; int nSumG = 0; int nSumB = 0; BilinearWeightType nTotalWeightY = 0; for(long i = 0; i<= nLineRange; i++) { int nSumRowR = 0; int nSumRowG = 0; int nSumRowB = 0; BilinearWeightType nTotalWeightX = 0; Scanline pScanlineSrc = rCtx.mpSrc->GetScanline( nLineStart + i ); for(long j = 0; j <= nRowRange; j++) { BitmapColor aCol0 = rCtx.mpSrc->GetPaletteColor ( rCtx.mpSrc->GetIndexFromData( pScanlineSrc, nRowStart + j ) ); if(nX == nEndX ) { nSumRowB += aCol0.GetBlue() << MAP_PRECISION; nSumRowG += aCol0.GetGreen() << MAP_PRECISION; nSumRowR += aCol0.GetRed() << MAP_PRECISION; nTotalWeightX += lclMaxWeight(); } else if( j == 0 ) { BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ]; nSumRowB += ( nWeightX *aCol0.GetBlue()) ; nSumRowG += ( nWeightX *aCol0.GetGreen()) ; nSumRowR += ( nWeightX *aCol0.GetRed()) ; nTotalWeightX += nWeightX; } else if ( nRowRange == j ) { BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ; nSumRowB += ( nWeightX *aCol0.GetBlue() ); nSumRowG += ( nWeightX *aCol0.GetGreen() ); nSumRowR += ( nWeightX *aCol0.GetRed() ); nTotalWeightX += nWeightX; } else { nSumRowB += aCol0.GetBlue() << MAP_PRECISION; nSumRowG += aCol0.GetGreen() << MAP_PRECISION; nSumRowR += aCol0.GetRed() << MAP_PRECISION; nTotalWeightX += lclMaxWeight(); } } long nWeightY = lclMaxWeight(); if( nY == nEndY ) nWeightY = lclMaxWeight(); else if( i == 0 ) nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ]; else if( nLineRange == 1 ) nWeightY = rCtx.maMapFY[ nTop ]; else if ( nLineRange == i ) nWeightY = rCtx.maMapFY[ nBottom ]; if (nTotalWeightX) { nSumRowB /= nTotalWeightX; nSumRowG /= nTotalWeightX; nSumRowR /= nTotalWeightX; } nSumB += nWeightY * nSumRowB; nSumG += nWeightY * nSumRowG; nSumR += nWeightY * nSumRowR; nTotalWeightY += nWeightY; } if (nTotalWeightY) { nSumR /= nTotalWeightY; nSumG /= nTotalWeightY; nSumB /= nTotalWeightY; } BitmapColor aColRes(static_cast(nSumR), static_cast(nSumG), static_cast(nSumB)); rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes ); } } } void scaleDown24bit(ScaleContext &rCtx, long nStartY, long nEndY) { const int constColorComponents = 3; const long nStartX = 0; const long nEndX = rCtx.mnDestW - 1; for (long nY = nStartY; nY <= nEndY; nY++) { long nTop = rCtx.mbVMirr ? (nY + 1) : nY; long nBottom = rCtx.mbVMirr ? nY : (nY + 1); long nLineStart; long nLineRange; if (nY == nEndY) { nLineStart = rCtx.maMapIY[nY]; nLineRange = 0; } else { nLineStart = rCtx.maMapIY[nTop]; nLineRange = (rCtx.maMapIY[nBottom] == rCtx.maMapIY[nTop]) ? 1 : (rCtx.maMapIY[nBottom] - rCtx.maMapIY[nTop]); } Scanline pScanDest = rCtx.mpDest->GetScanline(nY); for (long nX = nStartX; nX <= nEndX; nX++) { long nLeft = rCtx.mbHMirr ? (nX + 1) : nX; long nRight = rCtx.mbHMirr ? nX : (nX + 1); long nRowStart; long nRowRange; if (nX == nEndX) { nRowStart = rCtx.maMapIX[nX]; nRowRange = 0; } else { nRowStart = rCtx.maMapIX[nLeft]; nRowRange = (rCtx.maMapIX[nRight] == rCtx.maMapIX[nLeft]) ? 1 : (rCtx.maMapIX[nRight] - rCtx.maMapIX[nLeft]); } int nSum1 = 0; int nSum2 = 0; int nSum3 = 0; BilinearWeightType nTotalWeightY = 0; for (long i = 0; i<= nLineRange; i++) { Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i); Scanline pTmpX = pTmpY + constColorComponents * nRowStart; int nSumRow1 = 0; int nSumRow2 = 0; int nSumRow3 = 0; BilinearWeightType nTotalWeightX = 0; for (long j = 0; j <= nRowRange; j++) { if (nX == nEndX) { nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++; nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++; nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++; nTotalWeightX += lclMaxWeight(); } else if(j == 0) { BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft]; nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++; nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++; nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++; nTotalWeightX += nWeightX; } else if ( nRowRange == j ) { BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ; nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++; nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++; nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++; nTotalWeightX += nWeightX; } else { nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++; nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++; nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++; nTotalWeightX += lclMaxWeight(); } } BilinearWeightType nWeightY = lclMaxWeight(); if (nY == nEndY) nWeightY = lclMaxWeight(); else if (i == 0) nWeightY = lclMaxWeight() - rCtx.maMapFY[nTop]; else if (nLineRange == 1) nWeightY = rCtx.maMapFY[nTop]; else if (nLineRange == i) nWeightY = rCtx.maMapFY[nBottom]; if (nTotalWeightX) { nSumRow1 /= nTotalWeightX; nSumRow2 /= nTotalWeightX; nSumRow3 /= nTotalWeightX; } nSum1 += nWeightY * nSumRow1; nSum2 += nWeightY * nSumRow2; nSum3 += nWeightY * nSumRow3; nTotalWeightY += nWeightY; } if (nTotalWeightY) { nSum1 /= nTotalWeightY; nSum2 /= nTotalWeightY; nSum3 /= nTotalWeightY; } // Write the calculated color components to the destination *pScanDest = nSum1; pScanDest++; *pScanDest = nSum2; pScanDest++; *pScanDest = nSum3; pScanDest++; } } } void scaleDownNonPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY) { const long nStartX = 0, nEndX = rCtx.mnDestW - 1; for( long nY = nStartY; nY <= nEndY; nY++ ) { long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY; long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ; long nLineStart, nLineRange; if( nY ==nEndY ) { nLineStart = rCtx.maMapIY[ nY ]; nLineRange = 0; } else { nLineStart = rCtx.maMapIY[ nTop ] ; nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] ); } Scanline pScanDest = rCtx.mpDest->GetScanline( nY ); for( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ ) { long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX; long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ; long nRowStart, nRowRange; if( nX == nEndX ) { nRowStart = rCtx.maMapIX[ nX ]; nRowRange = 0; } else { nRowStart = rCtx.maMapIX[ nLeft ]; nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] ); } int nSumR = 0; int nSumG = 0; int nSumB = 0; BilinearWeightType nTotalWeightY = 0; for(long i = 0; i<= nLineRange; i++) { int nSumRowR = 0; int nSumRowG = 0; int nSumRowB = 0; BilinearWeightType nTotalWeightX = 0; Scanline pScanlineSrc = rCtx.mpSrc->GetScanline( nLineStart + i ); for(long j = 0; j <= nRowRange; j++) { BitmapColor aCol0 = rCtx.mpSrc->GetPixelFromData( pScanlineSrc, nRowStart + j ); if(nX == nEndX ) { nSumRowB += aCol0.GetBlue() << MAP_PRECISION; nSumRowG += aCol0.GetGreen() << MAP_PRECISION; nSumRowR += aCol0.GetRed() << MAP_PRECISION; nTotalWeightX += lclMaxWeight(); } else if( j == 0 ) { BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ]; nSumRowB += ( nWeightX *aCol0.GetBlue()) ; nSumRowG += ( nWeightX *aCol0.GetGreen()) ; nSumRowR += ( nWeightX *aCol0.GetRed()) ; nTotalWeightX += nWeightX; } else if ( nRowRange == j ) { BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ; nSumRowB += ( nWeightX *aCol0.GetBlue() ); nSumRowG += ( nWeightX *aCol0.GetGreen() ); nSumRowR += ( nWeightX *aCol0.GetRed() ); nTotalWeightX += nWeightX; } else { nSumRowB += aCol0.GetBlue() << MAP_PRECISION; nSumRowG += aCol0.GetGreen() << MAP_PRECISION; nSumRowR += aCol0.GetRed() << MAP_PRECISION; nTotalWeightX += lclMaxWeight(); } } BilinearWeightType nWeightY = lclMaxWeight(); if( nY == nEndY ) nWeightY = lclMaxWeight(); else if( i == 0 ) nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ]; else if( nLineRange == 1 ) nWeightY = rCtx.maMapFY[ nTop ]; else if ( nLineRange == i ) nWeightY = rCtx.maMapFY[ nBottom ]; if (nTotalWeightX) { nSumRowB /= nTotalWeightX; nSumRowG /= nTotalWeightX; nSumRowR /= nTotalWeightX; } nSumB += nWeightY * nSumRowB; nSumG += nWeightY * nSumRowG; nSumR += nWeightY * nSumRowR; nTotalWeightY += nWeightY; } if (nTotalWeightY) { nSumR /= nTotalWeightY; nSumG /= nTotalWeightY; nSumB /= nTotalWeightY; } BitmapColor aColRes(static_cast(nSumR), static_cast(nSumG), static_cast(nSumB)); rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes ); } } } } // end anonymous namespace BitmapScaleSuperFilter::BitmapScaleSuperFilter(const double& rScaleX, const double& rScaleY) : mrScaleX(rScaleX), mrScaleY(rScaleY) {} BitmapScaleSuperFilter::~BitmapScaleSuperFilter() {} BitmapEx BitmapScaleSuperFilter::execute(BitmapEx const& rBitmap) const { Bitmap aBitmap(rBitmap.GetBitmap()); bool bRet = false; const Size aSizePix(rBitmap.GetSizePixel()); bool bHMirr = mrScaleX < 0; bool bVMirr = mrScaleY < 0; double fScaleX = std::fabs(mrScaleX); double fScaleY = std::fabs(mrScaleY); const long nDstW = FRound(aSizePix.Width() * fScaleX); const long nDstH = FRound(aSizePix.Height() * fScaleY); const double fScaleThresh = 0.6; if (nDstW <= 1 || nDstH <= 1) return BitmapEx(); // check cache for a previously scaled version of this ScaleCacheKey aKey(aBitmap.ImplGetSalBitmap().get(), Size(nDstW, nDstH)); ImplSVData* pSVData = ImplGetSVData(); auto& rCache = pSVData->maGDIData.maScaleCache; auto aFind = rCache.find(aKey); if (aFind != rCache.end()) { if (aFind->second.GetSizePixel().Width() == nDstW && aFind->second.GetSizePixel().Height() == nDstH) return aFind->second; else SAL_WARN("vcl.gdi", "Error: size mismatch in scale cache"); } { Bitmap::ScopedReadAccess pReadAccess(aBitmap); sal_uInt16 nSourceBitcount = aBitmap.GetBitCount(); Bitmap aOutBmp(Size(nDstW, nDstH), std::max(nSourceBitcount, sal_uInt16(24))); Size aOutSize = aOutBmp.GetSizePixel(); sal_uInt16 nTargetBitcount = aOutBmp.GetBitCount(); if (!aOutSize.Width() || !aOutSize.Height()) { SAL_WARN("vcl.gdi", "bmp creation failed"); return BitmapEx(); } BitmapScopedWriteAccess pWriteAccess(aOutBmp); const long nStartY = 0; const long nEndY = nDstH - 1; if (pReadAccess && pWriteAccess) { ScaleRangeFn pScaleRangeFn; ScaleContext aContext( pReadAccess.get(), pWriteAccess.get(), pReadAccess->Width(), pWriteAccess->Width(), pReadAccess->Height(), pWriteAccess->Height(), bVMirr, bHMirr ); bool bScaleUp = fScaleX >= fScaleThresh && fScaleY >= fScaleThresh; // If we have a source bitmap with a palette the scaling converts // from up to 8 bit image -> 24 bit non-palette, which is then // adapted back to the same type as original. if (pReadAccess->HasPalette()) { switch( pReadAccess->GetScanlineFormat() ) { case ScanlineFormat::N8BitPal: pScaleRangeFn = bScaleUp ? scaleUpPalette8bit : scaleDownPalette8bit; break; default: pScaleRangeFn = bScaleUp ? scaleUpPaletteGeneral : scaleDownPaletteGeneral; break; } } // Here we know that we are dealing with a non-palette source bitmap. // The target is either 24 or 32 bit, depending on the image and // the capabilities of the backend. If for some reason the destination // is not the same bit-depth as the source, then we can't use // a fast path, so we always need to process with a general scaler. else if (nSourceBitcount != nTargetBitcount) { pScaleRangeFn = bScaleUp ? scaleUpNonPaletteGeneral : scaleDownNonPaletteGeneral; } // If we get here then we can only use a fast path, but let's // still keep the fallback to the general scaler alive. else { switch( pReadAccess->GetScanlineFormat() ) { case ScanlineFormat::N24BitTcBgr: case ScanlineFormat::N24BitTcRgb: pScaleRangeFn = bScaleUp ? scaleUp24bit : scaleDown24bit; break; case ScanlineFormat::N32BitTcRgba: case ScanlineFormat::N32BitTcBgra: case ScanlineFormat::N32BitTcArgb: case ScanlineFormat::N32BitTcAbgr: pScaleRangeFn = bScaleUp ? scaleUp32bit : scaleDown32bit; break; default: pScaleRangeFn = bScaleUp ? scaleUpNonPaletteGeneral : scaleDownNonPaletteGeneral; break; } } // We want to thread - only if there is a lot of work to do: // We work hard when there is a large destination image, or // A large source image. bool bHorizontalWork = pReadAccess->Height() >= 512 && pReadAccess->Width() >= 512; bool bUseThreads = true; static bool bDisableThreadedScaling = getenv ("VCL_NO_THREAD_SCALE"); if (bDisableThreadedScaling || !bHorizontalWork) { SAL_INFO("vcl.gdi", "Scale in main thread"); bUseThreads = false; } if (bUseThreads) { try { // partition and queue work comphelper::ThreadPool &rShared = comphelper::ThreadPool::getSharedOptimalPool(); std::shared_ptr pTag = comphelper::ThreadPool::createThreadTaskTag(); long nStripYStart = nStartY; long nStripYEnd = nStripYStart + constScaleThreadStrip - 1; while (nStripYEnd < nEndY) { std::unique_ptr pTask(new ScaleTask(pTag, pScaleRangeFn, aContext, nStripYStart, nStripYEnd)); rShared.pushTask(std::move(pTask)); nStripYStart += constScaleThreadStrip; nStripYEnd += constScaleThreadStrip; } if (nStripYStart <= nEndY) { std::unique_ptr pTask(new ScaleTask(pTag, pScaleRangeFn, aContext, nStripYStart, nEndY)); rShared.pushTask(std::move(pTask)); } rShared.waitUntilDone(pTag); SAL_INFO("vcl.gdi", "All threaded scaling tasks complete"); } catch (...) { SAL_WARN("vcl.gdi", "threaded bitmap scaling failed"); bUseThreads = false; } } if (!bUseThreads) pScaleRangeFn( aContext, nStartY, nEndY ); pWriteAccess.reset(); bRet = true; aBitmap.AdaptBitCount(aOutBmp); aBitmap = aOutBmp; } } if (bRet) { tools::Rectangle aRect(Point(0, 0), Point(nDstW, nDstH)); aBitmap.Crop(aRect); BitmapEx aRet(aBitmap); rCache.insert(std::make_pair(aKey, aRet)); return aRet; } return BitmapEx(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */