@pixagram/renderart 0.4.4 → 1.0.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.
Files changed (47) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +171 -67
  3. package/dist/crt-gpu.d.ts +30 -0
  4. package/dist/crt-gpu.d.ts.map +1 -0
  5. package/dist/crt-gpu.js +282 -0
  6. package/dist/crt-gpu.js.map +1 -0
  7. package/dist/hex-gpu.d.ts +35 -0
  8. package/dist/hex-gpu.d.ts.map +1 -0
  9. package/dist/hex-gpu.js +382 -0
  10. package/dist/hex-gpu.js.map +1 -0
  11. package/dist/index.d.ts +21 -300
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +36 -963
  14. package/dist/index.js.map +1 -1
  15. package/dist/types.d.ts +84 -0
  16. package/dist/types.d.ts.map +1 -0
  17. package/dist/types.js +7 -0
  18. package/dist/types.js.map +1 -0
  19. package/dist/wasm-wrapper.d.ts +71 -0
  20. package/dist/wasm-wrapper.d.ts.map +1 -0
  21. package/dist/wasm-wrapper.js +76 -0
  22. package/dist/wasm-wrapper.js.map +1 -0
  23. package/dist/xbrz-gpu.d.ts +34 -0
  24. package/dist/xbrz-gpu.d.ts.map +1 -0
  25. package/dist/xbrz-gpu.js +640 -0
  26. package/dist/xbrz-gpu.js.map +1 -0
  27. package/package.json +48 -35
  28. package/src/crt-gpu.ts +313 -0
  29. package/src/hex-gpu.ts +426 -0
  30. package/src/index.ts +52 -0
  31. package/src/types.ts +90 -0
  32. package/src/wasm/crt.rs +181 -0
  33. package/src/wasm/hex.rs +324 -0
  34. package/src/wasm/lib.rs +285 -0
  35. package/src/wasm/xbrz.rs +262 -0
  36. package/src/wasm-wrapper.ts +195 -0
  37. package/src/xbrz-gpu.ts +671 -0
  38. package/dist/index.d.mts +0 -305
  39. package/dist/index.mjs +0 -948
  40. package/dist/index.mjs.map +0 -1
  41. package/pkg/LICENSE +0 -21
  42. package/pkg/README.md +0 -117
  43. package/pkg/renderart_wasm.d.ts +0 -52
  44. package/pkg/renderart_wasm.js +0 -5
  45. package/pkg/renderart_wasm_bg.js +0 -283
  46. package/pkg/renderart_wasm_bg.wasm +0 -0
  47. package/pkg/renderart_wasm_bg.wasm.d.ts +0 -24
package/package.json CHANGED
@@ -1,59 +1,72 @@
1
1
  {
2
2
  "name": "@pixagram/renderart",
3
- "version": "0.4.4",
4
- "description": "High-performance pixel art rendering engines (CRT, HEX, XBRZ) with GPU and CPU support",
3
+ "version": "1.0.0",
4
+ "description": "High-performance pixel art rendering engines with WebGL2 GPU acceleration and WASM support",
5
+ "keywords": [
6
+ "pixel-art",
7
+ "upscaling",
8
+ "xbrz",
9
+ "crt",
10
+ "hexagonal",
11
+ "webgl",
12
+ "wasm",
13
+ "image-processing",
14
+ "retro",
15
+ "gamedev"
16
+ ],
17
+ "author": "",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": ""
22
+ },
23
+ "type": "module",
5
24
  "main": "./dist/index.js",
6
- "module": "./dist/index.mjs",
25
+ "module": "./dist/index.js",
7
26
  "types": "./dist/index.d.ts",
8
27
  "exports": {
9
28
  ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.mjs",
12
- "require": "./dist/index.js"
29
+ "import": "./dist/index.js",
30
+ "types": "./dist/index.d.ts"
31
+ },
32
+ "./crt": {
33
+ "import": "./dist/crt-gpu.js",
34
+ "types": "./dist/crt-gpu.d.ts"
35
+ },
36
+ "./hex": {
37
+ "import": "./dist/hex-gpu.js",
38
+ "types": "./dist/hex-gpu.d.ts"
39
+ },
40
+ "./xbrz": {
41
+ "import": "./dist/xbrz-gpu.js",
42
+ "types": "./dist/xbrz-gpu.d.ts"
13
43
  },
14
44
  "./wasm": {
15
- "import": "./pkg/renderart_wasm_bg.js",
16
- "require": "./pkg/renderart_wasm_bg.js"
45
+ "import": "./dist/wasm/renderart.js",
46
+ "types": "./dist/wasm/renderart.d.ts"
17
47
  }
18
48
  },
19
49
  "files": [
20
50
  "dist",
21
- "pkg",
51
+ "src",
22
52
  "README.md",
23
53
  "LICENSE"
24
54
  ],
