Improve high resolution PNG export
This commit is contained in:
+23
-18
@@ -1026,9 +1026,6 @@ export default function Home() {
|
||||
moved: boolean;
|
||||
} | null>(null);
|
||||
const suppressCanvasClickRef = useRef(false);
|
||||
const cropDialogImage = cropDialogPurpose === "pattern-import"
|
||||
? patternImportImageRef.current
|
||||
: sourceImageRef.current;
|
||||
|
||||
useEffect(() => {
|
||||
logUsage({ event: "page_view" });
|
||||
@@ -1152,7 +1149,9 @@ export default function Home() {
|
||||
useEffect(() => {
|
||||
if (!cropDialogOpen) return;
|
||||
const canvas = cropDialogCanvasRef.current;
|
||||
const image = cropDialogImage;
|
||||
const image = cropDialogPurpose === "pattern-import"
|
||||
? patternImportImageRef.current
|
||||
: sourceImageRef.current;
|
||||
if (!canvas || !image) return;
|
||||
const maximumWidth = 1200;
|
||||
const maximumHeight = 760;
|
||||
@@ -1162,7 +1161,7 @@ export default function Home() {
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
}, [cropDialogOpen, cropDialogPurpose, sourceUrl, patternImportUrl, cropDialogImage]);
|
||||
}, [cropDialogOpen, cropDialogPurpose, sourceUrl, patternImportUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!cropDialogOpen) return;
|
||||
@@ -1397,7 +1396,9 @@ export default function Home() {
|
||||
let width = Math.abs(endX - startX);
|
||||
let height = Math.abs(endY - startY);
|
||||
if (cropSelectionMode === "square") {
|
||||
const image = cropDialogImage;
|
||||
const image = cropDialogPurpose === "pattern-import"
|
||||
? patternImportImageRef.current
|
||||
: sourceImageRef.current;
|
||||
if (!image) return;
|
||||
const normalizedSquareRatio = image.naturalHeight / image.naturalWidth;
|
||||
if (width / Math.max(height, 0.0001) > normalizedSquareRatio) width = height * normalizedSquareRatio;
|
||||
@@ -1430,7 +1431,9 @@ export default function Home() {
|
||||
const chooseCropSelectionMode = (mode: CropSelectionMode) => {
|
||||
setCropSelectionMode(mode);
|
||||
if (mode !== "square") return;
|
||||
const image = cropDialogImage;
|
||||
const image = cropDialogPurpose === "pattern-import"
|
||||
? patternImportImageRef.current
|
||||
: sourceImageRef.current;
|
||||
if (!image) return;
|
||||
const currentCenterX = draftCropRect.x + draftCropRect.width / 2;
|
||||
const currentCenterY = draftCropRect.y + draftCropRect.height / 2;
|
||||
@@ -1754,14 +1757,16 @@ export default function Home() {
|
||||
const exportPng = () => {
|
||||
if (!pixels.length) return;
|
||||
const longestSide = Math.max(gridWidth, gridHeight);
|
||||
const cell = longestSide <= 120 ? 36 : longestSide <= 180 ? 30 : 24;
|
||||
const ruler = Math.max(42, cell + 12);
|
||||
const maximumExportSide = 8192;
|
||||
const preferredCell = longestSide <= 120 ? 56 : longestSide <= 180 ? 40 : 32;
|
||||
const cell = Math.max(24, Math.min(preferredCell, Math.floor((maximumExportSide - 72) / longestSide)));
|
||||
const ruler = Math.max(48, cell + 16);
|
||||
const gridPixelWidth = gridWidth * cell;
|
||||
const gridPixelHeight = gridHeight * cell;
|
||||
const canvasWidth = ruler + gridPixelWidth;
|
||||
const legendPadding = 18;
|
||||
const legendItemWidth = 78;
|
||||
const legendItemHeight = 36;
|
||||
const legendItemWidth = 128;
|
||||
const legendItemHeight = 42;
|
||||
const legendColumns = Math.max(1, Math.floor((canvasWidth - legendPadding * 2) / legendItemWidth));
|
||||
const legendRows = Math.ceil(usedColors.length / legendColumns);
|
||||
const legendHeight = usedColors.length > 0 ? legendPadding * 2 + legendRows * legendItemHeight : 0;
|
||||
@@ -1805,9 +1810,9 @@ export default function Home() {
|
||||
const label = color.code;
|
||||
const [red, green, blue] = hexToRgb(color.hex);
|
||||
ctx.fillStyle = red * 0.299 + green * 0.587 + blue * 0.114 > 160 ? "#18211d" : "#ffffff";
|
||||
let fontSize = Math.max(6, Math.floor(cell * 0.29));
|
||||
let fontSize = Math.max(8, Math.floor(cell * 0.31));
|
||||
ctx.font = `700 ${fontSize}px Arial`;
|
||||
while (fontSize > 6 && ctx.measureText(label).width > cell - 3) {
|
||||
while (fontSize > 8 && ctx.measureText(label).width > cell - 5) {
|
||||
fontSize -= 1;
|
||||
ctx.font = `700 ${fontSize}px Arial`;
|
||||
}
|
||||
@@ -1855,7 +1860,7 @@ export default function Home() {
|
||||
const y = legendTop + legendPadding + row * legendItemHeight;
|
||||
ctx.fillStyle = color.hex;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x + 11, y + 11, 9, 0, Math.PI * 2);
|
||||
ctx.arc(x + 12, y + 13, 10, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = "rgba(32,38,36,.28)";
|
||||
ctx.lineWidth = 1;
|
||||
@@ -1864,7 +1869,7 @@ export default function Home() {
|
||||
ctx.font = "700 13px Arial";
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(color.code, x + 26, y + 11);
|
||||
ctx.fillText(`${color.code} · ${color.count}颗`, x + 28, y + 13);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1879,8 +1884,8 @@ export default function Home() {
|
||||
link.href = url;
|
||||
link.click();
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
||||
setStatus("PNG 已生成:格内为 MARD 色号,底部为色块和对应色号");
|
||||
logUsage({ event: "download_png", width: gridWidth, height: gridHeight, colors: usedColors.length });
|
||||
setStatus(`高清 PNG 已生成:${canvas.width} × ${canvas.height}px,底部图例包含色号和用量`);
|
||||
logUsage({ event: "download_png", width: gridWidth, height: gridHeight, colors: usedColors.length, export_width: canvas.width, export_height: canvas.height });
|
||||
}, "image/png");
|
||||
};
|
||||
|
||||
@@ -1981,7 +1986,7 @@ export default function Home() {
|
||||
<label><input type="checkbox" checked={showGrid} onChange={(e) => setShowGrid(e.target.checked)} /> 网格</label>
|
||||
<label><input type="checkbox" checked={showCodes} onChange={(e) => setShowCodes(e.target.checked)} /> 格内编号</label>
|
||||
<button className="save-button" onClick={savePattern}>保存历史</button>
|
||||
<button onClick={exportPng}>下载 PNG</button>
|
||||
<button onClick={exportPng}>下载高清 PNG</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pattern-legend">
|
||||
|
||||
Reference in New Issue
Block a user