Ctrl K
ComunidadGuías…

frontend/test/crop-geometry.test.ts

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { describe, expect, it } from 'vitest';

import {
  boxPercent, centeredBox, clampBox, defaultPanelBox, frameWithin, handleOutside, HANDLE_ROOM, MIN_CROP_EDGE, moveBox,
  previewStyle, resizeFromCorner, scaleBox, toNatural, windowStyle,
} from '../src/crop-geometry';

/** 本机封套最常见的尺寸。 */
const SLEEVE = { width: 800, height: 538 };
/** 正封的宽高比,与 `src/peach/jav_poster_crop.py` 的 `PANEL_ASPECT` 同一个数。 */
const PANEL_ASPECT = 0.704;

describe('默认框', () => {
  it('方框按短边居中,头像框的两条边一样长', () => {
    expect(centeredBox(SLEEVE, 1)).toEqual({ x0: 131, y0: 0, x1: 669, y1: 538 });
  });

  it('不锁比例时默认框就是整张图', () => {
    expect(centeredBox(SLEEVE, null)).toEqual({ x0: 0, y0: 0, x1: 800, y1: 538 });
  });

  it('正封的默认框满高贴右缘,人只要挪折痕那一条边', () => {
    // 538×0.704≈379,从右缘 800 量回去就是 421——和算法在没找到折痕时给的数一致。
    expect(defaultPanelBox(SLEEVE, PANEL_ASPECT)).toEqual({ x0: 421, y0: 0, x1: 800, y1: 538 });
  });

  it('按比例取的宽超过图宽时退成整张图', () => {
    expect(defaultPanelBox({ width: 300, height: 538 }, PANEL_ASPECT))
      .toEqual({ x0: 0, y0: 0, x1: 300, y1: 538 });
  });

  it('尺寸还没量出来时不硬算', () => {
    expect(centeredBox({ width: 0, height: 0 }, 1)).toEqual({ x0: 0, y0: 0, x1: 0, y1: 0 });
  });
});

describe('围着取景区取景', () => {
  it('脸那块方图里取方框就是它自己,取 3:4 是削掉两侧、上下不动', () => {
    const face = { x0: 592, y0: 114, x1: 688, y1: 210 };
    expect(frameWithin(face, SLEEVE, 1)).toEqual(face);
    expect(frameWithin(face, SLEEVE, 3 / 4)).toEqual({ x0: 604, y0: 114, x1: 676, y1: 210 });
  });

  it('竖长的正封里取方框按它的宽,居中不带进书脊', () => {
    const panel = { x0: 421, y0: 0, x1: 800, y1: 538 };
    expect(frameWithin(panel, SLEEVE, 1)).toEqual({ x0: 421, y0: 80, x1: 800, y1: 459 });
  });

  it('越出图外的取景区先收回图里', () => {
    expect(frameWithin({ x0: 700, y0: -40, x1: 900, y1: 160 }, SLEEVE, 1))
      .toEqual({ x0: 600, y0: 0, x1: 800, y1: 200 });
  });

  it('整张图按绝对定位摆,让框里那一块正好摆满同比例的格子', () => {
    expect(windowStyle({ x0: 200, y0: 100, x1: 400, y1: 300 }, { width: 800, height: 600 })).toEqual({
      left: '-100%', top: '-50%', width: '400%', height: '300%',
    });
  });
});

describe('框始终收在图里', () => {
  it('越界的框贴着边缘停下,尺寸不缩水', () => {
    expect(clampBox({ x0: 700, y0: 400, x1: 900, y1: 600 }, SLEEVE))
      .toEqual({ x0: 600, y0: 338, x1: 800, y1: 538 });
  });

  it('比最小边长还小的框被撑回最小边长', () => {
    const box = clampBox({ x0: 10, y0: 10, x1: 12, y1: 12 }, SLEEVE);
    expect(box.x1 - box.x0).toBe(MIN_CROP_EDGE);
    expect(box.y1 - box.y0).toBe(MIN_CROP_EDGE);
  });

  it('锁了比例就按比例补另一条边,装不下时换一条边定尺寸', () => {
    expect(clampBox({ x0: 0, y0: 0, x1: 400, y1: 100 }, SLEEVE, 1))
      .toEqual({ x0: 0, y0: 0, x1: 400, y1: 400 });
    // 宽 700 的方框在 538 高的图里放不下,改由高定尺寸。
    expect(clampBox({ x0: 0, y0: 0, x1: 700, y1: 100 }, SLEEVE, 1))
      .toEqual({ x0: 0, y0: 0, x1: 538, y1: 538 });
  });
});