25
55
  "scripts": {
26
- "build:wasm": "wasm-pack build --target bundler --out-dir pkg --release && rm -f pkg/.gitignore pkg/package.json",
27
- "build:ts": "tsup",
28
56
  "build": "npm run build:wasm && npm run build:ts",
29
- "test": "cargo test",
30
- "prepublishOnly": "npm run build"
31
- },
32
- "keywords": [
33
- "pixel-art",
34
- "upscale",
35
- "crt",
36
- "hexagon",
37
- "xbrz",
38
- "webgl",
39
- "wasm",
40
- "shader",
41
- "rendering",
42
- "image-processing"
43
- ],
44
- "author": "Pixagram SA",
45
- "license": "MIT",
46
- "repository": {
47
- "type": "git",
48
- "url": "https://github.com/pixagram/renderart.git"
57
+ "build:ts": "tsc",
58
+ "build:wasm": "wasm-pack build --target web --out-dir dist/wasm",
59
+ "clean": "rm -rf dist",
60
+ "prepublishOnly": "npm run clean && npm run build",
61
+ "test": "node --test dist/**/*.test.js"
49
62
  },
50
63
  "devDependencies": {
51
- "@types/node": "^20.10.0",
52
- "tsup": "^8.0.0",
53
- "typescript": "^5.3.0"
64
+ "@types/node": "^25.0.3",
65
+ "typescript": "^5.3.0",
66
+ "wasm-pack": "^0.12.0"
54
67
  },
55
68
  "engines": {
56
- "node": ">=16.0.0"
69
+ "node": ">=18.0.0"
57
70
  },
58
71
  "sideEffects": false
59
72
  }
