Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ def _fit_first_frame(frame: bytes, size: str, *, background: tuple[int, int, int

小于目标画布的输入必须**放大**:128x128 的 sprite 原尺寸贴进 1280x720 只占 13% 高,
等于自愿把主体有效分辨率砍掉七分之六,之后无论 i2v 还是重抠图都补不回来。

**放大用 NEAREST,缩小用 LANCZOS。** 放大是把一个源像素铺成一块,插值会在块边界
造出源图里没有的中间色:实测一张 256x256 的像素画母版放到 720x720,唯一色从 5982
涨到 32479(5.4 倍),硬边糊成渐变,而这张糊图正是喂给 i2v 的输入。缩小反过来,
NEAREST 会丢样出锯齿。交付侧的 ``_fit_to`` 早就是这条规则,这里与它对齐。
"""
from PIL import Image

Expand All @@ -83,7 +88,7 @@ def _fit_first_frame(frame: bytes, size: str, *, background: tuple[int, int, int
pad = im.getpixel((0, 0)) # 不透明输入沿用角点色,补边与画面自身背景连成一片
scale = min(w/im.width, h/im.height)
tw, th = max(1, round(im.width*scale)), max(1, round(im.height*scale))
fitted = im.resize((tw, th), Image.LANCZOS)
fitted = im.resize((tw, th), Image.NEAREST if scale > 1 else Image.LANCZOS)
canvas = Image.new("RGB", (w, h), pad)
canvas.paste(fitted, ((w - tw)//2, (h - th)//2))
buf = io.BytesIO()
Expand Down
36 changes: 36 additions & 0 deletions backend/tests/test_sufy_video_download.py
Original file line number Diff line number Diff line change
Expand Up @@ -1044,3 +1044,39 @@ def test_square_first_frame_forms_a_720x720_content_region_in_a_1280x720_canvas(
assert 715 <= cols.max()-cols.min()+1 <= 725, f"内容区宽 {cols.max()-cols.min()+1},应≈720"
assert 715 <= rows.max()-rows.min()+1 <= 725, f"内容区高 {rows.max()-rows.min()+1},应≈720"
assert abs(cols.min() - 280) <= 4, f"内容区左边界 {cols.min()},应≈280(左右各补 280)"


def test_upscaling_a_small_sprite_keeps_hard_edges_instead_of_interpolating_them():
"""放大用 NEAREST:插值会把 sprite 的硬边糊成渐变,而这张糊图正是喂给 i2v 的输入。

判据取一条竖直硬边在成品里的**过渡带宽度**(既不接近左色、也不接近右色的像素数)。
倍率越大差距越明显,实测同一函数下:64x64 源(11.2x)NEAREST 4px、LANCZOS 20px;
而 256x256 源(2.8x)只有 4px 对 5px —— 所以这条用小 sprite 测,大图分辨不出来。
小 sprite 也正是 #509 点名的场景。

不测色数:JPEG q90 本身就把 7704 色变成 23000 色,两种滤波在那个量上分不开
(12 张真实母版配对实测 1.0 倍),拿色数当判据会得出"没区别"的错结论。
"""
import io as _io

import numpy as _np
from PIL import Image as _Image

left, right = (30, 60, 200), (240, 200, 40)
src = _np.zeros((64, 64, 4), _np.uint8)
src[:, :, 3] = 255
src[:, :32] = (*left, 255)
src[:, 32:] = (*right, 255)
buf = _io.BytesIO()
_Image.fromarray(src, "RGBA").save(buf, "PNG")

im = _submitted_first_frame(buf.getvalue())
# 只取内容区:1280 宽的画布里,720x720 的内容区居中,两侧各 280px 是补边
row = _np.asarray(im.convert("RGB"))[360].astype(int)[280:1000]
far_from_left = _np.abs(row - _np.array(left)).sum(1) > 40
far_from_right = _np.abs(row - _np.array(right)).sum(1) > 40
width = int((far_from_left & far_from_right).sum())
assert width <= 8, (
f"硬边过渡带 {width}px,说明放大用了插值(实测 LANCZOS 20px、BICUBIC 10px、"
f"NEAREST 4px)。sprite 的硬边被糊掉后,i2v 拿到的就是一张糊图。"
)
Loading