blob: fb67754e4fc5bbca51c348ec7f63e4cc927db108 [file] [log] [blame]
/*
* Copyright (C) 2008 Alex Mathews <possessedpenguinbob@gmail.com>
* Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
* Copyright (C) 2012 University of Szeged
* Copyright (C) 2015-2021 Apple Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include "FilterEffect.h"
#include "Color.h"
#include "Filter.h"
#include "GeometryUtilities.h"
#include "GraphicsContext.h"
#include "ImageBuffer.h"
#include "Logging.h"
#include "PixelBuffer.h"
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/TypedArrayInlines.h>
#include <wtf/text/TextStream.h>
#if HAVE(ARM_NEON_INTRINSICS)
#include <arm_neon.h>
#endif
#if USE(ACCELERATE)
#include <Accelerate/Accelerate.h>
#endif
namespace WebCore {
FilterEffect::FilterEffect(Filter& filter, Type type)
: m_filter(filter)
, m_filterEffectClassType(type)
{
}
FilterEffect::~FilterEffect() = default;
void FilterEffect::determineAbsolutePaintRect()
{
m_absolutePaintRect = IntRect();
for (auto& effect : m_inputEffects)
m_absolutePaintRect.unite(effect->absolutePaintRect());
clipAbsolutePaintRect();
}
void FilterEffect::clipAbsolutePaintRect()
{
// Filters in SVG clip to primitive subregion, while CSS doesn't.
if (m_clipsToBounds)
m_absolutePaintRect.intersect(enclosingIntRect(m_maxEffectRect));
else
m_absolutePaintRect.unite(enclosingIntRect(m_maxEffectRect));
}
FloatPoint FilterEffect::mapPointFromUserSpaceToBuffer(FloatPoint userSpacePoint) const
{
FloatPoint absolutePoint = mapPoint(userSpacePoint, m_filterPrimitiveSubregion, m_absoluteUnclippedSubregion);
absolutePoint.moveBy(-m_absolutePaintRect.location());
return absolutePoint;
}
IntRect FilterEffect::requestedRegionOfInputPixelBuffer(const IntRect& effectRect) const
{
IntPoint location = m_absolutePaintRect.location();
location.moveBy(-effectRect.location());
return IntRect(location, m_absolutePaintRect.size());
}
FloatRect FilterEffect::drawingRegionOfInputImage(const IntRect& srcRect) const
{
ASSERT(hasResult());
FloatSize scale;
ImageBuffer::clampedSize(m_absolutePaintRect.size(), scale);
AffineTransform transform;
transform.scale(scale).translate(-m_absolutePaintRect.location());
return transform.mapRect(srcRect);
}
FloatRect FilterEffect::determineFilterPrimitiveSubregion()
{
// FETile, FETurbulence, FEFlood don't have input effects, take the filter region as unite rect.
FloatRect subregion;
if (unsigned numberOfInputEffects = inputEffects().size()) {
subregion = inputEffect(0)->determineFilterPrimitiveSubregion();
for (unsigned i = 1; i < numberOfInputEffects; ++i) {
auto inputPrimitiveSubregion = inputEffect(i)->determineFilterPrimitiveSubregion();
subregion.unite(inputPrimitiveSubregion);
}
} else
subregion = m_filter.filterRegionInUserSpace();
// After calling determineFilterPrimitiveSubregion on the target effect, reset the subregion again for <feTile>.
if (filterEffectType() == FilterEffectTypeTile)
subregion = m_filter.filterRegionInUserSpace();
auto boundaries = effectBoundaries();
if (hasX())
subregion.setX(boundaries.x());
if (hasY())
subregion.setY(boundaries.y());
if (hasWidth())
subregion.setWidth(boundaries.width());
if (hasHeight())
subregion.setHeight(boundaries.height());
setFilterPrimitiveSubregion(subregion);
auto absoluteSubregion = m_filter.absoluteTransform().mapRect(subregion);
auto filterResolution = m_filter.filterResolution();
absoluteSubregion.scale(filterResolution);
// Save this before clipping so we can use it to map lighting points from user space to buffer coordinates.
setUnclippedAbsoluteSubregion(absoluteSubregion);
// Clip every filter effect to the filter region.
auto absoluteScaledFilterRegion = m_filter.filterRegion();
absoluteScaledFilterRegion.scale(filterResolution);
absoluteSubregion.intersect(absoluteScaledFilterRegion);
setMaxEffectRect(absoluteSubregion);
return subregion;
}
FilterEffect* FilterEffect::inputEffect(unsigned number) const
{
ASSERT_WITH_SECURITY_IMPLICATION(number < m_inputEffects.size());
return m_inputEffects.at(number).get();
}
static unsigned collectEffects(const FilterEffect*effect, HashSet<const FilterEffect*>& allEffects)
{
allEffects.add(effect);
unsigned size = effect->numberOfEffectInputs();
for (unsigned i = 0; i < size; ++i) {
FilterEffect* in = effect->inputEffect(i);
collectEffects(in, allEffects);
}
return allEffects.size();
}
unsigned FilterEffect::totalNumberOfEffectInputs() const
{
HashSet<const FilterEffect*> allEffects;
return collectEffects(this, allEffects);
}
void FilterEffect::apply()
{
if (hasResult())
return;
unsigned size = m_inputEffects.size();
for (unsigned i = 0; i < size; ++i) {
FilterEffect* in = m_inputEffects.at(i).get();
in->apply();
if (!in->hasResult())
return;
// Convert input results to the current effect's color space.
transformResultColorSpace(in, i);
}
determineAbsolutePaintRect();
setResultColorSpace(m_operatingColorSpace);
LOG_WITH_STREAM(Filters, stream << "FilterEffect " << filterName() << " " << this << " apply():\n filterPrimitiveSubregion " << m_filterPrimitiveSubregion << "\n effectBoundaries " << m_effectBoundaries << "\n absoluteUnclippedSubregion " << m_absoluteUnclippedSubregion << "\n absolutePaintRect " << m_absolutePaintRect << "\n maxEffectRect " << m_maxEffectRect << "\n filter scale " << m_filter.filterScale() << "\n filter resolution " << m_filter.filterResolution());
if (m_absolutePaintRect.isEmpty() || ImageBuffer::sizeNeedsClamping(m_absolutePaintRect.size()))
return;
if (requiresValidPreMultipliedPixels()) {
for (unsigned i = 0; i < size; ++i)
inputEffect(i)->correctFilterResultIfNeeded();
}
// Add platform specific apply functions here and return earlier.
platformApplySoftware();
}
void FilterEffect::forceValidPreMultipliedPixels()
{
// Must operate on pre-multiplied results; other formats cannot have invalid pixels.
if (!m_premultipliedImageResult)
return;
auto& imageArray = m_premultipliedImageResult->data();
uint8_t* pixelData = imageArray.data();
int pixelArrayLength = imageArray.length();
// We must have four bytes per pixel, and complete pixels
ASSERT(!(pixelArrayLength % 4));
#if HAVE(ARM_NEON_INTRINSICS)
if (pixelArrayLength >= 64) {
uint8_t* lastPixel = pixelData + (pixelArrayLength & ~0x3f);
do {
// Increments pixelData by 64.
uint8x16x4_t sixteenPixels = vld4q_u8(pixelData);
sixteenPixels.val[0] = vminq_u8(sixteenPixels.val[0], sixteenPixels.val[3]);
sixteenPixels.val[1] = vminq_u8(sixteenPixels.val[1], sixteenPixels.val[3]);
sixteenPixels.val[2] = vminq_u8(sixteenPixels.val[2], sixteenPixels.val[3]);
vst4q_u8(pixelData, sixteenPixels);
pixelData += 64;
} while (pixelData < lastPixel);
pixelArrayLength &= 0x3f;
if (!pixelArrayLength)
return;
}
#endif
int numPixels = pixelArrayLength / 4;
// Iterate over each pixel, checking alpha and adjusting color components if necessary
while (--numPixels >= 0) {
// Alpha is the 4th byte in a pixel
uint8_t a = *(pixelData + 3);
// Clamp each component to alpha, and increment the pixel location
for (int i = 0; i < 3; ++i) {
if (*pixelData > a)
*pixelData = a;
++pixelData;
}
// Increment for alpha
++pixelData;
}
}
void FilterEffect::clearResult()
{
m_imageBufferResult = nullptr;
m_unmultipliedImageResult = std::nullopt;
m_premultipliedImageResult = std::nullopt;
}
void FilterEffect::clearResultsRecursive()
{
// Clear all results, regardless that the current effect has
// a result. Can be used if an effect is in an erroneous state.
if (hasResult())
clearResult();
unsigned size = m_inputEffects.size();
for (unsigned i = 0; i < size; ++i)
m_inputEffects.at(i).get()->clearResultsRecursive();
}
ImageBuffer* FilterEffect::imageBufferResult()
{
LOG_WITH_STREAM(Filters, stream << "FilterEffect " << filterName() << " " << this << " imageBufferResult(). Existing image buffer " << m_imageBufferResult.get() << " m_premultipliedImageResult " << m_premultipliedImageResult << " m_unmultipliedImageResult " << m_unmultipliedImageResult);
if (!hasResult())
return nullptr;
if (m_imageBufferResult)
return m_imageBufferResult.get();
m_imageBufferResult = ImageBuffer::create(m_absolutePaintRect.size(), m_filter.renderingMode(), m_filter.filterScale(), m_resultColorSpace, PixelFormat::BGRA8);
if (!m_imageBufferResult)
return nullptr;
IntRect destinationRect(IntPoint(), m_absolutePaintRect.size());
if (m_premultipliedImageResult)
m_imageBufferResult->putPixelBuffer(*m_premultipliedImageResult, destinationRect);
else
m_imageBufferResult->putPixelBuffer(*m_unmultipliedImageResult, destinationRect);
return m_imageBufferResult.get();
}
RefPtr<Uint8ClampedArray> FilterEffect::unmultipliedResult(const IntRect& rect, std::optional<DestinationColorSpace> colorSpace)
{
IntSize scaledSize(rect.size());
ASSERT(!ImageBuffer::sizeNeedsClamping(scaledSize));
scaledSize.scale(m_filter.filterScale());
auto checkedArea = scaledSize.area<RecordOverflow>() * 4;
if (checkedArea.hasOverflowed())
return nullptr;
auto pixelArray = Uint8ClampedArray::tryCreateUninitialized(checkedArea);
if (!pixelArray)
return nullptr;
copyUnmultipliedResult(*pixelArray, rect, colorSpace);
return pixelArray;
}
RefPtr<Uint8ClampedArray> FilterEffect::premultipliedResult(const IntRect& rect, std::optional<DestinationColorSpace> colorSpace)
{
IntSize scaledSize(rect.size());
ASSERT(!ImageBuffer::sizeNeedsClamping(scaledSize));
scaledSize.scale(m_filter.filterScale());
auto checkedArea = scaledSize.area<RecordOverflow>() * 4;
if (checkedArea.hasOverflowed())
return nullptr;
auto pixelArray = Uint8ClampedArray::tryCreateUninitialized(checkedArea);
if (!pixelArray)
return nullptr;
copyPremultipliedResult(*pixelArray, rect, colorSpace);
return pixelArray;
}
void FilterEffect::copyImageBytes(const Uint8ClampedArray& source, Uint8ClampedArray& destination, const IntRect& rect) const
{
IntRect scaledRect(rect);
scaledRect.scale(m_filter.filterScale());
IntSize scaledPaintSize(m_absolutePaintRect.size());
scaledPaintSize.scale(m_filter.filterScale());
// Initialize the destination to transparent black, if not entirely covered by the source.
if (scaledRect.x() < 0 || scaledRect.y() < 0 || scaledRect.maxX() > scaledPaintSize.width() || scaledRect.maxY() > scaledPaintSize.height())
memset(destination.data(), 0, destination.length());
// Early return if the rect does not intersect with the source.
if (scaledRect.maxX() <= 0 || scaledRect.maxY() <= 0 || scaledRect.x() >= scaledPaintSize.width() || scaledRect.y() >= scaledPaintSize.height())
return;
int xOrigin = scaledRect.x();
int xDest = 0;
if (xOrigin < 0) {
xDest = -xOrigin;
xOrigin = 0;
}
int xEnd = scaledRect.maxX();
if (xEnd > scaledPaintSize.width())
xEnd = scaledPaintSize.width();
int yOrigin = scaledRect.y();
int yDest = 0;
if (yOrigin < 0) {
yDest = -yOrigin;
yOrigin = 0;
}
int yEnd = scaledRect.maxY();
if (yEnd > scaledPaintSize.height())
yEnd = scaledPaintSize.height();
int size = (xEnd - xOrigin) * 4;
int destinationScanline = scaledRect.width() * 4;
int sourceScanline = scaledPaintSize.width() * 4;
uint8_t* destinationPixel = destination.data() + ((yDest * scaledRect.width()) + xDest) * 4;
const uint8_t* sourcePixel = source.data() + ((yOrigin * scaledPaintSize.width()) + xOrigin) * 4;
while (yOrigin < yEnd) {
memcpy(destinationPixel, sourcePixel, size);
destinationPixel += destinationScanline;
sourcePixel += sourceScanline;
++yOrigin;
}
}
static void copyPremultiplyingAlpha(const Uint8ClampedArray& source, Uint8ClampedArray& destination, const IntSize& inputSize)
{
#if USE(ACCELERATE)
size_t rowBytes = inputSize.width() * 4;
vImage_Buffer src;
src.width = inputSize.width();
src.height = inputSize.height();
src.rowBytes = rowBytes;
src.data = reinterpret_cast<void*>(source.data());
vImage_Buffer dest;
dest.width = inputSize.width();
dest.height = inputSize.height();
dest.rowBytes = rowBytes;
dest.data = reinterpret_cast<void*>(destination.data());
vImagePremultiplyData_RGBA8888(&src, &dest, kvImageNoFlags);
#else
const uint8_t* sourceComponent = source.data();
const uint8_t* end = sourceComponent + (inputSize.area() * 4).value();
uint8_t* destinationComponent = destination.data();
while (sourceComponent < end) {
int alpha = sourceComponent[3];
destinationComponent[0] = static_cast<int>(sourceComponent[0]) * alpha / 255;
destinationComponent[1] = static_cast<int>(sourceComponent[1]) * alpha / 255;
destinationComponent[2] = static_cast<int>(sourceComponent[2]) * alpha / 255;
destinationComponent[3] = alpha;
sourceComponent += 4;
destinationComponent += 4;
}
#endif
}
static void copyUnpremultiplyingAlpha(const Uint8ClampedArray& source, Uint8ClampedArray& destination, const IntSize& inputSize)
{
#if USE(ACCELERATE)
size_t rowBytes = inputSize.width() * 4;
vImage_Buffer src;
src.width = inputSize.width();
src.height = inputSize.height();
src.rowBytes = rowBytes;
src.data = reinterpret_cast<void*>(source.data());
vImage_Buffer dest;
dest.width = inputSize.width();
dest.height = inputSize.height();
dest.rowBytes = rowBytes;
dest.data = reinterpret_cast<void*>(destination.data());
vImageUnpremultiplyData_RGBA8888(&src, &dest, kvImageNoFlags);
#else
const uint8_t* sourceComponent = source.data();
const uint8_t* end = sourceComponent + (inputSize.area() * 4).value();
uint8_t* destinationComponent = destination.data();
while (sourceComponent < end) {
int alpha = sourceComponent[3];
if (alpha) {
destinationComponent[0] = static_cast<int>(sourceComponent[0]) * 255 / alpha;
destinationComponent[1] = static_cast<int>(sourceComponent[1]) * 255 / alpha;
destinationComponent[2] = static_cast<int>(sourceComponent[2]) * 255 / alpha;
} else {
destinationComponent[0] = 0;
destinationComponent[1] = 0;
destinationComponent[2] = 0;
}
destinationComponent[3] = alpha;
sourceComponent += 4;
destinationComponent += 4;
}
#endif
}
std::optional<PixelBuffer> FilterEffect::convertPixelBufferToColorSpace(const DestinationColorSpace& targetColorSpace, PixelBuffer& pixelBuffer)
{
// FIXME: Using an ImageBuffer to perform the color space conversion is unnecessary. We can do it directly.
IntRect destinationRect(IntPoint(), pixelBuffer.size());
destinationRect.scale(1 / m_filter.filterScale());
FloatSize clampedSize = ImageBuffer::clampedSize(destinationRect.size());
// Create an ImageBuffer to store incoming PixelBuffer
auto buffer = ImageBuffer::create(clampedSize, m_filter.renderingMode(), m_filter.filterScale(), operatingColorSpace(), PixelFormat::BGRA8);
if (!buffer)
return std::nullopt;
buffer->putPixelBuffer(pixelBuffer, destinationRect);
return convertImageBufferToColorSpace(targetColorSpace, *buffer, destinationRect, pixelBuffer.format().alphaFormat);
}
std::optional<PixelBuffer> FilterEffect::convertImageBufferToColorSpace(const DestinationColorSpace& targetColorSpace, ImageBuffer& inputBuffer, const IntRect& rect, AlphaPremultiplication outputAlphaFormat)
{
// FIXME: This can be done more directly using PixelBufferConversion.
FloatSize clampedSize = ImageBuffer::clampedSize(rect.size());
// Create an ImageBuffer with the correct color space and utilize CG to handle color space conversion
auto convertedBuffer = ImageBuffer::create(clampedSize, m_filter.renderingMode(), m_filter.filterScale(), targetColorSpace, PixelFormat::BGRA8);
if (!convertedBuffer)
return std::nullopt;
// Color space conversion happens internally when drawing from one image buffer to another
convertedBuffer->context().drawImageBuffer(inputBuffer, rect);
PixelBufferFormat format { outputAlphaFormat, PixelFormat::RGBA8, targetColorSpace };
return convertedBuffer->getPixelBuffer(format, rect);
}
void FilterEffect::copyConvertedImageBufferToDestination(Uint8ClampedArray& destination, const DestinationColorSpace& colorSpace, AlphaPremultiplication outputFormat, const IntRect& destRect)
{
// Converts the data stored in m_imageBufferResult, and save to destination
auto convertedPixelBuffer = convertImageBufferToColorSpace(colorSpace, *m_imageBufferResult, { IntPoint(), m_absolutePaintRect.size() }, outputFormat);
if (!convertedPixelBuffer)
return;
copyImageBytes(convertedPixelBuffer->data(), destination, destRect);
}
void FilterEffect::copyConvertedPixelBufferToDestination(Uint8ClampedArray& destination, PixelBuffer& pixelBuffer, const DestinationColorSpace& colorSpace, const IntRect& destRect)
{
// Converts the data stored in m_unmultipliedImageResult/m_premultipliedImageResult,
// whichever isn't null, and save to destination
auto convertedPixelBuffer = convertPixelBufferToColorSpace(colorSpace, pixelBuffer);
if (!convertedPixelBuffer)
return;
copyImageBytes(convertedPixelBuffer->data(), destination, destRect);
}
void FilterEffect::copyUnmultipliedResult(Uint8ClampedArray& destination, const IntRect& rect, std::optional<DestinationColorSpace> colorSpace)
{
ASSERT(hasResult());
LOG_WITH_STREAM(Filters, stream << "FilterEffect " << filterName() << " " << this << " copyUnmultipliedResult(). Existing image buffer " << m_imageBufferResult.get() << " m_premultipliedImageResult " << m_premultipliedImageResult << " m_unmultipliedImageResult " << m_unmultipliedImageResult);
if (!m_unmultipliedImageResult) {
// We prefer a conversion from the image buffer.
if (m_imageBufferResult) {
if (requiresPixelBufferColorSpaceConversion(colorSpace)) {
copyConvertedImageBufferToDestination(destination, *colorSpace, AlphaPremultiplication::Unpremultiplied, rect);
return;
}
ASSERT(m_imageBufferResult->colorSpace() == m_resultColorSpace);
PixelBufferFormat format { AlphaPremultiplication::Unpremultiplied, PixelFormat::RGBA8, m_resultColorSpace };
m_unmultipliedImageResult = m_imageBufferResult->getPixelBuffer(format, { IntPoint(), m_absolutePaintRect.size() });
if (!m_unmultipliedImageResult)
return;
} else {
IntSize inputSize(m_absolutePaintRect.size());
ASSERT(!ImageBuffer::sizeNeedsClamping(inputSize));
inputSize.scale(m_filter.filterScale());
ASSERT(m_premultipliedImageResult->format().colorSpace == m_resultColorSpace);
PixelBufferFormat format { AlphaPremultiplication::Unpremultiplied, PixelFormat::RGBA8, m_resultColorSpace };
m_unmultipliedImageResult = PixelBuffer::tryCreate(format, inputSize);
if (!m_unmultipliedImageResult)
return;
copyUnpremultiplyingAlpha(m_premultipliedImageResult->data(), m_unmultipliedImageResult->data(), inputSize);
}
}
if (requiresPixelBufferColorSpaceConversion(colorSpace)) {
copyConvertedPixelBufferToDestination(destination, *m_unmultipliedImageResult, *colorSpace, rect);
return;
}
copyImageBytes(m_unmultipliedImageResult->data(), destination, rect);
}
void FilterEffect::copyPremultipliedResult(Uint8ClampedArray& destination, const IntRect& rect, std::optional<DestinationColorSpace> colorSpace)
{
ASSERT(hasResult());
LOG_WITH_STREAM(Filters, stream << "FilterEffect " << filterName() << " " << this << " copyPremultipliedResult(). Existing image buffer " << m_imageBufferResult.get() << " m_premultipliedImageResult " << m_premultipliedImageResult << " m_unmultipliedImageResult " << m_unmultipliedImageResult);
if (!m_premultipliedImageResult) {
// We prefer a conversion from the image buffer.
if (m_imageBufferResult) {
if (requiresPixelBufferColorSpaceConversion(colorSpace)) {
copyConvertedImageBufferToDestination(destination, *colorSpace, AlphaPremultiplication::Premultiplied, rect);
return;
}
ASSERT(m_imageBufferResult->colorSpace() == m_resultColorSpace);
PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, m_resultColorSpace };
m_premultipliedImageResult = m_imageBufferResult->getPixelBuffer(format, { IntPoint(), m_absolutePaintRect.size() });
if (!m_premultipliedImageResult)
return;
} else {
IntSize inputSize(m_absolutePaintRect.size());
ASSERT(!ImageBuffer::sizeNeedsClamping(inputSize));
inputSize.scale(m_filter.filterScale());
ASSERT(m_unmultipliedImageResult->format().colorSpace == m_resultColorSpace);
PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, m_resultColorSpace };
m_premultipliedImageResult = PixelBuffer::tryCreate(format, inputSize);
if (!m_premultipliedImageResult)
return;
copyPremultiplyingAlpha(m_unmultipliedImageResult->data(), m_premultipliedImageResult->data(), inputSize);
}
}
if (requiresPixelBufferColorSpaceConversion(colorSpace)) {
copyConvertedPixelBufferToDestination(destination, *m_premultipliedImageResult, *colorSpace, rect);
return;
}
copyImageBytes(m_premultipliedImageResult->data(), destination, rect);
}
ImageBuffer* FilterEffect::createImageBufferResult()
{
LOG(Filters, "FilterEffect %s %p createImageBufferResult %dx%d", filterName(), this, m_absolutePaintRect.size().width(), m_absolutePaintRect.size().height());
// Only one result type is allowed.
ASSERT(!hasResult());
if (m_absolutePaintRect.isEmpty())
return nullptr;
FloatSize clampedSize = ImageBuffer::clampedSize(m_absolutePaintRect.size());
m_imageBufferResult = ImageBuffer::create(clampedSize, m_filter.renderingMode(), m_filter.filterScale(), m_resultColorSpace, PixelFormat::BGRA8);
return m_imageBufferResult.get();
}
std::optional<PixelBuffer>& FilterEffect::createUnmultipliedImageResult()
{
LOG(Filters, "FilterEffect %s %p createUnmultipliedImageResult", filterName(), this);
// Only one result type is allowed.
ASSERT(!hasResult());
ASSERT(!m_unmultipliedImageResult);
if (m_absolutePaintRect.isEmpty())
return m_unmultipliedImageResult;
IntSize resultSize(m_absolutePaintRect.size());
ASSERT(!ImageBuffer::sizeNeedsClamping(resultSize));
resultSize.scale(m_filter.filterScale());
PixelBufferFormat format { AlphaPremultiplication::Unpremultiplied, PixelFormat::RGBA8, m_resultColorSpace };
m_unmultipliedImageResult = PixelBuffer::tryCreate(format, resultSize);
return m_unmultipliedImageResult;
}
std::optional<PixelBuffer>& FilterEffect::createPremultipliedImageResult()
{
LOG(Filters, "FilterEffect %s %p createPremultipliedImageResult", filterName(), this);
// Only one result type is allowed.
ASSERT(!hasResult());
ASSERT(!m_premultipliedImageResult);
if (m_absolutePaintRect.isEmpty())
return m_premultipliedImageResult;
IntSize resultSize(m_absolutePaintRect.size());
ASSERT(!ImageBuffer::sizeNeedsClamping(resultSize));
resultSize.scale(m_filter.filterScale());
PixelBufferFormat format { AlphaPremultiplication::Premultiplied, PixelFormat::RGBA8, m_resultColorSpace };
m_premultipliedImageResult = PixelBuffer::tryCreate(format, resultSize);
return m_premultipliedImageResult;
}
bool FilterEffect::requiresPixelBufferColorSpaceConversion(std::optional<DestinationColorSpace> destinationColorSpace)
{
#if USE(CG)
// This function determines whether we need the step of an extra color space conversion
// We only need extra color conversion when 1) color space is different in the input
// AND 2) the filter is manipulating raw pixels
return destinationColorSpace && resultColorSpace() != *destinationColorSpace;
#else
// Additional color space conversion is not needed on non-CG
UNUSED_PARAM(destinationColorSpace);
return false;
#endif
}
void FilterEffect::transformResultColorSpace(const DestinationColorSpace& destinationColorSpace)
{
#if USE(CG)
// CG handles color space adjustments internally.
UNUSED_PARAM(destinationColorSpace);
#else
if (!hasResult() || destinationColorSpace == m_resultColorSpace)
return;
// FIXME: We can avoid this potentially unnecessary ImageBuffer conversion by adding
// color space transform support for the {pre,un}multiplied arrays.
imageBufferResult()->transformToColorSpace(destinationColorSpace);
m_resultColorSpace = destinationColorSpace;
m_unmultipliedImageResult = std::nullopt;
m_premultipliedImageResult = std::nullopt;
#endif
}
TextStream& FilterEffect::externalRepresentation(TextStream& ts, RepresentationType representationType) const
{
// FIXME: We should dump the subRegions of the filter primitives here later. This isn't
// possible at the moment, because we need more detailed informations from the target object.
if (representationType == RepresentationType::Debugging) {
TextStream::IndentScope indentScope(ts);
ts.dumpProperty("alpha image", m_alphaImage);
ts.dumpProperty("operating colorspace", m_operatingColorSpace);
ts.dumpProperty("result colorspace", m_resultColorSpace);
ts << "\n" << indent;
}
return ts;
}
TextStream& operator<<(TextStream& ts, const FilterEffect& filter)
{
// Use a new stream because we want multiline mode for logging filters.
TextStream filterStream;
filter.externalRepresentation(filterStream, FilterEffect::RepresentationType::Debugging);
return ts << filterStream.release();
}
} // namespace WebCore