← 블로그 목록

[Core2026] CPU Engine - Fill

Core2026 › Engine › CPU Engine

@Nor-s
  • #ThorVG
  • #Core
  • #CPU
목차
Linear·Radial Gradient의 공통 입력, Prepare에서 생성하는 SwFill 색상 테이블과 기하 계수, Draw의 위치별 색 조회와 RLE coverage를 통한 Surface 기록을 연결합니다.
0:00

1. Fill과 Gradient

Shape는 단색 RGBA를 직접 보관하거나 Fill 객체를 참조할 수 있습니다. 단색은 모든 위치에서 같은 source 색을 사용하고, Gradient는 위치를 color table의 index로 변환해 서로 다른 색을 읽습니다. 이 글은 두 경로 중 Gradient의 준비와 조회를 중심으로 설명합니다.

Gradient는 Shape를 하나의 색으로 채우는 대신, 여러 색을 연속적으로 보간하여 채우는 방식입니다.

CPU Engine은 렌더링 전에 Color Stop 사이의 색을 보간한 Color Table을 미리 생성합니다.

  • Color Stop: Gradient 구간의 위치(0.0~1.0)와 해당 위치의 색상을 정의

    • Fill::ColorStop으로 색상과 offset을 표현하고, Fill::colorStops()로 Gradient에 설정

      // Fill::ColorStop
      /**
       * @brief A data structure storing the information about the color and its relative position inside the gradient bounds.
       */
      struct ColorStop
      {
          float offset; /**< The relative position of the color. */
          uint8_t r;    /**< The red color channel value in the range [0 ~ 255]. */
          uint8_t g;    /**< The green color channel value in the range [0 ~ 255]. */
          uint8_t b;    /**< The blue color channel value in the range [0 ~ 255]. */
          uint8_t a;    /**< The alpha color channel value in the range [0 ~ 255], where 0 is completely transparent and 255 is opaque. */
      };
      
      // Fill::colorStops()
      /**
       * @brief Sets the parameters of the colors of the gradient and their position.
       *
       * @param[in] colorStops An array of ColorStop data structure.
       * @param[in] cnt The count of the @p colorStops array equal to the colors number used in the gradient.
       */
      Result colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept;
      
      /**
       * @brief Gets the parameters of the colors of the gradient, their position and number.
       *
       * @param[out] colorStops A pointer to the memory location, where the array of the gradient's ColorStop is stored.
       *
       * @return The number of colors used in the gradient. This value corresponds to the length of the @p colorStops array.
       */
      uint32_t colorStops(const ColorStop** colorStops) const noexcept;
  • Color Table: Color Stop 사이의 색을 미리 보간한 1024개 항목의 lookup table(LUT).

    • 렌더링할 때는 색을 다시 계산하지 않고 이 테이블에서 조회

색상 테이블

색상과 위치를 바꾸고, 1,024개 항목에서 보간된 색을 확인하세요.

직접 조작
INDEX512t0.500COLOR#000000rgb(0, 0, 0)
Color Stop 1
Color Stop 2
Color Stop 3

위 예제는 ColorStop 이 3개인 ColorTable을 보여줍니다. 실제 생성 흐름은 다음과 같습니다.

Canvas::Impl::update()
→ Paint::Impl::update()
→ ShapeImpl::update()
→ SwRenderer::prepare()
→ SwRenderer::prepareCommon()
→ TaskScheduler::request()
→ SwShapeTask::run()
→ fillPrepare()
→ _updateColorTable()
  • 렌더러는 Shape의 각 픽셀을 Gradient 내의 상대 위치 t로 변환하고, t에 해당하는 색을 Color Table에서 조회
  • Linear와 Radial Gradient는 이 t를 계산하는 기준이 다름
    • Linear Gradient: 픽셀을 시작점과 끝점이 정의하는 축에 투영하여 t를 계산
    • Radial Gradient: 픽셀이 시작 원과 끝 원 사이에서 차지하는 상대 위치로 t를 계산

1.1 Fill Spread

같은 위치를 왕복하며 Pad의 끝색 유지, Repeat의 반복, Reflect의 반사 반복을 비교합니다.
0:00
  • Fill::spread()는 Gradient 범위를 벗어난 영역을 채우는 방식을 설정 (기본값 FillSpread::Pad)
// FillSpread
/**
 * @brief Enumeration specifying how to fill the area outside the gradient bounds.
 */
