gradient fixes

This commit is contained in:
Martín Lucas Golini
2026-06-02 02:33:49 -03:00
parent 27bfd11b51
commit 312ea199a3
3 changed files with 67 additions and 151 deletions

View File

@@ -19,8 +19,9 @@ class EE_API LinearGradientDrawable : public Graphics::Drawable {
GradientUnit unit{ CSS::StyleSheetLength::Percentage };
ColorStop() : color( Color::White ) {}
ColorStop( Float val, const Color& col, GradientUnit u = CSS::StyleSheetLength::Percentage ) :
value( val ), color( col ), unit( u ) {}
ColorStop( Float val, const Color& col,
GradientUnit u = CSS::StyleSheetLength::Percentage ) :
color( col ), value( val ), unit( u ) {}
Float getNormalized( Float gradientLineLength ) const {
if ( unit == CSS::StyleSheetLength::Percentage )

View File

@@ -20,8 +20,9 @@ class EE_API RadialGradientDrawable : public Graphics::Drawable {
GradientUnit unit{ CSS::StyleSheetLength::Percentage };
ColorStop() : color( Color::White ) {}
ColorStop( Float val, const Color& col, GradientUnit u = CSS::StyleSheetLength::Percentage ) :
value( val ), color( col ), unit( u ) {}
ColorStop( Float val, const Color& col,
GradientUnit u = CSS::StyleSheetLength::Percentage ) :
color( col ), value( val ), unit( u ) {}
Float getNormalized( Float gradientRayLength ) const {
if ( unit == CSS::StyleSheetLength::Percentage )

View File

@@ -48,40 +48,20 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
Float cx = w * 0.5f;
Float cy = h * 0.5f;
Float tVals[4];
int validCount = 0;
auto addT = [&]( Float t, Float px, Float py ) {
if ( !std::isfinite( t ) )
return;
if ( px >= -0.001f && px <= w + 0.001f && py >= -0.001f && py <= h + 0.001f ) {
tVals[validCount++] = t;
}
};
if ( std::abs( dx ) > 0.0001f ) {
Float tLeft = -cx / dx;
addT( tLeft, 0, cy + tLeft * dy );
Float tRight = ( w - cx ) / dx;
addT( tRight, w, cy + tRight * dy );
}
if ( std::abs( dy ) > 0.0001f ) {
Float tTop = -cy / dy;
addT( tTop, cx + tTop * dx, 0 );
Float tBottom = ( h - cy ) / dy;
addT( tBottom, cx + tBottom * dx, h );
}
if ( validCount < 2 )
return;
Float tMin = tVals[0];
Float tMax = tVals[0];
for ( int i = 1; i < validCount; i++ ) {
if ( tVals[i] < tMin )
tMin = tVals[i];
if ( tVals[i] > tMax )
tMax = tVals[i];
// CSS: gradient-line endpoints are found by projecting each corner
// of the box onto the gradient direction. For diagonal angles this
// gives a corner-to-corner line, whereas intersecting with the four
// edges would produce a shorter line — and degenerate perpendicular
// segments near the corners.
Float corners[4][2] = { { 0, 0 }, { w, 0 }, { w, h }, { 0, h } };
Float tMin = ( corners[0][0] - cx ) * dx + ( corners[0][1] - cy ) * dy;
Float tMax = tMin;
for ( int i = 1; i < 4; i++ ) {
Float t = ( corners[i][0] - cx ) * dx + ( corners[i][1] - cy ) * dy;
if ( t < tMin )
tMin = t;
if ( t > tMax )
tMax = t;
}
if ( std::abs( tMax - tMin ) < 0.0001f )
@@ -99,7 +79,6 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
std::sort( stops.begin(), stops.end(),
[]( const ColorStop& a, const ColorStop& b ) { return a.value < b.value; } );
Float px = -dy;
Float py = dx;
@@ -108,6 +87,29 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
sBR->setBlendMode( BlendMode::Alpha() );
sBR->quadsBegin();
// Helper: compute a perpendicular segment that always spans the box.
// When the perpendicular line through (gx,gy) only grazes a corner we
// extend the segment so the quad-to-quad tiling fills the whole box.
auto perpSeg = [&]( Float gx, Float gy ) {
Float ds[4];
// signed dist along perpendicular from (gx,gy) to each corner
ds[0] = ( -gx ) * px + ( -gy ) * py;
ds[1] = ( w - gx ) * px + ( -gy ) * py;
ds[2] = ( w - gx ) * px + ( h - gy ) * py;
ds[3] = ( -gx ) * px + ( h - gy ) * py;
Float dMin = ds[0];
Float dMax = ds[0];
for ( int i = 1; i < 4; i++ ) {
if ( ds[i] < dMin )
dMin = ds[i];
if ( ds[i] > dMax )
dMax = ds[i];
}
Vector2f a( gx + dMin * px + position.x, gy + dMin * py + position.y );
Vector2f b( gx + dMax * px + position.x, gy + dMax * py + position.y );
return std::make_pair( a, b );
};
if ( isRepeating() ) {
Float firstPos = stops.front().value;
Float lastPos = stops.back().value;
@@ -143,16 +145,14 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
Float frac1 = ( clip1 - p0 ) / bw;
const Color& sc0 = stops[i].color;
const Color& sc1 = stops[i + 1].color;
Color cc0(
(Uint8)( (Float)sc0.r + frac0 * (Float)( sc1.r - sc0.r ) ),
(Uint8)( (Float)sc0.g + frac0 * (Float)( sc1.g - sc0.g ) ),
(Uint8)( (Float)sc0.b + frac0 * (Float)( sc1.b - sc0.b ) ),
(Uint8)( (Float)sc0.a + frac0 * (Float)( sc1.a - sc0.a ) ) );
Color cc1(
(Uint8)( (Float)sc0.r + frac1 * (Float)( sc1.r - sc0.r ) ),
(Uint8)( (Float)sc0.g + frac1 * (Float)( sc1.g - sc0.g ) ),
(Uint8)( (Float)sc0.b + frac1 * (Float)( sc1.b - sc0.b ) ),
(Uint8)( (Float)sc0.a + frac1 * (Float)( sc1.a - sc0.a ) ) );
Color cc0( (Uint8)( (Float)sc0.r + frac0 * (Float)( sc1.r - sc0.r ) ),
(Uint8)( (Float)sc0.g + frac0 * (Float)( sc1.g - sc0.g ) ),
(Uint8)( (Float)sc0.b + frac0 * (Float)( sc1.b - sc0.b ) ),
(Uint8)( (Float)sc0.a + frac0 * (Float)( sc1.a - sc0.a ) ) );
Color cc1( (Uint8)( (Float)sc0.r + frac1 * (Float)( sc1.r - sc0.r ) ),
(Uint8)( (Float)sc0.g + frac1 * (Float)( sc1.g - sc0.g ) ),
(Uint8)( (Float)sc0.b + frac1 * (Float)( sc1.b - sc0.b ) ),
(Uint8)( (Float)sc0.a + frac1 * (Float)( sc1.a - sc0.a ) ) );
Float tc0 = tMin + clip0 * txLen;
Float tc1 = tMin + clip1 * txLen;
@@ -161,69 +161,13 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
Float gx1 = cx + tc1 * dx;
Float gy1 = cy + tc1 * dy;
Float ptVals0[4];
int ptCount0 = 0;
auto addPT0 = [&]( Float pt, Float qx, Float qy ) {
if ( !std::isfinite( pt ) )
return;
if ( qx >= -0.001f && qx <= w + 0.001f && qy >= -0.001f && qy <= h + 0.001f )
ptVals0[ptCount0++] = pt;
};
if ( std::abs( px ) > 0.0001f ) {
Float ptLeft = -gx0 / px;
addPT0( ptLeft, 0, gy0 + ptLeft * py );
Float ptRight = ( w - gx0 ) / px;
addPT0( ptRight, w, gy0 + ptRight * py );
}
if ( std::abs( py ) > 0.0001f ) {
Float ptTop = -gy0 / py;
addPT0( ptTop, gx0 + ptTop * px, 0 );
Float ptBottom = ( h - gy0 ) / py;
addPT0( ptBottom, gx0 + ptBottom * px, h );
}
if ( ptCount0 < 2 )
continue;
Float ptMin0 = ptVals0[0], ptMax0 = ptVals0[0];
for ( int j = 1; j < ptCount0; j++ ) {
if ( ptVals0[j] < ptMin0 )
ptMin0 = ptVals0[j];
if ( ptVals0[j] > ptMax0 )
ptMax0 = ptVals0[j];
}
Vector2f a0( gx0 + ptMin0 * px + position.x, gy0 + ptMin0 * py + position.y );
Vector2f b0( gx0 + ptMax0 * px + position.x, gy0 + ptMax0 * py + position.y );
auto seg0 = perpSeg( gx0, gy0 );
Vector2f a0 = seg0.first;
Vector2f b0 = seg0.second;
Float ptVals1[4];
int ptCount1 = 0;
auto addPT1 = [&]( Float pt, Float qx, Float qy ) {
if ( !std::isfinite( pt ) )
return;
if ( qx >= -0.001f && qx <= w + 0.001f && qy >= -0.001f && qy <= h + 0.001f )
ptVals1[ptCount1++] = pt;
};
if ( std::abs( px ) > 0.0001f ) {
Float ptLeft = -gx1 / px;
addPT1( ptLeft, 0, gy1 + ptLeft * py );
Float ptRight = ( w - gx1 ) / px;
addPT1( ptRight, w, gy1 + ptRight * py );
}
if ( std::abs( py ) > 0.0001f ) {
Float ptTop = -gy1 / py;
addPT1( ptTop, gx1 + ptTop * px, 0 );
Float ptBottom = ( h - gy1 ) / py;
addPT1( ptBottom, gx1 + ptBottom * px, h );
}
if ( ptCount1 < 2 )
continue;
Float ptMin1 = ptVals1[0], ptMax1 = ptVals1[0];
for ( int j = 1; j < ptCount1; j++ ) {
if ( ptVals1[j] < ptMin1 )
ptMin1 = ptVals1[j];
if ( ptVals1[j] > ptMax1 )
ptMax1 = ptVals1[j];
}
Vector2f a1( gx1 + ptMin1 * px + position.x, gy1 + ptMin1 * py + position.y );
Vector2f b1( gx1 + ptMax1 * px + position.x, gy1 + ptMax1 * py + position.y );
auto seg1 = perpSeg( gx1, gy1 );
Vector2f a1 = seg1.first;
Vector2f b1 = seg1.second;
Color fc0 = ( mColor.a == 255 ) ? cc0 : Color( cc0 ).blendAlpha( mColor.a );
Color fc1 = ( mColor.a == 255 ) ? cc1 : Color( cc1 ).blendAlpha( mColor.a );
@@ -232,6 +176,12 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
}
}
} else {
// CSS: first and last color extend to the box boundaries (pad mode)
if ( stops.front().value > 0.f )
stops.insert( stops.begin(), ColorStop( 0.f, stops.front().color ) );
if ( stops.back().value < 1.f )
stops.push_back( ColorStop( 1.f, stops.back().color ) );
struct PerpSeg {
Vector2f a;
Vector2f b;
@@ -240,49 +190,13 @@ void LinearGradientDrawable::draw( const Vector2f& position, const Sizef& size )
segments.reserve( stops.size() );
for ( size_t i = 0; i < stops.size(); i++ ) {
PerpSeg seg;
Float t = tMin + stops[i].value * txLen;
Float gx = cx + t * dx;
Float gy = cy + t * dy;
Float ptVals[4];
int ptCount = 0;
auto addPT = [&]( Float pt, Float qx, Float qy ) {
if ( !std::isfinite( pt ) )
return;
if ( qx >= -0.001f && qx <= w + 0.001f && qy >= -0.001f && qy <= h + 0.001f ) {
ptVals[ptCount++] = pt;
}
};
if ( std::abs( px ) > 0.0001f ) {
Float ptLeft = -gx / px;
addPT( ptLeft, 0, gy + ptLeft * py );
Float ptRight = ( w - gx ) / px;
addPT( ptRight, w, gy + ptRight * py );
}
if ( std::abs( py ) > 0.0001f ) {
Float ptTop = -gy / py;
addPT( ptTop, gx + ptTop * px, 0 );
Float ptBottom = ( h - gy ) / py;
addPT( ptBottom, gx + ptBottom * px, h );
}
if ( ptCount < 2 )
return;
Float ptMin = ptVals[0];
Float ptMax = ptVals[0];
for ( int j = 1; j < ptCount; j++ ) {
if ( ptVals[j] < ptMin )
ptMin = ptVals[j];
if ( ptVals[j] > ptMax )
ptMax = ptVals[j];
}
seg.a = Vector2f( gx + ptMin * px + position.x, gy + ptMin * py + position.y );
seg.b = Vector2f( gx + ptMax * px + position.x, gy + ptMax * py + position.y );
auto segPair = perpSeg( gx, gy );
PerpSeg seg;
seg.a = segPair.first;
seg.b = segPair.second;
segments.push_back( seg );
}