LibreOffice Module vcl (master) 1
BitmapScaleSuperFilter.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
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/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
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 .
18 */
19
22#include <tools/helpers.hxx>
23
26
27#include <algorithm>
28#include <memory>
29#include <svdata.hxx>
30#include <sal/log.hxx>
31
32/*
33A scaling algorithm that uses bilinear if not downscaling too much,
34and averaging otherwise (bilinear would produce poor results for big downscaling).
35
36By default the combination of two filters is used: bilinear and averaging algorithm.
37Bilinear filtering is used for bitmap enlarging and shrinking till factor 0.6. Below
38this bilinear gives bad results because of limited sampling. For such cases averaging
39is used which is a simple algorithm for shrinking. In averaging the algorithm
40calculates the average of samples which result is the new pixel.
41*/
42
43namespace {
44
45constexpr int MAP_PRECISION = 7;
46
47typedef sal_Int32 BilinearWeightType;
48
49constexpr BilinearWeightType lclMaxWeight()
50{
51 return BilinearWeightType(1) << MAP_PRECISION;
52}
53
54constexpr sal_uInt8 MAP(sal_uInt8 cVal0, sal_uInt8 cVal1, BilinearWeightType nFrac)
55{
56 return sal_uInt8(((BilinearWeightType(cVal0) << MAP_PRECISION) + nFrac * (BilinearWeightType(cVal1) - BilinearWeightType(cVal0))) >> MAP_PRECISION);
57}
58
59struct ScaleContext
60{
61 BitmapReadAccess* mpSrc;
62 BitmapWriteAccess* mpDest;
63 sal_Int32 mnDestW;
64 bool mbHMirr;
65 bool mbVMirr;
66 std::vector<sal_Int32> maMapIX;
67 std::vector<sal_Int32> maMapIY;
68 std::vector<BilinearWeightType> maMapFX;
69 std::vector<BilinearWeightType> maMapFY;
70
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)
76 : mpSrc(pSrc)
77 , mpDest(pDest)
78 , mnDestW(nDestW)
79 , mbHMirr(bHMirr)
80 , mbVMirr(bVMirr)
81 , maMapIX(nDestW)
82 , maMapIY(nDestH)
83 , maMapFX(nDestW)
84 , maMapFY(nDestH)
85 {
86 generateMap(nSrcW, nDestW, bHMirr, maMapIX, maMapFX);
87 generateMap(nSrcH, nDestH, bVMirr, maMapIY, maMapFY);
88 }
89
90 static void generateMap(sal_Int32 nSourceLength, sal_Int32 nDestinationLength, bool bMirrored,
91 std::vector<sal_Int32> & rMapIX, std::vector<BilinearWeightType> & rMapFX)
92 {
93 const double fRevScale = (nDestinationLength > 1) ? double(nSourceLength - 1) / (nDestinationLength - 1) : 0.0;
94
95 sal_Int32 nTemp = nSourceLength - 2;
96 sal_Int32 nTempX = nSourceLength - 1;
97
98 for (sal_Int32 i = 0; i < nDestinationLength; i++)
99 {
100 double fTemp = i * fRevScale;
101 if (bMirrored)
102 fTemp = nTempX - fTemp;
103 rMapIX[i] = MinMax(sal_Int32(fTemp), 0, nTemp);
104 rMapFX[i] = BilinearWeightType((fTemp - rMapIX[i]) * (BilinearWeightType(1) << MAP_PRECISION));
105 }
106 }
107};
108
109constexpr sal_Int32 constScaleThreadStrip = 32;
110
111typedef void (*ScaleRangeFn)(const ScaleContext & rContext, sal_Int32 nStartY, sal_Int32 nEndY);
112
113template <size_t nSize> struct ScaleFunc
114{
115 // for scale down
116
117 static inline void generateSumRows(Scanline& pTmpX, std::array<int, nSize>& sumRows)
118 {
119 for (int& n : sumRows)
120 n += (*pTmpX++) << MAP_PRECISION;
121 }
122
123 static inline void generateSumRows(BilinearWeightType const nWeightX, Scanline& pTmpX,
124 std::array<int, nSize>& sumRows)
125 {
126 for (int& n : sumRows)
127 n += (nWeightX * (*pTmpX++));
128 }
129
130 static inline void generateSumRows(BilinearWeightType const nTotalWeightX,
131 std::array<int, nSize>& sumRows)
132 {
133 for (int& n : sumRows)
134 n /= nTotalWeightX;
135 }
136
137 static inline void generateSumNumbers(BilinearWeightType const nWeightY,
138 std::array<int, nSize>& sumRows,
139 std::array<int, nSize>& sumNumbers)
140 {
141 std::transform(sumRows.begin(), sumRows.end(), sumNumbers.begin(), sumNumbers.begin(),
142 [nWeightY](int n1, int n2) { return nWeightY * n1 + n2; });
143 }
144
145 static inline void generateSumNumbers(BilinearWeightType const nTotalWeightY,
146 std::array<int, nSize>& sumNumbers)
147 {
148 for (int& n : sumNumbers)
149 n /= nTotalWeightY;
150 }
151
152 static inline void calculateDestination(Scanline& pScanDest, std::array<int, nSize>& sumNumbers)
153 {
154 pScanDest = std::copy(sumNumbers.begin(), sumNumbers.end(), pScanDest);
155 }
156
157 // for scale up
158
159 static inline void generateComponent(Scanline pColorPtr0, Scanline pColorPtr1,
160 BilinearWeightType const nTempFX,
161 std::array<sal_uInt8, nSize>& nComponents)
162 {
163 for (sal_uInt8& rComponent : nComponents)
164 rComponent = MAP(*pColorPtr0++, *pColorPtr1++, nTempFX);
165 }
166
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)
170 {
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); });
174 }
175};
176
177template <int nColorBits>
178void scaleDown (const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
179{
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;
186
187 for (sal_Int32 nY = nStartY; nY <= nEndY; nY++)
188 {
189 sal_Int32 nTop = rCtx.mbVMirr ? (nY + 1) : nY;
190 sal_Int32 nBottom = rCtx.mbVMirr ? nY : (nY + 1);
191
192 sal_Int32 nLineStart;
193 sal_Int32 nLineRange;
194 if (nY == nEndY)
195 {
196 nLineStart = rCtx.maMapIY[nY];
197 nLineRange = 0;
198 }
199 else
200 {
201 nLineStart = rCtx.maMapIY[nTop];
202 nLineRange = (rCtx.maMapIY[nBottom] == rCtx.maMapIY[nTop]) ?
203 1 : (rCtx.maMapIY[nBottom] - rCtx.maMapIY[nTop]);
204 }
205
206 Scanline pScanDest = rCtx.mpDest->GetScanline(nY);
207 for (sal_Int32 nX = nStartX; nX <= nEndX; nX++)
208 {
209 sal_Int32 nLeft = rCtx.mbHMirr ? (nX + 1) : nX;
210 sal_Int32 nRight = rCtx.mbHMirr ? nX : (nX + 1);
211
212 sal_Int32 nRowStart;
213 sal_Int32 nRowRange;
214 if (nX == nEndX)
215 {
216 nRowStart = rCtx.maMapIX[nX];
217 nRowRange = 0;
218 }
219 else
220 {
221 nRowStart = rCtx.maMapIX[nLeft];
222 nRowRange = (rCtx.maMapIX[nRight] == rCtx.maMapIX[nLeft]) ?
223 1 : (rCtx.maMapIX[nRight] - rCtx.maMapIX[nLeft]);
224 }
225
226 std::array<int, nColorComponents> sumNumbers{}; // zero-initialize
227 BilinearWeightType nTotalWeightY = 0;
228
229 for (sal_Int32 i = 0; i<= nLineRange; i++)
230 {
231 Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i);
232 Scanline pTmpX = pTmpY + nColorComponents * nRowStart;
233
234 std::array<int, nColorComponents> sumRows{}; // zero-initialize
235 BilinearWeightType nTotalWeightX = 0;
236
237 for (sal_Int32 j = 0; j <= nRowRange; j++)
238 {
239 if (nX == nEndX)
240 {
241 ScaleFunction::generateSumRows(pTmpX, sumRows);
242 nTotalWeightX += lclMaxWeight();
243 }
244 else if(j == 0)
245 {
246 BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft];
247 ScaleFunction::generateSumRows(nWeightX, pTmpX, sumRows);
248 nTotalWeightX += nWeightX;
249 }
250 else if ( nRowRange == j )
251 {
252 BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
253 ScaleFunction::generateSumRows(nWeightX, pTmpX, sumRows);
254 nTotalWeightX += nWeightX;
255 }
256 else
257 {
258 ScaleFunction::generateSumRows(pTmpX, sumRows);
259 nTotalWeightX += lclMaxWeight();
260 }
261 }
262
263 BilinearWeightType nWeightY = lclMaxWeight();
264 if (nY == nEndY)
265 nWeightY = lclMaxWeight();
266 else if (i == 0)
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];
272
273 if (nTotalWeightX)
274 {
275 ScaleFunction::generateSumRows(nTotalWeightX, sumRows);
276 }
277 ScaleFunction::generateSumNumbers(nWeightY, sumRows, sumNumbers);
278 nTotalWeightY += nWeightY;
279
280 }
281
282 if (nTotalWeightY)
283 {
284 ScaleFunction::generateSumNumbers(nTotalWeightY, sumNumbers);
285 }
286
287 // Write the calculated color components to the destination
288 ScaleFunction::calculateDestination(pScanDest, sumNumbers);
289 }
290 }
291}
292
293template <int nColorBits>
294void scaleUp(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
295{
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;
302
303 for (sal_Int32 nY = nStartY; nY <= nEndY; nY++)
304 {
305 sal_Int32 nTempY = rCtx.maMapIY[nY];
306 BilinearWeightType nTempFY = rCtx.maMapFY[nY];
307
308 Scanline pLine0 = rCtx.mpSrc->GetScanline(nTempY+0);
309 Scanline pLine1 = rCtx.mpSrc->GetScanline(nTempY+1);
310 Scanline pScanDest = rCtx.mpDest->GetScanline(nY);
311
312 std::array<sal_uInt8, nColorComponents> nComponents1; // no need to initialize since it's
313 std::array<sal_uInt8, nColorComponents> nComponents2; // initialized in generateComponent
314
315 Scanline pColorPtr0;
316 Scanline pColorPtr1;
317
318 for (sal_Int32 nX = nStartX; nX <= nEndX; nX++)
319 {
320 sal_Int32 nTempX = rCtx.maMapIX[nX];
321 BilinearWeightType nTempFX = rCtx.maMapFX[nX];
322
323 pColorPtr0 = pLine0 + nTempX * nColorComponents;
324 pColorPtr1 = pColorPtr0 + nColorComponents;
325
326 ScaleFunction::generateComponent(pColorPtr0, pColorPtr1, nTempFX, nComponents1);
327
328 pColorPtr0 = pLine1 + nTempX * nColorComponents;
329 pColorPtr1 = pColorPtr0 + nColorComponents;
330
331 ScaleFunction::generateComponent(pColorPtr0, pColorPtr1, nTempFX, nComponents2);
332 ScaleFunction::calculateDestination(pScanDest, nTempFY, nComponents1, nComponents2);
333 }
334 }
335}
336
337class ScaleTask : public comphelper::ThreadTask
338{
339 ScaleRangeFn mpScaleRangeFunction;
340 const ScaleContext& mrContext;
341 sal_Int32 mnStartY;
342 sal_Int32 mnEndY;
343
344public:
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)
352 , mnStartY(nStartY)
353 , mnEndY(nEndY)
354 {}
355
356 virtual void doWork() override
357 {
358 mpScaleRangeFunction(mrContext, mnStartY, mnEndY);
359 }
360};
361
362void scaleUpPalette8bit(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
363{
364 const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
365
366 for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
367 {
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 );
373
374 for(sal_Int32 nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
375 {
376 sal_Int32 nTempX = rCtx.maMapIX[ nX ];
377 BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
378
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 ] );
383
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 );
387
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 );
391
392 BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
393 MAP( cG0, cG1, nTempFY ),
394 MAP( cB0, cB1, nTempFY ) );
395 rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
396 }
397 }
398}
399
400void scaleUpPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
401{
402 const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
403
404 for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
405 {
406 sal_Int32 nTempY = rCtx.maMapIY[ nY ];
407 BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
408 Scanline pScanline = rCtx.mpDest->GetScanline( nY );
409
410 for( sal_Int32 nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
411 {
412 sal_Int32 nTempX = rCtx.maMapIX[ nX ];
413 BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
414
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 );
420
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 );
426
427 BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
428 MAP( cG0, cG1, nTempFY ),
429 MAP( cB0, cB1, nTempFY ) );
430 rCtx.mpDest->SetPixelOnData( pScanline, nXDst++, aColRes );
431 }
432 }
433}
434
435void scaleUpNonPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
436{
437 const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
438
439 for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
440 {
441 sal_Int32 nTempY = rCtx.maMapIY[ nY ];
442 BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
443 Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
444
445 for( sal_Int32 nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
446 {
447 sal_Int32 nTempX = rCtx.maMapIX[ nX ];
448 BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
449
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 );
455
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 );
461
462 BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
463 MAP( cG0, cG1, nTempFY ),
464 MAP( cB0, cB1, nTempFY ) );
465 rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
466 }
467 }
468}
469
470void scaleDownPalette8bit(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
471{
472 const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
473
474 for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
475 {
476 sal_Int32 nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
477 sal_Int32 nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
478
479 sal_Int32 nLineStart, nLineRange;
480 if( nY == nEndY )
481 {
482 nLineStart = rCtx.maMapIY[ nY ];
483 nLineRange = 0;
484 }
485 else
486 {
487 nLineStart = rCtx.maMapIY[ nTop ] ;
488 nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
489 }
490
491 Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
492 for( sal_Int32 nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
493 {
494 sal_Int32 nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
495 sal_Int32 nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
496
497 sal_Int32 nRowStart;
498 sal_Int32 nRowRange;
499 if( nX == nEndX )
500 {
501 nRowStart = rCtx.maMapIX[ nX ];
502 nRowRange = 0;
503 }
504 else
505 {
506 nRowStart = rCtx.maMapIX[ nLeft ];
507 nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
508 }
509
510 int nSumR = 0;
511 int nSumG = 0;
512 int nSumB = 0;
513 BilinearWeightType nTotalWeightY = 0;
514
515 for(sal_Int32 i = 0; i<= nLineRange; i++)
516 {
517 Scanline pTmpY = rCtx.mpSrc->GetScanline( nLineStart + i );
518 int nSumRowR = 0;
519 int nSumRowG = 0;
520 int nSumRowB = 0;
521 BilinearWeightType nTotalWeightX = 0;
522
523 for(sal_Int32 j = 0; j <= nRowRange; j++)
524 {
525 const BitmapColor& rCol = rCtx.mpSrc->GetPaletteColor( pTmpY[ nRowStart + j ] );
526
527 if(nX == nEndX )
528 {
529 nSumRowB += rCol.GetBlue() << MAP_PRECISION;
530 nSumRowG += rCol.GetGreen() << MAP_PRECISION;
531 nSumRowR += rCol.GetRed() << MAP_PRECISION;
532 nTotalWeightX += lclMaxWeight();
533 }
534 else if( j == 0 )
535 {
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;
541 }
542 else if ( nRowRange == j )
543 {
544 BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
545 nSumRowB += ( nWeightX *rCol.GetBlue() );
546 nSumRowG += ( nWeightX *rCol.GetGreen() );
547 nSumRowR += ( nWeightX *rCol.GetRed() );
548 nTotalWeightX += nWeightX;
549 }
550 else
551 {
552 nSumRowB += rCol.GetBlue() << MAP_PRECISION;
553 nSumRowG += rCol.GetGreen() << MAP_PRECISION;
554 nSumRowR += rCol.GetRed() << MAP_PRECISION;
555 nTotalWeightX += lclMaxWeight();
556 }
557 }
558
559 BilinearWeightType nWeightY = lclMaxWeight();
560 if( nY == nEndY )
561 nWeightY = lclMaxWeight();
562 else if( i == 0 )
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 ];
568
569 if (nTotalWeightX)
570 {
571 nSumRowB /= nTotalWeightX;
572 nSumRowG /= nTotalWeightX;
573 nSumRowR /= nTotalWeightX;
574 }
575
576 nSumB += nWeightY * nSumRowB;
577 nSumG += nWeightY * nSumRowG;
578 nSumR += nWeightY * nSumRowR;
579 nTotalWeightY += nWeightY;
580 }
581
582 if (nTotalWeightY)
583 {
584 nSumR /= nTotalWeightY;
585 nSumG /= nTotalWeightY;
586 nSumB /= nTotalWeightY;
587 }
588
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 );
591 }
592 }
593}
594
595void scaleDownPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
596{
597 const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
598
599 for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
600 {
601 sal_Int32 nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
602 sal_Int32 nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
603
604 sal_Int32 nLineStart, nLineRange;
605 if( nY ==nEndY )
606 {
607 nLineStart = rCtx.maMapIY[ nY ];
608 nLineRange = 0;
609 }
610 else
611 {
612 nLineStart = rCtx.maMapIY[ nTop ] ;
613 nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
614 }
615
616 Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
617 for( sal_Int32 nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
618 {
619 sal_Int32 nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
620 sal_Int32 nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
621
622 sal_Int32 nRowStart, nRowRange;
623 if( nX == nEndX )
624 {
625 nRowStart = rCtx.maMapIX[ nX ];
626 nRowRange = 0;
627 }
628 else
629 {
630 nRowStart = rCtx.maMapIX[ nLeft ];
631 nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
632 }
633
634 int nSumR = 0;
635 int nSumG = 0;
636 int nSumB = 0;
637 BilinearWeightType nTotalWeightY = 0;
638
639 for(sal_Int32 i = 0; i<= nLineRange; i++)
640 {
641 int nSumRowR = 0;
642 int nSumRowG = 0;
643 int nSumRowB = 0;
644 BilinearWeightType nTotalWeightX = 0;
645
646 Scanline pScanlineSrc = rCtx.mpSrc->GetScanline( nLineStart + i );
647 for(sal_Int32 j = 0; j <= nRowRange; j++)
648 {
649 BitmapColor aCol0 = rCtx.mpSrc->GetPaletteColor ( rCtx.mpSrc->GetIndexFromData( pScanlineSrc, nRowStart + j ) );
650
651 if(nX == nEndX )
652 {
653
654 nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
655 nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
656 nSumRowR += aCol0.GetRed() << MAP_PRECISION;
657 nTotalWeightX += lclMaxWeight();
658 }
659 else if( j == 0 )
660 {
661
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;
667 }
668 else if ( nRowRange == j )
669 {
670
671 BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
672 nSumRowB += ( nWeightX *aCol0.GetBlue() );
673 nSumRowG += ( nWeightX *aCol0.GetGreen() );
674 nSumRowR += ( nWeightX *aCol0.GetRed() );
675 nTotalWeightX += nWeightX;
676 }
677 else
678 {
679
680 nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
681 nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
682 nSumRowR += aCol0.GetRed() << MAP_PRECISION;
683 nTotalWeightX += lclMaxWeight();
684 }
685 }
686
687 sal_Int32 nWeightY = lclMaxWeight();
688 if( nY == nEndY )
689 nWeightY = lclMaxWeight();
690 else if( i == 0 )
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 ];
696
697 if (nTotalWeightX)
698 {
699 nSumRowB /= nTotalWeightX;
700 nSumRowG /= nTotalWeightX;
701 nSumRowR /= nTotalWeightX;
702 }
703
704 nSumB += nWeightY * nSumRowB;
705 nSumG += nWeightY * nSumRowG;
706 nSumR += nWeightY * nSumRowR;
707 nTotalWeightY += nWeightY;
708 }
709
710 if (nTotalWeightY)
711 {
712 nSumR /= nTotalWeightY;
713 nSumG /= nTotalWeightY;
714 nSumB /= nTotalWeightY;
715 }
716
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 );
719 }
720 }
721}
722
723void scaleDownNonPaletteGeneral(const ScaleContext &rCtx, sal_Int32 nStartY, sal_Int32 nEndY)
724{
725 const sal_Int32 nStartX = 0, nEndX = rCtx.mnDestW - 1;
726
727 for( sal_Int32 nY = nStartY; nY <= nEndY; nY++ )
728 {
729 sal_Int32 nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
730 sal_Int32 nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
731
732 sal_Int32 nLineStart, nLineRange;
733 if( nY ==nEndY )
734 {
735 nLineStart = rCtx.maMapIY[ nY ];
736 nLineRange = 0;
737 }
738 else
739 {
740 nLineStart = rCtx.maMapIY[ nTop ] ;
741 nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
742 }
743
744 Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
745 for( sal_Int32 nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
746 {
747 sal_Int32 nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
748 sal_Int32 nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
749
750 sal_Int32 nRowStart, nRowRange;
751 if( nX == nEndX )
752 {
753 nRowStart = rCtx.maMapIX[ nX ];
754 nRowRange = 0;
755 }
756 else
757 {
758 nRowStart = rCtx.maMapIX[ nLeft ];
759 nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
760 }
761
762 int nSumR = 0;
763 int nSumG = 0;
764 int nSumB = 0;
765 BilinearWeightType nTotalWeightY = 0;
766
767 for(sal_Int32 i = 0; i<= nLineRange; i++)
768 {
769 int nSumRowR = 0;
770 int nSumRowG = 0;
771 int nSumRowB = 0;
772 BilinearWeightType nTotalWeightX = 0;
773
774 Scanline pScanlineSrc = rCtx.mpSrc->GetScanline( nLineStart + i );
775 for(sal_Int32 j = 0; j <= nRowRange; j++)
776 {
777 BitmapColor aCol0 = rCtx.mpSrc->GetPixelFromData( pScanlineSrc, nRowStart + j );
778
779 if(nX == nEndX )
780 {
781
782 nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
783 nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
784 nSumRowR += aCol0.GetRed() << MAP_PRECISION;
785 nTotalWeightX += lclMaxWeight();
786 }
787 else if( j == 0 )
788 {
789
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;
795 }
796 else if ( nRowRange == j )
797 {
798
799 BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
800 nSumRowB += ( nWeightX *aCol0.GetBlue() );
801 nSumRowG += ( nWeightX *aCol0.GetGreen() );
802 nSumRowR += ( nWeightX *aCol0.GetRed() );
803 nTotalWeightX += nWeightX;
804 }
805 else
806 {
807 nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
808 nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
809 nSumRowR += aCol0.GetRed() << MAP_PRECISION;
810 nTotalWeightX += lclMaxWeight();
811 }
812 }
813
814 BilinearWeightType nWeightY = lclMaxWeight();
815 if( nY == nEndY )
816 nWeightY = lclMaxWeight();
817 else if( i == 0 )
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 ];
823
824 if (nTotalWeightX)
825 {
826 nSumRowB /= nTotalWeightX;
827 nSumRowG /= nTotalWeightX;
828 nSumRowR /= nTotalWeightX;
829 }
830
831 nSumB += nWeightY * nSumRowB;
832 nSumG += nWeightY * nSumRowG;
833 nSumR += nWeightY * nSumRowR;
834 nTotalWeightY += nWeightY;
835 }
836
837 if (nTotalWeightY)
838 {
839 nSumR /= nTotalWeightY;
840 nSumG /= nTotalWeightY;
841 nSumB /= nTotalWeightY;
842 }
843
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 );
846 }
847 }
848}
849
850} // end anonymous namespace
851
852BitmapScaleSuperFilter::BitmapScaleSuperFilter(const double& rScaleX, const double& rScaleY) :
853 mrScaleX(rScaleX),
854 mrScaleY(rScaleY)
855{}
856
858{}
859
861{
862 Bitmap aBitmap(rBitmap.GetBitmap());
863 bool bRet = false;
864
865 const Size aSizePix(rBitmap.GetSizePixel());
866
867 bool bHMirr = mrScaleX < 0;
868 bool bVMirr = mrScaleY < 0;
869
870 double fScaleX = std::fabs(mrScaleX);
871 double fScaleY = std::fabs(mrScaleY);
872
873 const sal_Int32 nDstW = FRound(aSizePix.Width() * fScaleX);
874 const sal_Int32 nDstH = FRound(aSizePix.Height() * fScaleY);
875
876 constexpr double fScaleThresh = 0.6;
877
878 if (nDstW <= 1 || nDstH <= 1)
879 return BitmapEx();
880
881 // check cache for a previously scaled version of this
882 ScaleCacheKey aKey(aBitmap.ImplGetSalBitmap().get(),
883 Size(nDstW, nDstH));
884
885 ImplSVData* pSVData = ImplGetSVData();
886 auto& rCache = pSVData->maGDIData.maScaleCache;
887 auto aFind = rCache.find(aKey);
888 if (aFind != rCache.end())
889 {
890 if (aFind->second.GetSizePixel().Width() == nDstW && aFind->second.GetSizePixel().Height() == nDstH)
891 return aFind->second;
892 else
893 SAL_WARN("vcl.gdi", "Error: size mismatch in scale cache");
894 }
895
896 {
897 Bitmap::ScopedReadAccess pReadAccess(aBitmap);
898
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;
904
905 Bitmap aOutBmp(Size(nDstW, nDstH), ePixelFormat);
906 Size aOutSize = aOutBmp.GetSizePixel();
907 auto eTargetPixelFormat = aOutBmp.getPixelFormat();
908
909 if (!aOutSize.Width() || !aOutSize.Height())
910 {
911 SAL_WARN("vcl.gdi", "bmp creation failed");
912 return BitmapEx();
913 }
914
915 BitmapScopedWriteAccess pWriteAccess(aOutBmp);
916
917 const sal_Int32 nEndY = nDstH - 1;
918
919 if (pReadAccess && pWriteAccess)
920 {
921 ScaleRangeFn pScaleRangeFn;
922 const ScaleContext aContext( pReadAccess.get(),
923 pWriteAccess.get(),
924 pReadAccess->Width(),
925 pWriteAccess->Width(),
926 pReadAccess->Height(),
927 pWriteAccess->Height(),
928 bVMirr, bHMirr );
929
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())
935 {
936 switch( pReadAccess->GetScanlineFormat() )
937 {
939 pScaleRangeFn = bScaleUp ? scaleUpPalette8bit
940 : scaleDownPalette8bit;
941 break;
942 default:
943 pScaleRangeFn = bScaleUp ? scaleUpPaletteGeneral
944 : scaleDownPaletteGeneral;
945 break;
946 }
947 }
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)
954 {
955 pScaleRangeFn = bScaleUp ? scaleUpNonPaletteGeneral : scaleDownNonPaletteGeneral;
956 }
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.
959 else
960 {
961 switch( pReadAccess->GetScanlineFormat() )
962 {
965 pScaleRangeFn = bScaleUp ? scaleUp<24> : scaleDown<24>;
966 break;
971 pScaleRangeFn = bScaleUp ? scaleUp<32> : scaleDown<32>;
972 break;
973 default:
974 pScaleRangeFn = bScaleUp ? scaleUpNonPaletteGeneral
975 : scaleDownNonPaletteGeneral;
976 break;
977 }
978 }
979
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;
986
987 static bool bDisableThreadedScaling = getenv ("VCL_NO_THREAD_SCALE");
988 if (bDisableThreadedScaling || !bHorizontalWork)
989 {
990 SAL_INFO("vcl.gdi", "Scale in main thread");
991 bUseThreads = false;
992 }
993
994 if (bUseThreads)
995 {
996 try
997 {
998 // partition and queue work
1000 std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag();
1001
1002 vcl::bitmap::generateStripRanges<constScaleThreadStrip>(nStartY, nEndY,
1003 [&] (sal_Int32 const nStart, sal_Int32 const nEnd, bool const bLast)
1004 {
1005 if (!bLast)
1006 {
1007 auto pTask(std::make_unique<ScaleTask>(pTag, pScaleRangeFn, aContext, nStart, nEnd));
1008 rShared.pushTask(std::move(pTask));
1009 }
1010 else
1011 pScaleRangeFn(aContext, nStart, nEnd);
1012 });
1013 rShared.waitUntilDone(pTag);
1014 SAL_INFO("vcl.gdi", "All threaded scaling tasks complete");
1015 }
1016 catch (...)
1017 {
1018 SAL_WARN("vcl.gdi", "threaded bitmap scaling failed");
1019 bUseThreads = false;
1020 }
1021 }
1022
1023 if (!bUseThreads)
1024 pScaleRangeFn( aContext, nStartY, nEndY );
1025
1026 pWriteAccess.reset();
1027 bRet = true;
1028 aBitmap.AdaptBitCount(aOutBmp);
1029 aBitmap = aOutBmp;
1030 }
1031 }
1032
1033 if (bRet)
1034 {
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));
1039 return aRet;
1040 }
1041
1042 return BitmapEx();
1043
1044}
1045
1046/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
const NodeContext & mrContext
Bitmap GetBitmap(Color aTransparentReplaceColor) const
Definition: BitmapEx.cxx:217
const Size & GetSizePixel() const
Definition: bitmapex.hxx:73
tools::Long Height() const
tools::Long Width() const
bool HasPalette() const
ScanlineFormat GetScanlineFormat() const
virtual BitmapEx execute(BitmapEx const &rBitmap) const override
BitmapScaleSuperFilter(const double &rScaleX, const double &rScaleY)
virtual ~BitmapScaleSuperFilter() override
const std::shared_ptr< SalBitmap > & ImplGetSalBitmap() const
bool Crop(const tools::Rectangle &rRectPixel)
Crop the bitmap.
Size GetSizePixel() const
void AdaptBitCount(Bitmap &rNew) const
vcl::PixelFormat getPixelFormat() const
sal_uInt8 GetBlue() const
sal_uInt8 GetRed() const
sal_uInt8 GetGreen() const
constexpr tools::Long Height() const
constexpr tools::Long Width() const
static ThreadPool & getSharedOptimalPool()
void waitUntilDone(const std::shared_ptr< ThreadTaskTag > &, bool bJoin=true)
static std::shared_ptr< ThreadTaskTag > createThreadTaskTag()
void pushTask(std::unique_ptr< ThreadTask > pTask)
virtual void doWork()=0
list_const_iterator_t find(const Key &key)
tools::Long FRound(double fVal)
std::enable_if< std::is_signed< T >::value||std::is_floating_point< T >::value, long >::type MinMax(T nVal, tools::Long nMin, tools::Long nMax)
sal_Int64 n
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
int i
ImplSVGDIData maGDIData
Definition: svdata.hxx:398
lru_scale_cache maScaleCache
Definition: svdata.hxx:227
Cache multiple scalings for the same bitmap.
Definition: svdata.hxx:174
ImplSVData * ImplGetSVData()
Definition: svdata.cxx:77
unsigned char sal_uInt8
#define MAP(name, prefix, token, type, context)