[COLRv1] Map linear gradient extrema to [0, 1] range

Prepare data to Skia's linear gradient shader so that the extrema are
[0, 1] when the data in the font does not have them at these
positions. This is required for the shader to correctly compute repeat
modes.

Add the test font from the Google color fonts repo [1] with stops at [0,
1], [0.2, 0.8], [0, 1.5], [0.5, 1.5] respectively, compare[2].

[1] https://github.com/googlefonts/color-fonts/blob/main/fonts/more_samples-glyf_colr_1.ttf
[2] https://github.com/googlefonts/colr-gradients-spec/issues/281#issuecomment-828346428

Bug: skia:11922
Change-Id: Ic62a26911fa177af35ca16f183b5985d884237a5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/405717
Commit-Queue: Dominik Röttsches <drott@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
This commit is contained in:
Dominik Röttsches 2021-05-11 15:59:43 +03:00 committed by Skia Commit-Bot
parent d2567de8bb
commit 2da029b28f
3 changed files with 119 additions and 58 deletions

View File

@ -26,28 +26,43 @@ namespace skiagm {
class ColrV1GM : public GM {
public:
ColrV1GM(SkScalar skewX, SkScalar rotateDeg) : fSkewX(skewX), fRotateDeg(rotateDeg) { }
// TODO(drott): Consolidate test fonts.
enum ColrV1TestFont {
kSkiaSampleFont,
kColorFontsRepoSampleFont
};
ColrV1GM(ColrV1TestFont useTestFont, SkScalar skewX, SkScalar rotateDeg) : fSkewX(skewX), fRotateDeg(rotateDeg), fTestFont(useTestFont) { }
protected:
struct EmojiFont {
sk_sp<SkTypeface> fTypeface;
const uint16_t fGlyphs[10] = { 19, 33, 34, 35, 20, 21, 22, 23, 24, 25 };
const size_t fGlyphs_bytesize =
SK_ARRAY_COUNT(fGlyphs) * sizeof(uint16_t);
std::vector<uint16_t> fGlyphs;
size_t bytesize() { return fGlyphs.size() * sizeof(uint16_t); }
} fEmojiFont;
void onOnceBeforeDraw() override {
fEmojiFont.fTypeface =
MakeResourceAsTypeface("fonts/colrv1_samples.ttf");
if (fTestFont == kSkiaSampleFont) {
fEmojiFont.fTypeface = MakeResourceAsTypeface("fonts/colrv1_samples.ttf");
fEmojiFont.fGlyphs = {19, 33, 34, 35, 20, 21, 22, 23, 24, 25};
} else {
fEmojiFont.fTypeface = MakeResourceAsTypeface("fonts/more_samples-glyf_colr_1.ttf");
fEmojiFont.fGlyphs = {5, 6, 7, 8};
}
}
SkString onShortName() override {
if (fSkewX != 0 || fRotateDeg != 0) {
return SkStringPrintf("colrv1_skew_%g_rotate_%g",
fSkewX,
fRotateDeg);
SkString gm_name = SkStringPrintf("colrv1_%s_samples",
fTestFont == kSkiaSampleFont ? "skia" : "color_repo");
if (fSkewX) {
gm_name.append("_skew");
}
return SkString("colrv1");
if (fRotateDeg) {
gm_name.append("_rotate");
}
return gm_name;
}
SkISize onISize() override { return SkISize::Make(1400, 600); }
@ -74,8 +89,8 @@ protected:
font.setSize(textSize);
font.getMetrics(&metrics);
y += -metrics.fAscent;
canvas->drawSimpleText(fEmojiFont.fGlyphs,
fEmojiFont.fGlyphs_bytesize,
canvas->drawSimpleText(fEmojiFont.fGlyphs.data(),
fEmojiFont.bytesize(),
SkTextEncoding::kGlyphID,
10, y, font, paint);
y += metrics.fDescent + metrics.fLeading;
@ -87,11 +102,13 @@ private:
using INHERITED = GM;
SkScalar fSkewX;
SkScalar fRotateDeg;
ColrV1TestFont fTestFont;
};
DEF_GM(return new ColrV1GM(0.f, 0.f);)
DEF_GM(return new ColrV1GM(-0.5f, 0.f);)
DEF_GM(return new ColrV1GM(0.f, 20.f);)
DEF_GM(return new ColrV1GM(-0.5f, 20.f);)
DEF_GM(return new ColrV1GM(ColrV1GM::kSkiaSampleFont, 0.f, 0.f);)
DEF_GM(return new ColrV1GM(ColrV1GM::kSkiaSampleFont, -0.5f, 0.f);)
DEF_GM(return new ColrV1GM(ColrV1GM::kSkiaSampleFont, 0.f, 20.f);)
DEF_GM(return new ColrV1GM(ColrV1GM::kSkiaSampleFont, -0.5f, 20.f);)
DEF_GM(return new ColrV1GM(ColrV1GM::kColorFontsRepoSampleFont, 0.f, 0.f);)
} // namespace skiagm

Binary file not shown.

View File

@ -495,50 +495,65 @@ void colrv1_configure_skpaint(FT_Face face, const FT_Color* palette,
std::vector<SkScalar>& stops,
std::vector<SkColor>& colors) {
const FT_UInt num_color_stops = color_stop_iterator.num_color_stops;
stops.resize(num_color_stops);
colors.resize(num_color_stops);
// 5.7.11.2.4 ColorIndex, ColorStop and ColorLine
// "Applications shall apply the colorStops in increasing stopOffset order."
struct ColorStop {
SkScalar stop_pos;
SkColor color;
};
std::vector<ColorStop> sorted_stops;
sorted_stops.resize(num_color_stops);
FT_ColorStop color_stop;
while (FT_Get_Colorline_Stops(face, &color_stop, &color_stop_iterator)) {
FT_UInt index = color_stop_iterator.current_color_stop - 1;
stops[index] = color_stop.stop_offset / float(1 << 14);
sorted_stops[index].stop_pos = color_stop.stop_offset / float(1 << 14);
FT_UInt16& palette_index = color_stop.color.palette_index;
// TODO(drott): Ensure palette_index is sanitized on the FreeType
// side and 0xFFFF foreground color will be handled correctly here.
colors[index] = SkColorSetARGB(
sorted_stops[index].color = SkColorSetARGB(
palette[palette_index].alpha * SkColrV1AlphaToFloat(color_stop.color.alpha),
palette[palette_index].red,
palette[palette_index].green,
palette[palette_index].blue);
}
std::stable_sort(
sorted_stops.begin(),
sorted_stops.end(),
[](const ColorStop& a, const ColorStop& b) { return a.stop_pos < b.stop_pos; });
stops.resize(num_color_stops);
colors.resize(num_color_stops);
for (size_t i = 0; i < num_color_stops; ++i) {
stops[i] = sorted_stops[i].stop_pos;
colors[i] = sorted_stops[i].color;
}
};
switch (colrv1_paint.format) {
case FT_COLR_PAINTFORMAT_SOLID: {
FT_PaintSolid solid = colrv1_paint.u.solid;
SkColor color =
SkColorSetARGB(palette[colrv1_paint.u.solid.color.palette_index].alpha *
SkColrV1AlphaToFloat(colrv1_paint.u.solid.color.alpha),
palette[colrv1_paint.u.solid.color.palette_index].red,
palette[colrv1_paint.u.solid.color.palette_index].green,
palette[colrv1_paint.u.solid.color.palette_index].blue);
SkColorSetARGB(palette[solid.color.palette_index].alpha *
SkColrV1AlphaToFloat(solid.color.alpha),
palette[solid.color.palette_index].red,
palette[solid.color.palette_index].green,
palette[solid.color.palette_index].blue);
paint->setShader(nullptr);
paint->setColor(color);
break;
}
case FT_COLR_PAINTFORMAT_LINEAR_GRADIENT: {
/* retrieve color stop */
SkPoint line_positions[2];
line_positions[0].fX = colrv1_paint.u.linear_gradient.p0.x;
line_positions[0].fY = -colrv1_paint.u.linear_gradient.p0.y;
line_positions[1].fX = colrv1_paint.u.linear_gradient.p1.x;
line_positions[1].fY = -colrv1_paint.u.linear_gradient.p1.y;
SkPoint& p0 = line_positions[0];
SkPoint& p1 = line_positions[1];
SkPoint p2;
p2.set(colrv1_paint.u.linear_gradient.p2.x,
-colrv1_paint.u.linear_gradient.p2.y);
FT_PaintLinearGradient& linear_gradient = colrv1_paint.u.linear_gradient;
SkPoint line_positions[2] = {
SkPoint::Make(linear_gradient.p0.x, -linear_gradient.p0.y),
SkPoint::Make(linear_gradient.p1.x, -linear_gradient.p1.y)
};
SkPoint p0 = line_positions[0];
SkPoint p1 = line_positions[1];
SkPoint p2 = SkPoint::Make(linear_gradient.p2.x, -linear_gradient.p2.y);
// Do not draw the gradient of p0p1 is parallel to p0p2.
if (p1 == p0 || p2 == p0 || !SkPoint::CrossProduct(p1 - p0, p2 - p0)) break;
@ -547,56 +562,85 @@ void colrv1_configure_skpaint(FT_Face face, const FT_Color* palette,
// https://github.com/googlefonts/nanoemoji/blob/0ac6e7bb4d8202db692574d8530a9b643f1b3b3c/src/nanoemoji/svg.py#L188
// to compute a new gradient end point as the orthogonal projection of the vector from p0 to p1 onto a line
// perpendicular to line p0p2 and passing through p0.
SkPoint perpendicular_to_p2_p0 = (p2 - p0);
SkVector perpendicular_to_p2_p0 = (p2 - p0);
perpendicular_to_p2_p0 = SkPoint::Make(perpendicular_to_p2_p0.y(), -perpendicular_to_p2_p0.x());
line_positions[1] = p0 + SkVectorProjection((p1 - p0), perpendicular_to_p2_p0);
std::vector<SkScalar> stops;
std::vector<SkColor> colors;
fetch_color_stops(colrv1_paint.u.linear_gradient.colorline.color_stop_iterator, stops, colors);
fetch_color_stops(linear_gradient.colorline.color_stop_iterator, stops, colors);
if (stops.empty()) {
break;
}
if (stops.size() == 1) {
paint->setColor(colors[0]);
break;
}
// Project/scale points according to stop extrema along p0p1 line,
// then scale stops to to [0, 1] range so that repeat modes work.
// The Skia linear gradient shader performs the repeat modes over
// the 0 to 1 range, that's why we need to scale the stops to within
// that range.
SkVector p0p1 = p1 - p0;
SkVector new_p0_offset = p0p1;
new_p0_offset.scale(stops.front());
SkVector new_p1_offset = p0p1;
new_p1_offset.scale(stops.back());
line_positions[0] = p0 + new_p0_offset;
line_positions[1] = p0 + new_p1_offset;
SkScalar scale_factor = 1 / (stops.back() - stops.front());
SkScalar start_offset = stops.front();
for (SkScalar& stop : stops) {
stop = (stop - start_offset) * scale_factor;
}
sk_sp<SkShader> shader(SkGradientShader::MakeLinear(
line_positions, colors.data(), stops.data(), stops.size(),
ToSkTileMode(colrv1_paint.u.linear_gradient.colorline.extend)));
line_positions,
colors.data(),
stops.data(),
stops.size(),
ToSkTileMode(linear_gradient.colorline.extend)));
SkASSERT(shader);
// An opaque color is needed to ensure the gradient's not modulated by alpha.
paint->setColor(SK_ColorBLACK);
paint->setShader(shader);
break;
}
case FT_COLR_PAINTFORMAT_RADIAL_GRADIENT: {
SkPoint start =
SkPoint::Make(colrv1_paint.u.radial_gradient.c0.x,
-colrv1_paint.u.radial_gradient.c0.y);
SkScalar radius = colrv1_paint.u.radial_gradient.r0;
SkPoint end = SkPoint::Make(colrv1_paint.u.radial_gradient.c1.x,
-colrv1_paint.u.radial_gradient.c1.y);
SkScalar end_radius = colrv1_paint.u.radial_gradient.r1;
FT_PaintRadialGradient& radial_gradient = colrv1_paint.u.radial_gradient;
SkPoint start = SkPoint::Make(radial_gradient.c0.x, -radial_gradient.c0.y);
SkScalar radius = radial_gradient.r0;
SkPoint end = SkPoint::Make(radial_gradient.c1.x, -radial_gradient.c1.y);
SkScalar end_radius = radial_gradient.r1;
std::vector<SkScalar> stops;
std::vector<SkColor> colors;
fetch_color_stops(colrv1_paint.u.radial_gradient.colorline.color_stop_iterator, stops, colors);
fetch_color_stops(radial_gradient.colorline.color_stop_iterator, stops, colors);
// An opaque color is needed to ensure the gradient's not modulated by alpha.
paint->setColor(SK_ColorBLACK);
paint->setShader(SkGradientShader::MakeTwoPointConical(
start, radius, end, end_radius, colors.data(), stops.data(), stops.size(),
ToSkTileMode(colrv1_paint.u.radial_gradient.colorline.extend)));
ToSkTileMode(radial_gradient.colorline.extend)));
break;
}
case FT_COLR_PAINTFORMAT_SWEEP_GRADIENT: {
SkPoint center = SkPoint::Make(colrv1_paint.u.sweep_gradient.center.x,
-colrv1_paint.u.sweep_gradient.center.y);
SkScalar startAngle = SkFixedToScalar(colrv1_paint.u.sweep_gradient.start_angle);
SkScalar endAngle = SkFixedToScalar(colrv1_paint.u.sweep_gradient.end_angle);
FT_PaintSweepGradient& sweep_gradient = colrv1_paint.u.sweep_gradient;
SkPoint center = SkPoint::Make(sweep_gradient.center.x, -sweep_gradient.center.y);
SkScalar startAngle = SkFixedToScalar(sweep_gradient.start_angle);
SkScalar endAngle = SkFixedToScalar(sweep_gradient.end_angle);
std::vector<SkScalar> stops;
std::vector<SkColor> colors;
fetch_color_stops(colrv1_paint.u.sweep_gradient.colorline.color_stop_iterator, stops, colors);
fetch_color_stops(sweep_gradient.colorline.color_stop_iterator, stops, colors);
// An opaque color is needed to ensure the gradient's not modulated by alpha.
paint->setColor(SK_ColorBLACK);