Canvas:掌握图像变换合成与裁剪状态像素操作

想象一下,用几行代码就能创造出如此逼真的图像和动画,仿佛将艺术与科技完美融合,前端开发的Canvas技术正是这个数字化时代中最具魔力的一环,它不仅仅是网页的一部分,更是一个无限创意的画布,一个让你的想象力自由驰骋的平台。

目录

图像变换设置

图像合成设置

裁剪路径设置

状态保存与恢复

像素操作设置

图像变换设置

接下来我们开始讲解如何对canvas绘制的图像进行一些简单的操作,这里借助代码进行一个简单的概述及演示:

位移操作:这里借助translate函数传递两个参数代表水平和竖直移动的距离:

html 复制代码
<body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <script>
        let canvas = document.getElementById("canvas");      
        // 获取2维画笔,上下文对象
        let ctx = canvas.getContext("2d");
        // 位移,translate,位移坐标系,水平位移和竖直位移
        ctx.translate(100, 100)
        // 绘制矩形
        ctx.fillRect(0, 0, 50, 50)
    </script>
</body>

缩放操作:这里借助scale函数传递两个参数代表水平和竖直缩放的倍速:

html 复制代码
<body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <script>
        let canvas = document.getElementById("canvas");      
        // 获取2维画笔,上下文对象
        let ctx = canvas.getContext("2d");
        // 缩放,scale,水平缩放5倍,垂直缩放2倍
        ctx.scale(5, 2)
        // 绘制矩形
        ctx.fillRect(0, 0, 50, 50)
    </script>
</body>

旋转操作:这里借助rotate函数传递一个参数代表旋转的角度:

html 复制代码
<body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <script>
        let canvas = document.getElementById("canvas");      
        // 获取2维画笔,上下文对象
        let ctx = canvas.getContext("2d");
        // 旋转,rotate,旋转的角度
        ctx.rotate(Math.PI / 4);
        ctx.translate(300, 0);
        
        // 绘制矩形
        ctx.fillRect(-250, -25, 500, 50)
    </script>
</body>

综合操作:这里借助transform函数传递六个参数代表不同的作用:

html 复制代码
<body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <script>
        let canvas = document.getElementById("canvas");      
        // 获取2维画笔,上下文对象
        let ctx = canvas.getContext("2d");
        // transform,参数分别代表:
        /*
            1. 水平缩放
            2. 水平倾斜
            3. 垂直倾斜
            4. 垂直缩放
            5. 水平位移
            6. 垂直位移
        */
        ctx.transform(2, 1, -1, 2, 50, 0); 
        
        // 绘制矩形
        ctx.fillRect(0, 0, 50, 50)
    </script>
</body>

图像合成设置

在canvas中不仅可以在已有图形后面再画新图形,还可以用来遮盖指定区域,清除画布中的某些部分(清除区域不仅限于矩形,像clearRect()方法做的那样)以及更多其他操作,这里就其做一个简单的概述,globalCompositeOperation = type 这个属性设定了在画新图形时采用的遮盖策略,其值是一个标识 12 种遮盖方式的字符串。这里给出其简易的示例代码:

source-over:这是默认设置,并在现有画布上下文之上绘制新图形。

source-in:新图形只在新图形和目标画布重叠的地方绘制。其他的都是透明的。

html 复制代码
<body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <script>
        let canvas = document.getElementById("canvas");      
        // 获取2维画笔,上下文对象
        let ctx = canvas.getContext("2d");
        
        // 绘制矩形
        ctx.fillStyle = "rgba(0, 0, 255, 1)"; // 蓝色
        ctx.fillRect(300, 200, 100, 100)
        ctx.globalCompositeOperation='source-in'; // 蓝色和红色重叠部分的红色区域
        ctx.fillStyle = "rgba(255, 0, 0, 1)"; // 红色
        ctx.fillRect(250, 150, 100, 100)
    </script>
</body>

source-out:在不与现有画布内容重叠的地方绘制新图形。

source-atop:新图形只在与现有画布内容重叠的地方绘制。

destination-over:在现有的画布内容后面绘制新的图形。

destination-in:现有的画布内容保持在新图形和现有画布内容重叠的位置。其他的都是透明的。

