From efeb3bcc41b06f1faf1427d9fc887a2e0d502a75 Mon Sep 17 00:00:00 2001 From: 24kycj <1637269896@qq.com> Date: Sun, 28 Jun 2026 22:16:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(design1):=20=E5=85=89=E7=9B=98=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E7=8E=AF=E5=BD=A2=E8=A3=81=E5=89=AA=E4=B8=8E=E7=94=BB?= =?UTF-8?q?=E5=B8=83/=E7=BC=A9=E7=95=A5=E5=9B=BE=E6=A0=B7=E5=BC=8F?= =?UTF-8?q?=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 导出与预览 PNG 按光盘外圆与中心孔透明裁剪;画布蒙版改为与背景同色 #31373D;缩略图改 PNG 环形铺底;Web/Electron 同步。 Co-authored-by: Cursor --- frontend-electron/js/design1/core.js | 4 +- frontend-electron/js/design1/output.js | 114 ++++++++++++++++++++++--- frontend-web/js/design1/core.js | 2 +- frontend-web/js/design1/output.js | 92 +++++++++++++++++--- 4 files changed, 185 insertions(+), 27 deletions(-) diff --git a/frontend-electron/js/design1/core.js b/frontend-electron/js/design1/core.js index ab4e0be..8923801 100644 --- a/frontend-electron/js/design1/core.js +++ b/frontend-electron/js/design1/core.js @@ -1751,7 +1751,7 @@ function initCanvasEvents() { const ctx = currentCanvas.getContext(); let bgObj = (currentCanvas === canvas1) ? background_image1 : background_image2; - if (bgObj) { + if (bgObj && !window._soonSkipDesign1CdMask) { const width = currentCanvas.width; const height = currentCanvas.height; @@ -1795,7 +1795,7 @@ function initCanvasEvents() { ctx.arc(cx, cy, r_inner, 0, Math.PI * 2, false); ctx.closePath(); - ctx.fillStyle = "rgba(0, 0, 0, 0.9)"; // 90% 遮罩 + ctx.fillStyle = '#31373D'; ctx.fill(); ctx.restore(); } diff --git a/frontend-electron/js/design1/output.js b/frontend-electron/js/design1/output.js index ce9aea6..6777c2e 100644 --- a/frontend-electron/js/design1/output.js +++ b/frontend-electron/js/design1/output.js @@ -51,6 +51,91 @@ async function saveImageAsPNG(buffer) { } } +function soonDesign1GetHoleDiameterMm(canvas) { + var hole = 24; + if (canvas === window.canvas1 && typeof window.bg_version !== 'undefined' && window.bg_version === 2) { + hole = 42; + } + return hole; +} + +function soonDesign1ApplyDiscAlpha(tctx, w, h, holeDiameterMm) { + var cx = w / 2; + var cy = h / 2; + var rOuter = w / 2; + var rInner = rOuter * (holeDiameterMm / 120); + tctx.save(); + tctx.globalCompositeOperation = 'destination-in'; + tctx.fillStyle = '#fff'; + tctx.beginPath(); + tctx.arc(cx, cy, rOuter, 0, Math.PI * 2); + tctx.moveTo(cx + rInner, cy); + tctx.arc(cx, cy, rInner, 0, Math.PI * 2, true); + tctx.fill('evenodd'); + tctx.restore(); +} + +function soonDesign1ApplyDiscMatte(tctx, w, h, holeDiameterMm, color) { + var snap = document.createElement('canvas'); + snap.width = w; + snap.height = h; + snap.getContext('2d').drawImage(tctx.canvas, 0, 0); + tctx.fillStyle = color; + tctx.fillRect(0, 0, w, h); + tctx.save(); + var cx = w / 2; + var cy = h / 2; + var rOuter = w / 2; + var rInner = rOuter * (holeDiameterMm / 120); + tctx.beginPath(); + tctx.arc(cx, cy, rOuter, 0, Math.PI * 2); + tctx.moveTo(cx + rInner, cy); + tctx.arc(cx, cy, rInner, 0, Math.PI * 2, true); + tctx.clip('evenodd'); + tctx.drawImage(snap, 0, 0); + tctx.restore(); +} + +function soonDesign1CaptureCanvasRect(canvas, outW, outH, format, quality, clipDisc, discMatte) { + var bg = window.background_image1 || window.background_image; + if (!canvas || !bg) return ''; + var srcEl = canvas.lowerCanvasEl; + if (!srcEl || !srcEl.getContext) return ''; + var sx = bg.scaleX == null ? 1 : bg.scaleX; + var sy = bg.scaleY == null ? 1 : bg.scaleY; + var sw = bg.width * sx; + var sh = bg.height * sy; + var retina = (typeof canvas.getRetinaScaling === 'function') ? canvas.getRetinaScaling() : 1; + outW = outW || Math.round(sw); + outH = outH || Math.round(sh); + var tmp = document.createElement('canvas'); + tmp.width = outW; + tmp.height = outH; + var tctx = tmp.getContext('2d'); + tctx.drawImage( + srcEl, + bg.left * retina, bg.top * retina, sw * retina, sh * retina, + 0, 0, outW, outH + ); + if (discMatte) { + soonDesign1ApplyDiscMatte(tctx, outW, outH, soonDesign1GetHoleDiameterMm(canvas), discMatte); + } else if (clipDisc && format !== 'jpeg') { + soonDesign1ApplyDiscAlpha(tctx, outW, outH, soonDesign1GetHoleDiameterMm(canvas)); + } + if (format === 'jpeg') return tmp.toDataURL('image/jpeg', quality == null ? 0.75 : quality); + return tmp.toDataURL('image/png'); +} + +function soonDesign1CaptureDiscPng(canvas, outW, outH) { + window._soonSkipDesign1CdMask = true; + if (canvas.discardActiveObject) canvas.discardActiveObject(); + canvas.renderAll(); + var url = soonDesign1CaptureCanvasRect(canvas, outW, outH, 'png', null, true); + window._soonSkipDesign1CdMask = false; + canvas.renderAll(); + return url; +} + // 将 display_func 附加到 window 对象,确保全局可访问 window.display_func = function display_func(img1, img2, img3) { $("#base_control").hide(); @@ -217,13 +302,16 @@ window.display_func = function display_func(img1, img2, img3) { left: all_left }); - g3.push(img1); - let url3 = new fabric.Group(g3).toDataURL({ - height: 2787, - width: 2787, - top: all_top, - left: all_left + var hiddenForPreview = []; + canvas1.getObjects().forEach(function (o) { + if (o.isGuideLine) { + hiddenForPreview.push(o); + o.visible = false; + } }); + let url3 = soonDesign1CaptureDiscPng(canvas1, 2787, 2787); + hiddenForPreview.forEach(function (o) { o.visible = true; }); + canvas1.renderAll(); let url5 = new fabric.Group(g5).toDataURL({ height: 2787, width: 2787, @@ -596,12 +684,16 @@ window.output = function output(callback = null, _save = save) { left: all_left }); g3.push(img1); - let url3 = new fabric.Group(g3).toDataURL({ - height: 2787, - width: 2787, - top: all_top, - left: all_left + var hiddenForSave = []; + canvas1.getObjects().forEach(function (o) { + if (o.isGuideLine) { + hiddenForSave.push(o); + o.visible = false; + } }); + let url3 = soonDesign1CaptureDiscPng(canvas1, 2787, 2787); + hiddenForSave.forEach(function (o) { o.visible = true; }); + canvas1.renderAll(); let url5 = new fabric.Group(g5).toDataURL({ height: 2787, width: 2787, diff --git a/frontend-web/js/design1/core.js b/frontend-web/js/design1/core.js index 4d6da44..2dd48ff 100644 --- a/frontend-web/js/design1/core.js +++ b/frontend-web/js/design1/core.js @@ -1889,7 +1889,7 @@ function initCanvasEvents() { ctx.arc(cx, cy, r_inner, 0, Math.PI * 2, false); ctx.closePath(); - ctx.fillStyle = "rgba(0, 0, 0, 0.9)"; // 90% 遮罩 + ctx.fillStyle = '#31373D'; ctx.fill(); ctx.restore(); } diff --git a/frontend-web/js/design1/output.js b/frontend-web/js/design1/output.js index 21df98d..c29fb20 100644 --- a/frontend-web/js/design1/output.js +++ b/frontend-web/js/design1/output.js @@ -52,8 +52,55 @@ async function saveImageAsPNG(buffer) { } } -/** 从画布像素截取光盘外接方(含 after:render 圆外暗蒙版),与编辑器所见一致 */ -function soonDesign1CaptureCanvasRect(canvas, outW, outH, format, quality) { +/** 孔径规则与 core.js after:render 一致 */ +function soonDesign1GetHoleDiameterMm(canvas) { + var hole = 24; + if (canvas === window.canvas1 && typeof window.bg_version !== 'undefined' && window.bg_version === 2) { + hole = 42; + } + return hole; +} + +/** 圆外四角与中心孔 alpha 置 0,保留环形可打印区 */ +function soonDesign1ApplyDiscAlpha(tctx, w, h, holeDiameterMm) { + var cx = w / 2; + var cy = h / 2; + var rOuter = w / 2; + var rInner = rOuter * (holeDiameterMm / 120); + tctx.save(); + tctx.globalCompositeOperation = 'destination-in'; + tctx.fillStyle = '#fff'; + tctx.beginPath(); + tctx.arc(cx, cy, rOuter, 0, Math.PI * 2); + tctx.moveTo(cx + rInner, cy); + tctx.arc(cx, cy, rInner, 0, Math.PI * 2, true); + tctx.fill('evenodd'); + tctx.restore(); +} + +/** 圆外与中心孔铺画布底色,保留环形内容(缩略图用) */ +function soonDesign1ApplyDiscMatte(tctx, w, h, holeDiameterMm, color) { + var snap = document.createElement('canvas'); + snap.width = w; + snap.height = h; + snap.getContext('2d').drawImage(tctx.canvas, 0, 0); + tctx.fillStyle = color; + tctx.fillRect(0, 0, w, h); + tctx.save(); + var cx = w / 2; + var cy = h / 2; + var rOuter = w / 2; + var rInner = rOuter * (holeDiameterMm / 120); + tctx.beginPath(); + tctx.arc(cx, cy, rOuter, 0, Math.PI * 2); + tctx.moveTo(cx + rInner, cy); + tctx.arc(cx, cy, rInner, 0, Math.PI * 2, true); + tctx.clip('evenodd'); + tctx.drawImage(snap, 0, 0); + tctx.restore(); +} + +function soonDesign1CaptureCanvasRect(canvas, outW, outH, format, quality, clipDisc, discMatte) { var bg = window.background_image1 || window.background_image; if (!canvas || !bg) return ''; var srcEl = canvas.lowerCanvasEl; @@ -74,10 +121,26 @@ function soonDesign1CaptureCanvasRect(canvas, outW, outH, format, quality) { bg.left * retina, bg.top * retina, sw * retina, sh * retina, 0, 0, outW, outH ); + if (discMatte) { + soonDesign1ApplyDiscMatte(tctx, outW, outH, soonDesign1GetHoleDiameterMm(canvas), discMatte); + } else if (clipDisc && format !== 'jpeg') { + soonDesign1ApplyDiscAlpha(tctx, outW, outH, soonDesign1GetHoleDiameterMm(canvas)); + } if (format === 'jpeg') return tmp.toDataURL('image/jpeg', quality == null ? 0.75 : quality); return tmp.toDataURL('image/png'); } +/** 导出/打印:跳过编辑器暗蒙版,按光盘环形裁剪 */ +function soonDesign1CaptureDiscPng(canvas, outW, outH) { + window._soonSkipDesign1CdMask = true; + if (canvas.discardActiveObject) canvas.discardActiveObject(); + canvas.renderAll(); + var url = soonDesign1CaptureCanvasRect(canvas, outW, outH, 'png', null, true); + window._soonSkipDesign1CdMask = false; + canvas.renderAll(); + return url; +} + function soonBuildDesign1SavePreviewPic(canvas) { var THUMB_MAX = 102400; var c1 = window.canvas1; @@ -90,17 +153,18 @@ function soonBuildDesign1SavePreviewPic(canvas) { } }); if (canvas.discardActiveObject) canvas.discardActiveObject(); + window._soonSkipDesign1CdMask = true; canvas.renderAll(); var url = ''; - var sizes = [320, 280, 240, 200]; - var quals = [0.82, 0.68, 0.52, 0.38]; + var sizes = [280, 240, 200, 180, 160]; for (var i = 0; i < sizes.length; i++) { - url = soonDesign1CaptureCanvasRect(canvas, sizes[i], sizes[i], 'jpeg', quals[i]); + url = soonDesign1CaptureCanvasRect(canvas, sizes[i], sizes[i], 'png', null, false, '#31373D'); if (url && url.indexOf('data:image/') === 0 && url.length <= THUMB_MAX) break; url = ''; } + window._soonSkipDesign1CdMask = false; hidden.forEach(function (obj) { obj.visible = true; }); canvas.renderAll(); if (canvas.requestRenderAll) canvas.requestRenderAll(); @@ -280,9 +344,7 @@ window.display_func = function display_func(img1, img2, img3) { o.visible = false; } }); - canvas1.discardActiveObject(); - canvas1.renderAll(); - let url3 = soonDesign1CaptureCanvasRect(canvas1, 2787, 2787, 'png'); + let url3 = soonDesign1CaptureDiscPng(canvas1, 2787, 2787); hiddenForPreview.forEach(function (o) { o.visible = true; }); canvas1.renderAll(); let url5 = new fabric.Group(g5).toDataURL({ @@ -675,12 +737,16 @@ window.output = function output(callback = null, _save = save) { left: all_left }); g3.push(img1); - let url3 = new fabric.Group(g3).toDataURL({ - height: 2787, - width: 2787, - top: all_top, - left: all_left + var hiddenForSave = []; + canvas1.getObjects().forEach(function (o) { + if (o.isGuideLine) { + hiddenForSave.push(o); + o.visible = false; + } }); + let url3 = soonDesign1CaptureDiscPng(canvas1, 2787, 2787); + hiddenForSave.forEach(function (o) { o.visible = true; }); + canvas1.renderAll(); let url5 = new fabric.Group(g5).toDataURL({ height: 2787, width: 2787,