Search code examples
konvajsreact-konvakonvajs-reactjskonva

How can I tint an image with react-konva?


I've created a class to load images into my canvas using event listeners, based on the URLImage example on the Konva website.

I have a PNG composed entirely of white pixels on a transparent background. I want to tint the white pixels a solid color (e.g., red, purple, or cyan). I've looked at the filters example, but I'm having trouble understanding how these particular pieces fit together.

How can I do this using react-konva.Image (using, e.g., <Image filters={[...]} image={...} ref={node => {this.imageNode = node;} />)?

Here's the TypeScript class I mentioned above:

// import Konva from 'konva';
import React from 'react';
import {Image} from 'react-konva';

type ImgProps = {
    color?: string,
    src: string,
    x?: number,
    y?: number,
};

type ImgState = {
    image: any,
};

class Img extends React.Component<ImgProps, ImgState> {
    private image: HTMLImageElement | undefined;
    // private imageNode: Konva.Image | null = null;

    constructor(props: any) {
        super(props);

        this.state = {
            image: undefined,
        };
    }

    public componentDidMount = () => {
        this.loadImage();
    }

    public componentDidUpdate = (oldProps: ImgProps) => {
        if (oldProps.src !== this.props.src) {
            this.loadImage();
        }
    }

    public componentWillUnmount = () => {
        if (this.image) {
            this.image.removeEventListener('load', this.handleLoad);
        }
    }

    public render = (): React.ReactNode => {
        return (
            <Image
                image={this.state.image}
                // ref={node => {this.imageNode = node;}}
                x={this.props.x}
                y={this.props.y}
            />
        );
    }

    private loadImage = () => {
        // Save to `this` to remove `load` handler on unmount.
        this.image = new window.Image();
        this.image.src = this.props.src;
        this.image.addEventListener('load', this.handleLoad);
    }

    private handleLoad = () => {
        // After setState react-konva will update canvas and redraw the layer
        // because the `image` property has changed.
        this.setState({
            image: this.image,
        });

        // if (this.imageNode) {
        //     ...
        // }
    };
}

export default Img;

Solution

  • I figured it out. Using the Konva.Image object, which can be set via ref as shown in the question, you must do something like the following:

    this.imageNode.cache();
    this.imageNode.red(255);
    this.imageNode.green(0);
    this.imageNode.blue(0);
    

    In the class example above, this could be in the handleLoad function, or anywhere that has access to this object.

    Then render with a filters property, like the following example, where Image is a Konva.Image object:

    <Image
        image={this.state.image}
        filters={[Konva.Filters.RGB]}
        ref={node => {this.imageNode = node;}}
    />
    

    The documentation for Konva.Image.cache() states (emphasis mine):

    cache node to improve drawing performance, apply filters, or create more accurate hit regions. For all basic shapes size of cache canvas will be automatically detected.

    The documentation for inverting an image states (emphasis mine):

    To apply filter to an Konva.Image, we have to cache it first with cache() function.