bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / bitmap / BitmapScaleSuperFilter.cxx
blob9ee6e80c7b40047d95d8cf004162145a556885a6
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/threadpool.hxx>
22 #include <tools/helpers.hxx>
23 #include <vcl/bitmapaccess.hxx>
25 #include <bitmapwriteaccess.hxx>
26 #include <BitmapScaleSuperFilter.hxx>
28 #include <algorithm>
29 #include <memory>
30 #include <svdata.hxx>
31 #include <sal/log.hxx>
33 namespace {
35 #define MAP_PRECISION 7
37 typedef sal_Int32 BilinearWeightType;
39 constexpr BilinearWeightType lclMaxWeight()
41 return BilinearWeightType(1) << MAP_PRECISION;
44 constexpr sal_uInt8 MAP(sal_uInt8 cVal0, sal_uInt8 cVal1, BilinearWeightType nFrac)
46 return sal_uInt8(((BilinearWeightType(cVal0) << MAP_PRECISION) + nFrac * (BilinearWeightType(cVal1) - BilinearWeightType(cVal0))) >> MAP_PRECISION);
49 struct ScaleContext
51 BitmapReadAccess* const mpSrc;
52 BitmapWriteAccess* mpDest;
53 long mnDestW;
54 bool mbHMirr;
55 bool mbVMirr;
56 std::vector<long> maMapIX;
57 std::vector<long> maMapIY;
58 std::vector<BilinearWeightType> maMapFX;
59 std::vector<BilinearWeightType> maMapFY;
61 ScaleContext( BitmapReadAccess *pSrc,
62 BitmapWriteAccess *pDest,
63 long nSrcW, long nDestW,
64 long nSrcH, long nDestH,
65 bool bHMirr, bool bVMirr)
66 : mpSrc(pSrc)
67 , mpDest(pDest)
68 , mnDestW(nDestW)
69 , mbHMirr(bHMirr)
70 , mbVMirr(bVMirr)
71 , maMapIX(nDestW)
72 , maMapIY(nDestH)
73 , maMapFX(nDestW)
74 , maMapFY(nDestH)
76 generateMap(nSrcW, nDestW, bHMirr, maMapIX, maMapFX);
77 generateMap(nSrcH, nDestH, bVMirr, maMapIY, maMapFY);
80 static void generateMap(long nSourceLength, long nDestinationLength, bool bMirrored,
81 std::vector<long> & rMapIX, std::vector<BilinearWeightType> & rMapFX)
83 const double fRevScale = (nDestinationLength > 1) ? double(nSourceLength - 1) / (nDestinationLength - 1) : 0.0;
85 long nTemp = nSourceLength - 2;
86 long nTempX = nSourceLength - 1;
88 for (long i = 0; i < nDestinationLength; i++)
90 double fTemp = i * fRevScale;
91 if (bMirrored)
92 fTemp = nTempX - fTemp;
93 rMapIX[i] = MinMax(long(fTemp), 0, nTemp);
94 rMapFX[i] = BilinearWeightType((fTemp - rMapIX[i]) * (BilinearWeightType(1) << MAP_PRECISION));
99 constexpr long constScaleThreadStrip = 32;
101 typedef void (*ScaleRangeFn)(ScaleContext &rContext, long nStartY, long nEndY);
103 class ScaleTask : public comphelper::ThreadTask
105 ScaleRangeFn const mpScaleRangeFunction;
106 ScaleContext& mrContext;
107 const long mnStartY;
108 const long mnEndY;
110 public:
111 explicit ScaleTask(const std::shared_ptr<comphelper::ThreadTaskTag>& pTag,
112 ScaleRangeFn pScaleRangeFunction,
113 ScaleContext& rContext,
114 long nStartY, long nEndY)
115 : comphelper::ThreadTask(pTag)
116 , mpScaleRangeFunction(pScaleRangeFunction)
117 , mrContext(rContext)
118 , mnStartY(nStartY)
119 , mnEndY(nEndY)
122 virtual void doWork() override
124 mpScaleRangeFunction(mrContext, mnStartY, mnEndY);
128 void scaleUp32bit(ScaleContext &rCtx, long nStartY, long nEndY)
130 const int nColorComponents = 4;
132 const long nStartX = 0;
133 const long nEndX = rCtx.mnDestW - 1;
135 for (long nY = nStartY; nY <= nEndY; nY++)
137 long nTempY = rCtx.maMapIY[nY];
138 BilinearWeightType nTempFY = rCtx.maMapFY[nY];
140 Scanline pLine0 = rCtx.mpSrc->GetScanline(nTempY+0);
141 Scanline pLine1 = rCtx.mpSrc->GetScanline(nTempY+1);
142 Scanline pScanDest = rCtx.mpDest->GetScanline(nY);
144 sal_uInt8 nComponent1[nColorComponents];
145 sal_uInt8 nComponent2[nColorComponents];
147 Scanline pColorPtr0;
148 Scanline pColorPtr1;
150 for (long nX = nStartX; nX <= nEndX; nX++)
152 long nTempX = rCtx.maMapIX[nX];
153 BilinearWeightType nTempFX = rCtx.maMapFX[nX];
155 pColorPtr0 = pLine0 + nTempX * nColorComponents;
156 pColorPtr1 = pColorPtr0 + nColorComponents;
158 nComponent1[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
159 pColorPtr0++; pColorPtr1++;
160 nComponent1[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
161 pColorPtr0++; pColorPtr1++;
162 nComponent1[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
163 pColorPtr0++; pColorPtr1++;
164 nComponent1[3] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
166 pColorPtr0 = pLine1 + nTempX * nColorComponents;
167 pColorPtr1 = pColorPtr0 + nColorComponents;
169 nComponent2[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
170 pColorPtr0++; pColorPtr1++;
171 nComponent2[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
172 pColorPtr0++; pColorPtr1++;
173 nComponent2[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
174 pColorPtr0++; pColorPtr1++;
175 nComponent2[3] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
177 *pScanDest = MAP(nComponent1[0], nComponent2[0], nTempFY);
178 pScanDest++;
179 *pScanDest = MAP(nComponent1[1], nComponent2[1], nTempFY);
180 pScanDest++;
181 *pScanDest = MAP(nComponent1[2], nComponent2[2], nTempFY);
182 pScanDest++;
183 *pScanDest = MAP(nComponent1[3], nComponent2[3], nTempFY);
184 pScanDest++;
189 void scaleUpPalette8bit(ScaleContext &rCtx, long nStartY, long nEndY)
191 const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
193 for( long nY = nStartY; nY <= nEndY; nY++ )
195 long nTempY = rCtx.maMapIY[ nY ];
196 BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
197 Scanline pLine0 = rCtx.mpSrc->GetScanline( nTempY );
198 Scanline pLine1 = rCtx.mpSrc->GetScanline( ++nTempY );
199 Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
201 for(long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
203 long nTempX = rCtx.maMapIX[ nX ];
204 BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
206 const BitmapColor& rCol0 = rCtx.mpSrc->GetPaletteColor( pLine0[ nTempX ] );
207 const BitmapColor& rCol2 = rCtx.mpSrc->GetPaletteColor( pLine1[ nTempX ] );
208 const BitmapColor& rCol1 = rCtx.mpSrc->GetPaletteColor( pLine0[ ++nTempX ] );
209 const BitmapColor& rCol3 = rCtx.mpSrc->GetPaletteColor( pLine1[ nTempX ] );
211 sal_uInt8 cR0 = MAP( rCol0.GetRed(), rCol1.GetRed(), nTempFX );
212 sal_uInt8 cG0 = MAP( rCol0.GetGreen(), rCol1.GetGreen(), nTempFX );
213 sal_uInt8 cB0 = MAP( rCol0.GetBlue(), rCol1.GetBlue(), nTempFX );
215 sal_uInt8 cR1 = MAP( rCol2.GetRed(), rCol3.GetRed(), nTempFX );
216 sal_uInt8 cG1 = MAP( rCol2.GetGreen(), rCol3.GetGreen(), nTempFX );
217 sal_uInt8 cB1 = MAP( rCol2.GetBlue(), rCol3.GetBlue(), nTempFX );
219 BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
220 MAP( cG0, cG1, nTempFY ),
221 MAP( cB0, cB1, nTempFY ) );
222 rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
227 void scaleUpPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY)
229 const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
231 for( long nY = nStartY; nY <= nEndY; nY++ )
233 long nTempY = rCtx.maMapIY[ nY ];
234 BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
235 Scanline pScanline = rCtx.mpDest->GetScanline( nY );
237 for( long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
239 long nTempX = rCtx.maMapIX[ nX ];
240 BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
242 BitmapColor aCol0 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY, nTempX ) );
243 BitmapColor aCol1 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY, ++nTempX ) );
244 sal_uInt8 cR0 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
245 sal_uInt8 cG0 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
246 sal_uInt8 cB0 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
248 aCol1 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( ++nTempY, nTempX ) );
249 aCol0 = rCtx.mpSrc->GetPaletteColor( rCtx.mpSrc->GetPixelIndex( nTempY--, --nTempX ) );
250 sal_uInt8 cR1 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
251 sal_uInt8 cG1 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
252 sal_uInt8 cB1 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
254 BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
255 MAP( cG0, cG1, nTempFY ),
256 MAP( cB0, cB1, nTempFY ) );
257 rCtx.mpDest->SetPixelOnData( pScanline, nXDst++, aColRes );
262 void scaleUp24bit(ScaleContext &rCtx, long nStartY, long nEndY)
264 const int nColorComponents = 3;
266 const long nStartX = 0;
267 const long nEndX = rCtx.mnDestW - 1;
269 for (long nY = nStartY; nY <= nEndY; nY++)
271 long nTempY = rCtx.maMapIY[nY];
272 BilinearWeightType nTempFY = rCtx.maMapFY[nY];
274 Scanline pLine0 = rCtx.mpSrc->GetScanline(nTempY+0);
275 Scanline pLine1 = rCtx.mpSrc->GetScanline(nTempY+1);
276 Scanline pScanDest = rCtx.mpDest->GetScanline(nY);
278 sal_uInt8 nComponent1[nColorComponents];
279 sal_uInt8 nComponent2[nColorComponents];
281 Scanline pColorPtr0;
282 Scanline pColorPtr1;
284 for (long nX = nStartX; nX <= nEndX; nX++)
286 long nTempX = rCtx.maMapIX[nX];
287 BilinearWeightType nTempFX = rCtx.maMapFX[nX];
289 pColorPtr0 = pLine0 + nTempX * nColorComponents;
290 pColorPtr1 = pColorPtr0 + nColorComponents;
292 nComponent1[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
293 pColorPtr0++; pColorPtr1++;
294 nComponent1[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
295 pColorPtr0++; pColorPtr1++;
296 nComponent1[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
298 pColorPtr0 = pLine1 + nTempX * nColorComponents;
299 pColorPtr1 = pColorPtr0 + nColorComponents;
301 nComponent2[0] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
302 pColorPtr0++; pColorPtr1++;
303 nComponent2[1] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
304 pColorPtr0++; pColorPtr1++;
305 nComponent2[2] = MAP(*pColorPtr0, *pColorPtr1, nTempFX);
307 *pScanDest = MAP(nComponent1[0], nComponent2[0], nTempFY);
308 pScanDest++;
309 *pScanDest = MAP(nComponent1[1], nComponent2[1], nTempFY);
310 pScanDest++;
311 *pScanDest = MAP(nComponent1[2], nComponent2[2], nTempFY);
312 pScanDest++;
317 void scaleUpNonPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY)
319 const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
321 for( long nY = nStartY; nY <= nEndY; nY++ )
323 long nTempY = rCtx.maMapIY[ nY ];
324 BilinearWeightType nTempFY = rCtx.maMapFY[ nY ];
325 Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
327 for( long nX = nStartX, nXDst = 0; nX <= nEndX; nX++ )
329 long nTempX = rCtx.maMapIX[ nX ];
330 BilinearWeightType nTempFX = rCtx.maMapFX[ nX ];
332 BitmapColor aCol0 = rCtx.mpSrc->GetPixel( nTempY, nTempX );
333 BitmapColor aCol1 = rCtx.mpSrc->GetPixel( nTempY, ++nTempX );
334 sal_uInt8 cR0 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
335 sal_uInt8 cG0 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
336 sal_uInt8 cB0 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
338 aCol1 = rCtx.mpSrc->GetPixel( ++nTempY, nTempX );
339 aCol0 = rCtx.mpSrc->GetPixel( nTempY--, --nTempX );
340 sal_uInt8 cR1 = MAP( aCol0.GetRed(), aCol1.GetRed(), nTempFX );
341 sal_uInt8 cG1 = MAP( aCol0.GetGreen(), aCol1.GetGreen(), nTempFX );
342 sal_uInt8 cB1 = MAP( aCol0.GetBlue(), aCol1.GetBlue(), nTempFX );
344 BitmapColor aColRes( MAP( cR0, cR1, nTempFY ),
345 MAP( cG0, cG1, nTempFY ),
346 MAP( cB0, cB1, nTempFY ) );
347 rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
352 void scaleDown32bit(ScaleContext &rCtx, long nStartY, long nEndY)
354 const int constColorComponents = 4;
356 const long nStartX = 0;
357 const long nEndX = rCtx.mnDestW - 1;
359 for (long nY = nStartY; nY <= nEndY; nY++)
361 long nTop = rCtx.mbVMirr ? (nY + 1) : nY;
362 long nBottom = rCtx.mbVMirr ? nY : (nY + 1);
364 long nLineStart;
365 long nLineRange;
366 if (nY == nEndY)
368 nLineStart = rCtx.maMapIY[nY];
369 nLineRange = 0;
371 else
373 nLineStart = rCtx.maMapIY[nTop];
374 nLineRange = (rCtx.maMapIY[nBottom] == rCtx.maMapIY[nTop]) ?
375 1 : (rCtx.maMapIY[nBottom] - rCtx.maMapIY[nTop]);
378 Scanline pScanDest = rCtx.mpDest->GetScanline(nY);
379 for (long nX = nStartX; nX <= nEndX; nX++)
381 long nLeft = rCtx.mbHMirr ? (nX + 1) : nX;
382 long nRight = rCtx.mbHMirr ? nX : (nX + 1);
384 long nRowStart;
385 long nRowRange;
386 if (nX == nEndX)
388 nRowStart = rCtx.maMapIX[nX];
389 nRowRange = 0;
391 else
393 nRowStart = rCtx.maMapIX[nLeft];
394 nRowRange = (rCtx.maMapIX[nRight] == rCtx.maMapIX[nLeft]) ?
395 1 : (rCtx.maMapIX[nRight] - rCtx.maMapIX[nLeft]);
398 int nSum1 = 0;
399 int nSum2 = 0;
400 int nSum3 = 0;
401 int nSum4 = 0;
402 BilinearWeightType nTotalWeightY = 0;
404 for (long i = 0; i<= nLineRange; i++)
406 Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i);
407 Scanline pTmpX = pTmpY + constColorComponents * nRowStart;
409 int nSumRow1 = 0;
410 int nSumRow2 = 0;
411 int nSumRow3 = 0;
412 int nSumRow4 = 0;
413 BilinearWeightType nTotalWeightX = 0;
415 for (long j = 0; j <= nRowRange; j++)
417 if (nX == nEndX)
419 nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++;
420 nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++;
421 nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++;
422 nSumRow4 += (*pTmpX) << MAP_PRECISION; pTmpX++;
423 nTotalWeightX += lclMaxWeight();
425 else if(j == 0)
427 BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft];
428 nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++;
429 nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++;
430 nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++;
431 nSumRow4 += (nWeightX * (*pTmpX)); pTmpX++;
432 nTotalWeightX += nWeightX;
434 else if ( nRowRange == j )
436 BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
437 nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++;
438 nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++;
439 nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++;
440 nSumRow4 += (nWeightX * (*pTmpX)); pTmpX++;
441 nTotalWeightX += nWeightX;
443 else
445 nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++;
446 nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++;
447 nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++;
448 nSumRow4 += (*pTmpX) << MAP_PRECISION; pTmpX++;
449 nTotalWeightX += lclMaxWeight();
453 BilinearWeightType nWeightY = lclMaxWeight();
454 if (nY == nEndY)
455 nWeightY = lclMaxWeight();
456 else if (i == 0)
457 nWeightY = lclMaxWeight() - rCtx.maMapFY[nTop];
458 else if (nLineRange == 1)
459 nWeightY = rCtx.maMapFY[nTop];
460 else if (nLineRange == i)
461 nWeightY = rCtx.maMapFY[nBottom];
463 if (nTotalWeightX)
465 nSumRow1 /= nTotalWeightX;
466 nSumRow2 /= nTotalWeightX;
467 nSumRow3 /= nTotalWeightX;
468 nSumRow4 /= nTotalWeightX;
470 nSum1 += nWeightY * nSumRow1;
471 nSum2 += nWeightY * nSumRow2;
472 nSum3 += nWeightY * nSumRow3;
473 nSum4 += nWeightY * nSumRow4;
474 nTotalWeightY += nWeightY;
477 if (nTotalWeightY)
479 nSum1 /= nTotalWeightY;
480 nSum2 /= nTotalWeightY;
481 nSum3 /= nTotalWeightY;
482 nSum4 /= nTotalWeightY;
485 // Write the calculated color components to the destination
486 *pScanDest = nSum1; pScanDest++;
487 *pScanDest = nSum2; pScanDest++;
488 *pScanDest = nSum3; pScanDest++;
489 *pScanDest = nSum4; pScanDest++;
494 void scaleDownPalette8bit(ScaleContext &rCtx, long nStartY, long nEndY)
496 const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
498 for( long nY = nStartY; nY <= nEndY; nY++ )
500 long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
501 long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
503 long nLineStart, nLineRange;
504 if( nY == nEndY )
506 nLineStart = rCtx.maMapIY[ nY ];
507 nLineRange = 0;
509 else
511 nLineStart = rCtx.maMapIY[ nTop ] ;
512 nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
515 Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
516 for( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
518 long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
519 long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
521 long nRowStart;
522 long nRowRange;
523 if( nX == nEndX )
525 nRowStart = rCtx.maMapIX[ nX ];
526 nRowRange = 0;
528 else
530 nRowStart = rCtx.maMapIX[ nLeft ];
531 nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
534 int nSumR = 0;
535 int nSumG = 0;
536 int nSumB = 0;
537 BilinearWeightType nTotalWeightY = 0;
539 for(long i = 0; i<= nLineRange; i++)
541 Scanline pTmpY = rCtx.mpSrc->GetScanline( nLineStart + i );
542 int nSumRowR = 0;
543 int nSumRowG = 0;
544 int nSumRowB = 0;
545 BilinearWeightType nTotalWeightX = 0;
547 for(long j = 0; j <= nRowRange; j++)
549 const BitmapColor& rCol = rCtx.mpSrc->GetPaletteColor( pTmpY[ nRowStart + j ] );
551 if(nX == nEndX )
553 nSumRowB += rCol.GetBlue() << MAP_PRECISION;
554 nSumRowG += rCol.GetGreen() << MAP_PRECISION;
555 nSumRowR += rCol.GetRed() << MAP_PRECISION;
556 nTotalWeightX += lclMaxWeight();
558 else if( j == 0 )
560 BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ];
561 nSumRowB += ( nWeightX *rCol.GetBlue()) ;
562 nSumRowG += ( nWeightX *rCol.GetGreen()) ;
563 nSumRowR += ( nWeightX *rCol.GetRed()) ;
564 nTotalWeightX += nWeightX;
566 else if ( nRowRange == j )
568 BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
569 nSumRowB += ( nWeightX *rCol.GetBlue() );
570 nSumRowG += ( nWeightX *rCol.GetGreen() );
571 nSumRowR += ( nWeightX *rCol.GetRed() );
572 nTotalWeightX += nWeightX;
574 else
576 nSumRowB += rCol.GetBlue() << MAP_PRECISION;
577 nSumRowG += rCol.GetGreen() << MAP_PRECISION;
578 nSumRowR += rCol.GetRed() << MAP_PRECISION;
579 nTotalWeightX += lclMaxWeight();
583 BilinearWeightType nWeightY = lclMaxWeight();
584 if( nY == nEndY )
585 nWeightY = lclMaxWeight();
586 else if( i == 0 )
587 nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ];
588 else if( nLineRange == 1 )
589 nWeightY = rCtx.maMapFY[ nTop ];
590 else if ( nLineRange == i )
591 nWeightY = rCtx.maMapFY[ nBottom ];
593 if (nTotalWeightX)
595 nSumRowB /= nTotalWeightX;
596 nSumRowG /= nTotalWeightX;
597 nSumRowR /= nTotalWeightX;
600 nSumB += nWeightY * nSumRowB;
601 nSumG += nWeightY * nSumRowG;
602 nSumR += nWeightY * nSumRowR;
603 nTotalWeightY += nWeightY;
606 if (nTotalWeightY)
608 nSumR /= nTotalWeightY;
609 nSumG /= nTotalWeightY;
610 nSumB /= nTotalWeightY;
613 BitmapColor aColRes(static_cast<sal_uInt8>(nSumR), static_cast<sal_uInt8>(nSumG), static_cast<sal_uInt8>(nSumB));
614 rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
619 void scaleDownPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY)
621 const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
623 for( long nY = nStartY; nY <= nEndY; nY++ )
625 long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
626 long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
628 long nLineStart, nLineRange;
629 if( nY ==nEndY )
631 nLineStart = rCtx.maMapIY[ nY ];
632 nLineRange = 0;
634 else
636 nLineStart = rCtx.maMapIY[ nTop ] ;
637 nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
640 Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
641 for( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
643 long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
644 long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
646 long nRowStart, nRowRange;
647 if( nX == nEndX )
649 nRowStart = rCtx.maMapIX[ nX ];
650 nRowRange = 0;
652 else
654 nRowStart = rCtx.maMapIX[ nLeft ];
655 nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
658 int nSumR = 0;
659 int nSumG = 0;
660 int nSumB = 0;
661 BilinearWeightType nTotalWeightY = 0;
663 for(long i = 0; i<= nLineRange; i++)
665 int nSumRowR = 0;
666 int nSumRowG = 0;
667 int nSumRowB = 0;
668 BilinearWeightType nTotalWeightX = 0;
670 Scanline pScanlineSrc = rCtx.mpSrc->GetScanline( nLineStart + i );
671 for(long j = 0; j <= nRowRange; j++)
673 BitmapColor aCol0 = rCtx.mpSrc->GetPaletteColor ( rCtx.mpSrc->GetIndexFromData( pScanlineSrc, nRowStart + j ) );
675 if(nX == nEndX )
678 nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
679 nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
680 nSumRowR += aCol0.GetRed() << MAP_PRECISION;
681 nTotalWeightX += lclMaxWeight();
683 else if( j == 0 )
686 BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ];
687 nSumRowB += ( nWeightX *aCol0.GetBlue()) ;
688 nSumRowG += ( nWeightX *aCol0.GetGreen()) ;
689 nSumRowR += ( nWeightX *aCol0.GetRed()) ;
690 nTotalWeightX += nWeightX;
692 else if ( nRowRange == j )
695 BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
696 nSumRowB += ( nWeightX *aCol0.GetBlue() );
697 nSumRowG += ( nWeightX *aCol0.GetGreen() );
698 nSumRowR += ( nWeightX *aCol0.GetRed() );
699 nTotalWeightX += nWeightX;
701 else
704 nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
705 nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
706 nSumRowR += aCol0.GetRed() << MAP_PRECISION;
707 nTotalWeightX += lclMaxWeight();
711 long nWeightY = lclMaxWeight();
712 if( nY == nEndY )
713 nWeightY = lclMaxWeight();
714 else if( i == 0 )
715 nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ];
716 else if( nLineRange == 1 )
717 nWeightY = rCtx.maMapFY[ nTop ];
718 else if ( nLineRange == i )
719 nWeightY = rCtx.maMapFY[ nBottom ];
721 if (nTotalWeightX)
723 nSumRowB /= nTotalWeightX;
724 nSumRowG /= nTotalWeightX;
725 nSumRowR /= nTotalWeightX;
728 nSumB += nWeightY * nSumRowB;
729 nSumG += nWeightY * nSumRowG;
730 nSumR += nWeightY * nSumRowR;
731 nTotalWeightY += nWeightY;
734 if (nTotalWeightY)
736 nSumR /= nTotalWeightY;
737 nSumG /= nTotalWeightY;
738 nSumB /= nTotalWeightY;
741 BitmapColor aColRes(static_cast<sal_uInt8>(nSumR), static_cast<sal_uInt8>(nSumG), static_cast<sal_uInt8>(nSumB));
742 rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
747 void scaleDown24bit(ScaleContext &rCtx, long nStartY, long nEndY)
749 const int constColorComponents = 3;
751 const long nStartX = 0;
752 const long nEndX = rCtx.mnDestW - 1;
754 for (long nY = nStartY; nY <= nEndY; nY++)
756 long nTop = rCtx.mbVMirr ? (nY + 1) : nY;
757 long nBottom = rCtx.mbVMirr ? nY : (nY + 1);
759 long nLineStart;
760 long nLineRange;
761 if (nY == nEndY)
763 nLineStart = rCtx.maMapIY[nY];
764 nLineRange = 0;
766 else
768 nLineStart = rCtx.maMapIY[nTop];
769 nLineRange = (rCtx.maMapIY[nBottom] == rCtx.maMapIY[nTop]) ?
770 1 : (rCtx.maMapIY[nBottom] - rCtx.maMapIY[nTop]);
773 Scanline pScanDest = rCtx.mpDest->GetScanline(nY);
774 for (long nX = nStartX; nX <= nEndX; nX++)
776 long nLeft = rCtx.mbHMirr ? (nX + 1) : nX;
777 long nRight = rCtx.mbHMirr ? nX : (nX + 1);
779 long nRowStart;
780 long nRowRange;
781 if (nX == nEndX)
783 nRowStart = rCtx.maMapIX[nX];
784 nRowRange = 0;
786 else
788 nRowStart = rCtx.maMapIX[nLeft];
789 nRowRange = (rCtx.maMapIX[nRight] == rCtx.maMapIX[nLeft]) ?
790 1 : (rCtx.maMapIX[nRight] - rCtx.maMapIX[nLeft]);
793 int nSum1 = 0;
794 int nSum2 = 0;
795 int nSum3 = 0;
796 BilinearWeightType nTotalWeightY = 0;
798 for (long i = 0; i<= nLineRange; i++)
800 Scanline pTmpY = rCtx.mpSrc->GetScanline(nLineStart + i);
801 Scanline pTmpX = pTmpY + constColorComponents * nRowStart;
803 int nSumRow1 = 0;
804 int nSumRow2 = 0;
805 int nSumRow3 = 0;
806 BilinearWeightType nTotalWeightX = 0;
808 for (long j = 0; j <= nRowRange; j++)
810 if (nX == nEndX)
812 nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++;
813 nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++;
814 nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++;
815 nTotalWeightX += lclMaxWeight();
817 else if(j == 0)
819 BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[nLeft];
820 nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++;
821 nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++;
822 nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++;
823 nTotalWeightX += nWeightX;
825 else if ( nRowRange == j )
827 BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
828 nSumRow1 += (nWeightX * (*pTmpX)); pTmpX++;
829 nSumRow2 += (nWeightX * (*pTmpX)); pTmpX++;
830 nSumRow3 += (nWeightX * (*pTmpX)); pTmpX++;
831 nTotalWeightX += nWeightX;
833 else
835 nSumRow1 += (*pTmpX) << MAP_PRECISION; pTmpX++;
836 nSumRow2 += (*pTmpX) << MAP_PRECISION; pTmpX++;
837 nSumRow3 += (*pTmpX) << MAP_PRECISION; pTmpX++;
838 nTotalWeightX += lclMaxWeight();
842 BilinearWeightType nWeightY = lclMaxWeight();
843 if (nY == nEndY)
844 nWeightY = lclMaxWeight();
845 else if (i == 0)
846 nWeightY = lclMaxWeight() - rCtx.maMapFY[nTop];
847 else if (nLineRange == 1)
848 nWeightY = rCtx.maMapFY[nTop];
849 else if (nLineRange == i)
850 nWeightY = rCtx.maMapFY[nBottom];
852 if (nTotalWeightX)
854 nSumRow1 /= nTotalWeightX;
855 nSumRow2 /= nTotalWeightX;
856 nSumRow3 /= nTotalWeightX;
858 nSum1 += nWeightY * nSumRow1;
859 nSum2 += nWeightY * nSumRow2;
860 nSum3 += nWeightY * nSumRow3;
861 nTotalWeightY += nWeightY;
864 if (nTotalWeightY)
866 nSum1 /= nTotalWeightY;
867 nSum2 /= nTotalWeightY;
868 nSum3 /= nTotalWeightY;
871 // Write the calculated color components to the destination
872 *pScanDest = nSum1; pScanDest++;
873 *pScanDest = nSum2; pScanDest++;
874 *pScanDest = nSum3; pScanDest++;
879 void scaleDownNonPaletteGeneral(ScaleContext &rCtx, long nStartY, long nEndY)
881 const long nStartX = 0, nEndX = rCtx.mnDestW - 1;
883 for( long nY = nStartY; nY <= nEndY; nY++ )
885 long nTop = rCtx.mbVMirr ? ( nY + 1 ) : nY;
886 long nBottom = rCtx.mbVMirr ? nY : ( nY + 1 ) ;
888 long nLineStart, nLineRange;
889 if( nY ==nEndY )
891 nLineStart = rCtx.maMapIY[ nY ];
892 nLineRange = 0;
894 else
896 nLineStart = rCtx.maMapIY[ nTop ] ;
897 nLineRange = ( rCtx.maMapIY[ nBottom ] == rCtx.maMapIY[ nTop ] ) ? 1 :( rCtx.maMapIY[ nBottom ] - rCtx.maMapIY[ nTop ] );
900 Scanline pScanDest = rCtx.mpDest->GetScanline( nY );
901 for( long nX = nStartX , nXDst = 0; nX <= nEndX; nX++ )
903 long nLeft = rCtx.mbHMirr ? ( nX + 1 ) : nX;
904 long nRight = rCtx.mbHMirr ? nX : ( nX + 1 ) ;
906 long nRowStart, nRowRange;
907 if( nX == nEndX )
909 nRowStart = rCtx.maMapIX[ nX ];
910 nRowRange = 0;
912 else
914 nRowStart = rCtx.maMapIX[ nLeft ];
915 nRowRange = ( rCtx.maMapIX[ nRight ] == rCtx.maMapIX[ nLeft ] )? 1 : ( rCtx.maMapIX[ nRight ] - rCtx.maMapIX[ nLeft ] );
918 int nSumR = 0;
919 int nSumG = 0;
920 int nSumB = 0;
921 BilinearWeightType nTotalWeightY = 0;
923 for(long i = 0; i<= nLineRange; i++)
925 int nSumRowR = 0;
926 int nSumRowG = 0;
927 int nSumRowB = 0;
928 BilinearWeightType nTotalWeightX = 0;
930 Scanline pScanlineSrc = rCtx.mpSrc->GetScanline( nLineStart + i );
931 for(long j = 0; j <= nRowRange; j++)
933 BitmapColor aCol0 = rCtx.mpSrc->GetPixelFromData( pScanlineSrc, nRowStart + j );
935 if(nX == nEndX )
938 nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
939 nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
940 nSumRowR += aCol0.GetRed() << MAP_PRECISION;
941 nTotalWeightX += lclMaxWeight();
943 else if( j == 0 )
946 BilinearWeightType nWeightX = lclMaxWeight() - rCtx.maMapFX[ nLeft ];
947 nSumRowB += ( nWeightX *aCol0.GetBlue()) ;
948 nSumRowG += ( nWeightX *aCol0.GetGreen()) ;
949 nSumRowR += ( nWeightX *aCol0.GetRed()) ;
950 nTotalWeightX += nWeightX;
952 else if ( nRowRange == j )
955 BilinearWeightType nWeightX = rCtx.maMapFX[ nRight ] ;
956 nSumRowB += ( nWeightX *aCol0.GetBlue() );
957 nSumRowG += ( nWeightX *aCol0.GetGreen() );
958 nSumRowR += ( nWeightX *aCol0.GetRed() );
959 nTotalWeightX += nWeightX;
961 else
963 nSumRowB += aCol0.GetBlue() << MAP_PRECISION;
964 nSumRowG += aCol0.GetGreen() << MAP_PRECISION;
965 nSumRowR += aCol0.GetRed() << MAP_PRECISION;
966 nTotalWeightX += lclMaxWeight();
970 BilinearWeightType nWeightY = lclMaxWeight();
971 if( nY == nEndY )
972 nWeightY = lclMaxWeight();
973 else if( i == 0 )
974 nWeightY = lclMaxWeight() - rCtx.maMapFY[ nTop ];
975 else if( nLineRange == 1 )
976 nWeightY = rCtx.maMapFY[ nTop ];
977 else if ( nLineRange == i )
978 nWeightY = rCtx.maMapFY[ nBottom ];
980 if (nTotalWeightX)
982 nSumRowB /= nTotalWeightX;
983 nSumRowG /= nTotalWeightX;
984 nSumRowR /= nTotalWeightX;
987 nSumB += nWeightY * nSumRowB;
988 nSumG += nWeightY * nSumRowG;
989 nSumR += nWeightY * nSumRowR;
990 nTotalWeightY += nWeightY;
993 if (nTotalWeightY)
995 nSumR /= nTotalWeightY;
996 nSumG /= nTotalWeightY;
997 nSumB /= nTotalWeightY;
1000 BitmapColor aColRes(static_cast<sal_uInt8>(nSumR), static_cast<sal_uInt8>(nSumG), static_cast<sal_uInt8>(nSumB));
1001 rCtx.mpDest->SetPixelOnData( pScanDest, nXDst++, aColRes );
1006 } // end anonymous namespace
1008 BitmapScaleSuperFilter::BitmapScaleSuperFilter(const double& rScaleX, const double& rScaleY) :
1009 mrScaleX(rScaleX),
1010 mrScaleY(rScaleY)
1013 BitmapScaleSuperFilter::~BitmapScaleSuperFilter()
1016 BitmapEx BitmapScaleSuperFilter::execute(BitmapEx const& rBitmap) const
1018 Bitmap aBitmap(rBitmap.GetBitmap());
1019 SalBitmap* pKey = aBitmap.ImplGetSalBitmap().get();
1021 bool bRet = false;
1023 const Size aSizePix(rBitmap.GetSizePixel());
1025 bool bHMirr = mrScaleX < 0;
1026 bool bVMirr = mrScaleY < 0;
1028 double fScaleX = std::fabs(mrScaleX);
1029 double fScaleY = std::fabs(mrScaleY);
1031 const long nDstW = FRound(aSizePix.Width() * fScaleX);
1032 const long nDstH = FRound(aSizePix.Height() * fScaleY);
1034 const double fScaleThresh = 0.6;
1036 if (nDstW <= 1 || nDstH <= 1)
1037 return BitmapEx();
1039 // check cache for a previously scaled version of this
1040 ImplSVData* pSVData = ImplGetSVData();
1041 auto& rCache = pSVData->maGDIData.maScaleCache;
1042 auto aFind = rCache.find(pKey);
1043 if (aFind != rCache.end())
1045 if (aFind->second.GetSizePixel().Width() == nDstW && aFind->second.GetSizePixel().Height() == nDstH)
1046 return aFind->second;
1050 Bitmap::ScopedReadAccess pReadAccess(aBitmap);
1052 sal_uInt16 nSourceBitcount = aBitmap.GetBitCount();
1054 Bitmap aOutBmp(Size(nDstW, nDstH), std::max(nSourceBitcount, sal_uInt16(24)));
1055 Size aOutSize = aOutBmp.GetSizePixel();
1056 sal_uInt16 nTargetBitcount = aOutBmp.GetBitCount();
1058 if (!aOutSize.Width() || !aOutSize.Height())
1060 SAL_WARN("vcl.gdi", "bmp creation failed");
1061 return BitmapEx();
1064 BitmapScopedWriteAccess pWriteAccess(aOutBmp);
1066 const long nStartY = 0;
1067 const long nEndY = nDstH - 1;
1069 if (pReadAccess && pWriteAccess)
1071 ScaleRangeFn pScaleRangeFn;
1072 ScaleContext aContext( pReadAccess.get(),
1073 pWriteAccess.get(),
1074 pReadAccess->Width(),
1075 pWriteAccess->Width(),
1076 pReadAccess->Height(),
1077 pWriteAccess->Height(),
1078 bVMirr, bHMirr );
1080 bool bScaleUp = fScaleX >= fScaleThresh && fScaleY >= fScaleThresh;
1081 // If we have a source bitmap with a palette the scaling converts
1082 // from up to 8 bit image -> 24 bit non-palette, which is then
1083 // adapted back to the same type as original.
1084 if (pReadAccess->HasPalette())
1086 switch( pReadAccess->GetScanlineFormat() )
1088 case ScanlineFormat::N8BitPal:
1089 pScaleRangeFn = bScaleUp ? scaleUpPalette8bit
1090 : scaleDownPalette8bit;
1091 break;
1092 default:
1093 pScaleRangeFn = bScaleUp ? scaleUpPaletteGeneral
1094 : scaleDownPaletteGeneral;
1095 break;
1098 // Here we know that we are dealing with a non-palette source bitmap.
1099 // The target is either 24 or 32 bit, depending on the image and
1100 // the capabilities of the backend. If for some reason the destination
1101 // is not the same bit-depth as the source, then we can't use
1102 // a fast path, so we always need to process with a general scaler.
1103 else if (nSourceBitcount != nTargetBitcount)
1105 pScaleRangeFn = bScaleUp ? scaleUpNonPaletteGeneral : scaleDownNonPaletteGeneral;
1107 // If we get here then we can only use a fast path, but let's
1108 // still keep the fallback to the general scaler alive.
1109 else
1111 switch( pReadAccess->GetScanlineFormat() )
1113 case ScanlineFormat::N24BitTcBgr:
1114 case ScanlineFormat::N24BitTcRgb:
1115 pScaleRangeFn = bScaleUp ? scaleUp24bit : scaleDown24bit;
1116 break;
1117 case ScanlineFormat::N32BitTcRgba:
1118 case ScanlineFormat::N32BitTcBgra:
1119 case ScanlineFormat::N32BitTcArgb:
1120 case ScanlineFormat::N32BitTcAbgr:
1121 pScaleRangeFn = bScaleUp ? scaleUp32bit : scaleDown32bit;
1122 break;
1123 default:
1124 pScaleRangeFn = bScaleUp ? scaleUpNonPaletteGeneral
1125 : scaleDownNonPaletteGeneral;
1126 break;
1130 // We want to thread - only if there is a lot of work to do:
1131 // We work hard when there is a large destination image, or
1132 // A large source image.
1133 bool bHorizontalWork = pReadAccess->Height() >= 512 && pReadAccess->Width() >= 512;
1134 bool bUseThreads = true;
1136 static bool bDisableThreadedScaling = getenv ("VCL_NO_THREAD_SCALE");
1137 if (bDisableThreadedScaling || !bHorizontalWork)
1139 SAL_INFO("vcl.gdi", "Scale in main thread");
1140 bUseThreads = false;
1143 if (bUseThreads)
1147 // partition and queue work
1148 comphelper::ThreadPool &rShared = comphelper::ThreadPool::getSharedOptimalPool();
1149 std::shared_ptr<comphelper::ThreadTaskTag> pTag = comphelper::ThreadPool::createThreadTaskTag();
1151 long nStripYStart = nStartY;
1152 long nStripYEnd = nStripYStart + constScaleThreadStrip - 1;
1154 while (nStripYEnd < nEndY)
1156 std::unique_ptr<ScaleTask> pTask(new ScaleTask(pTag, pScaleRangeFn, aContext, nStripYStart, nStripYEnd));
1157 rShared.pushTask(std::move(pTask));
1158 nStripYStart += constScaleThreadStrip;
1159 nStripYEnd += constScaleThreadStrip;
1161 if (nStripYStart <= nEndY)
1163 std::unique_ptr<ScaleTask> pTask(new ScaleTask(pTag, pScaleRangeFn, aContext, nStripYStart, nEndY));
1164 rShared.pushTask(std::move(pTask));
1166 rShared.waitUntilDone(pTag);
1167 SAL_INFO("vcl.gdi", "All threaded scaling tasks complete");
1169 catch (...)
1171 SAL_WARN("vcl.gdi", "threaded bitmap scaling failed");
1172 bUseThreads = false;
1176 if (!bUseThreads)
1177 pScaleRangeFn( aContext, nStartY, nEndY );
1179 bRet = true;
1180 aBitmap.AdaptBitCount(aOutBmp);
1181 aBitmap = aOutBmp;
1185 if (bRet)
1187 tools::Rectangle aRect(Point(0, 0), Point(nDstW, nDstH));
1188 aBitmap.Crop(aRect);
1189 BitmapEx aRet(aBitmap);
1190 rCache.insert(std::make_pair(pKey, aRet));
1191 return aRet;
1194 return BitmapEx();
1198 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */