模拟模拟weshop实现,图层选择工具,如下图所示:
接下来我会从算法返回的图层分割数据开始梳理具体实现过程,所以先看一下图层分割的数据结构(json比较长无法全部展示出来,只保留了一条数据)
{
"height": 987,
"width": 987,
"masks_list": [
{
"index_list":[
217629,
3,
981,
7,
982,
4,
13699,
6,
979,
11,
975,
18,
968,
23,
963,
26,
960,
29,
957,
32,
954,
34,
952,
37,
949,
40,
946,
42,
944,
44,
942,
46,
940,
48,
938,
49,
937,
50,
936,
51,
935,
51,
936,
50,
937,
48,
938,
48,
939,
48,
939,
47,
940,
47,
940,
46,
942,
45,
942,
45,
942,
44,
944,
44,
943,
45,
943,
45,
943,
46,
941,
49,
940,
50,
938,
52,
937,
55,
933,
58,
930,
59,
929,
60,
928,
63,
925,
64,
113,
5,
806,
66,
109,
9,
804,
67,
107,
11,
803,
69,
104,
13,
802,
73,
14,
10,
74,
17,
800,
98,
72,
19,
798,
100,
70,
22,
796,
100,
68,
26,
794,
100,
23,
1,
43,
31,
790,
99,
23,
2,
42,
34,
788,
98,
22,
3,
42,
37,
786,
98,
20,
4,
41,
40,
785,
97,
19,
6,
40,
42,
784,
96,
18,
7,
39,
44,
784,
95,
17,
9,
36,
48,
783,
94,
16,
10,
34,
51,
783,
93,
15,
12,
32,
53,
783,
92,
15,
13,
30,
55,
783,
91,
14,
15,
28,
56,
785,
90,
12,
17,
26,
58,
785,
89,
11,
19,
25,
59,
786,
87,
10,
20,
25,
59,
788,
85,
9,
22,
23,
61,
788,
84,
8,
23,
23,
62,
788,
84,
6,
25,
22,
62,
789,
85,
1,
28,
22,
63,
789,
113,
22,
64,
789,
112,
22,
64,
790,
111,
22,
65,
790,
110,
22,
65,
791,
109,
22,
66,
792,
107,
21,
68,
792,
105,
22,
68,
793,
104,
21,
70,
793,
103,
20,
72,
793,
102,
20,
72,
794,
101,
19,
74,
795,
99,
18,
76,
795,
97,
19,
76,
796,
96,
18,
78,
797,
94,
17,
79,
799,
91,
18,
80,
799,
90,
18,
81,
799,
89,
17,
82,
801,
86,
18,
83,
801,
85,
18,
83,
802,
83,
18,
85,
803,
80,
19,
85,
804,
78,
20,
86,
805,
75,
21,
86,
806,
73,
21,
88,
807,
71,
21,
89,
807,
69,
22,
89,
809,
66,
23,
90,
809,
64,
23,
91,
811,
61,
24,
92,
811,
59,
25,
92,
812,
58,
24,
93,
813,
56,
25,
94,
813,
55,
24,
95,
813,
54,
25,
96,
812,
53,
25,
97,
813,
52,
24,
98,
813,
52,
23,
100,
813,
50,
23,
101,
813,
50,
22,
103,
812,
50,
21,
104,
813,
48,
20,
107,
812,
48,
18,
109,
812,
47,
18,
110,
813,
46,
17,
112,
812,
45,
17,
113,
812,
45,
16,
114,
813,
44,
16,
115,
812,
43,
16,
116,
813,
42,
16,
117,
812,
42,
15,
118,
812,
42,
15,
118,
813,
41,
14,
120,
812,
41,
14,
120,
812,
41,
14,
120,
813,
41,
12,
122,
812,
41,
12,
122,
812,
42,
10,
124,
812,
41,
10,
124,
812,
42,
8,
126,
811,
42,
8,
126,
812,
42,
6,
128,
811,
42,
6,
128,
812,
42,
4,
130,
811,
42,
4,
130,
811,
177,
811,
176,
811,
177,
811,
176,
811,
176,
811,
177,
810,
177,
811,
176,
811,
127,
3,
46,
812,
125,
7,
43,
812,
124,
9,
43,
812,
122,
11,
42,
812,
122,
12,
42,
811,
121,
13,
42,
812,
120,
14,
42,
811,
120,
14,
42,
812,
118,
16,
41,
812,
118,
17,
40,
812,
118,
18,
39,
813,
117,
19,
39,
812,
116,
21,
38,
812,
116,
21,
38,
813,
115,
22,
37,
813,
115,
22,
37,
813,
114,
24,
36,
814,
113,
24,
35,
815,
113,
25,
34,
816,
111,
26,
34,
816,
111,
26,
34,
817,
110,
27,
32,
818,
110,
27,
31,
819,
109,
29,
26,
823,
109,
30,
21,
826,
110,
31,
15,
830,
110,
33,
7,
837,
110,
877,
110,
876,
110,
877,
110,
877,
109,
877,
110,
877,
109,
877,
110,
876,
110,
876,
111,
876,
110,
876,
111,
876,
110,
876,
110,
876,
111,
874,
112,
874,
112,
874,
112,
875,
111,
876,
109,
877,
109,
877,
107,
879,
104,
882,
103,
883,
103,
882,
105,
882,
105,
881,
106,
881,
106,
880,
107,
879,
108,
877,
109,
877,
110,
876,
110,
877,
109,
878,
108,
878,
108,
878,
109,
878,
108,
877,
110,
876,
110,
877,
110,
876,
111,
876,
111,
876,
111,
875,
113,
874,
115,
871,
119,
867,
122,
865,
122,
865,
123,
863,
124,
863,
125,
862,
126,
860,
127,
859,
129,
858,
129,
857,
130,
857,
130,
857,
130,
856,
131,
856,
131,
855,
132,
855,
132,
854,
133,
854,
133,
854,
133,
854,
133,
854,
133,
853,
135,
852,
135,
852,
134,
853,
134,
852,
135,
852,
135,
852,
135,
851,
136,
850,
136,
851,
136,
851,
136,
850,
137,
850,
137,
850,
137,
850,
137,
850,
136,
850,
137,
850,
137,
849,
138,
849,
138,
849,
137,
849,
138,
849,
138,
848,
139,
848,
139,
848,
138,
849,
138,
849,
138,
848,
139,
848,
139,
847,
140,
847,
140,
846,
140,
847,
140,
846,
141,
846,
141,
846,
140,
846,
141,
846,
141,
846,
141,
845,
142,
845,
141,
846,
141,
846,
141,
845,
142,
845,
141,
846,
141,
846,
141,
845,
142,
845,
142,
845,
141,
846,
141,
846,
141,
845,
142,
844,
142,
844,
143,
844,
143,
844,
142,
845,
142,
845,
142,
845,
142,
844,
143,
844,
143,
843,
143,
844,
143,
843,
144,
843,
144,
843,
143,
844,
143,
844,
143,
843,
144,
843,
144,
843,
143,
844,
143,
843,
144,
843,
143,
843,
144,
843,
144,
842,
145,
842,
145,
842,
144,
842,
145,
842,
145,
841,
146,
841,
145,
841,
146,
841,
146,
841,
145,
842,
145,
841,
146,
841,
146,
841,
145,
842,
145,
841,
146,
841,
146,
841,
146,
842,
144,
843,
144,
844,
143,
846,
141,
848,
138,
851,
136,
852,
135,
854,
132,
857,
130,
859,
128,
860,
126,
863,
124,
866,
121,
867,
120,
869,
118,
871,
115,
874,
113,
876,
111,
878,
109,
880,
106,
883,
104,
887,
100,
891,
95,
894,
93,
895,
92,
896,
91,
898,
88,
901,
86,
902,
85,
903,
84,
904,
82,
910,
77,
912,
75,
915,
71,
920,
67,
923,
64,
927,
60,
929,
57,
934,
53,
937,
50,
942,
44,
946,
41,
952,
34,
959,
28,
963,
23,
971,
15
],
"masks_url": null
}
]
}
第一步:数据解读
数据中的index_list是实现本功能的重点,它代表的是非透明区域的索引及偏移量。 如下图,图中的蓝色区域即非透明区域,这个图可以划分为(987*987)974169个点的集合,集合中点按照从左上角到右下角的顺序排列。
- index_list[0]对应的数字(即217629)代表第217629点是非透明区域的起点
- index_list[1]对应的数字(即3)代表217629,217630,217631这三个点都是非透明的
- index_list[2]对应的数字(即981)代表217632,217633....(217632+980)这些个点都是透明的
- 之后的点的逻辑都与index_list[1]和index_list[2]相同
第二步:处理数据
以下为处理方法,重点部分在注释写明
/****
item:masks_list的子集
_width:被分割的原始图片的宽度
_height:被分割的原始图片的高度
分割后的每个图层默认与被分割的原始图片尺寸相同
***/
const indexList2image = (item, _width, _height) => {
let canvas = document.createElement("canvas");
let instance = canvas.getContext("2d");
canvas.width = _width;
canvas.height = _height;
let _rgbaData =
(function (e) {
let width = _width;
let height = _height;
let points = Array(width * height).fill(!1); // 创建一个(width * height)个点的集合
let pos = 0;
let isShow = !1;
/**
通过遍历index_list将points中非透明的点设置为1透明的点设置为0
index_list[0]是第一个非透明的点此前的点均为0(start)
index_list[1]是非透明点的点数字(例如为3则 start~start+3-1)设置为1
index_list[2]是透明点的点数字(例如为4则 start+3 ~ start+3+4-1)设置为0
...
最后的得到的points结构为[0,0,.....1,1,1.....,0,0]
**/
for (let t of e.index_list) {
for (let e = 0; e < t; e++) {
points[pos + e] = Number(isShow);
}
pos += t;
isShow = !isShow;
}
let rgbaData = Array(height); // 将rgbaData设置高度长度的数组
/**
rgbaData的每一个元素是从points取的rowIndex对应每一行的点的数据
结构大致为[[0,0,0,....],[0,0,0,....1,1,1...],.....]
**/
for (let rowIndex = 0; rowIndex < height; rowIndex++) {
rgbaData[rowIndex] = points.slice(
rowIndex * width,
(rowIndex + 1) * width
);
}
return rgbaData;
})(item || []) || [];
for (let y = 0; y < _rgbaData.length; y++) {
let col = _rgbaData[y];
for (let x = 0; x < col.length; x++) {
let s = _rgbaData[y][x];
if (1 === s && instance) {
// 值为1的点填充颜色
(instance.fillStyle = "#2352D8"), instance.fillRect(x, y, 1, 1);
}
}
}
// canvas当前层的图片
// 也个可以通过canvas.toDataURL('image/png')直接返回base64图片
// matrix即上边生成的二维数组
return { canvas: canvas, matrix: _rgbaData };
};
第三步:遍历图层分割中的masks_list就可以得到所有图层对应的图片及matrix即layers
第四步:将layers布局到页面中,可以通过canvas也和使用div
我使用的canvas以为后期处理一些细节操作比较方便,比如通过画笔精修。但是用div应该更简单布局应该更简单,但是上边的canvas直接到出base64图片;
<div id="layer-box" style="width: 500px; height: 500px; position: relative">
<img
style="width: 100%; height: 100%; position: absolute"
src="原始图base64"
/>
</div>
const layers = res.masks_list.map((child) =>
indexList2image(child, 987, 987)
);
const box = document.querySelector("#layer-box");
const baseStyle = "width:100%;height:100%;position: absolute;";
layers.forEach((ele) => {
const image = document.createElement("img");
image.src = ele.canvas;
image.style = `${baseStyle}opacity:0`;
image.className = "layer";
box.append(image);
});
box.addEventListener("mousemove", (e, index) => {
const { clientX, clientY, offset } = e;
const x = parseInt((res.width / box.offsetWidth) * clientX);
const y = parseInt((res.height / box.offsetHeight) * clientY);
box.querySelectorAll(".layer")[0].style = `${baseStyle}opacity:0`;
if (layers.find((item) => item.matrix[y][x])) {
box.querySelectorAll(".layer")[0].style = `${baseStyle}opacity:1`;
}
});
第五步:监听mousemove事件,通过clientX, clientY获取对应的坐标
const { clientX, clientY } = e;
const x = parseInt(
(res.width / (box.offsetWidth - box.offsetLeft)) * clientX
);
const y = parseInt(
(res.height / (box.offsetHeight - box.offsetTop)) * clientY
);
const allLayers = box.querySelectorAll(".layer");
const index = layers.findIndex((item) => item.matrix?.[y]?.[x]);
allLayers.forEach((ele, i) => {
if (i === index) {
ele.style = `${baseStyle}opacity:1`;
} else {
ele.style = `${baseStyle}opacity:0`;
}
});
根据坐标(x,y)layers中查找matrix[y][x]为1的索引值,将对索引img透明度设置为1其他为0
实现效果如下