enum struct FillSpread : uint8_t
{
    Pad = 0, ///< The remaining area is filled with the closest stop color.
    Reflect, ///< The gradient pattern is reflected outside the gradient area until the expected region is filled.
    Repeat   ///< The gradient pattern is repeated continuously beyond the gradient area until the expected region is filled.
};

// Fill::spread()
/**
 * @brief Sets the FillSpread value, which specifies how to fill the area outside the gradient bounds.
 *
 * @param[in] s The FillSpread value.
 */
Result Fill::spread(FillSpread s) noexcept;

1.2. Linear Gradient

LinearGradient는 LinearGradient::gen()으로 생성하며, LinearGradient::linear()로 Gradient의 시작점과 끝점을 설정합니다.

/**
 * @brief Sets the linear gradient bounds.
 *
 * The bounds of the linear gradient are defined as a surface constrained by two parallel lines crossing
 * the given points (@p x1, @p y1) and (@p x2, @p y2), respectively. Both lines are perpendicular to the line linking
 * (@p x1, @p y1) and (@p x2, @p y2).
 *
 * @param[in] x1 The horizontal coordinate of the first point used to determine the gradient bounds.
 * @param[in] y1 The vertical coordinate of the first point used to determine the gradient bounds.
 * @param[in] x2 The horizontal coordinate of the second point used to determine the gradient bounds.
 * @param[in] y2 The vertical coordinate of the second point used to determine the gradient bounds.
 *
 * @note In case the first and the second points are equal, an object is filled with a single color using the last color specified in the colorStops().
 * @see Fill::colorStops()
 */
Result LinearGradient::linear(float x1, float y1, float x2, float y2) noexcept;
  • (x1, y1): 시작점 p1
  • (x2, y2): 끝점 p2

Linear Gradient

시작점·끝점·샘플 픽셀을 움직이며 색이 결정되는 과정을 살펴보세요.

직접 조작
렌더러 준비 중
Color Tabled = p2 − p1p − p1p1 + t·dP1P2P

점을 드래그하거나 방향키로 이동

d = p2 − p1(420, 150)p − p1(225, −27)dot(p − p1, d) / dot(d, d)90,450 / 198,900 = 0.455u = clamp(t, 0, 1)Table index512
Color Stops

캔버스 전체를 덮는 Shape에 Linear Gradient를 적용한 결과입니다.

  • P1/P2: Gradient 축의 시작점/끝점
  • P: 샘플 픽셀
  • 축의 색상 띠: Color Table
  • 원형 마커: Color Stop
  • 벡터 선: p - p1
  • 점선: 축으로의 사영
  • 투영점: p1 + t·d
  • 조회 마커: 범위 처리 후 u의 테이블 위치
  • t = dot(p - p1, d) / dot(d, d)
    • d = p2 - p1: p1에서 p2로 향하는 Gradient의 방향 벡터
    • dot(p - p1, d): 픽셀 p가 Gradient 축 방향으로 얼마나 떨어져 있는지
    • dot(d, d): 축 길이의 제곱이다. 분자를 이 값으로 나누어 Gradient 축을 기준으로 한 상대 위치
    • t = 0은 p1, t = 1은 p2, 0 < t < 1은 두 점 사이를 뜻함

실제 Shape에 Linear Gradient를 적용하는 흐름은 다음과 같습니다.

Fill::ColorStop stops[] = {
    {0.0f, 255, 90, 54, 255},
    {0.5f, 255, 209, 102, 255},
    {1.0f, 23, 107, 255, 255}
};

auto gradient = LinearGradient::gen();
gradient->linear(130.0f, 105.0f, 550.0f, 255.0f);
gradient->colorStops(stops, 3);

auto shape = Shape::gen();
shape->appendRect(0.0f, 0.0f, 680.0f, 360.0f);
shape->fill(gradient);

canvas->add(shape);

호출이 성공하면 Shape::fill()에서 Gradient의 소유권이 Shape로, Canvas::add()에서 Shape의 소유권이 Canvas로 이전됩니다.

  • 위 API 호출에서 LinearGradient::linear()는 p1, p2를 LinearGradient에 저장
  • Shape::fill()은 Gradient를 Shape에 연결
  • 이 시점에는 아직 SW Renderer의 계수가 계산되지 않음
    • 초기 준비 또는 갱신 단계에서 _prepareLinear()가 호출되면, API로 설정한 값이 렌더링에 사용할 SwFill::SwLinear 구조체로 변환
struct SwLinear {
    float dx, dy;
    float offset;
};

초기 준비와 이후 갱신에서 SwLinear가 채워지는 흐름은 다음과 같습니다.

