feat(ImageShow-&-Mask): for crop rotate ... image

BREAKING CHANGE:
This commit is contained in:
songsenand 2023-01-03 00:31:51 +08:00
parent 99ae37aac6
commit 60ed3faf63
9 changed files with 668 additions and 112 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 KiB

After

Width:  |  Height:  |  Size: 85 KiB

BIN
public/test1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 KiB

BIN
public/test3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

View File

@ -1,21 +1,26 @@
<script>
import { v4 as uuidv4 } from "uuid";
import { onMount, onDestroy } from "svelte";
import Navbar from "./Navbar.svelte";
import Mask from "./Mask.svelte";
import { ImageProcess } from "./utils";
const cv = window.cv;
let image_process = new ImageProcess();
export let url = "test.jpg";
let canvas_dom;
let canvas;
let resize_tag;
let image;
let image_for_show;
let window_width;
let window_height;
let canvas_width = 0;
let canvas_height = 0;
let active;
let mask_tag = false;
function LabelItem(label, items) {
this.label = label;
@ -26,7 +31,6 @@
}
function OperationItem(name, icon, handler = null) {
this.id = uuidv4();
this.name = name;
this.icon = icon;
this.handler = handler;
@ -38,12 +42,42 @@
["重新采集", "mdi mdi-reply-outline"],
]),
new LabelItem("图像操作", [
["向左旋转", "mdi mdi-file-rotate-left-outline", () => {}],
["向右旋转", "mdi mdi-file-rotate-right-outline"],
[
"向左旋转",
"mdi mdi-file-rotate-left-outline",
() => {
image_process.turn_left();
cv.rotate(image_for_show, image_for_show, 0);
show_image();
},
],
[
"向右旋转",
"mdi mdi-file-rotate-right-outline",
() => {
image_process.turn_right();
cv.rotate(image_for_show, image_for_show, 2);
show_image();
},
],
["文本加强", "mdi mdi-image-filter-center-focus-strong"],
["裁剪", "mdi mdi-crop"],
["滚轮缩放", "mdi mdi-loupe"],
["取消", "mdi mdi-alpha-x-circle-outline"],
[
"裁剪",
"mdi mdi-crop",
() => {
console.log(mask_tag);
mask_tag = mask_tag ? false : true;
console.log(mask_tag);
},
],
[
"还原",
"mdi mdi-alpha-x-circle-outline",
() => {
image_process.recover();
init_canvas();
},
],
]),
new LabelItem("文件操作", [
["保存到本地", "mdi mdi-content-save-outline"],
@ -57,108 +91,153 @@
}
function on_window_resize() {
/*setTimeout(() => {
let scale = Math.min(
canvas_height / image.height,
canvas_width / image.width
);
canvas.setWidth(image.width * scale);
canvas.setHeight(image.height * scale);
image.scale(scale);
}, 50);*/
}
function rotate(img, angle) {
let dst = new cv.Mat();
let dsize = new cv.Size(img.rows, img.cols);
let center = new cv.Point(img.cols / 2, img.rows / 2);
let M = cv.getRotationMatrix2D(center, angle, 1);
cv.warpAffine(
img,
dst,
M,
dsize,
cv.INTER_LINEAR,
cv.BORDER_CONSTANT,
new cv.Scalar()
);
return dst;
}
function resize(img, scale) {
let dst = new cv.Mat();
let dsize = new cv.Size(scale * img.cols, scale * img.rows);
cv.resize(img, dst, dsize, 0, 0, cv.INTER_AREA);
return dst;
}
function show_image(img, { option = {} }) {
if (option.hasOwnProperty("angle")) {
if (resize_tag) {
clearTimeout(resize_tag);
}
let dst = img.clone();
for (const [key, value] of Object.entries(option)) {
if (key === "resize") {
let scale = Math.min(
canvas_height / image.height,
canvas_width / image.width
resize_tag = setTimeout(function () {
image_process.roi_init();
if (image) {
let pre_scale = Math.min(
canvas_height / image.rows,
canvas_width / image.cols
);
dst = resize(dst, scale);
console.log(dst);
}
if (key === "rotate") {
dst = rotate
let pre_dsize = new cv.Size(
pre_scale * image.cols,
pre_scale * image.rows
);
cv.resize(
image_for_show,
image_for_show,
pre_dsize,
0,
0,
cv.INTER_AREA
);
show_image();
}
}, 40);
}
function show_image() {
mask_tag = false;
let dst = new cv.Mat();
console.time("image show");
if (image_process.roi_tag) {
dst = image_for_show.roi(image_process.ROI_rect);
} else {
dst = image_for_show.clone();
}
let scale = Math.min(canvas_height / dst.rows, canvas_width / dst.cols);
let dsize = new cv.Size(scale * dst.cols, scale * dst.rows);
cv.resize(dst, dst, dsize, 0, 0, cv.INTER_AREA);
cv.imshow("canvas", dst);
dst.delete;
console.timeEnd("image show");
dst.delete();
}
async function init_canvas() {
let img = new Image();
image_for_show = new cv.Mat();
img.src = url;
img.onload = function () {
setTimeout(() => {
image = cv.imread(img);
let scale = Math.min(
let pre_scale = Math.min(
canvas_height / image.rows,
canvas_width / image.cols
);
let dst = new cv.Mat();
let dsize = new cv.Size(scale * image.cols, scale * image.rows);
let pre_dsize = new cv.Size(
pre_scale * image.cols,
pre_scale * image.rows
);
cv.resize(image, image_for_show, pre_dsize, 0, 0, cv.INTER_AREA);
cv.resize(image, dst, dsize, 0, 0, cv.INTER_AREA);
cv.imshow("canvas", dst);
/* image = new fabric.Image(img);
let scale = Math.min(
canvas_height / image.height,
canvas_width / image.width
);
image.scale(scale);
canvas.setWidth(image.width * scale);
canvas.setHeight(image.height * scale);
canvas.add(image);
console.log(image);*/
show_image();
}, 50);
};
}
function wheel_event_handler(e) {
e.preventDefault();
let mouse_point = new cv.Point(
e.clientX - canvas_dom.offsetLeft,
e.clientY - canvas_dom.offsetTop
);
image_process.roi(
image_for_show.rows,
image_for_show.cols,
mouse_point,
e.deltaY
);
show_image();
}
function keyboard_move_roi(event) {
if (event.shiftKey && event.altKey && event.ctrlKey) {
return;
}
if (event.keyCode > 40 || event.keyCode < 37) {
return;
}
event.preventDefault();
switch (event.key) {
case "ArrowUp":
image_process.roi_up();
break;
case "ArrowDown":
image_process.roi_down(image_for_show.rows);
break;
case "ArrowLeft":
image_process.roi_left();
break;
case "ArrowRight":
image_process.roi_right(image_for_show.cols);
break;
}
show_image();
}
/*function mouse_move_roi(event) {
if (event.type == "mousedown") {
roi_move_tag = true;
} else if (event.type == "mouseup") {
roi_move_tag = false;
}
}
function drag_roi(event) {
if (roi_move_tag) {
console.log(event);
let delta = {
x: event.clientX - mouse_move.x,
y: event.clientY - mouse_move.y,
};
image_process.roi_move(delta);
mouse_move = {
x: event.clientX,
y: event.clientY,
};
show_image();
}
}*/
onMount(async () => {
canvas_height = window_height - 64;
canvas_width = (window_width * 13) / 16;
init_canvas();
console.log(window);
});
onDestroy(async () => {
if (image.delete) {
image.delete();
}
if (image_for_show.delete) {
image_for_show.delete();
}
});
</script>
@ -166,6 +245,7 @@
bind:innerHeight={window_height}
bind:innerWidth={window_width}
on:resize={on_window_resize}
on:keydown={keyboard_move_roi}
/>
<div class="disable-selection">
@ -181,9 +261,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<li>
<a
class:is-active={active === item.id}
on:click={() => {
active = item.id;
if (item.handler) {
item.handler();
}
@ -201,7 +279,16 @@
</aside>
</div>
<div class="column" style="text-align:center">
<canvas bind:this={canvas_dom} id="canvas" />
<div>
{#if mask_tag}
<Mask {canvas_dom} />
{/if}
<canvas
bind:this={canvas_dom}
id="canvas"
on:wheel={wheel_event_handler}
/>
</div>
</div>
</div>
</div>

View File

@ -1,16 +1,12 @@
<script>
import { v4 as uuidv4 } from "uuid";
import { onMount } from "svelte";
import { fabric } from "fabric";
import Navbar from "./Navbar.svelte";
import { ImageProcessing } from "./utils";
export let url = "test.jpg";
let img_process = new ImageProcessing();
let canvas_dom;
let canvas;
let image;
@ -18,7 +14,7 @@
let window_height;
let canvas_width = 0;
let canvas_height = 0;
let active;
let resize_tag;
function LabelItem(label, items) {
this.label = label;
@ -29,7 +25,6 @@
}
function OperationItem(name, icon, handler = null) {
this.id = uuidv4();
this.name = name;
this.icon = icon;
this.handler = handler;
@ -39,20 +34,34 @@
new LabelItem("采集操作", [
["确认并返回", "mdi mdi-checkbox-marked-circle-outline"],
["重新采集", "mdi mdi-reply-outline"],
["取消", "mdi mdi-alpha-x-circle-outline"],
]),
new LabelItem("图像操作", [
[
"向左旋转",
"mdi mdi-file-rotate-left-outline",
() => {
console.log("rotate 90");
image.rotate(90);
},
],
["向右旋转", "mdi mdi-file-rotate-right-outline"],
[
"向右旋转",
"mdi mdi-file-rotate-right-outline",
() => {
console.log("rotate 270");
image.rotate(270);
},
],
["文本加强", "mdi mdi-image-filter-center-focus-strong"],
["裁剪", "mdi mdi-crop"],
["滚轮缩放", "mdi mdi-loupe"],
[
"还原",
"mdi mdi-alpha-x-circle-outline",
() => {
init_canvas();
},
],
]),
new LabelItem("文件操作", [
["保存到本地", "mdi mdi-content-save-outline"],
@ -66,7 +75,10 @@
}
function resize() {
setTimeout(() => {
if (resize_tag) {
clearTimeout(resize_tag);
}
resize_tag = setTimeout(() => {
let scale = Math.min(
canvas_height / image.height,
canvas_width / image.width
@ -74,7 +86,7 @@
canvas.setWidth(image.width * scale);
canvas.setHeight(image.height * scale);
image.rotate(90);
image.scale(scale);
}, 50);
}
@ -85,20 +97,17 @@
img.onload = async function () {
setTimeout(() => {
canvas = new fabric.StaticCanvas(canvas_dom);
setTimeout(() => {
image = new fabric.Image(img);
let scale = Math.min(
canvas_height / image.height,
canvas_width / image.width
);
image = new fabric.Image(img);
let scale = Math.min(
canvas_height / image.height,
canvas_width / image.width
);
image.scale(scale);
canvas.setWidth(image.width * scale);
canvas.setHeight(image.height * scale);
canvas.add(image);
console.log(image);
}, 50);
});
image.scale(scale);
canvas.setWidth(image.width * scale);
canvas.setHeight(image.height * scale);
canvas.add(image);
}, 50);
};
}
@ -106,7 +115,6 @@
canvas_height = window_height - 64;
canvas_width = (window_width * 13) / 16;
init_canvas();
console.log(window)
});
</script>
@ -129,9 +137,7 @@
<!-- svelte-ignore a11y-click-events-have-key-events -->
<li>
<a
class:is-active={active === item.id}
on:click={() => {
active = item.id;
if (item.handler) {
item.handler();
}

283
src/lib/Mask.svelte Normal file
View File

@ -0,0 +1,283 @@
<script>
import { onMount } from "svelte";
// 应该确定四个点
export let canvas_dom;
let mask_top = canvas_dom.offsetTop;
let mask_left = canvas_dom.offsetLeft;
let mask_width = canvas_dom.clientWidth;
let mask_height = canvas_dom.clientHeight;
let cursor = "default";
let crop_tag = false;
let crop_area = -1;
let mask_rects = [];
function get_crop_rect(rects) {
if (rects.length == 9) {
return {
x: rects[4][0] - mask_left,
y: rects[4][1] - mask_top,
width: rects[4][2],
height: rects[4][3],
};
} else {
return {};
}
}
export let crop_rect = {};
let crop_rect_with_offset = {
x: mask_left + mask_width * 0.2,
y: mask_top,
width: mask_width * 0.6,
height: mask_height * 0.6,
};
$: {
crop_rect = get_crop_rect(mask_rects);
console.log(crop_rect);
}
function init_mask_rects() {
// TODO:
return [
[
mask_left,
mask_top,
crop_rect_with_offset.x - mask_left,
crop_rect_with_offset.y - mask_top,
],
[
crop_rect_with_offset.x,
mask_top,
crop_rect_with_offset.width,
crop_rect_with_offset.y - mask_top,
],
[
crop_rect_with_offset.x + crop_rect_with_offset.width,
mask_top,
mask_left + mask_width,
crop_rect_with_offset.y - mask_top,
],
[
mask_left,
crop_rect_with_offset.y,
crop_rect_with_offset.x - mask_left,
crop_rect_with_offset.y - mask_top,
],
[
crop_rect_with_offset.x,
crop_rect_with_offset.y,
crop_rect_with_offset.width,
crop_rect_with_offset.height,
],
];
}
function _set_crop_rect(e) {
if (crop_area < 0) {
return;
}
switch (crop_area) {
case 0:
console.log("mask_rects.4:" + mask_rects[4]);
crop_rect_with_offset.x = e.clientX;
crop_rect_with_offset.y = e.clientY;
crop_rect_with_offset.width =
mask_rects[4][2] - (mask_rects[4][0] - e.clientX);
crop_rect_with_offset.height =
mask_rects[4][3] - (mask_rects[4][1] - e.clientY);
mask_rects = init_mask_rects();
return;
/*mask_rects[4][2] = r4[3] - (r4[0] - e.clientX);
mask_rects[4][3] = r4[4] - (r4[1] - e.clientY);
case 1:
mask_rects[4][1] = e.clientY;
mask_rects[4][3] = r4[4] - (r4[1] - e.clientY);*/
}
}
function set_crop_rect(e, i) {
console.log(crop_tag);
if (crop_tag) {
_set_crop_rect(e);
return;
}
if (i != 4) {
crop_area = -1;
return;
}
if (
e.clientX - mask_rects[4][1] <= mask_width * 0.01 &&
e.clientY - mask_rects[4][0] <= mask_height * 0.01
) {
cursor = "nwse-resize";
crop_area = 0;
return;
}
if (e.clientY - mask_rects[4][0] <= mask_height * 0.01) {
cursor = "ns-resize";
crop_area = 1;
return;
}
if (
e.clientX - mask_rects[4][1] <= mask_width * 0.01 &&
e.clientY - mask_rects[4][0] - mask_rects[4][3] >= -mask_height * 0.01
) {
cursor = "nesw-resize";
crop_area = 2;
return;
}
if (e.clientX - mask_rects[4][1] <= mask_width * 0.01) {
cursor = "ew-resize";
crop_area = 3;
return;
}
if (e.clientX - mask_rects[4][1] - mask_rects[4][2] >= -mask_width * 0.01) {
cursor = "ew-resize";
crop_area = 4;
return;
}
if (
e.clientX - mask_rects[4][1] - mask_rects[4][2] >= -mask_width * 0.01 &&
e.clientY - mask_rects[4][0] <= mask_height * 0.01
) {
cursor = "nesw-resize";
crop_area = 5;
return;
}
if (
e.clientY - mask_rects[4][0] - mask_rects[4][3] >=
-mask_height * 0.01
) {
cursor = "ns-resize";
crop_area = 6;
return;
}
if (
e.clientX - mask_rects[4][1] - mask_rects[4][2] >= -mask_width * 0.01 &&
e.clientY - mask_rects[4][0] - mask_rects[4][3] >= -mask_height * 0.01
) {
cursor = "nwse-resize";
crop_area = 7;
return;
}
crop_area = -1;
}
onMount(() => {
for (let i = 0; i < 9; i++) {
mask_rects[i] = [
mask_top_pos(i),
mask_left_pos(i),
mask_width_pos(i),
mask_height_pos(i),
];
}
});
function mask_top_pos(index) {
let k = Math.floor(index / 3);
switch (k) {
case 0:
return mask_top;
case 1:
return mask_top + mask_height * 0.2;
case 2:
return mask_top + mask_height * 0.8;
}
}
function mask_left_pos(index) {
let k = index % 3;
switch (k) {
case 0:
return mask_left;
case 1:
return mask_left + mask_width * 0.2;
case 2:
return mask_left + mask_width * 0.8;
}
}
function mask_width_pos(index) {
let k = index % 3;
switch (k) {
case 0:
return mask_width * 0.2;
case 1:
return mask_width * 0.6;
case 2:
return mask_width * 0.2;
}
}
function mask_height_pos(index) {
let k = Math.floor(index / 3);
switch (k) {
case 0:
return mask_height * 0.2;
case 1:
return mask_height * 0.6;
case 2:
return mask_height * 0.2;
}
}
</script>
{#each mask_rects as r, i}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class={i != 4 ? "mask" : "mask-center"}
style:top={r[0] + "px"}
style:left={r[1] + "px"}
style:width={r[2] + "px"}
style:height={r[3] + "px"}
style:cursor
on:mousemove={(e) => {
set_crop_rect(e, i);
}}
on:mousedown={() => {
if (i == 4) {
crop_tag = true;
}
}}
on:mouseup={() => {
crop_tag = false;
}}
>
<div class="mask-inner" />
</div>
{/each}
<style>
.mask {
position: fixed;
background-color: gray;
opacity: 0.6;
}
.mask-center {
position: fixed;
border: 2px solid black;
display: flex;
justify-content: center;
align-items: center;
background-color: red;
}
.mask-inner {
width: 98%;
height: 98%;
cursor: default;
}
</style>

View File

@ -5,7 +5,7 @@
let user;
let guide = new RouterGuide();
function logout() {
guide.push("/login");
}

View File

@ -1,5 +1,8 @@
import procedure from "../assets/Procedure.json";
const cv = window.cv;
const move_step = 3;
export function Operation(step) {
this.step = step;
@ -18,11 +21,187 @@ export function Operation(step) {
if (!s) {
return procedure.find((e) => e.id === this.step).name;
}
console.log(s);
return procedure.find((e) => e.id === s).name;
};
this.get_color = () => {
return procedure.find((e) => e.id === this.step).color;
};
}
}
export function ImageProcess() {
let angle = 0;
this.roi_tag = false;
this.roi_scale_ratio = 1;
this.rotate_tag = -1;
this.ROI_rect = new cv.Rect(0, 0, 0, 0);
this.crop_rect = null;
this.recover = () => {
angle = 0;
this.roi_tag = false;
this.roi_scale_ratio = 1;
this.rotate_tag = -1;
this.ROI_rect = new cv.Rect(0, 0, 0, 0);
this.crop_rect = null;
};
this.roi_init = () => {
this.roi_tag = false;
this.roi_scale_ratio = 1;
this.ROI_rect = new cv.Rect(0, 0, 0, 0);
};
this.roi_move = ({ x, y }) => {
if (x >= 0) {
this.roi_right(x);
} else {
this.roi_left(-x);
}
if (y >= 0) {
this.roi_down(y);
} else {
this.roi_down(-y);
}
};
this.roi_up = (step = move_step) => {
let x = this.ROI_rect.x;
let y = this.ROI_rect.y >= step ? this.ROI_rect.y - step : 0;
let width = this.ROI_rect.width;
let height = this.ROI_rect.height;
this.ROI_rect = new cv.Rect(x, y, width, height);
};
this.roi_down = (rows, step = move_step) => {
let x = this.ROI_rect.x;
let y =
this.ROI_rect.y <= rows - this.ROI_rect.height - step
? this.ROI_rect.y + step
: rows - this.ROI_rect.height - step;
let width = this.ROI_rect.width;
let height = this.ROI_rect.height;
this.ROI_rect = new cv.Rect(x, y, width, height);
};
this.roi_left = (step = move_step) => {
let x = this.ROI_rect.x >= step ? this.ROI_rect.x - step : 0;
let y = this.ROI_rect.y;
let width = this.ROI_rect.width;
let height = this.ROI_rect.height;
this.ROI_rect = new cv.Rect(x, y, width, height);
};
this.roi_right = (cols, step = move_step) => {
let x =
this.ROI_rect.x + this.ROI_rect.width + step <= cols
? this.ROI_rect.x + step
: cols - this.ROI_rect.width - step;
let y = this.ROI_rect.y;
let width = this.ROI_rect.width;
let height = this.ROI_rect.height;
this.ROI_rect = new cv.Rect(x, y, width, height);
};
this.roi = (rows, cols, center, delta_wheel, scale_ratio = 0.05) => {
// Transition coordinate system at first.
let c_at_image = new cv.Point(
center.x * this.roi_scale_ratio + this.ROI_rect.x,
center.y * this.roi_scale_ratio + this.ROI_rect.y
);
let ratio = this.roi_scale_ratio + (delta_wheel / 144) * scale_ratio;
this.roi_scale_ratio = ratio <= 1 ? ratio : 1;
if (!this.roi_tag) {
this.ROI_rect = new cv.Rect(0, 0, cols, rows);
this.roi_tag = true;
}
let roi_width = cols * this.roi_scale_ratio;
let roi_height = rows * this.roi_scale_ratio;
if (Math.min(roi_width / cols, roi_height / rows) <= 0.3) {
this.roi_scale_ratio = 0.3;
return;
}
let x =
c_at_image.x -
(c_at_image.x * roi_width) / this.ROI_rect.width +
(this.ROI_rect.x * roi_width) / this.ROI_rect.width;
let y =
c_at_image.y -
(c_at_image.y * roi_height) / this.ROI_rect.height +
(this.ROI_rect.y * roi_height) / this.ROI_rect.height;
if (roi_width + x <= cols && roi_height + y <= rows) {
this.ROI_rect = new cv.Rect(
x >= 0 ? x : 0,
y >= 0 ? y : 0,
roi_width,
roi_height
);
} else if (roi_width + x <= cols && roi_height + y >= rows) {
this.ROI_rect = new cv.Rect(
x >= 0 ? x : 0,
rows - roi_height,
roi_width,
roi_height
);
} else if (roi_width + x >= cols && roi_height + y >= rows) {
this.ROI_rect = new cv.Rect(
cols - roi_width,
rows - roi_height,
roi_width,
roi_height
);
}
};
this.turn_left = () => {
this.roi_init();
angle = angle + 270;
let k = angle % 360;
if (k == 90) {
this.rotate_tag = 0;
} else if (k == 180) {
this.rotate_tag = 1;
} else if (k == 270) {
this.rotate_tag = 2;
} else {
this.rotate_tag = -1;
}
};
this.turn_right = () => {
this.roi_init();
angle = angle + 90;
let k = angle % 360;
if (k == 90) {
this.rotate_tag = 0;
} else if (k == 180) {
this.rotate_tag = 1;
} else if (k == 270) {
this.rotate_tag = 2;
} else {
this.rotate_tag = -1;
}
};
this.set_roi = (rect) => {
this.ROI_rect = rect;
};
this.set_crop_roi = (rect) => {
this.crop_rect = rect;
};
}

View File

@ -4,6 +4,7 @@ import { setContext } from "svelte";
import Main from "../lib/Main.svelte";
import Login from "../lib/Login.svelte";
import Image from "../lib/ImageShow.svelte";
// import Image from "../lib/ImageShowWithFabric.svelte";
const RouterMap = {
index: Main,