blob: e16321814261dd0648629445d920273a92d66379 [file] [log] [blame]
/*
* Copyright (C) 2004, 2005, 2006, 2007 Nikolas Zimmermann <zimmermann@kde.org>
* Copyright (C) 2004, 2005 Rob Buis <buis@kde.org>
* Copyright (C) 2005 Eric Seidel <eric@webkit.org>
* Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
* Copyright (C) Research In Motion Limited 2010. All rights reserved.
* 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 "config.h"
#include "core/platform/graphics/filters/FEMorphology.h"
#include "core/platform/graphics/filters/Filter.h"
#include "core/platform/graphics/filters/SkiaImageFilterBuilder.h"
#include "core/rendering/RenderTreeAsText.h"
#include "platform/text/TextStream.h"
#include "wtf/ParallelJobs.h"
#include "wtf/Uint8ClampedArray.h"
#include "wtf/Vector.h"
#include "SkMorphologyImageFilter.h"
using std::min;
using std::max;
namespace WebCore {
FEMorphology::FEMorphology(Filter* filter, MorphologyOperatorType type, float radiusX, float radiusY)
: FilterEffect(filter)
, m_type(type)
, m_radiusX(radiusX)
, m_radiusY(radiusY)
{
}
PassRefPtr<FEMorphology> FEMorphology::create(Filter* filter, MorphologyOperatorType type, float radiusX, float radiusY)
{
return adoptRef(new FEMorphology(filter, type, radiusX, radiusY));
}
MorphologyOperatorType FEMorphology::morphologyOperator() const
{
return m_type;
}
bool FEMorphology::setMorphologyOperator(MorphologyOperatorType type)
{
if (m_type == type)
return false;
m_type = type;
return true;
}
float FEMorphology::radiusX() const
{
return m_radiusX;
}
bool FEMorphology::setRadiusX(float radiusX)
{
if (m_radiusX == radiusX)
return false;
m_radiusX = radiusX;
return true;
}
float FEMorphology::radiusY() const
{
return m_radiusY;
}
void FEMorphology::determineAbsolutePaintRect()
{
FloatRect paintRect = mapRect(inputEffect(0)->absolutePaintRect());
if (clipsToBounds())
paintRect.intersect(maxEffectRect());
else
paintRect.unite(maxEffectRect());
setAbsolutePaintRect(enclosingIntRect(paintRect));
}
FloatRect FEMorphology::mapRect(const FloatRect& rect, bool)
{
FloatRect result = rect;
result.inflateX(filter()->applyHorizontalScale(m_radiusX));
result.inflateY(filter()->applyVerticalScale(m_radiusY));
return result;
}
bool FEMorphology::setRadiusY(float radiusY)
{
if (m_radiusY == radiusY)
return false;
m_radiusY = radiusY;
return true;
}
void FEMorphology::platformApplyGeneric(PaintingData* paintingData, int yStart, int yEnd)
{
Uint8ClampedArray* srcPixelArray = paintingData->srcPixelArray;
Uint8ClampedArray* dstPixelArray = paintingData->dstPixelArray;
const int width = paintingData->width;
const int height = paintingData->height;
const int effectWidth = width * 4;
const int radiusX = paintingData->radiusX;
const int radiusY = paintingData->radiusY;
Vector<unsigned char> extrema;
for (int y = yStart; y < yEnd; ++y) {
int extremaStartY = max(0, y - radiusY);
int extremaEndY = min(height - 1, y + radiusY);
for (unsigned int clrChannel = 0; clrChannel < 4; ++clrChannel) {
extrema.clear();
// Compute extremas for each columns
for (int x = 0; x <= radiusX; ++x) {
unsigned char columnExtrema = srcPixelArray->item(extremaStartY * effectWidth + 4 * x + clrChannel);
for (int eY = extremaStartY + 1; eY < extremaEndY; ++eY) {
unsigned char pixel = srcPixelArray->item(eY * effectWidth + 4 * x + clrChannel);
if ((m_type == FEMORPHOLOGY_OPERATOR_ERODE && pixel <= columnExtrema)
|| (m_type == FEMORPHOLOGY_OPERATOR_DILATE && pixel >= columnExtrema)) {
columnExtrema = pixel;
}
}
extrema.append(columnExtrema);
}
// Kernel is filled, get extrema of next column
for (int x = 0; x < width; ++x) {
const int endX = min(x + radiusX, width - 1);
unsigned char columnExtrema = srcPixelArray->item(extremaStartY * effectWidth + endX * 4 + clrChannel);
for (int i = extremaStartY + 1; i <= extremaEndY; ++i) {
unsigned char pixel = srcPixelArray->item(i * effectWidth + endX * 4 + clrChannel);
if ((m_type == FEMORPHOLOGY_OPERATOR_ERODE && pixel <= columnExtrema)
|| (m_type == FEMORPHOLOGY_OPERATOR_DILATE && pixel >= columnExtrema))
columnExtrema = pixel;
}
if (x - radiusX >= 0)
extrema.remove(0);
if (x + radiusX <= width)
extrema.append(columnExtrema);
unsigned char entireExtrema = extrema[0];
for (unsigned kernelIndex = 1; kernelIndex < extrema.size(); ++kernelIndex) {
if ((m_type == FEMORPHOLOGY_OPERATOR_ERODE && extrema[kernelIndex] <= entireExtrema)
|| (m_type == FEMORPHOLOGY_OPERATOR_DILATE && extrema[kernelIndex] >= entireExtrema))
entireExtrema = extrema[kernelIndex];
}
dstPixelArray->set(y * effectWidth + 4 * x + clrChannel, entireExtrema);
}
}
}
}
void FEMorphology::platformApplyWorker(PlatformApplyParameters* param)
{
param->filter->platformApplyGeneric(param->paintingData, param->startY, param->endY);
}
void FEMorphology::platformApply(PaintingData* paintingData)
{
int optimalThreadNumber = (paintingData->width * paintingData->height) / s_minimalArea;
if (optimalThreadNumber > 1) {
ParallelJobs<PlatformApplyParameters> parallelJobs(&WebCore::FEMorphology::platformApplyWorker, optimalThreadNumber);
int numOfThreads = parallelJobs.numberOfJobs();
if (numOfThreads > 1) {
// Split the job into "jobSize"-sized jobs but there a few jobs that need to be slightly larger since
// jobSize * jobs < total size. These extras are handled by the remainder "jobsWithExtra".
const int jobSize = paintingData->height / numOfThreads;
const int jobsWithExtra = paintingData->height % numOfThreads;
int currentY = 0;
for (int job = numOfThreads - 1; job >= 0; --job) {
PlatformApplyParameters& param = parallelJobs.parameter(job);
param.filter = this;
param.startY = currentY;
currentY += job < jobsWithExtra ? jobSize + 1 : jobSize;
param.endY = currentY;
param.paintingData = paintingData;
}
parallelJobs.execute();
return;
}
// Fallback to single thread model
}
platformApplyGeneric(paintingData, 0, paintingData->height);
}
void FEMorphology::applySoftware()
{
FilterEffect* in = inputEffect(0);
Uint8ClampedArray* dstPixelArray = createPremultipliedImageResult();
if (!dstPixelArray)
return;
setIsAlphaImage(in->isAlphaImage());
if (m_radiusX <= 0 || m_radiusY <= 0) {
dstPixelArray->zeroFill();
return;
}
Filter* filter = this->filter();
int radiusX = static_cast<int>(floorf(filter->applyHorizontalScale(m_radiusX)));
int radiusY = static_cast<int>(floorf(filter->applyVerticalScale(m_radiusY)));
IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect());
RefPtr<Uint8ClampedArray> srcPixelArray = in->asPremultipliedImage(effectDrawingRect);
PaintingData paintingData;
paintingData.srcPixelArray = srcPixelArray.get();
paintingData.dstPixelArray = dstPixelArray;
paintingData.width = effectDrawingRect.width();
paintingData.height = effectDrawingRect.height();
paintingData.radiusX = min(effectDrawingRect.width() - 1, radiusX);
paintingData.radiusY = min(effectDrawingRect.height() - 1, radiusY);
platformApply(&paintingData);
}
bool FEMorphology::applySkia()
{
ImageBuffer* resultImage = createImageBufferResult();
if (!resultImage)
return false;
FilterEffect* in = inputEffect(0);
IntRect drawingRegion = drawingRegionOfInputImage(in->absolutePaintRect());
setIsAlphaImage(in->isAlphaImage());
float radiusX = filter()->applyHorizontalScale(m_radiusX);
float radiusY = filter()->applyVerticalScale(m_radiusY);
RefPtr<Image> image = in->asImageBuffer()->copyImage(DontCopyBackingStore);
SkPaint paint;
GraphicsContext* dstContext = resultImage->context();
if (m_type == FEMORPHOLOGY_OPERATOR_DILATE)
paint.setImageFilter(new SkDilateImageFilter(radiusX, radiusY))->unref();
else if (m_type == FEMORPHOLOGY_OPERATOR_ERODE)
paint.setImageFilter(new SkErodeImageFilter(radiusX, radiusY))->unref();
dstContext->saveLayer(0, &paint);
dstContext->drawImage(image.get(), drawingRegion.location(), CompositeCopy);
dstContext->restoreLayer();
return true;
}
PassRefPtr<SkImageFilter> FEMorphology::createImageFilter(SkiaImageFilterBuilder* builder)
{
RefPtr<SkImageFilter> input(builder->build(inputEffect(0), operatingColorSpace()));
SkScalar radiusX = SkFloatToScalar(filter()->applyHorizontalScale(m_radiusX));
SkScalar radiusY = SkFloatToScalar(filter()->applyVerticalScale(m_radiusY));
SkImageFilter::CropRect rect = getCropRect(builder->cropOffset());
if (m_type == FEMORPHOLOGY_OPERATOR_DILATE)
return adoptRef(new SkDilateImageFilter(radiusX, radiusY, input.get(), &rect));
return adoptRef(new SkErodeImageFilter(radiusX, radiusY, input.get(), &rect));
}
static TextStream& operator<<(TextStream& ts, const MorphologyOperatorType& type)
{
switch (type) {
case FEMORPHOLOGY_OPERATOR_UNKNOWN:
ts << "UNKNOWN";
break;
case FEMORPHOLOGY_OPERATOR_ERODE:
ts << "ERODE";
break;
case FEMORPHOLOGY_OPERATOR_DILATE:
ts << "DILATE";
break;
}
return ts;
}
TextStream& FEMorphology::externalRepresentation(TextStream& ts, int indent) const
{
writeIndent(ts, indent);
ts << "[feMorphology";
FilterEffect::externalRepresentation(ts);
ts << " operator=\"" << morphologyOperator() << "\" "
<< "radius=\"" << radiusX() << ", " << radiusY() << "\"]\n";
inputEffect(0)->externalRepresentation(ts, indent + 1);
return ts;
}
} // namespace WebCore