-
Notifications
You must be signed in to change notification settings - Fork 33
/
Copy path楚留香图片转影壁.js
950 lines (869 loc) · 34.6 KB
/
楚留香图片转影壁.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
//使用auto.js 4.0.1 beta版本 编写&运行
var config = storages.create("hallo1_clximgplotter_config");
var getPosInteractive = requireShared("getPosInteractive.js");
console.show(true);
const colorTableMaxAllowedDiff = 10; //获取颜色表时允许的最大颜色差异
/**
* 加载共享的js文件, 和require类似,用来解决几个项目共享js文件的问题。
* 安卓不能软链接,如果把共享的js文件放上一个目录,打包之后就找不到了。
* @param {string} fileName
*/
function requireShared(fileName) {
const sharedDirRel = "./shared/";
const cacheDirRel = "./sharedcache/";
const alternativeSharedDir = "/sdcard/脚本/clxTools/shared/";
function copyDir(src, dst) {
let filess = files.listDir(src);
for (let i = 0; i < filess.length; i++) {
let file = filess[i];
if (files.isDir(src + file)) {
copyDir(src + file + "/", dst + file + "/");
} else {
console.verbose(`复制文件: ${src + file} -> ${dst + file}`);
files.copy(src + file, dst + file);
}
}
}
let sharedDir = files.path(sharedDirRel);
let cacheDir = files.path(cacheDirRel);
//检查是否在/data/user/目录下运行,如果是,则使用备用目录 (调试用)
console.log(files.cwd());
if (files.cwd().startsWith("/data/user/")) {
sharedDir = alternativeSharedDir;
}
files.ensureDir(cacheDir);
let sourceExists = files.exists(sharedDir + fileName);
let cacheExists = files.exists(cacheDir + fileName);
if (sourceExists && !cacheExists) {
console.log("复制共享文件夹");
copyDir(sharedDir, cacheDir);
return require(cacheDir + fileName);
} else if (!sourceExists && cacheExists) {
//如果共享文件不存在,但是缓存文件存在,则直接加载缓存文件(打包之后,共享文件会丢失)
console.log("共享文件不存在,加载缓存文件: " + fileName);
return require(cacheDir + fileName);
} else if (!sourceExists && !cacheExists) {
throw new Error("共享文件不存在: " + fileName);
}
//都存在,检查是否有更新
let sourceLastModified = java.nio.file.Files.getLastModifiedTime(java.nio.file.Paths.get(sharedDir + fileName)).toMillis();
let cacheLastModified = java.nio.file.Files.getLastModifiedTime(java.nio.file.Paths.get(cacheDir + fileName)).toMillis();
if (sourceLastModified > cacheLastModified) {
console.log("共享文件有更新: " + fileName);
copyDir(sharedDir, cacheDir);
}
return require(cacheDir + fileName);
}
let cmp = (x, y) => {
// If both x and y are null or undefined and exactly the same
if (x === y) {
return true;
}
// If they are not strictly equal, they both need to be Objects
if (!(x instanceof Object) || !(y instanceof Object)) {
return false;
}
//They must have the exact same prototype chain,the closest we can do is
//test the constructor.
if (x.constructor !== y.constructor) {
return false;
}
for (var p in x) {
//Inherited properties were tested using x.constructor === y.constructor
if (x.hasOwnProperty(p)) {
// Allows comparing x[ p ] and y[ p ] when set to undefined
if (!y.hasOwnProperty(p)) {
return false;
}
// If they have the same strict value or identity then they are equal
if (x[p] === y[p]) {
continue;
}
// Numbers, Strings, Functions, Booleans must be strictly equal
if (typeof(x[p]) !== "object") {
return false;
}
// Objects and Arrays must be tested recursively
if (!Object.equals(x[p], y[p])) {
return false;
}
}
}
for (p in y) {
// allows x[ p ] to be set to undefined
if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) {
return false;
}
}
return true;
};
function setConfigSafe(key, val) {
config.put(key, val);
let tmp = config.get(key);
if (cmp(tmp, val)) {
toast("设置保存成功");
} else {
toast("设置保存失败!");
};
};
//用户设置
function runSetup() {
switch (dialogs.select("请选择一个设置,所有设置都会自动保存", ["查看当前设置", "更改默认图片路径","使用自定义坐标","设置自定义坐标"])) {
case 0: //查看当前设置
dialogs.alert("暂时还没做好");
break;
case 1: //更改默认图片路径
setConfigSafe("defaultImgPath", dialogs.rawInput("选择默认的图片路径", "/sdcard/test.jpg"));
break;
case 2:
if(!dialogs.confirm("","总是使用自定义坐标吗")){
setConfigSafe("alwaysUseCustomPos", false);
} else {
if (config.get("colorSelecterX", 0) === 0) { //无效的配置
dialogs.alert("", "你还没有设置自定义坐标!");
} else {
setConfigSafe("alwaysUseCustomPos", true);
}
}
break;
case 3: //设置自定义坐标
let colorSelecterX = 0;
let colorSelecterY = [];
let pos = getPosInteractive("颜色选择器中从上往下第一个颜色的按钮中心");
colorSelecterX = pos.x;
colorSelecterY.push(pos.y);
pos = getPosInteractive("从上往下第二个颜色的按钮中心");
colorSelecterY.push(pos.y);
pos = getPosInteractive("从上往下第三个颜色的按钮中心");
colorSelecterY.push(pos.y);
pos = getPosInteractive("从上往下第四个颜色的按钮中心");
colorSelecterY.push(pos.y);
dialogs.alert("","现在把颜色选择器翻到最下方!");
pos = getPosInteractive("从上往下第五个颜色的按钮中心");
colorSelecterY.push(pos.y);
pos = getPosInteractive("从上往下第六个颜色的按钮中心");
colorSelecterY.push(pos.y);
pos = getPosInteractive("从上往下第七个颜色的按钮中心");
colorSelecterY.push(pos.y);
pos = getPosInteractive("从上往下第八个颜色的按钮中心");
colorSelecterY.push(pos.y);
pos = getPosInteractive("画布左上角");
let printAreaBegin = [pos.x,pos.y];
pos = getPosInteractive("画布右下角");
let printAreaEnd = [pos.x,pos.y];
let pixelWidth = 12;
setConfigSafe("colorSelecterX",colorSelecterX);
setConfigSafe("colorSelecterY",colorSelecterY);
setConfigSafe("printAreaBegin",printAreaBegin);
setConfigSafe("printAreaEnd",printAreaEnd);
setConfigSafe("pixelWidth",pixelWidth);
dialogs.alert("","设置完成");
break;
};
};
// let pos = getPosInteractive("test");
// console.log(JSON.stringify(pos));
// exit();
//主函数
if (dialogs.select("君欲何为?", ["开始绘画", "更改设置"])) { //进入设置
let endSetup = 0;
while (!endSetup) {
runSetup();
endSetup = dialogs.select("继续设置吗?", ["继续设置", "退出,开始绘画"]);
};
};
dialogs.alert("","请在开始运行之前,切换到画板的\"画刷\"页面,并且调整滑块到最细的一端稍往上一点的位置!");
let imgPath = dialogs.rawInput("选择图片的路径", config.get("defaultImgPath","/sdcard/test.jpg"));
if(!files.exists(imgPath)){
dialogs.alert("","图片不存在!");
exit();
}
setConfigSafe("defaultImgPath", imgPath);
let gcodeMode = false;
let gcodeStr = "";
let img = null;
let algo = null;
if (imgPath.endsWith(".gcode") || imgPath.endsWith(".gc") || imgPath.endsWith(".ngc")) {
gcodeStr = files.read(imgPath);
gcodeMode = true;
} else {
img = images.read(imgPath);
algo = dialogs.select("请选择绘图算法", ["算法0:速度很慢,效果较好", "算法1: 速度较快,效果较差"]);
}
let useCustomPos = config.get("alwaysUseCustomPos", false);
//////一些预置的分辨率
if(!useCustomPos){
if (device.height == 3120 && device.width == 1440) {
//3120x1440(eg.LG G7)(图片尺寸为180×97)
var pixelWidth = 16;
var printAreaBegin = [1304, 345];
var printAreaEnd = [2760, 1138];
var colorSelecterX = 1150;
var colorSelecterY = [430, 595, 765, 930, 1090, 720, 880, 1050];
} else if (device.height == 1920 && device.width == 1080) {
//1920x1080(eg.小米5s)(图片尺寸为175×97)
var pixelWidth = 12;
var printAreaBegin = [769, 257];
var printAreaEnd = [1831, 853];
var colorSelecterX = 620;
var colorSelecterY = [320, 450, 570, 690, 806, 534, 660, 787];
} else if (device.height == 2160 && device.width == 1080) {
//2160x1080(来自酷安网友)(图片尺寸为174×97)
var pixelWidth = 12;
var printAreaBegin = [890, 257];
var printAreaEnd = [1951, 853];
var colorSelecterX = 735;
var colorSelecterY = [320, 450, 570, 690, 806, 534, 660, 787];
} else if (device.height == 2340 && device.width == 1080) {
//2340x1080(eg.红米k20pro)(图片尺寸为174×97)
var pixelWidth = 12;
var printAreaBegin = [980, 257];
var printAreaEnd = [2040, 853];
var colorSelecterX = 825;
var colorSelecterY = [320, 450, 570, 690, 806, 534, 660, 787];
} else if (device.height == 2280 && device.width == 1080) {
//2280x1080(图片尺寸为175×97)
var pixelWidth = 12;
var printAreaBegin = [948, 257];
var printAreaEnd = [2011, 854];
var colorSelecterX = 799;
var colorSelecterY = [320, 450, 570, 690, 806, 534, 660, 787];
} else if (device.height == 2400 && device.width == 1080) {
//2400x1080(图片尺寸为175×97)
var pixelWidth = 12;
var printAreaBegin = [1007, 257];
var printAreaEnd = [2070, 854];
var colorSelecterX = 860;
var colorSelecterY = [320, 450, 570, 690, 806, 534, 660, 787];
} else if (device.height == 2312 && device.width == 1080) {
//2312x1080(图片尺寸为175×97)
var pixelWidth = 12;
var printAreaBegin = [965, 257];
var printAreaEnd = [2027, 854];
var colorSelecterX = 770;
var colorSelecterY = [320, 450, 570, 690, 806, 534, 660, 787];
} else if (device.height == 1520 && device.width == 720) {
//1520x720(图片尺寸为139×77)
var pixelWidth = 10;
var printAreaBegin = [660, 170];
var printAreaEnd = [1368, 569];
var colorSelecterX = 560;
var colorSelecterY = [215, 305, 380, 465, 540, 360, 440, 525];
} else if (device.height == 2220 && device.width == 1080) {
//2220x1080(图片尺寸为175×97)
var pixelWidth = 12;
var printAreaBegin = [918, 257];
var printAreaEnd = [1981, 854];
var colorSelecterX = 768;
var colorSelecterY = [320, 450, 570, 690, 806, 534, 660, 787];
} else {
//暂时没适配的分辨率,你可以自己更改这个脚本
dialogs.alert("暂不支持此分辨率", "请在设置中设置你的坐标");
setConfigSafe("alwaysUseCustomPos",true);
//toast("你也可以打开脚本自行适配");
//请在修改结束后删掉这个 'exit();'
exit();
var pixelWidth = 16; //用比最小笔刷宽度大一点点的宽度点一个点,这个点的直径
var printAreaBegin = new Array(1350, 343); //绘图区左上角坐标
var printAreaEnd = new Array(2768, 1138); //绘图区右下角坐标
var colorSelecterX = 1150; //选择颜色区的x坐标(正中间)
var colorSelecterY = new Array(430, 595, 765, 930, 1090, 720, 880, 1050); //选择颜色区各个颜色对应的y坐标,最后3个需要向下翻页到底再获取
};
}else{
console.log("正在使用自定义坐标")
var pixelWidth = config.get("pixelWidth");
var printAreaBegin = config.get("printAreaBegin");
var printAreaEnd = config.get("printAreaEnd");
var colorSelecterX = config.get("colorSelecterX");
var colorSelecterY = config.get("colorSelecterY");
}
console.log("绘图区域尺寸为"+(printAreaEnd[0]-printAreaBegin[0])+"x"+(printAreaEnd[1]-printAreaBegin[1]));
if (gcodeMode){
drawGcode(gcodeStr);
}
const pixelGap = pixelWidth / 2;
const maxWidth = (printAreaEnd[0] - printAreaBegin[0] - pixelWidth) / pixelGap;
const maxHeight = (printAreaEnd[1] - printAreaBegin[1] - pixelWidth) / pixelGap;
console.log("图片最大尺寸为" + maxWidth + "x" + maxHeight);
const knownColors = new Array("#FFFDFFFF", "#FFE7B81A", "#FF1BE6E4", "#FFE71A62", "#FFB51AE6", "#FF1BE675", "#FF010000", "#FF3700A7"); //画板里仅有的几个颜色
var colorTable = new Array();
//const hsvColorTable = [[180, 1, 1], [46, 0.89, 0.91], [179, 0.88, 0.90], [339, 0.89, 0.91], [286, 0.89, 0.90], [147, 0.88, 0.90], [0, 1, 0], [260, 1, 0.65]];
//现在颜色顺序会变化了!所以自动检测顺序
function buildColorTable() {
if(!requestScreenCapture()){
dialogs.alert("","脚本需要截图来获取颜色顺序,请允许这项权限!");
exit();
};
let buildComplete = false;
while (!buildComplete) {
swipe(colorSelecterX, colorSelecterY[0], colorSelecterX, device.width, 600); //滑到第一页
sleep(650);
let img = images.captureScreen();
for (let i = 0; i < 5; i++) {
colorTable.push(img.pixel(colorSelecterX, colorSelecterY[i])); //获取第一页中的颜色
};
swipe(colorSelecterX, colorSelecterY[4], colorSelecterX, 0, 600); //滑到第二页
sleep(600);
img = images.captureScreen();
for (let i = 5; i < colorSelecterY.length; i++) {
colorTable.push(img.pixel(colorSelecterX, colorSelecterY[i])); //获取第二页中的颜色
};
sleep(600);
swipe(colorSelecterX, colorSelecterY[0], colorSelecterX, device.width, 600); //滑到第一页
console.log("获得的颜色为" + JSON.stringify(colorTable));
//和已知颜色对比, 判断是否有漏掉的颜色, 使用colors.isSimilar()函数
buildComplete = true;
let colorTableCopy = colorTable.slice(0);
for (let i = 0; i < knownColors.length; i++) {
let haveSimilarColor = false;
let knownColor = colors.parseColor(knownColors[i]);
for (let j = 0; j < colorTableCopy.length; j++) {
if (colors.isSimilar(knownColor, colorTableCopy[j], colorTableMaxAllowedDiff, "rgb+")) {
haveSimilarColor = true;
break;
};
};
if (!haveSimilarColor) {
buildComplete = false;
console.log("颜色 %s 没有找到, 重新获取", knownColors[i]);
colorTable = new Array();
break;
}
}
};
console.log("获取完成, 颜色顺序为" + JSON.stringify(colorTable));
}
/**
* 判断是否滑动到位 (也不知道是bug还是为了反制这个脚本, 现在滑动颜色选择器会有一定概率会在半途卡住)
* @param {boolean} isUp 是否向上滑动(滑动到底部)
* @returns {boolean} 是否滑动到位
*/
function isScrollComplete(isUp) {
let img = images.captureScreen();
if (isUp) {
let actrualColor = img.pixel(colorSelecterX, colorSelecterY[colorSelecterY.length - 1]);
let expectedColor = colorTable[colorTable.length - 1];
return colors.isSimilar(actrualColor, expectedColor, colorTableMaxAllowedDiff, "rgb+");
}else{
let actrualColor = img.pixel(colorSelecterX, colorSelecterY[0]);
let expectedColor = colorTable[0];
return colors.isSimilar(actrualColor, expectedColor, colorTableMaxAllowedDiff, "rgb+");
}
}
function compareRGB(r1, g1, b1, r2, g2, b2) {
let rmean = (r1 + r2) / 2;
let dr = r1 - r2;
let dg = g1 - g2;
let db = b1 - b2;
//lab deltaE颜色相似度
return ((2 + rmean / 256) * (dr * dr) + 4 * (dg * dg) + (2 + (255 - rmean) / 256) * (db * db));
};
function findNearestColor(col, prevCol, prevColId) { //根据图片颜色确定最接近的笔刷颜色(实际上因为可选颜色太少,效果差劲)
if (Math.abs(colors.red(col) - colors.red(prevCol)) * 0.297 + Math.abs(colors.green(col) - colors.green(prevCol)) * 0.593 + Math.abs(colors.blue(col) - colors.blue(prevCol)) * 0.11 < 2) {
return prevColId;
};
/*
function compareHSV(h1, s1, v1, h2, s2, v2) {
const R = 100;
const angle = 30;
const h = R * Math.cos(angle / 180 * Math.PI);
const r = R * Math.sin(angle / 180 * Math.PI);
let x1 = r * v1 * s1 * Math.cos(h1 / 180 * Math.PI);
let y1 = r * v1 * s1 * Math.sin(h1 / 180 * Math.PI);
let z1 = h * (1 - v1);
let x2 = r * v2 * s2 * Math.cos(h2 / 180 * Math.PI);
let y2 = r * v2 * s2 * Math.sin(h2 / 180 * Math.PI);
let z2 = h * (1 - v2);
let dx = x1 - x2;
let dy = y1 - y2;
let dz = z1 - z2;
return Math.sqrt((dx * dx + dy * dy + dz * dz));
};
//如果两个颜色相距很小,直接返回前一个颜色
//转换为hsv颜色
let R = colors.red(col) / 255, G = colors.green(col) / 255, B = colors.blue(col) / 255;
let maxc = Math.max(R, G, B);
let minc = Math.min(R, G, B);
let H = 0;
if (R = maxc) H = (G - B) / (maxc - minc);
if (G = maxc) H = 2 + (B - R) / (maxc - minc);
if (B = maxc) H = 4 + (R - G) / (maxc - minc);
H = H * 60;
if (H < 0) H = H + 360;
let V = Math.max(R, G, B);
let S = (V == 0 ? 0 : (maxc - minc) / (maxc));
*/
let diff0 = +Infinity;
let out = 0;
for (let i = 0; i < colorTable.length; i++) {
//let diff = compareHSV(H, S, V, hsvColorTable[i][0], hsvColorTable[i][1], hsvColorTable[i][2]);
let diff = compareRGB(colors.red(col), colors.green(col), colors.blue(col), colors.red(colorTable[i]), colors.green(colorTable[i]), colors.blue(colorTable[i]))
if (diff < diff0) {
diff0 = diff;
out = i;
};
};
return out;
};
function switchColor(colId, needSwipe) { //更换当前笔刷颜色
if (needSwipe) {
let swipeSuccess = false;
while (!swipeSuccess) {
swipe(colorSelecterX, colorSelecterY[0], colorSelecterX, device.width, 600); //滑到第一页
sleep(50);
swipeSuccess = isScrollComplete(false);
if(!swipeSuccess){
console.log("滑动到第一页失败, 重试");
swipe(colorSelecterX, colorSelecterY[4], colorSelecterX, 0, 600); //滑回第二页
}
}
swipeSuccess = false;
if (colId >= 5) {
while (!swipeSuccess) {
swipe(colorSelecterX, colorSelecterY[4], colorSelecterX, 0, 600); //滑到第二页
sleep(50);
swipeSuccess = isScrollComplete(true);
if(!swipeSuccess){
console.log("滑动到第二页失败, 重试");
swipe(colorSelecterX, colorSelecterY[0], colorSelecterX, device.width, 600); //滑到第一页
}
}
};
} else {
//sleep(10);
};
press(colorSelecterX, colorSelecterY[colId], 20); //点选颜色
};
/**
* Algo0 - 最原始的算法,逐个像素进行绘画,效果尚可,但是需要很长的时间
*/
function execAlgo0(){
var prevColId = 0;
var prevCol = "#FFFFFFFF";
buildColorTable();
sleep(600);
//把初始颜色设置为白色, 否则会出现直到遇到第一个颜色为非白色的点前,笔刷颜色可能会变成错误的颜色的问题
let defaultColId = findNearestColor("#FFFFFFFF", "#00000000", 0);
switchColor(defaultColId, true);
sleep(600);
for (var i = 1; i <= pixelCountX; i++) {
for (var j = 1; j <= pixelCountY; j++) {
let searchx = (i - 1);
let searchy = (j - 1);
let colId = findNearestColor(img.pixel(searchx, searchy), prevCol, prevColId);
prevCol = img.pixel(searchx, searchy);
//if(colId==0)continue;//跳过白色
if (colId != prevColId) {
var needSwipe = 0;
if ((colId <= 4 && colId >= 0 && prevColId <= 4 && prevColId >= 0) || (colId <= 7 && colId >= 5 && prevColId <= 7 && prevColId >= 5)) {} else { //两个颜色不在同一页
needSwipe = 1;
};
prevColId = colId;
switchColor(colId, needSwipe);
};
press(printAreaBegin[0] + i * pixelGap, printAreaBegin[1] + j * pixelGap, 30);
//sleep(200);
};
toast(i + "/" + pixelCountX + "完成");
console.log(i + "/" + pixelCountX + "完成");
};
toast("绘画完成");
}
/**
* Algo1 - 逐个颜色进行绘画,效果稍差,但是速度快
*/
function execAlgo1(){
buildColorTable();
sleep(600);
let prevColId = 0;
let prevCol = "#FFFFFFFF"
//把初始颜色设置为白色, 否则会出现直到遇到第一个颜色为非白色的点前,笔刷颜色可能会变成错误的颜色的问题
let defaultColId = findNearestColor("#FFFFFFFF", "#00000000", 0);
switchColor(defaultColId, true);
sleep(600);
// a matrix of the same size as the image, filled with desired color
toast("正在计算颜色");
let m = new Array(pixelCountX);
for (let i = 0; i < pixelCountX; i++) {
m[i] = new Array(pixelCountY);
for (let j = 0; j < pixelCountY; j++) {
m[i][j] = findNearestColor(img.pixel(i, j), prevCol, prevColId);
prevCol = img.pixel(i, j);
prevColId = m[i][j];
}
}
//for each color in the matrix, draw it on the screen
//don't draw the white color
for(let colId = 0; colId < colorTable.length; colId++){
// if the current color is similar to white, skip it
let curCol = colorTable[colId];
let distance = compareRGB(colors.red(curCol), colors.green(curCol), colors.blue(curCol), 255, 255, 255);
if(distance < 100){
continue;
}
switchColor(colId, true); //在这种算法中,滑动带来的时间消耗少,所以默认不滑动
for(let i = 0; i < pixelCountX; i++){
for(let j = 0; j < pixelCountY; j++){
if(m[i][j]==colId){
//楚留香中绘图只支持单点触控,所以这里只能用单点触控。
press(printAreaBegin[0] + i * pixelGap, printAreaBegin[1] + j * pixelGap, 1);
}
}
}
}
toast("绘画完成");
}
if (img == null) {
toast("输入图片错误!请检查图片路径与格式");
exit();
};
let optimalSize = true;
var pixelCountX = img.getWidth();
var pixelCountY = img.getHeight();
if (pixelCountX > maxWidth) {
toast("图片宽度过大!建议的图片最大宽度为" + maxWidth);
optimalSize = false
//exit();
};
if (pixelCountY > maxHeight) {
toast("图片高度过大!建议的图片最大高度为" + maxHeight);
optimalSize = false;
//exit();
};
if(!optimalSize){
img = images.scale(img, maxWidth/pixelCountX, maxHeight/pixelCountY);
img = images.clip(img, 0,0, maxWidth,maxHeight);
pixelCountX = img.getWidth();
pixelCountY = img.getHeight();
toast("图片已被缩放来满足比例");
}
switch (algo) {
case 0:
execAlgo0();
break;
case 1:
execAlgo1();
break;
case 2:
break;
default:
toast("算法错误!请检查算法参数"); //不会执行
exit();
break;
};
///////////GCode处理部分///////////
function GCodeGestureGenerator(){
const arcSegLength = 4; //弧线的每段长度
//GCode坐标系, 左下角为原点, x轴向右, y轴向上
var absPos = [0, 0]; //绝对坐标
//G90/G91
var isRelative = false; //是否使用相对坐标
//进给速度, 像素/分钟
var feedRate = 0;
//是否使用笔刷
var isPenDown = false;
//屏幕坐标系, 左上角为原点, x轴向右, y轴向下
var drawingAreaTopLeft = [0,0];
var drawingAreaBottomRight = [0,0];
//手势列表
var gestures = [];
//缩放
var scale = 1;
//速度缩放
var speedScale = 4;
//是否允许合并路径
var allowMergePath = true;
//一条路径的最大长度
var maxPathLength = 10;
//合并路径时两点之间的最大距离
var maxMergeDistance = 2;
var lastPosList = [];
var lastDuration = 0;
/**
* 设置绘图区域
* @param {[number, number]} topLeft 左上角坐标
* @param {[number, number]} bottomRight 右下角坐标
*/
this.setDrawingArea = function(topLeft, bottomRight){
drawingAreaTopLeft = topLeft;
drawingAreaBottomRight = bottomRight;
}
/**
* GCode坐标系转换为屏幕坐标系
* @param {[number, number]} absPos_ 绝对坐标
* @returns {[number, number]} 屏幕坐标系坐标
*/
this.GCode2Screen = function(absPos_){
absPos_ = absPos_.map(x => x * scale);
let pos = [drawingAreaTopLeft[0] + absPos_[0], drawingAreaBottomRight[1] - absPos_[1]];
let origPos = pos.slice();
let outRange = false;
if(pos[0] < drawingAreaTopLeft[0]){
pos[0] = drawingAreaTopLeft[0];
outRange = true;
}
if(pos[0] > drawingAreaBottomRight[0]){
pos[0] = drawingAreaBottomRight[0];
outRange = true;
}
if(pos[1] < drawingAreaTopLeft[1]){
pos[1] = drawingAreaTopLeft[1];
outRange = true;
}
if(pos[1] > drawingAreaBottomRight[1]){
pos[1] = drawingAreaBottomRight[1];
outRange = true;
}
if(outRange){
console.warn("坐标超出绘图区域!" + origPos + " -> " + pos);
}
return pos;
}
/**
* 更新坐标, 考虑当前的坐标模式
* @param {[number, number]} newPos 新的坐标
*/
this.updatePos = function (newPos) {
if (isRelative) {
if (newPos[0] != null) {
absPos[0] += newPos[0];
}
if (newPos[1] != null) {let lastPos = lastPosList[lastPosList.length - 1];
let firstPos = posList_[0];
let distance = Math.sqrt(Math.pow(lastPos[0] - firstPos[0], 2) + Math.pow(lastPos[1] - firstPos[1], 2));
if(distance < maxMergeDistance){
posList_ = lastPosList.concat(posList_);
}
}
} else {
if (newPos[0] != null) {
absPos[0] = newPos[0];
}
if (newPos[1] != null) {
absPos[1] = newPos[1];
}
}
}
/**
* 添加手势
* @param {Array<[number,number]>} posList 经过的点的坐标列表(GCode坐标系, 绝对坐标)
* @param {number} duration 持续时间(毫秒)
*/
this.addGesture = function(posList, duration){
if(duration == 0 || posList == null || posList.length == 0){
return;
}
if(duration < 1) duration = 1;
//转换为屏幕坐标系
var posList_ = [];
for(let i = 0; i < posList.length; i++){
posList_.push(this.GCode2Screen(posList[i]));
}
//合并路径
if(allowMergePath){
let mergedLength = lastPosList.length + posList_.length;
if (mergedLength <= maxPathLength) {
let firstPos = posList_[0];
let distance = 0;
if (lastPosList.length > 0) {
let lastPos = lastPosList[lastPosList.length - 1];
distance = this.distance(lastPos, firstPos);
}
if (distance <= maxMergeDistance) { //可以合并
lastPosList = lastPosList.concat(posList_);
lastDuration += duration;
} else { //不能合并
if (lastPosList.length > 0) {
gestures.push([lastDuration].concat(lastPosList));
}
lastPosList = posList_;
lastDuration = duration;
}
}else{
if(lastPosList.length > 0){
gestures.push([lastDuration].concat(lastPosList));
}
lastPosList = posList_;
lastDuration = duration;
}
} else {
if (posList_.length > 0) {
gestures.push([duration].concat(posList_));
}
}
}
/**
* 计算两点之间的直线距离
* @param {[number, number]} pos1 点1
* @param {[number, number]} pos2 点2
* @returns {number} 两点之间的直线距离
*/
this.distance = function(pos1, pos2){
return Math.sqrt(Math.pow(pos1[0] - pos2[0], 2) + Math.pow(pos1[1] - pos2[1], 2));
}
/**
* 计算移动需要的时间
* @param {number} distance 移动距离
* @returns {number} 移动需要的时间(毫秒)
*/
this.calcDuration = function(distance){
return distance / feedRate * 60 * 1000 / speedScale;
}
/**
* G0/G1
* @param {number|null} x x坐标
* @param {number|null} y y坐标
* @param {number|null} z z坐标, 用来控制笔刷
* @param {number|null} f 进给速度
*/
this.G0G1 = function(x, y, z, f){
if(x == null && y == null && z == null && f == null){
return;
}
if(f != null){
feedRate = f;
}
if (z > 0) isPenDown = false;
if (z < 0) isPenDown = true;
let lastPos = absPos.slice();
this.updatePos([x, y]);
if(feedRate == 0){
return;
}
if(isPenDown){
let duration = this.calcDuration(this.distance(lastPos, absPos));
this.addGesture([lastPos, absPos], duration);
}
}
/**
* G2/G3
* @param {number|null} x x坐标
* @param {number|null} y y坐标
* @param {number|null} z z坐标, 用来控制笔刷
* @param {number|null} i x轴圆心偏移量
* @param {number|null} j y轴圆心偏移量
* @param {number|null} f 进给速度
* @param {boolean} isClockwise 是否顺时针
*/
this.G2G3 = function(x, y, z, i, j, f, isClockwise){
if(f != null){
feedRate = f;
}
if (z > 0) isPenDown = false;
if (z < 0) isPenDown = true;
let lastPos = absPos.slice();
this.updatePos([x, y]);
let centerPos = [lastPos[0] + i, lastPos[1] + j];
let radius = this.distance(centerPos, lastPos);
let startAngle = Math.atan2(lastPos[1] - centerPos[1], lastPos[0] - centerPos[0]);
let endAngle = Math.atan2(absPos[1] - centerPos[1], absPos[0] - centerPos[0]);
if(isClockwise){
if(endAngle > startAngle){
endAngle -= 2 * Math.PI;
}
}else{
if(endAngle < startAngle){
endAngle += 2 * Math.PI;
}
}
let angle = endAngle - startAngle;
let distance = radius * angle;
let duration = this.calcDuration(distance);
let segmentCount = Math.ceil(distance / arcSegLength);
let posList = [];
for(let i = 0; i <= segmentCount; i++){
let angle_ = startAngle + angle * i / segmentCount;
let pos = [centerPos[0] + radius * Math.cos(angle_), centerPos[1] + radius * Math.sin(angle_)];
posList.push(pos);
}
this.addGesture(posList, duration);
}
/**
* 解析GCode
* @param {string} gcode GCode
*/
this.parseGCodeLine = function(gcode){
let gcode_ = gcode.trim();
//删除括号内的内容
gcode_ = gcode_.replace(/\(.*?\)/g, "");
//删除注释
gcode_ = gcode_.replace(/;.*/g, "");
//删除多余空格
gcode_ = gcode_.replace(/\s+/g, " ");
//是否是空行
if(gcode_ == ""){
return;
}
//分割
let gcodeList = gcode_.split(" ");
let cmd = gcodeList[0];
let params = {};
for(let i = 1; i < gcodeList.length; i++){
let param = gcodeList[i];
let key = param[0];
let value = parseFloat(param.substr(1));
params[key] = value;
}
switch(cmd){
case "G00":
case "G01":
this.G0G1(params["X"], params["Y"], params["Z"], params["F"]);
break;
case "G02":
this.G2G3(params["X"], params["Y"], params["Z"], params["I"], params["J"], params["F"], true);
break;
case "G03":
this.G2G3(params["X"], params["Y"], params["Z"], params["I"], params["J"], params["F"], false);
break;
case "G90":
isRelative = false;
break;
case "G91":
isRelative = true;
break;
case "M03":
case "M3":
isPenDown = true;
break;
case "M05":
case "M5":
isPenDown = false;
break;
case "M2":
case "M30":
console.log("GCode解析完成");
default:
console.warn("未实现的GCode指令: " + gcode);
break;
}
}
/**
* 解析GCode
* @param {string} gcode GCode
*/
this.parseGCode = function(gcode){
let gcodeList = gcode.split("\n");
for(let i = 0; i < gcodeList.length; i++){
this.parseGCodeLine(gcodeList[i]);
if(i % 500 == 0){
console.log(i + "/" + gcodeList.length);
}
}
return gestures;
}
}
function drawGcode(gcode){
let gCodeGestureGenerator = new GCodeGestureGenerator();
gCodeGestureGenerator.setDrawingArea(printAreaBegin, printAreaEnd);
let gestureList = gCodeGestureGenerator.parseGCode(gcode);
for(let i = 0; i < gestureList.length; i++){
try{
gesture.apply(null, gestureList[i]);
}catch(e){
console.log(e);
console.log("gestureList[i]: " + JSON.stringify(gestureList[i]));
}
//sleep(gestureList[i][0]);
console.log("进度: " + (i + 1) + "/" + gestureList.length + " (" + ((i + 1) / gestureList.length * 100).toFixed(2) + "%)");
}
}