blob: 3c95b1a2d427c9b61d5dc3e9e42007d223e3d734 [file]
/*
* Copyright 2026 Rive
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gm.hpp"
#include "gmutils.hpp"
#include "rive/renderer.hpp"
#include "assets/nomoon.png.hpp"
#include <array>
using namespace rivegm;
using namespace rive;
// Exercises the additiveness paint value (a Rive extension), which
// interpolates the srcOver composite toward additive: 0 is normal srcOver,
// 1 is fully additive (result.rgb = src + dst, destination alpha preserved).
//
// Layout: 2 columns x 6 rows of 300x300 cells on a black background.
//
// Left column (srcOver references):
// Row 0: opaque green square overlaid by a red square whose alpha ramps
// from 1 at the top to 0 at the bottom.
// Row 1: stroked variants of row 0.
// Row 2: opaque gradient square overlaid by a gradient square whose alpha
// ramps from 1 at the top to 0 at the bottom.
// Row 3: image mesh overlaid by an image mesh whose opacity ramps from 1
// at the top to 0 at the bottom (drawn as horizontal strips).
// Row 4: drawImage (image paint on a path): base image at full opacity
// under an overlapping image at 60% opacity.
//
// Right column (additive):
// Row 0: filled RGB venn circles; overlaps accumulate to
// yellow/cyan/magenta/white.
// Row 1: stroked rings (additive applies to strokes independently).
// Rows 2 & 3: same geometry as the left column, with the overlay drawn
// additively, so left vs right isolates srcOver vs additive.
//
// Row 5: the additiveness slider (0 = srcOver, 1 = fully additive). An opaque
// green square under five red strips with additiveness 0, .25, .5,
// .75, 1 (srcOver to fully additive, left to right). Left column solid
// color, right column gradient.
constexpr float CELL = 300.f;
// Draws one quad (2 triangles) of an image as a mesh.
static void draw_image_mesh_quad(Renderer* renderer,
RenderImage* img,
const AABB& dst,
const AABB& uv,
float additiveness,
float opacity)
{
Factory* factory = TestingWindow::Get()->factory();
auto pts =
factory->makeRenderBuffer(RenderBufferType::vertex,
RenderBufferFlags::mappedOnceAtInitialization,
4 * sizeof(Vec2D));
memcpy(pts->map(),
std::array<Vec2D, 4>{Vec2D{dst.left(), dst.top()},
Vec2D{dst.right(), dst.top()},
Vec2D{dst.left(), dst.bottom()},
Vec2D{dst.right(), dst.bottom()}}
.data(),
pts->sizeInBytes());
pts->unmap();
auto uvs =
factory->makeRenderBuffer(RenderBufferType::vertex,
RenderBufferFlags::mappedOnceAtInitialization,
4 * sizeof(Vec2D));
memcpy(uvs->map(),
std::array<Vec2D, 4>{Vec2D{uv.left(), uv.top()},
Vec2D{uv.right(), uv.top()},
Vec2D{uv.left(), uv.bottom()},
Vec2D{uv.right(), uv.bottom()}}
.data(),
uvs->sizeInBytes());
uvs->unmap();
auto indices =
factory->makeRenderBuffer(RenderBufferType::index,
RenderBufferFlags::mappedOnceAtInitialization,
6 * sizeof(uint16_t));
memcpy(indices->map(),
std::array<uint16_t, 6>{0, 1, 2, 1, 3, 2}.data(),
indices->sizeInBytes());
indices->unmap();
renderer->drawImageMesh(img,
ImageSampler::LinearClamp(),
pts,
uvs,
indices,
4,
6,
BlendMode::srcOver,
opacity,
additiveness);
}
// Approximates a vertical opacity ramp (1 at the top, 0 at the bottom) by
// drawing the image as a stack of mesh strips with stepped opacity.
static void draw_image_mesh_alpha_ramp(Renderer* renderer,
RenderImage* img,
const AABB& dst,
float additiveness)
{
constexpr int strips = 256;
for (int i = 0; i < strips; ++i)
{
float t0 = float(i) / strips;
float t1 = float(i + 1) / strips;
AABB stripDst = {dst.left(),
dst.top() + t0 * dst.height(),
dst.right(),
dst.top() + t1 * dst.height()};
AABB stripUV = {0.f, t0, 1.f, t1};
draw_image_mesh_quad(renderer,
img,
stripDst,
stripUV,
additiveness,
1.f - (t0 + t1) * .5f);
}
}
DEF_SIMPLE_GM_WITH_CLEAR_COLOR(additive_blend, 0xff000000, 600, 1800, renderer)
{
Factory* factory = TestingWindow::Get()->factory();
constexpr float stops[2] = {0.f, 1.f};
// Base square and overlapping overlay square within a cell at (ox, oy).
auto baseRect = [](float ox, float oy) {
return AABB{ox + 40.f, oy + 40.f, ox + 190.f, oy + 190.f};
};
auto overlayRect = [](float ox, float oy) {
return AABB{ox + 110.f, oy + 110.f, ox + 260.f, oy + 260.f};
};
// Rows 0 & 1, left: opaque green square under a red square whose alpha
// ramps 1 -> 0 top to bottom (via a gradient that only varies alpha).
auto squaresCell = [&](float ox,
float oy,
RenderPaintStyle style,
float thickness,
BlendMode overlayBlend) {
AABB base = baseRect(ox, oy);
Paint basePaint;
basePaint->style(style);
if (style == RenderPaintStyle::stroke)
{
basePaint->thickness(thickness);
}
basePaint->color(0xff00ff00);
renderer->drawPath(PathBuilder::Rect(base), basePaint);
AABB overlay = overlayRect(ox, oy);
ColorInt overlayColors[2] = {0xffff0000, 0x00ff0000};
Paint overlayPaint;
overlayPaint->style(style);
if (style == RenderPaintStyle::stroke)
{
overlayPaint->thickness(thickness);
}
overlayPaint->shader(factory->makeLinearGradient(0,
overlay.top(),
0,
overlay.bottom(),
overlayColors,
stops,
2));
overlayPaint->blendMode(overlayBlend);
renderer->drawPath(PathBuilder::Rect(overlay), overlayPaint);
};
squaresCell(0, 0, RenderPaintStyle::fill, 0.f, BlendMode::srcOver);
squaresCell(0, CELL, RenderPaintStyle::stroke, 20.f, BlendMode::srcOver);
// Rows 0 & 1, right: classic RGB venn arrangement, all circles additive.
// Two-circle overlaps accumulate to yellow/cyan/magenta, all three to
// white.
auto vennCell =
[&](float ox, float oy, RenderPaintStyle style, float thickness) {
struct Circle
{
float cx, cy;
ColorInt color;
};
constexpr float radius = 80.f;
const Circle circles[] = {
{ox + 150.f, oy + 110.f, 0xffff0000}, // red, top
{ox + 110.f, oy + 185.f, 0xff00ff00}, // green, bottom-left
{ox + 190.f, oy + 185.f, 0xff0000ff}, // blue, bottom-right
};
for (const Circle& c : circles)
{
Paint paint;
paint->style(style);
if (style == RenderPaintStyle::stroke)
{
paint->thickness(thickness);
}
paint->color(c.color);
paint->additiveness(1.f);
renderer->drawPath(PathBuilder::Circle(c.cx, c.cy, radius),
paint);
}
};
vennCell(CELL, 0, RenderPaintStyle::fill, 0.f);
vennCell(CELL, CELL, RenderPaintStyle::stroke, 40.f);
// Row 2: opaque horizontal gradient square under a vertical gradient
// square that fades opaque red -> transparent yellow. Left srcOver,
// right additive.
auto gradientCell = [&](float ox, float overlayAdditiveness) {
constexpr float oy = CELL * 2;
AABB base = baseRect(ox, oy);
ColorInt baseColors[2] = {0xff00ff00, 0xff0000ff};
Paint basePaint;
basePaint->shader(factory->makeLinearGradient(base.left(),
0,
base.right(),
0,
baseColors,
stops,
2));
renderer->drawPath(PathBuilder::Rect(base), basePaint);
AABB overlay = overlayRect(ox, oy);
ColorInt overlayColors[2] = {0xffff0000, 0x00ffff00};
Paint overlayPaint;
overlayPaint->shader(factory->makeLinearGradient(0,
overlay.top(),
0,
overlay.bottom(),
overlayColors,
stops,
2));
overlayPaint->additiveness(overlayAdditiveness);
renderer->drawPath(PathBuilder::Rect(overlay), overlayPaint);
};
gradientCell(0, /*overlayAdditiveness =*/0.f);
gradientCell(CELL, /*overlayAdditiveness =*/1.f);
// Row 3: image mesh under an overlapping image mesh whose opacity ramps
// 1 -> 0 top to bottom. Left srcOver, right additive.
auto img = LoadImage(assets::nomoon_png());
if (img != nullptr)
{
auto imageCell = [&](float ox, float overlayAdditiveness) {
constexpr float oy = CELL * 3;
draw_image_mesh_quad(renderer,
img.get(),
baseRect(ox, oy),
{0.f, 0.f, 1.f, 1.f},
/*additiveness =*/0.f,
1.f);
draw_image_mesh_alpha_ramp(renderer,
img.get(),
overlayRect(ox, oy),
overlayAdditiveness);
};
imageCell(0, /*overlayAdditiveness =*/0.f);
imageCell(CELL, /*overlayAdditiveness =*/1.f);
// Row 4: drawImage (image paint on a path): base image at full
// opacity under an overlapping image at 60% opacity. Left srcOver,
// right additive.
auto drawImageRect =
[&](const AABB& rect, float additiveness, float opacity) {
renderer->save();
renderer->translate(rect.left(), rect.top());
renderer->scale(rect.width() / img->width(),
rect.height() / img->height());
renderer->drawImage(img.get(),
ImageSampler::LinearClamp(),
BlendMode::srcOver,
opacity,
additiveness);
renderer->restore();
};
auto drawImageCell = [&](float ox, float overlayAdditiveness) {
constexpr float oy = CELL * 4;
drawImageRect(baseRect(ox, oy), /*additiveness =*/0.f, 1.f);
drawImageRect(overlayRect(ox, oy), overlayAdditiveness, .6f);
};
drawImageCell(0, /*overlayAdditiveness =*/0.f);
drawImageCell(CELL, /*overlayAdditiveness =*/1.f);
}
// Row 5: the additiveness slider. An opaque green square under five red
// strips with additiveness 0, .25, .5, .75, 1 (srcOver to fully additive,
// left to right). Left cell solid color, right cell gradient.
auto additivenessSweepCell = [&](float ox, bool gradient) {
constexpr float oy = CELL * 5;
AABB base = baseRect(ox, oy);
Paint basePaint;
basePaint->color(0xff00ff00);
renderer->drawPath(PathBuilder::Rect(base), basePaint);
AABB overlay = overlayRect(ox, oy);
constexpr int strips = 5;
for (int i = 0; i < strips; ++i)
{
AABB strip = {overlay.left() + overlay.width() * i / strips,
overlay.top(),
overlay.left() + overlay.width() * (i + 1) / strips,
overlay.bottom()};
Paint paint;
if (gradient)
{
// Opaque red -> half-transparent red, top to bottom.
ColorInt colors[2] = {0xffff0000, 0x80ff0000};
paint->shader(factory->makeLinearGradient(0,
strip.top(),
0,
strip.bottom(),
colors,
stops,
2));
}
else
{
paint->color(0xc0ff0000);
}
paint->additiveness(float(i) / (strips - 1));
renderer->drawPath(PathBuilder::Rect(strip), paint);
}
};
additivenessSweepCell(0, /*gradient =*/false);
additivenessSweepCell(CELL, /*gradient =*/true);
}