style: match PSD dimension lines to reference style
This commit is contained in:
parent
cc53eee037
commit
d53308126d
@ -83,6 +83,9 @@ const PREVIEW_MIN_HEIGHT = 340;
|
||||
const LABEL_PLACEHOLDER = "例如:120cm / 50cm";
|
||||
const IMAGE_EXTENSION_REGEX = /\.(png|jpg|jpeg|webp)$/i;
|
||||
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)";
|
||||
|
||||
function clamp(value: number, min: number, max: number) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
@ -219,17 +222,21 @@ function getPreviewAnnotationGeometry(
|
||||
);
|
||||
const vector = getAnnotationVector(start.x, start.y, end.x, end.y);
|
||||
const label = getDimensionLabel(annotation);
|
||||
const labelWidth = clamp(label.length * 11 + 26, 76, 240);
|
||||
const labelHeight = 30;
|
||||
const labelOffset = 22;
|
||||
const labelOffset = 16;
|
||||
const rawAngleRadians = Math.atan2(vector.dy, vector.dx);
|
||||
const angleRadians =
|
||||
rawAngleRadians > Math.PI / 2
|
||||
? rawAngleRadians - Math.PI
|
||||
: rawAngleRadians < -Math.PI / 2
|
||||
? rawAngleRadians + Math.PI
|
||||
: rawAngleRadians;
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
vector,
|
||||
label,
|
||||
labelWidth,
|
||||
labelHeight,
|
||||
angleDegrees: (angleRadians * 180) / Math.PI,
|
||||
labelCenterX: (start.x + end.x) / 2 + vector.nx * labelOffset,
|
||||
labelCenterY: (start.y + end.y) / 2 + vector.ny * labelOffset,
|
||||
};
|
||||
@ -303,62 +310,19 @@ function downloadBlob(blob: Blob, fileName: string) {
|
||||
URL.revokeObjectURL(objectUrl);
|
||||
}
|
||||
|
||||
function drawArrowHead(params: {
|
||||
function drawDimensionCap(params: {
|
||||
context: CanvasRenderingContext2D;
|
||||
point: Point;
|
||||
direction: Point;
|
||||
vector: ReturnType<typeof getAnnotationVector>;
|
||||
size: number;
|
||||
color: string;
|
||||
}) {
|
||||
const { context, point, direction, size, color } = params;
|
||||
const directionLength = Math.max(1, Math.hypot(direction.x, direction.y));
|
||||
const ux = direction.x / directionLength;
|
||||
const uy = direction.y / directionLength;
|
||||
const angle = Math.PI / 7;
|
||||
const leftAngleX = ux * Math.cos(angle) - uy * Math.sin(angle);
|
||||
const leftAngleY = ux * Math.sin(angle) + uy * Math.cos(angle);
|
||||
const rightAngleX = ux * Math.cos(-angle) - uy * Math.sin(-angle);
|
||||
const rightAngleY = ux * Math.sin(-angle) + uy * Math.cos(-angle);
|
||||
|
||||
const { context, point, vector, size } = params;
|
||||
context.beginPath();
|
||||
context.moveTo(point.x, point.y);
|
||||
context.lineTo(point.x - leftAngleX * size, point.y - leftAngleY * size);
|
||||
context.moveTo(point.x, point.y);
|
||||
context.lineTo(point.x - rightAngleX * size, point.y - rightAngleY * size);
|
||||
context.strokeStyle = color;
|
||||
context.lineCap = "round";
|
||||
context.moveTo(point.x + vector.nx * size, point.y + vector.ny * size);
|
||||
context.lineTo(point.x - vector.nx * size, point.y - vector.ny * size);
|
||||
context.stroke();
|
||||
}
|
||||
|
||||
function fillRoundedRect(params: {
|
||||
context: CanvasRenderingContext2D;
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
radius: number;
|
||||
}) {
|
||||
const { context, x, y, width, height, radius } = params;
|
||||
const clampedRadius = Math.min(radius, width / 2, height / 2);
|
||||
context.beginPath();
|
||||
context.moveTo(x + clampedRadius, y);
|
||||
context.lineTo(x + width - clampedRadius, y);
|
||||
context.quadraticCurveTo(x + width, y, x + width, y + clampedRadius);
|
||||
context.lineTo(x + width, y + height - clampedRadius);
|
||||
context.quadraticCurveTo(
|
||||
x + width,
|
||||
y + height,
|
||||
x + width - clampedRadius,
|
||||
y + height
|
||||
);
|
||||
context.lineTo(x + clampedRadius, y + height);
|
||||
context.quadraticCurveTo(x, y + height, x, y + height - clampedRadius);
|
||||
context.lineTo(x, y + clampedRadius);
|
||||
context.quadraticCurveTo(x, y, x + clampedRadius, y);
|
||||
context.closePath();
|
||||
context.fill();
|
||||
}
|
||||
|
||||
function drawAnnotationToCanvas(
|
||||
context: CanvasRenderingContext2D,
|
||||
annotation: DimensionAnnotation,
|
||||
@ -371,99 +335,62 @@ function drawAnnotationToCanvas(
|
||||
annotation.x2,
|
||||
annotation.y2
|
||||
);
|
||||
const rawAngleRadians = Math.atan2(vector.dy, vector.dx);
|
||||
const angleRadians =
|
||||
rawAngleRadians > Math.PI / 2
|
||||
? rawAngleRadians - Math.PI
|
||||
: rawAngleRadians < -Math.PI / 2
|
||||
? rawAngleRadians + Math.PI
|
||||
: rawAngleRadians;
|
||||
const strokeWidth = clamp(
|
||||
Math.round(Math.min(imageSize.width, imageSize.height) * 0.004),
|
||||
2,
|
||||
8
|
||||
Math.min(imageSize.width, imageSize.height) * 0.0018,
|
||||
1.25,
|
||||
3
|
||||
);
|
||||
const tickLength = strokeWidth * 2.5;
|
||||
const arrowSize = strokeWidth * 3.5;
|
||||
const capLength = strokeWidth * 6;
|
||||
const fontSize = clamp(
|
||||
Math.round(Math.min(imageSize.width, imageSize.height) * 0.028),
|
||||
16,
|
||||
42
|
||||
Math.round(Math.min(imageSize.width, imageSize.height) * 0.022),
|
||||
14,
|
||||
34
|
||||
);
|
||||
const labelPaddingX = fontSize * 0.7;
|
||||
const labelPaddingY = fontSize * 0.45;
|
||||
const labelOffset = fontSize * 1.4;
|
||||
const labelOffset = fontSize * 0.95;
|
||||
const centerX = (annotation.x1 + annotation.x2) / 2 + vector.nx * labelOffset;
|
||||
const centerY = (annotation.y1 + annotation.y2) / 2 + vector.ny * labelOffset;
|
||||
|
||||
context.save();
|
||||
context.strokeStyle = "#fb923c";
|
||||
context.strokeStyle = DIMENSION_LINE_COLOR;
|
||||
context.lineWidth = strokeWidth;
|
||||
context.lineCap = "round";
|
||||
context.lineJoin = "round";
|
||||
context.shadowColor = "rgba(249, 115, 22, 0.22)";
|
||||
context.shadowBlur = strokeWidth * 2;
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(annotation.x1, annotation.y1);
|
||||
context.lineTo(annotation.x2, annotation.y2);
|
||||
context.stroke();
|
||||
|
||||
context.beginPath();
|
||||
context.moveTo(
|
||||
annotation.x1 + vector.nx * tickLength,
|
||||
annotation.y1 + vector.ny * tickLength
|
||||
);
|
||||
context.lineTo(
|
||||
annotation.x1 - vector.nx * tickLength,
|
||||
annotation.y1 - vector.ny * tickLength
|
||||
);
|
||||
context.moveTo(
|
||||
annotation.x2 + vector.nx * tickLength,
|
||||
annotation.y2 + vector.ny * tickLength
|
||||
);
|
||||
context.lineTo(
|
||||
annotation.x2 - vector.nx * tickLength,
|
||||
annotation.y2 - vector.ny * tickLength
|
||||
);
|
||||
context.stroke();
|
||||
|
||||
drawArrowHead({
|
||||
drawDimensionCap({
|
||||
context,
|
||||
point: { x: annotation.x1, y: annotation.y1 },
|
||||
direction: {
|
||||
x: annotation.x1 - annotation.x2,
|
||||
y: annotation.y1 - annotation.y2,
|
||||
},
|
||||
size: arrowSize,
|
||||
color: "#fb923c",
|
||||
size: capLength,
|
||||
vector,
|
||||
});
|
||||
drawArrowHead({
|
||||
drawDimensionCap({
|
||||
context,
|
||||
point: { x: annotation.x2, y: annotation.y2 },
|
||||
direction: {
|
||||
x: annotation.x2 - annotation.x1,
|
||||
y: annotation.y2 - annotation.y1,
|
||||
},
|
||||
size: arrowSize,
|
||||
color: "#fb923c",
|
||||
size: capLength,
|
||||
vector,
|
||||
});
|
||||
|
||||
context.shadowBlur = 0;
|
||||
context.font = `600 ${fontSize}px "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif`;
|
||||
const textMetrics = context.measureText(label);
|
||||
const labelWidth = textMetrics.width + labelPaddingX * 2;
|
||||
const labelHeight = fontSize + labelPaddingY * 2;
|
||||
const labelX = centerX - labelWidth / 2;
|
||||
const labelY = centerY - labelHeight / 2;
|
||||
|
||||
context.fillStyle = "rgba(9, 9, 11, 0.88)";
|
||||
fillRoundedRect({
|
||||
context,
|
||||
x: labelX,
|
||||
y: labelY,
|
||||
width: labelWidth,
|
||||
height: labelHeight,
|
||||
radius: fontSize * 0.55,
|
||||
});
|
||||
|
||||
context.fillStyle = "#ffffff";
|
||||
context.translate(centerX, centerY);
|
||||
context.rotate(angleRadians);
|
||||
context.font = `500 ${fontSize}px "PingFang SC", "Microsoft YaHei", "Noto Sans SC", sans-serif`;
|
||||
context.textAlign = "center";
|
||||
context.textBaseline = "middle";
|
||||
context.fillText(label, centerX, centerY + 1);
|
||||
context.lineWidth = Math.max(2, fontSize * 0.22);
|
||||
context.strokeStyle = DIMENSION_TEXT_HALO_COLOR;
|
||||
context.strokeText(label, 0, 0);
|
||||
context.fillStyle = DIMENSION_TEXT_COLOR;
|
||||
context.fillText(label, 0, 0);
|
||||
context.restore();
|
||||
}
|
||||
|
||||
@ -986,19 +913,6 @@ export function AgentPsdTemplateExportPreview({
|
||||
className="absolute inset-0 h-full w-full"
|
||||
onPointerDown={handleOverlayPointerDown}
|
||||
>
|
||||
<defs>
|
||||
<marker
|
||||
id="psd-dimension-arrow"
|
||||
markerHeight="8"
|
||||
markerWidth="8"
|
||||
orient="auto-start-reverse"
|
||||
refX="4"
|
||||
refY="4"
|
||||
>
|
||||
<path d="M0,0 L8,4 L0,8 z" fill="#fb923c" />
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
{annotations.map((annotation) => {
|
||||
const isSelected = annotation.id === selectedAnnotationId;
|
||||
const geometry = getPreviewAnnotationGeometry(
|
||||
@ -1006,7 +920,8 @@ export function AgentPsdTemplateExportPreview({
|
||||
previewRect,
|
||||
imageSize
|
||||
);
|
||||
const tickLength = isSelected ? 10 : 8;
|
||||
const strokeWidth = isSelected ? 2 : 1.5;
|
||||
const tickLength = isSelected ? 8 : 6.5;
|
||||
|
||||
return (
|
||||
<g key={annotation.id}>
|
||||
@ -1022,13 +937,11 @@ export function AgentPsdTemplateExportPreview({
|
||||
y2={geometry.end.y}
|
||||
/>
|
||||
<line
|
||||
markerEnd="url(#psd-dimension-arrow)"
|
||||
markerStart="url(#psd-dimension-arrow)"
|
||||
opacity={isSelected ? 1 : 0.92}
|
||||
pointerEvents="none"
|
||||
stroke="#fb923c"
|
||||
stroke={DIMENSION_LINE_COLOR}
|
||||
strokeLinecap="round"
|
||||
strokeWidth={isSelected ? 3 : 2.5}
|
||||
strokeWidth={strokeWidth}
|
||||
x1={geometry.start.x}
|
||||
x2={geometry.end.x}
|
||||
y1={geometry.start.y}
|
||||
@ -1036,7 +949,7 @@ export function AgentPsdTemplateExportPreview({
|
||||
/>
|
||||
<line
|
||||
pointerEvents="none"
|
||||
stroke="#fdba74"
|
||||
stroke={DIMENSION_LINE_COLOR}
|
||||
strokeLinecap="round"
|
||||
strokeWidth={isSelected ? 2.5 : 2}
|
||||
x1={geometry.start.x + geometry.vector.nx * tickLength}
|
||||
@ -1046,7 +959,7 @@ export function AgentPsdTemplateExportPreview({
|
||||
/>
|
||||
<line
|
||||
pointerEvents="none"
|
||||
stroke="#fdba74"
|
||||
stroke={DIMENSION_LINE_COLOR}
|
||||
strokeLinecap="round"
|
||||
strokeWidth={isSelected ? 2.5 : 2}
|
||||
x1={geometry.end.x + geometry.vector.nx * tickLength}
|
||||
@ -1059,27 +972,18 @@ export function AgentPsdTemplateExportPreview({
|
||||
handleAnnotationBodyPointerDown(annotation, event)
|
||||
}
|
||||
>
|
||||
<rect
|
||||
fill="rgba(9,9,11,0.88)"
|
||||
height={geometry.labelHeight}
|
||||
rx={15}
|
||||
stroke={
|
||||
isSelected
|
||||
? "rgba(251,146,60,0.7)"
|
||||
: "rgba(255,255,255,0.14)"
|
||||
}
|
||||
strokeWidth={isSelected ? 1.5 : 1}
|
||||
width={geometry.labelWidth}
|
||||
x={geometry.labelCenterX - geometry.labelWidth / 2}
|
||||
y={geometry.labelCenterY - geometry.labelHeight / 2}
|
||||
/>
|
||||
<text
|
||||
dominantBaseline="middle"
|
||||
fill="#ffffff"
|
||||
fill={DIMENSION_TEXT_COLOR}
|
||||
fontSize={13}
|
||||
fontWeight={600}
|
||||
fontWeight={500}
|
||||
paintOrder="stroke"
|
||||
pointerEvents="none"
|
||||
stroke={DIMENSION_TEXT_HALO_COLOR}
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2.8}
|
||||
textAnchor="middle"
|
||||
transform={`rotate(${geometry.angleDegrees} ${geometry.labelCenterX} ${geometry.labelCenterY})`}
|
||||
x={geometry.labelCenterX}
|
||||
y={geometry.labelCenterY + 0.5}
|
||||
>
|
||||
@ -1099,9 +1003,9 @@ export function AgentPsdTemplateExportPreview({
|
||||
event
|
||||
)
|
||||
}
|
||||
r={6}
|
||||
stroke="#fb923c"
|
||||
strokeWidth={2}
|
||||
r={4}
|
||||
stroke={DIMENSION_LINE_COLOR}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<circle
|
||||
cx={geometry.end.x}
|
||||
@ -1114,9 +1018,9 @@ export function AgentPsdTemplateExportPreview({
|
||||
event
|
||||
)
|
||||
}
|
||||
r={6}
|
||||
stroke="#fb923c"
|
||||
strokeWidth={2}
|
||||
r={4}
|
||||
stroke={DIMENSION_LINE_COLOR}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user