busybar-kit 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 viirtualp1
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 viirtualp1
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,148 +1,148 @@
1
- # busybar-kit
2
-
3
- > [!IMPORTANT]
4
- > **Unofficial community project.** Built and maintained by [@viirtualp1](https://github.com/viirtualp1), **not** an official Flipper Devices / BUSY product, and not affiliated with, endorsed by, or supported by them. "BUSY Bar" remains their trademark. For the real hardware and official apps, visit **[busy.app](https://busy.app/)**.
5
-
6
- Shared building blocks for [BUSY Bar](https://busy.bar) apps. Everything here is
7
- app-agnostic: screen geometry, the palette neutrals, formatters, ticker text,
8
- the Bar's error taxonomy, the connection half of a config, and an offline PNG
9
- renderer that draws the same elements the device does.
10
-
11
- It is the common half of [busybar-dota](https://github.com/viirtualp1/busybar-dota),
12
- [busybar-mydota](https://github.com/viirtualp1/busybar-mydota) and
13
- [busybar-livesplit](https://github.com/viirtualp1/busybar-livesplit).
14
-
15
- ```bash
16
- npm install busybar-kit
17
- ```
18
-
19
- `@busy-app/busy-lib` is a peer dependency — the app owns that version.
20
-
21
- ## What is in it
22
-
23
- | Import | What it gives you |
24
- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
25
- | `busybar-kit/device` | `FRONT` / `BACK` screen sizes, `FONT_WIDTH`, `textWidth`, `clipToWidth`, `fittingChars`, `rowY`, `backRowGrid` |
26
- | `busybar-kit/colors` | `BASE_COLORS` — neutrals, chrome, and the transparent value that hides an element |
27
- | `busybar-kit/format` | `formatClock`, `formatGold`, `formatCountdown`, `formatStartTime`, `formatKda`, `formatPercent`, … |
28
- | `busybar-kit/elements` | `AnyElement`, `band()`, `fillWidth()` |
29
- | `busybar-kit/ticker` | `paginate` / `pageAt` / `scrollAt` / `tickerLine`, and `EventTicker` |
30
- | `busybar-kit/errors` | `BarApiError`, `toBarError`, `isLowPriority`, `isClientError`, `isForbidden` |
31
- | `busybar-kit/config` | `loadEnvFile`, `envReader`, `loadBarConfig` |
32
- | `busybar-kit/image` | `ditherToPanel` — a picture, in the back panel's 16 greys |
33
- | `busybar-kit/preview` | `renderFront` / `renderBack` / `Bitmap` — device-accurate PNGs with no hardware |
34
- | `busybar-kit/tsconfig.json`, `busybar-kit/eslint`, `busybar-kit/prettier` | the shared toolchain |
35
- | `busybar-fix-esm` (bin) | rewrites relative imports in `dist/` to carry `.js`, for Node ESM |
36
-
37
- ## Geometry is hardware, not layout
38
-
39
- `FRONT` (72×16) and `BACK` (160×80, with its header rows and 12px grid) are
40
- facts about the device. An app spreads its own named slots on top:
41
-
42
- ```ts
43
- import { FRONT as DEVICE, BACK as DEVICE_BACK } from 'busybar-kit/device';
44
-
45
- export const FRONT = {
46
- ...DEVICE,
47
- topY: 0,
48
- bottomY: 11,
49
- clockWidth: 24,
50
- } as const;
51
- ```
52
-
53
- An app whose header is a different height lays out its own row grid, and reads
54
- rows off that instead of the default:
55
-
56
- ```ts
57
- import { backRowGrid, rowY as gridRowY } from 'busybar-kit/device';
58
-
59
- export const BACK = { ...DEVICE_BACK, ...backRowGrid(16), nameX: 8 } as const;
60
-
61
- export const rowY = (index: number) => gridRowY(index, BACK);
62
- ```
63
-
64
- `clipToWidth` takes the truncation mark as a fourth argument (`'.'` by
65
- default, `'..'` where names are cut often enough to want the emphasis).
66
-
67
- Colours work the same way — `BASE_COLORS` holds what every app needs, and the
68
- app adds its own semantics:
69
-
70
- ```ts
71
- import { BASE_COLORS } from 'busybar-kit/colors';
72
-
73
- export const COLORS = {
74
- ...BASE_COLORS,
75
- radiant: '#3FBF5FFF',
76
- dire: '#E14B3AFF',
77
- } as const;
78
- ```
79
-
80
- ## Config
81
-
82
- `loadBarConfig` resolves which Bar to talk to and over which transport, and
83
- warns about credentials that transport will ignore. That is all it covers —
84
- render cadence and request timeouts are the app's own tempo (a speedrun timer
85
- wants 60ms frames where a match ticker is happy at 200), so they stay in the
86
- app's config with `DEFAULTS` / `LIMITS` as the values worth starting from.
87
-
88
- It hands back the same env reader, so an app's own settings collect their
89
- warnings into one list:
90
-
91
- ```ts
92
- import { loadBarConfig, loadEnvFile } from 'busybar-kit/config';
93
-
94
- loadEnvFile();
95
-
96
- export function loadConfig(env = process.env) {
97
- const warnings: string[] = [];
98
- const { bar, env: read } = loadBarConfig(env, warnings);
99
-
100
- return {
101
- warnings,
102
- config: {
103
- ...bar,
104
- gsiPort: read.number('GSI_PORT', 3080, { min: 1024, max: 65_535 }),
105
- },
106
- };
107
- }
108
- ```
109
-
110
- ## Pictures
111
-
112
- The back panel has 16 greys and quantises whatever you upload to them, so a
113
- photo sent as-is comes back in bands. `ditherToPanel` does the quantising here,
114
- with error diffusion, which leaves every pixel already sitting on a level:
115
-
116
- ```ts
117
- import { ditherToPanel } from 'busybar-kit/image';
118
-
119
- const cover = ditherToPanel(decodedRgba); // 80x80 Bitmap, panel-ready
120
- await bar.AssetsUpload({ application_name: 'app', file: 'art.png', data: cover.toPng() });
121
- ```
122
-
123
- It takes pixels, not files. Which image decoder to pay for is the app's
124
- choice — the kit stays dependency-free.
125
-
126
- `Bitmap.fromRgba` wraps pixels from anywhere, and `bitmap.blit(other, x, y)`
127
- draws one into another: the raster below cannot follow an image element's path,
128
- so a preview composites the picture itself.
129
-
130
- ## Preview
131
-
132
- `renderFront` / `renderBack` rasterise the very element list you send to the
133
- device — a 3×5 pixel font, the same anchoring rules, and the back panel
134
- flattened to the 16 greys the real OLED shows. Useful for screenshots in a
135
- README and for asserting a layout in tests without a Bar on the desk.
136
-
137
- ```ts
138
- import { renderFront } from 'busybar-kit/preview';
139
-
140
- writeFileSync('front.png', renderFront(frontElements(frame)).scale(8).toPng());
141
- ```
142
-
143
- ## Scripts
144
-
145
- ```bash
146
- npm run check # lint + typecheck + test
147
- npm run build # tsc + .js extension fixup
148
- ```
1
+ # busybar-kit
2
+
3
+ > [!IMPORTANT]
4
+ > **Unofficial community project.** Built and maintained by [@viirtualp1](https://github.com/viirtualp1), **not** an official Flipper Devices / BUSY product, and not affiliated with, endorsed by, or supported by them. "BUSY Bar" remains their trademark. For the real hardware and official apps, visit **[busy.app](https://busy.app/)**.
5
+
6
+ Shared building blocks for [BUSY Bar](https://busy.bar) apps. Everything here is
7
+ app-agnostic: screen geometry, the palette neutrals, formatters, ticker text,
8
+ the Bar's error taxonomy, the connection half of a config, and an offline PNG
9
+ renderer that draws the same elements the device does.
10
+
11
+ It is the common half of [busybar-dota](https://github.com/viirtualp1/busybar-dota),
12
+ [busybar-mydota](https://github.com/viirtualp1/busybar-mydota) and
13
+ [busybar-livesplit](https://github.com/viirtualp1/busybar-livesplit).
14
+
15
+ ```bash
16
+ npm install busybar-kit
17
+ ```
18
+
19
+ `@busy-app/busy-lib` is a peer dependency — the app owns that version.
20
+
21
+ ## What is in it
22
+
23
+ | Import | What it gives you |
24
+ | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
25
+ | `busybar-kit/device` | `FRONT` / `BACK` screen sizes, `FONT_WIDTH`, `textWidth`, `clipToWidth`, `fittingChars`, `rowY`, `backRowGrid` |
26
+ | `busybar-kit/colors` | `BASE_COLORS` — neutrals, chrome, and the transparent value that hides an element |
27
+ | `busybar-kit/format` | `formatClock`, `formatGold`, `formatCountdown`, `formatStartTime`, `formatKda`, `formatPercent`, … |
28
+ | `busybar-kit/elements` | `AnyElement`, `band()`, `fillWidth()` |
29
+ | `busybar-kit/ticker` | `paginate` / `pageAt` / `scrollAt` / `tickerLine`, and `EventTicker` |
30
+ | `busybar-kit/errors` | `BarApiError`, `toBarError`, `isLowPriority`, `isClientError`, `isForbidden` |
31
+ | `busybar-kit/config` | `loadEnvFile`, `envReader`, `loadBarConfig` |
32
+ | `busybar-kit/image` | `ditherToPanel` — a picture, in the back panel's 16 greys |
33
+ | `busybar-kit/preview` | `renderFront` / `renderBack` / `Bitmap` — device-accurate PNGs with no hardware |
34
+ | `busybar-kit/tsconfig.json`, `busybar-kit/eslint`, `busybar-kit/prettier` | the shared toolchain |
35
+ | `busybar-fix-esm` (bin) | rewrites relative imports in `dist/` to carry `.js`, for Node ESM |
36
+
37
+ ## Geometry is hardware, not layout
38
+
39
+ `FRONT` (72×16) and `BACK` (160×80, with its header rows and 12px grid) are
40
+ facts about the device. An app spreads its own named slots on top:
41
+
42
+ ```ts
43
+ import { FRONT as DEVICE, BACK as DEVICE_BACK } from 'busybar-kit/device';
44
+
45
+ export const FRONT = {
46
+ ...DEVICE,
47
+ topY: 0,
48
+ bottomY: 11,
49
+ clockWidth: 24,
50
+ } as const;
51
+ ```
52
+
53
+ An app whose header is a different height lays out its own row grid, and reads
54
+ rows off that instead of the default:
55
+
56
+ ```ts
57
+ import { backRowGrid, rowY as gridRowY } from 'busybar-kit/device';
58
+
59
+ export const BACK = { ...DEVICE_BACK, ...backRowGrid(16), nameX: 8 } as const;
60
+
61
+ export const rowY = (index: number) => gridRowY(index, BACK);
62
+ ```
63
+
64
+ `clipToWidth` takes the truncation mark as a fourth argument (`'.'` by
65
+ default, `'..'` where names are cut often enough to want the emphasis).
66
+
67
+ Colours work the same way — `BASE_COLORS` holds what every app needs, and the
68
+ app adds its own semantics:
69
+
70
+ ```ts
71
+ import { BASE_COLORS } from 'busybar-kit/colors';
72
+
73
+ export const COLORS = {
74
+ ...BASE_COLORS,
75
+ radiant: '#3FBF5FFF',
76
+ dire: '#E14B3AFF',
77
+ } as const;
78
+ ```
79
+
80
+ ## Config
81
+
82
+ `loadBarConfig` resolves which Bar to talk to and over which transport, and
83
+ warns about credentials that transport will ignore. That is all it covers —
84
+ render cadence and request timeouts are the app's own tempo (a speedrun timer
85
+ wants 60ms frames where a match ticker is happy at 200), so they stay in the
86
+ app's config with `DEFAULTS` / `LIMITS` as the values worth starting from.
87
+
88
+ It hands back the same env reader, so an app's own settings collect their
89
+ warnings into one list:
90
+
91
+ ```ts
92
+ import { loadBarConfig, loadEnvFile } from 'busybar-kit/config';
93
+
94
+ loadEnvFile();
95
+
96
+ export function loadConfig(env = process.env) {
97
+ const warnings: string[] = [];
98
+ const { bar, env: read } = loadBarConfig(env, warnings);
99
+
100
+ return {
101
+ warnings,
102
+ config: {
103
+ ...bar,
104
+ gsiPort: read.number('GSI_PORT', 3080, { min: 1024, max: 65_535 }),
105
+ },
106
+ };
107
+ }
108
+ ```
109
+
110
+ ## Pictures
111
+
112
+ The back panel has 16 greys and quantises whatever you upload to them, so a
113
+ photo sent as-is comes back in bands. `ditherToPanel` does the quantising here,
114
+ with error diffusion, which leaves every pixel already sitting on a level:
115
+
116
+ ```ts
117
+ import { ditherToPanel } from 'busybar-kit/image';
118
+
119
+ const cover = ditherToPanel(decodedRgba); // 80x80 Bitmap, panel-ready
120
+ await bar.AssetsUpload({ application_name: 'app', file: 'art.png', data: cover.toPng() });
121
+ ```
122
+
123
+ It takes pixels, not files. Which image decoder to pay for is the app's
124
+ choice — the kit stays dependency-free.
125
+
126
+ `Bitmap.fromRgba` wraps pixels from anywhere, and `bitmap.blit(other, x, y)`
127
+ draws one into another: the raster below cannot follow an image element's path,
128
+ so a preview composites the picture itself.
129
+
130
+ ## Preview
131
+
132
+ `renderFront` / `renderBack` rasterise the very element list you send to the
133
+ device — a 3×5 pixel font, the same anchoring rules, and the back panel
134
+ flattened to the 16 greys the real OLED shows. Useful for screenshots in a
135
+ README and for asserting a layout in tests without a Bar on the desk.
136
+
137
+ ```ts
138
+ import { renderFront } from 'busybar-kit/preview';
139
+
140
+ writeFileSync('front.png', renderFront(frontElements(frame)).scale(8).toPng());
141
+ ```
142
+
143
+ ## Scripts
144
+
145
+ ```bash
146
+ npm run check # lint + typecheck + test
147
+ npm run build # tsc + .js extension fixup
148
+ ```
@@ -1,6 +1,8 @@
1
1
  import { Bitmap } from '../preview/png.js';
2
2
  import { type RgbaImage } from './dither.js';
3
3
  export * from './dither.js';
4
+ export * from './png.js';
5
+ export * from './resize.js';
4
6
  export type PanelImageOptions = {
5
7
  /** Square side in pixels; the panel's own height by default. */
6
8
  size?: number;
@@ -12,8 +14,9 @@ export type PanelImageOptions = {
12
14
  * A picture, ready for the back panel: square, grey, dithered to the levels the
13
15
  * device can show. `toPng()` on the result is what `AssetsUpload` wants.
14
16
  *
15
- * Decoding is deliberately not here — JPEG and PNG decoders are a dependency,
16
- * and which one an app pays for is the app's business. Hand this the pixels.
17
+ * PNG arrives decoded by `decodePng` next door, which costs nothing because it
18
+ * is hand-rolled. JPEG stays the app's business: a decoder for it is a real
19
+ * dependency, and which one an app pays for is not this package's call.
17
20
  */
18
21
  export declare function ditherToPanel(image: RgbaImage, options?: PanelImageOptions): Bitmap;
19
22
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/image/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,UAAU,CAAC;AAElB,cAAc,UAAU,CAAC;AAEzB,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAevF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/image/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EAKL,KAAK,SAAS,EACf,MAAM,UAAU,CAAC;AAElB,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AAEzB,MAAM,MAAM,iBAAiB,GAAG;IAC9B,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAevF"}
@@ -2,12 +2,15 @@ import { BACK } from '../device.js';
2
2
  import { Bitmap } from '../preview/png.js';
3
3
  import { autoContrast, ditherToShades, SHADES, toGreyTile, } from './dither.js';
4
4
  export * from './dither.js';
5
+ export * from './png.js';
6
+ export * from './resize.js';
5
7
  /**
6
8
  * A picture, ready for the back panel: square, grey, dithered to the levels the
7
9
  * device can show. `toPng()` on the result is what `AssetsUpload` wants.
8
10
  *
9
- * Decoding is deliberately not here — JPEG and PNG decoders are a dependency,
10
- * and which one an app pays for is the app's business. Hand this the pixels.
11
+ * PNG arrives decoded by `decodePng` next door, which costs nothing because it
12
+ * is hand-rolled. JPEG stays the app's business: a decoder for it is a real
13
+ * dependency, and which one an app pays for is not this package's call.
11
14
  */
12
15
  export function ditherToPanel(image, options = {}) {
13
16
  const size = options.size ?? BACK.height;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/image/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EACL,YAAY,EACZ,cAAc,EACd,MAAM,EACN,UAAU,GAEX,MAAM,UAAU,CAAC;AAElB,cAAc,UAAU,CAAC;AAUzB;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,KAAgB,EAAE,UAA6B,EAAE;IAC7E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC;IACzC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAEtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;YACxC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/image/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,OAAO,EACL,YAAY,EACZ,cAAc,EACd,MAAM,EACN,UAAU,GAEX,MAAM,UAAU,CAAC;AAElB,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,UAAU,CAAC;AAUzB;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,KAAgB,EAAE,UAA6B,EAAE;IAC7E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC;IACzC,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;IAClE,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAEtC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;YACxC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { RgbaImage } from './dither.js';
2
+ /**
3
+ * Enough of a PNG decoder for album art: 8- and 16-bit samples, every colour
4
+ * type, no interlacing. busybar-kit already hand-rolls the encoder, so this
5
+ * keeps the pair symmetrical instead of pulling in a decoder dependency for
6
+ * the minority of covers that are not JPEG.
7
+ */
8
+ export declare function decodePng(bytes: Buffer): RgbaImage;
9
+ //# sourceMappingURL=png.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"png.d.ts","sourceRoot":"","sources":["../../src/image/png.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAM1C;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CA6ClD"}
@@ -0,0 +1,165 @@
1
+ import { inflateSync } from 'node:zlib';
2
+ const SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
3
+ const CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
4
+ /**
5
+ * Enough of a PNG decoder for album art: 8- and 16-bit samples, every colour
6
+ * type, no interlacing. busybar-kit already hand-rolls the encoder, so this
7
+ * keeps the pair symmetrical instead of pulling in a decoder dependency for
8
+ * the minority of covers that are not JPEG.
9
+ */
10
+ export function decodePng(bytes) {
11
+ if (bytes.length < 8 || SIGNATURE.some((byte, index) => bytes[index] !== byte)) {
12
+ throw new Error('not a PNG');
13
+ }
14
+ let header = null;
15
+ let palette = null;
16
+ let transparency = null;
17
+ const parts = [];
18
+ for (let offset = 8; offset + 8 <= bytes.length;) {
19
+ const length = bytes.readUInt32BE(offset);
20
+ const type = bytes.toString('ascii', offset + 4, offset + 8);
21
+ const start = offset + 8;
22
+ const end = start + length;
23
+ if (end > bytes.length) {
24
+ break;
25
+ }
26
+ if (type === 'IHDR') {
27
+ header = readHeader(bytes.subarray(start, end));
28
+ }
29
+ else if (type === 'PLTE') {
30
+ palette = bytes.subarray(start, end);
31
+ }
32
+ else if (type === 'tRNS') {
33
+ transparency = bytes.subarray(start, end);
34
+ }
35
+ else if (type === 'IDAT') {
36
+ parts.push(bytes.subarray(start, end));
37
+ }
38
+ else if (type === 'IEND') {
39
+ break;
40
+ }
41
+ offset = end + 4;
42
+ }
43
+ if (!header) {
44
+ throw new Error('PNG has no header');
45
+ }
46
+ if (parts.length === 0) {
47
+ throw new Error('PNG has no pixel data');
48
+ }
49
+ const raw = inflateSync(Buffer.concat(parts));
50
+ const samples = unfilter(raw, header);
51
+ return expand(samples, header, palette, transparency);
52
+ }
53
+ function readHeader(chunk) {
54
+ const width = chunk.readUInt32BE(0);
55
+ const height = chunk.readUInt32BE(4);
56
+ const depth = chunk[8] ?? 0;
57
+ const colorType = chunk[9] ?? 0;
58
+ const interlace = chunk[12] ?? 0;
59
+ const channels = CHANNELS[colorType];
60
+ if (!channels) {
61
+ throw new Error(`unsupported PNG colour type ${colorType}`);
62
+ }
63
+ if (depth !== 8 && depth !== 16) {
64
+ throw new Error(`unsupported PNG bit depth ${depth}`);
65
+ }
66
+ if (interlace !== 0) {
67
+ throw new Error('interlaced PNG is not supported');
68
+ }
69
+ if (width <= 0 || height <= 0) {
70
+ throw new Error('PNG has no size');
71
+ }
72
+ const bytesPerSample = depth / 8;
73
+ const bpp = channels * bytesPerSample;
74
+ return { width, height, depth, colorType, channels, bpp, stride: width * bpp };
75
+ }
76
+ function unfilter(raw, header) {
77
+ const { stride, bpp, height } = header;
78
+ const out = Buffer.alloc(stride * height);
79
+ for (let y = 0; y < height; y += 1) {
80
+ const filterOffset = y * (stride + 1);
81
+ const filter = raw[filterOffset] ?? 0;
82
+ const line = filterOffset + 1;
83
+ const target = y * stride;
84
+ for (let x = 0; x < stride; x += 1) {
85
+ const value = raw[line + x] ?? 0;
86
+ const left = x >= bpp ? (out[target + x - bpp] ?? 0) : 0;
87
+ const up = y > 0 ? (out[target - stride + x] ?? 0) : 0;
88
+ const upLeft = y > 0 && x >= bpp ? (out[target - stride + x - bpp] ?? 0) : 0;
89
+ out[target + x] = (value + reconstruct(filter, left, up, upLeft)) & 0xff;
90
+ }
91
+ }
92
+ return out;
93
+ }
94
+ function reconstruct(filter, left, up, upLeft) {
95
+ if (filter === 1) {
96
+ return left;
97
+ }
98
+ if (filter === 2) {
99
+ return up;
100
+ }
101
+ if (filter === 3) {
102
+ return (left + up) >> 1;
103
+ }
104
+ if (filter === 4) {
105
+ return paeth(left, up, upLeft);
106
+ }
107
+ return 0;
108
+ }
109
+ function paeth(a, b, c) {
110
+ const p = a + b - c;
111
+ const pa = Math.abs(p - a);
112
+ const pb = Math.abs(p - b);
113
+ const pc = Math.abs(p - c);
114
+ if (pa <= pb && pa <= pc) {
115
+ return a;
116
+ }
117
+ return pb <= pc ? b : c;
118
+ }
119
+ function expand(samples, header, palette, transparency) {
120
+ const { width, height, depth, colorType, channels } = header;
121
+ const step = depth / 8;
122
+ const data = new Uint8Array(width * height * 4);
123
+ for (let pixel = 0; pixel < width * height; pixel += 1) {
124
+ // 16-bit samples are read by their high byte: the panel has 16 grey levels,
125
+ // so the low byte cannot survive the trip anyway.
126
+ const base = pixel * channels * step;
127
+ const at = (index) => samples[base + index * step] ?? 0;
128
+ const out = pixel * 4;
129
+ let r;
130
+ let g;
131
+ let b;
132
+ let a = 255;
133
+ if (colorType === 0) {
134
+ r = g = b = at(0);
135
+ }
136
+ else if (colorType === 2) {
137
+ r = at(0);
138
+ g = at(1);
139
+ b = at(2);
140
+ }
141
+ else if (colorType === 3) {
142
+ const index = at(0);
143
+ r = palette?.[index * 3] ?? 0;
144
+ g = palette?.[index * 3 + 1] ?? 0;
145
+ b = palette?.[index * 3 + 2] ?? 0;
146
+ a = transparency?.[index] ?? 255;
147
+ }
148
+ else if (colorType === 4) {
149
+ r = g = b = at(0);
150
+ a = at(1);
151
+ }
152
+ else {
153
+ r = at(0);
154
+ g = at(1);
155
+ b = at(2);
156
+ a = at(3);
157
+ }
158
+ data[out] = r;
159
+ data[out + 1] = g;
160
+ data[out + 2] = b;
161
+ data[out + 3] = a;
162
+ }
163
+ return { width, height, data };
164
+ }
165
+ //# sourceMappingURL=png.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"png.js","sourceRoot":"","sources":["../../src/image/png.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { RgbaImage } from './dither.js';
2
+ /**
3
+ * Centre-crops to a square and box-filters down to `size`, in colour.
4
+ *
5
+ * The grey path next door does the same walk and throws the channels away at
6
+ * the end; a panel that can show colour wants them kept. Averaging happens in
7
+ * linear light for the same reason it does there — averaging gamma-encoded
8
+ * bytes is why a downscaled picture usually comes out muddier than the
9
+ * original. Alpha is averaged as-is, being linear already.
10
+ */
11
+ export declare function toRgbaTile(image: RgbaImage, size: number): RgbaImage;
12
+ //# sourceMappingURL=resize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resize.d.ts","sourceRoot":"","sources":["../../src/image/resize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAK1C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAyBpE"}