| /* |
| * 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 <cmath> |
| #include <cstddef> |
| #include <limits> |
| #include <vector> |
| |
| #include "delaunator.hpp" |
| #include "imgui.h" |
| |
| namespace { |
| |
| sk_sp<SkVertices> triangulate_pts(const std::vector<SkPoint>& pts, const std::vector<SkColor>& colors) { |
| // put points in the format delaunator wants |
| std::vector<double> 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<SkPoint> vertices; |
| std::vector<uint16_t> 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<SkShader> makeGradientShader(int w, int h, std::vector<SkPoint>& vertices, std::vector<SkColor>& 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<SkVertices> 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<SkPicture> 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<SkShader> asShader() const = 0; |
| |
| virtual void updateVertices(SkSpan<const SkPoint> vert_pos, |
| SkSpan<const SkColor4f> 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<SkShader> asShader() const override { return fShader; } |
| |
| void updateVertices(SkSpan<const SkPoint> vert_pos, |
| SkSpan<const SkColor4f> 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<SkRuntimeEffect> fEffect; |
| sk_sp<SkShader> 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<SkShader> asShader() const override { return fShader; } |
| |
| void updateVertices(SkSpan<const SkPoint> vert_pos, |
| SkSpan<const SkColor4f> vert_colors) override { |
| SkASSERT(vert_pos.size() == vert_colors.size()); |
| const auto vert_count = vert_pos.size(); |
| |
| if (!vert_count) { |
| return; |
| } |
| |
| std::vector<SkPoint> pos; |
| for (auto& p : vert_pos) { |
| pos.push_back(p); |
| } |
| std::vector<SkColor> colors; |
| for (auto& c : vert_colors) { |
| colors.push_back(c.toSkColor()); |
| } |
| |
| fShader = makeGradientShader(1, 1, pos, colors); |
| } |
| |
| private: |
| sk_sp<SkShader> fShader; |
| }; |
| |
| static constexpr struct RendererChoice { |
| const char* fName; |
| sk_sp<GradientRenderer>(*fFactory)(); |
| } gGradientRenderers[] = { |
| { |
| "AfterEffects Gradient", |
| []() -> sk_sp<GradientRenderer> { return sk_make_sp<AEGradientRenderer>(); } |
| }, |
| { |
| "n-Linear gradient", |
| []() -> sk_sp<GradientRenderer> { return sk_make_sp<LinearGradientRenderer>(); } |
| }, |
| { |
| "Illustrator (attempt) gradient", |
| []() -> sk_sp<GradientRenderer> { return sk_make_sp<IllGradientRenderer>(); } |
| }, |
| { |
| "Triangulated gradient", |
| []() -> sk_sp<GradientRenderer> { return sk_make_sp<TriangulatedGradientRenderer>(); } |
| } |
| }; |
| |
| float lerp(float a, float b, float t) { |
| return a + (b - a)*t; |
| } |
| |
| static constexpr struct VertexAnimator { |
| const char* fName; |
| void (*fAanimate)(SkSpan<const SkPoint> uvs, SkSpan<SkPoint> pos, float t); |
| } gVertexAnimators[] = { |
| { |
| "Wigglynator", |
| [](SkSpan<const SkPoint> uvs, SkSpan<SkPoint> 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<const SkPoint> uvs, SkSpan<SkPoint> 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<float>::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<const SkPoint> uvs, SkSpan<SkPoint> 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<const SkPoint> uvs, SkSpan<SkPoint> 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<const SkPoint> uvs, SkSpan<SkPoint> 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<SkPoint> fVertUVs; |
| std::vector<SkPoint> fVertPos; // fVertUVs after animation |
| std::vector<SkColor4f> fVertColors; |
| |
| SkRandom fRNG; |
| |
| sk_sp<GradientRenderer> 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{};) |