package/src/crt-gpu.ts ADDED
@@ -0,0 +1,313 @@
1
+ /**
2
+ * CRT GPU Renderer using WebGL2
3
+ *
4
+ * High-performance CRT effect rendering using fragment shaders.
5
+ */
6
+
7
+ import type { CrtOptions, ImageInput, ImageOutput, Renderer } from './types';
8
+
9
+ // Vertex shader
10
+ const VERTEX_SHADER = `#version 300 es
11
+ layout(location = 0) in vec2 position;
12
+ out vec2 vUv;
13
+
14
+ void main() {
15
+ vUv = position * 0.5 + 0.5;
16
+ gl_Position = vec4(position, 0.0, 1.0);
17
+ }`;
18
+
19
+ // Fragment shader with configurable CRT effects
20
+ const FRAGMENT_SHADER = `#version 300 es
21
+ precision highp float;
22
+
23
+ uniform sampler2D uTex;
24
+ uniform vec2 uRes;
25
+ uniform vec2 uWarp;
26
+ uniform float uScanHardness;
27
+ uniform float uScanOpacity;
28
+ uniform float uMaskOpacity;
29
+ uniform int uEnableWarp;
30
+ uniform int uEnableScanlines;
31
+ uniform int uEnableMask;
32
+
33
+ in vec2 vUv;
34
+ out vec4 outColor;
35
+
36
+ // Gamma 2.0 approximation
37
+ vec3 toLinear(vec3 c) { return c * c; }
38
+ vec3 toSrgb(vec3 c) { return sqrt(c); }
39
+
40
+ vec2 warp(vec2 uv) {
41
+ if (uEnableWarp == 0) return uv;
42
+ vec2 dc = abs(0.5 - uv);
43
+ vec2 dc2 = dc * dc;
44
+ uv.x -= 0.5; uv.x *= 1.0 + (dc2.y * (0.3 * uWarp.x)); uv.x += 0.5;
45
+ uv.y -= 0.5; uv.y *= 1.0 + (dc2.x * (0.4 * uWarp.y)); uv.y += 0.5;
46
+ return uv;
47
+ }
48
+
49
+ float scanline(float y, float sourceHeight) {
50
+ if (uEnableScanlines == 0) return 1.0;
51
+ float v = fract(y * sourceHeight);
52
+ float d = abs(v - 0.5);
53
+ float line = exp(d * d * uScanHardness);
54
+ return mix(1.0, line, uScanOpacity);
55
+ }
56
+
57
+ vec3 mask(vec2 pos) {
58
+ if (uEnableMask == 0) return vec3(1.0);
59
+ float x = fract(pos.x / 6.0);
60
+ vec3 m = vec3(1.0);
61
+ float step1 = 0.333;
62
+ float step2 = 0.666;
63
+
64
+ m.r = step(0.0, x) - step(step1, x);
65
+ m.g = step(step1, x) - step(step2, x);
66
+ m.b = step(step2, x) - step(1.0, x);
67
+
68
+ return mix(vec3(1.0), m, uMaskOpacity);
69
+ }
70
+
71
+ void main() {
72
+ // 1. Geometry
73
+ vec2 uv = warp(vUv);
74
+
75
+ // 2. Bounds Check
76
+ if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
77
+ outColor = vec4(0.0);
78
+ return;
79
+ }
80
+
81
+ // 3. Texture Sample
82
+ vec4 texSample = texture(uTex, uv);
83
+
84
+ if (texSample.a == 0.0) {
85
+ outColor = vec4(0.0);
86
+ return;
87
+ }
88
+
89
+ vec3 linearColor = toLinear(texSample.rgb);
90
+
91
+ // 4. CRT Effects
92
+ ivec2 texSize = textureSize(uTex, 0);
93
+
94
+ // Smart Bloom
95
+ float luma = dot(linearColor, vec3(0.299, 0.587, 0.114));
96
+ float bloom = luma * 0.7;
97
+
98
+ // Calculate patterns
99
+ float scan = scanline(uv.y, float(texSize.y));
100
+ vec3 m = mask(gl_FragCoord.xy);
101
+
102
+ // Apply effects
103
+ vec3 effects = m * scan;
104
+ vec3 finalRGB = linearColor * mix(effects, vec3(1.0), bloom);
105
+
106
+ // 5. Output
107
+ outColor = vec4(toSrgb(finalRGB), texSample.a);
108
+ }`;
109
+
110
+ /** CRT GPU Renderer */
111
+ export class CrtGpuRenderer implements Renderer<CrtOptions> {
112
+ private gl: WebGL2RenderingContext | null = null;
113
+ private canvas: OffscreenCanvas | null = null;
114
+ private program: WebGLProgram | null = null;
115
+ private texture: WebGLTexture | null = null;
116
+ private uniforms: Record<string, WebGLUniformLocation | null> = {};
117
+ private initialized = false;
118
+ private currentCanvasSize = { width: 0, height: 0 };
119
+ private currentTexSize = { width: 0, height: 0 };
120
+
121
+ /** Create a new CRT GPU renderer */
122
+ static create(): CrtGpuRenderer {
123
+ const renderer = new CrtGpuRenderer();
124
+ renderer.init();
125
+ return renderer;
126
+ }
127
+
128
+ private init(): void {
129
+ if (typeof OffscreenCanvas === 'undefined') {
130
+ throw new Error('OffscreenCanvas not supported');
131
+ }
132
+
133
+ this.canvas = new OffscreenCanvas(1, 1);
134
+ this.gl = this.canvas.getContext('webgl2', {
135
+ alpha: true,
136
+ premultipliedAlpha: false,
137
+ desynchronized: true,
138
+ powerPreference: 'high-performance',
139
+ antialias: false,
140
+ });
141
+
142
+ if (!this.gl) {
143
+ throw new Error('WebGL2 not supported');
144
+ }
145
+
146
+ const gl = this.gl;
147
+
148
+ // Create shaders
149
+ const vs = this.createShader(gl.VERTEX_SHADER, VERTEX_SHADER);
150
+ const fs = this.createShader(gl.FRAGMENT_SHADER, FRAGMENT_SHADER);
151
+
152
+ // Create program
153
+ this.program = gl.createProgram()!;
154
+ gl.attachShader(this.program, vs);
155
+ gl.attachShader(this.program, fs);
156
+ gl.linkProgram(this.program);
157
+
158
+ if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
159
+ throw new Error('Shader program link failed: ' + gl.getProgramInfoLog(this.program));
160
+ }
161
+
162
+ gl.useProgram(this.program);
163
+
164
+ // Get uniform locations
165
+ this.uniforms = {
166
+ uTex: gl.getUniformLocation(this.program, 'uTex'),
167
+ uRes: gl.getUniformLocation(this.program, 'uRes'),
168
+ uWarp: gl.getUniformLocation(this.program, 'uWarp'),
169
+ uScanHardness: gl.getUniformLocation(this.program, 'uScanHardness'),
170
+ uScanOpacity: gl.getUniformLocation(this.program, 'uScanOpacity'),
171
+ uMaskOpacity: gl.getUniformLocation(this.program, 'uMaskOpacity'),
172
+ uEnableWarp: gl.getUniformLocation(this.program, 'uEnableWarp'),
173
+ uEnableScanlines: gl.getUniformLocation(this.program, 'uEnableScanlines'),
174
+ uEnableMask: gl.getUniformLocation(this.program, 'uEnableMask'),
175
+ };
176
+
177
+ gl.uniform1i(this.uniforms.uTex, 0);
178
+
179
+ // Setup geometry (fullscreen triangle)
180
+ const buf = gl.createBuffer();
181
+ gl.bindBuffer(gl.ARRAY_BUFFER, buf);
182
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
183
+ gl.enableVertexAttribArray(0);
184
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
185
+
186
+ // Create texture
187
+ this.texture = gl.createTexture();
188
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
189
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
190
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
191
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
192
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
193
+
194
+ gl.clearColor(0.0, 0.0, 0.0, 0.0);
195
+
196
+ this.initialized = true;
197
+ }
198
+
199
+ private createShader(type: number, source: string): WebGLShader {
200
+ const gl = this.gl!;
201
+ const shader = gl.createShader(type)!;
202
+ gl.shaderSource(shader, source);
203
+ gl.compileShader(shader);
204
+
205
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
206
+ const info = gl.getShaderInfoLog(shader);
207
+ gl.deleteShader(shader);
208
+ throw new Error('Shader compile failed: ' + info);
209
+ }
210
+
211
+ return shader;
212
+ }
213
+
214
+ /** Check if renderer is ready */
215
+ isReady(): boolean {
216
+ return this.initialized;
217
+ }
218
+
219
+ /** Render CRT effect */
220
+ render(input: ImageInput | ImageData, options: CrtOptions = {}): ImageOutput {
221
+ if (!this.initialized || !this.gl || !this.canvas) {
222
+ throw new Error('Renderer not initialized');
223
+ }
224
+
225
+ const gl = this.gl;
226
+ const data = input instanceof ImageData ? input.data : input.data;
227
+ const width = input.width;
228
+ const height = input.height;
229
+
230
+ const scale = Math.min(32, Math.max(2, options.scale ?? 3));
231
+ const outWidth = width * scale;
232
+ const outHeight = height * scale;
233
+
234
+ // Resize canvas if needed
235
+ if (this.currentCanvasSize.width !== outWidth || this.currentCanvasSize.height !== outHeight) {
236
+ this.canvas.width = outWidth;
237
+ this.canvas.height = outHeight;
238
+ this.currentCanvasSize = { width: outWidth, height: outHeight };
239
+ gl.viewport(0, 0, outWidth, outHeight);
240
+ }
241
+
242
+ // Update uniforms
243
+ gl.uniform2f(this.uniforms.uRes, outWidth, outHeight);
244
+ gl.uniform2f(this.uniforms.uWarp, options.warpX ?? 0.015, options.warpY ?? 0.02);
245
+ gl.uniform1f(this.uniforms.uScanHardness, options.scanHardness ?? -4.0);
246
+ gl.uniform1f(this.uniforms.uScanOpacity, options.scanOpacity ?? 0.5);
247
+ gl.uniform1f(this.uniforms.uMaskOpacity, options.maskOpacity ?? 0.3);
248
+ gl.uniform1i(this.uniforms.uEnableWarp, options.enableWarp !== false ? 1 : 0);
249
+ gl.uniform1i(this.uniforms.uEnableScanlines, options.enableScanlines !== false ? 1 : 0);
250
+ gl.uniform1i(this.uniforms.uEnableMask, options.enableMask !== false ? 1 : 0);
251
+
252
+ // Update texture
253
+ gl.bindTexture(gl.TEXTURE_2D, this.texture);
254
+ if (this.currentTexSize.width !== width || this.currentTexSize.height !== height) {
255
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
256
+ this.currentTexSize = { width, height };
257
+ } else {
258
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
259
+ }
260
+
261
+ // Render
262
+ gl.clear(gl.COLOR_BUFFER_BIT);
263
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
264
+
265
+ // Read pixels
266
+ const pixels = new Uint8ClampedArray(outWidth * outHeight * 4);
267
+ gl.readPixels(0, 0, outWidth, outHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
268
+
269
+ return {
270
+ data: pixels,
271
+ width: outWidth,
272
+ height: outHeight,
273
+ };
274
+ }
275
+
276
+ /** Dispose resources */
277
+ dispose(): void {
278
+ if (this.gl) {
279
+ if (this.texture) this.gl.deleteTexture(this.texture);
280
+ if (this.program) this.gl.deleteProgram(this.program);
281
+ this.gl = null;
282
+ }
283
+ this.canvas = null;
284
+ this.initialized = false;
285
+ }
286
+ }
287
+
288
+ /** CRT presets */
289
+ export const CRT_PRESETS: Record<string, Partial<CrtOptions>> = {
290
+ default: {},
291
+ authentic: {
292
+ warpX: 0.02,
293
+ warpY: 0.025,
294
+ scanHardness: -6.0,
295
+ scanOpacity: 0.6,
296
+ maskOpacity: 0.4,
297
+ },
298
+ subtle: {
299
+ warpX: 0.008,
300
+ warpY: 0.01,
301
+ scanHardness: -3.0,
302
+ scanOpacity: 0.3,
303
+ maskOpacity: 0.15,
304
+ },
305
+ flat: {
306
+ warpX: 0,
307
+ warpY: 0,
308
+ enableWarp: false,
309
+ scanHardness: -4.0,
310
+ scanOpacity: 0.5,
311
+ maskOpacity: 0.3,
312
+ },
313
+ };