Canvas::update()
→ Canvas::Impl::update()
→ SceneImpl::update()
→ ShapeImpl::update()
→ SwRenderer::prepare()
→ SwRenderer::prepareCommon()
→ TaskScheduler::request()
→ SwShapeTask::run()
→ fillPrepare()
→ _prepareLinear()

_prepareLinear()는 LinearGradient에서 p1, p2를 가져와 dx, dy, offset을 계산합니다.

bool _prepareLinear(SwFill* fill, const LinearGradient* linear, const Matrix& pTransform)
{
    float x1, x2, y1, y2;
    linear->linear(&x1, &y1, &x2, &y2);

    // d = p2 - p1
    fill->linear.dx = x2 - x1;
    fill->linear.dy = y2 - y1;

    // len = dot(d, d): Gradient 축 길이의 제곱
    auto len = fill->linear.dx * fill->linear.dx + fill->linear.dy * fill->linear.dy;

    if (len < FLOAT_EPSILON) {
        // p1과 p2가 같으면 마지막 Color Stop의 색을 사용하는 단색으로 처리한다.
        if (tvg::zero(fill->linear.dx) && tvg::zero(fill->linear.dy)) {
            fill->solid = true;
        }
        return true;
    }

    // dx, dy = d / dot(d, d)
    fill->linear.dx /= len;
    fill->linear.dy /= len;

    // t = dx * x + dy * y + offset에서 p1의 t가 0이 되도록
    // p1을 대입한 값인 dx * x1 + dy * y1을 빼준다.
    fill->linear.offset = -fill->linear.dx * x1 - fill->linear.dy * y1;

    // Shape와 Gradient의 transform에 대한 역변환을 계수에 미리 반영한다.
    const auto& transform = pTransform * linear->transform();

    Matrix itransform;
    if (!inverse(&transform, &itransform)) return false;

    fill->linear.offset += fill->linear.dx * itransform.e13 + fill->linear.dy * itransform.e23;

    auto dx = fill->linear.dx;
    fill->linear.dx = dx * itransform.e11 + fill->linear.dy * itransform.e21;
    fill->linear.dy = dx * itransform.e12 + fill->linear.dy * itransform.e22;

    return true;
}

준비가 끝난 뒤 실제 렌더링에서는 다음 흐름으로 fillLinear()가 호출됩니다.

Shape가 단순한 사각형이면 Rect 경로를, 일반 경로이면 RLE 경로를 사용합니다.

Canvas::draw()
→ Canvas::Impl::draw()
→ SceneImpl::render()
→ ShapeImpl::render()
→ SwRenderer::renderShape()
→ rasterGradientShape()
→ _rasterLinearGradientRect() 또는 _rasterLinearGradientRle()
→ FillLinear::operator()()
→ fillLinear()

아래는 일반 블렌딩 경로에서 사용하는 fillLinear() 함수 전체입니다.

void fillLinear(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x, uint32_t len, SwBlenderA op, uint8_t a)
{
    //Rotation
    float rx = x + 0.5f;  //Pixel center x-coordinate
    float ry = y + 0.5f;  //Pixel center y-coordinate

    // 첫 픽셀의 상대 위치를 Color Table index로 변환한다.
    // dx * rx + dy * ry + offset은 dot(p - p1, d) / dot(d, d)와 같다.
    float t = (fill->linear.dx * rx + fill->linear.dy * ry + fill->linear.offset) * (SW_COLOR_TABLE - 1);

    // 스캔라인에서 다음 픽셀로 이동할 때 Color Table index의 변화량이다.
    float inc = (fill->linear.dx) * (SW_COLOR_TABLE - 1);

    // inc가 0이면 스캔라인에서 t가 변하지 않는다.
    // Color Table을 한 번만 조회해 구간 전체에 같은 색을 적용한다.
    if (tvg::zero(inc)) {
        auto color = _fixedPixel(fill, static_cast<int32_t>(t * FIXPT_SIZE));
        for (uint32_t i = 0; i < len; ++i, ++dst) {
            *dst = op(color, *dst, a);
        }
        return;
    }

    // t와 inc를 고정소수점 int32_t로 변환할 때의 오버플로를 방지한다.
    // FIXPT_BITS만큼의 변환 공간과 한 비트의 여유를 둔 안전 범위다.
    auto vMax = static_cast<float>(INT32_MAX >> (FIXPT_BITS + 1));
    auto vMin = -vMax;

    // 스캔라인 끝의 t가 안전 범위 안에 있는지 확인한다.
    auto v = t + (inc * len);

    // 안전 범위는 누적되는 t를 FIXPT_SIZE만큼 확대한 뒤에도
    // int32_t가 오버플로하지 않는 vMin ~ vMax 범위다.
    // 이 범위 안에서는 고정소수점으로 Color Table을 조회한다.
    if (v < vMax && v > vMin) {
        auto t2 = static_cast<int32_t>(t * FIXPT_SIZE);
        auto inc2 = static_cast<int32_t>(inc * FIXPT_SIZE);
        for (uint32_t j = 0; j < len; ++j, ++dst) {
            *dst = op(_fixedPixel(fill, t2), *dst, a);  // 고정소수점 index로 Color Table 조회
            t2 += inc2;
        }
    // 안전 범위를 벗어나면 부동소수점으로 Color Table을 조회한다.
    } else {
        uint32_t counter = 0;
        while (counter++ < len) {
            *dst = op(_pixel(fill, t / SW_COLOR_TABLE), *dst, a);  // 부동소수점 위치로 Color Table 조회
            ++dst;
            t += inc;
        }
    }
}

