1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <comphelper/profilezone.hxx>
21 #include <comphelper/threadpool.hxx>
22 #include <tools/helpers.hxx>
24 #include <vcl/BitmapWriteAccess.hxx>
25 #include <bitmap/BitmapScaleSuperFilter.hxx>
30 #include <sal/log.hxx>
33 A scaling algorithm that uses bilinear if not downscaling too much,
34 and averaging otherwise (bilinear would produce poor results for big downscaling).
36 By default the combination of two filters is used: bilinear and averaging algorithm.
37 Bilinear filtering is used for bitmap enlarging and shrinking till factor 0.6. Below
38 this bilinear gives bad results because of limited sampling. For such cases averaging
39 is used which is a simple algorithm for shrinking. In averaging the algorithm
40 calculates the average of samples which result is the new pixel.
45 constexpr int MAP_PRECISION
= 7;
47 typedef sal_Int32 BilinearWeightType
;
49 constexpr BilinearWeightType
lclMaxWeight()
51 return BilinearWeightType(1) << MAP_PRECISION
;
54 constexpr sal_uInt8
MAP(sal_uInt8 cVal0
, sal_uInt8 cVal1
, BilinearWeightType nFrac
)
56 return sal_uInt8(((BilinearWeightType(cVal0
) << MAP_PRECISION
) + nFrac
* (BilinearWeightType(cVal1
) - BilinearWeightType(cVal0
))) >> MAP_PRECISION
);
61 BitmapReadAccess
* mpSrc
;
62 BitmapWriteAccess
* mpDest
;
66 std::vector
<sal_Int32
> maMapIX
;
67 std::vector
<sal_Int32
> maMapIY
;
68 std::vector
<BilinearWeightType
> maMapFX
;
69 std::vector
<BilinearWeightType
> maMapFY
;
71 ScaleContext( BitmapReadAccess
*pSrc
,
72 BitmapWriteAccess
*pDest
,
73 sal_Int32 nSrcW
, sal_Int32 nDestW
,
74 sal_Int32 nSrcH
, sal_Int32 nDestH
,
75 bool bHMirr
, bool bVMirr
)
86 generateMap(nSrcW
, nDestW
, bHMirr
, maMapIX
, maMapFX
);
87 generateMap(nSrcH
, nDestH
, bVMirr
, maMapIY
, maMapFY
);
90 static void generateMap(sal_Int32 nSourceLength
, sal_Int32 nDestinationLength
, bool bMirrored
,
91 std::vector
<sal_Int32
> & rMapIX
, std::vector
<BilinearWeightType
> & rMapFX
)
93 const double fRevScale
= (nDestinationLength
> 1) ? double(nSourceLength
- 1) / (nDestinationLength
- 1) : 0.0;
95 sal_Int32 nTemp
= nSourceLength
- 2;
96 sal_Int32 nTempX
= nSourceLength
- 1;
98 for (sal_Int32 i
= 0; i
< nDestinationLength
; i
++)
100 double fTemp
= i
* fRevScale
;
102 fTemp
= nTempX
- fTemp
;
103 rMapIX
[i
] = std::clamp(sal_Int32(fTemp
), sal_Int32(0), nTemp
);
104 rMapFX
[i
] = BilinearWeightType((fTemp
- rMapIX
[i
]) * (BilinearWeightType(1) << MAP_PRECISION
));
109 constexpr sal_Int32 constScaleThreadStrip
= 32;
111 typedef void (*ScaleRangeFn
)(const ScaleContext
& rContext
, sal_Int32 nStartY
, sal_Int32 nEndY
);
113 template <size_t nSize
> struct ScaleFunc
117 static inline void generateSumRows(Scanline
& pTmpX
, std::array
<int, nSize
>& sumRows
)
119 for (int& n
: sumRows
)
120 n
+= (*pTmpX
++) << MAP_PRECISION
;
123 static inline void generateSumRows(BilinearWeightType
const nWeightX
, Scanline
& pTmpX
,
124 std::array
<int, nSize
>& sumRows
)
126 for (int& n
: sumRows
)
127 n
+= (nWeightX
* (*pTmpX
++));
130 static inline void generateSumRows(BilinearWeightType
const nTotalWeightX
,
131 std::array
<int, nSize
>& sumRows
)
133 for (int& n
: sumRows
)
137 static inline void generateSumNumbers(BilinearWeightType
const nWeightY
,
138 std::array
<int, nSize
>& sumRows
,
139 std::array
<int, nSize
>& sumNumbers
)
141 std::transform(sumRows
.begin(), sumRows
.end(), sumNumbers
.begin(), sumNumbers
.begin(),
142 [nWeightY
](int n1
, int n2
) { return nWeightY
* n1
+ n2
; });
145 static inline void generateSumNumbers(BilinearWeightType
const nTotalWeightY
,
146 std::array
<int, nSize
>& sumNumbers
)
148 for (int& n
: sumNumbers
)
152 static inline void calculateDestination(Scanline
& pScanDest
, std::array
<int, nSize
>& sumNumbers
)
154 pScanDest
= std::copy(sumNumbers
.begin(), sumNumbers
.end(), pScanDest
);
159 static inline void generateComponent(Scanline pColorPtr0
, Scanline pColorPtr1
,
160 BilinearWeightType
const nTempFX
,
161 std::array
<sal_uInt8
, nSize
>& nComponents
)
163 for (sal_uInt8
& rComponent
: nComponents
)
164 rComponent
= MAP(*pColorPtr0
++, *pColorPtr1
++, nTempFX
);
167 static inline void calculateDestination(Scanline
& pScanDest
, BilinearWeightType
const nTempFY
,
168 const std::array
<sal_uInt8
, nSize
>& nComponents1
,
169 const std::array
<sal_uInt8
, nSize
>& nComponents2
)
171 pScanDest
= std::transform(
172 nComponents1
.begin(), nComponents1
.end(), nComponents2
.begin(), pScanDest
,
173 [nTempFY
](sal_uInt8 c1
, sal_uInt8 c2
) { return MAP(c1
, c2
, nTempFY
); });
177 template <int nColorBits
>
178 void scaleDown (const ScaleContext
&rCtx
, sal_Int32 nStartY
, sal_Int32 nEndY
)
180 comphelper::ProfileZone
pz("BitmapScaleSuperFilter::scaleDown");
181 constexpr int nColorComponents
= nColorBits
/ 8;
182 static_assert(nColorComponents
* 8 == nColorBits
, "nColorBits must be divisible by 8");
183 using ScaleFunction
= ScaleFunc
<nColorComponents
>;
184 const sal_Int32 nStartX
= 0;
185 const sal_Int32 nEndX
= rCtx
.mnDestW
- 1;
187 for (sal_Int32 nY
= nStartY
; nY
<= nEndY
; nY
++)
189 sal_Int32 nTop
= rCtx
.mbVMirr
? (nY
+ 1) : nY
;
190 sal_Int32 nBottom
= rCtx
.mbVMirr
? nY
: (nY
+ 1);
192 sal_Int32 nLineStart
;
193 sal_Int32 nLineRange
;
196 nLineStart
= rCtx
.maMapIY
[nY
];
201 nLineStart
= rCtx
.maMapIY
[nTop
];
202 nLineRange
= (rCtx
.maMapIY
[nBottom
] == rCtx
.maMapIY
[nTop
]) ?
203 1 : (rCtx
.maMapIY
[nBottom
] - rCtx
.maMapIY
[nTop
]);
206 Scanline pScanDest
= rCtx
.mpDest
->GetScanline(nY
);
207 for (sal_Int32 nX
= nStartX
; nX
<= nEndX
; nX
++)
209 sal_Int32 nLeft
= rCtx
.mbHMirr
? (nX
+ 1) : nX
;
210 sal_Int32 nRight
= rCtx
.mbHMirr
? nX
: (nX
+ 1);
216 nRowStart
= rCtx
.maMapIX
[nX
];
221 nRowStart
= rCtx
.maMapIX
[nLeft
];
222 nRowRange
= (rCtx
.maMapIX
[nRight
] == rCtx
.maMapIX
[nLeft
]) ?
223 1 : (rCtx
.maMapIX
[nRight
] - rCtx
.maMapIX
[nLeft
]);
226 std::array
<int, nColorComponents
> sumNumbers
{}; // zero-initialize
227 BilinearWeightType nTotalWeightY
= 0;
229 for (sal_Int32 i
= 0; i
<= nLineRange
; i
++)
231 Scanline pTmpY
= rCtx
.mpSrc
->GetScanline(nLineStart
+ i
);
232 Scanline pTmpX
= pTmpY
+ nColorComponents
* nRowStart
;
234 std::array
<int, nColorComponents
> sumRows
{}; // zero-initialize
235 BilinearWeightType nTotalWeightX
= 0;
237 for (sal_Int32 j
= 0; j
<= nRowRange
; j
++)
241 ScaleFunction::generateSumRows(pTmpX
, sumRows
);
242 nTotalWeightX
+= lclMaxWeight();
246 BilinearWeightType nWeightX
= lclMaxWeight() - rCtx
.maMapFX
[nLeft
];
247 ScaleFunction::generateSumRows(nWeightX
, pTmpX
, sumRows
);
248 nTotalWeightX
+= nWeightX
;
250 else if ( nRowRange
== j
)
252 BilinearWeightType nWeightX
= rCtx
.maMapFX
[ nRight
] ;
253 ScaleFunction::generateSumRows(nWeightX
, pTmpX
, sumRows
);
254 nTotalWeightX
+= nWeightX
;
258 ScaleFunction::generateSumRows(pTmpX
, sumRows
);
259 nTotalWeightX
+= lclMaxWeight();
263 BilinearWeightType nWeightY
= lclMaxWeight();
265 nWeightY
= lclMaxWeight();
267 nWeightY
= lclMaxWeight() - rCtx
.maMapFY
[nTop
];
268 else if (nLineRange
== 1)
269 nWeightY
= rCtx
.maMapFY
[nTop
];
270 else if (nLineRange
== i
)
271 nWeightY
= rCtx
.maMapFY
[nBottom
];
275 ScaleFunction::generateSumRows(nTotalWeightX
, sumRows
);
277 ScaleFunction::generateSumNumbers(nWeightY
, sumRows
, sumNumbers
);
278 nTotalWeightY
+= nWeightY
;
284 ScaleFunction::generateSumNumbers(nTotalWeightY
, sumNumbers
);
287 // Write the calculated color components to the destination
288 ScaleFunction::calculateDestination(pScanDest
, sumNumbers
);
293 template <int nColorBits
>
294 void scaleUp(const ScaleContext
&rCtx
, sal_Int32 nStartY
, sal_Int32 nEndY
)
296 comphelper::ProfileZone
pz("BitmapScaleSuperFilter::scaleUp");
297 constexpr int nColorComponents
= nColorBits
/ 8;
298 static_assert(nColorComponents
* 8 == nColorBits
, "nColorBits must be divisible by 8");
299 using ScaleFunction
= ScaleFunc
<nColorComponents
>;
300 const sal_Int32 nStartX
= 0;
301 const sal_Int32 nEndX
= rCtx
.mnDestW
- 1;
303 for (sal_Int32 nY
= nStartY
; nY
<= nEndY
; nY
++)
305 sal_Int32 nTempY
= rCtx
.maMapIY
[nY
];
306 BilinearWeightType nTempFY
= rCtx
.maMapFY
[nY
];
308 Scanline pLine0
= rCtx
.mpSrc
->GetScanline(nTempY
+0);
309 Scanline pLine1
= rCtx
.mpSrc
->GetScanline(nTempY
+1);
310 Scanline pScanDest
= rCtx
.mpDest
->GetScanline(nY
);
312 std::array
<sal_uInt8
, nColorComponents
> nComponents1
; // no need to initialize since it's
313 std::array
<sal_uInt8
, nColorComponents
> nComponents2
; // initialized in generateComponent
318 for (sal_Int32 nX
= nStartX
; nX
<= nEndX
; nX
++)
320 sal_Int32 nTempX
= rCtx
.maMapIX
[nX
];
321 BilinearWeightType nTempFX
= rCtx
.maMapFX
[nX
];
323 pColorPtr0
= pLine0
+ nTempX
* nColorComponents
;
324 pColorPtr1
= pColorPtr0
+ nColorComponents
;
326 ScaleFunction::generateComponent(pColorPtr0
, pColorPtr1
, nTempFX
, nComponents1
);
328 pColorPtr0
= pLine1
+ nTempX
* nColorComponents
;
329 pColorPtr1
= pColorPtr0
+ nColorComponents
;
331 ScaleFunction::generateComponent(pColorPtr0
, pColorPtr1
, nTempFX
, nComponents2
);
332 ScaleFunction::calculateDestination(pScanDest
, nTempFY
, nComponents1
, nComponents2
);
337 class ScaleTask
: public comphelper::ThreadTask
339 ScaleRangeFn mpScaleRangeFunction
;
340 const ScaleContext
& mrContext
;
345 explicit ScaleTask(const std::shared_ptr
<comphelper::ThreadTaskTag
>& pTag
,
346 ScaleRangeFn pScaleRangeFunction
,
347 const ScaleContext
& rContext
,
348 sal_Int32 nStartY
, sal_Int32 nEndY
)
349 : comphelper::ThreadTask(pTag
)
350 , mpScaleRangeFunction(pScaleRangeFunction
)
351 , mrContext(rContext
)
356 virtual void doWork() override
358 mpScaleRangeFunction(mrContext
, mnStartY
, mnEndY
);
362 void scaleUpPalette8bit(const ScaleContext
&rCtx
, sal_Int32 nStartY
, sal_Int32 nEndY
)
364 const sal_Int32 nStartX
= 0, nEndX
= rCtx
.mnDestW
- 1;
366 for( sal_Int32 nY
= nStartY
; nY
<= nEndY
; nY
++ )
368 sal_Int32 nTempY
= rCtx
.maMapIY
[ nY
];
369 BilinearWeightType nTempFY
= rCtx
.maMapFY
[ nY
];
370 Scanline pLine0
= rCtx
.mpSrc
->GetScanline( nTempY
);
371 Scanline pLine1
= rCtx
.mpSrc
->GetScanline( ++nTempY
);
372 Scanline pScanDest
= rCtx
.mpDest
->GetScanline( nY
);
374 for(sal_Int32 nX
= nStartX
, nXDst
= 0; nX
<= nEndX
; nX
++ )
376 sal_Int32 nTempX
= rCtx
.maMapIX
[ nX
];
377 BilinearWeightType nTempFX
= rCtx
.maMapFX
[ nX
];
379 const BitmapColor
& rCol0
= rCtx
.mpSrc
->GetPaletteColor( pLine0
[ nTempX
] );
380 const BitmapColor
& rCol2
= rCtx
.mpSrc
->GetPaletteColor( pLine1
[ nTempX
] );
381 const BitmapColor
& rCol1
= rCtx
.mpSrc
->GetPaletteColor( pLine0
[ ++nTempX
] );
382 const BitmapColor
& rCol3
= rCtx
.mpSrc
->GetPaletteColor( pLine1
[ nTempX
] );
384 sal_uInt8 cR0
= MAP( rCol0
.GetRed(), rCol1
.GetRed(), nTempFX
);
385 sal_uInt8 cG0
= MAP( rCol0
.GetGreen(), rCol1
.GetGreen(), nTempFX
);
386 sal_uInt8 cB0
= MAP( rCol0
.GetBlue(), rCol1
.GetBlue(), nTempFX
);
388 sal_uInt8 cR1
= MAP( rCol2
.GetRed(), rCol3
.GetRed(), nTempFX
);
389 sal_uInt8 cG1
= MAP( rCol2
.GetGreen(), rCol3
.GetGreen(), nTempFX
);
390 sal_uInt8 cB1
= MAP( rCol2
.GetBlue(), rCol3
.GetBlue(), nTempFX
);
392 BitmapColor
aColRes( MAP( cR0
, cR1
, nTempFY
),
393 MAP( cG0
, cG1
, nTempFY
),
394 MAP( cB0
, cB1
, nTempFY
) );
395 rCtx
.mpDest
->SetPixelOnData( pScanDest
, nXDst
++, aColRes
);
400 void scaleUpPaletteGeneral(const ScaleContext
&rCtx
, sal_Int32 nStartY
, sal_Int32 nEndY
)
402 const sal_Int32 nStartX
= 0, nEndX
= rCtx
.mnDestW
- 1;
404 for( sal_Int32 nY
= nStartY
; nY
<= nEndY
; nY
++ )
406 sal_Int32 nTempY
= rCtx
.maMapIY
[ nY
];
407 BilinearWeightType nTempFY
= rCtx
.maMapFY
[ nY
];
408 Scanline pScanline
= rCtx
.mpDest
->GetScanline( nY
);
410 for( sal_Int32 nX
= nStartX
, nXDst
= 0; nX
<= nEndX
; nX
++ )
412 sal_Int32 nTempX
= rCtx
.maMapIX
[ nX
];
413 BilinearWeightType nTempFX
= rCtx
.maMapFX
[ nX
];
415 BitmapColor aCol0
= rCtx
.mpSrc
->GetPaletteColor( rCtx
.mpSrc
->GetPixelIndex( nTempY
, nTempX
) );
416 BitmapColor aCol1
= rCtx
.mpSrc
->GetPaletteColor( rCtx
.mpSrc
->GetPixelIndex( nTempY
, ++nTempX
) );
417 sal_uInt8 cR0
= MAP( aCol0
.GetRed(), aCol1
.GetRed(), nTempFX
);
418 sal_uInt8 cG0
= MAP( aCol0
.GetGreen(), aCol1
.GetGreen(), nTempFX
);
419 sal_uInt8 cB0
= MAP( aCol0
.GetBlue(), aCol1
.GetBlue(), nTempFX
);
421 aCol1
= rCtx
.mpSrc
->GetPaletteColor( rCtx
.mpSrc
->GetPixelIndex( ++nTempY
, nTempX
) );
422 aCol0
= rCtx
.mpSrc
->GetPaletteColor( rCtx
.mpSrc
->GetPixelIndex( nTempY
--, --nTempX
) );
423 sal_uInt8 cR1
= MAP( aCol0
.GetRed(), aCol1
.GetRed(), nTempFX
);
424 sal_uInt8 cG1
= MAP( aCol0
.GetGreen(), aCol1
.GetGreen(), nTempFX
);
425 sal_uInt8 cB1
= MAP( aCol0
.GetBlue(), aCol1
.GetBlue(), nTempFX
);
427 BitmapColor
aColRes( MAP( cR0
, cR1
, nTempFY
),
428 MAP( cG0
, cG1
, nTempFY
),
429 MAP( cB0
, cB1
, nTempFY
) );
430 rCtx
.mpDest
->SetPixelOnData( pScanline
, nXDst
++, aColRes
);
435 void scaleUpNonPaletteGeneral(const ScaleContext
&rCtx
, sal_Int32 nStartY
, sal_Int32 nEndY
)
437 const sal_Int32 nStartX
= 0, nEndX
= rCtx
.mnDestW
- 1;
439 for( sal_Int32 nY
= nStartY
; nY
<= nEndY
; nY
++ )
441 sal_Int32 nTempY
= rCtx
.maMapIY
[ nY
];
442 BilinearWeightType nTempFY
= rCtx
.maMapFY
[ nY
];
443 Scanline pScanDest
= rCtx
.mpDest
->GetScanline( nY
);
445 for( sal_Int32 nX
= nStartX
, nXDst
= 0; nX
<= nEndX
; nX
++ )
447 sal_Int32 nTempX
= rCtx
.maMapIX
[ nX
];
448 BilinearWeightType nTempFX
= rCtx
.maMapFX
[ nX
];
450 BitmapColor aCol0
= rCtx
.mpSrc
->GetPixel( nTempY
, nTempX
);
451 BitmapColor aCol1
= rCtx
.mpSrc
->GetPixel( nTempY
, ++nTempX
);
452 sal_uInt8 cR0
= MAP( aCol0
.GetRed(), aCol1
.GetRed(), nTempFX
);
453 sal_uInt8 cG0
= MAP( aCol0
.GetGreen(), aCol1
.GetGreen(), nTempFX
);
454 sal_uInt8 cB0
= MAP( aCol0
.GetBlue(), aCol1
.GetBlue(), nTempFX
);
456 aCol1
= rCtx
.mpSrc
->GetPixel( ++nTempY
, nTempX
);
457 aCol0
= rCtx
.mpSrc
->GetPixel( nTempY
--, --nTempX
);
458 sal_uInt8 cR1
= MAP( aCol0
.GetRed(), aCol1
.GetRed(), nTempFX
);
459 sal_uInt8 cG1
= MAP( aCol0
.GetGreen(), aCol1
.GetGreen(), nTempFX
);
460 sal_uInt8 cB1
= MAP( aCol0
.GetBlue(), aCol1
.GetBlue(), nTempFX
);
462 BitmapColor
aColRes( MAP( cR0
, cR1
, nTempFY
),
463 MAP( cG0
, cG1
, nTempFY
),
464 MAP( cB0
, cB1
, nTempFY
) );
465 rCtx
.mpDest
->SetPixelOnData( pScanDest
, nXDst
++, aColRes
);
470 void scaleDownPalette8bit(const ScaleContext
&rCtx
, sal_Int32 nStartY
, sal_Int32 nEndY
)
472 const sal_Int32 nStartX
= 0, nEndX
= rCtx
.mnDestW
- 1;
474 for( sal_Int32 nY
= nStartY
; nY
<= nEndY
; nY
++ )
476 sal_Int32 nTop
= rCtx
.mbVMirr
? ( nY
+ 1 ) : nY
;
477 sal_Int32 nBottom
= rCtx
.mbVMirr
? nY
: ( nY
+ 1 ) ;
479 sal_Int32 nLineStart
, nLineRange
;
482 nLineStart
= rCtx
.maMapIY
[ nY
];
487 nLineStart
= rCtx
.maMapIY
[ nTop
] ;
488 nLineRange
= ( rCtx
.maMapIY
[ nBottom
] == rCtx
.maMapIY
[ nTop
] ) ? 1 :( rCtx
.maMapIY
[ nBottom
] - rCtx
.maMapIY
[ nTop
] );
491 Scanline pScanDest
= rCtx
.mpDest
->GetScanline( nY
);
492 for( sal_Int32 nX
= nStartX
, nXDst
= 0; nX
<= nEndX
; nX
++ )
494 sal_Int32 nLeft
= rCtx
.mbHMirr
? ( nX
+ 1 ) : nX
;
495 sal_Int32 nRight
= rCtx
.mbHMirr
? nX
: ( nX
+ 1 ) ;
501 nRowStart
= rCtx
.maMapIX
[ nX
];
506 nRowStart
= rCtx
.maMapIX
[ nLeft
];
507 nRowRange
= ( rCtx
.maMapIX
[ nRight
] == rCtx
.maMapIX
[ nLeft
] )? 1 : ( rCtx
.maMapIX
[ nRight
] - rCtx
.maMapIX
[ nLeft
] );
513 BilinearWeightType nTotalWeightY
= 0;
515 for(sal_Int32 i
= 0; i
<= nLineRange
; i
++)
517 Scanline pTmpY
= rCtx
.mpSrc
->GetScanline( nLineStart
+ i
);
521 BilinearWeightType nTotalWeightX
= 0;
523 for(sal_Int32 j
= 0; j
<= nRowRange
; j
++)
525 const BitmapColor
& rCol
= rCtx
.mpSrc
->GetPaletteColor( pTmpY
[ nRowStart
+ j
] );
529 nSumRowB
+= rCol
.GetBlue() << MAP_PRECISION
;
530 nSumRowG
+= rCol
.GetGreen() << MAP_PRECISION
;
531 nSumRowR
+= rCol
.GetRed() << MAP_PRECISION
;
532 nTotalWeightX
+= lclMaxWeight();
536 BilinearWeightType nWeightX
= lclMaxWeight() - rCtx
.maMapFX
[ nLeft
];
537 nSumRowB
+= ( nWeightX
*rCol
.GetBlue()) ;
538 nSumRowG
+= ( nWeightX
*rCol
.GetGreen()) ;
539 nSumRowR
+= ( nWeightX
*rCol
.GetRed()) ;
540 nTotalWeightX
+= nWeightX
;
542 else if ( nRowRange
== j
)
544 BilinearWeightType nWeightX
= rCtx
.maMapFX
[ nRight
] ;
545 nSumRowB
+= ( nWeightX
*rCol
.GetBlue() );
546 nSumRowG
+= ( nWeightX
*rCol
.GetGreen() );
547 nSumRowR
+= ( nWeightX
*rCol
.GetRed() );
548 nTotalWeightX
+= nWeightX
;
552 nSumRowB
+= rCol
.GetBlue() << MAP_PRECISION
;
553 nSumRowG
+= rCol
.GetGreen() << MAP_PRECISION
;
554 nSumRowR
+= rCol
.GetRed() << MAP_PRECISION
;
555 nTotalWeightX
+= lclMaxWeight();
559 BilinearWeightType nWeightY
= lclMaxWeight();
561 nWeightY
= lclMaxWeight();
563 nWeightY
= lclMaxWeight() - rCtx
.maMapFY
[ nTop
];
564 else if( nLineRange
== 1 )
565 nWeightY
= rCtx
.maMapFY
[ nTop
];
566 else if ( nLineRange
== i
)
567 nWeightY
= rCtx
.maMapFY
[ nBottom
];
571 nSumRowB
/= nTotalWeightX
;
572 nSumRowG
/= nTotalWeightX
;
573 nSumRowR
/= nTotalWeightX
;
576 nSumB
+= nWeightY
* nSumRowB
;
577 nSumG
+= nWeightY
* nSumRowG
;
578 nSumR
+= nWeightY
* nSumRowR
;
579 nTotalWeightY
+= nWeightY
;
584 nSumR
/= nTotalWeightY
;
585 nSumG
/= nTotalWeightY
;
586 nSumB
/= nTotalWeightY
;
589 BitmapColor
aColRes(static_cast<sal_uInt8
>(nSumR
), static_cast<sal_uInt8
>(nSumG
), static_cast<sal_uInt8
>(nSumB
));
590 rCtx
.mpDest
->SetPixelOnData( pScanDest
, nXDst
++, aColRes
);
595 void scaleDownPaletteGeneral(const ScaleContext
&rCtx
, sal_Int32 nStartY
, sal_Int32 nEndY
)
597 const sal_Int32 nStartX
= 0, nEndX
= rCtx
.mnDestW
- 1;
599 for( sal_Int32 nY
= nStartY
; nY
<= nEndY
; nY
++ )
601 sal_Int32 nTop
= rCtx
.mbVMirr
? ( nY
+ 1 ) : nY
;
602 sal_Int32 nBottom
= rCtx
.mbVMirr
? nY
: ( nY
+ 1 ) ;
604 sal_Int32 nLineStart
, nLineRange
;
607 nLineStart
= rCtx
.maMapIY
[ nY
];
612 nLineStart
= rCtx
.maMapIY
[ nTop
] ;
613 nLineRange
= ( rCtx
.maMapIY
[ nBottom
] == rCtx
.maMapIY
[ nTop
] ) ? 1 :( rCtx
.maMapIY
[ nBottom
] - rCtx
.maMapIY
[ nTop
] );
616 Scanline pScanDest
= rCtx
.mpDest
->GetScanline( nY
);
617 for( sal_Int32 nX
= nStartX
, nXDst
= 0; nX
<= nEndX
; nX
++ )
619 sal_Int32 nLeft
= rCtx
.mbHMirr
? ( nX
+ 1 ) : nX
;
620 sal_Int32 nRight
= rCtx
.mbHMirr
? nX
: ( nX
+ 1 ) ;
622 sal_Int32 nRowStart
, nRowRange
;
625 nRowStart
= rCtx
.maMapIX
[ nX
];
630 nRowStart
= rCtx
.maMapIX
[ nLeft
];
631 nRowRange
= ( rCtx
.maMapIX
[ nRight
] == rCtx
.maMapIX
[ nLeft
] )? 1 : ( rCtx
.maMapIX
[ nRight
] - rCtx
.maMapIX
[ nLeft
] );
637 BilinearWeightType nTotalWeightY
= 0;
639 for(sal_Int32 i
= 0; i
<= nLineRange
; i
++)
644 BilinearWeightType nTotalWeightX
= 0;
646 Scanline pScanlineSrc
= rCtx
.mpSrc
->GetScanline( nLineStart
+ i
);
647 for(sal_Int32 j
= 0; j
<= nRowRange
; j
++)
649 BitmapColor aCol0
= rCtx
.mpSrc
->GetPaletteColor ( rCtx
.mpSrc
->GetIndexFromData( pScanlineSrc
, nRowStart
+ j
) );
654 nSumRowB
+= aCol0
.GetBlue() << MAP_PRECISION
;
655 nSumRowG
+= aCol0
.GetGreen() << MAP_PRECISION
;
656 nSumRowR
+= aCol0
.GetRed() << MAP_PRECISION
;
657 nTotalWeightX
+= lclMaxWeight();
662 BilinearWeightType nWeightX
= lclMaxWeight() - rCtx
.maMapFX
[ nLeft
];
663 nSumRowB
+= ( nWeightX
*aCol0
.GetBlue()) ;
664 nSumRowG
+= ( nWeightX
*aCol0
.GetGreen()) ;
665 nSumRowR
+= ( nWeightX
*aCol0
.GetRed()) ;
666 nTotalWeightX
+= nWeightX
;
668 else if ( nRowRange
== j
)
671 BilinearWeightType nWeightX
= rCtx
.maMapFX
[ nRight
] ;
672 nSumRowB
+= ( nWeightX
*aCol0
.GetBlue() );
673 nSumRowG
+= ( nWeightX
*aCol0
.GetGreen() );
674 nSumRowR
+= ( nWeightX
*aCol0
.GetRed() );
675 nTotalWeightX
+= nWeightX
;
680 nSumRowB
+= aCol0
.GetBlue() << MAP_PRECISION
;
681 nSumRowG
+= aCol0
.GetGreen() << MAP_PRECISION
;
682 nSumRowR
+= aCol0
.GetRed() << MAP_PRECISION
;
683 nTotalWeightX
+= lclMaxWeight();
687 sal_Int32 nWeightY
= lclMaxWeight();
689 nWeightY
= lclMaxWeight();
691 nWeightY
= lclMaxWeight() - rCtx
.maMapFY
[ nTop
];
692 else if( nLineRange
== 1 )
693 nWeightY
= rCtx
.maMapFY
[ nTop
];
694 else if ( nLineRange
== i
)
695 nWeightY
= rCtx
.maMapFY
[ nBottom
];
699 nSumRowB
/= nTotalWeightX
;
700 nSumRowG
/= nTotalWeightX
;
701 nSumRowR
/= nTotalWeightX
;
704 nSumB
+= nWeightY
* nSumRowB
;
705 nSumG
+= nWeightY
* nSumRowG
;
706 nSumR
+= nWeightY
* nSumRowR
;
707 nTotalWeightY
+= nWeightY
;
712 nSumR
/= nTotalWeightY
;
713 nSumG
/= nTotalWeightY
;
714 nSumB
/= nTotalWeightY
;
717 BitmapColor
aColRes(static_cast<sal_uInt8
>(nSumR
), static_cast<sal_uInt8
>(nSumG
), static_cast<sal_uInt8
>(nSumB
));
718 rCtx
.mpDest
->SetPixelOnData( pScanDest
, nXDst
++, aColRes
);
723 void scaleDownNonPaletteGeneral(const ScaleContext
&rCtx
, sal_Int32 nStartY
, sal_Int32 nEndY
)
725 const sal_Int32 nStartX
= 0, nEndX
= rCtx
.mnDestW
- 1;
727 for( sal_Int32 nY
= nStartY
; nY
<= nEndY
; nY
++ )
729 sal_Int32 nTop
= rCtx
.mbVMirr
? ( nY
+ 1 ) : nY
;
730 sal_Int32 nBottom
= rCtx
.mbVMirr
? nY
: ( nY
+ 1 ) ;
732 sal_Int32 nLineStart
, nLineRange
;
735 nLineStart
= rCtx
.maMapIY
[ nY
];
740 nLineStart
= rCtx
.maMapIY
[ nTop
] ;
741 nLineRange
= ( rCtx
.maMapIY
[ nBottom
] == rCtx
.maMapIY
[ nTop
] ) ? 1 :( rCtx
.maMapIY
[ nBottom
] - rCtx
.maMapIY
[ nTop
] );
744 Scanline pScanDest
= rCtx
.mpDest
->GetScanline( nY
);
745 for( sal_Int32 nX
= nStartX
, nXDst
= 0; nX
<= nEndX
; nX
++ )
747 sal_Int32 nLeft
= rCtx
.mbHMirr
? ( nX
+ 1 ) : nX
;
748 sal_Int32 nRight
= rCtx
.mbHMirr
? nX
: ( nX
+ 1 ) ;
750 sal_Int32 nRowStart
, nRowRange
;
753 nRowStart
= rCtx
.maMapIX
[ nX
];
758 nRowStart
= rCtx
.maMapIX
[ nLeft
];
759 nRowRange
= ( rCtx
.maMapIX
[ nRight
] == rCtx
.maMapIX
[ nLeft
] )? 1 : ( rCtx
.maMapIX
[ nRight
] - rCtx
.maMapIX
[ nLeft
] );
765 BilinearWeightType nTotalWeightY
= 0;
767 for(sal_Int32 i
= 0; i
<= nLineRange
; i
++)
772 BilinearWeightType nTotalWeightX
= 0;
774 Scanline pScanlineSrc
= rCtx
.mpSrc
->GetScanline( nLineStart
+ i
);
775 for(sal_Int32 j
= 0; j
<= nRowRange
; j
++)
777 BitmapColor aCol0
= rCtx
.mpSrc
->GetPixelFromData( pScanlineSrc
, nRowStart
+ j
);
782 nSumRowB
+= aCol0
.GetBlue() << MAP_PRECISION
;
783 nSumRowG
+= aCol0
.GetGreen() << MAP_PRECISION
;
784 nSumRowR
+= aCol0
.GetRed() << MAP_PRECISION
;
785 nTotalWeightX
+= lclMaxWeight();
790 BilinearWeightType nWeightX
= lclMaxWeight() - rCtx
.maMapFX
[ nLeft
];
791 nSumRowB
+= ( nWeightX
*aCol0
.GetBlue()) ;
792 nSumRowG
+= ( nWeightX
*aCol0
.GetGreen()) ;
793 nSumRowR
+= ( nWeightX
*aCol0
.GetRed()) ;
794 nTotalWeightX
+= nWeightX
;
796 else if ( nRowRange
== j
)
799 BilinearWeightType nWeightX
= rCtx
.maMapFX
[ nRight
] ;
800 nSumRowB
+= ( nWeightX
*aCol0
.GetBlue() );
801 nSumRowG
+= ( nWeightX
*aCol0
.GetGreen() );
802 nSumRowR
+= ( nWeightX
*aCol0
.GetRed() );
803 nTotalWeightX
+= nWeightX
;
807 nSumRowB
+= aCol0
.GetBlue() << MAP_PRECISION
;
808 nSumRowG
+= aCol0
.GetGreen() << MAP_PRECISION
;
809 nSumRowR
+= aCol0
.GetRed() << MAP_PRECISION
;
810 nTotalWeightX
+= lclMaxWeight();
814 BilinearWeightType nWeightY
= lclMaxWeight();
816 nWeightY
= lclMaxWeight();
818 nWeightY
= lclMaxWeight() - rCtx
.maMapFY
[ nTop
];
819 else if( nLineRange
== 1 )
820 nWeightY
= rCtx
.maMapFY
[ nTop
];
821 else if ( nLineRange
== i
)
822 nWeightY
= rCtx
.maMapFY
[ nBottom
];
826 nSumRowB
/= nTotalWeightX
;
827 nSumRowG
/= nTotalWeightX
;
828 nSumRowR
/= nTotalWeightX
;
831 nSumB
+= nWeightY
* nSumRowB
;
832 nSumG
+= nWeightY
* nSumRowG
;
833 nSumR
+= nWeightY
* nSumRowR
;
834 nTotalWeightY
+= nWeightY
;
839 nSumR
/= nTotalWeightY
;
840 nSumG
/= nTotalWeightY
;
841 nSumB
/= nTotalWeightY
;
844 BitmapColor
aColRes(static_cast<sal_uInt8
>(nSumR
), static_cast<sal_uInt8
>(nSumG
), static_cast<sal_uInt8
>(nSumB
));
845 rCtx
.mpDest
->SetPixelOnData( pScanDest
, nXDst
++, aColRes
);
850 } // end anonymous namespace
852 BitmapScaleSuperFilter::BitmapScaleSuperFilter(const double& rScaleX
, const double& rScaleY
) :
857 BitmapScaleSuperFilter::~BitmapScaleSuperFilter()
860 BitmapEx
BitmapScaleSuperFilter::execute(BitmapEx
const& rBitmap
) const
862 Bitmap
aBitmap(rBitmap
.GetBitmap());
865 const Size
aSizePix(rBitmap
.GetSizePixel());
867 bool bHMirr
= mrScaleX
< 0;
868 bool bVMirr
= mrScaleY
< 0;
870 double fScaleX
= std::fabs(mrScaleX
);
871 double fScaleY
= std::fabs(mrScaleY
);
873 const sal_Int32 nDstW
= basegfx::fround(aSizePix
.Width() * fScaleX
);
874 const sal_Int32 nDstH
= basegfx::fround(aSizePix
.Height() * fScaleY
);
876 constexpr double fScaleThresh
= 0.6;
878 if (nDstW
<= 1 || nDstH
<= 1)
881 // check cache for a previously scaled version of this
882 ScaleCacheKey
aKey(aBitmap
.ImplGetSalBitmap().get(),
885 ImplSVData
* pSVData
= ImplGetSVData();
886 auto& rCache
= pSVData
->maGDIData
.maScaleCache
;
887 auto aFind
= rCache
.find(aKey
);
888 if (aFind
!= rCache
.end())
890 if (aFind
->second
.GetSizePixel().Width() == nDstW
&& aFind
->second
.GetSizePixel().Height() == nDstH
)
891 return aFind
->second
;
893 SAL_WARN("vcl.gdi", "Error: size mismatch in scale cache");
897 BitmapScopedReadAccess
pReadAccess(aBitmap
);
899 // If source format is less than 24BPP, use 24BPP
900 auto eSourcePixelFormat
= aBitmap
.getPixelFormat();
901 auto ePixelFormat
= eSourcePixelFormat
;
902 if (sal_uInt16(eSourcePixelFormat
) < 24)
903 ePixelFormat
= vcl::PixelFormat::N24_BPP
;
905 Bitmap
aOutBmp(Size(nDstW
, nDstH
), ePixelFormat
);
906 Size aOutSize
= aOutBmp
.GetSizePixel();
907 auto eTargetPixelFormat
= aOutBmp
.getPixelFormat();
909 if (!aOutSize
.Width() || !aOutSize
.Height())
911 SAL_WARN("vcl.gdi", "bmp creation failed");
915 BitmapScopedWriteAccess
pWriteAccess(aOutBmp
);
917 const sal_Int32 nEndY
= nDstH
- 1;
919 if (pReadAccess
&& pWriteAccess
)
921 ScaleRangeFn pScaleRangeFn
;
922 const ScaleContext
aContext( pReadAccess
.get(),
924 pReadAccess
->Width(),
925 pWriteAccess
->Width(),
926 pReadAccess
->Height(),
927 pWriteAccess
->Height(),
930 bool bScaleUp
= fScaleX
>= fScaleThresh
&& fScaleY
>= fScaleThresh
;
931 // If we have a source bitmap with a palette the scaling converts
932 // from up to 8 bit image -> 24 bit non-palette, which is then
933 // adapted back to the same type as original.
934 if (pReadAccess
->HasPalette())
936 switch( pReadAccess
->GetScanlineFormat() )
938 case ScanlineFormat::N8BitPal
:
939 pScaleRangeFn
= bScaleUp
? scaleUpPalette8bit
940 : scaleDownPalette8bit
;
943 pScaleRangeFn
= bScaleUp
? scaleUpPaletteGeneral
944 : scaleDownPaletteGeneral
;
948 // Here we know that we are dealing with a non-palette source bitmap.
949 // The target is either 24 or 32 bit, depending on the image and
950 // the capabilities of the backend. If for some reason the destination
951 // is not the same bit-depth as the source, then we can't use
952 // a fast path, so we always need to process with a general scaler.
953 else if (eSourcePixelFormat
!= eTargetPixelFormat
)
955 pScaleRangeFn
= bScaleUp
? scaleUpNonPaletteGeneral
: scaleDownNonPaletteGeneral
;
957 // If we get here then we can only use a fast path, but let's
958 // still keep the fallback to the general scaler alive.
961 switch( pReadAccess
->GetScanlineFormat() )
963 case ScanlineFormat::N24BitTcBgr
:
964 case ScanlineFormat::N24BitTcRgb
:
965 pScaleRangeFn
= bScaleUp
? scaleUp
<24> : scaleDown
<24>;
967 case ScanlineFormat::N32BitTcRgba
:
968 case ScanlineFormat::N32BitTcBgra
:
969 case ScanlineFormat::N32BitTcArgb
:
970 case ScanlineFormat::N32BitTcAbgr
:
971 pScaleRangeFn
= bScaleUp
? scaleUp
<32> : scaleDown
<32>;
974 pScaleRangeFn
= bScaleUp
? scaleUpNonPaletteGeneral
975 : scaleDownNonPaletteGeneral
;
980 // We want to thread - only if there is a lot of work to do:
981 // We work hard when there is a large destination image, or
982 // A large source image.
983 bool bHorizontalWork
= pReadAccess
->Height() >= 512 && pReadAccess
->Width() >= 512;
984 bool bUseThreads
= true;
985 const sal_Int32 nStartY
= 0;
987 static bool bDisableThreadedScaling
= getenv ("VCL_NO_THREAD_SCALE");
988 if (bDisableThreadedScaling
|| !bHorizontalWork
)
990 SAL_INFO("vcl.gdi", "Scale in main thread");
998 // partition and queue work
999 comphelper::ThreadPool
&rShared
= comphelper::ThreadPool::getSharedOptimalPool();
1000 std::shared_ptr
<comphelper::ThreadTaskTag
> pTag
= comphelper::ThreadPool::createThreadTaskTag();
1002 vcl::bitmap::generateStripRanges
<constScaleThreadStrip
>(nStartY
, nEndY
,
1003 [&] (sal_Int32
const nStart
, sal_Int32
const nEnd
, bool const bLast
)
1007 auto pTask(std::make_unique
<ScaleTask
>(pTag
, pScaleRangeFn
, aContext
, nStart
, nEnd
));
1008 rShared
.pushTask(std::move(pTask
));
1011 pScaleRangeFn(aContext
, nStart
, nEnd
);
1013 rShared
.waitUntilDone(pTag
);
1014 SAL_INFO("vcl.gdi", "All threaded scaling tasks complete");
1018 SAL_WARN("vcl.gdi", "threaded bitmap scaling failed");
1019 bUseThreads
= false;
1024 pScaleRangeFn( aContext
, nStartY
, nEndY
);
1026 pWriteAccess
.reset();
1028 aBitmap
.AdaptBitCount(aOutBmp
);
1029 aBitmap
= std::move(aOutBmp
);
1035 tools::Rectangle
aRect(Point(0, 0), Point(nDstW
, nDstH
));
1036 aBitmap
.Crop(aRect
);
1037 BitmapEx
aRet(aBitmap
);
1038 rCache
.insert(std::make_pair(aKey
, aRet
));
1046 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */