绘制图像-clip方法

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 版本的核心思路

不是"去源图取东西",而是"把图片拉到窗口下面"

  1. clip() 在画布上开了一个"窗口",只能看到窗口下面的内容
  2. 通过 translate/scale 变换坐标系
  3. 负坐标 把图片的 (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>
相关推荐
焦糖玛奇朵婷2 小时前
解锁扭蛋机小程序的五大优势
java·大数据·服务器·前端·小程序
SwJieJie2 小时前
windsurf的配置和项目规则、工作流、agent技巧使用
前端
白日梦想家6812 小时前
从基础入手,分清一次性定时器与永久定时器
前端
AIwork4me2 小时前
别再把 RAG 当知识库:用 AutoClaw 搭一套会进化的 Karpathy LLM Wiki
前端
彩票管理中心秘书长2 小时前
Git 归档与补丁命令大全(完整详解版)
前端
RePeaT2 小时前
【Nginx】前端项目部署与反向代理实战指南
前端·nginx
索木木3 小时前
ShortCut MoE模型分析
前端·html
MXN_小南学前端4 小时前
Vue3 + Spring Boot 工单系统实战:用户反馈和客服处理的完整闭环(提供gitHub仓库地址)
前端·javascript·spring boot·后端·开源·github
轮子大叔4 小时前
CSS基础入门
前端·css