blob: 507f2bd176f72a788df8e39aeb40187ee629a459 [file] [log] [blame] [edit]
/*
* Copyright 2022 Rive
*/
#include "pls_paint.hpp"
#include "rive/pls/pls_image.hpp"
namespace rive::pls
{
PLSPaint::PLSPaint() {}
PLSPaint::~PLSPaint() {}
rcp<PLSGradient> PLSGradient::MakeLinear(float sx,
float sy,
float ex,
float ey,
const ColorInt colors[], // [count]
const float stops[], // [count]
size_t count)
{
float2 start = {sx, sy};
float2 end = {ex, ey};
PLSGradDataArray<ColorInt> newColors(colors, count);
PLSGradDataArray<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)
{
// 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;
// Transform the stops into the range defined by the new endpoints.
newStops[0] = 0;
if (count > 2)
{
float m = 1.f / (lastStop - firstStop);
float a = -firstStop * m;
for (size_t i = 1; i < count - 1; ++i)
{
newStops[i] = std::clamp(stops[i] * m + a, newStops[i - 1], 1.f);
}
}
newStops[count - 1] = 1;
}
float2 v = end - start;
v *= 1.f / simd::dot(v, v); // dot(v, end - start) == 1
return rcp(new PLSGradient(PaintType::linearGradient,
std::move(newColors),
std::move(newStops),
count,
v.x,
v.y,
-simd::dot(v, start)));
}
rcp<PLSGradient> PLSGradient::MakeRadial(float cx,
float cy,
float radius,
const ColorInt colors[], // [count]
const float stops[], // [count]
size_t count)
{
PLSGradDataArray<ColorInt> newColors(colors, count);
PLSGradDataArray<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)
{
// 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;
}
newStops[count - 1] = 1;
}
return rcp(new PLSGradient(PaintType::radialGradient,
std::move(newColors),
std::move(newStops),
count,
cx,
cy,
radius));
}
bool PLSGradient::isOpaque() const
{
if (m_isOpaque == pls::TriState::unknown)
{
ColorInt allColors = ~0;
for (int i = 0; i < m_count; ++i)
{
allColors &= m_colors[i];
}
m_isOpaque = colorAlpha(allColors) == 0xff ? pls::TriState::yes : pls::TriState::no;
}
return m_isOpaque == pls::TriState::yes;
}
void PLSPaint::color(ColorInt color)
{
m_paintType = PaintType::solidColor;
m_simpleValue.color = color;
m_gradient.reset();
m_imageTexture.reset();
}
void PLSPaint::shader(rcp<RenderShader> shader)
{
m_gradient = static_rcp_cast<PLSGradient>(std::move(shader));
m_paintType = m_gradient ? m_gradient->paintType() : PaintType::solidColor;
// m_simpleValue.colorRampLocation is unused at this level. A new location for a this gradient's
// color ramp will decided by the render context every frame.
m_simpleValue.color = 0xff000000;
m_imageTexture.reset();
}
void PLSPaint::image(rcp<const PLSTexture> imageTexture, float opacity)
{
m_paintType = PaintType::image;
m_simpleValue.imageOpacity = opacity;
m_gradient.reset();
m_imageTexture = std::move(imageTexture);
}
void PLSPaint::clipUpdate(uint32_t outerClipID)
{
m_paintType = PaintType::clipUpdate;
m_simpleValue.outerClipID = outerClipID;
m_gradient.reset();
m_imageTexture.reset();
}
bool PLSPaint::getIsOpaque() const
{
switch (m_paintType)
{
case pls::PaintType::solidColor:
return colorAlpha(m_simpleValue.color) == 0xff;
case pls::PaintType::linearGradient:
case pls::PaintType::radialGradient:
return m_gradient->isOpaque();
case pls::PaintType::image:
case pls::PaintType::clipUpdate:
return false;
}
RIVE_UNREACHABLE();
}
} // namespace rive::pls