1.3. Radial Gradient

RadialGradient는 RadialGradient::gen()으로 생성, RadialGradient::radial()로 시작 원과 끝 원을 설정합니다.

/**
 * @brief Sets the radial gradient attributes.
 *
 * The radial gradient is defined by the end circle with a center (@p cx, @p cy) and a radius @p r and
 * the start circle with a center/focal point (@p fx, @p fy) and a radius @p fr.
 * The gradient will be rendered such that the gradient stop at an offset of 100% aligns with the edge of the end circle
 * and the stop at an offset of 0% aligns with the edge of the start circle.
 *
 * @param[in] cx The horizontal coordinate of the center of the end circle.
 * @param[in] cy The vertical coordinate of the center of the end circle.
 * @param[in] r The radius of the end circle.
 * @param[in] fx The horizontal coordinate of the center of the start circle.
 * @param[in] fy The vertical coordinate of the center of the start circle.
 * @param[in] fr The radius of the start circle.
 *
 * @retval Result::InvalidArguments in case the radius @p r or @p fr value is negative.
 *
 * @note In case the radius @p r is zero, an object is filled with a single color using the last color specified in the Fill::colorStops().
 * @note In case the focal point (@p fx and @p fy) lies outside the end circle, it is projected onto the edge of the end circle.
 * @note If the start circle doesn't fully fit inside the end circle (after possible repositioning), the @p fr is reduced accordingly.
 */
Result RadialGradient::radial(float cx, float cy, float r, float fx, float fy, float fr) noexcept;
  • (cx, cy), r: Color Stop offset 1.0이 놓이는 끝 원의 중심과 반지름
  • (fx, fy), fr: Color Stop offset 0.0이 놓이는 시작 원의 중심과 반지름

Radial Gradient는 하나의 중심과 반지름만 사용하는 단순한 형태보다 범위가 넓습니다.

  • 시작 원은 초점 (fx, fy)와 반지름 fr로, 끝 원은 중심 (cx, cy)와 반지름 r로 정의
  • 두 원 사이에서 원의 중심과 반지름이 함께 변하며 색이 보간됨

Radial Gradient

원과 초점을 움직이고, 세 단계로 샘플 픽셀의 색을 찾아보세요.

직접 조작
렌더러 준비 중

P는 색을 구할 픽셀입니다. 먼저 P − F를 구합니다.

q = P − F보간 원 중심원 반지름Color Table보간 원 중심반지름tPad(t) = 1d = C − F시작 원 · 0끝 원 · 1CFRFRP

점을 드래그하거나 방향키로 이동

시작 원 F / FR(310, 145) / 20끝 원 C / R(365, 185) / 145입력 PP에서 구한 t0.65u = clamp(t, 0, 1)0.65Table index665
q = P − Fd = C − FΔr = r − fr

중심 = F + t(C − F), 반지름 = fr + t(r − fr)

0 ≤ t ≤ 1이면 중심은 F–C 선분 위, 범위 밖이면 연장선 위에 있습니다.

|q − t·d|² = (fr + t·Δr)²

진행률 후보 tP까지 거리원 반지름거리와 반지름 비교
원 중심 = F + t(C − F)반지름 = fr + t(r − fr)|P − 원 중심|² = 반지름²
Color Stops

