/* * Copyright 2024 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "include/core/SkCanvas.h" #include "include/core/SkColor.h" #include "include/core/SkCubicMap.h" #include "include/core/SkFont.h" #include "include/core/SkPicture.h" #include "include/core/SkPictureRecorder.h" #include "include/core/SkPoint.h" #include "include/core/SkRefCnt.h" #include "include/core/SkShader.h" #include "include/core/SkString.h" #include "include/core/SkVertices.h" #include "include/effects/SkRuntimeEffect.h" #include "include/private/base/SkDebug.h" #include "src/base/SkRandom.h" #include "tools/fonts/FontToolUtils.h" #include "tools/viewer/Slide.h" #include #include #include #include #include "delaunator.hpp" #include "imgui.h" namespace { sk_sp triangulate_pts(const std::vector& pts, const std::vector& colors) { // put points in the format delaunator wants std::vector coords; for (size_t i = 0; i < pts.size(); ++i) { coords.push_back(pts[i].x()); coords.push_back(pts[i].y()); } // triangulation happens here delaunator::Delaunator d(coords); // SkVertices parameters std::vector vertices; std::vector indices; // populate vertices & colors for(std::size_t i = 0; i < d.coords.size(); i+=2) { vertices.push_back(SkPoint::Make(d.coords[i], d.coords[i+1])); } // populate triangle indices for(std::size_t i = 0; i < d.triangles.size(); i+=3) { indices.push_back(d.triangles[i]); indices.push_back(d.triangles[i+1]); indices.push_back(d.triangles[i+2]); } return SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, vertices.size(), vertices.data(), nullptr, colors.data(), indices.size(), indices.data()); } sk_sp makeGradientShader(int w, int h, std::vector& vertices, std::vector& colors) { vertices.push_back(SkPoint::Make(0.f, 0.f)); vertices.push_back(SkPoint::Make(w, 0.f)); vertices.push_back(SkPoint::Make(0.f, h)); vertices.push_back(SkPoint::Make(w, h)); colors.push_back(SK_ColorTRANSPARENT); colors.push_back(SK_ColorTRANSPARENT); colors.push_back(SK_ColorTRANSPARENT); colors.push_back(SK_ColorTRANSPARENT); sk_sp sk_vertices = triangulate_pts(vertices, colors); // record with a picture SkRect tile = SkRect::MakeWH(w, h); SkPictureRecorder recorder; SkCanvas* c = recorder.beginRecording(tile); SkPaint p; p.setColor(SK_ColorWHITE); c->drawVertices(sk_vertices, SkBlendMode::kModulate, p); sk_sp picture(recorder.finishRecordingAsPicture()); return picture->makeShader(SkTileMode::kDecal, SkTileMode::kDecal, SkFilterMode::kNearest); } class GradientRenderer : public SkRefCnt { public: virtual void draw(SkCanvas*) const = 0; virtual sk_sp asShader() const = 0; virtual void updateVertices(SkSpan vert_pos, SkSpan vert_colors) = 0; }; class SkSlRenderer : public GradientRenderer { public: void draw(SkCanvas* canvas) const override { SkPaint paint; paint.setShader(fShader); canvas->drawRect(SkRect::MakeWH(1, 1), paint); } sk_sp asShader() const override { return fShader; } void updateVertices(SkSpan vert_pos, SkSpan vert_colors) override { SkASSERT(vert_pos.size() == vert_colors.size()); const auto vert_count = vert_pos.size(); if (!vert_count) { return; } // Effect compilation is expensive, so we cache and only recompile when the count changes. if (vert_count != fCachedCount) { this->buildEffect(vert_count); fCachedCount = vert_count; } SkRuntimeEffectBuilder builder(fEffect); builder.uniform("u_vertcolors").set(vert_colors.data(), vert_colors.size()); builder.uniform("u_vertpos") .set(vert_pos.data() , vert_pos.size()); fShader = builder.makeShader(); } virtual void buildEffect(size_t vert_count) = 0; protected: sk_sp fEffect; sk_sp fShader; size_t fCachedCount = 0; }; class AEGradientRenderer final : public SkSlRenderer { public: void buildEffect(size_t vert_count) override { static constexpr char gAEGradientSkSL[] = "uniform half4 u_vertcolors[%zu];" "uniform float2 u_vertpos[%zu];" "half4 main(float2 xy) {" "half4 c = half4(0);" "float w_acc = 0;" "for (int i = 0; i < %zu; ++i) {" "float d = distance(xy, u_vertpos[i]);" "float w = 1 / (d * d);" "c += u_vertcolors[i] * w;" "w_acc += w;" "}" "return c / w_acc;" "}"; const auto res = SkRuntimeEffect::MakeForShader( SkStringPrintf(gAEGradientSkSL, vert_count, vert_count, vert_count)); if (!res.effect) { SkDEBUGF("%s\n", res.errorText.c_str()); } fEffect = res.effect; SkASSERT(fEffect); } }; class LinearGradientRenderer final : public SkSlRenderer { public: void buildEffect(size_t vert_count) override { static constexpr char sksl[] = "uniform half4 u_vertcolors[%zu];" "uniform float2 u_vertpos[%zu];" "half4 main(float2 xy) {" "float v[%zu];" "for (int i = 0; i < %zu; i++) {" "v[i] = 1.;" "}" "for (int i = 0; i < %zu; ++i) {" "for (int j = 0; j < %zu; ++j) {" "vec2 delta;" "delta.x = u_vertpos[j].x - u_vertpos[i].x;" "delta.y = u_vertpos[j].y - u_vertpos[i].y;" "mat3 m = mat3 (" "delta.x, delta.y, 0.," // 1st column "-delta.y, delta.x, 0.," // 2nd column "u_vertpos[i].x, u_vertpos[i].y, 1." // 3rd column ");" "mat3 m_inv = inverse(m);" "vec3 p_h = vec3(xy.x, xy.y, 1.);" "vec3 u = m_inv*p_h;" "float t = u.x;" "if (t < 0) {" "v[j] = 0;" "} else if (t > 1) {" "v[i] = 0;" "} else {" "v[i] *= 1-t;" "v[j] *= t;" "}" "}" "}" "half4 c = half4(0);" "float w_acc = 0;" "for (int i = 0; i < %zu; i++) {" "c += u_vertcolors[i] * v[i];" "w_acc += v[i];" "}" "return c / w_acc;" "}"; const auto res = SkRuntimeEffect::MakeForShader( SkStringPrintf(sksl, vert_count, vert_count, vert_count, vert_count, vert_count, vert_count, vert_count)); if (!res.effect) { SkDEBUGF("%s\n", res.errorText.c_str()); } fEffect = res.effect; SkASSERT(fEffect); } }; class IllGradientRenderer final : public SkSlRenderer { public: void buildEffect(size_t vert_count) override { static constexpr char sksl[] = "uniform half4 u_vertcolors[%zu];" "uniform float2 u_vertpos[%zu];" "half4 main(float2 xy) {" "float d[%zu];" "for (int i = 0; i < %zu; i++) {" "d[i] = 0.;" "}" "for (int i = 0; i < %zu; ++i) {" "for (int j = 0; j < %zu; ++j) {" "vec2 delta;" "delta.x = u_vertpos[j].x - u_vertpos[i].x;" "delta.y = u_vertpos[j].y - u_vertpos[i].y;" "mat3 m = mat3 (" "delta.x, delta.y, 0.," // 1st column "-delta.y, delta.x, 0.," // 2nd column "u_vertpos[i].x, u_vertpos[i].y, 1." // 3rd column ");" "mat3 m_inv = inverse(m);" "vec3 p_h = vec3(xy.x, xy.y, 1.);" "vec3 u = m_inv*p_h;" "float t = u.x;" "float s = length(delta);" "if (t < 0) {" "d[i] += s*abs(u.y);" "d[j] += s*distance(vec2(u.x, u.y), vec2(1., 0.));" "} else if (t > 1) {" "d[j] += s*abs(u.y);" "d[i] += s*distance(vec2(u.x, u.y), vec2(0., 0.));" "} else {" "d[i] += s*distance(vec2(u.x, u.y), vec2(0., 0.));" "d[j] += s*distance(vec2(u.x, u.y), vec2(1., 0.));" "}" "}" "}" "half4 c = half4(0);" "float w_acc = 0;" "for (int i = 0; i < %zu; i++) {" "float w = 1 / (d[i] * d[i]);" "c += u_vertcolors[i] * w;" "w_acc += w;" "}" "return c / w_acc;" "}"; const auto res = SkRuntimeEffect::MakeForShader( SkStringPrintf(sksl, vert_count, vert_count, vert_count, vert_count, vert_count, vert_count, vert_count)); if (!res.effect) { SkDEBUGF("%s\n", res.errorText.c_str()); } fEffect = res.effect; SkASSERT(fEffect); } }; class TriangulatedGradientRenderer final : public GradientRenderer { public: void draw(SkCanvas* canvas) const override { SkPaint paint; paint.setShader(fShader); canvas->drawRect(SkRect::MakeWH(1, 1), paint); } sk_sp asShader() const override { return fShader; } void updateVertices(SkSpan vert_pos, SkSpan vert_colors) override { SkASSERT(vert_pos.size() == vert_colors.size()); const auto vert_count = vert_pos.size(); if (!vert_count) { return; } std::vector pos; for (auto& p : vert_pos) { pos.push_back(p); } std::vector colors; for (auto& c : vert_colors) { colors.push_back(c.toSkColor()); } fShader = makeGradientShader(1, 1, pos, colors); } private: sk_sp fShader; }; static constexpr struct RendererChoice { const char* fName; sk_sp(*fFactory)(); } gGradientRenderers[] = { { "AfterEffects Gradient", []() -> sk_sp { return sk_make_sp(); } }, { "n-Linear gradient", []() -> sk_sp { return sk_make_sp(); } }, { "Illustrator (attempt) gradient", []() -> sk_sp { return sk_make_sp(); } }, { "Triangulated gradient", []() -> sk_sp { return sk_make_sp(); } } }; float lerp(float a, float b, float t) { return a + (b - a)*t; } static constexpr struct VertexAnimator { const char* fName; void (*fAanimate)(SkSpan uvs, SkSpan pos, float t); } gVertexAnimators[] = { { "Wigglynator", [](SkSpan uvs, SkSpan pos, float t) { const float radius = t*0.2f/(std::sqrt(uvs.size()) - 1); for (size_t i = 0; i < uvs.size(); ++i) { const float phase = i*SK_FloatPI*0.31f, angle = phase + t*SK_FloatPI*2; pos[i] = uvs[i] + SkVector{ radius*std::cos(angle), radius*std::sin(angle), }; } }, }, { "Squircillator", // Pull all vertices towards center, proportionally, such that the outer square edge // is mapped to a circle for t == 1. [](SkSpan uvs, SkSpan pos, float t) { for (size_t i = 0; i < uvs.size(); ++i) { // remap to [-.5,.5] const auto uv = (uvs[i] - SkPoint{0.5,0.5}); // can't allow len to collapse to zero, lest bad things happen at {0.5, 0.5} const auto len = std::max(uv.length(), std::numeric_limits::min()); // Distance from center to outer edge for the line pasing through uv. const auto d = len*0.5f/std::max(std::abs(uv.fX), std::abs(uv.fY)); // Scale needed to pull the outer edge to the r=0.5 circle at t == 1. const auto s = lerp(1, (0.5f / d), t); pos[i] = uv*s + SkPoint{0.5, 0.5}; } }, }, { "Twirlinator", // Rotate vertices proportional to their distance to center. [](SkSpan uvs, SkSpan pos, float t) { static constexpr float kMaxRotate = SK_FloatPI*4; for (size_t i = 0; i < uvs.size(); ++i) { // remap to [-.5,.5] const auto uv = (uvs[i] - SkPoint{0.5,0.5}); const auto angle = kMaxRotate * t * uv.length(); pos[i] = SkMatrix::RotateRad(angle).mapPoint(uv) + SkPoint{0.5, 0.5}; } }, }, { "Cylinderator", // Simulate a cylinder rolling sideways across the 1x1 uv space. [](SkSpan uvs, SkSpan pos, float t) { static constexpr float kCylRadius = .2f; const auto cyl_pos = t; for (size_t i = 0; i < uvs.size(); ++i) { if (uvs[i].fX <= cyl_pos) { pos[i] = uvs[i]; continue; } const auto arc_len = uvs[i].fX - cyl_pos, arc_ang = arc_len/kCylRadius; pos[i] = SkPoint{ cyl_pos + std::sin(arc_ang)*kCylRadius, uvs[i].fY, }; } }, }, { "None", [](SkSpan uvs, SkSpan pos, float t) { memcpy(pos.data(), uvs.data(), uvs.size() * sizeof(SkPoint)); }, }, }; class MeshGradientSlide final : public Slide { public: MeshGradientSlide() : fCurrentRenderer(gGradientRenderers[0].fFactory()) , fFont(ToolUtils::DefaultPortableTypeface(), .75f) , fTimeMapper({0.5f, 0}, {0.5f, 1}) , fVertedCountTimeMapper({0, 0.5f}, {0.5f, 1}) { fName = "MeshGradient"; } void load(SkScalar w, SkScalar h) override { fSize = {w, h}; } void resize(SkScalar w, SkScalar h) override { fSize = {w, h}; } void draw(SkCanvas* canvas) override { SkAutoCanvasRestore acr(canvas, true); static constexpr float kMeshViewFract = 0.85f; const float mesh_size = std::min(fSize.fWidth, fSize.fHeight) * kMeshViewFract; canvas->translate((fSize.fWidth - mesh_size) * 0.5f, (fSize.fHeight - mesh_size) * 0.5f); canvas->scale(mesh_size, mesh_size); if (fShaderFill) { SkPaint paint; paint.setAntiAlias(true); paint.setShader(fCurrentRenderer->asShader()); canvas->drawString("SK", 0, 0.75f, fFont, paint); } else { fCurrentRenderer->draw(canvas); } this->drawControls(); } bool animate(double nanos) override { // Mesh count animation { if (!fVertexCountTimeBase) { fVertexCountTimeBase = nanos; } static constexpr float kSizeAdjustSeconds = 2; const float t = (nanos - fVertexCountTimeBase) * 0.000000001 / kSizeAdjustSeconds; this->updateMesh(lerp(fVertexCountFrom, fVertexCountTo, fVertedCountTimeMapper.computeYFromX(t))); } // Vertex animation { if (!fTimeBase) { fTimeBase = nanos; } const float t = std::abs((std::fmod((nanos - fTimeBase) * 0.000000001 * fAnimationSpeed, 2) - 1)); fCurrentAnimator->fAanimate(fVertUVs, fVertPos, fTimeMapper.computeYFromX(t)); fCurrentRenderer->updateVertices(fVertPos, fVertColors); } return true; } private: void updateMesh(size_t new_count) { // These look better than rng when the count is low. static constexpr struct { SkPoint fUv; SkColor4f fColor; } gFixedVertices[] = { {{ .25f, .25f}, {1, 0, 0, 1}}, {{ .75f, .75f}, {0, 1, 0, 1}}, {{ .75f, .25f}, {0, 0, 1, 1}}, {{ .25f, .75f}, {1, 1, 0, 1}}, {{ .50f, .50f}, {0, 1, 1, 1}}, }; SkASSERT(fVertUVs.size() == fVertPos.size()); SkASSERT(fVertUVs.size() == fVertColors.size()); const size_t current_count = fVertUVs.size(); fVertUVs.resize(new_count); fVertPos.resize(new_count); fVertColors.resize(new_count); for (size_t i = current_count; i < new_count; ++i) { const SkPoint uv = i < std::size(gFixedVertices) ? gFixedVertices[i].fUv : SkPoint{ fRNG.nextF(), fRNG.nextF() }; const SkColor4f color = i < std::size(gFixedVertices) ? gFixedVertices[i].fColor : SkColor4f{ fRNG.nextF(), fRNG.nextF(), fRNG.nextF(), 1.f }; fVertUVs[i] = fVertPos[i] = uv; fVertColors[i] = color; } if (new_count < current_count) { // Reset the RNG state fRNG.setSeed(0); static constexpr size_t kRandsPerVertex = 5; const size_t rand_vertex_count = std::max(new_count, std::size(gFixedVertices)) - std::size(gFixedVertices); for (size_t i = 0; i < rand_vertex_count * kRandsPerVertex; ++i) { fRNG.nextF(); } } } void drawControls() { ImGui::Begin("Mesh Options"); if (ImGui::BeginCombo("Gradient Type", fCurrentRendererChoice->fName)) { for (const auto& renderer : gGradientRenderers) { const auto is_selected = (fCurrentRendererChoice->fName == renderer.fName); if (ImGui::Selectable(renderer.fName) && !is_selected) { fCurrentRendererChoice = &renderer; fCurrentRenderer = renderer.fFactory(); fCurrentRenderer->updateVertices(fVertPos, fVertColors); } if (is_selected) { ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } if (ImGui::BeginCombo("Animator", fCurrentAnimator->fName)) { for (const auto& anim : gVertexAnimators) { const auto is_selected = (fCurrentAnimator->fName == anim.fName); if (ImGui::Selectable(anim.fName) && !is_selected) { fCurrentAnimator = &anim; fTimeBase = 0; } if (is_selected) { ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } if (ImGui::SliderInt("Vertex Count", &fVertexCountTo, 3, 64)) { fVertexCountTimeBase = 0; fVertexCountFrom = fVertUVs.size(); } ImGui::SliderFloat("Speed", &fAnimationSpeed, 0.25, 4, "%.2f"); ImGui::Checkbox("Shader Fill", &fShaderFill); ImGui::End(); } SkSize fSize; std::vector fVertUVs; std::vector fVertPos; // fVertUVs after animation std::vector fVertColors; SkRandom fRNG; sk_sp fCurrentRenderer; const SkFont fFont; // Animation state const SkCubicMap fTimeMapper, fVertedCountTimeMapper; double fTimeBase = 0, fVertexCountTimeBase = 0; int fVertexCountFrom = 0, fVertexCountTo = 5; // UI stuff const RendererChoice* fCurrentRendererChoice = &gGradientRenderers[0]; const VertexAnimator* fCurrentAnimator = &gVertexAnimators[0]; float fAnimationSpeed = 1.f; bool fShaderFill = false; }; } // anonymous namespace DEF_SLIDE(return new MeshGradientSlide{};)