diff options
Diffstat (limited to 'vcl/skia/salbmp.cxx')
-rw-r--r-- | vcl/skia/salbmp.cxx | 1500 |
1 files changed, 1500 insertions, 0 deletions
diff --git a/vcl/skia/salbmp.cxx b/vcl/skia/salbmp.cxx new file mode 100644 index 0000000000..f828338814 --- /dev/null +++ b/vcl/skia/salbmp.cxx @@ -0,0 +1,1500 @@ +/* -*- 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 <skia/salbmp.hxx> + +#include <o3tl/safeint.hxx> +#include <tools/helpers.hxx> +#include <boost/smart_ptr/make_shared.hpp> + +#include <salgdi.hxx> +#include <salinst.hxx> +#include <scanlinewriter.hxx> +#include <svdata.hxx> +#include <bitmap/bmpfast.hxx> +#include <vcl/BitmapReadAccess.hxx> + +#include <skia/utils.hxx> +#include <skia/zone.hxx> + +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkImage.h> +#include <SkPixelRef.h> +#include <SkShader.h> +#include <SkSurface.h> +#include <SkSwizzle.h> +#include <SkColorFilter.h> +#include <SkColorMatrix.h> +#include <skia_opts.hxx> + +#ifdef DBG_UTIL +#include <fstream> +#define CANARY "skia-canary" +#endif + +using namespace SkiaHelper; + +// As constexpr here, evaluating it directly in code makes Clang warn about unreachable code. +constexpr bool kN32_SkColorTypeIsBGRA = (kN32_SkColorType == kBGRA_8888_SkColorType); + +SkiaSalBitmap::SkiaSalBitmap() {} + +SkiaSalBitmap::~SkiaSalBitmap() {} + +SkiaSalBitmap::SkiaSalBitmap(const sk_sp<SkImage>& image) +{ + ResetAllData(); + mImage = image; + mPalette = BitmapPalette(); +#if SKIA_USE_BITMAP32 + mBitCount = 32; +#else + mBitCount = 24; +#endif + mSize = mPixelsSize = Size(image->width(), image->height()); + ComputeScanlineSize(); + mReadAccessCount = 0; +#ifdef DBG_UTIL + mWriteAccessCount = 0; +#endif + SAL_INFO("vcl.skia.trace", "bitmapfromimage(" << this << ")"); +} + +bool SkiaSalBitmap::Create(const Size& rSize, vcl::PixelFormat ePixelFormat, + const BitmapPalette& rPal) +{ + assert(mReadAccessCount == 0); + ResetAllData(); + if (ePixelFormat == vcl::PixelFormat::INVALID) + return false; + mPalette = rPal; + mBitCount = vcl::pixelFormatBitCount(ePixelFormat); + mSize = rSize; + ResetPendingScaling(); + if (!ComputeScanlineSize()) + { + mBitCount = 0; + mSize = mPixelsSize = Size(); + mScanlineSize = 0; + mPalette = BitmapPalette(); + return false; + } + SAL_INFO("vcl.skia.trace", "create(" << this << ")"); + return true; +} + +bool SkiaSalBitmap::ComputeScanlineSize() +{ + int bitScanlineWidth; + if (o3tl::checked_multiply<int>(mPixelsSize.Width(), mBitCount, bitScanlineWidth)) + { + SAL_WARN("vcl.skia", "checked multiply failed"); + return false; + } + mScanlineSize = AlignedWidth4Bytes(bitScanlineWidth); + return true; +} + +void SkiaSalBitmap::CreateBitmapData() +{ + assert(!mBuffer); + // Make sure code has not missed calling ComputeScanlineSize(). + assert(mScanlineSize == int(AlignedWidth4Bytes(mPixelsSize.Width() * mBitCount))); + // The pixels could be stored in SkBitmap, but Skia only supports 8bit gray, 16bit and 32bit formats + // (e.g. 24bpp is actually stored as 32bpp). But some of our code accessing the bitmap assumes that + // when it asked for 24bpp, the format really will be 24bpp (e.g. the png loader), so we cannot use + // SkBitmap to store the data. And even 8bpp is problematic, since Skia does not support palettes + // and a VCL bitmap can change its grayscale status simply by changing the palette. + // Moreover creating SkImage from SkBitmap does a data copy unless the bitmap is immutable. + // So just always store pixels in our buffer and convert as necessary. + if (mScanlineSize == 0 || mPixelsSize.Height() == 0) + return; + + size_t allocate = mScanlineSize * mPixelsSize.Height(); +#ifdef DBG_UTIL + allocate += sizeof(CANARY); +#endif + mBuffer = boost::make_shared_noinit<sal_uInt8[]>(allocate); +#ifdef DBG_UTIL + // fill with random garbage + sal_uInt8* buffer = mBuffer.get(); + for (size_t i = 0; i < allocate; i++) + buffer[i] = (i & 0xFF); + memcpy(buffer + allocate - sizeof(CANARY), CANARY, sizeof(CANARY)); +#endif +} + +bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp) +{ + return Create(rSalBmp, vcl::bitDepthToPixelFormat(rSalBmp.GetBitCount())); +} + +bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp, SalGraphics* pGraphics) +{ + auto ePixelFormat = vcl::PixelFormat::INVALID; + if (pGraphics) + ePixelFormat = vcl::bitDepthToPixelFormat(pGraphics->GetBitCount()); + else + ePixelFormat = vcl::bitDepthToPixelFormat(rSalBmp.GetBitCount()); + + return Create(rSalBmp, ePixelFormat); +} + +bool SkiaSalBitmap::Create(const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelFormat) +{ + assert(mReadAccessCount == 0); + assert(&rSalBmp != this); + ResetAllData(); + const SkiaSalBitmap& src = static_cast<const SkiaSalBitmap&>(rSalBmp); + mImage = src.mImage; + mImageImmutable = src.mImageImmutable; + mAlphaImage = src.mAlphaImage; + mBuffer = src.mBuffer; + mPalette = src.mPalette; + mBitCount = src.mBitCount; + mSize = src.mSize; + mPixelsSize = src.mPixelsSize; + mScanlineSize = src.mScanlineSize; + mScaleQuality = src.mScaleQuality; + mEraseColorSet = src.mEraseColorSet; + mEraseColor = src.mEraseColor; + if (vcl::pixelFormatBitCount(eNewPixelFormat) != src.GetBitCount()) + { + // This appears to be unused(?). Implement this just in case, but be lazy + // about it and rely on EnsureBitmapData() doing the conversion from mImage + // if needed, even if that may need unnecessary to- and from- SkImage + // conversion. + ResetToSkImage(GetSkImage()); + } + SAL_INFO("vcl.skia.trace", "create(" << this << "): (" << &src << ")"); + return true; +} + +bool SkiaSalBitmap::Create(const css::uno::Reference<css::rendering::XBitmapCanvas>&, Size&, bool) +{ + return false; +} + +void SkiaSalBitmap::Destroy() +{ + SAL_INFO("vcl.skia.trace", "destroy(" << this << ")"); +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + assert(mReadAccessCount == 0); + ResetAllData(); +} + +Size SkiaSalBitmap::GetSize() const { return mSize; } + +sal_uInt16 SkiaSalBitmap::GetBitCount() const { return mBitCount; } + +BitmapBuffer* SkiaSalBitmap::AcquireBuffer(BitmapAccessMode nMode) +{ + switch (nMode) + { + case BitmapAccessMode::Write: + EnsureBitmapUniqueData(); + if (!mBuffer) + return nullptr; + assert(mPixelsSize == mSize); + assert(!mEraseColorSet); + break; + case BitmapAccessMode::Read: + EnsureBitmapData(); + if (!mBuffer) + return nullptr; + assert(mPixelsSize == mSize); + assert(!mEraseColorSet); + break; + case BitmapAccessMode::Info: + // Related tdf#156629 and tdf#156630 force snapshot of alpha mask + // On macOS, with Skia/Metal or Skia/Raster with a Retina display + // (i.e. 2.0 window scale), the alpha mask gets upscaled in certain + // cases. + // This bug appears to be caused by pending scaling of an existing + // SkImage in the bitmap parameter. So, force the SkiaSalBitmap to + // handle its pending scaling. + // Note: also handle pending scaling if SAL_FORCE_HIDPI_SCALING is + // set otherwise exporting the following animated .png image will + // fail: + // https://bugs.documentfoundation.org/attachment.cgi?id=188792 + static const bool bForceHiDPIScaling = getenv("SAL_FORCE_HIDPI_SCALING") != nullptr; + if (mImage && !mImageImmutable && mBitCount == 8 && mPalette.IsGreyPalette8Bit() + && (mPixelsSize != mSize || bForceHiDPIScaling)) + { + ResetToSkImage(GetSkImage()); + ResetPendingScaling(); + assert(mPixelsSize == mSize); + + // When many of the images affected by tdf#156629 and + // tdf#156630 are exported to PDF the first time after the + // image has been opened and before it has been printed or run + // in a slideshow, the alpha mask will unexpectedly be + // inverted. Fix that by marking this alpha mask as immutable + // so that when Invert() is called on this alpha mask, it will + // be a noop. Invert() is a noop after EnsureBitmapData() is + // called but we don't want to call that due to performance + // so set a flag instead. + mImageImmutable = true; + } + break; + } +#ifdef DBG_UTIL + // BitmapWriteAccess stores also a copy of the palette and it can + // be modified, so concurrent reading of it might result in inconsistencies. + assert(mWriteAccessCount == 0 || nMode == BitmapAccessMode::Write); +#endif + BitmapBuffer* buffer = new BitmapBuffer; + buffer->mnWidth = mSize.Width(); + buffer->mnHeight = mSize.Height(); + buffer->mnBitCount = mBitCount; + buffer->maPalette = mPalette; + if (nMode != BitmapAccessMode::Info) + buffer->mpBits = mBuffer.get(); + else + buffer->mpBits = nullptr; + if (mPixelsSize == mSize) + buffer->mnScanlineSize = mScanlineSize; + else + { + // The value of mScanlineSize is based on internal mPixelsSize, but the outside + // world cares about mSize, the size that the report as the size of the bitmap, + // regardless of any internal state. So report scanline size for that size. + Size savedPixelsSize = mPixelsSize; + mPixelsSize = mSize; + ComputeScanlineSize(); + buffer->mnScanlineSize = mScanlineSize; + mPixelsSize = savedPixelsSize; + ComputeScanlineSize(); + } + switch (mBitCount) + { + case 1: + buffer->mnFormat = ScanlineFormat::N1BitMsbPal; + break; + case 8: + buffer->mnFormat = ScanlineFormat::N8BitPal; + break; + case 24: + // Make the RGB/BGR format match the default Skia 32bpp format, to allow + // easy conversion later. + buffer->mnFormat = kN32_SkColorTypeIsBGRA ? ScanlineFormat::N24BitTcBgr + : ScanlineFormat::N24BitTcRgb; + break; + case 32: + buffer->mnFormat = kN32_SkColorTypeIsBGRA ? ScanlineFormat::N32BitTcBgra + : ScanlineFormat::N32BitTcRgba; + break; + default: + abort(); + } + buffer->mnFormat |= ScanlineFormat::TopDown; + // Refcount all read/write accesses, to catch problems with existing accesses while + // a bitmap changes, and also to detect when we can free mBuffer if wanted. + // Write mode implies also reading. It would be probably a good idea to count even + // Info accesses, but VclCanvasBitmap keeps one around pointlessly, causing tdf#150817. + if (nMode == BitmapAccessMode::Read || nMode == BitmapAccessMode::Write) + ++mReadAccessCount; +#ifdef DBG_UTIL + if (nMode == BitmapAccessMode::Write) + ++mWriteAccessCount; +#endif + return buffer; +} + +void SkiaSalBitmap::ReleaseBuffer(BitmapBuffer* pBuffer, BitmapAccessMode nMode) +{ + ReleaseBuffer(pBuffer, nMode, false); +} + +void SkiaSalBitmap::ReleaseBuffer(BitmapBuffer* pBuffer, BitmapAccessMode nMode, + bool dontChangeToErase) +{ + if (nMode == BitmapAccessMode::Write) + { +#ifdef DBG_UTIL + assert(mWriteAccessCount > 0); + --mWriteAccessCount; +#endif + mPalette = pBuffer->maPalette; + ResetToBuffer(); + DataChanged(); + } + if (nMode == BitmapAccessMode::Read || nMode == BitmapAccessMode::Write) + { + assert(mReadAccessCount > 0); + --mReadAccessCount; + } + // Are there any more ground movements underneath us ? + assert(pBuffer->mnWidth == mSize.Width()); + assert(pBuffer->mnHeight == mSize.Height()); + assert(pBuffer->mnBitCount == mBitCount); + assert(pBuffer->mpBits == mBuffer.get() || nMode == BitmapAccessMode::Info); + verify(); + delete pBuffer; + if (nMode == BitmapAccessMode::Write && !dontChangeToErase) + { + // This saves memory and is also used by IsFullyOpaqueAsAlpha() to avoid unnecessary + // alpha blending. + if (IsAllBlack()) + { + SAL_INFO("vcl.skia.trace", "releasebuffer(" << this << "): erasing to black"); + EraseInternal(COL_BLACK); + } + } +} + +static bool isAllZero(const sal_uInt8* data, size_t size) +{ // For performance, check in larger data chunks. +#ifdef UINT64_MAX + const int64_t* d = reinterpret_cast<const int64_t*>(data); +#else + const int32_t* d = reinterpret_cast<const int32_t*>(data); +#endif + constexpr size_t step = sizeof(*d) * 8; + for (size_t i = 0; i < size / step; ++i) + { // Unrolled loop. + if (d[0] != 0) + return false; + if (d[1] != 0) + return false; + if (d[2] != 0) + return false; + if (d[3] != 0) + return false; + if (d[4] != 0) + return false; + if (d[5] != 0) + return false; + if (d[6] != 0) + return false; + if (d[7] != 0) + return false; + d += 8; + } + for (size_t i = size / step * step; i < size; ++i) + if (data[i] != 0) + return false; + return true; +} + +bool SkiaSalBitmap::IsAllBlack() const +{ + if (mBitCount % 8 != 0 || (!!mPalette && mPalette[0] != COL_BLACK)) + return false; // Don't bother. + if (mSize.Width() * mBitCount / 8 == mScanlineSize) + return isAllZero(mBuffer.get(), mScanlineSize * mSize.Height()); + for (tools::Long y = 0; y < mSize.Height(); ++y) + if (!isAllZero(mBuffer.get() + mScanlineSize * y, mSize.Width() * mBitCount / 8)) + return false; + return true; +} + +bool SkiaSalBitmap::GetSystemData(BitmapSystemData&) +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + return false; +} + +bool SkiaSalBitmap::ScalingSupported() const { return true; } + +bool SkiaSalBitmap::Scale(const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag) +{ + SkiaZone zone; +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + Size newSize(FRound(mSize.Width() * rScaleX), FRound(mSize.Height() * rScaleY)); + if (mSize == newSize) + return true; + + SAL_INFO("vcl.skia.trace", "scale(" << this << "): " << mSize << "/" << mBitCount << "->" + << newSize << ":" << static_cast<int>(nScaleFlag)); + + if (mEraseColorSet) + { // Simple. + mSize = newSize; + ResetPendingScaling(); + EraseInternal(mEraseColor); + return true; + } + + if (mBitCount < 24 && !mPalette.IsGreyPalette8Bit()) + { + // Scaling can introduce additional colors not present in the original + // bitmap (e.g. when smoothing). If the bitmap is indexed (has non-trivial palette), + // this would break the bitmap, because the actual scaling is done only somewhen later. + // Linear 8bit palette (grey) is ok, since there we use directly the values as colors. + SAL_INFO("vcl.skia.trace", "scale(" << this << "): indexed bitmap"); + return false; + } + // The idea here is that the actual scaling will be delayed until the result + // is actually needed. Usually the scaled bitmap will be drawn somewhere, + // so delaying will mean the scaling can be done as a part of GetSkImage(). + // That means it can be GPU-accelerated, while done here directly it would need + // to be either done by CPU, or with the CPU->GPU->CPU roundtrip required + // by GPU-accelerated scaling. + // Pending scaling is detected by 'mSize != mPixelsSize' for mBuffer, + // and 'imageSize(mImage) != mSize' for mImage. It is not intended to have 3 different + // sizes though, code below keeps only mBuffer or mImage. Note that imageSize(mImage) + // may or may not be equal to mPixelsSize, depending on whether mImage is set here + // (sizes will be equal) or whether it's set in GetSkImage() (will not be equal). + // Pending scaling is considered "done" by the time mBuffer is resized (or created). + // Resizing of mImage is somewhat independent of this, since mImage is primarily + // considered to be a cached object (although sometimes it's the only data available). + + // If there is already one scale() pending, use the lowest quality of all requested. + switch (nScaleFlag) + { + case BmpScaleFlag::Fast: + mScaleQuality = nScaleFlag; + break; + case BmpScaleFlag::NearestNeighbor: + // We handle this the same way as Fast by mapping to Skia's nearest-neighbor, + // and it's needed for unittests (mScaling and testTdf132367()). + mScaleQuality = nScaleFlag; + break; + case BmpScaleFlag::Default: + if (mScaleQuality == BmpScaleFlag::BestQuality) + mScaleQuality = nScaleFlag; + break; + case BmpScaleFlag::BestQuality: + // Best is the maximum, set by default. + break; + default: + SAL_INFO("vcl.skia.trace", "scale(" << this << "): unsupported scale algorithm"); + return false; + } + mSize = newSize; + // If we have both mBuffer and mImage, prefer mImage, since it likely will be drawn later. + // We could possibly try to keep the buffer as well, but that would complicate things + // with two different data structures to be scaled on-demand, and it's a question + // if that'd realistically help with anything. + if (mImage) + ResetToSkImage(mImage); + else + ResetToBuffer(); + DataChanged(); + // The rest will be handled when the scaled bitmap is actually needed, + // such as in EnsureBitmapData() or GetSkImage(). + return true; +} + +bool SkiaSalBitmap::Replace(const Color&, const Color&, sal_uInt8) +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + return false; +} + +bool SkiaSalBitmap::ConvertToGreyscale() +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + // Normally this would need to convert contents of mBuffer for all possible formats, + // so just let the VCL algorithm do it. + // Avoid the costly SkImage->buffer->SkImage conversion. + if (!mBuffer && mImage && !mEraseColorSet) + { + if (mBitCount == 8 && mPalette.IsGreyPalette8Bit()) + return true; + sk_sp<SkSurface> surface + = createSkSurface(imageSize(mImage), mImage->imageInfo().alphaType()); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha + // VCL uses different coefficients for conversion to gray than Skia, so use the VCL + // values from Bitmap::ImplMakeGreyscales(). Do not use kGray_8_SkColorType, + // Skia would use its gray conversion formula. + // NOTE: The matrix is 4x5 organized as columns (i.e. each line is a column, not a row). + constexpr SkColorMatrix toGray(77 / 256.0, 151 / 256.0, 28 / 256.0, 0, 0, // R column + 77 / 256.0, 151 / 256.0, 28 / 256.0, 0, 0, // G column + 77 / 256.0, 151 / 256.0, 28 / 256.0, 0, 0, // B column + 0, 0, 0, 1, 0); // don't modify alpha + paint.setColorFilter(SkColorFilters::Matrix(toGray)); + surface->getCanvas()->drawImage(mImage, 0, 0, SkSamplingOptions(), &paint); + mBitCount = 8; + ComputeScanlineSize(); + mPalette = Bitmap::GetGreyPalette(256); + ResetToSkImage(makeCheckedImageSnapshot(surface)); + DataChanged(); + SAL_INFO("vcl.skia.trace", "converttogreyscale(" << this << ")"); + return true; + } + return false; +} + +bool SkiaSalBitmap::InterpretAs8Bit() +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + if (mBitCount == 8 && mPalette.IsGreyPalette8Bit()) + return true; + if (mEraseColorSet) + { + mBitCount = 8; + ComputeScanlineSize(); + mPalette = Bitmap::GetGreyPalette(256); + EraseInternal(mEraseColor); + SAL_INFO("vcl.skia.trace", "interpretas8bit(" << this << ") with erase color"); + return true; + } + // This is usually used by AlphaMask, the point is just to treat + // the content as an alpha channel. This is often used + // by the horrible separate-alpha-outdev hack, where the bitmap comes + // from SkiaSalGraphicsImpl::GetBitmap(), so only mImage is set, + // and that is followed by a later call to GetAlphaSkImage(). + // Avoid the costly SkImage->buffer->SkImage conversion and simply + // just treat the SkImage as being for 8bit bitmap. EnsureBitmapData() + // will do the conversion if needed, but the normal case will be + // GetAlphaSkImage() creating kAlpha_8_SkColorType SkImage from it. + if (mImage) + { + mBitCount = 8; + ComputeScanlineSize(); + mPalette = Bitmap::GetGreyPalette(256); + ResetToSkImage(mImage); // keep mImage, it will be interpreted as 8bit if needed + DataChanged(); + SAL_INFO("vcl.skia.trace", "interpretas8bit(" << this << ") with image"); + return true; + } + SAL_INFO("vcl.skia.trace", "interpretas8bit(" << this << ") with pixel data, ignoring"); + return false; +} + +bool SkiaSalBitmap::Erase(const Color& color) +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + // Optimized variant, just remember the color and apply it when needed, + // which may save having to do format conversions (e.g. GetSkImage() + // may directly erase the SkImage). + EraseInternal(color); + SAL_INFO("vcl.skia.trace", "erase(" << this << ")"); + return true; +} + +void SkiaSalBitmap::EraseInternal(const Color& color) +{ + ResetAllData(); + mEraseColorSet = true; + mEraseColor = color; +} + +bool SkiaSalBitmap::AlphaBlendWith(const SalBitmap& rSalBmp) +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + const SkiaSalBitmap* otherBitmap = dynamic_cast<const SkiaSalBitmap*>(&rSalBmp); + if (!otherBitmap) + return false; + if (mSize != otherBitmap->mSize) + return false; + // We're called from AlphaMask, which should ensure 8bit. + assert(GetBitCount() == 8 && mPalette.IsGreyPalette8Bit()); + // If neither bitmap have Skia images, then AlphaMask::BlendWith() will be faster, + // as it will operate on mBuffer pixel buffers, while for Skia we'd need to convert it. + // If one has and one doesn't, do it using Skia, under the assumption that after this + // the resulting Skia image will be needed for drawing. + if (!(mImage || mEraseColorSet) && !(otherBitmap->mImage || otherBitmap->mEraseColorSet)) + return false; + // This is for AlphaMask, which actually stores the alpha as the pixel values. + // I.e. take value of the color channel (one of them, if >8bit, they should be the same). + if (mEraseColorSet && otherBitmap->mEraseColorSet) + { + const sal_uInt16 nGrey1 = mEraseColor.GetRed(); + const sal_uInt16 nGrey2 = otherBitmap->mEraseColor.GetRed(); + // See comment in AlphaMask::BlendWith for how this calculation was derived + const sal_uInt8 nGrey = static_cast<sal_uInt8>(nGrey1 * nGrey2 / 255); + mEraseColor = Color(nGrey, nGrey, nGrey); + DataChanged(); + SAL_INFO("vcl.skia.trace", + "alphablendwith(" << this << ") : with erase color " << otherBitmap); + return true; + } + std::unique_ptr<SkiaSalBitmap> otherBitmapAllocated; + if (otherBitmap->GetBitCount() != 8 || !otherBitmap->mPalette.IsGreyPalette8Bit()) + { // Convert/interpret as 8bit if needed. + otherBitmapAllocated = std::make_unique<SkiaSalBitmap>(); + if (!otherBitmapAllocated->Create(*otherBitmap) || !otherBitmapAllocated->InterpretAs8Bit()) + return false; + otherBitmap = otherBitmapAllocated.get(); + } + // This is 8-bit bitmap serving as mask, so the image itself needs no alpha. + sk_sp<SkSurface> surface = createSkSurface(mSize, kOpaque_SkAlphaType); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is + surface->getCanvas()->drawImage(GetSkImage(), 0, 0, SkSamplingOptions(), &paint); + // in the 0..1 range that skia uses, the equation we want is: + // r = 1 - ((1 - src) + (1 - dest) - (1 - src) * (1 - dest)) + // which simplifies to: + // r = src * dest + // which is SkBlendMode::kModulate + paint.setBlendMode(SkBlendMode::kModulate); + surface->getCanvas()->drawImage(otherBitmap->GetSkImage(), 0, 0, SkSamplingOptions(), &paint); + ResetToSkImage(makeCheckedImageSnapshot(surface)); + DataChanged(); + SAL_INFO("vcl.skia.trace", "alphablendwith(" << this << ") : with image " << otherBitmap); + return true; +} + +bool SkiaSalBitmap::Invert() +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + // Normally this would need to convert contents of mBuffer for all possible formats, + // so just let the VCL algorithm do it. + // Avoid the costly SkImage->buffer->SkImage conversion. + if (!mBuffer && mImage && !mImageImmutable && !mEraseColorSet) + { + // This is 8-bit bitmap serving as alpha/transparency/mask, so the image itself needs no alpha. + // tdf#156866 use mSize instead of mPixelSize for inverted surface + // Commit 5baac4e53128d3c0fc73b9918dc9a9c2777ace08 switched to setting + // the surface size to mPixelsSize in an attempt to avoid downscaling + // mImage but since it causes tdf#156866, revert back to setting the + // surface size to mSize. + sk_sp<SkSurface> surface = createSkSurface(mSize, kOpaque_SkAlphaType); + surface->getCanvas()->clear(SK_ColorWHITE); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kDifference); + // Drawing the image does not work so create a shader from the image + paint.setShader(GetSkShader(SkSamplingOptions())); + surface->getCanvas()->drawRect(SkRect::MakeXYWH(0, 0, mSize.Width(), mSize.Height()), + paint); + ResetToSkImage(makeCheckedImageSnapshot(surface)); + DataChanged(); + +#ifdef MACOSX + // tdf#158014 make image immutable after using Skia to invert + // I can't explain why inverting using Skia causes this bug on + // macOS but not other platforms. My guess is that Skia on macOS + // is sharing some data when different SkiaSalBitmap instances + // are created from the same OutputDevice. So, mark this + // SkiaSalBitmap instance's image as immutable so that successive + // inversions are done with buffered bitmap data instead of Skia. + mImageImmutable = true; +#endif + + SAL_INFO("vcl.skia.trace", "invert(" << this << ")"); + return true; + } + return false; +} + +SkBitmap SkiaSalBitmap::GetAsSkBitmap() const +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + EnsureBitmapData(); + assert(mSize == mPixelsSize); // data has already been scaled if needed + SkiaZone zone; + SkBitmap bitmap; + if (mBuffer) + { + if (mBitCount == 32) + { + // Make a copy, the bitmap should be immutable (otherwise converting it + // to SkImage will make a copy anyway). + const size_t bytes = mPixelsSize.Height() * mScanlineSize; + std::unique_ptr<sal_uInt8[]> data(new sal_uInt8[bytes]); + memcpy(data.get(), mBuffer.get(), bytes); + if (!bitmap.installPixels( + SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(), alphaType()), + data.release(), mScanlineSize, + [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr)) + abort(); + } + else if (mBitCount == 24) + { + // Convert 24bpp RGB/BGR to 32bpp RGBA/BGRA. + std::unique_ptr<uint32_t[]> data( + new uint32_t[mPixelsSize.Height() * mPixelsSize.Width()]); + uint32_t* dest = data.get(); + // SkConvertRGBToRGBA() also works as BGR to BGRA (the function extends 3 bytes to 4 + // by adding 0xFF alpha, so position of B and R doesn't matter). + if (mPixelsSize.Width() * 3 == mScanlineSize) + SkConvertRGBToRGBA(dest, mBuffer.get(), mPixelsSize.Height() * mPixelsSize.Width()); + else + { + for (tools::Long y = 0; y < mPixelsSize.Height(); ++y) + { + const sal_uInt8* src = mBuffer.get() + mScanlineSize * y; + SkConvertRGBToRGBA(dest, src, mPixelsSize.Width()); + dest += mPixelsSize.Width(); + } + } + if (!bitmap.installPixels( + SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(), + kOpaque_SkAlphaType), + data.release(), mPixelsSize.Width() * 4, + [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr)) + abort(); + } + else if (mBitCount == 8 && mPalette.IsGreyPalette8Bit()) + { + // Convert 8bpp gray to 32bpp RGBA/BGRA. + // There's also kGray_8_SkColorType, but it's probably simpler to make + // GetAsSkBitmap() always return 32bpp SkBitmap and then assume mImage + // is always 32bpp too. + std::unique_ptr<uint32_t[]> data( + new uint32_t[mPixelsSize.Height() * mPixelsSize.Width()]); + uint32_t* dest = data.get(); + if (mPixelsSize.Width() * 1 == mScanlineSize) + SkConvertGrayToRGBA(dest, mBuffer.get(), + mPixelsSize.Height() * mPixelsSize.Width()); + else + { + for (tools::Long y = 0; y < mPixelsSize.Height(); ++y) + { + const sal_uInt8* src = mBuffer.get() + mScanlineSize * y; + SkConvertGrayToRGBA(dest, src, mPixelsSize.Width()); + dest += mPixelsSize.Width(); + } + } + if (!bitmap.installPixels( + SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(), + kOpaque_SkAlphaType), + data.release(), mPixelsSize.Width() * 4, + [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr)) + abort(); + } + else + { + std::unique_ptr<sal_uInt8[]> data = convertDataBitCount( + mBuffer.get(), mPixelsSize.Width(), mPixelsSize.Height(), mBitCount, mScanlineSize, + mPalette, kN32_SkColorTypeIsBGRA ? BitConvert::BGRA : BitConvert::RGBA); + if (!bitmap.installPixels( + SkImageInfo::MakeS32(mPixelsSize.Width(), mPixelsSize.Height(), + kOpaque_SkAlphaType), + data.release(), mPixelsSize.Width() * 4, + [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr)) + abort(); + } + } + bitmap.setImmutable(); + return bitmap; +} + +// If mEraseColor is set, this is the color to use when the bitmap is used as alpha bitmap. +// E.g. COL_BLACK actually means fully transparent and COL_WHITE means fully opaque. +// This is because the alpha value is set as the color itself, not the alpha of the color. +static SkColor fromEraseColorToAlphaImageColor(Color color) +{ + return SkColorSetARGB(color.GetBlue(), 0, 0, 0); +} + +// SkiaSalBitmap can store data in both the SkImage and our mBuffer, which with large +// images can waste quite a lot of memory. Ideally we should store the data in Skia's +// SkBitmap, but LO wants us to support data formats that Skia doesn't support. +// So try to conserve memory by keeping the data only once in that was the most +// recently wanted storage, and drop the other one. Usually the other one won't be needed +// for a long time, and especially with raster the conversion is usually fast. +// Do this only with raster, to avoid GPU->CPU transfer in GPU mode (exception is 32bit +// builds, where memory is more important). Also don't do this with paletted bitmaps, +// where EnsureBitmapData() would be expensive. +// Ideally SalBitmap should be able to say which bitmap formats it supports +// and VCL code should oblige, which would allow reusing the same data. +bool SkiaSalBitmap::ConserveMemory() const +{ + static bool keepBitmapBuffer = getenv("SAL_SKIA_KEEP_BITMAP_BUFFER") != nullptr; + constexpr bool is32Bit = sizeof(void*) == 4; + // 16MiB bitmap data at least (set to 0 for easy testing). + constexpr tools::Long maxBufferSize = 2000 * 2000 * 4; + return !keepBitmapBuffer && (renderMethodToUse() == RenderRaster || is32Bit) + && mPixelsSize.Height() * mScanlineSize > maxBufferSize + && (mBitCount > 8 || (mBitCount == 8 && mPalette.IsGreyPalette8Bit())); +} + +const sk_sp<SkImage>& SkiaSalBitmap::GetSkImage(DirectImage direct) const +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + if (direct == DirectImage::Yes) + return mImage; + if (mEraseColorSet) + { + if (mImage) + { + assert(imageSize(mImage) == mSize); + return mImage; + } + SkiaZone zone; + sk_sp<SkSurface> surface = createSkSurface( + mSize, mEraseColor.IsTransparent() ? kPremul_SkAlphaType : kOpaque_SkAlphaType); + assert(surface); + surface->getCanvas()->clear(toSkColor(mEraseColor)); + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mImage = makeCheckedImageSnapshot(surface); + SAL_INFO("vcl.skia.trace", "getskimage(" << this << ") from erase color " << mEraseColor); + return mImage; + } + if (mPixelsSize != mSize && !mImage && renderMethodToUse() != RenderRaster) + { + // The bitmap has a pending scaling, but no image. This function would below call GetAsSkBitmap(), + // which would do CPU-based pixel scaling, and then it would get converted to an image. + // Be more efficient, first convert to an image and then the block below will scale on the GPU. + SAL_INFO("vcl.skia.trace", "getskimage(" << this << "): shortcut image scaling " + << mPixelsSize << "->" << mSize); + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + Size savedSize = mSize; + thisPtr->mSize = mPixelsSize; // block scaling + SkiaZone zone; + sk_sp<SkImage> image = createSkImage(GetAsSkBitmap()); + assert(image); + thisPtr->mSize = savedSize; + thisPtr->ResetToSkImage(image); + } + if (mImage) + { + if (imageSize(mImage) != mSize) + { + assert(!mBuffer); // This code should be only called if only mImage holds data. + SkiaZone zone; + sk_sp<SkSurface> surface = createSkSurface(mSize, mImage->imageInfo().alphaType()); + assert(surface); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha + surface->getCanvas()->drawImageRect( + mImage, SkRect::MakeWH(mSize.Width(), mSize.Height()), + makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize, 1), &paint); + SAL_INFO("vcl.skia.trace", "getskimage(" << this << "): image scaled " + << Size(mImage->width(), mImage->height()) + << "->" << mSize << ":" + << static_cast<int>(mScaleQuality)); + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mImage = makeCheckedImageSnapshot(surface); + } + return mImage; + } + SkiaZone zone; + sk_sp<SkImage> image = createSkImage(GetAsSkBitmap()); + assert(image); + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mImage = image; + // The data is now stored both in the SkImage and in our mBuffer, so drop the buffer + // if conserving memory. It'll be converted back by EnsureBitmapData() if needed. + if (ConserveMemory() && mReadAccessCount == 0) + { + SAL_INFO("vcl.skia.trace", "getskimage(" << this << "): dropping buffer"); + thisPtr->ResetToSkImage(mImage); + } + SAL_INFO("vcl.skia.trace", "getskimage(" << this << ")"); + return mImage; +} + +const sk_sp<SkImage>& SkiaSalBitmap::GetAlphaSkImage(DirectImage direct) const +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + if (direct == DirectImage::Yes) + return mAlphaImage; + if (mEraseColorSet) + { + if (mAlphaImage) + { + assert(imageSize(mAlphaImage) == mSize); + return mAlphaImage; + } + SkiaZone zone; + sk_sp<SkSurface> surface = createSkSurface(mSize, kAlpha_8_SkColorType); + assert(surface); + surface->getCanvas()->clear(fromEraseColorToAlphaImageColor(mEraseColor)); + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mAlphaImage = makeCheckedImageSnapshot(surface); + SAL_INFO("vcl.skia.trace", + "getalphaskimage(" << this << ") from erase color " << mEraseColor); + return mAlphaImage; + } + if (mAlphaImage) + { + if (imageSize(mAlphaImage) == mSize) + return mAlphaImage; + } + if (mImage) + { + SkiaZone zone; + const bool scaling = imageSize(mImage) != mSize; + SkPixmap pixmap; + if (mImage->peekPixels(&pixmap)) + { + assert(pixmap.colorType() == kN32_SkColorType); + // In non-GPU mode, convert 32bit data to 8bit alpha, this is faster than + // the SkColorFilter below. Since this is the VCL alpha-vdev alpha, where + // all R,G,B are the same and in fact mean alpha, this means we simply take one + // 8bit channel from the input, and that's the output. + SkBitmap bitmap; + if (!bitmap.installPixels(pixmap)) + abort(); + SkBitmap alphaBitmap; + if (!alphaBitmap.tryAllocPixels(SkImageInfo::MakeA8(bitmap.width(), bitmap.height()))) + abort(); + if (int(bitmap.rowBytes()) == bitmap.width() * 4) + { + SkConvertRGBAToR(alphaBitmap.getAddr8(0, 0), bitmap.getAddr32(0, 0), + bitmap.width() * bitmap.height()); + } + else + { + for (tools::Long y = 0; y < bitmap.height(); ++y) + SkConvertRGBAToR(alphaBitmap.getAddr8(0, y), bitmap.getAddr32(0, y), + bitmap.width()); + } + alphaBitmap.setImmutable(); + sk_sp<SkImage> alphaImage = createSkImage(alphaBitmap); + assert(alphaImage); + SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << ") from raster image"); + // Don't bother here with ConserveMemory(), mImage -> mAlphaImage conversions should + // generally only happen with the separate-alpha-outdev hack, and those bitmaps should + // be temporary. + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mAlphaImage = alphaImage; + // Fix testDelayedScaleAlphaImage unit test + // Do not return the alpha mask if it is awaiting pending scaling. + // Pending scaling has not yet been done at this point since the + // scaling is done in the code following this block. + if (!scaling) + return mAlphaImage; + } + // Move the R channel value to the alpha channel. This seems to be the only + // way to reinterpret data in SkImage as an alpha SkImage without accessing the pixels. + // NOTE: The matrix is 4x5 organized as columns (i.e. each line is a column, not a row). + constexpr SkColorMatrix redToAlpha(0, 0, 0, 0, 0, // R column + 0, 0, 0, 0, 0, // G column + 0, 0, 0, 0, 0, // B column + 1, 0, 0, 0, 0); // A column + SkPaint paint; + paint.setColorFilter(SkColorFilters::Matrix(redToAlpha)); + if (scaling) + assert(!mBuffer); // This code should be only called if only mImage holds data. + sk_sp<SkSurface> surface = createSkSurface(mSize, kAlpha_8_SkColorType); + assert(surface); + paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha + surface->getCanvas()->drawImageRect( + mImage, SkRect::MakeWH(mSize.Width(), mSize.Height()), + scaling ? makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize, 1) + : SkSamplingOptions(), + &paint); + if (scaling) + SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << "): image scaled " + << Size(mImage->width(), mImage->height()) + << "->" << mSize << ":" + << static_cast<int>(mScaleQuality)); + else + SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << ") from image"); + // Don't bother here with ConserveMemory(), mImage -> mAlphaImage conversions should + // generally only happen with the separate-alpha-outdev hack, and those bitmaps should + // be temporary. + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mAlphaImage = makeCheckedImageSnapshot(surface); + return mAlphaImage; + } + SkiaZone zone; + EnsureBitmapData(); + assert(mSize == mPixelsSize); // data has already been scaled if needed + SkBitmap alphaBitmap; + if (mBuffer && mBitCount <= 8) + { + assert(mBuffer.get()); + verify(); + std::unique_ptr<sal_uInt8[]> data + = convertDataBitCount(mBuffer.get(), mSize.Width(), mSize.Height(), mBitCount, + mScanlineSize, mPalette, BitConvert::A8); + if (!alphaBitmap.installPixels( + SkImageInfo::MakeA8(mSize.Width(), mSize.Height()), data.release(), mSize.Width(), + [](void* addr, void*) { delete[] static_cast<sal_uInt8*>(addr); }, nullptr)) + abort(); + alphaBitmap.setImmutable(); + sk_sp<SkImage> image = createSkImage(alphaBitmap); + assert(image); + const_cast<sk_sp<SkImage>&>(mAlphaImage) = image; + } + else + { + sk_sp<SkSurface> surface = createSkSurface(mSize, kAlpha_8_SkColorType); + assert(surface); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha + // Move the R channel value to the alpha channel. This seems to be the only + // way to reinterpret data in SkImage as an alpha SkImage without accessing the pixels. + // NOTE: The matrix is 4x5 organized as columns (i.e. each line is a column, not a row). + constexpr SkColorMatrix redToAlpha(0, 0, 0, 0, 0, // R column + 0, 0, 0, 0, 0, // G column + 0, 0, 0, 0, 0, // B column + 1, 0, 0, 0, 0); // A column + paint.setColorFilter(SkColorFilters::Matrix(redToAlpha)); + surface->getCanvas()->drawImage(GetAsSkBitmap().asImage(), 0, 0, SkSamplingOptions(), + &paint); + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mAlphaImage = makeCheckedImageSnapshot(surface); + } + // The data is now stored both in the SkImage and in our mBuffer, so drop the buffer + // if conserving memory and the conversion back would be simple (it'll be converted back + // by EnsureBitmapData() if needed). + if (ConserveMemory() && mBitCount == 8 && mPalette.IsGreyPalette8Bit() && mReadAccessCount == 0) + { + SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << "): dropping buffer"); + SkiaSalBitmap* thisPtr = const_cast<SkiaSalBitmap*>(this); + thisPtr->mBuffer.reset(); + } + SAL_INFO("vcl.skia.trace", "getalphaskimage(" << this << ")"); + return mAlphaImage; +} + +void SkiaSalBitmap::TryDirectConvertToAlphaNoScaling() +{ + // This is a bit of a hack. Because of the VCL alpha hack where alpha is stored + // separately, we often convert mImage to mAlphaImage to represent the alpha + // channel. If code finds out that there is mImage but no mAlphaImage, + // this will create it from it, without checking for delayed scaling (i.e. + // it is "direct"). + assert(mImage); + assert(!mAlphaImage); + // Set wanted size, trigger conversion. + Size savedSize = mSize; + mSize = imageSize(mImage); + GetAlphaSkImage(); + assert(mAlphaImage); + mSize = savedSize; +} + +// If the bitmap is to be erased, SkShader with the color set is more efficient +// than creating an image filled with the color. +bool SkiaSalBitmap::PreferSkShader() const { return mEraseColorSet; } + +sk_sp<SkShader> SkiaSalBitmap::GetSkShader(const SkSamplingOptions& samplingOptions, + DirectImage direct) const +{ + if (mEraseColorSet) + return SkShaders::Color(toSkColor(mEraseColor)); + return GetSkImage(direct)->makeShader(samplingOptions); +} + +sk_sp<SkShader> SkiaSalBitmap::GetAlphaSkShader(const SkSamplingOptions& samplingOptions, + DirectImage direct) const +{ + if (mEraseColorSet) + return SkShaders::Color(fromEraseColorToAlphaImageColor(mEraseColor)); + return GetAlphaSkImage(direct)->makeShader(samplingOptions); +} + +bool SkiaSalBitmap::IsFullyOpaqueAsAlpha() const +{ + if (!mEraseColorSet) // Set from Erase() or ReleaseBuffer(). + return false; + // If the erase color is set so that this bitmap used as alpha would + // mean a fully opaque alpha mask (= noop), we can skip using it. + return SkColorGetA(fromEraseColorToAlphaImageColor(mEraseColor)) == 255; +} + +SkAlphaType SkiaSalBitmap::alphaType() const +{ + if (mEraseColorSet) + return mEraseColor.IsTransparent() ? kPremul_SkAlphaType : kOpaque_SkAlphaType; +#if SKIA_USE_BITMAP32 + // The bitmap's alpha matters only if SKIA_USE_BITMAP32 is set, otherwise + // the alpha is in a separate bitmap. + if (mBitCount == 32) + return kPremul_SkAlphaType; +#endif + return kOpaque_SkAlphaType; +} + +void SkiaSalBitmap::PerformErase() +{ + if (mPixelsSize.IsEmpty()) + return; + BitmapBuffer* bitmapBuffer = AcquireBuffer(BitmapAccessMode::Write); + if (bitmapBuffer == nullptr) + abort(); + Color fastColor = mEraseColor; + if (!!mPalette) + fastColor = Color(ColorAlpha, mPalette.GetBestIndex(fastColor)); + if (!ImplFastEraseBitmap(*bitmapBuffer, fastColor)) + { + FncSetPixel setPixel = BitmapReadAccess::SetPixelFunction(bitmapBuffer->mnFormat); + assert(bitmapBuffer->mnFormat & ScanlineFormat::TopDown); + // Set first scanline, copy to others. + Scanline scanline = bitmapBuffer->mpBits; + for (tools::Long x = 0; x < bitmapBuffer->mnWidth; ++x) + setPixel(scanline, x, mEraseColor, bitmapBuffer->maColorMask); + for (tools::Long y = 1; y < bitmapBuffer->mnHeight; ++y) + memcpy(scanline + y * bitmapBuffer->mnScanlineSize, scanline, + bitmapBuffer->mnScanlineSize); + } + ReleaseBuffer(bitmapBuffer, BitmapAccessMode::Write, true); +} + +void SkiaSalBitmap::EnsureBitmapData() +{ + if (mEraseColorSet) + { + SkiaZone zone; + assert(mPixelsSize == mSize); + assert(!mBuffer); + CreateBitmapData(); + // Unset now, so that repeated call will return mBuffer. + mEraseColorSet = false; + PerformErase(); + verify(); + SAL_INFO("vcl.skia.trace", + "ensurebitmapdata(" << this << ") from erase color " << mEraseColor); + return; + } + + if (mBuffer) + { + if (mSize == mPixelsSize) + return; + // Pending scaling. Create raster SkImage from the bitmap data + // at the pixel size and then the code below will scale at the correct + // bpp from the image. + SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): pixels to be scaled " + << mPixelsSize << "->" << mSize << ":" + << static_cast<int>(mScaleQuality)); + Size savedSize = mSize; + mSize = mPixelsSize; + ResetToSkImage(SkImages::RasterFromBitmap(GetAsSkBitmap())); + mSize = savedSize; + } + + // Convert from alpha image, if the conversion is simple. + if (mAlphaImage && imageSize(mAlphaImage) == mSize && mBitCount == 8 + && mPalette.IsGreyPalette8Bit()) + { + assert(mAlphaImage->colorType() == kAlpha_8_SkColorType); + SkiaZone zone; + SkBitmap bitmap; + SkPixmap pixmap; + if (mAlphaImage->peekPixels(&pixmap)) + bitmap.installPixels(pixmap); + else + { + if (!bitmap.tryAllocPixels(SkImageInfo::MakeA8(mSize.Width(), mSize.Height()))) + abort(); + SkCanvas canvas(bitmap); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha + canvas.drawImage(mAlphaImage, 0, 0, SkSamplingOptions(), &paint); + canvas.flush(); + } + bitmap.setImmutable(); + ResetPendingScaling(); + CreateBitmapData(); + assert(mBuffer != nullptr); + assert(mPixelsSize == mSize); + if (int(bitmap.rowBytes()) == mScanlineSize) + memcpy(mBuffer.get(), bitmap.getPixels(), mSize.Height() * mScanlineSize); + else + { + for (tools::Long y = 0; y < mSize.Height(); ++y) + { + const uint8_t* src = static_cast<uint8_t*>(bitmap.getAddr(0, y)); + sal_uInt8* dest = mBuffer.get() + mScanlineSize * y; + memcpy(dest, src, mScanlineSize); + } + } + verify(); + // We've created the bitmap data from mAlphaImage, drop the image if conserving memory, + // it'll be converted back if needed. + if (ConserveMemory()) + { + SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): dropping images"); + ResetToBuffer(); + } + SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): from alpha image"); + return; + } + + if (!mImage) + { + // No data at all, create uninitialized data. + CreateBitmapData(); + SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): uninitialized"); + return; + } + // Try to fill mBuffer from mImage. + assert(mImage->colorType() == kN32_SkColorType); + SkiaZone zone; + // If the source image has no alpha, then use no alpha (faster to convert), otherwise + // use kUnpremul_SkAlphaType to make Skia convert from premultiplied alpha when reading + // from the SkImage (the alpha will be ignored if converting to bpp<32 formats, but + // the color channels must be unpremultiplied. Unless bpp==32 and SKIA_USE_BITMAP32, + // in which case use kPremul_SkAlphaType, since SKIA_USE_BITMAP32 implies premultiplied alpha. + SkAlphaType alphaType = kUnpremul_SkAlphaType; + if (mImage->imageInfo().alphaType() == kOpaque_SkAlphaType) + alphaType = kOpaque_SkAlphaType; +#if SKIA_USE_BITMAP32 + if (mBitCount == 32) + alphaType = kPremul_SkAlphaType; +#endif + SkBitmap bitmap; + SkPixmap pixmap; + if (imageSize(mImage) == mSize && mImage->imageInfo().alphaType() == alphaType + && mImage->peekPixels(&pixmap)) + { + bitmap.installPixels(pixmap); + } + else + { + if (!bitmap.tryAllocPixels(SkImageInfo::MakeS32(mSize.Width(), mSize.Height(), alphaType))) + abort(); + SkCanvas canvas(bitmap); + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is, including alpha + if (imageSize(mImage) != mSize) // pending scaling? + { + canvas.drawImageRect(mImage, SkRect::MakeWH(mSize.getWidth(), mSize.getHeight()), + makeSamplingOptions(mScaleQuality, imageSize(mImage), mSize, 1), + &paint); + SAL_INFO("vcl.skia.trace", + "ensurebitmapdata(" << this << "): image scaled " << imageSize(mImage) << "->" + << mSize << ":" << static_cast<int>(mScaleQuality)); + } + else + canvas.drawImage(mImage, 0, 0, SkSamplingOptions(), &paint); + canvas.flush(); + } + bitmap.setImmutable(); + ResetPendingScaling(); + CreateBitmapData(); + assert(mBuffer != nullptr); + assert(mPixelsSize == mSize); + if (mBitCount == 32) + { + if (int(bitmap.rowBytes()) == mScanlineSize) + memcpy(mBuffer.get(), bitmap.getPixels(), mSize.Height() * mScanlineSize); + else + { + for (tools::Long y = 0; y < mSize.Height(); ++y) + { + const uint8_t* src = static_cast<uint8_t*>(bitmap.getAddr(0, y)); + sal_uInt8* dest = mBuffer.get() + mScanlineSize * y; + memcpy(dest, src, mScanlineSize); + } + } + } + else if (mBitCount == 24) // non-paletted + { + if (int(bitmap.rowBytes()) == mSize.Width() * 4 && mSize.Width() * 3 == mScanlineSize) + { + SkConvertRGBAToRGB(mBuffer.get(), bitmap.getAddr32(0, 0), + mSize.Height() * mSize.Width()); + } + else + { + for (tools::Long y = 0; y < mSize.Height(); ++y) + { + const uint32_t* src = bitmap.getAddr32(0, y); + sal_uInt8* dest = mBuffer.get() + mScanlineSize * y; + SkConvertRGBAToRGB(dest, src, mSize.Width()); + } + } + } + else if (mBitCount == 8 && mPalette.IsGreyPalette8Bit()) + { // no actual data conversion, use one color channel as the gray value + if (int(bitmap.rowBytes()) == mSize.Width() * 4 && mSize.Width() * 1 == mScanlineSize) + { + SkConvertRGBAToR(mBuffer.get(), bitmap.getAddr32(0, 0), mSize.Height() * mSize.Width()); + } + else + { + for (tools::Long y = 0; y < mSize.Height(); ++y) + { + const uint32_t* src = bitmap.getAddr32(0, y); + sal_uInt8* dest = mBuffer.get() + mScanlineSize * y; + SkConvertRGBAToR(dest, src, mSize.Width()); + } + } + } + else + { + std::unique_ptr<vcl::ScanlineWriter> pWriter + = vcl::ScanlineWriter::Create(mBitCount, mPalette); + for (tools::Long y = 0; y < mSize.Height(); ++y) + { + const uint8_t* src = static_cast<uint8_t*>(bitmap.getAddr(0, y)); + sal_uInt8* dest = mBuffer.get() + mScanlineSize * y; + pWriter->nextLine(dest); + for (tools::Long x = 0; x < mSize.Width(); ++x) + { + sal_uInt8 r = *src++; + sal_uInt8 g = *src++; + sal_uInt8 b = *src++; + ++src; // skip alpha + pWriter->writeRGB(r, g, b); + } + } + } + verify(); + // We've created the bitmap data from mImage, drop the image if conserving memory, + // it'll be converted back if needed. + if (ConserveMemory()) + { + SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << "): dropping images"); + ResetToBuffer(); + } + SAL_INFO("vcl.skia.trace", "ensurebitmapdata(" << this << ")"); +} + +void SkiaSalBitmap::EnsureBitmapUniqueData() +{ +#ifdef DBG_UTIL + assert(mWriteAccessCount == 0); +#endif + EnsureBitmapData(); + assert(mPixelsSize == mSize); + if (mBuffer.use_count() > 1) + { + sal_uInt32 allocate = mScanlineSize * mSize.Height(); +#ifdef DBG_UTIL + assert(memcmp(mBuffer.get() + allocate, CANARY, sizeof(CANARY)) == 0); + allocate += sizeof(CANARY); +#endif + boost::shared_ptr<sal_uInt8[]> newBuffer = boost::make_shared_noinit<sal_uInt8[]>(allocate); + memcpy(newBuffer.get(), mBuffer.get(), allocate); + mBuffer = newBuffer; + } +} + +void SkiaSalBitmap::ResetToBuffer() +{ + SkiaZone zone; + // This should never be called to drop mImage if that's the only data we have. + assert(mBuffer || !mImage); + mImage.reset(); + mImageImmutable = false; + mAlphaImage.reset(); + mEraseColorSet = false; +} + +void SkiaSalBitmap::ResetToSkImage(sk_sp<SkImage> image) +{ + assert(mReadAccessCount == 0); // can't reset mBuffer if there's a read access pointing to it + SkiaZone zone; + mBuffer.reset(); + // Just to be safe, assume mutability of the image does not change + mImage = image; + mAlphaImage.reset(); + mEraseColorSet = false; +} + +void SkiaSalBitmap::ResetAllData() +{ + assert(mReadAccessCount == 0); + SkiaZone zone; + mBuffer.reset(); + mImage.reset(); + mImageImmutable = false; + mAlphaImage.reset(); + mEraseColorSet = false; + mPixelsSize = mSize; + ComputeScanlineSize(); + DataChanged(); +} + +void SkiaSalBitmap::DataChanged() { InvalidateChecksum(); } + +void SkiaSalBitmap::ResetPendingScaling() +{ + if (mPixelsSize == mSize) + return; + SkiaZone zone; + mScaleQuality = BmpScaleFlag::BestQuality; + mPixelsSize = mSize; + ComputeScanlineSize(); + // Information about the pending scaling has been discarded, so make sure we do not + // keep around any cached images that would still need scaling. + if (mImage && imageSize(mImage) != mSize) + { + mImage.reset(); + mImageImmutable = false; + } + if (mAlphaImage && imageSize(mAlphaImage) != mSize) + mAlphaImage.reset(); +} + +OString SkiaSalBitmap::GetImageKey(DirectImage direct) const +{ + if (mEraseColorSet) + { + std::stringstream ss; + ss << std::hex << std::setfill('0') << std::setw(6) + << static_cast<sal_uInt32>(mEraseColor.GetRGBColor()) << std::setw(2) + << static_cast<int>(mEraseColor.GetAlpha()); + return OString::Concat("E") + ss.str().c_str(); + } + assert(direct == DirectImage::No || mImage); + sk_sp<SkImage> image = GetSkImage(direct); + // In some cases drawing code may try to draw the same content but using + // different bitmaps (even underlying bitmaps), for example canvas apparently + // copies the same things around in tdf#146095. For pixel-based images + // it should be still cheaper to compute a checksum and avoid re-caching. + if (!image->isTextureBacked()) + return OString::Concat("C") + OString::number(getSkImageChecksum(image)); + return OString::Concat("I") + OString::number(image->uniqueID()); +} + +OString SkiaSalBitmap::GetAlphaImageKey(DirectImage direct) const +{ + if (mEraseColorSet) + { + std::stringstream ss; + ss << std::hex << std::setfill('0') << std::setw(2) + << static_cast<int>(SkColorGetA(fromEraseColorToAlphaImageColor(mEraseColor))); + return OString::Concat("E") + ss.str().c_str(); + } + assert(direct == DirectImage::No || mAlphaImage); + sk_sp<SkImage> image = GetAlphaSkImage(direct); + if (!image->isTextureBacked()) + return OString::Concat("C") + OString::number(getSkImageChecksum(image)); + return OString::Concat("I") + OString::number(image->uniqueID()); +} + +void SkiaSalBitmap::dump(const char* file) const +{ + // Use a copy, so that debugging doesn't affect this instance. + SkiaSalBitmap copy; + copy.Create(*this); + SkiaHelper::dump(copy.GetSkImage(), file); +} + +#ifdef DBG_UTIL +void SkiaSalBitmap::verify() const +{ + if (!mBuffer) + return; + // Use mPixelsSize, that describes the size of the actual data. + assert(memcmp(mBuffer.get() + mScanlineSize * mPixelsSize.Height(), CANARY, sizeof(CANARY)) + == 0); +} +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |