blob: 012aa16942c3e34da95c1c0c30f5bdffa40f8bda [file]
#include "rive/shapes/paint/paint_image.hpp"
#include "rive/shapes/paint/shape_paint.hpp"
#include "rive/assets/file_asset.hpp"
#include "rive/assets/image_asset.hpp"
#include "rive/renderer.hpp"
#include "rive/core_context.hpp"
using namespace rive;
StatusCode PaintImage::import(ImportStack& importStack)
{
// The image is optional, so registration is best-effort: unlike the Image
// node we must not fail the import when there's no backboard importer. The
// backboard importer safely skips out-of-range indices during resolve.
registerReferencer(importStack);
return Super::import(importStack);
}
bool PaintImage::validate(CoreContext* context)
{
if (!Super::validate(context))
{
return false;
}
auto coreObject = context->resolve(parentId());
// we know it's not nullptr from Super::validate
assert(coreObject != nullptr);
return coreObject->is<ShapePaint>();
}
Core* PaintImage::clone() const
{
PaintImage* twin = PaintImageBase::clone()->as<PaintImage>();
// The generated clone copies the imageAssetId property but not the resolved
// asset, so carry it over (mirrors Image::clone). Without this a nested
// artboard instance loses its paint image.
if (m_fileAsset != nullptr)
{
twin->setAsset(m_fileAsset);
}
return twin;
}
uint32_t PaintImage::assetId() { return imageAssetId(); }
void PaintImage::invalidatePaint()
{
if (parent() != nullptr && parent()->is<ShapePaint>())
{
parent()->as<ShapePaint>()->invalidateRendering();
}
}
void PaintImage::setAsset(rcp<FileAsset> asset)
{
if (asset != nullptr && asset->is<ImageAsset>())
{
FileAssetReferencer::setAsset(asset);
invalidatePaint();
}
}
// The texture usually lands after setAsset -- decoding is async, and in the
// editor the bytes arrive from the CDN later still. ImageAsset publishes it by
// calling assetUpdated() on its referencers, so re-invalidate here; otherwise a
// paint that recorded "no image" on its first draw keeps that cleared state
// until some unrelated redraw happens to dirty it.
void PaintImage::assetUpdated() { invalidatePaint(); }
ImageAsset* PaintImage::imageAsset() const
{
return static_cast<ImageAsset*>(m_fileAsset.get());
}
ImageSampler PaintImage::imageSampler() const
{
// Clamp file values so the key stays inside the backends' sampler tables.
auto filterValue = [](uint32_t value) {
return value <= (uint32_t)ImageFilter::nearest
? static_cast<ImageFilter>(value)
: ImageFilter::bilinear;
};
auto wrapValue = [](uint32_t value) {
return value <= (uint32_t)ImageWrap::mirror
? static_cast<ImageWrap>(value)
: ImageWrap::clamp;
};
ImageSampler sampler = ImageSampler::LinearClamp();
if (ImageAsset* asset = imageAsset())
{
sampler.filter = filterValue(asset->samplerFilter());
sampler.wrapX = wrapValue(asset->samplerWrapX());
sampler.wrapY = wrapValue(asset->samplerWrapY());
}
// Paint values are offset by one, zero means inherit from the asset.
if (imageSamplerFilter() != 0)
{
sampler.filter = filterValue(imageSamplerFilter() - 1);
}
if (imageSamplerWrapX() != 0)
{
sampler.wrapX = wrapValue(imageSamplerWrapX() - 1);
}
if (imageSamplerWrapY() != 0)
{
sampler.wrapY = wrapValue(imageSamplerWrapY() - 1);
}
return sampler;
}
bool PaintImage::applyTo(RenderPaint* paint, const AABB& bounds)
{
ImageAsset* asset = imageAsset();
RenderImage* renderImage =
asset != nullptr ? asset->renderImage() : nullptr;
if (renderImage == nullptr)
{
// The asset was cleared (or replaced by one that hasn't decoded yet).
// The caller clears the stale texture off the (persistent) paint.
return false;
}
// Maps the image's normalized texture space (0..1) into the path's
// coordinate space. The base fits the image across the bounds once; the
// user transform (scale/offset/rotation about the bounds center) modifies
// it. At defaults it reproduces the plain fit; with scale < 1 and
// wrap=repeat/mirror it tiles. Base tile size: shape bounds (fill) or the
// image's native pixel size (original). 0 = fill, 1 = original.
bool original = imageSizeMode() == 1;
float baseW = original ? (float)renderImage->width() : bounds.width();
float baseH = original ? (float)renderImage->height() : bounds.height();
float cx = bounds.left() + bounds.width() * 0.5f;
float cy = bounds.top() + bounds.height() * 0.5f;
Mat2D imageTransform =
Mat2D::fromTranslate(cx, cy) * Mat2D::fromRotation(imageRotation()) *
Mat2D::fromTranslate(imageOffsetX() * baseW, imageOffsetY() * baseH) *
Mat2D::fromScale(baseW * imageScaleX(), baseH * imageScaleY()) *
Mat2D::fromTranslate(-0.5f, -0.5f);
paint->modulatedImage(renderImage, imageSampler(), imageTransform);
return true;
}