bool colrv1_configure_skpaint()

in src/ports/SkFontHost_FreeType_common.cpp [603:1078]


bool colrv1_configure_skpaint(FT_Face face,
                              const SkSpan<SkColor>& palette,
                              const SkColor foregroundColor,
                              const FT_COLR_Paint& colrPaint,
                              SkPaint* paint) {
    auto fetchColorStops = [&face, &palette, &foregroundColor](
                                               const FT_ColorStopIterator& colorStopIterator,
                                               std::vector<SkScalar>& stops,
                                               std::vector<SkColor4f>& colors) -> bool {
        const FT_UInt colorStopCount = colorStopIterator.num_color_stops;
        if (colorStopCount == 0) {
            return false;
        }

        // 5.7.11.2.4 ColorIndex, ColorStop and ColorLine
        // "Applications shall apply the colorStops in increasing stopOffset order."
        struct ColorStop {
            SkScalar pos;
            SkColor4f color;
        };
        std::vector<ColorStop> colorStopsSorted;
        colorStopsSorted.resize(colorStopCount);

        FT_ColorStop ftStop;
        FT_ColorStopIterator mutable_color_stop_iterator = colorStopIterator;
        while (FT_Get_Colorline_Stops(face, &ftStop, &mutable_color_stop_iterator)) {
            FT_UInt index = mutable_color_stop_iterator.current_color_stop - 1;
            ColorStop& skStop = colorStopsSorted[index];
            skStop.pos = ftStop.stop_offset / kColorStopShift;
            FT_UInt16& palette_index = ftStop.color.palette_index;
            if (palette_index == kForegroundColorPaletteIndex) {
                skStop.color = SkColor4f::FromColor(foregroundColor);
            } else if (palette_index >= palette.size()) {
                return false;
            } else {
                skStop.color = SkColor4f::FromColor(palette[palette_index]);
            }
            skStop.color.fA *= SkColrV1AlphaToFloat(ftStop.color.alpha);
        }

        std::stable_sort(colorStopsSorted.begin(), colorStopsSorted.end(),
                         [](const ColorStop& a, const ColorStop& b) { return a.pos < b.pos; });

        stops.resize(colorStopCount);
        colors.resize(colorStopCount);
        for (size_t i = 0; i < colorStopCount; ++i) {
            stops[i] = colorStopsSorted[i].pos;
            colors[i] = colorStopsSorted[i].color;
        }
        return true;
    };

    switch (colrPaint.format) {
        case FT_COLR_PAINTFORMAT_SOLID: {
            FT_PaintSolid solid = colrPaint.u.solid;

            // Dont' draw anything with this color if the palette index is out of bounds.
            SkColor4f color = SkColors::kTransparent;
            if (solid.color.palette_index == kForegroundColorPaletteIndex) {
                color = SkColor4f::FromColor(foregroundColor);
            } else if (solid.color.palette_index >= palette.size()) {
                return false;
            } else {
                color = SkColor4f::FromColor(palette[solid.color.palette_index]);
            }
            color.fA *= SkColrV1AlphaToFloat(solid.color.alpha);
            paint->setShader(nullptr);
            paint->setColor(color);
            return true;
        }
        case FT_COLR_PAINTFORMAT_LINEAR_GRADIENT: {
            const FT_PaintLinearGradient& linearGradient = colrPaint.u.linear_gradient;
            std::vector<SkScalar> stops;
            std::vector<SkColor4f> colors;

            if (!fetchColorStops(linearGradient.colorline.color_stop_iterator, stops, colors)) {
                return false;
            }

            if (stops.size() == 1) {
                paint->setColor(colors[0]);
                return true;
            }

            SkPoint linePositions[2] = {SkPoint::Make( SkFixedToScalar(linearGradient.p0.x),
                                                      -SkFixedToScalar(linearGradient.p0.y)),
                                        SkPoint::Make( SkFixedToScalar(linearGradient.p1.x),
                                                      -SkFixedToScalar(linearGradient.p1.y))};
            SkPoint p0 = linePositions[0];
            SkPoint p1 = linePositions[1];
            SkPoint p2 = SkPoint::Make( SkFixedToScalar(linearGradient.p2.x),
                                       -SkFixedToScalar(linearGradient.p2.y));

            // If p0p1 or p0p2 are degenerate probably nothing should be drawn.
            // If p0p1 and p0p2 are parallel then one side is the first color and the other side is
            // the last color, depending on the direction.
            // For now, just use the first color.
            if (p1 == p0 || p2 == p0 || !SkPoint::CrossProduct(p1 - p0, p2 - p0)) {
                paint->setColor(colors[0]);
                return true;
            }

            // Follow implementation note in nanoemoji:
            // https://github.com/googlefonts/nanoemoji/blob/0ac6e7bb4d8202db692574d8530a9b643f1b3b3c/src/nanoemoji/svg.py#L188
            // to compute a new gradient end point P3 as the orthogonal
            // projection of the vector from p0 to p1 onto a line perpendicular
            // to line p0p2 and passing through p0.
            SkVector perpendicularToP2P0 = (p2 - p0);
            perpendicularToP2P0 = SkPoint::Make( perpendicularToP2P0.y(),
                                                -perpendicularToP2P0.x());
            SkVector p3 = p0 + SkVectorProjection((p1 - p0), perpendicularToP2P0);
            linePositions[1] = p3;

            // Project/scale points according to stop extrema along p0p3 line,
            // p3 being the result of the projection above, 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.
            SkTileMode tileMode = ToSkTileMode(linearGradient.colorline.extend);
            SkScalar colorStopRange = stops.back() - stops.front();
            // If the color stops are all at the same offset position, repeat and reflect modes
            // become meaningless.
            if (colorStopRange == 0.f) {
              if (tileMode != SkTileMode::kClamp) {
                paint->setColor(SK_ColorTRANSPARENT);
                return true;
              } else {
                // Insert duplicated fake color stop in pad case at +1.0f to enable the projection
                // of circles for an originally 0-length color stop range. Adding this stop will
                // paint the equivalent gradient, because: All font specified color stops are in the
                // same spot, mode is pad, so everything before this spot is painted with the first
                // color, everything after this spot is painted with the last color. Not adding this
                // stop will skip the projection and result in specifying non-normalized color stops
                // to the shader.
                stops.push_back(stops.back() + 1.0f);
                colors.push_back(colors.back());
                colorStopRange = 1.0f;
              }
            }
            SkASSERT(colorStopRange != 0.f);

            // If the colorStopRange is 0 at this point, the default behavior of the shader is to
            // clamp to 1 color stops that are above 1, clamp to 0 for color stops that are below 0,
            // and repeat the outer color stops at 0 and 1 if the color stops are inside the
            // range. That will result in the correct rendering.
            if ((colorStopRange != 1 || stops.front() != 0.f)) {
                SkVector p0p3 = p3 - p0;
                SkVector p0Offset = p0p3;
                p0Offset.scale(stops.front());
                SkVector p1Offset = p0p3;
                p1Offset.scale(stops.back());

                linePositions[0] = p0 + p0Offset;
                linePositions[1] = p0 + p1Offset;

                SkScalar scaleFactor = 1 / colorStopRange;
                SkScalar startOffset = stops.front();
                for (SkScalar& stop : stops) {
                    stop = (stop - startOffset) * scaleFactor;
                }
            }

            sk_sp<SkShader> shader(SkGradientShader::MakeLinear(
                linePositions,
                colors.data(), SkColorSpace::MakeSRGB(), stops.data(), stops.size(),
                tileMode,
                SkGradientShader::Interpolation{
                    SkGradientShader::Interpolation::InPremul::kNo,
                    SkGradientShader::Interpolation::ColorSpace::kSRGB,
                    SkGradientShader::Interpolation::HueMethod::kShorter
                },
                nullptr));

            SkASSERT(shader);
            // An opaque color is needed to ensure the gradient is not modulated by alpha.
            paint->setColor(SK_ColorBLACK);
            paint->setShader(shader);
            return true;
        }
        case FT_COLR_PAINTFORMAT_RADIAL_GRADIENT: {
            const FT_PaintRadialGradient& radialGradient = colrPaint.u.radial_gradient;
            SkPoint start = SkPoint::Make( SkFixedToScalar(radialGradient.c0.x),
                                          -SkFixedToScalar(radialGradient.c0.y));
            SkScalar startRadius = SkFixedToScalar(radialGradient.r0);
            SkPoint end = SkPoint::Make( SkFixedToScalar(radialGradient.c1.x),
                                        -SkFixedToScalar(radialGradient.c1.y));
            SkScalar endRadius = SkFixedToScalar(radialGradient.r1);


            std::vector<SkScalar> stops;
            std::vector<SkColor4f> colors;
            if (!fetchColorStops(radialGradient.colorline.color_stop_iterator, stops, colors)) {
                return false;
            }

            if (stops.size() == 1) {
                paint->setColor(colors[0]);
                return true;
            }

            SkScalar colorStopRange = stops.back() - stops.front();
            SkTileMode tileMode = ToSkTileMode(radialGradient.colorline.extend);

            if (colorStopRange == 0.f) {
              if (tileMode != SkTileMode::kClamp) {
                paint->setColor(SK_ColorTRANSPARENT);
                return true;
              } else {
                // Insert duplicated fake color stop in pad case at +1.0f to enable the projection
                // of circles for an originally 0-length color stop range. Adding this stop will
                // paint the equivalent gradient, because: All font specified color stops are in the
                // same spot, mode is pad, so everything before this spot is painted with the first
                // color, everything after this spot is painted with the last color. Not adding this
                // stop will skip the projection and result in specifying non-normalized color stops
                // to the shader.
                stops.push_back(stops.back() + 1.0f);
                colors.push_back(colors.back());
                colorStopRange = 1.0f;
              }
            }
            SkASSERT(colorStopRange != 0.f);

            // If the colorStopRange is 0 at this point, the default behavior of the shader is to
            // clamp to 1 color stops that are above 1, clamp to 0 for color stops that are below 0,
            // and repeat the outer color stops at 0 and 1 if the color stops are inside the
            // range. That will result in the correct rendering.
            if (colorStopRange != 1 || stops.front() != 0.f) {
                // For the Skia two-point caonical shader to understand the
                // COLRv1 color stops we need to scale stops to 0 to 1 range and
                // interpolate new centers and radii. Otherwise the shader
                // clamps stops outside the range to 0 and 1 (larger interval)
                // or repeats the outer stops at 0 and 1 if the (smaller
                // interval).
                SkVector startToEnd = end - start;
                SkScalar radiusDiff = endRadius - startRadius;
                SkScalar scaleFactor = 1 / colorStopRange;
                SkScalar stopsStartOffset = stops.front();

                SkVector startOffset = startToEnd;
                startOffset.scale(stops.front());
                SkVector endOffset = startToEnd;
                endOffset.scale(stops.back());

                // The order of the following computations is important in order to avoid
                // overwriting start or startRadius before the second reassignment.
                end = start + endOffset;
                start = start + startOffset;
                endRadius = startRadius + radiusDiff * stops.back();
                startRadius = startRadius + radiusDiff * stops.front();

                for (auto& stop : stops) {
                    stop = (stop - stopsStartOffset) * scaleFactor;
                }
            }

            // For negative radii, interpolation is needed to prepare parameters suitable
            // for invoking the shader. Implementation below as resolution discussed in
            // https://github.com/googlefonts/colr-gradients-spec/issues/367.
            // Truncate to manually interpolated color for tile mode clamp, otherwise
            // calculate positive projected circles.
            if (startRadius < 0 || endRadius < 0) {
                if (startRadius == endRadius && startRadius < 0) {
                    paint->setColor(SK_ColorTRANSPARENT);
                    return true;
                }

                if (tileMode == SkTileMode::kClamp) {
                    SkVector startToEnd = end - start;
                    SkScalar radiusDiff = endRadius - startRadius;
                    SkScalar zeroRadiusStop = 0.f;
                    TruncateStops truncateSide = TruncateStart;
                    if (startRadius < 0) {
                        truncateSide = TruncateStart;

                        // Compute color stop position where radius is = 0.  After the scaling
                        // of stop positions to the normal 0,1 range that we have done above,
                        // the size of the radius as a function of the color stops is: r(x) = r0
                        // + x*(r1-r0) Solving this function for r(x) = 0, we get: x = -r0 /
                        // (r1-r0)
                        zeroRadiusStop = -startRadius / (endRadius - startRadius);
                        startRadius = 0.f;
                        SkVector startEndDiff = end - start;
                        startEndDiff.scale(zeroRadiusStop);
                        start = start + startEndDiff;
                    }

                    if (endRadius < 0) {
                        truncateSide = TruncateEnd;
                        zeroRadiusStop = -startRadius / (endRadius - startRadius);
                        endRadius = 0.f;
                        SkVector startEndDiff = end - start;
                        startEndDiff.scale(1 - zeroRadiusStop);
                        end = end - startEndDiff;
                    }

                    if (!(startRadius == 0 && endRadius == 0)) {
                        truncateToStopInterpolating(
                                zeroRadiusStop, colors, stops, truncateSide);
                    } else {
                        // If both radii have become negative and where clamped to 0, we need to
                        // produce a single color cone, otherwise the shader colors the whole
                        // plane in a single color when two radii are specified as 0.
                        if (radiusDiff > 0) {
                            end = start + startToEnd;
                            endRadius = radiusDiff;
                            colors.erase(colors.begin(), colors.end() - 1);
                            stops.erase(stops.begin(), stops.end() - 1);
                        } else {
                            start -= startToEnd;
                            startRadius = -radiusDiff;
                            colors.erase(colors.begin() + 1, colors.end());
                            stops.erase(stops.begin() + 1, stops.end());
                        }
                    }
                } else {
                    if (startRadius < 0 || endRadius < 0) {
                        auto roundIntegerMultiple = [](SkScalar factorZeroCrossing,
                                                       SkTileMode tileMode) {
                            int roundedMultiple = factorZeroCrossing > 0
                                                          ? ceilf(factorZeroCrossing)
                                                          : floorf(factorZeroCrossing) - 1;
                            if (tileMode == SkTileMode::kMirror && roundedMultiple % 2 != 0) {
                                roundedMultiple += roundedMultiple < 0 ? -1 : 1;
                            }
                            return roundedMultiple;
                        };

                        SkVector startToEnd = end - start;
                        SkScalar radiusDiff = endRadius - startRadius;
                        SkScalar factorZeroCrossing = (startRadius / (startRadius - endRadius));
                        bool inRange = 0.f <= factorZeroCrossing && factorZeroCrossing <= 1.0f;
                        SkScalar direction = inRange && radiusDiff < 0 ? -1.0f : 1.0f;
                        SkScalar circleProjectionFactor =
                                roundIntegerMultiple(factorZeroCrossing * direction, tileMode);
                        startToEnd.scale(circleProjectionFactor);
                        startRadius += circleProjectionFactor * radiusDiff;
                        endRadius += circleProjectionFactor * radiusDiff;
                        start += startToEnd;
                        end += startToEnd;
                    }
                }
            }

            // An opaque color is needed to ensure the gradient is not modulated by alpha.
            paint->setColor(SK_ColorBLACK);

            paint->setShader(SkGradientShader::MakeTwoPointConical(
                start, startRadius, end, endRadius,
                colors.data(), SkColorSpace::MakeSRGB(), stops.data(), stops.size(),
                tileMode,
                SkGradientShader::Interpolation{
                    SkGradientShader::Interpolation::InPremul::kNo,
                    SkGradientShader::Interpolation::ColorSpace::kSRGB,
                    SkGradientShader::Interpolation::HueMethod::kShorter
                },
                nullptr));

            return true;
        }
        case FT_COLR_PAINTFORMAT_SWEEP_GRADIENT: {
            const FT_PaintSweepGradient& sweepGradient = colrPaint.u.sweep_gradient;
            SkPoint center = SkPoint::Make( SkFixedToScalar(sweepGradient.center.x),
                                           -SkFixedToScalar(sweepGradient.center.y));


            SkScalar startAngle = SkFixedToScalar(sweepGradient.start_angle * 180.0f);
            SkScalar endAngle = SkFixedToScalar(sweepGradient.end_angle * 180.0f);
            // OpenType 1.9.1 adds a shift to the angle to ease specification of a 0 to 360
            // degree sweep.
            startAngle += 180.0f;
            endAngle += 180.0f;

            std::vector<SkScalar> stops;
            std::vector<SkColor4f> colors;
            if (!fetchColorStops(sweepGradient.colorline.color_stop_iterator, stops, colors)) {
                return false;
            }

            if (stops.size() == 1) {
                paint->setColor(colors[0]);
                return true;
            }

            // An opaque color is needed to ensure the gradient is not modulated by alpha.
            paint->setColor(SK_ColorBLACK);

            // New (Var)SweepGradient implementation compliant with OpenType 1.9.1 from here.

            // The shader expects stops from 0 to 1, so we need to account for
            // minimum and maximum stop positions being different from 0 and
            // 1. We do that by scaling minimum and maximum stop positions to
            // the 0 to 1 interval and scaling the angles inverse proportionally.

            // 1) Scale angles to their equivalent positions if stops were from 0 to 1.

            SkScalar sectorAngle = endAngle - startAngle;
            SkTileMode tileMode = ToSkTileMode(sweepGradient.colorline.extend);
            if (sectorAngle == 0 && tileMode != SkTileMode::kClamp) {
                // "If the ColorLine's extend mode is reflect or repeat and start and end angle
                // are equal, nothing is drawn.".
                paint->setColor(SK_ColorTRANSPARENT);
                return true;
            }


            SkScalar startAngleScaled = startAngle + sectorAngle * stops.front();
            SkScalar endAngleScaled = startAngle + sectorAngle * stops.back();

            // 2) Scale stops accordingly to 0 to 1 range.

            float colorStopRange = stops.back() - stops.front();
            if (colorStopRange == 0.f) {
              if (tileMode != SkTileMode::kClamp) {
                paint->setColor(SK_ColorTRANSPARENT);
                return true;
              } else {
                // Insert duplicated fake color stop in pad case at +1.0f to feed the shader correct
                // values and enable painting a pad sweep gradient with two colors. Adding this stop
                // will paint the equivalent gradient, because: All font specified color stops are
                // in the same spot, mode is pad, so everything before this spot is painted with the
                // first color, everything after this spot is painted with the last color. Not
                // adding this stop will skip the projection and result in specifying non-normalized
                // color stops to the shader.
                stops.push_back(stops.back() + 1.0f);
                colors.push_back(colors.back());
                colorStopRange = 1.0f;
              }
            }

            SkScalar scaleFactor = 1 / colorStopRange;
            SkScalar startOffset = stops.front();

            for (SkScalar& stop : stops) {
                stop = (stop - startOffset) * scaleFactor;
            }

            /* https://docs.microsoft.com/en-us/typography/opentype/spec/colr#sweep-gradients
             * "The angles are expressed in counter-clockwise degrees from
             * the direction of the positive x-axis on the design
             * grid. [...]  The color line progresses from the start angle
             * to the end angle in the counter-clockwise direction;" -
             * Convert angles and stops from counter-clockwise to clockwise
             * for the shader if the gradient is not already reversed due to
             * start angle being larger than end angle. */
            startAngleScaled = 360.f - startAngleScaled;
            endAngleScaled = 360.f - endAngleScaled;
            if (startAngleScaled >= endAngleScaled) {
                std::swap(startAngleScaled, endAngleScaled);
                std::reverse(stops.begin(), stops.end());
                std::reverse(colors.begin(), colors.end());
                for (auto& stop : stops) {
                    stop = 1.0f - stop;
                }
            }

            paint->setShader(SkGradientShader::MakeSweep(
                center.x(), center.y(),
                colors.data(), SkColorSpace::MakeSRGB(), stops.data(), stops.size(),
                tileMode,
                startAngleScaled, endAngleScaled,
                SkGradientShader::Interpolation{
                    SkGradientShader::Interpolation::InPremul::kNo,
                    SkGradientShader::Interpolation::ColorSpace::kSRGB,
                    SkGradientShader::Interpolation::HueMethod::kShorter
                },
                nullptr));

            return true;
        }
        default: {
            SkASSERT(false);
            return false;
        }
    }
    SkUNREACHABLE;
}