describe('拖动、缩放与拖角', () => {
  it('平移不改尺寸,碰到边就停', () => {
    const box = { x0: 100, y0: 100, x1: 300, y1: 300 };
    expect(moveBox(box, 50, -40, SLEEVE)).toEqual({ x0: 150, y0: 60, x1: 350, y1: 260 });
    expect(moveBox(box, 9999, 9999, SLEEVE)).toEqual({ x0: 600, y0: 338, x1: 800, y1: 538 });
  });

  it('滚轮以框心为中心缩放', () => {
    expect(scaleBox({ x0: 300, y0: 200, x1: 500, y1: 400 }, 1.5, SLEEVE))
      .toEqual({ x0: 250, y0: 150, x1: 550, y1: 450 });
  });

  it('拖右下角时左上角钉住不动', () => {
    expect(resizeFromCorner({ x0: 100, y0: 100, x1: 200, y1: 200 }, 460, 380, SLEEVE))
      .toEqual({ x0: 100, y0: 100, x1: 460, y1: 380 });
    // 锁了比例的方框:往右拖得比往下多,就由高来定尺寸,免得框冲出图底。
    expect(resizeFromCorner({ x0: 100, y0: 100, x1: 200, y1: 200 }, 700, 300, SLEEVE, 1))
      .toEqual({ x0: 100, y0: 100, x1: 300, y1: 300 });
  });
});

describe('框换算成样式', () => {
  it('覆盖层的百分比是相对图片本身的,与显示尺寸无关', () => {
    expect(boxPercent({ x0: 200, y0: 0, x1: 600, y1: 538 }, SLEEVE))
      .toEqual({ left: 25, top: 0, width: 50, height: 100 });
  });

  it('圆形预览把框里那一块摆满容器', () => {
    expect(previewStyle({ x0: 200, y0: 138, x1: 600, y1: 538 }, SLEEVE))
      .toEqual({ size: '200% 134.5%', position: '50% 100%' });
  });

  it('框等于整张图时位置取 0:图和容器一样大,摆哪里都一样', () => {
    expect(previewStyle({ x0: 0, y0: 0, x1: 800, y1: 538 }, SLEEVE))
      .toEqual({ size: '100% 100%', position: '0% 0%' });
  });

  it('屏幕上量到的像素换回源图像素', () => {
    expect(toNatural(100, 400, 800)).toBe(200);
    // 图还没布局出来,除数是 0:给 0 而不是 Infinity。
    expect(toNatural(100, 0, 800)).toBe(0);
  });
});

describe('角柄放哪儿', () => {
  it('框在屏幕上够大时角柄压在框角上', () => {
    expect(handleOutside({ width: 50, height: 50 }, { width: 400, height: 400 })).toBe(false);
  });
  it('4000×200 的图只剩 20px 高,框比角柄大不了多少,角柄挪到框外', () => {
    // 方框占满图高:屏幕上 20×20。
    expect(handleOutside({ width: 5, height: 100 }, { width: 400, height: 20 })).toBe(true);
  });
  it('只看短边:长条框哪怕很长也要挪', () => {
    expect(handleOutside({ width: 100, height: 100 }, { width: 600, height: HANDLE_ROOM - 1 })).toBe(true);
  });
  it('图的显示尺寸还没量到时不挪', () => {
    expect(handleOutside({ width: 5, height: 5 }, null)).toBe(false);
    expect(handleOutside({ width: 5, height: 5 }, { width: 0, height: 0 })).toBe(false);
  });
});