Created
January 19, 2026 15:47
-
-
Save tompng/1e5e072b83a51fea96b870eef4b95ac9 to your computer and use it in GitHub Desktop.
Render RubyKaigi 2026 logo to canvas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <canvas width="1600" height="1600" style="background:black"></canvas> | |
| <script> | |
| const canvas = document.querySelector('canvas') | |
| const ctx = canvas.getContext('2d') | |
| const outer = [...new Array(30)].map((_, i) => { | |
| const rot = 0.00 | |
| const th1 = Math.PI * 2 * (i - i % 6) / 30 + rot | |
| const th2 = th1 + Math.PI * 2 / 5 | |
| const th = th1 + [0, 0.335, 0.34, 0.5, 0.66, 0.665][i % 6] * (th2 - th1) | |
| const r = [1, 0.71, 0.62, 0.65, 0.62, 0.71][i % 6] | |
| return { x: r * Math.cos(th), y: r * Math.sin(th) } | |
| }) | |
| const outerXs = outer.map(p => p.x) | |
| const outerYs = outer.map(p => p.y) | |
| const offset = 16 | |
| const xmin = Math.min(...outerXs), xmax = Math.max(...outerXs) | |
| const ymin = Math.min(...outerYs), ymax = Math.max(...outerYs) | |
| const scale = Math.min((canvas.width - offset * 2) / (xmax - xmin), (canvas.height - offset * 2) / (ymax - ymin)) | |
| console.log(canvas.width / (xmax - xmin), canvas.height / (ymax - ymin)) | |
| ctx.translate((canvas.width - scale * (xmax - xmin)) / 2, (canvas.height - scale * (ymax - ymin)) / 2) | |
| ctx.scale(scale, scale) | |
| ctx.translate(-xmin, -ymin) | |
| ctx.translate(0.003, -0.001) | |
| const rubyParam = { x: 0.026, y: -0.01, centerWidth: 0.555, topLen: 0.18 } | |
| rubyParam.bottomLen = rubyParam.centerWidth / 2 * (outer[18].y - rubyParam.y) / (outer[18].x - (rubyParam.x - rubyParam.centerWidth / 2)) | |
| rubyParam.topWidth = rubyParam.centerWidth + 2 * rubyParam.topLen * (outer[24].x - (rubyParam.x - rubyParam.centerWidth / 2)) / (outer[24].y - rubyParam.y) | |
| ctx.fillStyle = 'white' | |
| const rubyCoords = [ | |
| { x: rubyParam.x - rubyParam.centerWidth / 2, y: rubyParam.y }, | |
| { x: rubyParam.x - rubyParam.topWidth / 2, y: rubyParam.y - rubyParam.topLen }, | |
| { x: rubyParam.x + rubyParam.topWidth / 2, y: rubyParam.y - rubyParam.topLen }, | |
| { x: rubyParam.x + rubyParam.centerWidth / 2, y: rubyParam.y }, | |
| { x: rubyParam.x, y: rubyParam.y + rubyParam.bottomLen }, | |
| ] | |
| ctx.beginPath() | |
| ctx.moveTo(outer[0].x, outer[0].y) | |
| for (let i = 1; i < outer.length; i++) { | |
| ctx.lineTo(outer[i].x, outer[i].y) | |
| } | |
| ctx.closePath() | |
| ctx.clip() | |
| function normalize(v) { | |
| const l = Math.hypot(v.x, v.y) | |
| return { x: v.x / l, y: v.y / l } | |
| } | |
| ctx.beginPath() | |
| for (let i = 0; i < 5; i++) { | |
| const distAdjust = [1, 1.01, 1.01, 0.997, 1][i] | |
| const a = rubyCoords[i] | |
| const start = rubyCoords[(i + 1) % 5] | |
| const c = rubyCoords[(i + 2) % 5] | |
| const lineDir = normalize({ x: c.x - start.x, y: c.y - start.y }) | |
| const dir = normalize({ x: start.x - a.x, y: start.y - a.y }) | |
| const dot = lineDir.x * dir.x + lineDir.y * dir.y | |
| const dist = 0.02134 / Math.sqrt(1 - dot * dot) | |
| const len = 1.5 | |
| for (let j = 0; ; j++) { | |
| const t = j * dist * distAdjust | |
| const dtmin = dist / 10 | |
| const dt = dist * 0.5 * Math.exp(-j/16.0) + dtmin | |
| const sx = start.x + dir.x * t, sy = start.y + dir.y * t | |
| if (Math.hypot(sx, sy) > 1.2) break | |
| ctx.moveTo(sx, sy) | |
| ctx.lineTo(start.x + dir.x * (t+dt), start.y + dir.y * (t+dt)) | |
| ctx.bezierCurveTo( | |
| start.x + dir.x * (t + dtmin + (dt - dtmin) / 4) + len * lineDir.x / 4, | |
| start.y + dir.y * (t + dtmin + (dt - dtmin) / 4) + len * lineDir.y / 4, | |
| start.x + dir.x * (t + dtmin + (dt - dtmin) / 8) + len * lineDir.x / 2, | |
| start.y + dir.y * (t + dtmin + (dt - dtmin) / 8) + len * lineDir.y / 2, | |
| start.x + dir.x * (t + dtmin) + len * lineDir.x, | |
| start.y + dir.y * (t + dtmin) + len * lineDir.y | |
| ) | |
| ctx.lineTo(start.x + dir.x * t + len * lineDir.x, start.y + dir.y * t + len * lineDir.y) | |
| ctx.closePath() | |
| } | |
| } | |
| ctx.fill() | |
| </script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment