Search code examples
javascriptreactjsreact-konvacamanjskonva

Apply Caman JS preset filters to Konva React


By creating a ref for the layer component I am able to access the canvas element which I could then pass to the caman function to apply a preset filter to the image in the canvas. This works as expected however, when saving the stage as an image the filter is not applied. How can I export the Konva stage as an image with the Caman filter applied.

Component Editor.js

import React, {useRef} from 'react';
import {Stage, Layer, Image} from 'react-konva';
import EditorControls from './EditorControls';
const caman = window.Caman;

const Editor = ({image}) => {
  const stageRef = useRef();
  const mainLayer = useRef();
 
  const filterSelect = filter => {
    const canvas = mainLayer.current.canvas._canvas;
    caman(canvas, image, function() {
      this.revert();
      if (!filter) {return;}
      this[filter]().render();
    });
  }

  return (
    <div className='Editor'>
      <Stage 
        width={500}
        height={500}
        ref={stageRef}
      >
        <Layer ref={mainLayer}>
          <Image
            x={0}
            y={0}
            width={500}
            height={500}
            image={image}
          />
        </Layer>
      </Stage>
      <EditorControls
        onFilterSelect={filterSelect}
      />
      <button
        onClick={() => {
          const link = document.createElement('a');
          link.download = 'test.png';
          link.href = stageRef.current.getStage().toDataURL({pixelRatio: 3});    
          link.click();
          link.remove();
        }}
      >
        SAVE
      </button>
    </div>
  );
}

export default Editor;

Component EditorControls.js

import React from 'react';

const filters = ['vintage', 'lomo', 'clarity', 'sinCity', 'sunrise', 'crossProcess', 'orangePeel', 'love', 'grungy', 'jarques', 'pinhole', 'oldBoot', 'glowingSun', 'hazyDays', 'herMajesty', 'nostalgia', 'hemingway', 'concentrate'];

const EditorControls = ({onFilterSelect}) => {
  return (
    <div className='EditorControls'>
      <div className='EditorControls__filters'>
        <button onClick={() => onFilterSelect('')}>
          <p>noFilter</p>
        </button>
        {filters.map((filter, index) => (
          <button key={filters[index]} onClick={() => onFilterSelect(filter)}>
            <p>{filters[index]}</p>
          </button>
        ))}
      </div>
    </div>
  );
}

export default EditorControls;

Solution

  • According to https://konvajs.org/docs/sandbox/Native_Context_Access.html, it is not recommended to manually change canvas content (by Caman manipulation in your case).

    If you want to apply Caman you can:

    1. Cache node and write your own custom filter https://konvajs.org/docs/filters/Custom_Filter.html. To make it work with Caman you can convert imageData that is used in Konva filters into canvas, then apply Caman filter, then convert back to imageData
    2. Or if you want to apply filter just once (on export) you can convert a node into <canvas> element first with node.toCanvas() method. Then use resulted canvas in Caman.