Changer l'opacité en dehors d'un rectangle de rognage dans konva

Nov 21 2020

J'essaie de créer un outil de recadrage d'image avec React et konva. Je voudrais changer l'opacité en dehors du rectangle de recadrage pour brouiller le reste de l'image.

J'ai jusqu'à présent essayé de définir différentes opacités pour le rectangle et l'image, mais j'ai échoué. J'ai levé les yeux et il n'y a pas de moyen direct de le faire

Voici la fonction de recadrage que j'ai adaptée pour réagir à l'aide de cette réponse

import React, { useState, useEffect, useRef } from "react";
import { render } from "react-dom";
import { Stage, Layer, Rect, Image } from "react-konva";
import Konva from "konva";

const App = () => {
  // Stage dims
  let sW = 720,
    sH = 720,
    sX = 0,
    sY = 0;

   let src = "https://dummyimage.com/720x720/e85de8/fff&text=SO Rocks!";
  let img = document.createElement("img");

  useEffect(() => {
    img.src = src;
    function loadStatus() {
      setloading(false);
    }
    img.addEventListener("load", loadStatus);
    return () => {
      img.removeEventListener("load", loadStatus);
    };
  }, [img, src]);

  let scale = 1;
  const [loading, setloading] = useState(true);
  const [posStart, setposStart] = useState({});
  const [posNow, setposNow] = useState({});
  const [mode, setmode] = useState("");

  /**
   * Sets the state of posStart and posNow for tracking the coordinates of the cropping rectangle
   * @param {Object} posIn - Coordinates of the pointer when MouseDown is fired
   */
  function startDrag(posIn) {
    setposStart({ x: posIn.x, y: posIn.y });
    setposNow({ x: posIn.x, y: posIn.y });
  }

  /**
   * Updates the state accordingly when the MouseMove event is fired
   * @param {Object} posIn - Coordiantes of the current position of the pointer
   */
  function updateDrag(posIn) {
    setposNow({ x: posIn.x, y: posIn.y });
    let posRect = reverse(posStart, posNow);
    r2.current.x(posRect.x1);
    r2.current.y(posRect.y1);
    r2.current.width(posRect.x2 - posRect.x1);
    r2.current.height(posRect.y2 - posRect.y1);
    r2.current.visible(true);
  }

  /**
   * Reverse coordinates if dragged left or up
   * @param {Object} r1 - Coordinates of the starting position of cropping rectangle
   * @param {Object} r2 - Coordinates of the current position of cropping rectangle
   */
  function reverse(r1, r2) {
    let r1x = r1.x,
      r1y = r1.y,
      r2x = r2.x,
      r2y = r2.y,
      d;
    if (r1x > r2x) {
      d = Math.abs(r1x - r2x);
      r1x = r2x;
      r2x = r1x + d;
    }
    if (r1y > r2y) {
      d = Math.abs(r1y - r2y);
      r1y = r2y;
      r2y = r1y + d;
    }
    return { x1: r1x, y1: r1y, x2: r2x, y2: r2y }; // return the corrected rect.
  }

  /**
   * Crops the image and saves it in jpeg format
   * @param {Konva.Rect} r - Ref of the cropping rectangle
   */
  function setCrop(r) {
    let jpeg = new Konva.Image({
      image: img,
      x: sX,
      y: sY
    });
    jpeg.cropX(r.x());
    jpeg.cropY(r.y());
    jpeg.cropWidth(r.width() * scale);
    jpeg.cropHeight(r.height() * scale);
    jpeg.width(r.width());
    jpeg.height(r.height());
    const url = jpeg.toDataURL({ mimeType: "image/jpeg", quality: "1.0" });
    const a = document.createElement("a");
    a.href = url;
    a.download = "cropped.jpg";
    a.click();
  }

  // Foreground rect to capture events
  const r1 = useRef();

  // Cropping rect
  const r2 = useRef();

  const image = useRef();

  return (
    <div className="container">
      <Stage width={sW} height={sH}>
        <Layer>
          {!loading && (
            <Image
              ref={image}
              {...{
                image: img,
                x: sX,
                y: sY
              }}
            />
          )}
          <Rect
            ref={r1}
            {...{
              x: 0,
              y: 0,
              width: sW,
              height: sH,
              fill: "white",
              opacity: 0
            }}
            onMouseDown={function (e) {
              setmode("drawing");
              startDrag({ x: e.evt.layerX, y: e.evt.layerY });
            }}
            onMouseMove={function (e) {
              if (mode === "drawing") {
                updateDrag({ x: e.evt.layerX, y: e.evt.layerY });
                image.current.opacity(0.5);
                r2.current.opacity(1);
              }
            }}
            onMouseUp={function (e) {
              setmode("");
              r2.current.visible(false);
              setCrop(r2.current);
              image.current.opacity(1);
            }}
          />
          <Rect
            ref={r2}
            listening={false}
            {...{
              x: 0,
              y: 0,
              width: 0,
              height: 0,
              stroke: "white",
              dash: [5, 5]
            }}
          />
        </Layer>
      </Stage>
    </div>
  );
};

render(<App />, document.getElementById("root"));

Démo pour le code ci-dessus

Réponses

2 mplusr Nov 21 2020 at 14:04

Cela peut être fait en utilisant Konva.Group et sa propriété clip. Ajoutez une nouvelle Konva.Image au groupe et définissez ses positions et sa taille de découpage pour qu'elles soient identiques à celles du rectangle de recadrage. N'oubliez pas de régler le support d'écoute du groupe sur false sinon cela compliquera les choses. Voici le résultat final

