mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 13:11:19 +01:00
fix(scripting): search first parent transform component to build script node feature: modulate opacity (#11427) 128d9d61e0 * feature: modulate opacity * fix: clang-format * fix: rust renderer has a no-op modulateOpacity * fix: no-op modulateOpacity for canvas android * feature: modulate opacity on android canvas * fix: rcp ref * fix: missing override * fix: gms * fix: make flutter_renderer match cg one * fix: josh pr feedback * fix: remove CG transparency layer * fix: save modulated gradient up-front * fix: store only one gradient ref * fix: remove specific constructor * fix: use GradDataArray! * fix: expose currentModulatedOpacity * fix: cg_factory modulated opacity value * fix: modulate negative opacity test * fix: verify double modulate negative also clamps Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com> Co-authored-by: hernan <hernan@rive.app>
227 lines
7.3 KiB
C++
227 lines
7.3 KiB
C++
/*
|
|
* Copyright 2022 Rive
|
|
*/
|
|
|
|
#include "gradient.hpp"
|
|
|
|
namespace rive::gpu
|
|
{
|
|
// Ensure the given gradient stops are in a format expected by PLS.
|
|
static bool validate_gradient_stops(const ColorInt colors[], // [count]
|
|
const float stops[], // [count]
|
|
size_t count)
|
|
{
|
|
// Stops cannot be empty.
|
|
if (count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < count; ++i)
|
|
{
|
|
// Stops must be finite, real numbers in the range [0, 1].
|
|
if (!(0 <= stops[i] && stops[i] <= 1))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
for (size_t i = 1; i < count; ++i)
|
|
{
|
|
// Stops must be ordered.
|
|
if (!(stops[i - 1] <= stops[i]))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
rcp<Gradient> Gradient::MakeLinear(float sx,
|
|
float sy,
|
|
float ex,
|
|
float ey,
|
|
const ColorInt colors[], // [count]
|
|
const float stops[], // [count]
|
|
size_t count)
|
|
{
|
|
if (!validate_gradient_stops(colors, stops, count))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
float2 start = {sx, sy};
|
|
float2 end = {ex, ey};
|
|
GradDataArray<ColorInt> newColors(colors, count);
|
|
GradDataArray<float> newStops(stops, count);
|
|
|
|
// If the stops don't begin and end on 0 and 1, transform the gradient so
|
|
// they do. This allows us to take full advantage of the gradient's range of
|
|
// pixels in the texture.
|
|
float firstStop = stops[0];
|
|
float lastStop = stops[count - 1];
|
|
if ((firstStop != 0 || lastStop != 1) &&
|
|
lastStop - firstStop > math::EPSILON)
|
|
{
|
|
// Tighten the endpoints to align with the mininum and maximum gradient
|
|
// stops.
|
|
float4 newEndpoints =
|
|
simd::precise_mix(start.xyxy,
|
|
end.xyxy,
|
|
float4{firstStop, firstStop, lastStop, lastStop});
|
|
start = newEndpoints.xy;
|
|
end = newEndpoints.zw;
|
|
newStops[0] = 0;
|
|
newStops[count - 1] = 1;
|
|
if (count > 2)
|
|
{
|
|
// Transform the stops into the range defined by the new endpoints.
|
|
float m = 1.f / (lastStop - firstStop);
|
|
float a = -firstStop * m;
|
|
for (size_t i = 1; i < count - 1; ++i)
|
|
{
|
|
newStops[i] = stops[i] * m + a;
|
|
}
|
|
|
|
// Clamp the interior stops so they remain monotonically increasing.
|
|
// newStops[0] and newStops[count - 1] are already 0 and 1, so this
|
|
// also ensures they stay within 0..1.
|
|
for (size_t i = 1; i < count - 1; ++i)
|
|
{
|
|
newStops[i] = fmaxf(newStops[i - 1], newStops[i]);
|
|
}
|
|
for (size_t i = count - 2; i != 0; --i)
|
|
{
|
|
newStops[i] = fminf(newStops[i], newStops[i + 1]);
|
|
}
|
|
}
|
|
assert(validate_gradient_stops(newColors.get(), newStops.get(), count));
|
|
}
|
|
|
|
float2 v = end - start;
|
|
v *= 1.f / simd::dot(v, v); // dot(v, end - start) == 1
|
|
return rcp(new Gradient(gpu::PaintType::linearGradient,
|
|
std::move(newColors),
|
|
std::move(newStops),
|
|
count,
|
|
v.x,
|
|
v.y,
|
|
-simd::dot(v, start)));
|
|
}
|
|
|
|
rcp<Gradient> Gradient::MakeRadial(float cx,
|
|
float cy,
|
|
float radius,
|
|
const ColorInt colors[], // [count]
|
|
const float stops[], // [count]
|
|
size_t count)
|
|
{
|
|
if (!validate_gradient_stops(colors, stops, count))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
GradDataArray<ColorInt> newColors(colors, count);
|
|
GradDataArray<float> newStops(stops, count);
|
|
|
|
// If the stops don't end on 1, scale the gradient so they do. This allows
|
|
// us to take better advantage of the gradient's full range of pixels in the
|
|
// texture.
|
|
//
|
|
// TODO: If we want to take full advantage of the gradient texture pixels,
|
|
// we could add an inner radius that specifies where t=0 begins (instead of
|
|
// assuming it begins at the center).
|
|
float lastStop = stops[count - 1];
|
|
if (lastStop != 1 && lastStop > math::EPSILON)
|
|
{
|
|
// Update the gradient to finish on 1.
|
|
newStops[count - 1] = 1;
|
|
|
|
// Scale the radius to align with the final stop.
|
|
radius *= lastStop;
|
|
|
|
// Scale the stops into the range defined by the new radius.
|
|
float inverseLastStop = 1.f / lastStop;
|
|
for (size_t i = 0; i < count - 1; ++i)
|
|
{
|
|
newStops[i] = stops[i] * inverseLastStop;
|
|
}
|
|
|
|
if (count > 1)
|
|
{
|
|
// Clamp the stops so they remain monotonically increasing.
|
|
// newStops[count - 1] is already 1, so this also ensures they stay
|
|
// within 0..1.
|
|
newStops[0] = fmaxf(0, newStops[0]);
|
|
for (size_t i = 1; i < count - 1; ++i)
|
|
{
|
|
newStops[i] = fmaxf(newStops[i - 1], newStops[i]);
|
|
}
|
|
for (size_t i = count - 2; i != -1; --i)
|
|
{
|
|
newStops[i] = fminf(newStops[i], newStops[i + 1]);
|
|
}
|
|
}
|
|
|
|
assert(validate_gradient_stops(newColors.get(), newStops.get(), count));
|
|
}
|
|
|
|
return rcp(new Gradient(gpu::PaintType::radialGradient,
|
|
std::move(newColors),
|
|
std::move(newStops),
|
|
count,
|
|
cx,
|
|
cy,
|
|
radius));
|
|
}
|
|
|
|
bool Gradient::isOpaque() const
|
|
{
|
|
if (m_isOpaque == gpu::TriState::unknown)
|
|
{
|
|
ColorInt allColors = ~0;
|
|
for (int i = 0; i < m_count; ++i)
|
|
{
|
|
allColors &= m_colors[i];
|
|
}
|
|
m_isOpaque = colorAlpha(allColors) == 0xff ? gpu::TriState::yes
|
|
: gpu::TriState::no;
|
|
}
|
|
return m_isOpaque == gpu::TriState::yes;
|
|
}
|
|
|
|
rcp<Gradient> Gradient::getModulated(float opacity) const
|
|
{
|
|
// Fast path: no modulation needed
|
|
if (opacity == 1.0f)
|
|
{
|
|
return ref_rcp(const_cast<Gradient*>(this));
|
|
}
|
|
|
|
// Check single-entry cache
|
|
if (m_lastModulatedOpacity == opacity && m_lastModulatedGradient)
|
|
{
|
|
return m_lastModulatedGradient;
|
|
}
|
|
|
|
// Create new modulated gradient
|
|
GradDataArray<ColorInt> newColors(m_count);
|
|
for (size_t i = 0; i < m_count; ++i)
|
|
{
|
|
newColors[i] = colorModulateOpacity(m_colors[i], opacity);
|
|
}
|
|
|
|
GradDataArray<float> newStops(m_stops.get(), m_count);
|
|
|
|
m_lastModulatedGradient = rcp(new Gradient(m_paintType,
|
|
std::move(newColors),
|
|
std::move(newStops),
|
|
m_count,
|
|
m_coeffs[0],
|
|
m_coeffs[1],
|
|
m_coeffs[2]));
|
|
m_lastModulatedOpacity = opacity;
|
|
|
|
return m_lastModulatedGradient;
|
|
}
|
|
|
|
} // namespace rive::gpu
|