模擬紡綞狀筆刷. 2端細中間粗
好問題!HTML Canvas 的 ctx.lineWidth 屬性設定的線條寬度是
使用多個 lineTo 繪製一個細長的多邊形 (最簡單的錐形): 想像你的線條是從 (x1, y1) 到 (x2, y2)。 它的中間最寬處是 maxWidth。 它的兩端寬度是 minWidth (通常是 0 或接近 0)。 你需要計算出構成這個錐形(或紡錘形)的至少 4 個頂點,然後用 fill() 填充。
範例:畫一個兩端尖銳,中間最寬的直線 function drawTaperedLine(ctx, x1, y1, x2, y2, maxWidth, color = 'black') { const dx = x2 - x1; const dy = y2 - y1; const len = Math.sqrt(dx * dx + dy * dy); if (len === 0) return; // 計算線條方向的單位向量 const ux = dx / len; const uy = dy / len; // 計算垂直於線條方向的單位向量 (法向量) const nx = -uy; const ny = ux; // 中點 const midX = (x1 + x2) / 2; const midY = (y1 + y2) / 2; // 計算錐形/紡錘形的4個頂點 // 頂點1: 起點 const p1x = x1; const p1y = y1; // 頂點2: 中點沿法向量偏移 maxWidth/2 const p2x = midX + nx * (maxWidth / 2); const p2y = midY + ny * (maxWidth / 2); // 頂點3: 終點 const p3x = x2; const p3y = y2; // 頂點4: 中點沿反法向量偏移 maxWidth/2 const p4x = midX - nx * (maxWidth / 2); const p4y = midY - ny * (maxWidth / 2); ctx.beginPath(); ctx.moveTo(p1x, p1y); ctx.lineTo(p2x, p2y); ctx.lineTo(p3x, p3y); ctx.lineTo(p4x, p4y); ctx.closePath(); // 閉合路徑形成一個四邊形 ctx.fillStyle = color; ctx.fill(); } // 使用範例: // const canvas = document.getElementById('myCanvas'); // const ctx = canvas.getContext('2d'); // drawTaperedLine(ctx, 50, 50, 250, 100, 10, 'blue'); // 從(50,50)到(250,100),中間最寬10px // drawTaperedLine(ctx, 50, 150, 250, 200, 5, 'red');這個方法會畫出一個菱形的錐形線條。 使用 quadraticCurveTo 或 bezierCurveTo 繪製更平滑的紡錘形: 這種方法可以畫出更像毛筆筆觸的效果,線條邊緣是平滑的曲線。 你需要定義兩條相對的貝茲曲線或二次貝茲曲線,它們在起點和終點相交(或非常接近)。 概念(較複雜,需要仔細計算控制點): function drawSmoothTaperedLine(ctx, x1, y1, x2, y2, maxWidth, color = 'black') { const dx = x2 - x1; const dy = y2 - y1; const len = Math.sqrt(dx * dx + dy * dy); if (len === 0) return; const ux = dx / len; const uy = dy / len; const nx = -uy; const ny = ux; const midX = (x1 + x2) / 2; const midY = (y1 + y2) / 2; // 計算控制點的偏移量,使其在中間形成 bulge const controlOffsetFactor = 0.5; // 可調整此因子來改變曲線的"胖瘦" ctx.beginPath(); ctx.moveTo(x1, y1); // 起點 // 第一條曲線 (一側) const cp1x_1 = midX + nx * (maxWidth / 2) - ux * len * controlOffsetFactor; const cp1y_1 = midY + ny * (maxWidth / 2) - uy * len * controlOffsetFactor; // 如果使用 quadraticCurveTo,只需要一個控制點,可以簡化為中點附近向外偏移 // 例如: const qcp1x = midX + nx * (maxWidth * 0.8); // 稍微誇張一點的控制點 // const qcp1y = midY + ny * (maxWidth * 0.8); // ctx.quadraticCurveTo(qcp1x, qcp1y, x2, y2); // 為了更像紡錘形,我們可能需要兩個控制點或更複雜的計算 // 簡化版:用兩段二次貝茲曲線模擬一邊 const bulgePoint1X = midX + nx * (maxWidth / 2); const bulgePoint1Y = midY + ny * (maxWidth / 2); ctx.quadraticCurveTo(x1 + dx * 0.25 + nx * (maxWidth * 0.3), y1 + dy * 0.25 + ny * (maxWidth * 0.3), bulgePoint1X, bulgePoint1Y); ctx.quadraticCurveTo(x2 - dx * 0.25 + nx * (maxWidth * 0.3), y2 - dy * 0.25 + ny * (maxWidth * 0.3), x2, y2); // 第二條曲線 (另一側,回到起點) const bulgePoint2X = midX - nx * (maxWidth / 2); const bulgePoint2Y = midY - ny * (maxWidth / 2); ctx.quadraticCurveTo(x2 - dx * 0.25 - nx * (maxWidth * 0.3), y2 - dy * 0.25 - ny * (maxWidth * 0.3), bulgePoint2X, bulgePoint2Y); ctx.quadraticCurveTo(x1 + dx * 0.25 - nx * (maxWidth * 0.3), y1 + dy * 0.25 - ny * (maxWidth * 0.3), x1, y1); ctx.closePath(); ctx.fillStyle = color; ctx.fill(); } // 使用範例: // drawSmoothTaperedLine(ctx, 50, 250, 250, 300, 20, 'green');注意: drawSmoothTaperedLine 的控制點計算是比較棘手的,上面的範例是一個簡化的嘗試,可能需要仔細調整控制點位置才能達到理想的平滑紡錘形。更精確的毛筆效果通常需要更複雜的算法。逐點繪製不同寬度的短線段或圓點 (模擬筆刷壓力變化): 將你的長線條分解成許多非常短的小線段。 為每個小線段計算一個寬度 (例如,使用 sin 函數或一個預設的壓力曲線,使得中間的線段寬,兩端的線段窄)。 使用 ctx.lineWidth 設定當前小線段的寬度。 使用 ctx.lineCap = 'round' 可以讓這些短線段連接起來更平滑。 畫出這個短線段。 這種方法計算量較大,但能提供最大的靈活性。
function drawPressureLine(ctx, x1, y1, x2, y2, maxLineWidth, segments = 50, color = 'black') { ctx.beginPath(); ctx.lineCap = 'round'; // 讓線段末端變圓,連接更平滑 ctx.lineJoin = 'round'; ctx.strokeStyle = color; for (let i = 0; i <= segments; i++) { const t = i / segments; // 參數 t 從 0 到 1 // 計算當前點的位置 const currentX = x1 + (x2 - x1) * t; const currentY = y1 + (y2 - y1) * t; // 計算當前點的線條寬度 (例如,使用 sin 函數形成中間粗兩端細) // Math.sin(t * Math.PI) 在 t=0 和 t=1 時為 0, 在 t=0.5 時為 1 const currentWidth = Math.sin(t * Math.PI) * maxLineWidth; ctx.lineWidth = Math.max(0.1, currentWidth); // 確保線寬不為0或負 if (i === 0) { ctx.moveTo(currentX, currentY); } else { // 如果 lineWidth 變化很快,直接 lineTo 可能不夠平滑 // 一個改進是用很多小圓點填充 // ctx.beginPath(); // ctx.arc(currentX, currentY, currentWidth / 2, 0, Math.PI * 2); // ctx.fillStyle = color; // ctx.fill(); // 但這裡我們先嘗試用 lineTo ctx.lineTo(currentX, currentY); } } ctx.stroke(); // 一次性描邊整個路徑 } // 使用範例 // drawPressureLine(ctx, 300, 50, 450, 150, 15, 100, 'purple');注意: drawPressureLine 如果線寬變化劇烈,單純用 lineTo 和 stroke 可能在連接處不夠平滑,用小圓點填充會更好,但效能開銷更大。
最簡單直接的錐形: 使用方法 1 (繪製四邊形並填充)。更平滑的筆觸感: 方法 2 (使用貝茲曲線填充),但控制點計算複雜。模擬筆刷壓力: 方法 3 (逐段改變 lineWidth),靈活但可能需要更多調整以達到平滑效果。
留言
張貼留言