ntk 5.2.0 → 5.3.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/lib/clipboard.js +576 -74
- package/lib/image.js +10 -17
- package/lib/imagedata.js +388 -0
- package/lib/index.js +16 -0
- package/lib/renderingcontext_2d.js +362 -28
- package/lib/surface.js +118 -0
- package/lib/widgets/svgview.js +118 -2
- package/lib/window.js +136 -0
- package/package.json +1 -1
package/lib/image.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import jpeg from 'jpeg-js';
|
|
2
2
|
import { PNG } from 'pngjs';
|
|
3
3
|
|
|
4
|
+
import { fromStraightRgba, pixelLayout } from './imagedata.js';
|
|
4
5
|
import Picture from './picture.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -40,23 +41,15 @@ export class Image {
|
|
|
40
41
|
const Render = app.display.Render;
|
|
41
42
|
const pixmap = app.createPixmap({ depth: 32, width: this.width, height: this.height });
|
|
42
43
|
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
bgra[i + 2] = src[i];
|
|
53
|
-
} else {
|
|
54
|
-
bgra[i] = (src[i + 2] * a + 127) / 255;
|
|
55
|
-
bgra[i + 1] = (src[i + 1] * a + 127) / 255;
|
|
56
|
-
bgra[i + 2] = (src[i] * a + 127) / 255;
|
|
57
|
-
}
|
|
58
|
-
bgra[i + 3] = a;
|
|
59
|
-
}
|
|
44
|
+
// straight RGBA in, the server's own premultiplied layout out. This used
|
|
45
|
+
// to assume BGRA, which is only what a little-endian server with the
|
|
46
|
+
// standard visual masks happens to want.
|
|
47
|
+
const bgra = fromStraightRgba(
|
|
48
|
+
this.data,
|
|
49
|
+
pixelLayout(app.display, 32),
|
|
50
|
+
this.width,
|
|
51
|
+
this.height
|
|
52
|
+
);
|
|
60
53
|
|
|
61
54
|
// node-x11 has no FreeGC — share one upload GC per app (valid for any
|
|
62
55
|
// depth-32 drawable on the screen)
|
package/lib/imagedata.js
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canvas pixel contract, and the conversions between it and what an X
|
|
3
|
+
* server actually hands over.
|
|
4
|
+
*
|
|
5
|
+
* Canvas `ImageData` is straight (non-premultiplied) RGBA bytes, one byte per
|
|
6
|
+
* channel, rows top to bottom. An X drawable is none of those things by
|
|
7
|
+
* default:
|
|
8
|
+
*
|
|
9
|
+
* - `GetImage` returns the drawable's raw words in the *server's*
|
|
10
|
+
* `image_byte_order`, which is a different field from the byte order this
|
|
11
|
+
* connection speaks;
|
|
12
|
+
* - which bits inside a word hold which channel comes from the visual's
|
|
13
|
+
* masks, not from a convention;
|
|
14
|
+
* - anything XRender composited into is premultiplied, and a drawable
|
|
15
|
+
* without an alpha channel has a fourth byte that is undefined padding
|
|
16
|
+
* rather than opaque.
|
|
17
|
+
*
|
|
18
|
+
* Everything crossing that boundary goes through here, so callers see RGBA
|
|
19
|
+
* and only this file knows about the rest. `readPixels()` is the way out for
|
|
20
|
+
* code that genuinely wants the server's own bytes — it reports the layout
|
|
21
|
+
* alongside them instead of leaving the caller to assume one.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** shift of the lowest set bit, i.e. where a channel starts inside a word */
|
|
25
|
+
function shiftOf(mask) {
|
|
26
|
+
let s = 0;
|
|
27
|
+
while (s < 32 && !((mask >>> s) & 1)) s++;
|
|
28
|
+
return s;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** width of a contiguous mask, so 5- and 6-bit channels can be scaled to 8 */
|
|
32
|
+
function widthOf(mask, shift) {
|
|
33
|
+
let w = 0;
|
|
34
|
+
while (shift + w < 32 && (mask >>> (shift + w)) & 1) w++;
|
|
35
|
+
return w;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* How pixels of `depth` are laid out on this display: which bits hold which
|
|
40
|
+
* channel, whether there is an alpha channel at all, and which end of a word
|
|
41
|
+
* the server writes first.
|
|
42
|
+
*
|
|
43
|
+
* Channel positions come from a TrueColor visual of that depth when the
|
|
44
|
+
* screen has one. Depth 32 has no visual on most servers (XQuartz has none at
|
|
45
|
+
* all) but every depth-32 drawable ntk makes is paired with a `Render.rgba32`
|
|
46
|
+
* picture, whose PictStandardARGB32 format pins A=24 R=16 G=8 B=0 — so that
|
|
47
|
+
* is the fallback, and it is a specification rather than a guess.
|
|
48
|
+
*/
|
|
49
|
+
export function pixelLayout(display, depth) {
|
|
50
|
+
const screen = display.screen[0];
|
|
51
|
+
let masks = null;
|
|
52
|
+
for (const visual of Object.values(screen.depths?.[depth] ?? {})) {
|
|
53
|
+
if (visual.class === 4 || visual.class === 5) {
|
|
54
|
+
// TrueColor / DirectColor
|
|
55
|
+
masks = { red: visual.red_mask, green: visual.green_mask, blue: visual.blue_mask };
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!masks) masks = { red: 0xff0000, green: 0x00ff00, blue: 0x0000ff };
|
|
60
|
+
|
|
61
|
+
const bitsPerPixel = display.format?.[depth]?.bits_per_pixel ?? (depth > 16 ? 32 : depth);
|
|
62
|
+
if (bitsPerPixel !== 32) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`ntk works in 32-bit pixels; depth ${depth} on this server is ` +
|
|
65
|
+
`${bitsPerPixel} bits per pixel. Use readPixels() and unpack it yourself.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const hasAlpha = depth === 32;
|
|
70
|
+
// whatever the colour masks leave over, which for ARGB32 is the top byte
|
|
71
|
+
const alpha = hasAlpha ? (~(masks.red | masks.green | masks.blue) & 0xffffffff) >>> 0 : 0;
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
depth,
|
|
75
|
+
bitsPerPixel,
|
|
76
|
+
masks: { ...masks, alpha },
|
|
77
|
+
// 0 LSBFirst, 1 MSBFirst — the server's pixel order, NOT display.byte_order
|
|
78
|
+
byteOrder: display.image_byte_order ? 'msb' : 'lsb',
|
|
79
|
+
// XRender composites premultiplied, so anything with an alpha channel
|
|
80
|
+
// that ntk has drawn into holds premultiplied colour
|
|
81
|
+
premultiplied: hasAlpha
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** the common case: LSBFirst words with 8-bit channels at ARGB32 positions */
|
|
86
|
+
function isStandardLsb(layout) {
|
|
87
|
+
return (
|
|
88
|
+
layout.byteOrder === 'lsb' &&
|
|
89
|
+
layout.masks.red === 0xff0000 &&
|
|
90
|
+
layout.masks.green === 0x00ff00 &&
|
|
91
|
+
layout.masks.blue === 0x0000ff
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Server pixels -> straight RGBA. `raw` is a `GetImage` reply body.
|
|
97
|
+
*
|
|
98
|
+
* @param {Buffer} raw
|
|
99
|
+
* @param {object} layout from `pixelLayout()`
|
|
100
|
+
* @param {number} width
|
|
101
|
+
* @param {number} height
|
|
102
|
+
* @returns {Uint8ClampedArray}
|
|
103
|
+
*/
|
|
104
|
+
export function toStraightRgba(raw, layout, width, height) {
|
|
105
|
+
const n = width * height;
|
|
106
|
+
const out = new Uint8ClampedArray(n * 4);
|
|
107
|
+
const { premultiplied } = layout;
|
|
108
|
+
|
|
109
|
+
if (isStandardLsb(layout)) {
|
|
110
|
+
// bytes arrive B, G, R, A — a straight index shuffle, which measures
|
|
111
|
+
// faster than reading 32-bit words back out of the Buffer
|
|
112
|
+
for (let i = 0; i < n * 4; i += 4) {
|
|
113
|
+
const a = layout.depth === 32 ? raw[i + 3] : 255;
|
|
114
|
+
if (a === 255) {
|
|
115
|
+
out[i] = raw[i + 2];
|
|
116
|
+
out[i + 1] = raw[i + 1];
|
|
117
|
+
out[i + 2] = raw[i];
|
|
118
|
+
} else if (a === 0 || !premultiplied) {
|
|
119
|
+
out[i] = raw[i + 2];
|
|
120
|
+
out[i + 1] = raw[i + 1];
|
|
121
|
+
out[i + 2] = raw[i];
|
|
122
|
+
} else {
|
|
123
|
+
out[i] = (raw[i + 2] * 255) / a;
|
|
124
|
+
out[i + 1] = (raw[i + 1] * 255) / a;
|
|
125
|
+
out[i + 2] = (raw[i] * 255) / a;
|
|
126
|
+
}
|
|
127
|
+
out[i + 3] = a;
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const { masks } = layout;
|
|
133
|
+
const rs = shiftOf(masks.red);
|
|
134
|
+
const gs = shiftOf(masks.green);
|
|
135
|
+
const bs = shiftOf(masks.blue);
|
|
136
|
+
const as = masks.alpha ? shiftOf(masks.alpha) : 0;
|
|
137
|
+
const rw = widthOf(masks.red, rs);
|
|
138
|
+
const gw = widthOf(masks.green, gs);
|
|
139
|
+
const bw = widthOf(masks.blue, bs);
|
|
140
|
+
// scale a channel of `w` bits up to 8, so 0b11111 becomes 255 not 248
|
|
141
|
+
const up = (v, w) => (w === 8 ? v : Math.round((v * 255) / ((1 << w) - 1)));
|
|
142
|
+
const readWord = layout.byteOrder === 'msb'
|
|
143
|
+
? (o) => raw.readUInt32BE(o)
|
|
144
|
+
: (o) => raw.readUInt32LE(o);
|
|
145
|
+
|
|
146
|
+
for (let i = 0; i < n; i++) {
|
|
147
|
+
const px = readWord(i * 4);
|
|
148
|
+
const a = masks.alpha ? (px & masks.alpha) >>> as : 255;
|
|
149
|
+
let r = up((px & masks.red) >>> rs, rw);
|
|
150
|
+
let g = up((px & masks.green) >>> gs, gw);
|
|
151
|
+
let b = up((px & masks.blue) >>> bs, bw);
|
|
152
|
+
if (premultiplied && a !== 0 && a !== 255) {
|
|
153
|
+
r = (r * 255) / a;
|
|
154
|
+
g = (g * 255) / a;
|
|
155
|
+
b = (b * 255) / a;
|
|
156
|
+
}
|
|
157
|
+
out[i * 4] = r;
|
|
158
|
+
out[i * 4 + 1] = g;
|
|
159
|
+
out[i * 4 + 2] = b;
|
|
160
|
+
out[i * 4 + 3] = a;
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Straight RGBA -> server pixels, ready for `PutImage`.
|
|
167
|
+
*
|
|
168
|
+
* @param {Uint8Array} rgba straight RGBA, 4 bytes per pixel
|
|
169
|
+
* @param {object} layout from `pixelLayout()`
|
|
170
|
+
* @param {number} width
|
|
171
|
+
* @param {number} height
|
|
172
|
+
* @returns {Buffer}
|
|
173
|
+
*/
|
|
174
|
+
export function fromStraightRgba(rgba, layout, width, height) {
|
|
175
|
+
const n = width * height;
|
|
176
|
+
const out = Buffer.allocUnsafe(n * 4);
|
|
177
|
+
const { premultiplied } = layout;
|
|
178
|
+
|
|
179
|
+
if (isStandardLsb(layout)) {
|
|
180
|
+
for (let i = 0; i < n * 4; i += 4) {
|
|
181
|
+
const a = rgba[i + 3];
|
|
182
|
+
if (!premultiplied || a === 255) {
|
|
183
|
+
out[i] = rgba[i + 2];
|
|
184
|
+
out[i + 1] = rgba[i + 1];
|
|
185
|
+
out[i + 2] = rgba[i];
|
|
186
|
+
} else {
|
|
187
|
+
// +127 rounds to nearest rather than truncating, which otherwise
|
|
188
|
+
// drifts a translucent fill visibly darker over repeated round trips
|
|
189
|
+
out[i] = (rgba[i + 2] * a + 127) / 255;
|
|
190
|
+
out[i + 1] = (rgba[i + 1] * a + 127) / 255;
|
|
191
|
+
out[i + 2] = (rgba[i] * a + 127) / 255;
|
|
192
|
+
}
|
|
193
|
+
out[i + 3] = layout.depth === 32 ? a : 0;
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const { masks } = layout;
|
|
199
|
+
const rs = shiftOf(masks.red);
|
|
200
|
+
const gs = shiftOf(masks.green);
|
|
201
|
+
const bs = shiftOf(masks.blue);
|
|
202
|
+
const as = masks.alpha ? shiftOf(masks.alpha) : 0;
|
|
203
|
+
const rw = widthOf(masks.red, rs);
|
|
204
|
+
const gw = widthOf(masks.green, gs);
|
|
205
|
+
const bw = widthOf(masks.blue, bs);
|
|
206
|
+
const down = (v, w) => (w === 8 ? v : Math.round((v * ((1 << w) - 1)) / 255));
|
|
207
|
+
const writeWord = layout.byteOrder === 'msb'
|
|
208
|
+
? (v, o) => out.writeUInt32BE(v, o)
|
|
209
|
+
: (v, o) => out.writeUInt32LE(v, o);
|
|
210
|
+
|
|
211
|
+
for (let i = 0; i < n; i++) {
|
|
212
|
+
const a = rgba[i * 4 + 3];
|
|
213
|
+
let r = rgba[i * 4];
|
|
214
|
+
let g = rgba[i * 4 + 1];
|
|
215
|
+
let b = rgba[i * 4 + 2];
|
|
216
|
+
if (premultiplied && a !== 255) {
|
|
217
|
+
r = (r * a + 127) / 255;
|
|
218
|
+
g = (g * a + 127) / 255;
|
|
219
|
+
b = (b * a + 127) / 255;
|
|
220
|
+
}
|
|
221
|
+
const word =
|
|
222
|
+
((down(r | 0, rw) << rs) |
|
|
223
|
+
(down(g | 0, gw) << gs) |
|
|
224
|
+
(down(b | 0, bw) << bs) |
|
|
225
|
+
(masks.alpha ? (a << as) & masks.alpha : 0)) >>>
|
|
226
|
+
0;
|
|
227
|
+
writeWord(word, i * 4);
|
|
228
|
+
}
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Canvas `ImageData`: straight RGBA in a `Uint8ClampedArray`, rows top to
|
|
234
|
+
* bottom. Both spec constructor forms work:
|
|
235
|
+
*
|
|
236
|
+
* new ImageData(width, height)
|
|
237
|
+
* new ImageData(data, width[, height])
|
|
238
|
+
*
|
|
239
|
+
* `data` may be any `Uint8Array` — a node `Buffer` included — and is adopted
|
|
240
|
+
* as-is when it is already a `Uint8ClampedArray`, copied otherwise.
|
|
241
|
+
*/
|
|
242
|
+
export class ImageData {
|
|
243
|
+
constructor(a, b, c) {
|
|
244
|
+
if (typeof a === 'number') {
|
|
245
|
+
const width = a;
|
|
246
|
+
const height = b;
|
|
247
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
|
|
248
|
+
throw new Error('ImageData: width and height must be positive integers');
|
|
249
|
+
}
|
|
250
|
+
this.width = width;
|
|
251
|
+
this.height = height;
|
|
252
|
+
this.data = new Uint8ClampedArray(width * height * 4);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (!ArrayBuffer.isView(a)) {
|
|
256
|
+
throw new TypeError('ImageData: first argument must be a width or a typed array');
|
|
257
|
+
}
|
|
258
|
+
const width = b;
|
|
259
|
+
if (!Number.isInteger(width) || width <= 0) {
|
|
260
|
+
throw new Error('ImageData: width must be a positive integer');
|
|
261
|
+
}
|
|
262
|
+
if (a.length % 4 !== 0) {
|
|
263
|
+
throw new Error(`ImageData: data length ${a.length} is not a whole number of RGBA pixels`);
|
|
264
|
+
}
|
|
265
|
+
const height = c ?? a.length / 4 / width;
|
|
266
|
+
if (!Number.isInteger(height) || height <= 0) {
|
|
267
|
+
throw new Error(`ImageData: ${a.length} bytes is not a whole number of ${width}px rows`);
|
|
268
|
+
}
|
|
269
|
+
if (a.length !== width * height * 4) {
|
|
270
|
+
throw new Error(
|
|
271
|
+
`ImageData: data must be ${width * height * 4} bytes for ${width}x${height}, got ${a.length}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
this.width = width;
|
|
275
|
+
this.height = height;
|
|
276
|
+
this.data =
|
|
277
|
+
a instanceof Uint8ClampedArray
|
|
278
|
+
? a
|
|
279
|
+
: new Uint8ClampedArray(a.buffer, a.byteOffset, a.byteLength).slice();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** the sRGB colour space, for parity with the browser's property */
|
|
283
|
+
get colorSpace() {
|
|
284
|
+
return 'srgb';
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// --- EWMH _NET_WM_ICON -----------------------------------------------------
|
|
289
|
+
//
|
|
290
|
+
// The property is a run of CARD32s, repeated once per size:
|
|
291
|
+
//
|
|
292
|
+
// width, height, pixel[0] … pixel[width*height-1]
|
|
293
|
+
//
|
|
294
|
+
// Each pixel is straight ARGB inside the word — A=24 R=16 G=8 B=0. Straight,
|
|
295
|
+
// not premultiplied, which is the same thing ImageData holds, so this is a
|
|
296
|
+
// repack rather than a conversion. The one thing to get right is which byte
|
|
297
|
+
// order: a format-32 property is read back by the server in the *connection's*
|
|
298
|
+
// byte order, which is a different handshake field from the image_byte_order
|
|
299
|
+
// that `pixelLayout()` uses for GetImage.
|
|
300
|
+
|
|
301
|
+
const wordIO = (display) =>
|
|
302
|
+
display.byte_order
|
|
303
|
+
? { read: (b, o) => b.readUInt32BE(o), write: (b, v, o) => b.writeUInt32BE(v, o) }
|
|
304
|
+
: { read: (b, o) => b.readUInt32LE(o), write: (b, v, o) => b.writeUInt32LE(v, o) };
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Pack one or more images into `_NET_WM_ICON` property bytes.
|
|
308
|
+
*
|
|
309
|
+
* @param {Array<{width: number, height: number, data: Uint8Array}>} images
|
|
310
|
+
* straight RGBA, as `Image` and `ImageData` both are
|
|
311
|
+
* @param {object} display
|
|
312
|
+
* @returns {Buffer}
|
|
313
|
+
*/
|
|
314
|
+
export function packIcons(images, display) {
|
|
315
|
+
let words = 0;
|
|
316
|
+
images.forEach((img, i) => {
|
|
317
|
+
const { width, height, data } = img ?? {};
|
|
318
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width <= 0 || height <= 0) {
|
|
319
|
+
throw new Error(`icon ${i}: width and height must be positive integers`);
|
|
320
|
+
}
|
|
321
|
+
if (!data || data.length !== width * height * 4) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`icon ${i}: data must be ${width * height * 4} RGBA bytes for ${width}x${height}, ` +
|
|
324
|
+
`got ${data ? data.length : 'nothing'}`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
words += 2 + width * height;
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const out = Buffer.allocUnsafe(words * 4);
|
|
331
|
+
const { write } = wordIO(display);
|
|
332
|
+
let o = 0;
|
|
333
|
+
for (const { width, height, data } of images) {
|
|
334
|
+
write(out, width, o);
|
|
335
|
+
write(out, height, o + 4);
|
|
336
|
+
o += 8;
|
|
337
|
+
for (let i = 0; i < width * height; i++) {
|
|
338
|
+
write(
|
|
339
|
+
out,
|
|
340
|
+
(((data[i * 4 + 3] << 24) |
|
|
341
|
+
(data[i * 4] << 16) |
|
|
342
|
+
(data[i * 4 + 1] << 8) |
|
|
343
|
+
data[i * 4 + 2]) >>>
|
|
344
|
+
0),
|
|
345
|
+
o
|
|
346
|
+
);
|
|
347
|
+
o += 4;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return out;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Read `_NET_WM_ICON` bytes back into images.
|
|
355
|
+
*
|
|
356
|
+
* Deliberately forgiving about what it is handed: a window manager runs this
|
|
357
|
+
* over properties written by other people's clients, so a truncated or
|
|
358
|
+
* nonsensical run stops the scan and returns whatever parsed cleanly rather
|
|
359
|
+
* than throwing or trying to allocate a claimed 4-gigapixel icon.
|
|
360
|
+
*
|
|
361
|
+
* @returns {ImageData[]}
|
|
362
|
+
*/
|
|
363
|
+
export function unpackIcons(buf, display) {
|
|
364
|
+
const { read } = wordIO(display);
|
|
365
|
+
const out = [];
|
|
366
|
+
let o = 0;
|
|
367
|
+
while (o + 8 <= buf.length) {
|
|
368
|
+
const width = read(buf, o);
|
|
369
|
+
const height = read(buf, o + 4);
|
|
370
|
+
// guard the multiplication itself, not just its result
|
|
371
|
+
if (width <= 0 || height <= 0 || width > 0xffff || height > 0xffff) break;
|
|
372
|
+
const pixels = width * height;
|
|
373
|
+
if (o + 8 + pixels * 4 > buf.length) break;
|
|
374
|
+
o += 8;
|
|
375
|
+
|
|
376
|
+
const rgba = new Uint8ClampedArray(pixels * 4);
|
|
377
|
+
for (let i = 0; i < pixels; i++) {
|
|
378
|
+
const px = read(buf, o + i * 4);
|
|
379
|
+
rgba[i * 4] = (px >>> 16) & 0xff;
|
|
380
|
+
rgba[i * 4 + 1] = (px >>> 8) & 0xff;
|
|
381
|
+
rgba[i * 4 + 2] = px & 0xff;
|
|
382
|
+
rgba[i * 4 + 3] = (px >>> 24) & 0xff;
|
|
383
|
+
}
|
|
384
|
+
o += pixels * 4;
|
|
385
|
+
out.push(new ImageData(rgba, width, height));
|
|
386
|
+
}
|
|
387
|
+
return out;
|
|
388
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -9,6 +9,15 @@ import { decodeKey, groupForState } from './keyboard.js';
|
|
|
9
9
|
import Pixmap from './pixmap.js';
|
|
10
10
|
import Picture from './picture.js';
|
|
11
11
|
import { Image, decodeImage, loadImage } from './image.js';
|
|
12
|
+
import {
|
|
13
|
+
ImageData,
|
|
14
|
+
fromStraightRgba,
|
|
15
|
+
packIcons,
|
|
16
|
+
pixelLayout,
|
|
17
|
+
toStraightRgba,
|
|
18
|
+
unpackIcons
|
|
19
|
+
} from './imagedata.js';
|
|
20
|
+
import { Surface } from './surface.js';
|
|
12
21
|
import { Path2D, parseSvgPath } from './path.js';
|
|
13
22
|
import Font from './text/font.js';
|
|
14
23
|
import FontManager from './text/fontmanager.js';
|
|
@@ -149,7 +158,14 @@ export {
|
|
|
149
158
|
resolveCursorShape,
|
|
150
159
|
Pixmap,
|
|
151
160
|
Picture,
|
|
161
|
+
Surface,
|
|
152
162
|
Image,
|
|
163
|
+
ImageData,
|
|
164
|
+
pixelLayout,
|
|
165
|
+
toStraightRgba,
|
|
166
|
+
fromStraightRgba,
|
|
167
|
+
packIcons,
|
|
168
|
+
unpackIcons,
|
|
153
169
|
decodeImage,
|
|
154
170
|
loadImage,
|
|
155
171
|
Path2D,
|