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 /*
33 A scaling algorithm that uses bilinear if not downscaling too much,
34 and averaging otherwise (bilinear would produce poor results for big downscaling).
35 
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.
41 */
42 
43 namespace {
44 
45 constexpr int MAP_PRECISION = 7;
46 
47 typedef sal_Int32 BilinearWeightType;
48 
49 constexpr BilinearWeightType lclMaxWeight()
50 {
51  return BilinearWeightType(1) << MAP_PRECISION;
52 }
53 
54 constexpr 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 
59 struct 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 
109 constexpr sal_Int32 constScaleThreadStrip = 32;
110 
111 typedef void (*ScaleRangeFn)(const ScaleContext & rContext, sal_Int32 nStartY, sal_Int32 nEndY);
112 
113 template <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 
177 template <int nColorBits>
178 void 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 
293 template <int nColorBits>
294 void 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 
337 class ScaleTask : public comphelper::ThreadTask
338 {
339  ScaleRangeFn mpScaleRangeFunction;
340  const ScaleContext& mrContext;
341  sal_Int32 mnStartY;
342  sal_Int32 mnEndY;
343 
344 public:
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 
362 void 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 
400 void 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 
435 void 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 
470 void 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 
595 void 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 
723 void 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 
852 BitmapScaleSuperFilter::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: */
tools::Long Height() const
sal_uInt8 GetRed() const
virtual ~BitmapScaleSuperFilter() override
int n1
virtual void doWork()=0
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
virtual BitmapEx execute(BitmapEx const &rBitmap) const override
Size GetSizePixel() const
#define MAP(name, prefix, token, type, context)
void waitUntilDone(const std::shared_ptr< ThreadTaskTag > &, bool bJoin=true)
constexpr tools::Long Width() const
int n2
Cache multiple scalings for the same bitmap.
Definition: svdata.hxx:178
sal_uInt8 GetBlue() const
static ThreadPool & getSharedOptimalPool()
ImplSVData * ImplGetSVData()
Definition: svdata.cxx:76
sal_uInt8 * Scanline
Definition: Scanline.hxx:26
vcl::PixelFormat getPixelFormat() const
int i
bool HasPalette() const
void pushTask(std::unique_ptr< ThreadTask > pTask)
ScanlineFormat GetScanlineFormat() const
tools::Long FRound(double fVal)
tools::Long Width() const
const NodeContext & mrContext
Bitmap GetBitmap(Color aTransparentReplaceColor) const
Definition: BitmapEx.cxx:203
sal_uInt8 GetGreen() const
constexpr tools::Long Height() const
unsigned char sal_uInt8
#define SAL_INFO(area, stream)
BitmapScaleSuperFilter(const double &rScaleX, const double &rScaleY)
static std::shared_ptr< ThreadTaskTag > createThreadTaskTag()
#define SAL_WARN(area, stream)
const Size & GetSizePixel() const
Definition: bitmapex.hxx:72
typedef void(CALLTYPE *GetFuncDataPtr)(sal_uInt16 &nNo