fix: use cm dimensions in PSD annotation labels
This commit is contained in:
parent
d53308126d
commit
b26f46cc80
@ -6028,6 +6028,7 @@ export function AgentGenerationWorkspaceDarkV2({
|
||||
}}
|
||||
sourceImageUrl={psdTemplateDialogSourceUrl}
|
||||
brandName={workflowContext?.brand || null}
|
||||
sizeInfoText={workflowContext?.summaryText || null}
|
||||
templateMode={psdTemplateDialogMode}
|
||||
/>
|
||||
</ClientErrorBoundary>
|
||||
|
||||
@ -564,12 +564,14 @@ export function AgentPsdTemplateEditorDialog({
|
||||
onOpenChange,
|
||||
sourceImageUrl,
|
||||
brandName,
|
||||
sizeInfoText,
|
||||
templateMode,
|
||||
}: {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
sourceImageUrl: string | null;
|
||||
brandName?: string | null;
|
||||
sizeInfoText?: string | null;
|
||||
templateMode?: AgentPsdTemplateMode | null;
|
||||
}) {
|
||||
const normalizedBrandName = String(brandName || "").trim();
|
||||
@ -2169,6 +2171,7 @@ export function AgentPsdTemplateEditorDialog({
|
||||
imageAlt="PSD 模板导出结果"
|
||||
imageUrl={renderedResultUrl}
|
||||
key={`${selectedTemplate?.id || "template"}-${renderedResultUrl}`}
|
||||
sizeInfoText={sizeInfoText}
|
||||
/>
|
||||
) : selectedTextLayer ? (
|
||||
<div className="flex min-h-0 flex-1 flex-col rounded-2xl border border-white/10 bg-white/[0.02] p-3.5 xl:p-4">
|
||||
|
||||
@ -21,6 +21,7 @@ type AgentPsdTemplateExportPreviewProps = {
|
||||
imageAlt?: string;
|
||||
enableDimensionAnnotations?: boolean;
|
||||
downloadName?: string;
|
||||
sizeInfoText?: string | null;
|
||||
};
|
||||
|
||||
type ImageSize = {
|
||||
@ -86,6 +87,15 @@ const DIMENSION_SUFFIX_REGEX = /-尺寸标注(?=\.)/;
|
||||
const DIMENSION_LINE_COLOR = "#666b73";
|
||||
const DIMENSION_TEXT_COLOR = "#2a2f35";
|
||||
const DIMENSION_TEXT_HALO_COLOR = "rgba(255,255,255,0.92)";
|
||||
const SIZE_INFO_LINE_REGEX = /尺寸信息[::]\s*([^\n]+)/i;
|
||||
const NUMBER_REGEX = /\d+(?:\.\d+)?/g;
|
||||
|
||||
type AutoDimensionValues = {
|
||||
widthCm?: number;
|
||||
depthCm?: number;
|
||||
heightCm?: number;
|
||||
rawValues: number[];
|
||||
};
|
||||
|
||||
function clamp(value: number, min: number, max: number) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
@ -132,13 +142,145 @@ function getDimensionLength(annotation: DimensionAnnotation) {
|
||||
);
|
||||
}
|
||||
|
||||
function getDimensionLabel(annotation: DimensionAnnotation) {
|
||||
function formatDimensionValue(value: number) {
|
||||
return Number.isInteger(value) ? `${value}cm` : `${value.toFixed(1)}cm`;
|
||||
}
|
||||
|
||||
function extractAutoDimensionValues(
|
||||
sizeInfoText: string | null | undefined
|
||||
): AutoDimensionValues | null {
|
||||
const normalizedText = String(sizeInfoText || "").trim();
|
||||
if (!normalizedText) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const matchedLine =
|
||||
normalizedText.match(SIZE_INFO_LINE_REGEX)?.[1] || normalizedText;
|
||||
const values = Array.from(matchedLine.matchAll(NUMBER_REGEX))
|
||||
.map((item) => Number(item[0]))
|
||||
.filter((item) => Number.isFinite(item) && item > 0)
|
||||
.slice(0, 3);
|
||||
|
||||
if (values.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (values.length === 1) {
|
||||
return {
|
||||
heightCm: values[0],
|
||||
rawValues: values,
|
||||
widthCm: values[0],
|
||||
};
|
||||
}
|
||||
|
||||
if (values.length === 2) {
|
||||
const [larger, smaller] = [...values].sort((left, right) => right - left);
|
||||
return {
|
||||
heightCm: larger,
|
||||
rawValues: values,
|
||||
widthCm: smaller,
|
||||
};
|
||||
}
|
||||
|
||||
const sortedValues = [...values].sort((left, right) => right - left);
|
||||
const [heightCm, widthCm, depthCm] = sortedValues;
|
||||
|
||||
return {
|
||||
depthCm,
|
||||
heightCm,
|
||||
rawValues: values,
|
||||
widthCm,
|
||||
};
|
||||
}
|
||||
|
||||
function getAnnotationOrientation(annotation: DimensionAnnotation) {
|
||||
const deltaX = Math.abs(annotation.x2 - annotation.x1);
|
||||
const deltaY = Math.abs(annotation.y2 - annotation.y1);
|
||||
|
||||
if (deltaY > deltaX * 1.25) {
|
||||
return "vertical";
|
||||
}
|
||||
|
||||
if (deltaX > deltaY * 1.25) {
|
||||
return "horizontal";
|
||||
}
|
||||
|
||||
return "diagonal";
|
||||
}
|
||||
|
||||
function getAutoDimensionLabel(params: {
|
||||
annotation: DimensionAnnotation;
|
||||
annotations: DimensionAnnotation[];
|
||||
autoDimensionValues?: AutoDimensionValues | null;
|
||||
}) {
|
||||
const { annotation, annotations, autoDimensionValues } = params;
|
||||
if (!autoDimensionValues) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (autoDimensionValues.rawValues.length === 1) {
|
||||
return formatDimensionValue(autoDimensionValues.rawValues[0]);
|
||||
}
|
||||
|
||||
const orientation = getAnnotationOrientation(annotation);
|
||||
if (orientation === "vertical" && autoDimensionValues.heightCm) {
|
||||
return formatDimensionValue(autoDimensionValues.heightCm);
|
||||
}
|
||||
|
||||
if (orientation === "diagonal" && autoDimensionValues.depthCm) {
|
||||
return formatDimensionValue(autoDimensionValues.depthCm);
|
||||
}
|
||||
|
||||
if (orientation === "horizontal") {
|
||||
const horizontalAnnotations = annotations
|
||||
.filter((item) => getAnnotationOrientation(item) === "horizontal")
|
||||
.sort(
|
||||
(left, right) => getDimensionLength(right) - getDimensionLength(left)
|
||||
);
|
||||
const currentIndex = horizontalAnnotations.findIndex(
|
||||
(item) => item.id === annotation.id
|
||||
);
|
||||
|
||||
if (currentIndex <= 0 && autoDimensionValues.widthCm) {
|
||||
return formatDimensionValue(autoDimensionValues.widthCm);
|
||||
}
|
||||
|
||||
if (autoDimensionValues.depthCm) {
|
||||
return formatDimensionValue(autoDimensionValues.depthCm);
|
||||
}
|
||||
|
||||
if (autoDimensionValues.widthCm) {
|
||||
return formatDimensionValue(autoDimensionValues.widthCm);
|
||||
}
|
||||
}
|
||||
|
||||
if (autoDimensionValues.widthCm) {
|
||||
return formatDimensionValue(autoDimensionValues.widthCm);
|
||||
}
|
||||
|
||||
if (autoDimensionValues.heightCm) {
|
||||
return formatDimensionValue(autoDimensionValues.heightCm);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
function getDimensionLabel(params: {
|
||||
annotation: DimensionAnnotation;
|
||||
annotations: DimensionAnnotation[];
|
||||
autoDimensionValues?: AutoDimensionValues | null;
|
||||
}) {
|
||||
const { annotation, annotations, autoDimensionValues } = params;
|
||||
const customLabel = String(annotation.label || "").trim();
|
||||
if (customLabel) {
|
||||
return customLabel;
|
||||
}
|
||||
|
||||
return `${Math.max(1, Math.round(getDimensionLength(annotation)))} px`;
|
||||
return getAutoDimensionLabel({
|
||||
annotation,
|
||||
annotations,
|
||||
autoDimensionValues,
|
||||
});
|
||||
}
|
||||
|
||||
function mapImagePointToPreviewPoint(
|
||||
@ -205,11 +347,20 @@ function getAnnotationVector(x1: number, y1: number, x2: number, y2: number) {
|
||||
};
|
||||
}
|
||||
|
||||
function getPreviewAnnotationGeometry(
|
||||
annotation: DimensionAnnotation,
|
||||
previewRect: PreviewRect,
|
||||
imageSize: ImageSize
|
||||
) {
|
||||
function getPreviewAnnotationGeometry(params: {
|
||||
annotation: DimensionAnnotation;
|
||||
annotations: DimensionAnnotation[];
|
||||
previewRect: PreviewRect;
|
||||
imageSize: ImageSize;
|
||||
autoDimensionValues?: AutoDimensionValues | null;
|
||||
}) {
|
||||
const {
|
||||
annotation,
|
||||
annotations,
|
||||
previewRect,
|
||||
imageSize,
|
||||
autoDimensionValues,
|
||||
} = params;
|
||||
const start = mapImagePointToPreviewPoint(
|
||||
{ x: annotation.x1, y: annotation.y1 },
|
||||
previewRect,
|
||||
@ -221,7 +372,11 @@ function getPreviewAnnotationGeometry(
|
||||
imageSize
|
||||
);
|
||||
const vector = getAnnotationVector(start.x, start.y, end.x, end.y);
|
||||
const label = getDimensionLabel(annotation);
|
||||
const label = getDimensionLabel({
|
||||
annotation,
|
||||
annotations,
|
||||
autoDimensionValues,
|
||||
});
|
||||
const labelOffset = 16;
|
||||
const rawAngleRadians = Math.atan2(vector.dy, vector.dx);
|
||||
const angleRadians =
|
||||
@ -323,12 +478,20 @@ function drawDimensionCap(params: {
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
function drawAnnotationToCanvas(
|
||||
context: CanvasRenderingContext2D,
|
||||
annotation: DimensionAnnotation,
|
||||
imageSize: ImageSize
|
||||
) {
|
||||
const label = getDimensionLabel(annotation);
|
||||
function drawAnnotationToCanvas(params: {
|
||||
context: CanvasRenderingContext2D;
|
||||
annotation: DimensionAnnotation;
|
||||
imageSize: ImageSize;
|
||||
annotations: DimensionAnnotation[];
|
||||
autoDimensionValues?: AutoDimensionValues | null;
|
||||
}) {
|
||||
const { context, annotation, imageSize, annotations, autoDimensionValues } =
|
||||
params;
|
||||
const label = getDimensionLabel({
|
||||
annotation,
|
||||
annotations,
|
||||
autoDimensionValues,
|
||||
});
|
||||
const vector = getAnnotationVector(
|
||||
annotation.x1,
|
||||
annotation.y1,
|
||||
@ -399,6 +562,7 @@ export function AgentPsdTemplateExportPreview({
|
||||
imageAlt = "PSD 模板导出结果",
|
||||
enableDimensionAnnotations = false,
|
||||
downloadName,
|
||||
sizeInfoText,
|
||||
}: AgentPsdTemplateExportPreviewProps) {
|
||||
const previewViewportRef = useRef<HTMLDivElement | null>(null);
|
||||
const dragStateRef = useRef<DragState | null>(null);
|
||||
@ -416,6 +580,10 @@ export function AgentPsdTemplateExportPreview({
|
||||
() => buildImageProxyUrl(imageUrl),
|
||||
[imageUrl]
|
||||
);
|
||||
const autoDimensionValues = useMemo(
|
||||
() => extractAutoDimensionValues(sizeInfoText),
|
||||
[sizeInfoText]
|
||||
);
|
||||
const selectedAnnotation = useMemo(
|
||||
() =>
|
||||
annotations.find(
|
||||
@ -794,9 +962,15 @@ export function AgentPsdTemplateExportPreview({
|
||||
|
||||
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
for (const annotation of annotations) {
|
||||
drawAnnotationToCanvas(context, annotation, {
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
drawAnnotationToCanvas({
|
||||
annotation,
|
||||
annotations,
|
||||
autoDimensionValues,
|
||||
context,
|
||||
imageSize: {
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -824,7 +998,13 @@ export function AgentPsdTemplateExportPreview({
|
||||
} finally {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
}, [annotations, downloadName, enableDimensionAnnotations, imageUrl]);
|
||||
}, [
|
||||
annotations,
|
||||
autoDimensionValues,
|
||||
downloadName,
|
||||
enableDimensionAnnotations,
|
||||
imageUrl,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col gap-3 rounded-2xl border border-white/10 bg-white/[0.02] p-3.5 xl:p-4">
|
||||
@ -915,11 +1095,13 @@ export function AgentPsdTemplateExportPreview({
|
||||
>
|
||||
{annotations.map((annotation) => {
|
||||
const isSelected = annotation.id === selectedAnnotationId;
|
||||
const geometry = getPreviewAnnotationGeometry(
|
||||
const geometry = getPreviewAnnotationGeometry({
|
||||
annotation,
|
||||
annotations,
|
||||
autoDimensionValues,
|
||||
imageSize,
|
||||
previewRect,
|
||||
imageSize
|
||||
);
|
||||
});
|
||||
const strokeWidth = isSelected ? 2 : 1.5;
|
||||
const tickLength = isSelected ? 8 : 6.5;
|
||||
|
||||
@ -1068,17 +1250,25 @@ export function AgentPsdTemplateExportPreview({
|
||||
<div>
|
||||
<div className="font-medium text-sm text-white">尺寸文字</div>
|
||||
<div className="text-white/45 text-xs">
|
||||
留空时会自动显示像素长度,可手动改成真实尺寸。
|
||||
{autoDimensionValues
|
||||
? "留空时会优先使用商品尺寸信息自动显示 cm,可继续手动覆盖。"
|
||||
: "当前没有拿到商品尺寸信息,请直接手动输入 cm 尺寸。"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-full border border-white/10 bg-black/40 px-2.5 py-1 text-[11px] text-white/65">
|
||||
长度{" "}
|
||||
{Math.max(
|
||||
1,
|
||||
Math.round(getDimensionLength(selectedAnnotation))
|
||||
)}{" "}
|
||||
px
|
||||
</div>
|
||||
{getDimensionLabel({
|
||||
annotation: selectedAnnotation,
|
||||
annotations,
|
||||
autoDimensionValues,
|
||||
}) ? (
|
||||
<div className="rounded-full border border-white/10 bg-black/40 px-2.5 py-1 text-[11px] text-white/65">
|
||||
建议值{" "}
|
||||
{getDimensionLabel({
|
||||
annotation: selectedAnnotation,
|
||||
annotations,
|
||||
autoDimensionValues,
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<Input
|
||||
className="border-white/10 bg-black/35 text-white placeholder:text-white/28"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user