Sheet04/Sheet05 改为横版 1536x1024 + 多画少取策略
- Sheet04: 竖版 4x6 → 横版 6x4,3组粗切 → 4组多画少取取3组,消除右列歧义 - Sheet05: 竖版 4x6 → 横版 6x4,6组 → 8组多画少取取3组,消除右列歧义 - PuzzleClearAtlasSheetSpec: layout 从固定数组改为 Vec<Vec>,新增 cols/rows,移除 Copy - build_puzzle_clear_atlas_prompt: 画布尺寸动态计算(orientation w×h) - 生成函数: per-sheet 独立 generation_size 传入 API - slice_puzzle_clear_sheet: detect_cell_grid_seed 改用 sheet 自身 rows/cols - 提示词全部统一为 x行y列 表述 - 移除未使用的 PUZZLE_CLEAR_SHEET_*_USIZE 常量 - puzzle_clear_full_pipeline 测试适配 per-sheet 维度
This commit is contained in:
@@ -56,8 +56,6 @@ const PUZZLE_CLEAR_RUNTIME_RUNS_ROUTE: &str = "/api/runtime/puzzle-clear/runs";
|
||||
const PUZZLE_CLEAR_ATLAS_CELL_SIZE: u32 = 256;
|
||||
const PUZZLE_CLEAR_SHEET_COLUMNS: u32 = 4;
|
||||
const PUZZLE_CLEAR_SHEET_ROWS: u32 = 6;
|
||||
const PUZZLE_CLEAR_SHEET_COLUMNS_USIZE: usize = 4;
|
||||
const PUZZLE_CLEAR_SHEET_ROWS_USIZE: usize = 6;
|
||||
const PUZZLE_CLEAR_FINAL_ATLAS_COLUMNS: u32 = 10;
|
||||
const PUZZLE_CLEAR_FINAL_ATLAS_ROWS: u32 = 10;
|
||||
const PUZZLE_CLEAR_SHEET_UNUSED_CELL: &str = ".";
|
||||
@@ -577,11 +575,13 @@ struct PuzzleClearAtlasCardSlice {
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
struct PuzzleClearAtlasSheetSpec {
|
||||
sheet_id: &'static str,
|
||||
layout: [[&'static str; PUZZLE_CLEAR_SHEET_COLUMNS_USIZE]; PUZZLE_CLEAR_SHEET_ROWS_USIZE],
|
||||
layout: Vec<Vec<&'static str>>,
|
||||
layout_prompt: &'static str,
|
||||
cols: u32,
|
||||
rows: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -1196,6 +1196,11 @@ async fn maybe_prepare_puzzle_clear_assets_inner(
|
||||
.iter()
|
||||
.map(|sheet_spec| {
|
||||
let sheet_prompt = build_puzzle_clear_atlas_prompt(theme_prompt, sheet_spec);
|
||||
let generation_size = format!(
|
||||
"{}x{}",
|
||||
sheet_spec.cols * PUZZLE_CLEAR_ATLAS_CELL_SIZE,
|
||||
sheet_spec.rows * PUZZLE_CLEAR_ATLAS_CELL_SIZE,
|
||||
);
|
||||
let client = http_client.clone();
|
||||
let settings = settings.clone();
|
||||
let debug_run = image_debug_run.clone();
|
||||
@@ -1218,7 +1223,7 @@ async fn maybe_prepare_puzzle_clear_assets_inner(
|
||||
&settings,
|
||||
sheet_prompt.as_str(),
|
||||
Some(PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT),
|
||||
PUZZLE_CLEAR_ATLAS_GENERATION_SIZE,
|
||||
&generation_size,
|
||||
1,
|
||||
&[],
|
||||
failure_context.as_str(),
|
||||
@@ -1274,7 +1279,7 @@ async fn maybe_prepare_puzzle_clear_assets_inner(
|
||||
);
|
||||
}
|
||||
return Ok(PuzzleClearGeneratedSheet {
|
||||
spec: *sheet_spec,
|
||||
spec: sheet_spec.clone(),
|
||||
prompt: sheet_prompt.clone(),
|
||||
task_id,
|
||||
image,
|
||||
@@ -1453,13 +1458,15 @@ fn puzzle_clear_atlas_sheet_specs() -> Vec<PuzzleClearAtlasSheetSpec> {
|
||||
// Sheet 1: 1×2 横向 12 组, 满画布 24cells
|
||||
PuzzleClearAtlasSheetSpec {
|
||||
sheet_id: "sheet-01",
|
||||
layout: [
|
||||
["A01","A01", "A03","A03"],
|
||||
["A05","A05", "A07","A07"],
|
||||
["A09","A09", "A11","A11"],
|
||||
["A13","A13", "A15","A15"],
|
||||
["A17","A17", "A19","A19"],
|
||||
["A21","A21", "A23","A23"],
|
||||
cols: 4,
|
||||
rows: 6,
|
||||
layout: vec![
|
||||
vec!["A01","A01", "A03","A03"],
|
||||
vec!["A05","A05", "A07","A07"],
|
||||
vec!["A09","A09", "A11","A11"],
|
||||
vec!["A13","A13", "A15","A15"],
|
||||
vec!["A17","A17", "A19","A19"],
|
||||
vec!["A21","A21", "A23","A23"],
|
||||
],
|
||||
layout_prompt: concat!(
|
||||
"画面等分为 6 行 4 列,每格 256×256。",
|
||||
@@ -1472,17 +1479,19 @@ fn puzzle_clear_atlas_sheet_specs() -> Vec<PuzzleClearAtlasSheetSpec> {
|
||||
// Sheet 2: 1×2 纵向 12 组 (取11), 满画布 24cells
|
||||
PuzzleClearAtlasSheetSpec {
|
||||
sheet_id: "sheet-02",
|
||||
layout: [
|
||||
["A02","A04", "A06","A08"],
|
||||
["A02","A04", "A06","A08"],
|
||||
["A10","A12", "A14","A16"],
|
||||
["A10","A12", "A14","A16"],
|
||||
["A18","A20", "A22",buf],
|
||||
["A18","A20", "A22",buf],
|
||||
cols: 4,
|
||||
rows: 6,
|
||||
layout: vec![
|
||||
vec!["A02","A04", "A06","A08"],
|
||||
vec!["A02","A04", "A06","A08"],
|
||||
vec!["A10","A12", "A14","A16"],
|
||||
vec!["A10","A12", "A14","A16"],
|
||||
vec!["A18","A20", "A22",buf],
|
||||
vec!["A18","A20", "A22",buf],
|
||||
],
|
||||
layout_prompt: concat!(
|
||||
"画面等分为 6 行 4 列,每格 256×256,各行高度严格相等。",
|
||||
"共 12 个纵向裁片,每行排 4 个,两两成组上下排列,每裁片严格占 1 列 2 行。",
|
||||
"共 12 个纵向裁片,每行排 4 个,两两成组上下排列,每裁片严格占 2 行 1 列。",
|
||||
"裁片内部画面连续无间断,不可拆成上下两格独立小图。",
|
||||
"不同裁片之间用洋红细线分隔。画面四周留洋红边距。",
|
||||
"绝对不要画网格线、边框、编号、文字或水印。",
|
||||
@@ -1491,55 +1500,57 @@ fn puzzle_clear_atlas_sheet_specs() -> Vec<PuzzleClearAtlasSheetSpec> {
|
||||
// Sheet 3: 2×2 正方形 6 组 (取4), 满画布 24cells
|
||||
PuzzleClearAtlasSheetSpec {
|
||||
sheet_id: "sheet-03",
|
||||
layout: [
|
||||
["C01","C01", "C02","C02"],
|
||||
["C01","C01", "C02","C02"],
|
||||
["C03","C03", "C04","C04"],
|
||||
["C03","C03", "C04","C04"],
|
||||
[buf,buf, buf,buf],
|
||||
[buf,buf, buf,buf],
|
||||
cols: 4,
|
||||
rows: 6,
|
||||
layout: vec![
|
||||
vec!["C01","C01", "C02","C02"],
|
||||
vec!["C01","C01", "C02","C02"],
|
||||
vec!["C03","C03", "C04","C04"],
|
||||
vec!["C03","C03", "C04","C04"],
|
||||
vec![buf,buf, buf,buf],
|
||||
vec![buf,buf, buf,buf],
|
||||
],
|
||||
layout_prompt: concat!(
|
||||
"画面等分为 6 行 4 列,每格 256×256,各行高度严格相等。",
|
||||
"共 6 个正方形裁片,每行左右各一个,每裁片严格占 2 列 2 行。",
|
||||
"共 6 个正方形裁片,每行左右各一个,每裁片严格占 2 行 2 列。",
|
||||
"裁片内部画面连续无间断,不可拆成四格独立小图。",
|
||||
"不同裁片之间用洋红细线分隔。画面四周留洋红边距。",
|
||||
"绝对不要画网格线、边框、编号、文字或水印。",
|
||||
),
|
||||
},
|
||||
// Sheet 4: 2×3 横向 3 组, 满画布 + 右列缓冲
|
||||
// Sheet 4: 2×3 横向 4 组 (取3), 横版满画布 6×4=24cells
|
||||
PuzzleClearAtlasSheetSpec {
|
||||
sheet_id: "sheet-04",
|
||||
layout: [
|
||||
["D01","D01","D01",buf],
|
||||
["D01","D01","D01",buf],
|
||||
["D02","D02","D02",buf],
|
||||
["D02","D02","D02",buf],
|
||||
["D03","D03","D03",buf],
|
||||
["D03","D03","D03",buf],
|
||||
cols: 6,
|
||||
rows: 4,
|
||||
layout: vec![
|
||||
vec!["D01","D01","D01", "D02","D02","D02"],
|
||||
vec!["D01","D01","D01", "D02","D02","D02"],
|
||||
vec!["D03","D03","D03", buf,buf,buf],
|
||||
vec!["D03","D03","D03", buf,buf,buf],
|
||||
],
|
||||
layout_prompt: concat!(
|
||||
"画面等分为 6 行 4 列,每格 256×256,各行高度严格相等。",
|
||||
"共 3 个横向宽裁片,每两行一个,每裁片严格占 3 列 2 行(宽 3 格高 2 格)。",
|
||||
"画面等分为 4 行 6 列,每格 256×256,各列宽度严格相等。",
|
||||
"共 4 个横向宽裁片,每行左右各一个,每裁片严格占 2 行 3 列(宽 3 格高 2 格)。",
|
||||
"裁片内部画面连续无间断,不可拆成独立格子。",
|
||||
"不同裁片之间用洋红细线分隔。画面四周留洋红边距。",
|
||||
"绝对不要画网格线、边框、编号、文字或水印。",
|
||||
),
|
||||
},
|
||||
// Sheet 5: 1×3 横向 6 组 (取3), 满画布 18cells + 右列缓冲
|
||||
// Sheet 5: 1×3 横向 8 组 (取3), 横版满画布 6×4=24cells
|
||||
PuzzleClearAtlasSheetSpec {
|
||||
sheet_id: "sheet-05",
|
||||
layout: [
|
||||
["B01","B01","B01",buf],
|
||||
["B03","B03","B03",buf],
|
||||
["B05","B05","B05",buf],
|
||||
[buf,buf,buf,buf],
|
||||
[buf,buf,buf,buf],
|
||||
[buf,buf,buf,buf],
|
||||
cols: 6,
|
||||
rows: 4,
|
||||
layout: vec![
|
||||
vec!["B01","B01","B01", "B03","B03","B03"],
|
||||
vec!["B05","B05","B05", buf,buf,buf],
|
||||
vec![buf,buf,buf, buf,buf,buf],
|
||||
vec![buf,buf,buf, buf,buf,buf],
|
||||
],
|
||||
layout_prompt: concat!(
|
||||
"画面等分为 6 行 4 列,每格 256×256,各行高度严格相等。",
|
||||
"共 6 个横向宽裁片,每行一个,每裁片严格占 3 列 1 行(宽 3 格高 1 格)。",
|
||||
"画面等分为 4 行 6 列,每格 256×256,各列宽度严格相等。",
|
||||
"共 8 个横向宽裁片,每行左右各一个,每裁片严格占 1 行 3 列(宽 3 格高 1 格)。",
|
||||
"裁片内部画面连续无间断,不可拆成三格独立小图。",
|
||||
"不同裁片之间用洋红细线分隔。画面四周留洋红边距。",
|
||||
"绝对不要画网格线、边框、编号、文字或水印。",
|
||||
@@ -1548,17 +1559,19 @@ fn puzzle_clear_atlas_sheet_specs() -> Vec<PuzzleClearAtlasSheetSpec> {
|
||||
// Sheet 6: 1×3 纵向 8 组 (取2), 满画布 24cells
|
||||
PuzzleClearAtlasSheetSpec {
|
||||
sheet_id: "sheet-06",
|
||||
layout: [
|
||||
["B02",buf,buf,buf],
|
||||
["B02",buf,buf,buf],
|
||||
["B02",buf,buf,buf],
|
||||
["B04",buf,buf,buf],
|
||||
["B04",buf,buf,buf],
|
||||
["B04",buf,buf,buf],
|
||||
cols: 4,
|
||||
rows: 6,
|
||||
layout: vec![
|
||||
vec!["B02",buf,buf,buf],
|
||||
vec!["B02",buf,buf,buf],
|
||||
vec!["B02",buf,buf,buf],
|
||||
vec!["B04",buf,buf,buf],
|
||||
vec!["B04",buf,buf,buf],
|
||||
vec!["B04",buf,buf,buf],
|
||||
],
|
||||
layout_prompt: concat!(
|
||||
"画面等分为 6 行 4 列,每格 256×256,各列宽度严格相等。",
|
||||
"共 8 个纵向裁片,每列排 2 组上下相邻,每裁片严格占 1 列 3 行(宽 1 格高 3 格)。",
|
||||
"共 8 个纵向裁片,每列排 2 组上下相邻,每裁片严格占 3 行 1 列(宽 1 格高 3 格)。",
|
||||
"裁片内部画面连续无间断,不可拆成三格独立小图。",
|
||||
"不同裁片之间用洋红细线分隔。画面四周留洋红边距。",
|
||||
"绝对不要画网格线、边框、编号、文字或水印。",
|
||||
@@ -1572,16 +1585,20 @@ fn build_puzzle_clear_atlas_prompt(
|
||||
sheet_spec: &PuzzleClearAtlasSheetSpec,
|
||||
) -> String {
|
||||
let subject = normalize_non_empty_str(theme_prompt).unwrap_or_else(|| "梦幻物件".to_string());
|
||||
let w = sheet_spec.cols * PUZZLE_CLEAR_ATLAS_CELL_SIZE;
|
||||
let h = sheet_spec.rows * PUZZLE_CLEAR_ATLAS_CELL_SIZE;
|
||||
let orientation = if h > w { "竖版" } else { "横版" };
|
||||
format!(
|
||||
concat!(
|
||||
"生成一张拼消消卡牌图集,主题是「{subject}」,竖版 1024x1536。\n",
|
||||
"照片式构图、绘本式渲染。画面由若干场景裁片组成,",
|
||||
"每个裁片是完整连续画面,内部无接缝、无分隔线、无网格。",
|
||||
"不同裁片之间用纯洋红(#FF00FF)细线分隔。",
|
||||
"不要文字、Logo、水印、UI、网格线、边框、编号、纯色背景或孤立主体。\n",
|
||||
"{layout_prompt}"
|
||||
),
|
||||
"生成一张拼消消卡牌图集,主题是「{subject}」,{orientation} {w}x{h}。\n\
|
||||
照片式构图、绘本式渲染。画面由若干场景裁片组成,\n\
|
||||
每个裁片是完整连续画面,内部无接缝、无分隔线、无网格。\n\
|
||||
不同裁片之间用纯洋红(#FF00FF)细线分隔。\n\
|
||||
不要文字、Logo、水印、UI、网格线、边框、编号、纯色背景或孤立主体。\n\
|
||||
{layout_prompt}",
|
||||
subject = subject,
|
||||
orientation = orientation,
|
||||
w = w,
|
||||
h = h,
|
||||
layout_prompt = sheet_spec.layout_prompt
|
||||
)
|
||||
}
|
||||
@@ -1791,8 +1808,8 @@ fn slice_puzzle_clear_sheet(
|
||||
source_rgba.as_raw(),
|
||||
source_width,
|
||||
source_height,
|
||||
PUZZLE_CLEAR_SHEET_ROWS,
|
||||
PUZZLE_CLEAR_SHEET_COLUMNS,
|
||||
sheet_spec.rows,
|
||||
sheet_spec.cols,
|
||||
);
|
||||
let mut slices = Vec::new();
|
||||
let mut cells_by_group: BTreeMap<&str, Vec<(u32, u32)>> = BTreeMap::new();
|
||||
@@ -2585,7 +2602,6 @@ mod tests {
|
||||
fn puzzle_clear_full_pipeline_saves_intermediate_results() {
|
||||
use super::{
|
||||
compose_puzzle_clear_final_atlas, slice_puzzle_clear_sheet, PUZZLE_CLEAR_ATLAS_CELL_SIZE,
|
||||
PUZZLE_CLEAR_SHEET_COLUMNS, PUZZLE_CLEAR_SHEET_ROWS,
|
||||
PUZZLE_CLEAR_FINAL_ATLAS_COLUMNS, PUZZLE_CLEAR_FINAL_ATLAS_ROWS,
|
||||
};
|
||||
use crate::openai_image_generation::DownloadedOpenAiImage;
|
||||
@@ -2609,16 +2625,15 @@ mod tests {
|
||||
.map(|g| (g.group_id.clone(), g))
|
||||
.collect();
|
||||
|
||||
let w = PUZZLE_CLEAR_SHEET_COLUMNS * PUZZLE_CLEAR_ATLAS_CELL_SIZE;
|
||||
let h = PUZZLE_CLEAR_SHEET_ROWS * PUZZLE_CLEAR_ATLAS_CELL_SIZE;
|
||||
|
||||
let mut all_slices = Vec::new();
|
||||
|
||||
for sheet in &sheets {
|
||||
// 中文注释:为每张 sheet 生成合成测试图,按 group_id 分配颜色
|
||||
// 中文注释:为每张 sheet 生成合成测试图,按 sheet 自身 cols/rows 分配颜色
|
||||
let w = sheet.cols * PUZZLE_CLEAR_ATLAS_CELL_SIZE;
|
||||
let h = sheet.rows * PUZZLE_CLEAR_ATLAS_CELL_SIZE;
|
||||
let mut img = RgbaImage::from_pixel(w, h, Rgba([248, 246, 240, 255]));
|
||||
for row in 0..PUZZLE_CLEAR_SHEET_ROWS {
|
||||
for col in 0..PUZZLE_CLEAR_SHEET_COLUMNS {
|
||||
for row in 0..sheet.rows {
|
||||
for col in 0..sheet.cols {
|
||||
let gid = sheet.layout[row as usize][col as usize];
|
||||
let (r, g, b) = group_color(gid);
|
||||
let x0 = col * PUZZLE_CLEAR_ATLAS_CELL_SIZE;
|
||||
@@ -2717,7 +2732,7 @@ mod tests {
|
||||
use super::{
|
||||
compose_puzzle_clear_final_atlas, slice_puzzle_clear_sheet,
|
||||
build_puzzle_clear_atlas_prompt, prepare_puzzle_clear_magenta_cleanup,
|
||||
PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT, PUZZLE_CLEAR_ATLAS_GENERATION_SIZE,
|
||||
PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT,
|
||||
};
|
||||
use crate::openai_image_generation::DownloadedOpenAiImage;
|
||||
use platform_image::{
|
||||
@@ -2762,7 +2777,6 @@ mod tests {
|
||||
let theme = "梦幻幻想";
|
||||
|
||||
let neg = PUZZLE_CLEAR_ATLAS_NEGATIVE_PROMPT;
|
||||
let size = PUZZLE_CLEAR_ATLAS_GENERATION_SIZE;
|
||||
|
||||
// 中文注释:5 张 sheet 并行生成
|
||||
println!("开始并行生成 {} 张 sheet...", sheets.len());
|
||||
@@ -2770,13 +2784,18 @@ mod tests {
|
||||
.iter()
|
||||
.map(|sheet| {
|
||||
let prompt = build_puzzle_clear_atlas_prompt(theme, sheet);
|
||||
let size = format!(
|
||||
"{}x{}",
|
||||
sheet.cols * PUZZLE_CLEAR_ATLAS_CELL_SIZE,
|
||||
sheet.rows * PUZZLE_CLEAR_ATLAS_CELL_SIZE,
|
||||
);
|
||||
let failure = format!("拼消消测试 {} 生成", sheet.sheet_id);
|
||||
let client = http_client.clone();
|
||||
let settings = settings.clone();
|
||||
println!(" -> {} prompt={}chars", sheet.sheet_id, prompt.len());
|
||||
async move {
|
||||
let result = create_vector_engine_image_generation(
|
||||
&client, &settings, &prompt, Some(neg), size, 1, &[], &failure,
|
||||
&client, &settings, &prompt, Some(neg), &size, 1, &[], &failure,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|e| panic!("{} failed: {:?}", sheet.sheet_id, e));
|
||||
|
||||
Reference in New Issue
Block a user