mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-24 10:45:42 +00:00
3d3a05d1ee
According to bug 1345853 comment 5, tn said: You probably want to return whatever was drawn there regardless of the DrawResult. SVGMaskFrame has the same problem. Keep in mind that DrawResult is only reporting on how drawing of any images went, not the drawing of anything else. Also looking over the patches from bug 1258510 I see a couple places where BAD_ARGS is returned if the transform matrix is singular. We would want to return SUCCESS in that case I think, because we drew what we were instructed to draw. MozReview-Commit-ID: 5XcDuKQwXTJ --HG-- extra : rebase_source : ba764df599844c9eb179736f61d6c7f6ee46c9fc
406 lines
14 KiB
C++
406 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* 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/. */
|
|
|
|
// Main header first:
|
|
#include "nsSVGMaskFrame.h"
|
|
|
|
// Keep others in (case-insensitive) order:
|
|
#include "gfx2DGlue.h"
|
|
#include "gfxContext.h"
|
|
#include "mozilla/gfx/2D.h"
|
|
#include "mozilla/RefPtr.h"
|
|
#include "nsSVGEffects.h"
|
|
#include "mozilla/dom/SVGMaskElement.h"
|
|
#ifdef BUILD_ARM_NEON
|
|
#include "mozilla/arm.h"
|
|
#include "nsSVGMaskFrameNEON.h"
|
|
#endif
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
using namespace mozilla::gfx;
|
|
using namespace mozilla::image;
|
|
|
|
// c = n / 255
|
|
// c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4)) * 255 + 0.5
|
|
static const uint8_t gsRGBToLinearRGBMap[256] = {
|
|
0, 0, 0, 0, 0, 0, 0, 1,
|
|
1, 1, 1, 1, 1, 1, 1, 1,
|
|
1, 1, 2, 2, 2, 2, 2, 2,
|
|
2, 2, 3, 3, 3, 3, 3, 3,
|
|
4, 4, 4, 4, 4, 5, 5, 5,
|
|
5, 6, 6, 6, 6, 7, 7, 7,
|
|
8, 8, 8, 8, 9, 9, 9, 10,
|
|
10, 10, 11, 11, 12, 12, 12, 13,
|
|
13, 13, 14, 14, 15, 15, 16, 16,
|
|
17, 17, 17, 18, 18, 19, 19, 20,
|
|
20, 21, 22, 22, 23, 23, 24, 24,
|
|
25, 25, 26, 27, 27, 28, 29, 29,
|
|
30, 30, 31, 32, 32, 33, 34, 35,
|
|
35, 36, 37, 37, 38, 39, 40, 41,
|
|
41, 42, 43, 44, 45, 45, 46, 47,
|
|
48, 49, 50, 51, 51, 52, 53, 54,
|
|
55, 56, 57, 58, 59, 60, 61, 62,
|
|
63, 64, 65, 66, 67, 68, 69, 70,
|
|
71, 72, 73, 74, 76, 77, 78, 79,
|
|
80, 81, 82, 84, 85, 86, 87, 88,
|
|
90, 91, 92, 93, 95, 96, 97, 99,
|
|
100, 101, 103, 104, 105, 107, 108, 109,
|
|
111, 112, 114, 115, 116, 118, 119, 121,
|
|
122, 124, 125, 127, 128, 130, 131, 133,
|
|
134, 136, 138, 139, 141, 142, 144, 146,
|
|
147, 149, 151, 152, 154, 156, 157, 159,
|
|
161, 163, 164, 166, 168, 170, 171, 173,
|
|
175, 177, 179, 181, 183, 184, 186, 188,
|
|
190, 192, 194, 196, 198, 200, 202, 204,
|
|
206, 208, 210, 212, 214, 216, 218, 220,
|
|
222, 224, 226, 229, 231, 233, 235, 237,
|
|
239, 242, 244, 246, 248, 250, 253, 255
|
|
};
|
|
|
|
static void
|
|
ComputesRGBLuminanceMask(const uint8_t *aSourceData,
|
|
int32_t aSourceStride,
|
|
uint8_t *aDestData,
|
|
int32_t aDestStride,
|
|
const IntSize &aSize,
|
|
float aOpacity)
|
|
{
|
|
#ifdef BUILD_ARM_NEON
|
|
if (mozilla::supports_neon()) {
|
|
ComputesRGBLuminanceMask_NEON(aSourceData, aSourceStride,
|
|
aDestData, aDestStride,
|
|
aSize, aOpacity);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
|
|
int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
|
|
int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
|
|
int32_t sourceOffset = aSourceStride - 4 * aSize.width;
|
|
const uint8_t *sourcePixel = aSourceData;
|
|
int32_t destOffset = aDestStride - aSize.width;
|
|
uint8_t *destPixel = aDestData;
|
|
|
|
for (int32_t y = 0; y < aSize.height; y++) {
|
|
for (int32_t x = 0; x < aSize.width; x++) {
|
|
uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
|
|
|
|
if (a) {
|
|
*destPixel = (redFactor * sourcePixel[GFX_ARGB32_OFFSET_R] +
|
|
greenFactor * sourcePixel[GFX_ARGB32_OFFSET_G] +
|
|
blueFactor * sourcePixel[GFX_ARGB32_OFFSET_B]) >> 8;
|
|
} else {
|
|
*destPixel = 0;
|
|
}
|
|
sourcePixel += 4;
|
|
destPixel++;
|
|
}
|
|
sourcePixel += sourceOffset;
|
|
destPixel += destOffset;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ComputeLinearRGBLuminanceMask(const uint8_t *aSourceData,
|
|
int32_t aSourceStride,
|
|
uint8_t *aDestData,
|
|
int32_t aDestStride,
|
|
const IntSize &aSize,
|
|
float aOpacity)
|
|
{
|
|
int32_t redFactor = 55 * aOpacity; // 255 * 0.2125 * opacity
|
|
int32_t greenFactor = 183 * aOpacity; // 255 * 0.7154 * opacity
|
|
int32_t blueFactor = 18 * aOpacity; // 255 * 0.0721
|
|
int32_t sourceOffset = aSourceStride - 4 * aSize.width;
|
|
const uint8_t *sourcePixel = aSourceData;
|
|
int32_t destOffset = aDestStride - aSize.width;
|
|
uint8_t *destPixel = aDestData;
|
|
|
|
for (int32_t y = 0; y < aSize.height; y++) {
|
|
for (int32_t x = 0; x < aSize.width; x++) {
|
|
uint8_t a = sourcePixel[GFX_ARGB32_OFFSET_A];
|
|
|
|
// unpremultiply
|
|
if (a) {
|
|
if (a == 255) {
|
|
/* sRGB -> linearRGB -> intensity */
|
|
*destPixel =
|
|
static_cast<uint8_t>
|
|
((gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_R]] *
|
|
redFactor +
|
|
gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_G]] *
|
|
greenFactor +
|
|
gsRGBToLinearRGBMap[sourcePixel[GFX_ARGB32_OFFSET_B]] *
|
|
blueFactor) >> 8);
|
|
} else {
|
|
uint8_t tempPixel[4];
|
|
tempPixel[GFX_ARGB32_OFFSET_B] =
|
|
(255 * sourcePixel[GFX_ARGB32_OFFSET_B]) / a;
|
|
tempPixel[GFX_ARGB32_OFFSET_G] =
|
|
(255 * sourcePixel[GFX_ARGB32_OFFSET_G]) / a;
|
|
tempPixel[GFX_ARGB32_OFFSET_R] =
|
|
(255 * sourcePixel[GFX_ARGB32_OFFSET_R]) / a;
|
|
|
|
/* sRGB -> linearRGB -> intensity */
|
|
*destPixel =
|
|
static_cast<uint8_t>
|
|
(((gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_R]] *
|
|
redFactor +
|
|
gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_G]] *
|
|
greenFactor +
|
|
gsRGBToLinearRGBMap[tempPixel[GFX_ARGB32_OFFSET_B]] *
|
|
blueFactor) >> 8) * (a / 255.0f));
|
|
}
|
|
} else {
|
|
*destPixel = 0;
|
|
}
|
|
sourcePixel += 4;
|
|
destPixel++;
|
|
}
|
|
sourcePixel += sourceOffset;
|
|
destPixel += destOffset;
|
|
}
|
|
}
|
|
|
|
static void
|
|
ComputeAlphaMask(const uint8_t *aSourceData,
|
|
int32_t aSourceStride,
|
|
uint8_t *aDestData,
|
|
int32_t aDestStride,
|
|
const IntSize &aSize,
|
|
float aOpacity)
|
|
{
|
|
int32_t sourceOffset = aSourceStride - 4 * aSize.width;
|
|
const uint8_t *sourcePixel = aSourceData;
|
|
int32_t destOffset = aDestStride - aSize.width;
|
|
uint8_t *destPixel = aDestData;
|
|
|
|
for (int32_t y = 0; y < aSize.height; y++) {
|
|
for (int32_t x = 0; x < aSize.width; x++) {
|
|
*destPixel = sourcePixel[GFX_ARGB32_OFFSET_A] * aOpacity;
|
|
sourcePixel += 4;
|
|
destPixel++;
|
|
}
|
|
sourcePixel += sourceOffset;
|
|
destPixel += destOffset;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------
|
|
// Implementation
|
|
|
|
nsIFrame*
|
|
NS_NewSVGMaskFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
|
|
{
|
|
return new (aPresShell) nsSVGMaskFrame(aContext);
|
|
}
|
|
|
|
NS_IMPL_FRAMEARENA_HELPERS(nsSVGMaskFrame)
|
|
|
|
mozilla::Pair<DrawResult, RefPtr<SourceSurface>>
|
|
nsSVGMaskFrame::GetMaskForMaskedFrame(MaskParams& aParams)
|
|
{
|
|
// If the flag is set when we get here, it means this mask frame
|
|
// has already been used painting the current mask, and the document
|
|
// has a mask reference loop.
|
|
if (mInUse) {
|
|
NS_WARNING("Mask loop detected!");
|
|
return MakePair(DrawResult::SUCCESS, RefPtr<SourceSurface>());
|
|
}
|
|
AutoMaskReferencer maskRef(this);
|
|
|
|
gfxRect maskArea = GetMaskArea(aParams.maskedFrame);
|
|
gfxContext* context = aParams.ctx;
|
|
// Get the clip extents in device space:
|
|
// Minimizing the mask surface extents (using both the current clip extents
|
|
// and maskArea) is important for performance.
|
|
context->Save();
|
|
nsSVGUtils::SetClipRect(context, aParams.toUserSpace, maskArea);
|
|
context->SetMatrix(gfxMatrix());
|
|
gfxRect maskSurfaceRect = context->GetClipExtents();
|
|
maskSurfaceRect.RoundOut();
|
|
context->Restore();
|
|
|
|
bool resultOverflows;
|
|
IntSize maskSurfaceSize =
|
|
nsSVGUtils::ConvertToSurfaceSize(maskSurfaceRect.Size(), &resultOverflows);
|
|
|
|
if (resultOverflows || maskSurfaceSize.IsEmpty()) {
|
|
// Return value other then DrawResult::SUCCESS, so the caller can skip
|
|
// painting the masked frame(aParams.maskedFrame).
|
|
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
|
|
}
|
|
|
|
RefPtr<DrawTarget> maskDT =
|
|
gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
|
|
maskSurfaceSize, SurfaceFormat::B8G8R8A8);
|
|
|
|
if (!maskDT || !maskDT->IsValid()) {
|
|
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
|
|
}
|
|
|
|
gfxMatrix maskSurfaceMatrix =
|
|
context->CurrentMatrix() * gfxMatrix::Translation(-maskSurfaceRect.TopLeft());
|
|
|
|
RefPtr<gfxContext> tmpCtx = gfxContext::CreateOrNull(maskDT);
|
|
MOZ_ASSERT(tmpCtx); // already checked the draw target above
|
|
tmpCtx->SetMatrix(maskSurfaceMatrix);
|
|
|
|
mMatrixForChildren = GetMaskTransform(aParams.maskedFrame) *
|
|
aParams.toUserSpace;
|
|
DrawResult result = DrawResult::SUCCESS;
|
|
|
|
for (nsIFrame* kid = mFrames.FirstChild(); kid;
|
|
kid = kid->GetNextSibling()) {
|
|
// The CTM of each frame referencing us can be different
|
|
nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
|
|
if (SVGFrame) {
|
|
SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED);
|
|
}
|
|
gfxMatrix m = mMatrixForChildren;
|
|
if (kid->GetContent()->IsSVGElement()) {
|
|
m = static_cast<nsSVGElement*>(kid->GetContent())->
|
|
PrependLocalTransformsTo(m, eUserSpaceToParent);
|
|
}
|
|
result &= nsSVGUtils::PaintFrameWithEffects(kid, *tmpCtx, m);
|
|
}
|
|
|
|
RefPtr<SourceSurface> maskSnapshot = maskDT->Snapshot();
|
|
if (!maskSnapshot) {
|
|
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
|
|
}
|
|
RefPtr<DataSourceSurface> maskSurface = maskSnapshot->GetDataSurface();
|
|
DataSourceSurface::MappedSurface map;
|
|
if (!maskSurface->Map(DataSourceSurface::MapType::READ, &map)) {
|
|
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
|
|
}
|
|
|
|
// Create alpha channel mask for output
|
|
RefPtr<DataSourceSurface> destMaskSurface =
|
|
Factory::CreateDataSourceSurface(maskSurfaceSize, SurfaceFormat::A8);
|
|
if (!destMaskSurface) {
|
|
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
|
|
}
|
|
DataSourceSurface::MappedSurface destMap;
|
|
if (!destMaskSurface->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
|
|
return MakePair(DrawResult::TEMPORARY_ERROR, RefPtr<SourceSurface>());
|
|
}
|
|
|
|
uint8_t maskType;
|
|
if (aParams.maskMode == NS_STYLE_MASK_MODE_MATCH_SOURCE) {
|
|
maskType = StyleSVGReset()->mMaskType;
|
|
} else {
|
|
maskType = aParams.maskMode == NS_STYLE_MASK_MODE_LUMINANCE
|
|
? NS_STYLE_MASK_TYPE_LUMINANCE : NS_STYLE_MASK_TYPE_ALPHA;
|
|
}
|
|
|
|
if (maskType == NS_STYLE_MASK_TYPE_LUMINANCE) {
|
|
if (StyleSVG()->mColorInterpolation ==
|
|
NS_STYLE_COLOR_INTERPOLATION_LINEARRGB) {
|
|
ComputeLinearRGBLuminanceMask(map.mData, map.mStride,
|
|
destMap.mData, destMap.mStride,
|
|
maskSurfaceSize, aParams.opacity);
|
|
} else {
|
|
ComputesRGBLuminanceMask(map.mData, map.mStride,
|
|
destMap.mData, destMap.mStride,
|
|
maskSurfaceSize, aParams.opacity);
|
|
}
|
|
} else {
|
|
ComputeAlphaMask(map.mData, map.mStride,
|
|
destMap.mData, destMap.mStride,
|
|
maskSurfaceSize, aParams.opacity);
|
|
}
|
|
|
|
maskSurface->Unmap();
|
|
destMaskSurface->Unmap();
|
|
|
|
// Moz2D transforms in the opposite direction to Thebes
|
|
if (!maskSurfaceMatrix.Invert()) {
|
|
return MakePair(DrawResult::SUCCESS, RefPtr<SourceSurface>());
|
|
}
|
|
|
|
*aParams.maskTransform = ToMatrix(maskSurfaceMatrix);
|
|
RefPtr<SourceSurface> surface = destMaskSurface.forget();
|
|
return MakePair(result, Move(surface));
|
|
}
|
|
|
|
gfxRect
|
|
nsSVGMaskFrame::GetMaskArea(nsIFrame* aMaskedFrame)
|
|
{
|
|
SVGMaskElement *maskElem = static_cast<SVGMaskElement*>(mContent);
|
|
|
|
uint16_t units =
|
|
maskElem->mEnumAttributes[SVGMaskElement::MASKUNITS].GetAnimValue();
|
|
gfxRect bbox;
|
|
if (units == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
|
|
bbox = nsSVGUtils::GetBBox(aMaskedFrame);
|
|
}
|
|
|
|
// Bounds in the user space of aMaskedFrame
|
|
gfxRect maskArea = nsSVGUtils::GetRelativeRect(units,
|
|
&maskElem->mLengthAttributes[SVGMaskElement::ATTR_X],
|
|
bbox, aMaskedFrame);
|
|
|
|
return maskArea;
|
|
}
|
|
|
|
nsresult
|
|
nsSVGMaskFrame::AttributeChanged(int32_t aNameSpaceID,
|
|
nsIAtom* aAttribute,
|
|
int32_t aModType)
|
|
{
|
|
if (aNameSpaceID == kNameSpaceID_None &&
|
|
(aAttribute == nsGkAtoms::x ||
|
|
aAttribute == nsGkAtoms::y ||
|
|
aAttribute == nsGkAtoms::width ||
|
|
aAttribute == nsGkAtoms::height||
|
|
aAttribute == nsGkAtoms::maskUnits ||
|
|
aAttribute == nsGkAtoms::maskContentUnits)) {
|
|
nsSVGEffects::InvalidateDirectRenderingObservers(this);
|
|
}
|
|
|
|
return nsSVGContainerFrame::AttributeChanged(aNameSpaceID,
|
|
aAttribute, aModType);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
nsSVGMaskFrame::Init(nsIContent* aContent,
|
|
nsContainerFrame* aParent,
|
|
nsIFrame* aPrevInFlow)
|
|
{
|
|
NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::mask),
|
|
"Content is not an SVG mask");
|
|
|
|
nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
nsIAtom *
|
|
nsSVGMaskFrame::GetType() const
|
|
{
|
|
return nsGkAtoms::svgMaskFrame;
|
|
}
|
|
|
|
gfxMatrix
|
|
nsSVGMaskFrame::GetCanvasTM()
|
|
{
|
|
return mMatrixForChildren;
|
|
}
|
|
|
|
gfxMatrix
|
|
nsSVGMaskFrame::GetMaskTransform(nsIFrame* aMaskedFrame)
|
|
{
|
|
SVGMaskElement *content = static_cast<SVGMaskElement*>(mContent);
|
|
|
|
nsSVGEnum* maskContentUnits =
|
|
&content->mEnumAttributes[SVGMaskElement::MASKCONTENTUNITS];
|
|
|
|
return nsSVGUtils::AdjustMatrixForUnits(gfxMatrix(), maskContentUnits,
|
|
aMaskedFrame);
|
|
}
|