캔버스 전체를 덮는 Shape에 Radial Gradient를 적용한 결과입니다.

  • P: 샘플 픽셀
  • F / FR: 시작 원의 중심/반지름 (진행률 0)
  • C / R: 끝 원의 중심/반지름 (진행률 1)
  • t: 중심과 반지름을 보간해 P를 지나는 원을 찾은 진행률
  • 2번 버튼: 원을 찾는 과정 다시 재생
  • 각 픽셀에서 t를 구하려면 픽셀이 이 두 원 사이의 어느 지점에 놓이는지 계산이 필요
  • SW Renderer는 이 관계를 이차방정식으로 만들고 해를 구함
  • 한 스캔라인에서 다음 픽셀로 이동할 때 계수의 변화량을 누적하여, 매 픽셀마다 모든 항을 처음부터 다시 계산하지 않음
  • 이차항이 매우 작은 특수한 경우에는 별도의 선형 계산 경로를 사용

API 호출 흐름은 다음과 같습니다.

Fill::ColorStop stops[] = {
    {0.0f, 255, 90, 54, 255},
    {0.5f, 255, 209, 102, 255},
    {1.0f, 23, 107, 255, 255}
};

auto gradient = RadialGradient::gen();
gradient->radial(365.0f, 185.0f, 145.0f, 310.0f, 145.0f, 20.0f);
gradient->colorStops(stops, 3);

auto shape = Shape::gen();
shape->appendRect(0.0f, 0.0f, 680.0f, 360.0f);
shape->fill(gradient);

canvas->add(shape);
  • 위 API 호출에서 RadialGradient::radial()은 시작 원과 끝 원을 RadialGradient에 저장, Shape::fill()은 Gradient를 Shape에 연결
  • 초기 준비 또는 갱신 단계에서 _prepareRadial()이 호출되면, 두 원의 정보가 렌더링에 사용할 SwFill::SwRadial 구조체로 변환
struct SwRadial {
    float a11, a12, a13;  // 화면 좌표를 Gradient 좌표로 옮기는 역변환 행렬
    float a21, a22, a23;
    float fx, fy, fr;     // 보정된 시작 원의 중심과 반지름
    float dx, dy, dr;     // 시작 원에서 끝 원까지의 중심과 반지름 변화량
    float invA, a;        // t를 구하는 이차방정식의 계수 A와 역수
};

SwRadial이 채워지는 흐름은 Linear Gradient와 같으며 마지막에 _prepareRadial()을 호출하는 점이 다릅니다.

Canvas::update()
→ Canvas::Impl::update()
→ SceneImpl::update()
→ ShapeImpl::update()
→ SwRenderer::prepare()
→ SwRenderer::prepareCommon()
→ TaskScheduler::request()
→ SwShapeTask::run()
→ fillPrepare()
→ _prepareRadial()

_prepareRadial()은 RadialGradient에서 두 원을 가져와 픽셀의 위치 t를 계산하는 데 필요한 계수와 역변환 행렬을 준비합니다.

bool _prepareRadial(SwFill* fill, const RadialGradient* radial, const Matrix& pTransform)
{
    float cx, cy, r, fx, fy, fr;
    radial->radial(&cx, &cy, &r, &fx, &fy, &fr);

    // 초점이 끝 원 밖에 있으면 안쪽으로 옮기고 시작 반지름을 조정한다.
    // 끝 원의 반지름이 너무 작아 유효한 Gradient를 만들 수 없으면 단색으로 처리한다.
    if ((fill->solid = !CONST_RADIAL(radial)->correct(fx, fy, fr))) return true;

    // 시작 원에서 끝 원까지 반지름과 중심이 변하는 양을 저장한다.
    fill->radial.dr = r - fr;
    fill->radial.dx = cx - fx;
    fill->radial.dy = cy - fy;
    fill->radial.fr = fr;
    fill->radial.fx = fx;
    fill->radial.fy = fy;

    // |p - (f + t·d)| = fr + t·dr을 풀 때 사용하는 이차항 A다.
    fill->radial.a = fill->radial.dr * fill->radial.dr
                   - fill->radial.dx * fill->radial.dx
                   - fill->radial.dy * fill->radial.dy;

    // A가 상대 정밀도 기준보다 작으면 나눗셈 안정성을 위해 최솟값으로 보정한다.
    auto precision = std::max(1e-5f,
                              std::max(fill->radial.dr * fill->radial.dr, 1.0f) * 1e-4f);

    if (fill->radial.a < precision) fill->radial.a = precision;
    fill->radial.invA = 1.0f / fill->radial.a;

    // Shape와 Gradient의 transform을 합친 뒤 역행렬을 저장한다.
    // fillRadial()은 이 행렬로 화면의 픽셀을 Gradient 좌표계로 옮긴다.
    const auto& transform = pTransform * radial->transform();

    Matrix itransform;
    if (!inverse(&transform, &itransform)) return false;

    fill->radial.a11 = itransform.e11;
    fill->radial.a12 = itransform.e12;
    fill->radial.a13 = itransform.e13;
    fill->radial.a21 = itransform.e21;
    fill->radial.a22 = itransform.e22;
    fill->radial.a23 = itransform.e23;

    return true;
}

