gecko-dev/gfx/2d/DrawTarget.cpp
Lee Salzman b4ea58a9d9 Bug 1878182 - Instantiate Cairo scaled font for ScaledFontBase::GetPathForGlyphs. r=jfkthame
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
2024-02-02 20:40:37 +00:00

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