0

I've been trying to look over the Konva shape library and haven't found a stroke reapeating pattern method. I've been trying to look for a way to implement https://stackoverflow.com/a/32323610/20557085 into the shape's sceneFunc, but ended up with a static version that keeps itself in the top right corner of the canvas at all times, even if the canvas/camera is moved/dragged.

The end-goal would be to have a image that repeats itself following a line's bezier curve of points, that I can change the width of.

The question would be if there is something I am missing that is already a part of Konva, or if I should continue to trial my way through the sceneFunc?

The class component used in my attempt, that ended up static:

import React, { Component } from 'react';
import { createRoot } from 'react-dom/client';
import { Stage, Layer, Image, Shape } from 'react-konva';

var PI = Math.PI;


class URLImageStroke extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            image: null,
            points: [{ x: 0, y: 0 }, { x: 100, y: 100 }, { x: 150, y: 50 }, { x: 200, y: 200 }]
        };
    }

    componentDidMount() {
        this.loadImage();
        this.getPoints()
    }
    loadImage() {
        // save to "this" to remove "load" handler on unmount
        this.image = new window.Image();
        this.image.src = this.props.src;
        this.image.addEventListener('progress', (e) => console.log(e))
        this.image.addEventListener('load', this.handleLoad);
    }

    handleLoad = () => {
        this.setState({
            image: this.image,
        });
    };

    getPoints = () => {
        let points = [];

        //for (let i = 0; this.state.points.length > i; i++) {
        const s = this.state.points[0];
        const c1 = this.state.points[1];
        const c2 = this.state.points[2];
        const e = this.state.points[3];

        for (var t = 0; t <= 100; t += 0.25) {

            var T = t / 100;

            // plot a point on the curve
            var pos = getCubicBezierXYatT(s, c1, c2, e, T);

            // calculate the tangent angle of the curve at that point
            var tx = bezierTangent(s.x, c1.x, c2.x, e.x, T);
            var ty = bezierTangent(s.y, c1.y, c2.y, e.y, T);
            var a = Math.atan2(ty, tx) - PI / 2;

            // save the x/y position of the point and the tangent angle
            // in the points array
            points.push({
                x: pos.x,
                y: pos.y,
                angle: a
            });

        }

        this.setState({
            points: points
        });
    }

    render() {
        return (
            <Shape
                x={50}
                y={50}
                width={this.props?.width}
                height={this.props?.height}
                image={this.state.image}
                points={this.state?.points}
                sceneFunc={(ctx, shape) => {
                    const img = shape.attrs.image;
                    if (!img) {
                        console.log("no image")
                        return;
                    }
                    const points = shape.attrs.points;
                    if (!points) {
                        console.log("no points")
                        return;
                    }

                    // Note: increase the lineWidth if 
                    // the gradient has noticable gaps 
                    ctx.lineWidth = 8;

                    ctx.strokeStyle = 'skyblue';

                    let sliceCount = 0;

                    // draw a gradient-stroked line tangent to each point on the curve
                    for (let i = 0; i < points.length; i++) {
                        let p = points[i];
                        ctx.translate(p.x, p.y);
                        ctx.rotate(p.angle - PI / 2);
                        // draw multiple times to fill gaps on outside of rope slices
                        ctx.drawImage(img, sliceCount, 0, 1, img.height, 0, 0, 1, img.height);
                        ctx.drawImage(img, sliceCount, 0, 1, img.height, 0, 0, 1, img.height);
                        ctx.drawImage(img, sliceCount, 0, 1, img.height, 0, 0, 1, img.height);
                        ctx.setTransform(1, 0, 0, 1, 0, 0);
                        ++sliceCount;
                        if (sliceCount > (img.width - 1)) { sliceCount = 0; }


                    }
                    //ctx.strokeShape(this);
                }
                }
            />
        );
    }

}

//////////////////////////////////////////
// helper functions
//////////////////////////////////////////

// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)

function getCubicBezierXYatT(startPt, controlPt1, controlPt2, endPt, T) {
    var x = CubicN(T, startPt.x, controlPt1.x, controlPt2.x, endPt.x);
    var y = CubicN(T, startPt.y, controlPt1.y, controlPt2.y, endPt.y);
    return ({ x: x, y: y });
}

// cubic helper formula at T distance
function CubicN(T, a, b, c, d) {
    var t2 = T * T;
    var t3 = t2 * T;
    return a + (-a * 3 + T * (3 * a - a * T)) * T
        + (3 * b + T * (-6 * b + b * 3 * T)) * T
        + (c * 3 - c * 3 * T) * t2
        + d * t3;
}

// calculate the tangent angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
    return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};

export default URLImageStroke;

Kevin-
  • 1

0 Answers0