diff --git a/.github/workflows/contributors-png.yml b/.github/workflows/contributors-png.yml new file mode 100644 index 0000000000..79933b44a4 --- /dev/null +++ b/.github/workflows/contributors-png.yml @@ -0,0 +1,40 @@ +name: Generate Contributors PNG + +on: + push: + paths: + - '.all-contributorsrc' + +jobs: + build: + if: github.ref == 'refs/heads/main' && github.repository == 'processing/p5.js' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install canvas + + - name: Run contributors-png generator + run: node utils/contributors-png.js + + - name: Reset all changes except contributors.png + run: | + git restore --staged . + git add contributors.png + git checkout -- . + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + commit-message: "Update contributors.png from .all-contributorsrc" + branch: update-contributors-png + title: "chore: update contributors.png from .all-contributorsrc" + body: "This PR updates the contributors.png to reflect changes in .all-contributorsrc" + add-paths: contributors.png + token: ${{ secrets.ACCESS_TOKEN }} diff --git a/utils/contributors-png.js b/utils/contributors-png.js new file mode 100644 index 0000000000..2686f1685f --- /dev/null +++ b/utils/contributors-png.js @@ -0,0 +1,61 @@ +const { createCanvas, loadImage } = require('canvas'); +const fs = require('fs'); + +const data = fs.readFileSync('.all-contributorsrc', 'utf-8'); +const parsed = JSON.parse(data); +const contributors = parsed.contributors; + +const AVATAR_SIZE = 50; +const GAP = 4; +const COLS = 40; +const ROWS = Math.ceil(contributors.length / COLS); + +const width = COLS * AVATAR_SIZE + (COLS - 1) * GAP; +const height = ROWS * AVATAR_SIZE + (ROWS - 1) * GAP; + +const canvas = createCanvas(width, height); +const ctx = canvas.getContext('2d'); + +async function loadAvatar(url) { + try { + const res = await fetch(url); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + + const buffer = Buffer.from(await res.arrayBuffer()); + return await loadImage(buffer); + } catch (err) { + return null; + } +} + +(async () => { + for (let i = 0; i < contributors.length; i++) { + const c = contributors[i]; + + const col = i % COLS; + const row = Math.floor(i / COLS); + + const x = col * (AVATAR_SIZE + GAP); + const y = row * (AVATAR_SIZE + GAP); + + const img = await loadAvatar(c.avatar_url); + + ctx.save(); + ctx.beginPath(); + ctx.arc( + x + AVATAR_SIZE / 2, + y + AVATAR_SIZE / 2, + AVATAR_SIZE / 2, + 0, + Math.PI * 2 + ); + ctx.clip(); + + if (img) { + ctx.drawImage(img, x, y, AVATAR_SIZE, AVATAR_SIZE); + } + ctx.restore(); + } + + fs.writeFileSync('contributors.png', canvas.toBuffer('image/png')); +})();