destination-out:现有内容保持在新图形不重叠的地方。

destination-atop:现有的画布只保留与新图形重叠的部分,新的图形是在画布内容后面绘制的。

lighter:两个重叠图形的颜色是通过颜色值相加来确定的。

copy:只显示新图形。

当然还有一些其他的属性,如下供大家参考:

xor:图像中,那些重叠和正常绘制之外的其他地方是透明的。

multiply:将顶层像素与底层相应像素相乘,结果是一幅更黑暗的图片。

screen:像素被倒转,相乘,再倒转,结果是一幅更明亮的图片。

overlay:multiply和screen的结合,原本暗的地方更暗,原本亮的地方更亮。

darken:保留两个图层中最暗的像素。

lighten:保留两个图层中最亮的像素。

color-dodge:将底层除以顶层的反置。

color-burn:将反置的底层除以顶层,然后将结果反过来。

hard-light:屏幕相乘(Acombinationofmultiplyandscreen)类似于叠加,但上下图层互换了。

soft-light:用顶层减去底层或者相反来得到一个正值。

difference:一个柔和版本的强光(hard-light)。纯黑或纯白不会导致纯黑或纯白。

exclusion:和difference相似,但对比度较低。

hue:保留了底层的亮度(luma)和色度(chroma),同时采用了顶层的色调(hue)。

saturation:保留底层的亮度(luma)和色调(hue),同时采用顶层的色度(chroma)。

color:保留了底层的亮度(luma),同时采用了顶层的色调(hue)和色度(chroma)。

luminosity:保持底层的色调(hue)和色度(chroma),同时采用顶层的亮度(luma)。

接下来我们可以借助globalCompositeOperation属性中的destination-out属性值实现一个类似刮刮卡的效果出来,具体的代码如下所示:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        #ggk {
            width: 600px;
            height: 400px;
            font-size: 30px;
            font-weight: 900;
            text-align: center;
            line-height: 400px;
            overflow: hidden;
            position: absolute;
            left: 0;
            top: 0;
            z-index: -1;
        }
    </style>
</head>
<body>
    <div id="ggk">谢谢回顾</div>
    <canvas id="canvas" width="600" height="400"></canvas>
    <script>
        let canvas = document.getElementById("canvas");      
        // 获取2维画笔,上下文对象
        let ctx = canvas.getContext("2d");
        // 是否可以挂卡
        let isDraw = false
        
        // 设置canvas背景
        let img = new Image();
        img.src = "./1.jpg"
        img.onload = function () {
            ctx.drawImage(img, 0, 0, 600, 400)
        }
        // 鼠标按下去可以挂卡
        canvas.onmousedown = function () {
            isDraw = true
        }
        // 鼠标抬起可以停止挂卡
        canvas.onmouseup = function () {
            isDraw = false
        }
        // 鼠标移动可以挂卡
        canvas.onmousemove = function (e) {
            if (isDraw) {
                let x = e.pageX
                let y = e.pageY
                ctx.globalCompositeOperation = "destination-out";
                ctx.arc(x, y, 20, 0, 2 * Math.PI)
                ctx.fill()
            }
        }
        // 随机中奖事件
        let random = Math.random()
        if (random < 0.1) {
            let ggkDiv = document.getElementById("ggk");
            ggkDiv.innerHTML = "恭喜你中奖了"
        }
    </script>
</body>
</html> 

最终呈现的效果如下所示:

裁剪路径设置

裁切路径和普通的canvas图形差不多,不同的是它的作用是遮罩,用来隐藏不需要的部分,如果和上面介绍的globalCompositeOperation属性作一比较,它可以实现与source-in和source-atop差不多的效果。最重要的区别是裁切路径不会在canvas上绘制东西,而且它永远不受新图形的影响。这些特性使得它在特定区域里绘制图形时相当好用。给出如下代码示例:

html 复制代码
<body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <script>
        let canvas = document.getElementById("canvas");      
        // 获取2维画笔,上下文对象
        let ctx = canvas.getContext("2d");
        let chatPath = new Path2D();
        chatPath.moveTo(200, 300)
        chatPath.quadraticCurveTo(150, 300, 150, 200); 
        chatPath.quadraticCurveTo(150, 100, 300, 100); 
        chatPath.quadraticCurveTo(450, 100, 450, 200); 
        chatPath.quadraticCurveTo(450, 300, 250, 300); 
        chatPath.quadraticCurveTo(250, 350, 150, 350); 
        chatPath.quadraticCurveTo(200, 350, 200, 300); 
        ctx.clip(chatPath) // 裁剪路径

        // 获取图片
        let img = new Image();
        img.src = "./1.jpg";
        img.onload = function () {
            ctx.drawImage(img, 0, 0, 600, 400)
            ctx.lineWidth = 5 // 路径的宽度
            ctx.stroke(chatPath) // 显示路径,可有可无
        }
    </script>
</body>

最终呈现的效果如下所示:

状态保存与恢复

canvas的状态就是当前画面应用的所有样式和变形的一个快照,save和restore方法是用来保存和恢复canvas状态的且都没有参数,canvas状态存储在栈中,每当save()方法被调用后,当前的状态就被推送到栈中保存。示例代码如下所示:

html 复制代码
<body>
    <canvas id="canvas" width="800" height="800"></canvas>
    <script>
        let canvas = document.getElementById("canvas");      
        // 获取2维画笔,上下文对象
        let ctx = canvas.getContext("2d");
        // 绘制矩形
        ctx.fillStyle = "red";
        ctx.fillRect(0, 0, 100, 100);
        ctx.save() // 保存当前状态

        ctx.fillStyle = "green";
        ctx.fillRect(100, 100, 100, 100);
        ctx.save() // 保存当前状态

        ctx.fillStyle = "blue";
        ctx.fillRect(200, 200, 100, 100);
        ctx.save() // 保存当前状态

        ctx.fillStyle = "yellow";
        ctx.fillRect(300, 300, 100, 100);
 
        ctx.restore() // 恢复到上一次保存的状态
        ctx.fillRect(400, 400, 100, 100) // 蓝色

        ctx.restore() // 恢复到上一次保存的状态
        ctx.fillRect(500, 500, 100, 100) // 绿色
    </script>
</body>

最终呈现的效果如下所示:

像素操作设置

在canvas中我们可以直接通过ImageData对象操纵像素数据,直接读取或将数据数组写入该对象中,接下来我们开始讲解如何控制图像使其平滑(反锯齿)以及如何从canvas画布中保存对象。在让一些代码中我们通过getImageData获取图片的像素数据,并对其进行像素数据的修改,如下:

html 复制代码
<body>
    <canvas id="canvas" width="600" height="400"></canvas>
    <script>
        let canvas = document.getElementById("canvas");      
        // 获取2维画笔,上下文对象
        let ctx = canvas.getContext("2d");
        // 获取图片
        let img = new Image();
        img.src = "./1.jpg"
        img.onload = function () {
            ctx.drawImage(img, 0, 0, 600, 400);
            let imageData = ctx.getImageData(0, 0, 600, 400) // 获取图片信息数组
            for (let i = 0; i < imageData.data.length; i+=4) {
                // 计算当前像素的平均值
                let avg = (imageData.data[i] + imageData.data[i+1] + imageData.data[i+2]) / 3;
                imageData.data[i] = avg
                imageData.data[i + 1] = avg
                imageData.data[i + 2] = avg
                imageData.data[i + 3] = 255
            }
            // 将修改后的数据呈现渲染到画布上
            ctx.putImageData(imageData, 0, 0)
        }
    </script>
</body>

最终呈现的效果如下所示,可以看到我们对原本的图片实现了一个复古效果的展示:

如果想实现对图片像素的反向操作,可以设置如下的代码进行实现:

呈现的效果如下所示:

如果想绘制从某一坐标到另一坐标的的区域位置的话,可以通过如下代码操作:

javascript 复制代码
ctx.putImageData(imageData, 0, 0, 300, 200, 600, 400)
相关推荐
加班是不可能的,除非双倍日工资4 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi4 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip5 小时前
vite和webpack打包结构控制
前端·javascript
excel5 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国5 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼5 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy6 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT6 小时前
promise & async await总结
前端
Jerry说前后端6 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化
画个太阳作晴天6 小时前
A12预装app
linux·服务器·前端