import { render } from "react-dom";
import React, { useLayoutEffect, useRef, useState } from "react";
import { Stage, Layer, Image, Rect, Group } from "react-konva";

/**
 * Crops a portion of image in Konva stage and saves it in jpeg format
 * @param {*} props - Takes no props
 */
function Cropper(props) {
  // Stage dims
  let sW = 720,
    sH = 720,
    sX = 0,
    sY = 0;

  let src = "https://dummyimage.com/720x720/e85de8/fff&text=SO Rocks!";
  let img = new window.Image();
  useLayoutEffect(() => {
    img.src = src;
    function loadStatus() {
      // img.crossOrigin = "Anonymous";
      setloading(false);
    }
    img.addEventListener("load", loadStatus);
    return () => {
      img.removeEventListener("load", loadStatus);
    };
  }, [img, src]);
  let i = new Konva.Image({
    x: 0,
    y: 0,
    width: 0,
    height: 0
  });
  let scale = 1;
  const [loading, setloading] = useState(true);
  const [posStart, setposStart] = useState({});
  const [posNow, setposNow] = useState({});
  const [mode, setmode] = useState("");

  /**
   * Sets the state of posStart and posNow for tracking the coordinates of the cropping rectangle
   * @param {Object} posIn - Coordinates of the pointer when MouseDown is fired
   */
  function startDrag(posIn) {
    setposStart({ x: posIn.x, y: posIn.y });
    setposNow({ x: posIn.x, y: posIn.y });
  }

  /**
   * Updates the state accordingly when the MouseMove event is fired
   * @param {Object} posIn - Coordiantes of the current position of the pointer
   */
  function updateDrag(posIn) {
    setposNow({ x: posIn.x, y: posIn.y });
    let posRect = reverse(posStart, posNow);
    r2.current.x(posRect.x1);
    r2.current.y(posRect.y1);
    r2.current.width(posRect.x2 - posRect.x1);
    r2.current.height(posRect.y2 - posRect.y1);
    r2.current.visible(true);
    grp.current.clip({
      x: posRect.x1,
      y: posRect.y1,
      width: posRect.x2 - posRect.x1,
      height: posRect.y2 - posRect.y1
    });
    grp.current.add(i);
    i.image(img);
    i.width(img.width);
    i.height(img.height);
    i.opacity(1);
  }

  /**
   * Reverse coordinates if dragged left or up
   * @param {Object} r1 - Coordinates of the starting position of cropping rectangle
   * @param {Object} r2 - Coordinates of the current position of cropping rectangle
   */
  function reverse(r1, r2) {
    let r1x = r1.x,
      r1y = r1.y,
      r2x = r2.x,
      r2y = r2.y,
      d;
    if (r1x > r2x) {
      d = Math.abs(r1x - r2x);
      r1x = r2x;
      r2x = r1x + d;
    }
    if (r1y > r2y) {
      d = Math.abs(r1y - r2y);
      r1y = r2y;
      r2y = r1y + d;
    }
    return { x1: r1x, y1: r1y, x2: r2x, y2: r2y }; // return the corrected rect.
  }

  /**
   * Crops the image and saves it in jpeg format
   * @param {Konva.Rect} r - Ref of the cropping rectangle
   */
  function setCrop(r) {
    let jpeg = new Konva.Image({
      image: img,
      x: sX,
      y: sY
    });
    jpeg.cropX(r.x());
    jpeg.cropY(r.y());
    jpeg.cropWidth(r.width() * scale);
    jpeg.cropHeight(r.height() * scale);
    jpeg.width(r.width());
    jpeg.height(r.height());
    const url = jpeg.toDataURL({ mimeType: "image/jpeg", quality: "1.0" });
    const a = document.createElement("a");
    a.href = url;
    a.download = "cropped.jpg";
    a.click();
  }

  // Foreground rect to capture events
  const r1 = useRef();

  // Cropping rect
  const r2 = useRef();

  const image = useRef();
  const grp = useRef();

  return (
    <div className="container">
      <Stage width={sW} height={sH}>
        <Layer>
          {!loading && (
            <Image
              ref={image}
              listening={false}
              {...{
                image: img,
                x: sX,
                y: sY
              }}
            />
          )}

          <Rect
            ref={r1}
            {...{
              x: 0,
              y: 0,
              width: sW,
              height: sH,
              fill: "white",
              opacity: 0
            }}
            onMouseDown={function (e) {
              setmode("drawing");
              startDrag({ x: e.evt.layerX, y: e.evt.layerY });
              image.current.opacity(0.2);
            }}
            onMouseMove={function (e) {
              if (mode === "drawing") {
                updateDrag({ x: e.evt.layerX, y: e.evt.layerY });
              }
            }}
            onMouseUp={function (e) {
              setmode("");
              r2.current.visible(false);
              setCrop(r2.current);
              image.current.opacity(1);
              grp.current.removeChildren(i);
            }}
          />
          <Group listening={false} ref={grp}></Group>

          <Rect
            ref={r2}
            listening={false}
            {...{
              x: 0,
              y: 0,
              width: 0,
              height: 0,
              stroke: "white",
              dash: [5, 10]
            }}
          />
        </Layer>
      </Stage>
    </div>
  );
}

render(<Cropper />, document.getElementById("root"));

Merci à @Vanquished Wombat pour toutes les précieuses contributions. Ceci est une adaptation de sa réponse ici

Démo du code ci-dessus