mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
b4ea58a9d9
This bug likely dates back to bug 1584268, which makes it so that Cairo scaled fonts must be instantiated on demand for their first use. It seems like some StrokeGlyphs machinery got overlooked and somehow never caused a problem till now. Differential Revision: https://phabricator.services.mozilla.com/D200472
356 lines
12 KiB
C++
356 lines
12 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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/. */
|
|
|
|
#include "2D.h"
|
|
#include "Blur.h"
|
|
#include "Logging.h"
|
|
#include "PathHelpers.h"
|
|
#include "SourceSurfaceRawData.h"
|
|
#include "Tools.h"
|
|
|
|
#include "BufferEdgePad.h"
|
|
#include "BufferUnrotate.h"
|
|
|
|
#ifdef USE_NEON
|
|
# include "mozilla/arm.h"
|
|
# include "LuminanceNEON.h"
|
|
#endif
|
|
|
|
namespace mozilla {
|
|
namespace gfx {
|
|
|
|
/**
|
|
* Byte offsets of channels in a native packed gfxColor or cairo image surface.
|
|
*/
|
|
#ifdef IS_BIG_ENDIAN
|
|
# define GFX_ARGB32_OFFSET_A 0
|
|
# define GFX_ARGB32_OFFSET_R 1
|
|
# define GFX_ARGB32_OFFSET_G 2
|
|
# define GFX_ARGB32_OFFSET_B 3
|
|
#else
|
|
# define GFX_ARGB32_OFFSET_A 3
|
|
# define GFX_ARGB32_OFFSET_R 2
|
|
# define GFX_ARGB32_OFFSET_G 1
|
|
# define GFX_ARGB32_OFFSET_B 0
|
|
#endif
|
|
|
|
// 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 USE_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;
|
|
}
|
|
}
|
|
|
|
void DrawTarget::PushDeviceSpaceClipRects(const IntRect* aRects,
|
|
uint32_t aCount) {
|
|
Matrix oldTransform = GetTransform();
|
|
SetTransform(Matrix());
|
|
|
|
RefPtr<PathBuilder> pathBuilder = CreatePathBuilder();
|
|
for (uint32_t i = 0; i < aCount; i++) {
|
|
AppendRectToPath(pathBuilder, Rect(aRects[i]));
|
|
}
|
|
RefPtr<Path> path = pathBuilder->Finish();
|
|
PushClip(path);
|
|
|
|
SetTransform(oldTransform);
|
|
}
|
|
|
|
void DrawTarget::FillRoundedRect(const RoundedRect& aRect,
|
|
const Pattern& aPattern,
|
|
const DrawOptions& aOptions) {
|
|
RefPtr<Path> path = MakePathForRoundedRect(*this, aRect.rect, aRect.corners);
|
|
Fill(path, aPattern, aOptions);
|
|
}
|
|
|
|
void DrawTarget::StrokeCircle(const Point& aOrigin, float radius,
|
|
const Pattern& aPattern,
|
|
const StrokeOptions& aStrokeOptions,
|
|
const DrawOptions& aOptions) {
|
|
RefPtr<Path> path = MakePathForCircle(*this, aOrigin, radius);
|
|
Stroke(path, aPattern, aStrokeOptions, aOptions);
|
|
}
|
|
|
|
void DrawTarget::FillCircle(const Point& aOrigin, float radius,
|
|
const Pattern& aPattern,
|
|
const DrawOptions& aOptions) {
|
|
RefPtr<Path> path = MakePathForCircle(*this, aOrigin, radius);
|
|
Fill(path, aPattern, aOptions);
|
|
}
|
|
|
|
void DrawTarget::StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
|
|
const Pattern& aPattern,
|
|
const StrokeOptions& aStrokeOptions,
|
|
const DrawOptions& aOptions) {
|
|
if (RefPtr<Path> path = aFont->GetPathForGlyphs(aBuffer, this)) {
|
|
Stroke(path, aPattern, aStrokeOptions, aOptions);
|
|
}
|
|
}
|
|
|
|
already_AddRefed<SourceSurface> DrawTarget::IntoLuminanceSource(
|
|
LuminanceType aMaskType, float aOpacity) {
|
|
// The default IntoLuminanceSource implementation needs a format of B8G8R8A8.
|
|
if (mFormat != SurfaceFormat::B8G8R8A8) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<SourceSurface> surface = Snapshot();
|
|
if (!surface) {
|
|
return nullptr;
|
|
}
|
|
|
|
IntSize size = surface->GetSize();
|
|
|
|
RefPtr<DataSourceSurface> maskSurface = surface->GetDataSurface();
|
|
if (!maskSurface) {
|
|
return nullptr;
|
|
}
|
|
|
|
DataSourceSurface::MappedSurface map;
|
|
if (!maskSurface->Map(DataSourceSurface::MapType::READ, &map)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Create alpha channel mask for output
|
|
RefPtr<SourceSurfaceAlignedRawData> destMaskSurface =
|
|
new SourceSurfaceAlignedRawData;
|
|
if (!destMaskSurface->Init(size, SurfaceFormat::A8, false, 0)) {
|
|
return nullptr;
|
|
}
|
|
DataSourceSurface::MappedSurface destMap;
|
|
if (!destMaskSurface->Map(DataSourceSurface::MapType::WRITE, &destMap)) {
|
|
return nullptr;
|
|
}
|
|
|
|
switch (aMaskType) {
|
|
case LuminanceType::LUMINANCE: {
|
|
ComputesRGBLuminanceMask(map.mData, map.mStride, destMap.mData,
|
|
destMap.mStride, size, aOpacity);
|
|
break;
|
|
}
|
|
case LuminanceType::LINEARRGB: {
|
|
ComputeLinearRGBLuminanceMask(map.mData, map.mStride, destMap.mData,
|
|
destMap.mStride, size, aOpacity);
|
|
break;
|
|
}
|
|
}
|
|
|
|
maskSurface->Unmap();
|
|
destMaskSurface->Unmap();
|
|
|
|
return destMaskSurface.forget();
|
|
}
|
|
|
|
void DrawTarget::Blur(const AlphaBoxBlur& aBlur) {
|
|
uint8_t* data;
|
|
IntSize size;
|
|
int32_t stride;
|
|
SurfaceFormat format;
|
|
if (!LockBits(&data, &size, &stride, &format)) {
|
|
gfxWarning() << "Cannot perform in-place blur on non-data DrawTarget";
|
|
return;
|
|
}
|
|
|
|
// Sanity check that the blur size matches the draw target.
|
|
MOZ_ASSERT(size == aBlur.GetSize());
|
|
MOZ_ASSERT(stride == aBlur.GetStride());
|
|
aBlur.Blur(data);
|
|
|
|
ReleaseBits(data);
|
|
}
|
|
|
|
void DrawTarget::PadEdges(const IntRegion& aRegion) {
|
|
PadDrawTargetOutFromRegion(this, aRegion);
|
|
}
|
|
|
|
bool DrawTarget::Unrotate(IntPoint aRotation) {
|
|
unsigned char* data;
|
|
IntSize size;
|
|
int32_t stride;
|
|
SurfaceFormat format;
|
|
|
|
if (LockBits(&data, &size, &stride, &format)) {
|
|
uint8_t bytesPerPixel = BytesPerPixel(format);
|
|
BufferUnrotate(data, size.width * bytesPerPixel, size.height, stride,
|
|
aRotation.x * bytesPerPixel, aRotation.y);
|
|
ReleaseBits(data);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int32_t ShadowOptions::BlurRadius() const {
|
|
return AlphaBoxBlur::CalculateBlurRadius(Point(mSigma, mSigma)).width;
|
|
}
|
|
|
|
void DrawTarget::DrawShadow(const Path* aPath, const Pattern& aPattern,
|
|
const ShadowOptions& aShadow,
|
|
const DrawOptions& aOptions,
|
|
const StrokeOptions* aStrokeOptions) {
|
|
// Get the approximate bounds of the source path
|
|
Rect bounds = aPath->GetFastBounds(GetTransform(), aStrokeOptions);
|
|
if (bounds.IsEmpty()) {
|
|
return;
|
|
}
|
|
// Inflate the bounds by the blur radius
|
|
bounds += aShadow.mOffset;
|
|
int32_t blurRadius = aShadow.BlurRadius();
|
|
bounds.Inflate(blurRadius);
|
|
bounds.RoundOut();
|
|
// Check if the bounds intersect the viewport
|
|
Rect viewport(GetRect());
|
|
viewport.Inflate(blurRadius);
|
|
bounds = bounds.Intersect(viewport);
|
|
IntRect intBounds;
|
|
if (bounds.IsEmpty() || !bounds.ToIntRect(&intBounds) ||
|
|
!CanCreateSimilarDrawTarget(intBounds.Size(), SurfaceFormat::A8)) {
|
|
return;
|
|
}
|
|
// Create a draw target for drawing the shadow mask with enough room for blur
|
|
RefPtr<DrawTarget> shadowTarget = CreateShadowDrawTarget(
|
|
intBounds.Size(), SurfaceFormat::A8, aShadow.mSigma);
|
|
if (shadowTarget) {
|
|
// See bug 1524554.
|
|
shadowTarget->ClearRect(Rect());
|
|
}
|
|
if (!shadowTarget || !shadowTarget->IsValid()) {
|
|
return;
|
|
}
|
|
// Draw the path into the target for the initial shadow mask
|
|
Point offset = Point(intBounds.TopLeft()) - aShadow.mOffset;
|
|
shadowTarget->SetTransform(GetTransform().PostTranslate(-offset));
|
|
DrawOptions shadowDrawOptions(
|
|
aOptions.mAlpha, CompositionOp::OP_OVER,
|
|
blurRadius > 1 ? AntialiasMode::NONE : aOptions.mAntialiasMode);
|
|
if (aStrokeOptions) {
|
|
shadowTarget->Stroke(aPath, aPattern, *aStrokeOptions, shadowDrawOptions);
|
|
} else {
|
|
shadowTarget->Fill(aPath, aPattern, shadowDrawOptions);
|
|
}
|
|
RefPtr<SourceSurface> snapshot = shadowTarget->Snapshot();
|
|
// Finally, hand a snapshot of the mask to DrawSurfaceWithShadow for the
|
|
// final shadow blur
|
|
if (snapshot) {
|
|
DrawSurfaceWithShadow(snapshot, offset, aShadow, aOptions.mCompositionOp);
|
|
}
|
|
}
|
|
|
|
} // namespace gfx
|
|
} // namespace mozilla
|