we do not need to go via this complex luminance calculation, we just want the red channel of the RGB data. This is also what the skia backends do, ever since commit 2fcfbd73768b69ba58607a054e7f851be2942992 Author: Luboš Luňák <l.lunak@collabora.com> Date: Fri Apr 3 22:50:12 2020 +0200 no gray conversion needed for VCL alpha hacks Change-Id: Ie4a7adcc7c488d241ec58e64a130da544c3d39d0 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/184944 Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk> Tested-by: Jenkins
1780 lines
58 KiB
C++
1780 lines
58 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <config_features.h>
|
|
|
|
#include <sal/log.hxx>
|
|
#include <osl/diagnose.h>
|
|
#include <tools/helpers.hxx>
|
|
|
|
#include <utility>
|
|
#include <vcl/bitmap.hxx>
|
|
#include <vcl/bitmapex.hxx>
|
|
#include <vcl/outdev.hxx>
|
|
|
|
#include <svdata.hxx>
|
|
#include <salinst.hxx>
|
|
#include <salbmp.hxx>
|
|
#if HAVE_FEATURE_SKIA
|
|
#include <vcl/skia/SkiaHelper.hxx>
|
|
#endif
|
|
#include <vcl/bitmap/BitmapMonochromeFilter.hxx>
|
|
|
|
#include <bitmap/BitmapScaleSuperFilter.hxx>
|
|
#include <bitmap/BitmapScaleConvolutionFilter.hxx>
|
|
#include <bitmap/BitmapFastScaleFilter.hxx>
|
|
#include <bitmap/BitmapInterpolateScaleFilter.hxx>
|
|
#include <vcl/BitmapWriteAccess.hxx>
|
|
#include <bitmap/impoctree.hxx>
|
|
#include <bitmap/Octree.hxx>
|
|
|
|
#include "floyd.hxx"
|
|
|
|
#include <math.h>
|
|
#include <algorithm>
|
|
#include <memory>
|
|
|
|
#ifdef DBG_UTIL
|
|
#include <cstdlib>
|
|
#include <tools/stream.hxx>
|
|
#include <vcl/graphicfilter.hxx>
|
|
#endif
|
|
|
|
Bitmap::Bitmap()
|
|
{
|
|
}
|
|
|
|
Bitmap::Bitmap(const Bitmap& rBitmap)
|
|
: mxSalBmp(rBitmap.mxSalBmp)
|
|
, maPrefMapMode(rBitmap.maPrefMapMode)
|
|
, maPrefSize(rBitmap.maPrefSize)
|
|
{
|
|
}
|
|
|
|
Bitmap::Bitmap(std::shared_ptr<SalBitmap> pSalBitmap)
|
|
: mxSalBmp(std::move(pSalBitmap))
|
|
, maPrefMapMode(MapMode(MapUnit::MapPixel))
|
|
, maPrefSize(mxSalBmp->GetSize())
|
|
{
|
|
}
|
|
|
|
Bitmap::Bitmap( const Size& rSizePixel, vcl::PixelFormat ePixelFormat, const BitmapPalette* pPal )
|
|
{
|
|
if (!(rSizePixel.Width() && rSizePixel.Height()))
|
|
return;
|
|
|
|
switch (ePixelFormat)
|
|
{
|
|
case vcl::PixelFormat::N8_BPP:
|
|
{
|
|
static const BitmapPalette aPalN8_BPP = [] {
|
|
BitmapPalette aPal(1 << sal_uInt16(vcl::PixelFormat::N8_BPP));
|
|
aPal[ 0 ] = COL_BLACK;
|
|
aPal[ 1 ] = COL_BLUE;
|
|
aPal[ 2 ] = COL_GREEN;
|
|
aPal[ 3 ] = COL_CYAN;
|
|
aPal[ 4 ] = COL_RED;
|
|
aPal[ 5 ] = COL_MAGENTA;
|
|
aPal[ 6 ] = COL_BROWN;
|
|
aPal[ 7 ] = COL_GRAY;
|
|
aPal[ 8 ] = COL_LIGHTGRAY;
|
|
aPal[ 9 ] = COL_LIGHTBLUE;
|
|
aPal[ 10 ] = COL_LIGHTGREEN;
|
|
aPal[ 11 ] = COL_LIGHTCYAN;
|
|
aPal[ 12 ] = COL_LIGHTRED;
|
|
aPal[ 13 ] = COL_LIGHTMAGENTA;
|
|
aPal[ 14 ] = COL_YELLOW;
|
|
aPal[ 15 ] = COL_WHITE;
|
|
|
|
// Create dither palette
|
|
sal_uInt16 nActCol = 16;
|
|
|
|
for( sal_uInt16 nB = 0; nB < 256; nB += 51 )
|
|
for( sal_uInt16 nG = 0; nG < 256; nG += 51 )
|
|
for( sal_uInt16 nR = 0; nR < 256; nR += 51 )
|
|
aPal[ nActCol++ ] = BitmapColor( static_cast<sal_uInt8>(nR), static_cast<sal_uInt8>(nG), static_cast<sal_uInt8>(nB) );
|
|
|
|
// Set standard Office colors
|
|
aPal[ nActCol++ ] = BitmapColor( 0, 184, 255 );
|
|
return aPal;
|
|
}();
|
|
if (!pPal)
|
|
pPal = &aPalN8_BPP;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
static const BitmapPalette aPalEmpty;
|
|
if (!pPal || !vcl::isPalettePixelFormat(ePixelFormat))
|
|
pPal = &aPalEmpty;
|
|
break;
|
|
}
|
|
}
|
|
|
|
mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
|
|
mxSalBmp->Create(rSizePixel, ePixelFormat, *pPal);
|
|
}
|
|
|
|
#ifdef DBG_UTIL
|
|
|
|
namespace
|
|
{
|
|
void savePNG(const OUString& sWhere, const Bitmap& rBmp)
|
|
{
|
|
SvFileStream aStream(sWhere, StreamMode::WRITE | StreamMode::TRUNC);
|
|
GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
|
|
rFilter.compressAsPNG(BitmapEx(rBmp), aStream);
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
Bitmap::~Bitmap()
|
|
{
|
|
#ifdef DBG_UTIL
|
|
// VCL_DUMP_BMP_PATH should be like C:/path/ or ~/path/
|
|
static const OUString sDumpPath(OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
|
|
// Stepping into the dtor of a bitmap you need, and setting the volatile variable to true in
|
|
// debugger, would dump the bitmap in question
|
|
static volatile bool save(false);
|
|
if (!sDumpPath.isEmpty() && save)
|
|
{
|
|
save = false;
|
|
savePNG(sDumpPath + "BitmapDump.png", *this);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
namespace
|
|
{
|
|
template <size_t N>
|
|
constexpr std::enable_if_t<255 % (N - 1) == 0, std::array<BitmapColor, N>> getGreyscalePalette()
|
|
{
|
|
const int step = 255 / (N - 1);
|
|
std::array<BitmapColor, N> a;
|
|
for (size_t i = 0; i < N; ++i)
|
|
a[i] = BitmapColor(i * step, i * step, i * step);
|
|
return a;
|
|
}
|
|
}
|
|
|
|
const BitmapPalette& Bitmap::GetGreyPalette( int nEntries )
|
|
{
|
|
// Create greyscale palette with 2, 4, 16 or 256 entries
|
|
switch (nEntries)
|
|
{
|
|
case 2:
|
|
{
|
|
static const BitmapPalette aGreyPalette2 = getGreyscalePalette<2>();
|
|
return aGreyPalette2;
|
|
}
|
|
case 4:
|
|
{
|
|
static const BitmapPalette aGreyPalette4 = getGreyscalePalette<4>();
|
|
return aGreyPalette4;
|
|
}
|
|
case 16:
|
|
{
|
|
static const BitmapPalette aGreyPalette16 = getGreyscalePalette<16>();
|
|
return aGreyPalette16;
|
|
}
|
|
case 256:
|
|
{
|
|
static const BitmapPalette aGreyPalette256 = getGreyscalePalette<256>();
|
|
return aGreyPalette256;
|
|
}
|
|
}
|
|
OSL_FAIL("Bitmap::GetGreyPalette: invalid entry count (2/4/16/256 allowed)");
|
|
return GetGreyPalette(2);
|
|
}
|
|
|
|
Bitmap& Bitmap::operator=( const Bitmap& rBitmap )
|
|
{
|
|
if (this == &rBitmap)
|
|
return *this;
|
|
|
|
maPrefSize = rBitmap.maPrefSize;
|
|
maPrefMapMode = rBitmap.maPrefMapMode;
|
|
mxSalBmp = rBitmap.mxSalBmp;
|
|
|
|
return *this;
|
|
}
|
|
|
|
Bitmap& Bitmap::operator=( Bitmap&& rBitmap ) noexcept
|
|
{
|
|
maPrefSize = std::move(rBitmap.maPrefSize);
|
|
maPrefMapMode = std::move(rBitmap.maPrefMapMode);
|
|
mxSalBmp = std::move(rBitmap.mxSalBmp);
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool Bitmap::operator==( const Bitmap& rBmp ) const
|
|
{
|
|
if (rBmp.mxSalBmp == mxSalBmp) // Includes both are nullptr
|
|
return true;
|
|
if (!rBmp.mxSalBmp || !mxSalBmp)
|
|
return false;
|
|
if (rBmp.mxSalBmp->GetSize() != mxSalBmp->GetSize() ||
|
|
rBmp.mxSalBmp->GetBitCount() != mxSalBmp->GetBitCount())
|
|
return false;
|
|
BitmapChecksum aChecksum1 = rBmp.mxSalBmp->GetChecksum();
|
|
BitmapChecksum aChecksum2 = mxSalBmp->GetChecksum();
|
|
// If the bitmaps can't calculate a checksum, best to regard them as different.
|
|
if (aChecksum1 == 0 || aChecksum2 == 0)
|
|
return false;
|
|
return aChecksum1 == aChecksum2;
|
|
}
|
|
|
|
void Bitmap::SetEmpty()
|
|
{
|
|
maPrefMapMode = MapMode();
|
|
maPrefSize = Size();
|
|
mxSalBmp.reset();
|
|
}
|
|
|
|
Size Bitmap::GetSizePixel() const
|
|
{
|
|
return( mxSalBmp ? mxSalBmp->GetSize() : Size() );
|
|
}
|
|
|
|
vcl::PixelFormat Bitmap::getPixelFormat() const
|
|
{
|
|
if (!mxSalBmp)
|
|
return vcl::PixelFormat::INVALID;
|
|
|
|
sal_uInt16 nBitCount = mxSalBmp->GetBitCount();
|
|
if (nBitCount <= 8)
|
|
return vcl::PixelFormat::N8_BPP;
|
|
if (nBitCount <= 24)
|
|
return vcl::PixelFormat::N24_BPP;
|
|
if (nBitCount <= 32)
|
|
return vcl::PixelFormat::N32_BPP;
|
|
|
|
return vcl::PixelFormat::INVALID;
|
|
}
|
|
|
|
bool Bitmap::HasGreyPaletteAny() const
|
|
{
|
|
bool bRet = false;
|
|
|
|
BitmapScopedInfoAccess pIAcc(*this);
|
|
|
|
if( pIAcc )
|
|
{
|
|
bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPaletteAny();
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
bool Bitmap::HasGreyPalette8Bit() const
|
|
{
|
|
bool bRet = false;
|
|
BitmapScopedInfoAccess pIAcc(*this);
|
|
|
|
if( pIAcc )
|
|
{
|
|
bRet = pIAcc->HasPalette() && pIAcc->GetPalette().IsGreyPalette8Bit();
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
BitmapChecksum Bitmap::GetChecksum() const
|
|
{
|
|
if( !mxSalBmp )
|
|
return 0;
|
|
|
|
BitmapChecksum nRet = mxSalBmp->GetChecksum();
|
|
if (!nRet)
|
|
{
|
|
// nRet == 0 => probably, we were not able to acquire
|
|
// the buffer in SalBitmap::updateChecksum;
|
|
// so, we need to update the imp bitmap for this bitmap instance
|
|
// as we do in BitmapInfoAccess::ImplCreate
|
|
std::shared_ptr<SalBitmap> xNewImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
|
|
if (xNewImpBmp->Create(*mxSalBmp, getPixelFormat()))
|
|
{
|
|
Bitmap* pThis = const_cast<Bitmap*>(this);
|
|
pThis->mxSalBmp = std::move(xNewImpBmp);
|
|
nRet = mxSalBmp->GetChecksum();
|
|
}
|
|
}
|
|
|
|
return nRet;
|
|
}
|
|
|
|
void Bitmap::ImplMakeUnique()
|
|
{
|
|
if (mxSalBmp && mxSalBmp.use_count() > 1)
|
|
{
|
|
std::shared_ptr<SalBitmap> xOldImpBmp = mxSalBmp;
|
|
mxSalBmp = ImplGetSVData()->mpDefInst->CreateSalBitmap();
|
|
(void)mxSalBmp->Create(*xOldImpBmp);
|
|
}
|
|
}
|
|
|
|
void Bitmap::ReassignWithSize(const Bitmap& rBitmap)
|
|
{
|
|
const Size aOldSizePix(GetSizePixel());
|
|
const Size aNewSizePix(rBitmap.GetSizePixel());
|
|
const MapMode aOldMapMode(maPrefMapMode);
|
|
Size aNewPrefSize;
|
|
|
|
if ((aOldSizePix != aNewSizePix) && aOldSizePix.Width() && aOldSizePix.Height())
|
|
{
|
|
aNewPrefSize.setWidth(maPrefSize.Width() * aNewSizePix.Width() / aOldSizePix.Width());
|
|
aNewPrefSize.setHeight(maPrefSize.Height() * aNewSizePix.Height() / aOldSizePix.Height());
|
|
}
|
|
else
|
|
{
|
|
aNewPrefSize = maPrefSize;
|
|
}
|
|
|
|
*this = rBitmap;
|
|
|
|
maPrefSize = aNewPrefSize;
|
|
maPrefMapMode = aOldMapMode;
|
|
}
|
|
|
|
void Bitmap::ImplSetSalBitmap(const std::shared_ptr<SalBitmap>& xImpBmp)
|
|
{
|
|
mxSalBmp = xImpBmp;
|
|
}
|
|
|
|
bool Bitmap::Crop( const tools::Rectangle& rRectPixel )
|
|
{
|
|
const Size aSizePix( GetSizePixel() );
|
|
tools::Rectangle aRect( rRectPixel );
|
|
|
|
aRect.Intersection( tools::Rectangle( Point(), aSizePix ) );
|
|
|
|
if( aRect.IsEmpty() || aSizePix == aRect.GetSize())
|
|
return false;
|
|
|
|
BitmapScopedReadAccess pReadAcc(*this);
|
|
if( !pReadAcc )
|
|
return false;
|
|
|
|
const tools::Rectangle aNewRect( Point(), aRect.GetSize() );
|
|
Bitmap aNewBmp(aNewRect.GetSize(), getPixelFormat(), &pReadAcc->GetPalette());
|
|
BitmapScopedWriteAccess pWriteAcc(aNewBmp);
|
|
if( !pWriteAcc )
|
|
return false;
|
|
|
|
const tools::Long nOldX = aRect.Left();
|
|
const tools::Long nOldY = aRect.Top();
|
|
const tools::Long nNewWidth = aNewRect.GetWidth();
|
|
const tools::Long nNewHeight = aNewRect.GetHeight();
|
|
|
|
for( tools::Long nY = 0, nY2 = nOldY; nY < nNewHeight; nY++, nY2++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nY2);
|
|
for( tools::Long nX = 0, nX2 = nOldX; nX < nNewWidth; nX++, nX2++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nX, pReadAcc->GetPixelFromData( pScanlineRead, nX2 ) );
|
|
}
|
|
|
|
pWriteAcc.reset();
|
|
pReadAcc.reset();
|
|
|
|
ReassignWithSize( aNewBmp );
|
|
|
|
return true;
|
|
};
|
|
|
|
bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst,
|
|
const tools::Rectangle& rRectSrc )
|
|
{
|
|
const Size aSizePix( GetSizePixel() );
|
|
tools::Rectangle aRectDst( rRectDst );
|
|
|
|
aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
|
|
|
|
if( aRectDst.IsEmpty() )
|
|
return false;
|
|
|
|
tools::Rectangle aRectSrc( rRectSrc );
|
|
|
|
aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
|
|
|
|
if( aRectSrc.IsEmpty() || ( aRectSrc == aRectDst ) )
|
|
return false;
|
|
|
|
BitmapScopedWriteAccess pWriteAcc(*this);
|
|
if( !pWriteAcc )
|
|
return false;
|
|
|
|
const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
|
|
const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
|
|
const tools::Long nSrcX = aRectSrc.Left();
|
|
const tools::Long nSrcY = aRectSrc.Top();
|
|
const tools::Long nSrcEndX1 = nSrcX + nWidth - 1;
|
|
const tools::Long nSrcEndY1 = nSrcY + nHeight - 1;
|
|
const tools::Long nDstX = aRectDst.Left();
|
|
const tools::Long nDstY = aRectDst.Top();
|
|
const tools::Long nDstEndX1 = nDstX + nWidth - 1;
|
|
const tools::Long nDstEndY1 = nDstY + nHeight - 1;
|
|
|
|
if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) )
|
|
{
|
|
for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nYN);
|
|
Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
|
|
for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
|
|
}
|
|
}
|
|
else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) )
|
|
{
|
|
for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nYN);
|
|
Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
|
|
for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
|
|
}
|
|
}
|
|
else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) )
|
|
{
|
|
for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nYN);
|
|
Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
|
|
for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
|
|
pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nYN);
|
|
Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
|
|
for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
|
|
pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Bitmap::CopyPixel( const tools::Rectangle& rRectDst,
|
|
const tools::Rectangle& rRectSrc, const Bitmap& rBmpSrc )
|
|
{
|
|
const Size aSizePix( GetSizePixel() );
|
|
tools::Rectangle aRectDst( rRectDst );
|
|
|
|
aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
|
|
|
|
if( aRectDst.IsEmpty() )
|
|
return false;
|
|
|
|
if( rBmpSrc.mxSalBmp == mxSalBmp ) // if self-copy
|
|
return CopyPixel(rRectDst, rRectSrc);
|
|
|
|
Bitmap* pSrc = &const_cast<Bitmap&>(rBmpSrc);
|
|
const Size aCopySizePix( pSrc->GetSizePixel() );
|
|
tools::Rectangle aRectSrc( rRectSrc );
|
|
const sal_uInt16 nSrcBitCount = vcl::pixelFormatBitCount(rBmpSrc.getPixelFormat());
|
|
const sal_uInt16 nDstBitCount = vcl::pixelFormatBitCount(getPixelFormat());
|
|
|
|
if( nSrcBitCount > nDstBitCount )
|
|
{
|
|
int nNextIndex = 0;
|
|
|
|
if (nSrcBitCount == 24)
|
|
Convert( BmpConversion::N24Bit );
|
|
else if (nSrcBitCount == 8)
|
|
{
|
|
Convert( BmpConversion::N8BitColors );
|
|
nNextIndex = 16;
|
|
}
|
|
else if (nSrcBitCount == 4)
|
|
{
|
|
assert(false);
|
|
}
|
|
|
|
if( nNextIndex )
|
|
{
|
|
BitmapScopedReadAccess pSrcAcc(*pSrc);
|
|
BitmapScopedWriteAccess pDstAcc(*this);
|
|
|
|
if( pSrcAcc && pDstAcc )
|
|
{
|
|
const int nSrcCount = pSrcAcc->GetPaletteEntryCount();
|
|
const int nDstCount = 1 << nDstBitCount;
|
|
|
|
for (int i = 0; ( i < nSrcCount ) && ( nNextIndex < nDstCount ); ++i)
|
|
{
|
|
const BitmapColor& rSrcCol = pSrcAcc->GetPaletteColor( static_cast<sal_uInt16>(i) );
|
|
|
|
bool bFound = false;
|
|
|
|
for (int j = 0; j < nDstCount; ++j)
|
|
{
|
|
if( rSrcCol == pDstAcc->GetPaletteColor( static_cast<sal_uInt16>(j) ) )
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !bFound )
|
|
pDstAcc->SetPaletteColor( static_cast<sal_uInt16>(nNextIndex++), rSrcCol );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) );
|
|
|
|
if( aRectSrc.IsEmpty() )
|
|
return false;
|
|
|
|
BitmapScopedReadAccess pReadAcc(*pSrc);
|
|
if( !pReadAcc )
|
|
return false;
|
|
|
|
BitmapScopedWriteAccess pWriteAcc(*this);
|
|
if( !pWriteAcc )
|
|
return false;
|
|
|
|
const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
|
|
const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
|
|
const tools::Long nSrcEndX = aRectSrc.Left() + nWidth;
|
|
const tools::Long nSrcEndY = aRectSrc.Top() + nHeight;
|
|
tools::Long nDstY = aRectDst.Top();
|
|
|
|
if( pReadAcc->HasPalette() && pWriteAcc->HasPalette() )
|
|
{
|
|
const sal_uInt16 nCount = pReadAcc->GetPaletteEntryCount();
|
|
std::unique_ptr<sal_uInt8[]> pMap(new sal_uInt8[ nCount ]);
|
|
|
|
// Create index map for the color table, as the bitmap should be copied
|
|
// retaining it's color information relatively well
|
|
for( sal_uInt16 i = 0; i < nCount; i++ )
|
|
pMap[ i ] = static_cast<sal_uInt8>(pWriteAcc->GetBestPaletteIndex( pReadAcc->GetPaletteColor( i ) ));
|
|
|
|
for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nDstY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
|
|
for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nDstX, BitmapColor( pMap[ pReadAcc->GetIndexFromData( pScanlineRead, nSrcX ) ] ));
|
|
}
|
|
}
|
|
else if( pReadAcc->HasPalette() )
|
|
{
|
|
for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nDstY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
|
|
for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPaletteColor( pReadAcc->GetIndexFromData( pScanlineRead, nSrcX ) ) );
|
|
}
|
|
}
|
|
else
|
|
for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nDstY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
|
|
for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) );
|
|
}
|
|
|
|
bool bRet = ( nWidth > 0 ) && ( nHeight > 0 );
|
|
|
|
return bRet;
|
|
}
|
|
|
|
bool Bitmap::CopyPixel_AlphaOptimized( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc )
|
|
{
|
|
assert(HasGreyPalette8Bit());
|
|
// Note: this code is copied from Bitmap::CopyPixel but avoids any palette lookups
|
|
// This optimization is possible because the palettes of AlphaMasks are always identical (8bit GreyPalette, see ctor)
|
|
const Size aSizePix( GetSizePixel() );
|
|
tools::Rectangle aRectDst( rRectDst );
|
|
|
|
aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
|
|
|
|
if( aRectDst.IsEmpty() )
|
|
return false;
|
|
|
|
tools::Rectangle aRectSrc( rRectSrc );
|
|
aRectSrc.Intersection( tools::Rectangle( Point(), aSizePix ) );
|
|
if( aRectSrc.IsEmpty() || ( aRectSrc == aRectDst ) )
|
|
return false;
|
|
|
|
BitmapScopedWriteAccess pWriteAcc(*this);
|
|
if( !pWriteAcc )
|
|
return false;
|
|
|
|
const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
|
|
const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
|
|
const tools::Long nSrcX = aRectSrc.Left();
|
|
const tools::Long nSrcY = aRectSrc.Top();
|
|
const tools::Long nSrcEndX1 = nSrcX + nWidth - 1;
|
|
const tools::Long nSrcEndY1 = nSrcY + nHeight - 1;
|
|
const tools::Long nDstX = aRectDst.Left();
|
|
const tools::Long nDstY = aRectDst.Top();
|
|
const tools::Long nDstEndX1 = nDstX + nWidth - 1;
|
|
const tools::Long nDstEndY1 = nDstY + nHeight - 1;
|
|
|
|
if( ( nDstX <= nSrcX ) && ( nDstY <= nSrcY ) )
|
|
{
|
|
for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nYN);
|
|
Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
|
|
for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
|
|
}
|
|
}
|
|
else if( ( nDstX <= nSrcX ) && ( nDstY >= nSrcY ) )
|
|
{
|
|
for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nYN);
|
|
Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
|
|
for( tools::Long nX = nSrcX, nXN = nDstX; nX <= nSrcEndX1; nX++, nXN++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
|
|
}
|
|
}
|
|
else if( ( nDstX >= nSrcX ) && ( nDstY <= nSrcY ) )
|
|
{
|
|
for( tools::Long nY = nSrcY, nYN = nDstY; nY <= nSrcEndY1; nY++, nYN++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nYN);
|
|
Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
|
|
for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
|
|
pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( tools::Long nY = nSrcEndY1, nYN = nDstEndY1; nY >= nSrcY; nY--, nYN-- )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nYN);
|
|
Scanline pScanlineSrc = pWriteAcc->GetScanline(nY);
|
|
for( tools::Long nX = nSrcEndX1, nXN = nDstEndX1; nX >= nSrcX; nX--, nXN-- )
|
|
pWriteAcc->SetPixelOnData( pScanline, nXN, pWriteAcc->GetPixelFromData( pScanlineSrc, nX ) );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Bitmap::CopyPixel_AlphaOptimized( const tools::Rectangle& rRectDst, const tools::Rectangle& rRectSrc,
|
|
const AlphaMask& rBmpSrc )
|
|
{
|
|
assert(HasGreyPalette8Bit());
|
|
assert(rBmpSrc.GetBitmap().HasGreyPalette8Bit());
|
|
// Note: this code is copied from Bitmap::CopyPixel but avoids any palette lookups
|
|
// This optimization is possible because the palettes of AlphaMasks are always identical (8bit GreyPalette, see ctor)
|
|
const Size aSizePix( GetSizePixel() );
|
|
tools::Rectangle aRectDst( rRectDst );
|
|
|
|
aRectDst.Intersection( tools::Rectangle( Point(), aSizePix ) );
|
|
|
|
if( aRectDst.IsEmpty() )
|
|
return false;
|
|
|
|
if( rBmpSrc.GetBitmap().mxSalBmp == mxSalBmp ) // self-copy
|
|
return CopyPixel_AlphaOptimized(rRectDst, rRectSrc);
|
|
|
|
Bitmap* pSrc = &const_cast<Bitmap&>(rBmpSrc.GetBitmap());
|
|
const Size aCopySizePix( pSrc->GetSizePixel() );
|
|
tools::Rectangle aRectSrc( rRectSrc );
|
|
|
|
aRectSrc.Intersection( tools::Rectangle( Point(), aCopySizePix ) );
|
|
if( aRectSrc.IsEmpty() )
|
|
return false;
|
|
|
|
BitmapScopedReadAccess pReadAcc(*pSrc);
|
|
if( !pReadAcc )
|
|
return false;
|
|
|
|
BitmapScopedWriteAccess pWriteAcc(*this);
|
|
if( !pWriteAcc )
|
|
return false;
|
|
|
|
const tools::Long nWidth = std::min( aRectSrc.GetWidth(), aRectDst.GetWidth() );
|
|
const tools::Long nHeight = std::min( aRectSrc.GetHeight(), aRectDst.GetHeight() );
|
|
const tools::Long nSrcEndX = aRectSrc.Left() + nWidth;
|
|
const tools::Long nSrcEndY = aRectSrc.Top() + nHeight;
|
|
tools::Long nDstY = aRectDst.Top();
|
|
|
|
for( tools::Long nSrcY = aRectSrc.Top(); nSrcY < nSrcEndY; nSrcY++, nDstY++)
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nDstY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nSrcY);
|
|
for( tools::Long nSrcX = aRectSrc.Left(), nDstX = aRectDst.Left(); nSrcX < nSrcEndX; nSrcX++, nDstX++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nDstX, pReadAcc->GetPixelFromData( pScanlineRead, nSrcX ) );
|
|
}
|
|
|
|
bool bRet = ( nWidth > 0 ) && ( nHeight > 0 );
|
|
|
|
return bRet;
|
|
}
|
|
|
|
bool Bitmap::Expand( sal_Int32 nDX, sal_Int32 nDY, const Color* pInitColor )
|
|
{
|
|
if( !nDX && !nDY )
|
|
return false;
|
|
|
|
const Size aSizePixel( GetSizePixel() );
|
|
const tools::Long nWidth = aSizePixel.Width();
|
|
const tools::Long nHeight = aSizePixel.Height();
|
|
const Size aNewSize( nWidth + nDX, nHeight + nDY );
|
|
BitmapScopedReadAccess pReadAcc(*this);
|
|
if( !pReadAcc )
|
|
return false;
|
|
|
|
BitmapPalette aBmpPal( pReadAcc->GetPalette() );
|
|
Bitmap aNewBmp(aNewSize, getPixelFormat(), &aBmpPal);
|
|
BitmapScopedWriteAccess pWriteAcc(aNewBmp);
|
|
if( !pWriteAcc )
|
|
return false;
|
|
|
|
BitmapColor aColor;
|
|
const tools::Long nNewX = nWidth;
|
|
const tools::Long nNewY = nHeight;
|
|
const tools::Long nNewWidth = pWriteAcc->Width();
|
|
const tools::Long nNewHeight = pWriteAcc->Height();
|
|
tools::Long nX;
|
|
tools::Long nY;
|
|
|
|
if( pInitColor )
|
|
aColor = pWriteAcc->GetBestMatchingColor( *pInitColor );
|
|
|
|
for( nY = 0; nY < nHeight; nY++ )
|
|
{
|
|
pWriteAcc->CopyScanline( nY, *pReadAcc );
|
|
|
|
if( pInitColor && nDX )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
for( nX = nNewX; nX < nNewWidth; nX++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
|
|
}
|
|
}
|
|
|
|
if( pInitColor && nDY )
|
|
for( nY = nNewY; nY < nNewHeight; nY++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
for( nX = 0; nX < nNewWidth; nX++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nX, aColor );
|
|
}
|
|
|
|
pWriteAcc.reset();
|
|
pReadAcc.reset();
|
|
|
|
ReassignWithSize(aNewBmp);
|
|
|
|
return true;
|
|
}
|
|
|
|
Bitmap Bitmap::CreateDisplayBitmap( OutputDevice* pDisplay ) const
|
|
{
|
|
Bitmap aDispBmp( *this );
|
|
|
|
SalGraphics* pDispGraphics = pDisplay->GetGraphics();
|
|
|
|
if( mxSalBmp && pDispGraphics )
|
|
{
|
|
std::shared_ptr<SalBitmap> xImpDispBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
|
|
if (xImpDispBmp->Create(*mxSalBmp, pDispGraphics))
|
|
aDispBmp.ImplSetSalBitmap(xImpDispBmp);
|
|
}
|
|
|
|
return aDispBmp;
|
|
}
|
|
|
|
bool Bitmap::GetSystemData( BitmapSystemData& rData ) const
|
|
{
|
|
return mxSalBmp && mxSalBmp->GetSystemData(rData);
|
|
}
|
|
|
|
|
|
bool Bitmap::Convert( BmpConversion eConversion )
|
|
{
|
|
// try to convert in backend
|
|
if (mxSalBmp)
|
|
{
|
|
// avoid large chunk of obsolete and hopefully rarely used conversions.
|
|
if (eConversion == BmpConversion::N8BitNoConversion)
|
|
{
|
|
if (mxSalBmp->GetBitCount() == 8 && HasGreyPalette8Bit())
|
|
return true;
|
|
std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
|
|
// frequently used conversion for creating alpha masks
|
|
if (xImpBmp->Create(*mxSalBmp) && xImpBmp->InterpretAs8Bit())
|
|
{
|
|
ImplSetSalBitmap(xImpBmp);
|
|
SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
|
|
return true;
|
|
}
|
|
}
|
|
if (eConversion == BmpConversion::N8BitGreys)
|
|
{
|
|
std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
|
|
if (xImpBmp->Create(*mxSalBmp) && xImpBmp->ConvertToGreyscale())
|
|
{
|
|
ImplSetSalBitmap(xImpBmp);
|
|
SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
const sal_uInt16 nBitCount = vcl::pixelFormatBitCount(getPixelFormat());
|
|
bool bRet = false;
|
|
|
|
switch( eConversion )
|
|
{
|
|
case BmpConversion::N1BitThreshold:
|
|
{
|
|
BitmapEx aBmpEx(*this);
|
|
bRet = BitmapFilter::Filter(aBmpEx, BitmapMonochromeFilter(128));
|
|
*this = aBmpEx.GetBitmap();
|
|
}
|
|
break;
|
|
|
|
case BmpConversion::N8BitGreys:
|
|
bRet = ImplMakeGreyscales();
|
|
break;
|
|
|
|
case BmpConversion::N8BitNoConversion:
|
|
bRet = ImplMake8BitNoConversion();
|
|
break;
|
|
|
|
case BmpConversion::N8BitColors:
|
|
{
|
|
if( nBitCount < 8 )
|
|
bRet = ImplConvertUp(vcl::PixelFormat::N8_BPP);
|
|
else if( nBitCount > 8 )
|
|
bRet = ImplConvertDown8BPP();
|
|
else
|
|
bRet = true;
|
|
}
|
|
break;
|
|
|
|
case BmpConversion::N8BitTrans:
|
|
{
|
|
Color aTrans( BMP_COL_TRANS );
|
|
|
|
if( nBitCount < 8 )
|
|
bRet = ImplConvertUp(vcl::PixelFormat::N8_BPP, &aTrans );
|
|
else
|
|
bRet = ImplConvertDown8BPP(&aTrans );
|
|
}
|
|
break;
|
|
|
|
case BmpConversion::N24Bit:
|
|
{
|
|
if( nBitCount < 24 )
|
|
bRet = ImplConvertUp(vcl::PixelFormat::N24_BPP);
|
|
else
|
|
bRet = true;
|
|
}
|
|
break;
|
|
|
|
case BmpConversion::N32Bit:
|
|
{
|
|
if( nBitCount < 32 )
|
|
bRet = ImplConvertUp(vcl::PixelFormat::N32_BPP);
|
|
else
|
|
bRet = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
OSL_FAIL( "Bitmap::Convert(): Unsupported conversion" );
|
|
break;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
bool Bitmap::ImplMakeGreyscales()
|
|
{
|
|
BitmapScopedReadAccess pReadAcc(*this);
|
|
if( !pReadAcc )
|
|
return false;
|
|
|
|
const BitmapPalette& rPal = GetGreyPalette(256);
|
|
sal_uLong nShift = 0;
|
|
bool bPalDiffers = !pReadAcc->HasPalette() || ( rPal.GetEntryCount() != pReadAcc->GetPaletteEntryCount() );
|
|
|
|
if( !bPalDiffers )
|
|
bPalDiffers = ( rPal != pReadAcc->GetPalette() );
|
|
if( !bPalDiffers )
|
|
return true;
|
|
|
|
const auto ePixelFormat = vcl::PixelFormat::N8_BPP;
|
|
Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &rPal );
|
|
BitmapScopedWriteAccess pWriteAcc(aNewBmp);
|
|
if( !pWriteAcc )
|
|
return false;
|
|
|
|
const tools::Long nWidth = pWriteAcc->Width();
|
|
const tools::Long nHeight = pWriteAcc->Height();
|
|
|
|
if( pReadAcc->HasPalette() )
|
|
{
|
|
for( tools::Long nY = 0; nY < nHeight; nY++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nY);
|
|
for( tools::Long nX = 0; nX < nWidth; nX++ )
|
|
{
|
|
const sal_uInt8 cIndex = pReadAcc->GetIndexFromData( pScanlineRead, nX );
|
|
pWriteAcc->SetPixelOnData( pScanline, nX,
|
|
BitmapColor(pReadAcc->GetPaletteColor( cIndex ).GetLuminance() >> nShift) );
|
|
}
|
|
}
|
|
}
|
|
else if( pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr &&
|
|
pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
|
|
{
|
|
nShift += 8;
|
|
|
|
for( tools::Long nY = 0; nY < nHeight; nY++ )
|
|
{
|
|
Scanline pReadScan = pReadAcc->GetScanline( nY );
|
|
Scanline pWriteScan = pWriteAcc->GetScanline( nY );
|
|
|
|
for( tools::Long nX = 0; nX < nWidth; nX++ )
|
|
{
|
|
const sal_uLong nB = *pReadScan++;
|
|
const sal_uLong nG = *pReadScan++;
|
|
const sal_uLong nR = *pReadScan++;
|
|
|
|
*pWriteScan++ = static_cast<sal_uInt8>( ( nB * 28UL + nG * 151UL + nR * 77UL ) >> nShift );
|
|
}
|
|
}
|
|
}
|
|
else if( pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb &&
|
|
pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal )
|
|
{
|
|
nShift += 8;
|
|
|
|
for( tools::Long nY = 0; nY < nHeight; nY++ )
|
|
{
|
|
Scanline pReadScan = pReadAcc->GetScanline( nY );
|
|
Scanline pWriteScan = pWriteAcc->GetScanline( nY );
|
|
|
|
for( tools::Long nX = 0; nX < nWidth; nX++ )
|
|
{
|
|
const sal_uLong nR = *pReadScan++;
|
|
const sal_uLong nG = *pReadScan++;
|
|
const sal_uLong nB = *pReadScan++;
|
|
|
|
*pWriteScan++ = static_cast<sal_uInt8>( ( nB * 28UL + nG * 151UL + nR * 77UL ) >> nShift );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( tools::Long nY = 0; nY < nHeight; nY++ )
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nY);
|
|
for( tools::Long nX = 0; nX < nWidth; nX++ )
|
|
pWriteAcc->SetPixelOnData( pScanline, nX, BitmapColor(pReadAcc->GetPixelFromData( pScanlineRead, nX ).GetLuminance() >> nShift) );
|
|
}
|
|
}
|
|
|
|
pWriteAcc.reset();
|
|
pReadAcc.reset();
|
|
|
|
const MapMode aMap( maPrefMapMode );
|
|
const Size aSize( maPrefSize );
|
|
|
|
*this = std::move(aNewBmp);
|
|
|
|
maPrefMapMode = aMap;
|
|
maPrefSize = aSize;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Used for the bitmap->alpha layer conversion, just takes the red channel
|
|
bool Bitmap::ImplMake8BitNoConversion()
|
|
{
|
|
BitmapScopedReadAccess pReadAcc(*this);
|
|
if (!pReadAcc)
|
|
return false;
|
|
|
|
const BitmapPalette& rPal = GetGreyPalette(256);
|
|
bool bPalDiffers
|
|
= !pReadAcc->HasPalette() || (rPal.GetEntryCount() != pReadAcc->GetPaletteEntryCount());
|
|
|
|
if (!bPalDiffers)
|
|
bPalDiffers = (rPal != pReadAcc->GetPalette());
|
|
if (!bPalDiffers)
|
|
return true;
|
|
|
|
const auto ePixelFormat = vcl::PixelFormat::N8_BPP;
|
|
Bitmap aNewBmp(GetSizePixel(), ePixelFormat, &rPal);
|
|
BitmapScopedWriteAccess pWriteAcc(aNewBmp);
|
|
if (!pWriteAcc)
|
|
return false;
|
|
|
|
const tools::Long nWidth = pWriteAcc->Width();
|
|
const tools::Long nHeight = pWriteAcc->Height();
|
|
|
|
if (pReadAcc->HasPalette())
|
|
{
|
|
for (tools::Long nY = 0; nY < nHeight; nY++)
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nY);
|
|
for (tools::Long nX = 0; nX < nWidth; nX++)
|
|
{
|
|
const sal_uInt8 cIndex = pReadAcc->GetIndexFromData(pScanlineRead, nX);
|
|
pWriteAcc->SetPixelOnData(
|
|
pScanline, nX,
|
|
BitmapColor(pReadAcc->GetPaletteColor(cIndex).GetRed()));
|
|
}
|
|
}
|
|
}
|
|
else if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr
|
|
&& pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal)
|
|
{
|
|
for (tools::Long nY = 0; nY < nHeight; nY++)
|
|
{
|
|
Scanline pReadScan = pReadAcc->GetScanline(nY);
|
|
Scanline pWriteScan = pWriteAcc->GetScanline(nY);
|
|
|
|
for (tools::Long nX = 0; nX < nWidth; nX++)
|
|
{
|
|
pReadScan++;
|
|
pReadScan++;
|
|
const sal_uLong nR = *pReadScan++;
|
|
|
|
*pWriteScan++ = static_cast<sal_uInt8>(nR);
|
|
}
|
|
}
|
|
}
|
|
else if (pReadAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb
|
|
&& pWriteAcc->GetScanlineFormat() == ScanlineFormat::N8BitPal)
|
|
{
|
|
for (tools::Long nY = 0; nY < nHeight; nY++)
|
|
{
|
|
Scanline pReadScan = pReadAcc->GetScanline(nY);
|
|
Scanline pWriteScan = pWriteAcc->GetScanline(nY);
|
|
|
|
for (tools::Long nX = 0; nX < nWidth; nX++)
|
|
{
|
|
const sal_uLong nR = *pReadScan++;
|
|
pReadScan++;
|
|
pReadScan++;
|
|
|
|
*pWriteScan++ = static_cast<sal_uInt8>(nR);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (tools::Long nY = 0; nY < nHeight; nY++)
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nY);
|
|
for (tools::Long nX = 0; nX < nWidth; nX++)
|
|
pWriteAcc->SetPixelOnData(
|
|
pScanline, nX,
|
|
BitmapColor(pReadAcc->GetPixelFromData(pScanlineRead, nX).GetRed()));
|
|
}
|
|
}
|
|
|
|
pWriteAcc.reset();
|
|
pReadAcc.reset();
|
|
|
|
const MapMode aMap(maPrefMapMode);
|
|
const Size aSize(maPrefSize);
|
|
|
|
*this = std::move(aNewBmp);
|
|
|
|
maPrefMapMode = aMap;
|
|
maPrefSize = aSize;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Bitmap::ImplConvertUp(vcl::PixelFormat ePixelFormat, Color const* pExtColor)
|
|
{
|
|
SAL_WARN_IF(ePixelFormat <= getPixelFormat(), "vcl", "New pixel format must be greater!" );
|
|
|
|
BitmapScopedReadAccess pReadAcc(*this);
|
|
if (!pReadAcc)
|
|
return false;
|
|
|
|
BitmapPalette aPalette;
|
|
Bitmap aNewBmp(GetSizePixel(), ePixelFormat, pReadAcc->HasPalette() ? &pReadAcc->GetPalette() : &aPalette);
|
|
BitmapScopedWriteAccess pWriteAcc(aNewBmp);
|
|
if (!pWriteAcc)
|
|
return false;
|
|
|
|
const tools::Long nWidth = pWriteAcc->Width();
|
|
const tools::Long nHeight = pWriteAcc->Height();
|
|
|
|
if (pWriteAcc->HasPalette())
|
|
{
|
|
const BitmapPalette& rOldPalette = pReadAcc->GetPalette();
|
|
const sal_uInt16 nOldCount = rOldPalette.GetEntryCount();
|
|
assert(nOldCount <= (1 << vcl::pixelFormatBitCount(getPixelFormat())));
|
|
|
|
aPalette.SetEntryCount(1 << vcl::pixelFormatBitCount(ePixelFormat));
|
|
|
|
for (sal_uInt16 i = 0; i < nOldCount; i++)
|
|
aPalette[i] = rOldPalette[i];
|
|
|
|
if (pExtColor)
|
|
aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
|
|
|
|
pWriteAcc->SetPalette(aPalette);
|
|
|
|
for (tools::Long nY = 0; nY < nHeight; nY++)
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nY);
|
|
for (tools::Long nX = 0; nX < nWidth; nX++)
|
|
{
|
|
pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pReadAcc->HasPalette())
|
|
{
|
|
for (tools::Long nY = 0; nY < nHeight; nY++)
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nY);
|
|
for (tools::Long nX = 0; nX < nWidth; nX++)
|
|
{
|
|
pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX)));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (tools::Long nY = 0; nY < nHeight; nY++)
|
|
{
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nY);
|
|
for (tools::Long nX = 0; nX < nWidth; nX++)
|
|
{
|
|
pWriteAcc->SetPixelOnData(pScanline, nX, pReadAcc->GetPixelFromData(pScanlineRead, nX));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const MapMode aMap(maPrefMapMode);
|
|
const Size aSize(maPrefSize);
|
|
|
|
*this = std::move(aNewBmp);
|
|
|
|
maPrefMapMode = aMap;
|
|
maPrefSize = aSize;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Bitmap::ImplConvertDown8BPP(Color const * pExtColor)
|
|
{
|
|
SAL_WARN_IF(vcl::PixelFormat::N8_BPP > getPixelFormat(), "vcl", "New pixelformat must be lower ( or equal when pExtColor is set )!");
|
|
|
|
BitmapScopedReadAccess pReadAcc(*this);
|
|
if (!pReadAcc)
|
|
return false;
|
|
|
|
BitmapPalette aPalette;
|
|
Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N8_BPP, &aPalette);
|
|
BitmapScopedWriteAccess pWriteAcc(aNewBmp);
|
|
if (!pWriteAcc)
|
|
return false;
|
|
|
|
sal_Int16 nNewBitCount = sal_Int16(vcl::PixelFormat::N8_BPP);
|
|
const sal_uInt16 nCount = 1 << nNewBitCount;
|
|
const tools::Long nWidth = pWriteAcc->Width();
|
|
const tools::Long nWidth1 = nWidth - 1;
|
|
const tools::Long nHeight = pWriteAcc->Height();
|
|
Octree aOctree(*pReadAcc, pExtColor ? (nCount - 1) : nCount);
|
|
aPalette = aOctree.GetPalette();
|
|
InverseColorMap aColorMap(aPalette);
|
|
BitmapColor aColor;
|
|
ImpErrorQuad aErrQuad;
|
|
std::vector<ImpErrorQuad> aErrQuad1(nWidth);
|
|
std::vector<ImpErrorQuad> aErrQuad2(nWidth);
|
|
ImpErrorQuad* pQLine1 = aErrQuad1.data();
|
|
ImpErrorQuad* pQLine2 = nullptr;
|
|
tools::Long nYTmp = 0;
|
|
sal_uInt8 cIndex;
|
|
bool bQ1 = true;
|
|
|
|
if (pExtColor)
|
|
{
|
|
aPalette.SetEntryCount(aPalette.GetEntryCount() + 1);
|
|
aPalette[aPalette.GetEntryCount() - 1] = *pExtColor;
|
|
}
|
|
|
|
// set Black/White always, if we have enough space
|
|
if (aPalette.GetEntryCount() < (nCount - 1))
|
|
{
|
|
aPalette.SetEntryCount(aPalette.GetEntryCount() + 2);
|
|
aPalette[aPalette.GetEntryCount() - 2] = COL_BLACK;
|
|
aPalette[aPalette.GetEntryCount() - 1] = COL_WHITE;
|
|
}
|
|
|
|
pWriteAcc->SetPalette(aPalette);
|
|
|
|
for (tools::Long nY = 0; nY < std::min(nHeight, tools::Long(2)); nY++, nYTmp++)
|
|
{
|
|
pQLine2 = !nY ? aErrQuad1.data() : aErrQuad2.data();
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
|
|
for (tools::Long nX = 0; nX < nWidth; nX++)
|
|
{
|
|
if (pReadAcc->HasPalette())
|
|
pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
|
|
else
|
|
pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
|
|
}
|
|
}
|
|
|
|
assert(pQLine2 || nHeight == 0);
|
|
|
|
for (tools::Long nY = 0; nY < nHeight; nY++, nYTmp++)
|
|
{
|
|
// first pixel in the line
|
|
cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(pQLine1[0].ImplGetColor()));
|
|
Scanline pScanline = pWriteAcc->GetScanline(nY);
|
|
pWriteAcc->SetPixelOnData(pScanline, 0, BitmapColor(cIndex));
|
|
|
|
tools::Long nX;
|
|
for (nX = 1; nX < nWidth1; nX++)
|
|
{
|
|
aColor = pQLine1[nX].ImplGetColor();
|
|
cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(aColor));
|
|
aErrQuad = (ImpErrorQuad(aColor) -= pWriteAcc->GetPaletteColor(cIndex));
|
|
pQLine1[++nX].ImplAddColorError7(aErrQuad);
|
|
pQLine2[nX--].ImplAddColorError1(aErrQuad);
|
|
pQLine2[nX--].ImplAddColorError5(aErrQuad);
|
|
pQLine2[nX++].ImplAddColorError3(aErrQuad);
|
|
pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
|
|
}
|
|
|
|
// Last RowPixel
|
|
if (nX < nWidth)
|
|
{
|
|
cIndex = static_cast<sal_uInt8>(aColorMap.GetBestPaletteIndex(pQLine1[nWidth1].ImplGetColor()));
|
|
pWriteAcc->SetPixelOnData(pScanline, nX, BitmapColor(cIndex));
|
|
}
|
|
|
|
// Refill/copy row buffer
|
|
pQLine1 = pQLine2;
|
|
bQ1 = !bQ1;
|
|
pQLine2 = bQ1 ? aErrQuad2.data() : aErrQuad1.data();
|
|
|
|
if (nYTmp < nHeight)
|
|
{
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(nYTmp);
|
|
for (nX = 0; nX < nWidth; nX++)
|
|
{
|
|
if (pReadAcc->HasPalette())
|
|
pQLine2[nX] = pReadAcc->GetPaletteColor(pReadAcc->GetIndexFromData(pScanlineRead, nX));
|
|
else
|
|
pQLine2[nX] = pReadAcc->GetPixelFromData(pScanlineRead, nX);
|
|
}
|
|
}
|
|
}
|
|
|
|
pWriteAcc.reset();
|
|
|
|
const MapMode aMap(maPrefMapMode);
|
|
const Size aSize(maPrefSize);
|
|
|
|
*this = std::move(aNewBmp);
|
|
|
|
maPrefMapMode = aMap;
|
|
maPrefSize = aSize;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Bitmap::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
|
|
{
|
|
if(basegfx::fTools::equalZero(rScaleX) || basegfx::fTools::equalZero(rScaleY))
|
|
{
|
|
// no scale
|
|
return true;
|
|
}
|
|
|
|
if(basegfx::fTools::equal(rScaleX, 1.0) && basegfx::fTools::equal(rScaleY, 1.0))
|
|
{
|
|
// no scale
|
|
return true;
|
|
}
|
|
|
|
const auto eStartPixelFormat = getPixelFormat();
|
|
|
|
if (mxSalBmp && mxSalBmp->ScalingSupported())
|
|
{
|
|
// implementation specific scaling
|
|
std::shared_ptr<SalBitmap> xImpBmp(ImplGetSVData()->mpDefInst->CreateSalBitmap());
|
|
if (xImpBmp->Create(*mxSalBmp) && xImpBmp->Scale(rScaleX, rScaleY, nScaleFlag))
|
|
{
|
|
ImplSetSalBitmap(xImpBmp);
|
|
SAL_INFO( "vcl.opengl", "Ref count: " << mxSalBmp.use_count() );
|
|
maPrefMapMode = MapMode( MapUnit::MapPixel );
|
|
maPrefSize = xImpBmp->GetSize();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
BitmapEx aBmpEx(*this);
|
|
bool bRetval(false);
|
|
|
|
switch(nScaleFlag)
|
|
{
|
|
case BmpScaleFlag::Default:
|
|
if (GetSizePixel().Width() < 2 || GetSizePixel().Height() < 2)
|
|
bRetval = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(rScaleX, rScaleY));
|
|
else
|
|
bRetval = BitmapFilter::Filter(aBmpEx, BitmapScaleSuperFilter(rScaleX, rScaleY));
|
|
break;
|
|
|
|
case BmpScaleFlag::Fast:
|
|
case BmpScaleFlag::NearestNeighbor:
|
|
bRetval = BitmapFilter::Filter(aBmpEx, BitmapFastScaleFilter(rScaleX, rScaleY));
|
|
break;
|
|
|
|
case BmpScaleFlag::Interpolate:
|
|
bRetval = BitmapFilter::Filter(aBmpEx, BitmapInterpolateScaleFilter(rScaleX, rScaleY));
|
|
break;
|
|
|
|
case BmpScaleFlag::BestQuality:
|
|
case BmpScaleFlag::Lanczos:
|
|
bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleLanczos3Filter(rScaleX, rScaleY));
|
|
break;
|
|
|
|
case BmpScaleFlag::BiCubic:
|
|
bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleBicubicFilter(rScaleX, rScaleY));
|
|
break;
|
|
|
|
case BmpScaleFlag::BiLinear:
|
|
bRetval = BitmapFilter::Filter(aBmpEx, vcl::BitmapScaleBilinearFilter(rScaleX, rScaleY));
|
|
break;
|
|
}
|
|
|
|
if (bRetval)
|
|
*this = aBmpEx.GetBitmap();
|
|
|
|
OSL_ENSURE(!bRetval || eStartPixelFormat == getPixelFormat(), "Bitmap::Scale has changed the ColorDepth, this should *not* happen (!)");
|
|
return bRetval;
|
|
}
|
|
|
|
bool Bitmap::Scale( const Size& rNewSize, BmpScaleFlag nScaleFlag )
|
|
{
|
|
const Size aSize( GetSizePixel() );
|
|
bool bRet;
|
|
|
|
if( aSize.Width() && aSize.Height() )
|
|
{
|
|
bRet = Scale( static_cast<double>(rNewSize.Width()) / aSize.Width(),
|
|
static_cast<double>(rNewSize.Height()) / aSize.Height(),
|
|
nScaleFlag );
|
|
}
|
|
else
|
|
bRet = true;
|
|
|
|
return bRet;
|
|
}
|
|
|
|
bool Bitmap::HasFastScale()
|
|
{
|
|
#if HAVE_FEATURE_SKIA
|
|
if( SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::renderMethodToUse() != SkiaHelper::RenderRaster)
|
|
return true;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void Bitmap::AdaptBitCount(Bitmap& rNew) const
|
|
{
|
|
// aNew is the result of some operation; adapt it's BitCount to the original (this)
|
|
if (getPixelFormat() == rNew.getPixelFormat())
|
|
return;
|
|
|
|
switch (getPixelFormat())
|
|
{
|
|
case vcl::PixelFormat::N8_BPP:
|
|
{
|
|
if(HasGreyPaletteAny())
|
|
{
|
|
rNew.Convert(BmpConversion::N8BitGreys);
|
|
}
|
|
else
|
|
{
|
|
rNew.Convert(BmpConversion::N8BitColors);
|
|
}
|
|
break;
|
|
}
|
|
case vcl::PixelFormat::N24_BPP:
|
|
{
|
|
rNew.Convert(BmpConversion::N24Bit);
|
|
break;
|
|
}
|
|
case vcl::PixelFormat::N32_BPP:
|
|
{
|
|
rNew.Convert(BmpConversion::N32Bit);
|
|
break;
|
|
}
|
|
case vcl::PixelFormat::INVALID:
|
|
{
|
|
SAL_WARN("vcl", "Can't adapt the pixelformat as it is invalid.");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void shiftColors(sal_Int32* pColorArray, const BitmapScopedReadAccess& pReadAcc)
|
|
{
|
|
Scanline pScanlineRead = pReadAcc->GetScanline(0); // Why always 0?
|
|
for (tools::Long n = 0, nWidth = pReadAcc->Width(); n < nWidth; ++n)
|
|
{
|
|
const BitmapColor aColor = pReadAcc->GetColorFromData(pScanlineRead, n);
|
|
*pColorArray++ = static_cast<sal_Int32>(aColor.GetBlue()) << 12;
|
|
*pColorArray++ = static_cast<sal_Int32>(aColor.GetGreen()) << 12;
|
|
*pColorArray++ = static_cast<sal_Int32>(aColor.GetRed()) << 12;
|
|
}
|
|
}
|
|
|
|
bool Bitmap::Dither()
|
|
{
|
|
const Size aSize( GetSizePixel() );
|
|
if( aSize.Width() == 1 || aSize.Height() == 1 )
|
|
return true;
|
|
if( ( aSize.Width() <= 3 ) || ( aSize.Height() <= 2 ) )
|
|
return false;
|
|
|
|
BitmapScopedReadAccess pReadAcc(*this);
|
|
Bitmap aNewBmp(GetSizePixel(), vcl::PixelFormat::N8_BPP);
|
|
BitmapScopedWriteAccess pWriteAcc(aNewBmp);
|
|
if( !pReadAcc || !pWriteAcc )
|
|
return false;
|
|
|
|
tools::Long nWidth = pReadAcc->Width();
|
|
tools::Long nWidth1 = nWidth - 1;
|
|
tools::Long nHeight = pReadAcc->Height();
|
|
tools::Long nW = nWidth * 3;
|
|
tools::Long nW2 = nW - 3;
|
|
std::unique_ptr<sal_Int32[]> p1(new sal_Int32[ nW ]);
|
|
std::unique_ptr<sal_Int32[]> p2(new sal_Int32[ nW ]);
|
|
sal_Int32* p1T = p1.get();
|
|
sal_Int32* p2T = p2.get();
|
|
shiftColors(p2T, pReadAcc);
|
|
for( tools::Long nYAcc = 0; nYAcc < nHeight; nYAcc++ )
|
|
{
|
|
std::swap(p1T, p2T);
|
|
if (nYAcc < nHeight - 1)
|
|
shiftColors(p2T, pReadAcc);
|
|
|
|
auto CalcError = [](tools::Long n)
|
|
{
|
|
n = std::clamp<tools::Long>(n >> 12, 0, 255);
|
|
return std::pair(FloydErrMap[n], FloydMap[n]);
|
|
};
|
|
|
|
auto CalcErrors = [&](tools::Long n)
|
|
{ return std::tuple_cat(CalcError(p1T[n]), CalcError(p1T[n + 1]), CalcError(p1T[n + 2])); };
|
|
|
|
auto CalcT = [](sal_Int32* dst, const int* src, int b, int g, int r)
|
|
{
|
|
dst[0] += src[b];
|
|
dst[1] += src[g];
|
|
dst[2] += src[r];
|
|
};
|
|
|
|
auto Calc1 = [&](int x, int b, int g, int r) { CalcT(p2T + x + 3, FloydError1, b, g, r); };
|
|
auto Calc3 = [&](int x, int b, int g, int r) { CalcT(p2T + x - 3, FloydError3, b, g, r); };
|
|
auto Calc5 = [&](int x, int b, int g, int r) { CalcT(p2T + x, FloydError5, b, g, r); };
|
|
auto Calc7 = [&](int x, int b, int g, int r) { CalcT(p1T + x + 3, FloydError7, b, g, r); };
|
|
|
|
Scanline pScanline = pWriteAcc->GetScanline(nYAcc);
|
|
// Examine first Pixel separately
|
|
{
|
|
auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(0);
|
|
Calc1(0, nBErr, nGErr, nRErr);
|
|
Calc5(0, nBErr, nGErr, nRErr);
|
|
Calc7(0, nBErr, nGErr, nRErr);
|
|
pWriteAcc->SetPixelOnData( pScanline, 0, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
|
|
}
|
|
// Get middle Pixels using a loop
|
|
for ( tools::Long nX = 3, nXAcc = 1; nX < nW2; nX += 3, nXAcc++ )
|
|
{
|
|
auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(nX);
|
|
Calc1(nX, nBErr, nGErr, nRErr);
|
|
Calc3(nX, nBErr, nGErr, nRErr);
|
|
Calc5(nX, nBErr, nGErr, nRErr);
|
|
Calc7(nX, nBErr, nGErr, nRErr);
|
|
pWriteAcc->SetPixelOnData( pScanline, nXAcc, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
|
|
}
|
|
// Treat last Pixel separately
|
|
{
|
|
auto [nBErr, nBC, nGErr, nGC, nRErr, nRC] = CalcErrors(nW2);
|
|
Calc3(nW2, nBErr, nGErr, nRErr);
|
|
Calc5(nW2, nBErr, nGErr, nRErr);
|
|
pWriteAcc->SetPixelOnData( pScanline, nWidth1, BitmapColor(static_cast<sal_uInt8>(nVCLBLut[ nBC ] + nVCLGLut[nGC ] + nVCLRLut[nRC ])) );
|
|
}
|
|
}
|
|
pReadAcc.reset();
|
|
pWriteAcc.reset();
|
|
const MapMode aMap( maPrefMapMode );
|
|
const Size aPrefSize( maPrefSize );
|
|
*this = std::move(aNewBmp);
|
|
maPrefMapMode = aMap;
|
|
maPrefSize = aPrefSize;
|
|
return true;
|
|
}
|
|
|
|
bool Bitmap::Adjust( short nLuminancePercent, short nContrastPercent,
|
|
short nChannelRPercent, short nChannelGPercent, short nChannelBPercent,
|
|
double fGamma, bool bInvert, bool msoBrightness )
|
|
{
|
|
// nothing to do => return quickly
|
|
if( !nLuminancePercent && !nContrastPercent &&
|
|
!nChannelRPercent && !nChannelGPercent && !nChannelBPercent &&
|
|
( fGamma == 1.0 ) && !bInvert )
|
|
{
|
|
return true;
|
|
}
|
|
|
|
BitmapScopedWriteAccess pAcc(*this);
|
|
if( !pAcc )
|
|
return false;
|
|
|
|
BitmapColor aCol;
|
|
const tools::Long nW = pAcc->Width();
|
|
const tools::Long nH = pAcc->Height();
|
|
std::unique_ptr<sal_uInt8[]> cMapR(new sal_uInt8[ 256 ]);
|
|
std::unique_ptr<sal_uInt8[]> cMapG(new sal_uInt8[ 256 ]);
|
|
std::unique_ptr<sal_uInt8[]> cMapB(new sal_uInt8[ 256 ]);
|
|
double fM, fROff, fGOff, fBOff, fOff;
|
|
|
|
// calculate slope
|
|
if( nContrastPercent >= 0 )
|
|
fM = 128.0 / ( 128.0 - 1.27 * std::clamp( nContrastPercent, short(0), short(100) ) );
|
|
else
|
|
fM = ( 128.0 + 1.27 * std::clamp( nContrastPercent, short(-100), short(0) ) ) / 128.0;
|
|
|
|
if(!msoBrightness)
|
|
// total offset = luminance offset + contrast offset
|
|
fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55 + 128.0 - fM * 128.0;
|
|
else
|
|
fOff = std::clamp( nLuminancePercent, short(-100), short(100) ) * 2.55;
|
|
|
|
// channel offset = channel offset + total offset
|
|
fROff = nChannelRPercent * 2.55 + fOff;
|
|
fGOff = nChannelGPercent * 2.55 + fOff;
|
|
fBOff = nChannelBPercent * 2.55 + fOff;
|
|
|
|
// calculate gamma value
|
|
fGamma = ( fGamma <= 0.0 || fGamma > 10.0 ) ? 1.0 : ( 1.0 / fGamma );
|
|
const bool bGamma = ( fGamma != 1.0 );
|
|
|
|
// create mapping table
|
|
for( tools::Long nX = 0; nX < 256; nX++ )
|
|
{
|
|
if(!msoBrightness)
|
|
{
|
|
cMapR[nX] = basegfx::fround<sal_uInt8>(nX * fM + fROff);
|
|
cMapG[nX] = basegfx::fround<sal_uInt8>(nX * fM + fGOff);
|
|
cMapB[nX] = basegfx::fround<sal_uInt8>(nX * fM + fBOff);
|
|
}
|
|
else
|
|
{
|
|
// LO simply uses (in a somewhat optimized form) "newcolor = (oldcolor-128)*contrast+brightness+128"
|
|
// as the formula, i.e. contrast first, brightness afterwards. MSOffice, for whatever weird reason,
|
|
// use neither first, but apparently it applies half of brightness before contrast and half afterwards.
|
|
cMapR[nX] = basegfx::fround<sal_uInt8>((nX + fROff / 2 - 128) * fM + 128 + fROff / 2);
|
|
cMapG[nX] = basegfx::fround<sal_uInt8>((nX + fGOff / 2 - 128) * fM + 128 + fGOff / 2);
|
|
cMapB[nX] = basegfx::fround<sal_uInt8>((nX + fBOff / 2 - 128) * fM + 128 + fBOff / 2);
|
|
}
|
|
if( bGamma )
|
|
{
|
|
cMapR[ nX ] = GAMMA( cMapR[ nX ], fGamma );
|
|
cMapG[ nX ] = GAMMA( cMapG[ nX ], fGamma );
|
|
cMapB[ nX ] = GAMMA( cMapB[ nX ], fGamma );
|
|
}
|
|
|
|
if( bInvert )
|
|
{
|
|
cMapR[ nX ] = ~cMapR[ nX ];
|
|
cMapG[ nX ] = ~cMapG[ nX ];
|
|
cMapB[ nX ] = ~cMapB[ nX ];
|
|
}
|
|
}
|
|
|
|
// do modifying
|
|
if( pAcc->HasPalette() )
|
|
{
|
|
BitmapColor aNewCol;
|
|
|
|
for( sal_uInt16 i = 0, nCount = pAcc->GetPaletteEntryCount(); i < nCount; i++ )
|
|
{
|
|
const BitmapColor& rCol = pAcc->GetPaletteColor( i );
|
|
aNewCol.SetRed( cMapR[ rCol.GetRed() ] );
|
|
aNewCol.SetGreen( cMapG[ rCol.GetGreen() ] );
|
|
aNewCol.SetBlue( cMapB[ rCol.GetBlue() ] );
|
|
pAcc->SetPaletteColor( i, aNewCol );
|
|
}
|
|
}
|
|
else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcBgr )
|
|
{
|
|
for( tools::Long nY = 0; nY < nH; nY++ )
|
|
{
|
|
Scanline pScan = pAcc->GetScanline( nY );
|
|
|
|
for( tools::Long nX = 0; nX < nW; nX++ )
|
|
{
|
|
*pScan = cMapB[ *pScan ]; pScan++;
|
|
*pScan = cMapG[ *pScan ]; pScan++;
|
|
*pScan = cMapR[ *pScan ]; pScan++;
|
|
}
|
|
}
|
|
}
|
|
else if( pAcc->GetScanlineFormat() == ScanlineFormat::N24BitTcRgb )
|
|
{
|
|
for( tools::Long nY = 0; nY < nH; nY++ )
|
|
{
|
|
Scanline pScan = pAcc->GetScanline( nY );
|
|
|
|
for( tools::Long nX = 0; nX < nW; nX++ )
|
|
{
|
|
*pScan = cMapR[ *pScan ]; pScan++;
|
|
*pScan = cMapG[ *pScan ]; pScan++;
|
|
*pScan = cMapB[ *pScan ]; pScan++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( tools::Long nY = 0; nY < nH; nY++ )
|
|
{
|
|
Scanline pScanline = pAcc->GetScanline(nY);
|
|
for( tools::Long nX = 0; nX < nW; nX++ )
|
|
{
|
|
aCol = pAcc->GetPixelFromData( pScanline, nX );
|
|
aCol.SetRed( cMapR[ aCol.GetRed() ] );
|
|
aCol.SetGreen( cMapG[ aCol.GetGreen() ] );
|
|
aCol.SetBlue( cMapB[ aCol.GetBlue() ] );
|
|
pAcc->SetPixelOnData( pScanline, nX, aCol );
|
|
}
|
|
}
|
|
}
|
|
|
|
pAcc.reset();
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
inline sal_uInt8 backBlendAlpha(sal_uInt16 alpha, sal_uInt16 srcCol, sal_uInt16 startCol)
|
|
{
|
|
const sal_uInt16 nAlpha((alpha * startCol) / 255);
|
|
if(srcCol > nAlpha)
|
|
{
|
|
return static_cast<sal_uInt8>(((srcCol - nAlpha) * 255) / (255 - nAlpha));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void Bitmap::RemoveBlendedStartColor(
|
|
const Color& rStartColor,
|
|
const AlphaMask& rAlphaMask)
|
|
{
|
|
// no content, done
|
|
if(IsEmpty())
|
|
return;
|
|
|
|
BitmapScopedWriteAccess pAcc(*this);
|
|
const tools::Long nHeight(pAcc->Height());
|
|
const tools::Long nWidth(pAcc->Width());
|
|
|
|
// no content, done
|
|
if(0 == nHeight || 0 == nWidth)
|
|
return;
|
|
|
|
BitmapScopedReadAccess pAlphaAcc(rAlphaMask);
|
|
|
|
// inequal sizes of content and alpha, avoid change (maybe assert?)
|
|
if(pAlphaAcc->Height() != nHeight || pAlphaAcc->Width() != nWidth)
|
|
return;
|
|
|
|
// prepare local values as sal_uInt16 to avoid multiple conversions
|
|
const sal_uInt16 nStartColRed(rStartColor.GetRed());
|
|
const sal_uInt16 nStartColGreen(rStartColor.GetGreen());
|
|
const sal_uInt16 nStartColBlue(rStartColor.GetBlue());
|
|
|
|
for (tools::Long y = 0; y < nHeight; ++y)
|
|
{
|
|
for (tools::Long x = 0; x < nWidth; ++x)
|
|
{
|
|
// get alpha value
|
|
const sal_uInt8 nAlpha8(pAlphaAcc->GetColor(y, x).GetRed());
|
|
|
|
// not or completely transparent, no adaptation needed
|
|
if(0 == nAlpha8 || 255 == nAlpha8)
|
|
continue;
|
|
|
|
// prepare local value as sal_uInt16 to avoid multiple conversions
|
|
const sal_uInt16 nAlpha16(static_cast<sal_uInt16>(nAlpha8));
|
|
|
|
// get source color
|
|
BitmapColor aColor(pAcc->GetColor(y, x));
|
|
|
|
// modify/blend back source color
|
|
aColor.SetRed(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetRed()), nStartColRed));
|
|
aColor.SetGreen(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetGreen()), nStartColGreen));
|
|
aColor.SetBlue(backBlendAlpha(nAlpha16, static_cast<sal_uInt16>(aColor.GetBlue()), nStartColBlue));
|
|
|
|
// write result back
|
|
pAcc->SetPixel(y, x, aColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
const basegfx::SystemDependentDataHolder* Bitmap::accessSystemDependentDataHolder() const
|
|
{
|
|
return mxSalBmp.get();
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|