Switch to VectorEngine gpt-image-2 and edits
Replace uses of the legacy `gpt-image-2-all` model with `gpt-image-2` and standardize image workflows: no-reference generation uses POST /v1/images/generations, any-reference flows use POST /v1/images/edits with multipart `image` parts. Update SKILLs, generation scripts, decision logs, and docs to reflect the contract change and edits-vs-generations guidance. Apply corresponding changes across backend (api-server match3d/puzzle modules, openai image adapter, mappers, telemetry, spacetime client/module), frontend components and services (Match3D, Puzzle, CreativeImageInputPanel, runtime shells), and add new spritesheet/parser files and tests. Also add media/logo.png. These changes align repository code and documentation with the VectorEngine image API contract and update generation/upload handling (green-screen -> alpha processing, spritesheet handling, and related tests).
This commit is contained in:
@@ -197,6 +197,86 @@ pub(crate) fn slice_generated_asset_sheet(
|
||||
Ok(slices)
|
||||
}
|
||||
|
||||
pub(crate) fn slice_generated_asset_sheet_two_items_per_row(
|
||||
image: &DownloadedOpenAiImage,
|
||||
item_names: &[String],
|
||||
grid_size: usize,
|
||||
views_per_item: usize,
|
||||
) -> Result<Vec<Vec<GeneratedAssetSheetSliceImage>>, AppError> {
|
||||
if grid_size == 0 || views_per_item == 0 {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": GENERATED_ASSET_SHEET_PROVIDER,
|
||||
"message": "系列素材图集的 n 和每物品视图数必须大于 0。",
|
||||
})),
|
||||
);
|
||||
}
|
||||
if !grid_size.is_multiple_of(views_per_item) {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": GENERATED_ASSET_SHEET_PROVIDER,
|
||||
"message": "系列素材图集每行必须能均分为若干物品。",
|
||||
"gridSize": grid_size,
|
||||
"viewsPerItem": views_per_item,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
let grid_size_u32 = u32::try_from(grid_size).map_err(|_| {
|
||||
AppError::from_status(StatusCode::BAD_REQUEST).with_details(json!({
|
||||
"provider": GENERATED_ASSET_SHEET_PROVIDER,
|
||||
"message": "系列素材图集的 n 超出可支持范围。",
|
||||
}))
|
||||
})?;
|
||||
let source = image::load_from_memory(image.bytes.as_slice()).map_err(|error| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": GENERATED_ASSET_SHEET_PROVIDER,
|
||||
"message": format!("系列素材图集解码失败:{error}"),
|
||||
}))
|
||||
})?;
|
||||
let source = apply_generated_asset_sheet_green_screen_alpha(source);
|
||||
let (width, height) = source.dimensions();
|
||||
if width / grid_size_u32 == 0 || height / grid_size_u32 == 0 {
|
||||
return Err(
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": GENERATED_ASSET_SHEET_PROVIDER,
|
||||
"message": "系列素材图集尺寸过小,无法切割。",
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
let items_per_row = grid_size / views_per_item;
|
||||
let max_item_count = grid_size.saturating_mul(items_per_row);
|
||||
let mut slices = Vec::with_capacity(item_names.len().min(max_item_count));
|
||||
for item_index in 0..item_names.len().min(max_item_count) {
|
||||
let row = (item_index / items_per_row) as u32;
|
||||
let start_col = ((item_index % items_per_row) * views_per_item) as u32;
|
||||
let mut views = Vec::with_capacity(views_per_item);
|
||||
for view_offset in 0..views_per_item {
|
||||
let col = start_col + view_offset as u32;
|
||||
let (crop_x, crop_y, crop_width, crop_height) =
|
||||
resolve_generated_asset_sheet_cell_crop(&source, grid_size_u32, row, col);
|
||||
let cropped = source.crop_imm(crop_x, crop_y, crop_width, crop_height);
|
||||
let cleaned = crop_generated_asset_sheet_view_edge_matte(cropped);
|
||||
let mut cursor = std::io::Cursor::new(Vec::new());
|
||||
cleaned
|
||||
.write_to(&mut cursor, ImageFormat::Png)
|
||||
.map_err(|error| {
|
||||
AppError::from_status(StatusCode::BAD_GATEWAY).with_details(json!({
|
||||
"provider": GENERATED_ASSET_SHEET_PROVIDER,
|
||||
"message": format!("系列素材图集切割失败:{error}"),
|
||||
}))
|
||||
})?;
|
||||
views.push(GeneratedAssetSheetSliceImage {
|
||||
bytes: cursor.into_inner(),
|
||||
});
|
||||
}
|
||||
slices.push(views);
|
||||
}
|
||||
|
||||
Ok(slices)
|
||||
}
|
||||
|
||||
pub(crate) fn crop_generated_asset_sheet_view_edge_matte(
|
||||
image: image::DynamicImage,
|
||||
) -> image::DynamicImage {
|
||||
@@ -958,7 +1038,7 @@ fn collect_generated_asset_sheet_visible_neighbor_color(
|
||||
))
|
||||
}
|
||||
|
||||
fn apply_generated_asset_sheet_green_screen_alpha(
|
||||
pub(crate) fn apply_generated_asset_sheet_green_screen_alpha(
|
||||
source: image::DynamicImage,
|
||||
) -> image::DynamicImage {
|
||||
let mut image = source.to_rgba8();
|
||||
|
||||
Reference in New Issue
Block a user