collin/mahjong · c1de3f15
Cut each face out of the sheet, so a turned tile stops wearing a blue line
Collin Richards · 2026-08-21 20:04 UTC · c1de3f155bd40675a20193a465ff3683cf1015df · parent 5d3e6b14 · browse files
modifiedREADME.md+7 −0
| ⋯ 446 unchanged lines | |||
| 447 | 447 | the only way to know is to look at it and move something. (`tiles.html` is not | |
| 448 | 448 | one of the build's entry points, so it does not ship.) | |
| 449 | 449 | ||
| 450 | + | Each cell of the sheet is cut out into its own canvas the first time it is | |
| 451 | + | wanted. `drawImage` with a source rectangle is allowed to sample past that | |
| 452 | + | rectangle when it resamples, and the row under every face is the highlighted | |
| 453 | + | variant, whose border is bright blue — so a tile at any angle other than a right | |
| 454 | + | one wore a blue fringe along one edge. Square on it never showed, which is why | |
| 455 | + | it looked like a rotation bug rather than a spritesheet one. | |
| 456 | + | ||
| 450 | 457 | A tile used to be drawn as two passes — a slab of ivory offset down the screen | |
| 451 | 458 | with the face on top, so a sliver of the tile's own side showed along the bottom | |
| 452 | 459 | edge. At the size a discard is actually drawn that never read as thickness; it | |
| ⋯ 156 unchanged lines | |||
modifiedsrc/ui/tileArt.ts+52 −6
| ⋯ 121 unchanged lines | |||
| 122 | 122 | } | |
| 123 | 123 | ||
| 124 | 124 | /** | |
| 125 | + | * One cell of the sheet, cut out and kept. | |
| 126 | + | * | |
| 127 | + | * `drawImage` with a source rectangle is allowed to sample *past* that | |
| 128 | + | * rectangle when it resamples, and the row under every face is the sheet's own | |
| 129 | + | * highlighted variant, whose border is bright blue. Square on and at a right | |
| 130 | + | * angle nothing resamples across that boundary and all is well; at any other | |
| 131 | + | * angle the blue is dragged in and every turned tile wears a blue fringe along | |
| 132 | + | * one edge. Measured, an arbitrary rotation took the worst blue on the tile | |
| 133 | + | * from 92 — which is the tile's own ink — to 255. | |
| 134 | + | * | |
| 135 | + | * Cut out once at native size there is nothing beside it to bleed, and it is | |
| 136 | + | * the same 92 at every angle and every size. | |
| 137 | + | * | |
| 138 | + | * 43 columns, two rows; the second is the highlighted variant, which is what a | |
| 139 | + | * lifted tile in a hand uses. The tile in your fingers and the one still to be | |
| 140 | + | * claimed get it here for the same reason. | |
| 141 | + | */ | |
| 142 | + | const cells = new Map<string, HTMLCanvasElement>(); | |
| 143 | + | let cellsFrom: HTMLImageElement | null = null; | |
| 144 | + | ||
| 145 | + | function cellOf(sheet: HTMLImageElement, tile: number, lit: boolean): HTMLCanvasElement { | |
| 146 | + | // A different sheet — a hot reload, in practice — is a different set of cells. | |
| 147 | + | if (cellsFrom !== sheet) { | |
| 148 | + | cells.clear(); | |
| 149 | + | cellsFrom = sheet; | |
| 150 | + | } | |
| 151 | + | const key = `${tile}:${lit}`; | |
| 152 | + | const had = cells.get(key); | |
| 153 | + | if (had) return had; | |
| 154 | + | ||
| 155 | + | const cw = sheet.naturalWidth / SPRITE_COLS; | |
| 156 | + | const ch = sheet.naturalHeight / 2; | |
| 157 | + | const c = document.createElement('canvas'); | |
| 158 | + | c.width = Math.round(cw); | |
| 159 | + | c.height = Math.round(ch); | |
| 160 | + | c.getContext('2d')?.drawImage( | |
| 161 | + | sheet, | |
| 162 | + | SPRITE_COL[tile] * cw, | |
| 163 | + | lit ? ch : 0, | |
| 164 | + | cw, | |
| 165 | + | ch, | |
| 166 | + | 0, | |
| 167 | + | 0, | |
| 168 | + | c.width, | |
| 169 | + | c.height, | |
| 170 | + | ); | |
| 171 | + | cells.set(key, c); | |
| 172 | + | return c; | |
| 173 | + | } | |
| 174 | + | ||
| 175 | + | /** | |
| 125 | 176 | * A soft silhouette under a tile. | |
| 126 | 177 | * | |
| 127 | 178 | * The blur is baked into a sprite once per tile size rather than applied per | |
| ⋯ 109 unchanged lines | |||
| 237 | 288 | } | |
| 238 | 289 | ||
| 239 | 290 | if (art.faces) { | |
| 240 | - | // 43 columns, two rows; the second is the sheet's own highlighted variant, | |
| 241 | - | // which is what a lifted tile in a hand uses. The tile in your fingers and | |
| 242 | - | // the one still to be claimed get it here for the same reason. | |
| 243 | - | const cw = art.faces.naturalWidth / SPRITE_COLS; | |
| 244 | - | const ch = art.faces.naturalHeight / 2; | |
| 245 | - | ctx.drawImage(art.faces, SPRITE_COL[tile] * cw, lit ? ch : 0, cw, ch, -w / 2, -h / 2, w, h); | |
| 291 | + | ctx.drawImage(cellOf(art.faces, tile, lit), -w / 2, -h / 2, w, h); | |
| 246 | 292 | } else { | |
| 247 | 293 | tilePath(ctx, w, h); | |
| 248 | 294 | ctx.fillStyle = '#f6f0e2'; | |
| ⋯ 20 unchanged lines | |||