blob: 90e9763e12438322fea6e41add483c6f872efc64 [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) 2013 Google 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 "sky/engine/platform/graphics/filters/FilterEffect.h"
#include "sky/engine/platform/graphics/ImageBuffer.h"
#include "sky/engine/platform/graphics/UnacceleratedImageBufferSurface.h"
#include "sky/engine/platform/graphics/filters/Filter.h"
#if HAVE(ARM_NEON_INTRINSICS)
#include <arm_neon.h>
#endif
namespace blink {
static const float kMaxFilterArea = 4096 * 4096;
FilterEffect::FilterEffect(Filter* filter)
: m_alphaImage(false)
, m_filter(filter)
, m_hasX(false)
, m_hasY(false)
, m_hasWidth(false)
, m_hasHeight(false)
, m_clipsToBounds(true)
, m_operatingColorSpace(ColorSpaceLinearRGB)
, m_resultColorSpace(ColorSpaceDeviceRGB)
{
ASSERT(m_filter);
}
FilterEffect::~FilterEffect()
{
}
float FilterEffect::maxFilterArea()
{
return kMaxFilterArea;
}
bool FilterEffect::isFilterSizeValid(const FloatRect& rect)
{
if (rect.width() < 0 || rect.height() < 0
|| (rect.height() * rect.width() > kMaxFilterArea))
return false;
return true;
}
FloatRect FilterEffect::determineAbsolutePaintRect(const FloatRect& originalRequestedRect)
{
FloatRect requestedRect = originalRequestedRect;
// Filters in SVG clip to primitive subregion, while CSS doesn't.
if (m_clipsToBounds)
requestedRect.intersect(maxEffectRect());
// We may be called multiple times if result is used more than once. Return
// quickly if if nothing new is required.
if (absolutePaintRect().contains(enclosingIntRect(requestedRect)))
return requestedRect;
FloatRect inputRect = mapPaintRect(requestedRect, false);
FloatRect inputUnion;
unsigned size = m_inputEffects.size();
for (unsigned i = 0; i < size; ++i)
inputUnion.unite(m_inputEffects.at(i)->determineAbsolutePaintRect(inputRect));
inputUnion = mapPaintRect(inputUnion, true);
if (affectsTransparentPixels() || !size) {
inputUnion = requestedRect;
} else {
// Rect may have inflated. Re-intersect with request.
inputUnion.intersect(requestedRect);
}
addAbsolutePaintRect(inputUnion);
return inputUnion;
}
FloatRect FilterEffect::mapRectRecursive(const FloatRect& rect)
{
FloatRect result;
if (m_inputEffects.size() > 0) {
result = m_inputEffects.at(0)->mapRectRecursive(rect);
for (unsigned i = 1; i < m_inputEffects.size(); ++i)
result.unite(m_inputEffects.at(i)->mapRectRecursive(rect));
} else
result = rect;
return mapRect(result);
}
FloatRect FilterEffect::getSourceRect(const FloatRect& destRect, const FloatRect& destClipRect)
{
FloatRect sourceRect = mapRect(destRect, false);
FloatRect sourceClipRect = mapRect(destClipRect, false);
FloatRect boundaries = filter()->mapLocalRectToAbsoluteRect(effectBoundaries());
if (hasX())
sourceClipRect.setX(boundaries.x());
if (hasY())
sourceClipRect.setY(boundaries.y());
if (hasWidth())
sourceClipRect.setWidth(boundaries.width());
if (hasHeight())
sourceClipRect.setHeight(boundaries.height());
FloatRect result;
if (m_inputEffects.size() > 0) {
result = m_inputEffects.at(0)->getSourceRect(sourceRect, sourceClipRect);
for (unsigned i = 1; i < m_inputEffects.size(); ++i)
result.unite(m_inputEffects.at(i)->getSourceRect(sourceRect, sourceClipRect));
} else {
result = sourceRect;
result.intersect(sourceClipRect);
}
return result;
}
IntRect FilterEffect::requestedRegionOfInputImageData(const IntRect& effectRect) const
{
ASSERT(hasResult());
IntPoint location = m_absolutePaintRect.location();
location.moveBy(-effectRect.location());
return IntRect(location, m_absolutePaintRect.size());
}
IntRect FilterEffect::drawingRegionOfInputImage(const IntRect& srcRect) const
{
return IntRect(IntPoint(srcRect.x() - m_absolutePaintRect.x(),
srcRect.y() - m_absolutePaintRect.y()), srcRect.size());
}
FilterEffect* FilterEffect::inputEffect(unsigned number) const
{
ASSERT_WITH_SECURITY_IMPLICATION(number < m_inputEffects.size());
return m_inputEffects.at(number).get();
}
void FilterEffect::addAbsolutePaintRect(const FloatRect& paintRect)
{
IntRect intPaintRect(enclosingIntRect(paintRect));
if (m_absolutePaintRect.contains(intPaintRect))
return;
intPaintRect.unite(m_absolutePaintRect);
// Make sure we are not holding on to a smaller rendering.
clearResult();
m_absolutePaintRect = intPaintRect;
}
void FilterEffect::apply()
{
// Recursively determine paint rects first, so that we don't redraw images
// if a smaller section is requested first.
determineAbsolutePaintRect(maxEffectRect());
applyRecursive();
}
void FilterEffect::applyRecursive()
{
if (hasResult())
return;
unsigned size = m_inputEffects.size();
for (unsigned i = 0; i < size; ++i) {
FilterEffect* in = m_inputEffects.at(i).get();
in->applyRecursive();
if (!in->hasResult())
return;
// Convert input results to the current effect's color space.
transformResultColorSpace(in, i);
}
setResultColorSpace(m_operatingColorSpace);
if (!isFilterSizeValid(m_absolutePaintRect))
return;
if (!mayProduceInvalidPreMultipliedPixels()) {
for (unsigned i = 0; i < size; ++i)
inputEffect(i)->correctFilterResultIfNeeded();
}
applySoftware();
}
void FilterEffect::forceValidPreMultipliedPixels()
{
// Must operate on pre-multiplied results; other formats cannot have invalid pixels.
if (!m_premultipliedImageResult)
return;
Uint8ClampedArray* imageArray = m_premultipliedImageResult.get();
unsigned char* 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) {
unsigned char* 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
unsigned char 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()
{
if (m_imageBufferResult)
m_imageBufferResult.clear();
if (m_unmultipliedImageResult)
m_unmultipliedImageResult.clear();
if (m_premultipliedImageResult)
m_premultipliedImageResult.clear();
m_absolutePaintRect = IntRect();
for (int i = 0; i < 4; i++) {
m_imageFilters[i] = nullptr;
}
}
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::asImageBuffer()
{
if (!hasResult())
return 0;
if (m_imageBufferResult)
return m_imageBufferResult.get();
OwnPtr<ImageBufferSurface> surface;
surface = adoptPtr(new UnacceleratedImageBufferSurface(m_absolutePaintRect.size()));
m_imageBufferResult = ImageBuffer::create(surface.release());
if (!m_imageBufferResult)
return 0;
IntRect destinationRect(IntPoint(), m_absolutePaintRect.size());
if (m_premultipliedImageResult)
m_imageBufferResult->putByteArray(Premultiplied, m_premultipliedImageResult.get(), destinationRect.size(), destinationRect, IntPoint());
else
m_imageBufferResult->putByteArray(Unmultiplied, m_unmultipliedImageResult.get(), destinationRect.size(), destinationRect, IntPoint());
return m_imageBufferResult.get();
}
PassRefPtr<Uint8ClampedArray> FilterEffect::asUnmultipliedImage(const IntRect& rect)
{
ASSERT(isFilterSizeValid(rect));
RefPtr<Uint8ClampedArray> imageData = Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4);
copyUnmultipliedImage(imageData.get(), rect);
return imageData.release();
}
PassRefPtr<Uint8ClampedArray> FilterEffect::asPremultipliedImage(const IntRect& rect)
{
ASSERT(isFilterSizeValid(rect));
RefPtr<Uint8ClampedArray> imageData = Uint8ClampedArray::createUninitialized(rect.width() * rect.height() * 4);
copyPremultipliedImage(imageData.get(), rect);
return imageData.release();
}
inline void FilterEffect::copyImageBytes(Uint8ClampedArray* source, Uint8ClampedArray* destination, const IntRect& rect)
{
// Initialize the destination to transparent black, if not entirely covered by the source.
if (rect.x() < 0 || rect.y() < 0 || rect.maxX() > m_absolutePaintRect.width() || rect.maxY() > m_absolutePaintRect.height())
memset(destination->data(), 0, destination->length());
// Early return if the rect does not intersect with the source.
if (rect.maxX() <= 0 || rect.maxY() <= 0 || rect.x() >= m_absolutePaintRect.width() || rect.y() >= m_absolutePaintRect.height())
return;
int xOrigin = rect.x();
int xDest = 0;
if (xOrigin < 0) {
xDest = -xOrigin;
xOrigin = 0;
}
int xEnd = rect.maxX();
if (xEnd > m_absolutePaintRect.width())
xEnd = m_absolutePaintRect.width();
int yOrigin = rect.y();
int yDest = 0;
if (yOrigin < 0) {
yDest = -yOrigin;
yOrigin = 0;
}
int yEnd = rect.maxY();
if (yEnd > m_absolutePaintRect.height())
yEnd = m_absolutePaintRect.height();
int size = (xEnd - xOrigin) * 4;
int destinationScanline = rect.width() * 4;
int sourceScanline = m_absolutePaintRect.width() * 4;
unsigned char *destinationPixel = destination->data() + ((yDest * rect.width()) + xDest) * 4;
unsigned char *sourcePixel = source->data() + ((yOrigin * m_absolutePaintRect.width()) + xOrigin) * 4;
while (yOrigin < yEnd) {
memcpy(destinationPixel, sourcePixel, size);
destinationPixel += destinationScanline;
sourcePixel += sourceScanline;
++yOrigin;
}
}
void FilterEffect::copyUnmultipliedImage(Uint8ClampedArray* destination, const IntRect& rect)
{
ASSERT(hasResult());
if (!m_unmultipliedImageResult) {
// We prefer a conversion from the image buffer.
if (m_imageBufferResult)
m_unmultipliedImageResult = m_imageBufferResult->getImageData(Unmultiplied, IntRect(IntPoint(), m_absolutePaintRect.size()));
else {
ASSERT(isFilterSizeValid(m_absolutePaintRect));
m_unmultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4);
unsigned char* sourceComponent = m_premultipliedImageResult->data();
unsigned char* destinationComponent = m_unmultipliedImageResult->data();
unsigned char* end = sourceComponent + (m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4);
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;
}
}
}
copyImageBytes(m_unmultipliedImageResult.get(), destination, rect);
}
void FilterEffect::copyPremultipliedImage(Uint8ClampedArray* destination, const IntRect& rect)
{
ASSERT(hasResult());
if (!m_premultipliedImageResult) {
// We prefer a conversion from the image buffer.
if (m_imageBufferResult)
m_premultipliedImageResult = m_imageBufferResult->getImageData(Premultiplied, IntRect(IntPoint(), m_absolutePaintRect.size()));
else {
ASSERT(isFilterSizeValid(m_absolutePaintRect));
m_premultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4);
unsigned char* sourceComponent = m_unmultipliedImageResult->data();
unsigned char* destinationComponent = m_premultipliedImageResult->data();
unsigned char* end = sourceComponent + (m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4);
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;
}
}
}
copyImageBytes(m_premultipliedImageResult.get(), destination, rect);
}
ImageBuffer* FilterEffect::createImageBufferResult()
{
// Only one result type is allowed.
ASSERT(!hasResult());
ASSERT(isFilterSizeValid(m_absolutePaintRect));
OwnPtr<ImageBufferSurface> surface;
surface = adoptPtr(new UnacceleratedImageBufferSurface(m_absolutePaintRect.size()));
m_imageBufferResult = ImageBuffer::create(surface.release());
return m_imageBufferResult.get();
}
Uint8ClampedArray* FilterEffect::createUnmultipliedImageResult()
{
// Only one result type is allowed.
ASSERT(!hasResult());
ASSERT(isFilterSizeValid(m_absolutePaintRect));
if (m_absolutePaintRect.isEmpty())
return 0;
m_unmultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4);
return m_unmultipliedImageResult.get();
}
Uint8ClampedArray* FilterEffect::createPremultipliedImageResult()
{
// Only one result type is allowed.
ASSERT(!hasResult());
ASSERT(isFilterSizeValid(m_absolutePaintRect));
if (m_absolutePaintRect.isEmpty())
return 0;
m_premultipliedImageResult = Uint8ClampedArray::createUninitialized(m_absolutePaintRect.width() * m_absolutePaintRect.height() * 4);
return m_premultipliedImageResult.get();
}
Color FilterEffect::adaptColorToOperatingColorSpace(const Color& deviceColor)
{
// |deviceColor| is assumed to be DeviceRGB.
return ColorSpaceUtilities::convertColor(deviceColor, operatingColorSpace());
}
void FilterEffect::transformResultColorSpace(ColorSpace dstColorSpace)
{
if (!hasResult() || dstColorSpace == m_resultColorSpace)
return;
// FIXME: We can avoid this potentially unnecessary ImageBuffer conversion by adding
// color space transform support for the {pre,un}multiplied arrays.
asImageBuffer()->transformColorSpace(m_resultColorSpace, dstColorSpace);
m_resultColorSpace = dstColorSpace;
if (m_unmultipliedImageResult)
m_unmultipliedImageResult.clear();
if (m_premultipliedImageResult)
m_premultipliedImageResult.clear();
}
TextStream& FilterEffect::externalRepresentation(TextStream& ts, int) 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.
return ts;
}
FloatRect FilterEffect::determineFilterPrimitiveSubregion(DetermineSubregionFlags flags)
{
Filter* filter = this->filter();
ASSERT(filter);
// 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(flags);
for (unsigned i = 1; i < numberOfInputEffects; ++i)
subregion.unite(inputEffect(i)->determineFilterPrimitiveSubregion(flags));
} else {
subregion = filter->filterRegion();
}
// After calling determineFilterPrimitiveSubregion on the target effect, reset the subregion again for <feTile>.
if (filterEffectType() == FilterEffectTypeTile)
subregion = filter->filterRegion();
if (flags & MapRectForward) {
// mapRect works on absolute rectangles.
subregion = filter->mapAbsoluteRectToLocalRect(mapRect(
filter->mapLocalRectToAbsoluteRect(subregion)));
}
FloatRect 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);
FloatRect absoluteSubregion = filter->mapLocalRectToAbsoluteRect(subregion);
// Clip every filter effect to the filter region.
if (flags & ClipToFilterRegion) {
absoluteSubregion.intersect(filter->absoluteFilterRegion());
}
setMaxEffectRect(absoluteSubregion);
return subregion;
}
PassRefPtr<SkImageFilter> FilterEffect::createImageFilter(SkiaImageFilterBuilder* builder)
{
return nullptr;
}
PassRefPtr<SkImageFilter> FilterEffect::createImageFilterWithoutValidation(SkiaImageFilterBuilder* builder)
{
return createImageFilter(builder);
}
SkImageFilter::CropRect FilterEffect::getCropRect(const FloatSize& cropOffset) const
{
FloatRect rect = filter()->filterRegion();
uint32_t flags = 0;
FloatRect boundaries = effectBoundaries();
boundaries.move(cropOffset);
if (hasX()) {
rect.setX(boundaries.x());
flags |= SkImageFilter::CropRect::kHasLeft_CropEdge;
flags |= SkImageFilter::CropRect::kHasWidth_CropEdge;
}
if (hasY()) {
rect.setY(boundaries.y());
flags |= SkImageFilter::CropRect::kHasTop_CropEdge;
flags |= SkImageFilter::CropRect::kHasHeight_CropEdge;
}
if (hasWidth()) {
rect.setWidth(boundaries.width());
flags |= SkImageFilter::CropRect::kHasWidth_CropEdge;
}
if (hasHeight()) {
rect.setHeight(boundaries.height());
flags |= SkImageFilter::CropRect::kHasHeight_CropEdge;
}
rect.scale(filter()->absoluteTransform().a(), filter()->absoluteTransform().d());
return SkImageFilter::CropRect(rect, flags);
}
static int getImageFilterIndex(ColorSpace colorSpace, bool requiresPMColorValidation)
{
// Map the (colorspace, bool) tuple to an integer index as follows:
// 0 == linear colorspace, no PM validation
// 1 == device colorspace, no PM validation
// 2 == linear colorspace, PM validation
// 3 == device colorspace, PM validation
return (colorSpace == ColorSpaceLinearRGB ? 0x1 : 0x0) | (requiresPMColorValidation ? 0x2 : 0x0);
}
SkImageFilter* FilterEffect::getImageFilter(ColorSpace colorSpace, bool requiresPMColorValidation) const
{
int index = getImageFilterIndex(colorSpace, requiresPMColorValidation);
return m_imageFilters[index].get();
}
void FilterEffect::setImageFilter(ColorSpace colorSpace, bool requiresPMColorValidation, PassRefPtr<SkImageFilter> imageFilter)
{
int index = getImageFilterIndex(colorSpace, requiresPMColorValidation);
m_imageFilters[index] = imageFilter;
}
} // namespace blink