Chapter5 - 当一个勤劳的园丁,来修剪我们美丽的树(II)Canvas素材 修图、压缩、效能优化

树叶问题

先前在第三章画树时,就有发现把树叶画上去时,系统工作时间会增加而导致掉侦,原图是300x300,50kb左右,不是很大,但是我们要画一大堆落叶的话,计算量就会很大,因为影像处理的底层就是去运算300x300个像素点,然後再根据要求的大小,比如50x50,再进行缩放,而context.drawImage方法每次都会经历这一个步骤,因此重复多次相当花费效能,为此我们有两种方法做搭配:

  1. 编辑原本的PNG图档,再拿去像TinyPng这样的网站做影像压缩
  2. 在另一个迷你的Canvas(50x50)中以drawImage方法完成一次缩放後,之後以Canvas作为drawImage的图片来源,可以参考该方法的说明,包括图片、影片、SVG也都是可以绘制

处理素材

预估每个叶子最大的大小为100x100像素,我们就依此来裁切原档,在修图时有个重点,因为在Canvas在绘制图形时,都是以左上角为准,如果我们的素材不对齐左上角,就还要额外去做平移和计算,因此为了JS处理具有一致性和方便性,我们把所有叶子的根部都对齐左上:
https://ithelp.ithome.com.tw/upload/images/20211004/20135197uaXQbdUaiB.png

接着就是基本操作:拉辅助线、旋转缩放、调整版面尺寸

值得注意的是,100x100和300x300的大小可是差了约9倍,相当可观,即使调整微小,也是会省去不少效能,实际上裁切完的大小是:50kb >>> 12kb,接着再进一步拿去影像压缩:
https://ithelp.ithome.com.tw/upload/images/20211004/201351978bkGJIkm2o.png
又变得更小,剩下4kb了!不过提醒大家,一定要保留原档,因为所谓的压缩,本质上是一种对於档案的毁损,同时它也失去再修图的可能,只不过是肉眼看不太出来而已:
https://ithelp.ithome.com.tw/upload/images/20211004/20135197vA78eNH8je.png

绘制树叶

在树建立之初在Stick的原型上设置min变数为20,如此以来每一个节点都可以由node.min来取得该变数,而不会被其他无关的物件来取用到:Stick.prototype.min = 20;

if(node.r < node.min){
    // 在树枝建立之初就有设置过node.img,但为了避免pngImg存有的canvas还未被建立,而重新指向
    // 也可以在更源头的地方解决,即图片全部载入完毕,才开始游戏
    if(node.img == undefined) node.img = pngImg[1 + Math.floor(random(2))];
    else{
        context.strokeStyle = 'rgba(120, 215, 140, 1)';
        context.save();
        context.translate(x, y);
        context.rotate(-Math.PI / 4 - theta2);
        context.drawImage(node.img, 0, 0,
                          node.r * node.grow * 1.5,
                          node.r * node.grow * 1.5);
        context.restore();
    }
}

在canvas的座标中,不指Y是反过来的,包括角度也是反过来(顺时针为正),一开始原点为图片的左上角,表示叶子面向右下角,即45度(Math.PI/4),因此要先减去该角度,再减去theta2(该节点的生长方向)

搭配第二个方法试试

压缩图片之後,对效能的确有帮助,但似乎不太够,毕竟大约有近千片树叶等着绘制,在console中可观察到Tree.Draw()花了特别多的时间,其范围为22.74-31.24秒不等,会造成严重的掉侦问题:
https://ithelp.ithome.com.tw/upload/images/20211004/20135197l3Yulxm1u2.png

因此我们先尝试刚刚说的先将图片进行压缩,放到更小的画布上:

let leafImg = new Array();
let pngImg = new Array();
for(let N = 0; N < 4; N++){
    leafImg[N] = new Image();
    // 等待每一张图片读取好
    leafImg[N].onload = () => {
        // 每张图片创建一个对应的画布
        pngImg[N] = document.createElement('canvas');
        pngImg[N].width = 30;
        pngImg[N].height = 30;
        let ctx = pngImg[N].getContext("2d");
        // 画一次就可以了,以後就拿pngImg[N]来当图片(N为0~3之间)
        ctx.drawImage(leafImg[N], 0, 0, 30, 30);
    }
}
leafImg[0].src = "../images/tinyPng/Leave2O.png";
leafImg[1].src = "../images/tinyPng/Leave2Y.png";
leafImg[2].src = "../images/tinyPng/LeaveRY.png";
leafImg[3].src = "../images/tinyPng/LeaveRR.png";
// 设定完档案路径网页端才会开始读取,接着触发刚刚写的onload

实际尝试过後,似乎对於效能没有显着的帮助,至少还是需要21.64秒,仅快了1秒左右
https://ithelp.ithome.com.tw/upload/images/20211004/20135197kUgRCgq9te.png

不过值得注意的是,不只是树叶花了很多时间,其实当初在设计并绘制树枝时,也浪费了很多效能,如果把绘制树枝的贝兹曲线去掉,只保留节点的计算和树叶,便会发现时间落在11.03-13.34秒左右:
https://ithelp.ithome.com.tw/upload/images/20211004/20135197X7NvFZbqCQ.png
由此可知,演算约1400个树枝花了近10秒,演算约1000个树叶花了近12秒,算是相当接近了,

而只绘制树叶的效能表现也是好很多,因此之後设计游戏的时候,会考虑只有在游戏读取画面时,会有让树长大动画,而在玩游戏时,只有一个静置的树在後面(可能是另外一个canvas),不做重复绘制的动作。

当然,我们也可以把RATIO改回1,事情就会单纯很多:
(更正:由於Ratio改变,min=20就从原本的10px变成20px,使得树叶少递回1~2次,以下两个例子实际节点只有450个,约为原本的1/3)

100x100 - 4.92秒(更正:使min=10px,则约为10~11秒)

https://ithelp.ithome.com.tw/upload/images/20211005/20135197aFtDX9RvJF.png

30x30 - 2.60秒(更正:使min=10px,则约为8~9秒)

https://ithelp.ithome.com.tw/upload/images/20211005/201351976ejcT7sGYk.png

相较之下,就没什麽效能问题,不过就没有原本想要的高解析度了,毕竟少了Ratio增加画布大小,就只是单纯拿30x30的图片去放大

成果

再想想看怎麽办吧!以下是用RATIO=2绘制出来的,是真的比上面两张图精致好看很多。

https://ithelp.ithome.com.tw/upload/images/20211004/20135197oQzu84QgMA.png
https://ithelp.ithome.com.tw/upload/images/20211004/20135197Ret4Pfwslz.png

今天都就先用现有素材来做,不特别去找红花绿叶了xd,
不知道大家觉得哪个比较好看呢?


<<:  Day 31 | 常见 Livewire 问题: jQuery 在渲染时会打回原形

>>:  RISC-V: Memory Fence 指令

[访谈] APCS x 竞程沙漠 Howard

今天邀请到同系同级的 Howard 来分享他在高中学习程序的经历和对於 APCS 的想法~ 程序学习...

[13th][Day20] http request header(下)

接续昨天 header 的部分: If-Modified-Since:只在最近有来源最近有异动时发送...

Day 26 - Vue 与 HTTP请求 (1)

前一天中我们讲解了如何利用Vue CLI快速建立专案,再进入到专案开发之前,还是有一些知识需要恶补的...

Day2. Ruby 的基本介绍 - 让大家认识并爱上Ruby

Ruby on Rails为用Ruby程序语言写的开源网页框架,Rails的发明者DHH挑选了Rub...

第36天~就是自己KEY

这篇的上一篇:https://ithelp.ithome.com.tw/articles/10283...