Get the style color and number just once
[LibreOffice.git] / vcl / source / bitmap / bitmappaint.cxx
blobccfd14b3f760dbb5cdf64e56b516115bfb532cb9
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 <tools/poly.hxx>
21 #include <tools/helpers.hxx>
23 #include <vcl/bitmap.hxx>
24 #include <vcl/alpha.hxx>
26 #include <vcl/BitmapWriteAccess.hxx>
27 #include <salbmp.hxx>
28 #include <svdata.hxx>
29 #include <salinst.hxx>
31 #include <algorithm>
32 #include <memory>
34 static BitmapColor UpdatePaletteForNewColor(BitmapScopedWriteAccess& pAcc,
35 const sal_uInt16 nActColors,
36 const sal_uInt16 nMaxColors, const tools::Long nHeight,
37 const tools::Long nWidth,
38 const BitmapColor& rWantedColor);
40 bool Bitmap::Erase(const Color& rFillColor)
42 if (IsEmpty())
43 return true;
45 // implementation specific replace
46 std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
47 if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Erase(rFillColor))
49 ImplSetSalBitmap(xImpBmp);
50 maPrefMapMode = MapMode(MapUnit::MapPixel);
51 maPrefSize = xImpBmp->GetSize();
52 return true;
55 BitmapScopedWriteAccess pWriteAcc(*this);
56 bool bRet = false;
58 if (pWriteAcc)
60 pWriteAcc->Erase(rFillColor);
61 bRet = true;
64 return bRet;
67 bool Bitmap::Invert()
69 if (!mxSalBmp)
70 return false;
72 // try optimised call, much faster on Skia
73 if (mxSalBmp->Invert())
75 mxSalBmp->InvalidateChecksum();
76 return true;
79 BitmapScopedWriteAccess pWriteAcc(*this);
80 const tools::Long nWidth = pWriteAcc->Width();
81 const tools::Long nHeight = pWriteAcc->Height();
83 if (pWriteAcc->HasPalette())
85 const sal_uInt16 nActColors = pWriteAcc->GetPaletteEntryCount();
87 if (pWriteAcc->GetPalette().IsGreyPalette8Bit())
89 // For alpha masks, we need to actually invert the underlying data
90 // or the optimisations elsewhere do not always work right. If this is a bottleneck,
91 // probably better to try improving it inside the mxSalBmp->Invert() call above.
92 for (tools::Long nY = 0; nY < nHeight; nY++)
94 Scanline pScanline = pWriteAcc->GetScanline(nY);
95 for (tools::Long nX = 0; nX < nWidth; nX++)
97 BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX);
98 aBmpColor.SetIndex(0xff - aBmpColor.GetIndex());
99 pWriteAcc->SetPixelOnData(pScanline, nX, aBmpColor);
103 else
105 for (sal_uInt16 i = 0; i < nActColors; ++i)
107 BitmapColor aBmpColor = pWriteAcc->GetPaletteColor(i);
108 aBmpColor.Invert();
109 pWriteAcc->SetPaletteColor(i, aBmpColor);
113 else
115 for (tools::Long nY = 0; nY < nHeight; nY++)
117 Scanline pScanline = pWriteAcc->GetScanline(nY);
118 for (tools::Long nX = 0; nX < nWidth; nX++)
120 BitmapColor aBmpColor = pWriteAcc->GetPixelFromData(pScanline, nX);
121 aBmpColor.Invert();
122 pWriteAcc->SetPixelOnData(pScanline, nX, aBmpColor);
126 mxSalBmp->InvalidateChecksum();
128 return true;
131 namespace
133 // Put each scanline's content horizontally mirrored into the other one.
134 // (optimized version accessing pixel values directly).
135 template <int bitCount>
136 void mirrorScanlines(Scanline scanline1, Scanline scanline2, tools::Long nWidth)
138 constexpr int byteCount = bitCount / 8;
139 Scanline pos1 = scanline1;
140 Scanline pos2 = scanline2 + (nWidth - 1) * byteCount; // last in second scanline
141 sal_uInt8 tmp[byteCount];
142 for (tools::Long i = 0; i < nWidth; ++i)
144 memcpy(tmp, pos1, byteCount);
145 memcpy(pos1, pos2, byteCount);
146 memcpy(pos2, tmp, byteCount);
147 pos1 += byteCount;
148 pos2 -= byteCount;
153 bool Bitmap::Mirror(BmpMirrorFlags nMirrorFlags)
155 bool bHorz(nMirrorFlags & BmpMirrorFlags::Horizontal);
156 bool bVert(nMirrorFlags & BmpMirrorFlags::Vertical);
157 bool bRet = false;
159 if (bHorz && !bVert)
161 BitmapScopedWriteAccess pAcc(*this);
163 if (pAcc)
165 const tools::Long nWidth = pAcc->Width();
166 const tools::Long nHeight = pAcc->Height();
167 const tools::Long nWidth1 = nWidth - 1;
168 const tools::Long nWidth_2 = nWidth / 2;
169 const tools::Long nSecondHalf = nWidth - nWidth_2;
171 switch (pAcc->GetBitCount())
173 // Special-case these, swap the halves of scanlines while mirroring them.
174 case 32:
175 for (tools::Long nY = 0; nY < nHeight; nY++)
176 mirrorScanlines<32>(pAcc->GetScanline(nY),
177 pAcc->GetScanline(nY) + 4 * nSecondHalf, nWidth_2);
178 break;
179 case 24:
180 for (tools::Long nY = 0; nY < nHeight; nY++)
181 mirrorScanlines<24>(pAcc->GetScanline(nY),
182 pAcc->GetScanline(nY) + 3 * nSecondHalf, nWidth_2);
183 break;
184 case 8:
185 for (tools::Long nY = 0; nY < nHeight; nY++)
186 mirrorScanlines<8>(pAcc->GetScanline(nY),
187 pAcc->GetScanline(nY) + nSecondHalf, nWidth_2);
188 break;
189 default:
190 for (tools::Long nY = 0; nY < nHeight; nY++)
192 Scanline pScanline = pAcc->GetScanline(nY);
193 for (tools::Long nX = 0, nOther = nWidth1; nX < nWidth_2; nX++, nOther--)
195 const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX));
197 pAcc->SetPixelOnData(pScanline, nX,
198 pAcc->GetPixelFromData(pScanline, nOther));
199 pAcc->SetPixelOnData(pScanline, nOther, aTemp);
204 pAcc.reset();
205 bRet = true;
208 else if (bVert && !bHorz)
210 BitmapScopedWriteAccess pAcc(*this);
212 if (pAcc)
214 const tools::Long nScanSize = pAcc->GetScanlineSize();
215 std::unique_ptr<sal_uInt8[]> pBuffer(new sal_uInt8[nScanSize]);
216 const tools::Long nHeight = pAcc->Height();
217 const tools::Long nHeight1 = nHeight - 1;
218 const tools::Long nHeight_2 = nHeight >> 1;
220 for (tools::Long nY = 0, nOther = nHeight1; nY < nHeight_2; nY++, nOther--)
222 memcpy(pBuffer.get(), pAcc->GetScanline(nY), nScanSize);
223 memcpy(pAcc->GetScanline(nY), pAcc->GetScanline(nOther), nScanSize);
224 memcpy(pAcc->GetScanline(nOther), pBuffer.get(), nScanSize);
227 pAcc.reset();
228 bRet = true;
231 else if (bHorz && bVert)
233 BitmapScopedWriteAccess pAcc(*this);
235 if (pAcc)
237 const tools::Long nWidth = pAcc->Width();
238 const tools::Long nWidth1 = nWidth - 1;
239 const tools::Long nHeight = pAcc->Height();
240 tools::Long nHeight_2 = nHeight / 2;
241 const tools::Long nWidth_2 = nWidth / 2;
242 const tools::Long nSecondHalf = nWidth - nWidth_2;
244 switch (pAcc->GetBitCount())
246 case 32:
247 for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
248 mirrorScanlines<32>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY),
249 nWidth);
250 if (nHeight & 1)
251 mirrorScanlines<32>(pAcc->GetScanline(nHeight_2),
252 pAcc->GetScanline(nHeight_2) + 4 * nSecondHalf,
253 nWidth_2);
254 break;
255 case 24:
256 for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
257 mirrorScanlines<24>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY),
258 nWidth);
259 if (nHeight & 1)
260 mirrorScanlines<24>(pAcc->GetScanline(nHeight_2),
261 pAcc->GetScanline(nHeight_2) + 3 * nSecondHalf,
262 nWidth_2);
263 break;
264 case 8:
265 for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
266 mirrorScanlines<8>(pAcc->GetScanline(nY), pAcc->GetScanline(nOtherY),
267 nWidth);
268 if (nHeight & 1)
269 mirrorScanlines<8>(pAcc->GetScanline(nHeight_2),
270 pAcc->GetScanline(nHeight_2) + nSecondHalf, nWidth_2);
271 break;
272 default:
273 for (tools::Long nY = 0, nOtherY = nHeight - 1; nY < nHeight_2; nY++, nOtherY--)
275 Scanline pScanline = pAcc->GetScanline(nY);
276 Scanline pScanlineOther = pAcc->GetScanline(nOtherY);
277 for (tools::Long nX = 0, nOtherX = nWidth1; nX < nWidth; nX++, nOtherX--)
279 const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX));
281 pAcc->SetPixelOnData(pScanline, nX,
282 pAcc->GetPixelFromData(pScanlineOther, nOtherX));
283 pAcc->SetPixelOnData(pScanlineOther, nOtherX, aTemp);
287 // if necessary, also mirror the middle line horizontally
288 if (nHeight & 1)
290 Scanline pScanline = pAcc->GetScanline(nHeight_2);
291 for (tools::Long nX = 0, nOtherX = nWidth1; nX < nWidth_2; nX++, nOtherX--)
293 const BitmapColor aTemp(pAcc->GetPixelFromData(pScanline, nX));
294 pAcc->SetPixelOnData(pScanline, nX,
295 pAcc->GetPixelFromData(pScanline, nOtherX));
296 pAcc->SetPixelOnData(pScanline, nOtherX, aTemp);
301 pAcc.reset();
302 bRet = true;
305 else
306 bRet = true;
308 return bRet;
311 bool Bitmap::Rotate(Degree10 nAngle10, const Color& rFillColor)
313 nAngle10 %= 3600_deg10;
314 nAngle10 = (nAngle10 < 0_deg10) ? (Degree10(3599) + nAngle10) : nAngle10;
316 if (!nAngle10)
317 return true;
318 if (nAngle10 == 1800_deg10)
319 return Mirror(BmpMirrorFlags::Horizontal | BmpMirrorFlags::Vertical);
321 BitmapScopedReadAccess pReadAcc(*this);
322 if (!pReadAcc)
323 return false;
325 Bitmap aRotatedBmp;
326 bool bRet = false;
327 const Size aSizePix(GetSizePixel());
329 if (nAngle10 == 900_deg10 || nAngle10 == 2700_deg10)
331 const Size aNewSizePix(aSizePix.Height(), aSizePix.Width());
332 Bitmap aNewBmp(aNewSizePix, getPixelFormat(), &pReadAcc->GetPalette());
333 BitmapScopedWriteAccess pWriteAcc(aNewBmp);
335 if (pWriteAcc)
337 const tools::Long nWidth = aSizePix.Width();
338 const tools::Long nWidth1 = nWidth - 1;
339 const tools::Long nHeight = aSizePix.Height();
340 const tools::Long nHeight1 = nHeight - 1;
341 const tools::Long nNewWidth = aNewSizePix.Width();
342 const tools::Long nNewHeight = aNewSizePix.Height();
344 if (nAngle10 == 900_deg10)
346 for (tools::Long nY = 0, nOtherX = nWidth1; nY < nNewHeight; nY++, nOtherX--)
348 Scanline pScanline = pWriteAcc->GetScanline(nY);
349 for (tools::Long nX = 0, nOtherY = 0; nX < nNewWidth; nX++)
351 pWriteAcc->SetPixelOnData(pScanline, nX,
352 pReadAcc->GetPixel(nOtherY++, nOtherX));
356 else if (nAngle10 == 2700_deg10)
358 for (tools::Long nY = 0, nOtherX = 0; nY < nNewHeight; nY++, nOtherX++)
360 Scanline pScanline = pWriteAcc->GetScanline(nY);
361 for (tools::Long nX = 0, nOtherY = nHeight1; nX < nNewWidth; nX++)
363 pWriteAcc->SetPixelOnData(pScanline, nX,
364 pReadAcc->GetPixel(nOtherY--, nOtherX));
369 pWriteAcc.reset();
372 aRotatedBmp = std::move(aNewBmp);
374 else
376 Point aTmpPoint;
377 tools::Rectangle aTmpRectangle(aTmpPoint, aSizePix);
378 tools::Polygon aPoly(aTmpRectangle);
379 aPoly.Rotate(aTmpPoint, nAngle10);
381 tools::Rectangle aNewBound(aPoly.GetBoundRect());
382 const Size aNewSizePix(aNewBound.GetSize());
383 Bitmap aNewBmp(aNewSizePix, getPixelFormat(), &pReadAcc->GetPalette());
384 BitmapScopedWriteAccess pWriteAcc(aNewBmp);
386 if (pWriteAcc)
388 const BitmapColor aFillColor(pWriteAcc->GetBestMatchingColor(rFillColor));
389 const double fCosAngle = cos(toRadians(nAngle10));
390 const double fSinAngle = sin(toRadians(nAngle10));
391 const double fXMin = aNewBound.Left();
392 const double fYMin = aNewBound.Top();
393 const sal_Int32 nWidth = aSizePix.Width();
394 const sal_Int32 nHeight = aSizePix.Height();
395 const sal_Int32 nNewWidth = aNewSizePix.Width();
396 const sal_Int32 nNewHeight = aNewSizePix.Height();
397 // we store alternating values of cos/sin. We do this instead of
398 // separate arrays to improve cache hit.
399 std::unique_ptr<sal_Int32[]> pCosSinX(new sal_Int32[nNewWidth * 2]);
400 std::unique_ptr<sal_Int32[]> pCosSinY(new sal_Int32[nNewHeight * 2]);
402 for (sal_Int32 nIdx = 0, nX = 0; nX < nNewWidth; nX++)
404 const double fTmp = (fXMin + nX) * 64;
406 pCosSinX[nIdx++] = std::round(fCosAngle * fTmp);
407 pCosSinX[nIdx++] = std::round(fSinAngle * fTmp);
410 for (sal_Int32 nIdx = 0, nY = 0; nY < nNewHeight; nY++)
412 const double fTmp = (fYMin + nY) * 64;
414 pCosSinY[nIdx++] = std::round(fCosAngle * fTmp);
415 pCosSinY[nIdx++] = std::round(fSinAngle * fTmp);
418 for (sal_Int32 nCosSinYIdx = 0, nY = 0; nY < nNewHeight; nY++)
420 sal_Int32 nCosY = pCosSinY[nCosSinYIdx++];
421 sal_Int32 nSinY = pCosSinY[nCosSinYIdx++];
422 Scanline pScanline = pWriteAcc->GetScanline(nY);
424 for (sal_Int32 nCosSinXIdx = 0, nX = 0; nX < nNewWidth; nX++)
426 sal_Int32 nRotX = (pCosSinX[nCosSinXIdx++] - nSinY) >> 6;
427 sal_Int32 nRotY = (pCosSinX[nCosSinXIdx++] + nCosY) >> 6;
429 if ((nRotX > -1) && (nRotX < nWidth) && (nRotY > -1) && (nRotY < nHeight))
431 pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixel(nRotY, nRotX));
433 else
435 pWriteAcc->SetPixelOnData(pScanline, nX, aFillColor);
440 pWriteAcc.reset();
443 aRotatedBmp = std::move(aNewBmp);
446 pReadAcc.reset();
448 bRet = !aRotatedBmp.IsEmpty();
449 if (bRet)
450 ReassignWithSize(aRotatedBmp);
452 return bRet;
455 Bitmap Bitmap::CreateMask(const Color& rTransColor) const
457 BitmapScopedReadAccess pReadAcc(*this);
458 if (!pReadAcc)
459 return Bitmap();
461 // Historically LO used 1bpp masks, but 8bpp masks are much faster,
462 // better supported by hardware, and the memory savings are not worth
463 // it anymore.
464 // TODO: Possibly remove the 1bpp code later.
466 if ((pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal)
467 && pReadAcc->GetBestMatchingColor(COL_WHITE) == pReadAcc->GetBestMatchingColor(rTransColor))
469 // if we're a 1 bit pixel already, and the transcolor matches the color that would replace it
470 // already, then just return a copy
471 return *this;
474 auto ePixelFormat = vcl::PixelFormat::N8_BPP;
475 Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &Bitmap::GetGreyPalette(256));
476 BitmapScopedWriteAccess pWriteAcc(aNewBmp);
477 if (!pWriteAcc)
478 return Bitmap();
480 const tools::Long nWidth = pReadAcc->Width();
481 const tools::Long nHeight = pReadAcc->Height();
482 const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK));
483 const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE));
485 const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor));
487 if (pWriteAcc->GetScanlineFormat() == pReadAcc->GetScanlineFormat() && aWhite.GetIndex() == 1
488 && (pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal))
490 for (tools::Long nY = 0; nY < nHeight; ++nY)
492 Scanline pSrc = pReadAcc->GetScanline(nY);
493 Scanline pDst = pWriteAcc->GetScanline(nY);
494 assert(pWriteAcc->GetScanlineSize() == pReadAcc->GetScanlineSize());
495 const tools::Long nScanlineSize = pWriteAcc->GetScanlineSize();
496 for (tools::Long nX = 0; nX < nScanlineSize; ++nX)
497 pDst[nX] = ~pSrc[nX];
500 else if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal)
502 // optimized for 8Bit source palette
503 const sal_uInt8 cTest = aTest.GetIndex();
505 for (tools::Long nY = 0; nY < nHeight; ++nY)
507 Scanline pSrc = pReadAcc->GetScanline(nY);
508 Scanline pDst = pWriteAcc->GetScanline(nY);
509 for (tools::Long nX = 0; nX < nWidth; ++nX)
511 if (cTest == pSrc[nX])
512 pDst[nX] = aWhite.GetIndex();
513 else
514 pDst[nX] = aBlack.GetIndex();
518 else
520 // not optimized
521 for (tools::Long nY = 0; nY < nHeight; ++nY)
523 Scanline pScanline = pWriteAcc->GetScanline(nY);
524 Scanline pScanlineRead = pReadAcc->GetScanline(nY);
525 for (tools::Long nX = 0; nX < nWidth; ++nX)
527 if (aTest == pReadAcc->GetPixelFromData(pScanlineRead, nX))
528 pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
529 else
530 pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
535 pWriteAcc.reset();
536 pReadAcc.reset();
538 aNewBmp.maPrefSize = maPrefSize;
539 aNewBmp.maPrefMapMode = maPrefMapMode;
541 return aNewBmp;
544 Bitmap Bitmap::CreateMask(const Color& rTransColor, sal_uInt8 nTol) const
546 if (nTol == 0)
547 return CreateMask(rTransColor);
549 BitmapScopedReadAccess pReadAcc(*this);
550 if (!pReadAcc)
551 return Bitmap();
553 // Historically LO used 1bpp masks, but 8bpp masks are much faster,
554 // better supported by hardware, and the memory savings are not worth
555 // it anymore.
556 // TODO: Possibly remove the 1bpp code later.
558 auto ePixelFormat = vcl::PixelFormat::N8_BPP;
559 Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &Bitmap::GetGreyPalette(256));
560 BitmapScopedWriteAccess pWriteAcc(aNewBmp);
561 if (!pWriteAcc)
562 return Bitmap();
564 const tools::Long nWidth = pReadAcc->Width();
565 const tools::Long nHeight = pReadAcc->Height();
566 const BitmapColor aBlack(pWriteAcc->GetBestMatchingColor(COL_BLACK));
567 const BitmapColor aWhite(pWriteAcc->GetBestMatchingColor(COL_WHITE));
569 BitmapColor aCol;
570 tools::Long nR, nG, nB;
571 const tools::Long nMinR = std::clamp<tools::Long>(rTransColor.GetRed() - nTol, 0, 255);
572 const tools::Long nMaxR = std::clamp<tools::Long>(rTransColor.GetRed() + nTol, 0, 255);
573 const tools::Long nMinG = std::clamp<tools::Long>(rTransColor.GetGreen() - nTol, 0, 255);
574 const tools::Long nMaxG = std::clamp<tools::Long>(rTransColor.GetGreen() + nTol, 0, 255);
575 const tools::Long nMinB = std::clamp<tools::Long>(rTransColor.GetBlue() - nTol, 0, 255);
576 const tools::Long nMaxB = std::clamp<tools::Long>(rTransColor.GetBlue() + nTol, 0, 255);
578 if (pReadAcc->HasPalette())
580 for (tools::Long nY = 0; nY < nHeight; nY++)
582 Scanline pScanline = pWriteAcc->GetScanline(nY);
583 Scanline pScanlineRead = pReadAcc->GetScanline(nY);
584 for (tools::Long nX = 0; nX < nWidth; nX++)
586 aCol = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
587 nR = aCol.GetRed();
588 nG = aCol.GetGreen();
589 nB = aCol.GetBlue();
591 if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB
592 && nMaxB >= nB)
594 pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
596 else
598 pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
603 else
605 for (tools::Long nY = 0; nY < nHeight; nY++)
607 Scanline pScanline = pWriteAcc->GetScanline(nY);
608 Scanline pScanlineRead = pReadAcc->GetScanline(nY);
609 for (tools::Long nX = 0; nX < nWidth; nX++)
611 aCol = pReadAcc->GetPixelFromData(pScanlineRead, nX);
612 nR = aCol.GetRed();
613 nG = aCol.GetGreen();
614 nB = aCol.GetBlue();
616 if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB
617 && nMaxB >= nB)
619 pWriteAcc->SetPixelOnData(pScanline, nX, aWhite);
621 else
623 pWriteAcc->SetPixelOnData(pScanline, nX, aBlack);
629 pWriteAcc.reset();
630 pReadAcc.reset();
632 aNewBmp.maPrefSize = maPrefSize;
633 aNewBmp.maPrefMapMode = maPrefMapMode;
635 return aNewBmp;
638 AlphaMask Bitmap::CreateAlphaMask(const Color& rTransColor) const
640 BitmapScopedReadAccess pReadAcc(*this);
641 if (!pReadAcc)
642 return AlphaMask();
644 // Historically LO used 1bpp masks, but 8bpp masks are much faster,
645 // better supported by hardware, and the memory savings are not worth
646 // it anymore.
648 if ((pReadAcc->GetScanlineFormat() == ScanlineFormat::N1BitMsbPal)
649 && pReadAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT)
650 == pReadAcc->GetBestMatchingColor(rTransColor))
652 // if we're a 1 bit pixel already, and the transcolor matches the color that would replace it
653 // already, then just return a copy
654 return AlphaMask(*this);
657 AlphaMask aNewBmp(GetSizePixel());
658 BitmapScopedWriteAccess pWriteAcc(aNewBmp);
659 if (!pWriteAcc)
660 return AlphaMask();
662 const tools::Long nWidth = pReadAcc->Width();
663 const tools::Long nHeight = pReadAcc->Height();
664 const BitmapColor aOpaqueColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_OPAQUE));
665 const BitmapColor aTransparentColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT));
667 const BitmapColor aTest(pReadAcc->GetBestMatchingColor(rTransColor));
669 if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal)
671 // optimized for 8Bit source palette
672 const sal_uInt8 cTest = aTest.GetIndex();
674 for (tools::Long nY = 0; nY < nHeight; ++nY)
676 Scanline pSrc = pReadAcc->GetScanline(nY);
677 Scanline pDst = pWriteAcc->GetScanline(nY);
678 for (tools::Long nX = 0; nX < nWidth; ++nX)
680 if (cTest == pSrc[nX])
681 pDst[nX] = aTransparentColor.GetIndex();
682 else
683 pDst[nX] = aOpaqueColor.GetIndex();
687 else
689 // not optimized
690 for (tools::Long nY = 0; nY < nHeight; ++nY)
692 Scanline pScanline = pWriteAcc->GetScanline(nY);
693 Scanline pScanlineRead = pReadAcc->GetScanline(nY);
694 for (tools::Long nX = 0; nX < nWidth; ++nX)
696 if (aTest == pReadAcc->GetPixelFromData(pScanlineRead, nX))
697 pWriteAcc->SetPixelOnData(pScanline, nX, aTransparentColor);
698 else
699 pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor);
704 pWriteAcc.reset();
705 pReadAcc.reset();
707 aNewBmp.SetPrefSize(maPrefSize);
708 aNewBmp.SetPrefMapMode(maPrefMapMode);
710 return aNewBmp;
713 AlphaMask Bitmap::CreateAlphaMask(const Color& rTransColor, sal_uInt8 nTol) const
715 if (nTol == 0)
716 return CreateAlphaMask(rTransColor);
718 BitmapScopedReadAccess pReadAcc(*this);
719 if (!pReadAcc)
720 return AlphaMask();
722 // Historically LO used 1bpp masks, but 8bpp masks are much faster,
723 // better supported by hardware, and the memory savings are not worth
724 // it anymore.
725 // TODO: Possibly remove the 1bpp code later.
727 AlphaMask aNewBmp(GetSizePixel());
728 BitmapScopedWriteAccess pWriteAcc(aNewBmp);
729 if (!pWriteAcc)
730 return AlphaMask();
732 const tools::Long nWidth = pReadAcc->Width();
733 const tools::Long nHeight = pReadAcc->Height();
734 const BitmapColor aOpaqueColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_OPAQUE));
735 const BitmapColor aTransparentColor(pWriteAcc->GetBestMatchingColor(COL_ALPHA_TRANSPARENT));
737 BitmapColor aCol;
738 tools::Long nR, nG, nB;
739 const tools::Long nMinR = std::clamp<tools::Long>(rTransColor.GetRed() - nTol, 0, 255);
740 const tools::Long nMaxR = std::clamp<tools::Long>(rTransColor.GetRed() + nTol, 0, 255);
741 const tools::Long nMinG = std::clamp<tools::Long>(rTransColor.GetGreen() - nTol, 0, 255);
742 const tools::Long nMaxG = std::clamp<tools::Long>(rTransColor.GetGreen() + nTol, 0, 255);
743 const tools::Long nMinB = std::clamp<tools::Long>(rTransColor.GetBlue() - nTol, 0, 255);
744 const tools::Long nMaxB = std::clamp<tools::Long>(rTransColor.GetBlue() + nTol, 0, 255);
746 if (pReadAcc->HasPalette())
748 for (tools::Long nY = 0; nY < nHeight; nY++)
750 Scanline pScanline = pWriteAcc->GetScanline(nY);
751 Scanline pScanlineRead = pReadAcc->GetScanline(nY);
752 for (tools::Long nX = 0; nX < nWidth; nX++)
754 aCol = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
755 nR = aCol.GetRed();
756 nG = aCol.GetGreen();
757 nB = aCol.GetBlue();
759 if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB
760 && nMaxB >= nB)
762 pWriteAcc->SetPixelOnData(pScanline, nX, aTransparentColor);
764 else
766 pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor);
771 else
773 for (tools::Long nY = 0; nY < nHeight; nY++)
775 Scanline pScanline = pWriteAcc->GetScanline(nY);
776 Scanline pScanlineRead = pReadAcc->GetScanline(nY);
777 for (tools::Long nX = 0; nX < nWidth; nX++)
779 aCol = pReadAcc->GetPixelFromData(pScanlineRead, nX);
780 nR = aCol.GetRed();
781 nG = aCol.GetGreen();
782 nB = aCol.GetBlue();
784 if (nMinR <= nR && nMaxR >= nR && nMinG <= nG && nMaxG >= nG && nMinB <= nB
785 && nMaxB >= nB)
787 pWriteAcc->SetPixelOnData(pScanline, nX, aTransparentColor);
789 else
791 pWriteAcc->SetPixelOnData(pScanline, nX, aOpaqueColor);
797 pWriteAcc.reset();
798 pReadAcc.reset();
800 aNewBmp.SetPrefSize(maPrefSize);
801 aNewBmp.SetPrefMapMode(maPrefMapMode);
803 return aNewBmp;
806 vcl::Region Bitmap::CreateRegion(const Color& rColor, const tools::Rectangle& rRect) const
808 tools::Rectangle aRect(rRect);
809 BitmapScopedReadAccess pReadAcc(*this);
811 aRect.Intersection(tools::Rectangle(Point(), GetSizePixel()));
812 aRect.Normalize();
814 if (!pReadAcc)
815 return vcl::Region(aRect);
817 vcl::Region aRegion;
818 const tools::Long nLeft = aRect.Left();
819 const tools::Long nTop = aRect.Top();
820 const tools::Long nRight = aRect.Right();
821 const tools::Long nBottom = aRect.Bottom();
822 const BitmapColor aMatch(pReadAcc->GetBestMatchingColor(rColor));
824 std::vector<tools::Long> aLine;
825 tools::Long nYStart(nTop);
826 tools::Long nY(nTop);
828 for (; nY <= nBottom; nY++)
830 std::vector<tools::Long> aNewLine;
831 tools::Long nX(nLeft);
832 Scanline pScanlineRead = pReadAcc->GetScanline(nY);
834 for (; nX <= nRight;)
836 while ((nX <= nRight) && (aMatch != pReadAcc->GetPixelFromData(pScanlineRead, nX)))
837 nX++;
839 if (nX <= nRight)
841 aNewLine.push_back(nX);
843 while ((nX <= nRight) && (aMatch == pReadAcc->GetPixelFromData(pScanlineRead, nX)))
845 nX++;
848 aNewLine.push_back(nX - 1);
852 if (aNewLine != aLine)
854 // need to write aLine, it's different from the next line
855 if (!aLine.empty())
857 tools::Rectangle aSubRect;
859 // enter y values and proceed ystart
860 aSubRect.SetTop(nYStart);
861 aSubRect.SetBottom(nY ? nY - 1 : 0);
863 for (size_t a(0); a < aLine.size();)
865 aSubRect.SetLeft(aLine[a++]);
866 aSubRect.SetRight(aLine[a++]);
867 aRegion.Union(aSubRect);
871 // copy line as new line
872 aLine = std::move(aNewLine);
873 nYStart = nY;
877 // write last line if used
878 if (!aLine.empty())
880 tools::Rectangle aSubRect;
882 // enter y values
883 aSubRect.SetTop(nYStart);
884 aSubRect.SetBottom(nY ? nY - 1 : 0);
886 for (size_t a(0); a < aLine.size();)
888 aSubRect.SetLeft(aLine[a++]);
889 aSubRect.SetRight(aLine[a++]);
890 aRegion.Union(aSubRect);
894 pReadAcc.reset();
896 return aRegion;
899 bool Bitmap::ReplaceMask(const AlphaMask& rMask, const Color& rReplaceColor)
901 BitmapScopedReadAccess pMaskAcc(rMask);
902 BitmapScopedWriteAccess pAcc(*this);
904 if (!pMaskAcc || !pAcc)
905 return false;
907 const tools::Long nWidth = std::min(pMaskAcc->Width(), pAcc->Width());
908 const tools::Long nHeight = std::min(pMaskAcc->Height(), pAcc->Height());
909 const BitmapColor aMaskWhite(pMaskAcc->GetBestMatchingColor(COL_WHITE));
910 BitmapColor aReplace;
912 if (pAcc->HasPalette())
914 const sal_uInt16 nActColors = pAcc->GetPaletteEntryCount();
915 const sal_uInt16 nMaxColors = 1 << pAcc->GetBitCount();
917 aReplace = UpdatePaletteForNewColor(pAcc, nActColors, nMaxColors, nHeight, nWidth,
918 BitmapColor(rReplaceColor));
920 else
921 aReplace = rReplaceColor;
923 for (tools::Long nY = 0; nY < nHeight; nY++)
925 Scanline pScanline = pAcc->GetScanline(nY);
926 Scanline pScanlineMask = pMaskAcc->GetScanline(nY);
927 for (tools::Long nX = 0; nX < nWidth; nX++)
929 if (pMaskAcc->GetPixelFromData(pScanlineMask, nX) == aMaskWhite)
930 pAcc->SetPixelOnData(pScanline, nX, aReplace);
934 return true;
937 bool Bitmap::Replace(const AlphaMask& rAlpha, const Color& rMergeColor)
939 Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N24_BPP);
940 BitmapScopedReadAccess pAcc(*this);
941 BitmapScopedReadAccess pAlphaAcc(rAlpha);
942 BitmapScopedWriteAccess pNewAcc(aNewBmp);
944 if (!pAcc || !pAlphaAcc || !pNewAcc)
945 return false;
947 BitmapColor aCol;
948 const tools::Long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width());
949 const tools::Long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height());
951 for (tools::Long nY = 0; nY < nHeight; nY++)
953 Scanline pScanline = pNewAcc->GetScanline(nY);
954 Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY);
955 for (tools::Long nX = 0; nX < nWidth; nX++)
957 aCol = pAcc->GetColor(nY, nX);
958 aCol.Merge(rMergeColor, pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
959 pNewAcc->SetPixelOnData(pScanline, nX, aCol);
963 pAcc.reset();
964 pAlphaAcc.reset();
965 pNewAcc.reset();
967 const MapMode aMap(maPrefMapMode);
968 const Size aSize(maPrefSize);
970 *this = std::move(aNewBmp);
972 maPrefMapMode = aMap;
973 maPrefSize = aSize;
975 return true;
978 bool Bitmap::Replace(const Color& rSearchColor, const Color& rReplaceColor, sal_uInt8 nTol)
980 if (mxSalBmp)
982 // implementation specific replace
983 std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
984 if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Replace(rSearchColor, rReplaceColor, nTol))
986 ImplSetSalBitmap(xImpBmp);
987 maPrefMapMode = MapMode(MapUnit::MapPixel);
988 maPrefSize = xImpBmp->GetSize();
989 return true;
993 BitmapScopedWriteAccess pAcc(*this);
994 if (!pAcc)
995 return false;
997 const tools::Long nMinR = std::clamp<tools::Long>(rSearchColor.GetRed() - nTol, 0, 255);
998 const tools::Long nMaxR = std::clamp<tools::Long>(rSearchColor.GetRed() + nTol, 0, 255);
999 const tools::Long nMinG = std::clamp<tools::Long>(rSearchColor.GetGreen() - nTol, 0, 255);
1000 const tools::Long nMaxG = std::clamp<tools::Long>(rSearchColor.GetGreen() + nTol, 0, 255);
1001 const tools::Long nMinB = std::clamp<tools::Long>(rSearchColor.GetBlue() - nTol, 0, 255);
1002 const tools::Long nMaxB = std::clamp<tools::Long>(rSearchColor.GetBlue() + nTol, 0, 255);
1004 if (pAcc->HasPalette())
1006 for (sal_uInt16 i = 0, nPalCount = pAcc->GetPaletteEntryCount(); i < nPalCount; i++)
1008 const BitmapColor& rCol = pAcc->GetPaletteColor(i);
1010 if (nMinR <= rCol.GetRed() && nMaxR >= rCol.GetRed() && nMinG <= rCol.GetGreen()
1011 && nMaxG >= rCol.GetGreen() && nMinB <= rCol.GetBlue() && nMaxB >= rCol.GetBlue())
1013 pAcc->SetPaletteColor(i, rReplaceColor);
1017 else
1019 BitmapColor aCol;
1020 const BitmapColor aReplace(pAcc->GetBestMatchingColor(rReplaceColor));
1022 for (tools::Long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++)
1024 Scanline pScanline = pAcc->GetScanline(nY);
1025 for (tools::Long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++)
1027 aCol = pAcc->GetPixelFromData(pScanline, nX);
1029 if (nMinR <= aCol.GetRed() && nMaxR >= aCol.GetRed() && nMinG <= aCol.GetGreen()
1030 && nMaxG >= aCol.GetGreen() && nMinB <= aCol.GetBlue()
1031 && nMaxB >= aCol.GetBlue())
1033 pAcc->SetPixelOnData(pScanline, nX, aReplace);
1039 pAcc.reset();
1041 return true;
1044 bool Bitmap::Replace(const Color* pSearchColors, const Color* pReplaceColors, size_t nColorCount,
1045 sal_uInt8 const* pTols)
1047 BitmapScopedWriteAccess pAcc(*this);
1048 if (!pAcc)
1049 return false;
1051 std::vector<sal_uInt8> aMinR(nColorCount);
1052 std::vector<sal_uInt8> aMaxR(nColorCount);
1053 std::vector<sal_uInt8> aMinG(nColorCount);
1054 std::vector<sal_uInt8> aMaxG(nColorCount);
1055 std::vector<sal_uInt8> aMinB(nColorCount);
1056 std::vector<sal_uInt8> aMaxB(nColorCount);
1058 if (pTols)
1060 for (size_t i = 0; i < nColorCount; ++i)
1062 const Color& rCol = pSearchColors[i];
1063 const sal_uInt8 nTol = pTols[i];
1065 aMinR[i] = std::clamp(rCol.GetRed() - nTol, 0, 255);
1066 aMaxR[i] = std::clamp(rCol.GetRed() + nTol, 0, 255);
1067 aMinG[i] = std::clamp(rCol.GetGreen() - nTol, 0, 255);
1068 aMaxG[i] = std::clamp(rCol.GetGreen() + nTol, 0, 255);
1069 aMinB[i] = std::clamp(rCol.GetBlue() - nTol, 0, 255);
1070 aMaxB[i] = std::clamp(rCol.GetBlue() + nTol, 0, 255);
1073 else
1075 for (size_t i = 0; i < nColorCount; ++i)
1077 const Color& rCol = pSearchColors[i];
1079 aMinR[i] = rCol.GetRed();
1080 aMaxR[i] = rCol.GetRed();
1081 aMinG[i] = rCol.GetGreen();
1082 aMaxG[i] = rCol.GetGreen();
1083 aMinB[i] = rCol.GetBlue();
1084 aMaxB[i] = rCol.GetBlue();
1088 if (pAcc->HasPalette())
1090 for (sal_uInt16 nEntry = 0, nPalCount = pAcc->GetPaletteEntryCount(); nEntry < nPalCount;
1091 nEntry++)
1093 const BitmapColor& rCol = pAcc->GetPaletteColor(nEntry);
1095 for (size_t i = 0; i < nColorCount; ++i)
1097 if (aMinR[i] <= rCol.GetRed() && aMaxR[i] >= rCol.GetRed()
1098 && aMinG[i] <= rCol.GetGreen() && aMaxG[i] >= rCol.GetGreen()
1099 && aMinB[i] <= rCol.GetBlue() && aMaxB[i] >= rCol.GetBlue())
1101 pAcc->SetPaletteColor(nEntry, pReplaceColors[i]);
1102 break;
1107 else
1109 std::vector<BitmapColor> aReplaces(nColorCount);
1111 for (size_t i = 0; i < nColorCount; ++i)
1112 aReplaces[i] = pAcc->GetBestMatchingColor(pReplaceColors[i]);
1114 for (tools::Long nY = 0, nHeight = pAcc->Height(); nY < nHeight; nY++)
1116 Scanline pScanline = pAcc->GetScanline(nY);
1117 for (tools::Long nX = 0, nWidth = pAcc->Width(); nX < nWidth; nX++)
1119 BitmapColor aCol = pAcc->GetPixelFromData(pScanline, nX);
1121 for (size_t i = 0; i < nColorCount; ++i)
1123 if (aMinR[i] <= aCol.GetRed() && aMaxR[i] >= aCol.GetRed()
1124 && aMinG[i] <= aCol.GetGreen() && aMaxG[i] >= aCol.GetGreen()
1125 && aMinB[i] <= aCol.GetBlue() && aMaxB[i] >= aCol.GetBlue())
1127 pAcc->SetPixelOnData(pScanline, nX, aReplaces[i]);
1128 break;
1135 pAcc.reset();
1137 return true;
1140 // TODO: Have a look at OutputDevice::ImplDrawAlpha() for some
1141 // optimizations. Might even consolidate the code here and there.
1142 bool Bitmap::Blend(const AlphaMask& rAlpha, const Color& rBackgroundColor)
1144 // Convert to a truecolor bitmap, if we're a paletted one. There's room for tradeoff decision here,
1145 // maybe later for an overload (or a flag)
1146 if (vcl::isPalettePixelFormat(getPixelFormat()))
1147 Convert(BmpConversion::N24Bit);
1149 BitmapScopedReadAccess pAlphaAcc(rAlpha);
1151 BitmapScopedWriteAccess pAcc(*this);
1153 if (!pAlphaAcc || !pAcc)
1154 return false;
1156 const tools::Long nWidth = std::min(pAlphaAcc->Width(), pAcc->Width());
1157 const tools::Long nHeight = std::min(pAlphaAcc->Height(), pAcc->Height());
1159 for (tools::Long nY = 0; nY < nHeight; ++nY)
1161 Scanline pScanline = pAcc->GetScanline(nY);
1162 Scanline pScanlineAlpha = pAlphaAcc->GetScanline(nY);
1163 for (tools::Long nX = 0; nX < nWidth; ++nX)
1165 BitmapColor aBmpColor = pAcc->GetPixelFromData(pScanline, nX);
1166 aBmpColor.Merge(rBackgroundColor, pAlphaAcc->GetIndexFromData(pScanlineAlpha, nX));
1167 pAcc->SetPixelOnData(pScanline, nX, aBmpColor);
1171 return true;
1174 static BitmapColor UpdatePaletteForNewColor(BitmapScopedWriteAccess& pAcc,
1175 const sal_uInt16 nActColors,
1176 const sal_uInt16 nMaxColors, const tools::Long nHeight,
1177 const tools::Long nWidth,
1178 const BitmapColor& rWantedColor)
1180 // default to the nearest color
1181 sal_uInt16 aReplacePalIndex = pAcc->GetMatchingPaletteIndex(rWantedColor);
1182 if (aReplacePalIndex != SAL_MAX_UINT16)
1183 return BitmapColor(static_cast<sal_uInt8>(aReplacePalIndex));
1185 // for paletted images without a matching palette entry
1187 // if the palette has empty entries use the last one
1188 if (nActColors < nMaxColors)
1190 pAcc->SetPaletteEntryCount(nActColors + 1);
1191 pAcc->SetPaletteColor(nActColors, rWantedColor);
1192 return BitmapColor(static_cast<sal_uInt8>(nActColors));
1195 // look for an unused palette entry (NOTE: expensive!)
1196 std::unique_ptr<bool[]> pFlags(new bool[nMaxColors]);
1198 // Set all entries to false
1199 std::fill(pFlags.get(), pFlags.get() + nMaxColors, false);
1201 for (tools::Long nY = 0; nY < nHeight; nY++)
1203 Scanline pScanline = pAcc->GetScanline(nY);
1204 for (tools::Long nX = 0; nX < nWidth; nX++)
1205 pFlags[pAcc->GetIndexFromData(pScanline, nX)] = true;
1208 for (sal_uInt16 i = 0; i < nMaxColors; i++)
1210 // Hurray, we do have an unused entry
1211 if (!pFlags[i])
1213 pAcc->SetPaletteColor(i, rWantedColor);
1214 return BitmapColor(static_cast<sal_uInt8>(i));
1217 assert(false && "found nothing");
1218 return BitmapColor(0);
1221 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */