验证码还原算法

好的,给你看一个最核心的还原算法 代码示例。它本质上就是根据顺序数组,把碎片"剪切"并"粘贴"回正确位置

假设你已经通过逆向找到了控制顺序的数组 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 // 贴上去的尺寸(通常和取图尺寸一样)
        );
        // 循环结束后,所有碎片都贴到了正确位置,原画布上就呈现完整的还原图片了
    }
}
💡 这个代码的逻辑拆解
  1. 输入数据:你需要提供画布元素、顺序数组以及行列数(这些数值都可以通过分析前端JS找到)。
  2. 关键动作 :循环遍历 order 数组。假设 i = 0order[0] = 5,意思就是"还原后的第 1 个位置,放原来打乱图中的第 6 个碎片"。
  3. 坐标换算 :通过 %(取余)和 Math.floor(整除)来算出第 N 个碎片所在的 (x, y) 坐标。
  4. 绘制还原 :使用 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):

  1. 算列号(第几列)列号 = 编号 % 列数(% 是取余数)
  2. 算行号(第几行)行号 = 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列"?坐标完全对应!


🔄 第四步:把"还原动作"想象成玩拼图

现在回到你给的代码,它其实在干两件事:

  1. 从临时画布里取碎片(源坐标)

    比如 order[i] = 4,代码算出 srcX=80, srcY=80,也就是从临时画布的右下角(碎片4的位置)把图抠出来。

  2. 把碎片贴到目标画布的正确位置(目标坐标)

    假设此时 i = 0(目标位置是左上角),代码算出 dstX=0, dstY=0

    于是,它就把刚刚从右下角抠出来的碎片4,贴到了左上角(位置0)。

循环结束后,所有碎片都从错误的"老位置"搬到了正确的"新家",图片就还原了!


🧠 终极记忆口诀

记不住公式的时候,就想想电影院找座位

  • 编号 % 列数 = 第几排(列)
  • Math.floor(编号 / 列数) = 第几号(行)