1.前言
绘制图像,之前我们介绍过drawImage方法
javascript
ctx.drawImage(img, 300, 20, 500, 460, 20, 20, 1000, 920);
| 参数 | 含义 | 坐标系 |
|---|---|---|
| 300, 20 | 源图裁切起点 | 相对于图片左上角 (0,0) |
| 500, 460 | 源图裁切大小 | 相对于图片 |
| 20, 20 | 画布绘制起点 | 相对于画布左上角 (0,0) |
| 1000, 920 | 画布绘制大小 | 相对于画布 |
9 参数版是"主动去源图取东西" :从图片的 (300, 20) 位置开始,取 500x460 的区域,绘制到画布 (20, 20) 位置,缩放到 1000x920。
2.Transform + Clip 版本详解
目标效果
用 transform + clip 实现 9 参数 drawImage 的等价效果:
javascript
// 9 参数原版
ctx.drawImage(img, 300, 20, 500, 460, 20, 20, 1000, 920);
// transform + clip 版本
ctx.save();
ctx.translate(1100, 20);
ctx.scale(2.0, 2.0);
ctx.rect(0, 0, 500, 460);
ctx.clip();
ctx.drawImage(img, -300, -20);
ctx.restore();
Transform + Clip 版本的核心思路
不是"去源图取东西",而是"把图片拉到窗口下面":
clip()在画布上开了一个"窗口",只能看到窗口下面的内容- 通过
translate/scale变换坐标系 - 用负坐标 把图片的
(300, 20)区域"拉"到窗口下面
每一步的坐标系变化
初始状态
scss
画布坐标系:原点在 (0, 0)
第 1 步:translate(1100, 20)
scss
画布坐标系:原点移动到了 (1100, 20)
现在的 (0, 0) 实际是画布的 (1100, 20)
第 2 步:scale(2.0, 2.0)
scss
画布坐标系:单位长度放大 2 倍
现在的 1 个单位 = 原来的 2 个像素
现在的 (1, 1) 实际是画布的 (1100+2, 20+2) = (1102, 24)
第 3 步:rect(0, 0, 500, 460) + clip()
scss
在**当前坐标系**中定义裁切区域:
- 起点:(0, 0) → 实际画布 (1100, 20)
- 大小:500x460 → 实际画布 1000x920(因为缩放了 2 倍)
所以 clip 区域实际是画布的 (1100, 20, 1000, 920)
第 4 步:drawImage(img, -300, -20)
markdown
在**当前坐标系**中绘制图片:
- 图片的 (0, 0) 点画在 (-300, -20) 位置
换算成实际画布坐标:
- x: 1100 + (-300)*2 = 1100 - 600 = 500
- y: 20 + (-20)*2 = 20 - 40 = -20
关键理解:
- clip 区域在原点 (0, 0) 开始,大小 500x460(当前坐标系)
- 图片从 (-300, -20) 开始画
- 所以 clip 区域内显示的是图片的 (300, 20) 开始的部分
为什么要用负坐标?图解
clip 窗口(固定不动)
scss
┌─────────────────────────┐
│ │
│ (0,0) │ 只能看到这个矩形内的东西
│ ┌─────────┐ │
│ │ │ │
│ │ 可见 │ │
│ │ 区域 │ │
│ │ │ │
│ └─────────┘ │
│ │
└─────────────────────────┘
情况 1:drawImage(img, 0, 0) --- 图片左上角对齐窗口左上角
scss
┌─────────────────────────┐
│ img(0,0) 从这里开始画 │
│ ┌───────────────────────┤
│ │■■■■■可见■■■■■ │ 看到的是图片的 (0,0) 区域
│ └───────────────────────┤
─────────────────────────┘
情况 2:drawImage(img, -300, -20) --- 图片往左上角"拉"
scss
┌─────────────────────────┐
│ img(0,0) │ 图片起点在窗口外面(左上)
│ ┌───────────┤
│ │■■■■■■■■■ │ 窗口现在看到的是图片的
│ │■可见■ │ (300, 20) 区域!
│ │■■■■■■■■■ │
│ └───────────┤
└─────────────────────────┘
数学计算
scss
clip 窗口:从 (0, 0) 开始,大小 500x460
drawImage(img, -300, -20) 的含义:
- 图片的 (0, 0) 点 画在 (-300, -20) 位置
那么 clip 窗口内的 (0, 0) 点,对应图片的哪个位置?
- 窗口 (0, 0) - 图片起点 (-300, -20) = 图片上的 (300, 20)
所以窗口内显示的就是图片的 (300, 20) 开始的区域!
对比总结
| 版本 | 思路 | 坐标 |
|---|---|---|
| 9 参数版 | 从源图取:从图片 (300, 20) 开始取 500x460 | +300, +20(源图坐标) |
| transform 版 | 把图拉过来:把图片往反方向拉,让 (300, 20) 对齐窗口 | -300, -20(绘制起点偏移) |
关键理解
- 9 参数版的
(300, 20)是源图上的裁剪起点 - transform 版的
(-300, -20)是图片在画布上的绘制起点
两者方向相反,但效果等价!
为什么 clip 区域是 500x460,但显示是 1000x920?
javascript
ctx.scale(2.0, 2.0); // 先放大 2 倍
ctx.rect(0, 0, 500, 460); // 定义 500x460 的裁切区域
ctx.clip();
clip() 是在 scale() 之后调用的,所以:
rect(0, 0, 500, 460)是在缩放后的坐标系中定义的- 换算成实际画布像素:
500*2 = 1000,460*2 = 920 - 所以 clip 区域实际是
1000x920
完整的变换链
scss
原图 (1080x495)
│
│ drawImage(img, -300, -20)
│ → 图片的 (300, 20) 点对齐到 clip 窗口的 (0, 0)
▼
clip 窗口 (500x460 @ 缩放坐标系)
│
│ scale(2.0, 2.0)
│ → 放大到 1000x920 @ 画布像素
▼
画布显示区域 (1100, 20, 1000, 920)
记忆口诀
9 参数:从源图"取"东西 → 正坐标 (300, 20)
transform:把图片"拉"过来 → 负坐标 (-300, -20)
源代码案例
xml
<!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>
body{
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<canvas id="myCanvas"></canvas>
<script>
var canvas = document.getElementById("myCanvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext("2d");
var img = new Image();
img.src = "./imgs/月之暗面.png";
img.onload = function(){
console.log('图片尺寸:', img.width, 'x', img.height);
// 一.9参数原版
ctx.drawImage(img, 300, 20, 500, 460, 20, 20, 1000, 920);
// 用绿框标出
ctx.strokeStyle = 'lime';
ctx.lineWidth = 5;
ctx.strokeRect(20, 20, 1000, 920);
// ---------------------
// 二.transform + clip 版本
ctx.save();
// 1. 位移到目标位置
ctx.translate(1100, 20);
// 2. 缩放 2 倍
ctx.scale(2.0, 2.0);
// 3. 裁切 500x460 区域
ctx.beginPath();
ctx.rect(0, 0, 500, 460);
ctx.clip();
// 4. 绘图:关键!用负坐标让源图的 (300, 20) 对齐到 clip 区域的 (0, 0)
ctx.drawImage(img, -300, -20);//就是drawImage3个参数版 👉 源图只做"定位" (位移)
ctx.restore();
// 红框标出
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.lineWidth = 5;
ctx.strokeRect(1100, 20, 1000, 920);
}
</script>
</body>
</html>