在 konva 中更改裁剪矩形外的不透明度
Change the opacity outside of a cropping rectangle in konva
我正在尝试使用 React 和 konva 构建图像裁剪工具。我想更改裁剪矩形外的不透明度以模糊图像的其余部分。
到目前为止,我已经尝试为矩形和图像设置不同的不透明度但失败了。我查了一下,没有直接的方法可以做到这一点
这是我在
的帮助下适应的裁剪功能
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"));
Demo 上面的代码
可以使用 Konva.Group 及其剪辑 属性 来完成。向组中添加一个新的Konva.Image,并将其裁剪位置和大小设置为与裁剪矩形相同。不要忘记将组的 listening 属性设置为 false 否则会使事情复杂化。这是最终结果
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"));
感谢@Vanquished Wombat for all the precious inputs. This is an adaptation of his answer here
上面代码的
我正在尝试使用 React 和 konva 构建图像裁剪工具。我想更改裁剪矩形外的不透明度以模糊图像的其余部分。
到目前为止,我已经尝试为矩形和图像设置不同的不透明度但失败了。我查了一下,没有直接的方法可以做到这一点
这是我在
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"));
Demo 上面的代码
可以使用 Konva.Group 及其剪辑 属性 来完成。向组中添加一个新的Konva.Image,并将其裁剪位置和大小设置为与裁剪矩形相同。不要忘记将组的 listening 属性设置为 false 否则会使事情复杂化。这是最终结果
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"));
感谢@Vanquished Wombat for all the precious inputs. This is an adaptation of his answer here
上面代码的