Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
e93c770
feat: declarative shader pipeline, performance optimization, animated…
LuciNyan Apr 11, 2026
53551aa
perf: inline render() for curve, dithering, pixelate, halftone shaders
LuciNyan Apr 11, 2026
a3fae59
perf: add prefix-sum early-out to glow shader blur passes
LuciNyan Apr 11, 2026
b21e121
perf: eliminate PNG encode/decode roundtrip in render pipeline
LuciNyan Apr 11, 2026
9c1a04d
perf: eliminate PNG roundtrip in border blending
LuciNyan Apr 11, 2026
2e342fd
perf: pre-compute block colors for dominant/average pixelation modes
LuciNyan Apr 11, 2026
6c94646
perf: inline bilinear sampling in CRT and curve shaders
LuciNyan Apr 11, 2026
44e50a4
perf: replace Jimp PNG encoding with direct zlib-based encoder
LuciNyan Apr 11, 2026
2726dcc
perf: replace Math.pow(x, 0.25) with Math.sqrt(Math.sqrt(x)) in curve…
LuciNyan Apr 11, 2026
b1be028
perf: replace redundant Math.min/Math.max/Math.floor with bitwise |0 …
LuciNyan Apr 11, 2026
b12f9fe
perf: optimize halftone and orderedBayer shaders
LuciNyan Apr 11, 2026
1280778
refactor: remove dead render() framework and texture-filter code
LuciNyan Apr 11, 2026
98df05f
perf(pixelate): inline bilinear + eliminate Map<string> in dominant m…
LuciNyan Apr 11, 2026
417fc65
perf(png): use deflate level 1 for faster PNG encoding (13%↑)
LuciNyan Apr 11, 2026
839fd0b
perf(dithering): cache HSL for consecutive identical pixels (20%↑)
LuciNyan Apr 11, 2026
d1f54ef
perf(crt): optimize bloom with hoisted weights + interior fast path (…
LuciNyan Apr 11, 2026
61ef3e0
perf(pixelate,glow): inline bilinear in pixelateCenter + hoist divisi…
LuciNyan Apr 11, 2026
45abbee
perf(glow): interior+all-bright fast path + reuse luminance buffer
LuciNyan Apr 11, 2026
e9d08ea
perf: eliminate zero-fill overhead with Buffer.allocUnsafe + PNG enco…
LuciNyan Apr 11, 2026
4707e66
perf(dithering): pre-compute dither combo cache per unique RGB (28%↑)
LuciNyan Apr 11, 2026
b0cca3b
perf(pixelate): row-copy fill eliminates per-pixel writes (72%↑ center)
LuciNyan Apr 11, 2026
4055cc7
perf(png-encoder): single-buffer output eliminates concat + allocatio…
LuciNyan Apr 11, 2026
c03c31b
perf: zero-copy Buffer wrapping for Resvg pixels and Jimp bitmap
LuciNyan Apr 11, 2026
1856df6
perf(curve,crt): factor out bilinear row/col offsets (curve 11%↑, crt…
LuciNyan Apr 11, 2026
dd9224d
perf(halftone,glow): Uint32 writes + inline luminance + blend precompute
LuciNyan Apr 11, 2026
a94c053
perf: cache border image decode and precompute alpha in blendBorder
LuciNyan Apr 11, 2026
db4c2ec
perf(gif): rewrite GIF encoder with 8.3x speedup
LuciNyan Apr 11, 2026
41d7379
feat: expose animated GIF output via ?animation=true API parameter
LuciNyan Apr 11, 2026
6dfc4ed
feat: custom palette dithering with 6 built-in palettes + fix TS 5.8 …
LuciNyan Apr 11, 2026
e699d46
feat: contribution heatmap card — pixel-art GitHub contribution calendar
LuciNyan Apr 11, 2026
7ca242b
fix: resolve all tsc --noEmit type errors for TS 5.8 compatibility
LuciNyan Apr 11, 2026
804e038
perf(contributions): pre-render grid as pixel image, bypass Satori fo…
LuciNyan Apr 11, 2026
5ecd4bf
perf: in-place processing for point shaders (scanline, orderedBayer, …
LuciNyan Apr 11, 2026
4821c63
feat: GitHub Repo Card (/api/github-repo)
LuciNyan Apr 11, 2026
e847e5e
perf: Worker thread parallelization for CRT shader (4x speedup)
LuciNyan Apr 11, 2026
ae64212
perf: Worker thread parallelization for glow shader (up to 2.4x speedup)
LuciNyan Apr 11, 2026
8e34a82
perf: async animation pipeline with Worker parallelization (up to 2.1x)
LuciNyan Apr 11, 2026
64f481b
perf: optimize GIF encoder with array-based color lookup (39% faster)
LuciNyan Apr 11, 2026
0964ae7
perf: precompute curve vignette with row/col factoring (21% faster)
LuciNyan Apr 11, 2026
bf83c07
perf: Worker thread parallelization for curve shader (2.3x faster)
LuciNyan Apr 11, 2026
22c7525
perf: row-parallel glow dispatch replaces layer-parallel (21% faster)
LuciNyan Apr 11, 2026
911a01e
perf: merge glow vertical pass + composite into single worker dispatch
LuciNyan Apr 11, 2026
70cda05
perf: zero-copy SharedArrayBuffer returns from worker dispatches
LuciNyan Apr 11, 2026
9ce60a7
perf: reuse SharedArrayBuffer when source is already SAB-backed
LuciNyan Apr 11, 2026
3546f84
perf: precompute glow layers for glow-pulse animation (3.5x faster)
LuciNyan Apr 11, 2026
6463d03
refactor: remove contribution heatmap and repo card features, add ani…
LuciNyan Apr 11, 2026
fcf6094
perf: optimize GIF encoder palette/LUT generation
LuciNyan Apr 11, 2026
7dd984e
fix: add worker pool readiness check with timeout fallback
LuciNyan Apr 11, 2026
475b131
perf: optimize scanline-scroll and crt-flicker animations
LuciNyan Apr 11, 2026
e5f1d5d
perf: hoist glow composite out of crt-flicker frame loop
LuciNyan Apr 11, 2026
2f6c8a2
perf: optimize glow shader — shared luminance, precomputed colPrefix,…
LuciNyan Apr 14, 2026
ea25509
perf: fused multi-layer horizontal pass for glow shader
LuciNyan Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ pnpm start
### Roadmap
- [x] GitHub stats card
- [x] CRT-style stats card
- [x] Animated GIF output (crt-flicker, glow-pulse, scanline-scroll)

## Author

Expand Down
91 changes: 91 additions & 0 deletions bench.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { crt, curve, pixelate } from './packages/pixel-profile/src/shaders/index.ts'
import { glow } from './packages/pixel-profile/src/shaders/glow.ts'
import { scanline } from './packages/pixel-profile/src/shaders/scanline.ts'
import { orderedBayer } from './packages/pixel-profile/src/shaders/dithering.ts'
import { executePipelineSmart, buildStatsPipeline, buildCrtPipeline } from './packages/pixel-profile/src/pipeline.ts'
import { dispatchCrt, dispatchGlow, dispatchCurve, shutdownPool } from './packages/pixel-profile/src/workers/pool.ts'

const W = 1226, H = 430
const size = W * H * 4
const pixels = Buffer.alloc(size)

// Simulate realistic pixel data (dark background + bright text areas)
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
const idx = (y * W + x) * 4
const isBright = (x > 100 && x < 400 && y > 50 && y < 200) ||
(x > 500 && x < 900 && y > 100 && y < 300)
if (isBright) {
pixels[idx] = 200 + Math.random() * 55 | 0
pixels[idx+1] = 200 + Math.random() * 55 | 0
pixels[idx+2] = 200 + Math.random() * 55 | 0
} else {
pixels[idx] = Math.random() * 30 | 0
pixels[idx+1] = Math.random() * 30 | 0
pixels[idx+2] = Math.random() * 30 | 0
}
pixels[idx+3] = 255
}
}

function bench(name, fn, iterations = 5) {
// Warmup
fn()
const times = []
for (let i = 0; i < iterations; i++) {
const start = performance.now()
fn()
times.push(performance.now() - start)
}
times.sort((a, b) => a - b)
const median = times[Math.floor(times.length / 2)]
const min = times[0]
const max = times[times.length - 1]
console.log(`${name.padEnd(35)} median=${median.toFixed(1)}ms min=${min.toFixed(1)}ms max=${max.toFixed(1)}ms`)
}

async function benchAsync(name, fn, iterations = 5) {
await fn()
const times = []
for (let i = 0; i < iterations; i++) {
const start = performance.now()
await fn()
times.push(performance.now() - start)
}
times.sort((a, b) => a - b)
const median = times[Math.floor(times.length / 2)]
const min = times[0]
const max = times[times.length - 1]
console.log(`${name.padEnd(35)} median=${median.toFixed(1)}ms min=${min.toFixed(1)}ms max=${max.toFixed(1)}ms`)
}

console.log(`\nBenchmark: ${W}x${H} (${(size/1024/1024).toFixed(1)}MB)\n`)
console.log('=== Individual Shaders (sync) ===')

bench('crt (sync)', () => crt(pixels, W, H))
bench('glow r3 l2 (sync)', () => glow(pixels, W, H, { radius: 3, intensity: 0.3, color: [1,1,1], layers: 2, falloff: 'exponential' }))
bench('glow r5 l5 (sync)', () => glow(pixels, W, H, { radius: 5, intensity: 0.17, color: [1,1,1], layers: 5, falloff: 'exponential' }))
bench('curve (sync)', () => curve(pixels, W, H))
bench('pixelate center', () => pixelate(pixels, W, H, { blockSize: 4, samplingMode: 'center' }))
bench('pixelate dominant', () => pixelate(pixels, W, H, { blockSize: 4, samplingMode: 'dominant' }))
bench('scanline', () => scanline(pixels, W, H))
bench('orderedBayer', () => orderedBayer(pixels, W, H))

console.log('\n=== Worker Parallel Shaders ===')
await benchAsync('crt (parallel)', () => dispatchCrt(pixels, W, H, {}))
await benchAsync('glow r3 l2 (parallel)', () => dispatchGlow(pixels, W, H, { radius: 3, intensity: 0.3, color: [1,1,1], layers: 2, falloff: 'exponential' }))
await benchAsync('glow r5 l5 (parallel)', () => dispatchGlow(pixels, W, H, { radius: 5, intensity: 0.17, color: [1,1,1], layers: 5, falloff: 'exponential' }))
await benchAsync('curve (parallel)', () => dispatchCurve(pixels, W, H))

console.log('\n=== Full Pipelines (async/smart) ===')
const screenPipeline = buildStatsPipeline({ theme: 'default', screenEffect: true, isFastMode: false, dithering: false })
await benchAsync('screen effect pipeline', () => executePipelineSmart(pixels, W, H, screenPipeline))

const crtPipeline = buildCrtPipeline()
await benchAsync('CRT pipeline (l1)', () => executePipelineSmart(pixels, W, H, crtPipeline))

const crtThemePipeline = buildStatsPipeline({ theme: 'crt', screenEffect: false, isFastMode: false, dithering: false })
await benchAsync('CRT theme pipeline (l5)', () => executePipelineSmart(pixels, W, H, crtThemePipeline))

shutdownPool()
console.log('\nDone.')
24 changes: 20 additions & 4 deletions packages/pixel-profile-server/src/github-stats.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { CONSTANTS, parseArray, parseBoolean, parseString } from './utils'
import { Hono } from 'hono'
import { clamp, fetchStats, renderStats } from 'pixel-profile'
import { type AnimationOptions, clamp, fetchStats, renderStats } from 'pixel-profile'

const githubStats = new Hono()

githubStats.get('/', async (c) => {
const { req, res, body } = c
const {
animation,
animation_effect,
background,
cache_seconds = `${CONSTANTS.CARD_CACHE_SECONDS}`,
color,
Expand All @@ -19,10 +21,12 @@ githubStats.get('/', async (c) => {
username,
theme,
avatar_border,
dithering
dithering,
dithering_palette
} = req.query()

res.headers.set('Content-Type', 'image/png')
const isAnimated = parseBoolean(animation)
res.headers.set('Content-Type', isAnimated ? 'image/gif' : 'image/png')

try {
const showStats = parseArray(show)
Expand All @@ -46,6 +50,16 @@ githubStats.get('/', async (c) => {
`max-age=${cacheSeconds / 2}, s-maxage=${cacheSeconds}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`
)

let animationOpts: AnimationOptions | boolean | undefined
if (isAnimated) {
const effect = parseString(animation_effect)
if (effect === 'crt-flicker' || effect === 'glow-pulse' || effect === 'scanline-scroll') {
animationOpts = { effect }
} else {
animationOpts = true
}
}

const options = {
background: parseString(background),
color: parseString(color),
Expand All @@ -55,7 +69,9 @@ githubStats.get('/', async (c) => {
theme: parseString(theme),
screenEffect: parseBoolean(screen_effect),
avatarBorder: parseBoolean(avatar_border),
dithering: parseBoolean(dithering)
dithering: parseBoolean(dithering),
ditheringPalette: parseString(dithering_palette),
animation: animationOpts
}

const result = await renderStats(stats, options)
Expand Down
4 changes: 2 additions & 2 deletions packages/pixel-profile/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ The layout in this project is entirely done with JSX, so developing it is almost
### TODO

- [x] Github stats card.
- [ ] Github repo card.
- [ ] Leetcode stats card.
- [x] CRT-style stats card.
- [x] Animated GIF output.

## Author

Expand Down
Loading
Loading