Download web/src/app/image/components/image-annotation.ts from zlcy/study: direct link, hf CLI and curl.
- Browser
- Download file 6.05 kB
-
https://huggingface.co/spaces/zlcy/study/resolve/main/web/src/app/image/components/image-annotation.ts
- Command line
-
hf download hf://spaces/zlcy/study/web/src/app/image/components/image-annotation.ts
-
curl -L -o image-annotation.ts https://huggingface.co/spaces/zlcy/study/resolve/main/web/src/app/image/components/image-annotation.ts
6.05 kB
| import type { ImageAnnotationBounds } from "@/lib/api"; | |
| export type ImageEditAnnotation = { | |
| id: string; | |
| label: string; | |
| instruction: string; | |
| bounds: ImageAnnotationBounds; | |
| selectionType?: "box" | "point"; | |
| point?: { x: number; y: number }; | |
| visibleText?: string; | |
| description?: string; | |
| }; | |
| export type ImageEditorGenerationSettings = { | |
| count: number; | |
| model: string; | |
| quality?: string; | |
| resolution?: string; | |
| thirdPartyImageGroupId?: string; | |
| }; | |
| export type ImageEditorAction = ( | |
| | { | |
| type: "annotations"; | |
| annotations: ImageEditAnnotation[]; | |
| /** 被编辑图片自身的宽高比(整数格式,如 "2:3"),局部修改时输出应保持该比例 */ | |
| imageRatio?: string; | |
| } | |
| | { | |
| type: "expand"; | |
| aspectRatio: string; | |
| instruction?: string; | |
| systemPrompt: string; | |
| fillColor: string; | |
| referenceImageDataUrl: string; | |
| referenceImageWidth: number; | |
| referenceImageHeight: number; | |
| maskImageDataUrl: string; | |
| } | |
| ) & ImageEditorGenerationSettings; | |
| function percent(value: number) { | |
| return `${Math.max(0, Math.min(100, value)).toFixed(1)}%`; | |
| } | |
| /** 把扩图参考画布的像素宽高整理成 `宽x高` 形式,尺寸非法时返回空字符串。 */ | |
| export function toExpansionReferenceSize(width?: number, height?: number) { | |
| const parsedWidth = Math.round(Number(width) || 0); | |
| const parsedHeight = Math.round(Number(height) || 0); | |
| if (parsedWidth <= 0 || parsedHeight <= 0) return ""; | |
| return `${parsedWidth}x${parsedHeight}`; | |
| } | |
| export function buildImageAnnotationEditPrompt(annotations: ImageEditAnnotation[]) { | |
| const actionable = annotations.filter((item) => item.instruction.trim()); | |
| const items = actionable.map((item, index) => { | |
| const centerX = item.bounds.x + item.bounds.width / 2; | |
| const centerY = item.bounds.y + item.bounds.height / 2; | |
| const pointX = item.point?.x ?? centerX; | |
| const pointY = item.point?.y ?? centerY; | |
| const details = item.selectionType === "point" | |
| ? [ | |
| `${index + 1}. 点选目标:${item.label.trim() || "用户点选位置"}`, | |
| ` 点选位置:(x: ${percent(pointX)}, y: ${percent(pointY)})`, | |
| ` 识别参考范围:(x: ${percent(item.bounds.x)}, y: ${percent(item.bounds.y)}, w: ${percent(item.bounds.width)}, h: ${percent(item.bounds.height)})`, | |
| ] | |
| : [ | |
| `${index + 1}. 框选目标:${item.label.trim() || "用户框选区域"}`, | |
| ` 区域中心:(x: ${percent(centerX)}, y: ${percent(centerY)})`, | |
| ` 区域范围:(x: ${percent(item.bounds.x)}, y: ${percent(item.bounds.y)}, w: ${percent(item.bounds.width)}, h: ${percent(item.bounds.height)})`, | |
| ]; | |
| if (item.visibleText?.trim()) { | |
| details.push(` 当前可见文字:${item.visibleText.trim()}`); | |
| } | |
| if (item.description?.trim()) { | |
| details.push(` 视觉说明:${item.description.trim()}`); | |
| } | |
| details.push(` 修改:${item.instruction.trim()}`); | |
| return details.join("\n"); | |
| }); | |
| return [ | |
| "请编辑图像1。坐标以原图左上角为 (0%, 0%),右下角为 (100%, 100%)。", | |
| "以下每一项都对应用户在原图上明确点选或框选的目标:", | |
| "", | |
| items.join("\n\n"), | |
| "", | |
| "严格只修改以上点选目标或框选区域;未标记的商品、人物、文字、构图、颜色、光线和背景保持不变。", | |
| "不要在成品中保留编号、点位、选框、坐标或任何标注界面元素。", | |
| ].join("\n"); | |
| } | |
| export const DEFAULT_IMAGE_EXPANSION_FILL_COLOR = "#ffb6d9"; | |
| const IMAGE_EXPANSION_COLOR_NAMES: Record<string, string> = { | |
| "#ffffff": "白色", | |
| "#000000": "黑色", | |
| "#808080": "灰色", | |
| "#ff0000": "红色", | |
| "#00ff00": "绿色", | |
| "#0000ff": "蓝色", | |
| [DEFAULT_IMAGE_EXPANSION_FILL_COLOR]: "粉色", | |
| }; | |
| export function describeImageExpansionFillColor(fillColor: string) { | |
| const normalized = fillColor.trim().toLowerCase(); | |
| return IMAGE_EXPANSION_COLOR_NAMES[normalized] || `颜色为 ${normalized.toUpperCase()} 的纯色`; | |
| } | |
| export function buildDefaultImageExpansionPrompt(fillColor = DEFAULT_IMAGE_EXPANSION_FILL_COLOR) { | |
| const colorDescription = describeImageExpansionFillColor(fillColor); | |
| return `第一张参考图是扩展画布,其中${colorDescription}色块是需要补全的新增区域。透明蒙版可能作为第二张参考图提供,也可能通过 mask 字段提供;蒙版的透明区域与这些色块对应,是唯一允许填充的区域,不透明区域必须保持不变。根据原图内容自然填充新增区域,保持原图的清晰程度、色调和颜色不变,新增区域和原图的衔接要自然`; | |
| } | |
| export const DEFAULT_IMAGE_EXPANSION_PROMPT = buildDefaultImageExpansionPrompt(); | |
| export function buildImageExpansionPrompt( | |
| systemPrompt: string, | |
| instruction?: string, | |
| fillColor = DEFAULT_IMAGE_EXPANSION_FILL_COLOR, | |
| aspectRatio?: string, | |
| referenceWidth?: number, | |
| referenceHeight?: number, | |
| ) { | |
| const normalizedSystemPrompt = systemPrompt.trim() || buildDefaultImageExpansionPrompt(fillColor); | |
| const extraInstruction = instruction?.trim(); | |
| const parts = [normalizedSystemPrompt]; | |
| const referenceSize = toExpansionReferenceSize(referenceWidth, referenceHeight); | |
| if (referenceSize) { | |
| // 用「参考画布的实际像素尺寸」约束比例,而不是 16:9 / 404:281 这类比例字符串: | |
| // 比例字符串会被后端换算成 1024 长边的标准尺寸,与扩图画布的真实宽高对不上, | |
| // 生成结果会被二次裁切,导致扩图内容错位。 | |
| parts.push( | |
| `输出图片必须严格保持 ${referenceSize} 的像素尺寸与宽高比,不要拉伸、压缩或裁切画面,也不要改变画布比例。`, | |
| ); | |
| } else if (aspectRatio?.trim()) { | |
| parts.push(`输出图片的宽高比必须为 ${aspectRatio.trim()},请严格按照这个尺寸比例生成。`); | |
| } | |
| if (extraInstruction) { | |
| parts.push(`补充要求:${extraInstruction}`); | |
| } | |
| return parts.join("\n\n"); | |
| } | |