준비가 끝난 뒤 실제 렌더링에서는 Rectangle 또는 RLE 경로를 거쳐 fillRadial()이 호출됩니다.

Canvas::draw()
→ Canvas::Impl::draw()
→ SceneImpl::render()
→ ShapeImpl::render()
→ SwRenderer::renderShape()
→ rasterGradientShape()
→ _rasterRadialGradientRect() 또는 _rasterRadialGradientRle()
→ FillRadial::operator()()
→ fillRadial()
void fillRadial(const SwFill* fill, uint32_t* dst, uint32_t y, uint32_t x,
                uint32_t len, SwBlenderA op, uint8_t a)
{
    // 이차항 A가 매우 작으면 이차식을 선형식으로 단순화해 t를 구한다.
    if (fill->radial.a < RADIAL_A_THRESHOLD) {
        auto radial = &fill->radial;

        // 스캔라인 첫 픽셀의 중심을 Gradient 좌표계로 역변환한 뒤
        // 시작 원의 중심을 원점으로 하는 상대 좌표를 만든다.
        auto rx = (x + 0.5f) * radial->a11
                + (y + 0.5f) * radial->a12 + radial->a13 - radial->fx;
        auto ry = (x + 0.5f) * radial->a21
                + (y + 0.5f) * radial->a22 + radial->a23 - radial->fy;

        for (uint32_t i = 0; i < len; ++i, ++dst) {
            // 두 원의 관계를 나타내는 식에서 현재 픽셀의 위치 t를 구한다.
            auto x0 = 0.5f * (rx * rx + ry * ry - radial->fr * radial->fr)
                    / (radial->dr * radial->fr + rx * radial->dx + ry * radial->dy);

            // t에 해당하는 Color Table 색을 조회하고 Destination과 Alpha Blending한다.
            *dst = op(_pixel(fill, x0), *dst, a);

            // 화면에서 x가 한 픽셀 이동한 값을 역변환 좌표에 누적한다.
            rx += radial->a11;
            ry += radial->a21;
        }
    } else {
        float b, deltaB, det, deltaDet, deltaDeltaDet;

        // 첫 픽셀의 이차식 계수와 다음 픽셀에서 더할 변화량을 계산한다.
        _calculateCoefficients(fill, x, y, b, deltaB, det, deltaDet, deltaDeltaDet);

        for (uint32_t i = 0; i < len; ++i, ++dst) {
            // 이차방정식의 해 t = sqrt(det) - b로 Color Table을 조회한다.
            *dst = op(_pixel(fill, sqrtf(det) - b), *dst, a);

            // 다음 픽셀은 미리 구한 변화량을 누적해 계수를 갱신한다.
            det += deltaDet;
            deltaDet += deltaDeltaDet;
            b += deltaB;
        }
    }
}

1.4. RLE + FillGradient

RLE Gradient Fill

한 픽셀씩 또는 span 단위로 진행하며 Gradient가 채워지는 과정을 확인하세요.

직접 조작

RLE 로 구성된 Shape 에 Gradient 를 적용하는 예제입니다.

1.5 Gradient AA (Repeat)

Repeat 경계 Anti-Aliasing

반복 길이와 방향을 바꾸며 경계 보간 전후를 비교하세요.

초기화 중

p1–p2 길이Linear Gradient의 한 반복 주기

initial margin = floor(800 / 72) = 11 entries
적용 전
_applyAA()
적용 전
_applyAA()
initial margin
11 entries
begin margin
11 entries
end margin
11 entries

전체 Color Table 아래에서1023 → 0 경계와 실제로 교체되는 margin을 확대해 비교할 수 있다.

2. Conic Gradient

Conic Gradient도 color table을 사용하지만 각도 경계가 다시 만나는 seam에서 별도 AA 처리가 필요합니다. 구현 과정과 품질 개선은 Conic Gradient seam AA 이슈 사례로 분리했습니다.