好的,给你看一个最核心的还原算法 代码示例。它本质上就是根据顺序数组,把碎片"剪切"并"粘贴"回正确位置。
假设你已经通过逆向找到了控制顺序的数组 order(比如 [5, 1, 3, 0, 2, 4]),并且原图是被切成 2行 x 3列 的碎片。下面是具体的 JavaScript 实现:
javascript
/**
* 还原被切割打乱的验证码图片
* @param {HTMLCanvasElement} canvas - 页面上显示乱序图片的那个画布元素
* @param {Array} order - 还原顺序数组,比如 [5, 1, 3, 0, 2, 4]
* 意思:还原后第0个位置放原图的第5块,第1个位置放原图的第1块...
* @param {number} cols - 原图被切割成了几列(横向分成几份)
* @param {number} rows - 原图被切割成了几行(纵向分成几份)
*/
function restoreCaptcha(canvas, order, cols, rows) {
// ----- 第一步:准备工作 -----
// 拿到画布的"2D画笔",之后所有绘图操作都通过它
const ctx = canvas.getContext('2d');
// 获取画布的宽和高(单位:像素)
const width = canvas.width;
const height = canvas.height;
// 计算每个小碎片的宽度和高度(总宽/列数,总高/行数)
const pieceW = width / cols;
const pieceH = height / rows;
// ----- 第二步:备份当前乱序图片 -----
// 创建一个"看不见的"临时画布(只存在于内存中,不在页面上显示)
const tempCanvas = document.createElement('canvas');
tempCanvas.width = width;
tempCanvas.height = height;
// 获取这个临时画布的画笔
const tempCtx = tempCanvas.getContext('2d');
// 把当前画布上的乱序图片,完整地"复制"到临时画布中
// 这样临时画布里保存的就是原始乱序图片的"快照"
tempCtx.drawImage(canvas, 0, 0);
// ----- 第三步:清空原画布,准备画还原后的图 -----
// 清空原画布,相当于擦掉黑板上的内容,准备重新绘制
ctx.clearRect(0, 0, width, height);
// ----- 第四步:按照order数组逐个拼图 -----
// 循环遍历 order 数组的每一个元素,i 代表"还原后"的位置编号
for (let i = 0; i < order.length; i++) {
// 从 order 中取出当前应该使用的"原图碎片编号"
const srcIndex = order[i];
// --- 计算"原图碎片"在临时画布上的位置 ---
// 原图碎片按行优先编号,第0块是第0行第0列,第1块是第0行第1列...
// srcX = (编号 % 列数) * 碎片宽度,得到碎片的左上角x坐标
const srcX = (srcIndex % cols) * pieceW;
// srcY = Math.floor(编号 / 列数) * 碎片高度,得到碎片的左上角y坐标
const srcY = Math.floor(srcIndex / cols) * pieceH;
// --- 计算"还原后"碎片应该放在目标画布的哪个位置 ---
// 还原后的位置也是按行优先,i 就是目标位置编号
const dstX = (i % cols) * pieceW; // 目标位置的x坐标
const dstY = Math.floor(i / cols) * pieceH; // 目标位置的y坐标
// --- 第五步:从临时画布里"抠"出碎片,贴到原画布的正确位置 ---
// ctx.drawImage(源画布, 源x, 源y, 源宽, 源高, 目标x, 目标y, 目标宽, 目标高)
// 含义:从临时画布的 (srcX, srcY) 处,截取宽 pieceW 高 pieceH 的一块,
// 然后绘制到原画布的 (dstX, dstY) 处,宽度和高度保持不变。
ctx.drawImage(
tempCanvas, // 从哪里取图(临时画布)
srcX, srcY, // 取图的起始坐标(左上角)
pieceW, pieceH,// 取图的尺寸(即碎片的宽高)
dstX, dstY, // 贴到目标画布的哪个位置(左上角坐标)
pieceW, pieceH // 贴上去的尺寸(通常和取图尺寸一样)
);
// 循环结束后,所有碎片都贴到了正确位置,原画布上就呈现完整的还原图片了
}
}
💡 这个代码的逻辑拆解
- 输入数据:你需要提供画布元素、顺序数组以及行列数(这些数值都可以通过分析前端JS找到)。
- 关键动作 :循环遍历
order数组。假设i = 0,order[0] = 5,意思就是"还原后的第 1 个位置,放原来打乱图中的第 6 个碎片"。 - 坐标换算 :通过
%(取余)和Math.floor(整除)来算出第 N 个碎片所在的(x, y)坐标。 - 绘制还原 :使用
ctx.drawImage的裁剪功能,从临时画布把碎片"抠"出来,再贴到新位置。
🐍 如果是 Python (Pillow) 版本
逻辑完全一样,便于你在后端处理:
python
from PIL import Image
def restore_captcha(img_path, order, cols, rows):
img = Image.open(img_path)
w, h = img.size
pw, ph = w // cols, h // rows
new_img = Image.new('RGB', (w, h))
for i, src_idx in enumerate(order):
src_x = (src_idx % cols) * pw
src_y = (src_idx // cols) * ph
box = (src_x, src_y, src_x + pw, src_y + ph)
piece = img.crop(box)
dst_x = (i % cols) * pw
dst_y = (i // cols) * ph
new_img.paste(piece, (dst_x, dst_y))
return new_img
⚠️ 特别提醒
在实际的验证码中,顺序数组 order 往往不是写死的,而是由JS动态生成的(比如用 new Date().getTime() 做种子随机打乱)。你的核心任务依然是在浏览器调试器中动态捕获这个数组,然后把它传给上面的还原函数。
先在纸上画个格子。我带你一步步"走"一遍,保证你看完就豁然开朗。
🎯 第一步:画一个具体的"拼图"例子
假设图片被切成了 2行(rows=2) 、3列(cols=3) ,一共就是 6块碎片。
我们给这6块碎片编号(从0开始),它们在原图上的正确位置是这样的:
| 行\列 | 第0列 | 第1列 | 第2列 |
|---|---|---|---|
| 第0行 | 碎片 0 | 碎片 1 | 碎片 2 |
| 第1行 | 碎片 3 | 碎片 4 | 碎片 5 |
你看,编号就是每个碎片的"身份证",**坐标(x, y)**就是它家在画布上的具体左上角位置。
🔢 第二步:揭秘公式在算什么(坐标换算)
我们要把"碎片编号(如 4)"变成"屏幕上的像素坐标(如 x=80, y=80)"。
- x坐标(横着数):看它在第几列。
- y坐标(竖着数):看它在第几行。
公式拆解(假设每块宽80px,高80px):
- 算列号(第几列) :
列号 = 编号 % 列数(% 是取余数) - 算行号(第几行) :
行号 = Math.floor(编号 / 列数)(除法取整数,不要小数)
✍️ 第三步:代入具体数字,亲眼看看
我们拿 "碎片 4" 来算(就是表格里第二行第二列那块)。
-
算 x 坐标(列) :
4 % 3 = 1(余数是1)。➡️ 说明它在第1列 (第0列、第1列、第2列中的第1列)。
➡️ 像素位置:
1 * 80px = 80px。 -
算 y 坐标(行) :
Math.floor(4 / 3) = 1(4除以3等于1.333,取整是1)。➡️ 说明它在第1行 (第0行、第1行中的第1行)。
➡️ 像素位置:
1 * 80px = 80px。
结果 :碎片4的左上角坐标就是 (x=80, y=80)。
你回头看看上面的表格,碎片4是不是正好在"第1行、第1列"?坐标完全对应!
🔄 第四步:把"还原动作"想象成玩拼图
现在回到你给的代码,它其实在干两件事:
-
从临时画布里取碎片(源坐标)
比如
order[i] = 4,代码算出srcX=80, srcY=80,也就是从临时画布的右下角(碎片4的位置)把图抠出来。 -
把碎片贴到目标画布的正确位置(目标坐标)
假设此时
i = 0(目标位置是左上角),代码算出dstX=0, dstY=0。于是,它就把刚刚从右下角抠出来的碎片4,贴到了左上角(位置0)。
循环结束后,所有碎片都从错误的"老位置"搬到了正确的"新家",图片就还原了!
🧠 终极记忆口诀
记不住公式的时候,就想想电影院找座位:
编号 % 列数= 第几排(列)Math.floor(编号 / 列数)= 第几号(行)