const renderFox = function (canvas, opts) { const width = opts.canvas.width; const height = opts.canvas.height; const ctx = canvas.getContext('2d'); ctx.fillStyle = opts.canvas.color; ctx.fillRect(0, 0, width, height); renderEars(ctx, opts.ears); renderHead(ctx, opts.head); renderEyes(ctx, opts.eyes); renderNose(ctx, opts.nose); renderMouth(ctx, opts.mouth); shift_canvas(ctx, width, height, 0, 0.06 * height); }; function shift_canvas(ctx, w, h, dx, dy) { const topImage = ctx.getImageData(0, 0, w, h); const bottomImage = ctx.getImageData(0, h - dy, w, h); ctx.clearRect(0, 0, w, h); ctx.putImageData(bottomImage, 0, 0); ctx.putImageData(topImage, dx, dy); } function renderHead(ctx, opts) { ctx.save(); ctx.translate(ctx.canvas.width/2, ctx.canvas.height/2); ctx.rotate(Math.PI / 4); drawEllipseByCenter(ctx, 0, 0, opts.width, opts.height, opts.color, null, opts.kappa); ctx.restore(); ctx.clip(); drawEllipseByCenter(ctx, ctx.canvas.width / 2, ctx.canvas.height, opts.mask.width, opts.mask.height, '#fff', '#fff', 0.5); } function renderEars(ctx, opts) { const offset = { x: ctx.canvas.width/2, y: ctx.canvas.height/2 } ctx.save(); ctx.translate(offset.x, offset.y); ctx.rotate(-opts.left.angle); drawEllipseByCenter(ctx, opts.left.x - offset.x, opts.left.y - offset.y, opts.left.width, opts.left.height, opts.color, null, opts.kappa); ctx.restore(); ctx.save(); ctx.translate(offset.x, offset.y); ctx.rotate(-opts.right.angle); drawEllipseByCenter(ctx, opts.right.x - offset.x, opts.right.y - offset.y, opts.right.width, opts.right.height, opts.color, null, opts.kappa); ctx.restore(); } function renderEyes(ctx, opts) { switch (opts.style) { case "ellipse": drawEllipseByCenter(ctx, opts.left.x, opts.left.y, opts.width, opts.height, "black", null, 0.5); drawEllipseByCenter(ctx, opts.right.x, opts.right.y, opts.width, opts.height, "black", null, 0.5); break; case "smiley": ctx.strokeStyle = "black"; ctx.beginPath(); ctx.moveTo(opts.left.x - opts.width, opts.left.y + opts.height); ctx.bezierCurveTo(opts.left.x - opts.width, opts.left.y + opts.height, opts.left.x, opts.left.y, opts.left.x + opts.width, opts.left.y + opts.height); ctx.lineWidth = 2; ctx.stroke(); ctx.closePath(); ctx.beginPath(); ctx.moveTo(opts.right.x - opts.width, opts.right.y + opts.height); ctx.bezierCurveTo(opts.right.x - opts.width, opts.right.y + opts.height, opts.right.x, opts.right.y, opts.right.x + opts.width, opts.right.y + opts.height); ctx.lineWidth = 2; ctx.stroke(); ctx.closePath(); break; case "none": break; } } function renderNose(ctx, opts) { ctx.strokeStyle = "black"; ctx.beginPath(); ctx.moveTo(opts.x - opts.width/2, opts.y - opts.height/2); ctx.bezierCurveTo(opts.x - opts.width/2, opts.y - opts.height/2, opts.x, opts.y - opts.height, opts.x + opts.width/2, opts.y - opts.height/2); ctx.bezierCurveTo(opts.x + opts.width/2, opts.y - opts.height/2, opts.x + opts.width/2, opts.y + opts.height/2, opts.x, opts.y + opts.height/2); ctx.bezierCurveTo(opts.x, opts.y + opts.height/2, opts.x - opts.width/2, opts.y + opts.height/2, opts.x - opts.width/2, opts.y - opts.height/2); ctx.fillStyle = "black"; ctx.fill(); ctx.stroke(); } function renderMouth(ctx, opts) { ctx.strokeStyle = "black"; ctx.lineWidth = 0.01 * ctx.canvas.width; ctx.beginPath(); switch (opts.style) { case "smirk": ctx.moveTo(opts.x - opts.width/2, opts.y - opts.height/2); ctx.bezierCurveTo(opts.x - opts.width/2, opts.y - opts.height/2, opts.x - opts.width/2, opts.y + opts.height/2, opts.x + opts.width/2, opts.y ) break; case "cat": ctx.moveTo(opts.x - opts.width/2, opts.y + opts.height/2); ctx.bezierCurveTo(opts.x - opts.width/2, opts.y + opts.height/2, opts.x, opts.y + opts.height/2, opts.x, opts.y - opts.height/2 ) ctx.bezierCurveTo( opts.x, opts.y - opts.height/2, opts.x, opts.y + opts.height/2, opts.x + opts.width/2, opts.y + opts.height/2 ) break; } ctx.stroke(); } function drawEllipseByCenter(ctx, cx, cy, w, h, color, fillColor, kappa) { drawEllipse(ctx, cx - w/2.0, cy - h/2.0, w, h, color, fillColor, kappa); } function drawEllipse(ctx, x, y, w, h, color, fillColor, kappa=0.3) { const ox = (w / 2) * kappa; // control point offset horizontal const oy = (h / 2) * kappa; // control point offset vertical const xe = x + w; // x-end const ye = y + h; // y-end const xm = x + w / 2; // x-middle const ym = y + h / 2; // y-middle if (color) { ctx.strokeStyle = color; } ctx.beginPath(); ctx.moveTo(x, ym); ctx.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y); ctx.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym); ctx.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye); ctx.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym); fillColor = fillColor || color; if (fillColor) { ctx.fillStyle = fillColor; ctx.fill(); } ctx.stroke(); } module.exports = renderFox;