Search code examples
javascriptreactjskonvajskonvakonvajs-reactjs

How to set filters in React Konvajs?


I have SVG images and I need to change their color. If I work with usual HTML elements, I use function which converts hex to CSS filters. For example '#b5b5b5' is the same as 'invert(51%) sepia(81%) saturate(1433%) hue-rotate(195deg) brightness(100%) contrast(93%)'. So if my SVG was black it becomes blue.

enter image description here

How can I do the same with Konva? I've tried to apply filters as they did it here, but I got wrong colors. Can you show me a working example with react and the filters I wrote above?


Solution

  • Konva filters doesn't follow CSS specification for filters. Konva filters may have different behavior and accept different arguments. So, it may be hard to reproduce the same result using built-in filters.

    You have two options:

    1. Custom filter.

    Following Custom Filter Tutorial, you can write your filter function and manipulate pixels as you like it. For you use case, you can simplify the logic a lot. For example, just replace pixels with some color by a pixel with another color.

    You can 100% replace all CSS filters here, but it is not a trivial work.

    // lets define a custom filter:
    var ColorReplaceFilter = function (imageData) {
      var nPixels = imageData.data.length;
      for (var i = 0; i < nPixels - 4; i += 4) {
        const isTransparent = imageData.data[i + 3] === 0;
        if (!isTransparent) {
          imageData.data[i] = 90;
          imageData.data[i + 1] = 149;
          imageData.data[i + 2] = 246;
        }
      }
    };
    
    const CustomFilterImage = () => {
      const [image] = useImage(URL, "Anonimus");
      const imageRef = React.useRef();
      // when image is loaded we need to cache the shape
      React.useEffect(() => {
        if (image) {
          // you many need to reapply cache on some props changes like shadow, stroke, etc.
          imageRef.current.cache();
        }
      }, [image]);
    
      return (
        <Image
          ref={imageRef}
          x={100}
          y={10}
          image={image}
          filters={[ColorReplaceFilter]}
          blurRadius={10}
        />
      );
    };
    

    2. context.filter API.

    You can use CanvasRenderingContext2D.filter property to apply CSS filters on canvas element. For the demo, I will draw an image into external canvas to apply filters just on it instead of the whole Konva Layer.

    Be careful, at the time of writing this answer, the API is not supported in all browsers!

    const FilterViaCanvasImage = () => {
      const [image] = useImage(URL, "Anonimus");
    
      const canvas = React.useMemo(() => {
        if (!image) {
          return undefined;
        }
        const el = document.createElement("canvas");
    
        el.width = image.width;
        el.height = image.height;
        const ctx = el.getContext("2d");
        ctx.filter =
          "invert(51%) sepia(81%) saturate(1433%) hue-rotate(195deg) brightness(100%) contrast(93%)";
        ctx.drawImage(image, 0, 0);
        return el;
      }, [image]);
    
      return (
        <Image
          x={10}
          y={10}
          image={canvas}
          filters={[Konva.Filters.HSV]}
          hue={110}
          saturation={10}
          value={100}
        />
      );
    };
    

    Demo for the both cases: https://codesandbox.io/s/react-konva-color-replace-filters-fqy2q