@al8b/sprites 0.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/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # @al8b/sprites
2
+
3
+ Sprite asset helpers for L8B. This package wraps image frames into runtime-friendly sprite objects and includes convenience loaders for updating sprite data from asset payloads.
4
+
5
+ ## Public API
6
+
7
+ - `Sprite`
8
+ - `LoadSprite`
9
+ - `UpdateSprite`
10
+ - Type: `SpriteProperties`
11
+
12
+ ## Notes
13
+
14
+ - Built on top of `@al8b/image`.
15
+ - Used by runtime asset loading and by packages that render sprite-backed content.
16
+
17
+ ## Scripts
18
+
19
+ ```bash
20
+ bun run build
21
+ bun run test
22
+ bun run clean
23
+ ```
@@ -0,0 +1,2 @@
1
+ export { LoadSprite, Sprite, SpriteProperties, UpdateSprite } from './sprite.mjs';
2
+ import '@al8b/image';
@@ -0,0 +1,2 @@
1
+ export { LoadSprite, Sprite, SpriteProperties, UpdateSprite } from './sprite.js';
2
+ import '@al8b/image';
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ LoadSprite: () => LoadSprite,
25
+ Sprite: () => Sprite,
26
+ UpdateSprite: () => UpdateSprite
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/sprite.ts
31
+ var import_image = require("@al8b/image");
32
+ var Sprite = class {
33
+ static {
34
+ __name(this, "Sprite");
35
+ }
36
+ name = "";
37
+ frames = [];
38
+ animation_start = 0;
39
+ fps = 5;
40
+ ready = 0;
41
+ width;
42
+ height;
43
+ constructor(width, height) {
44
+ this.width = width;
45
+ this.height = height;
46
+ this.frames = [];
47
+ this.animation_start = 0;
48
+ this.fps = 5;
49
+ if (this.width > 0 && this.height > 0) {
50
+ this.frames.push(new import_image.Image(this.width, this.height));
51
+ this.ready = 1;
52
+ }
53
+ }
54
+ /**
55
+ * Set animation frames per second
56
+ * Preserves current animation position when changing FPS
57
+ */
58
+ setFPS(fps) {
59
+ const dt = 1e3 / this.fps;
60
+ const frame = (Date.now() - this.animation_start) / dt % this.frames.length;
61
+ this.fps = fps;
62
+ const newDt = 1e3 / fps;
63
+ this.animation_start = Date.now() - frame * newDt;
64
+ return fps;
65
+ }
66
+ /**
67
+ * Set the current animation frame
68
+ */
69
+ setFrame(frame) {
70
+ this.animation_start = Date.now() - 1e3 / this.fps * frame;
71
+ }
72
+ /**
73
+ * Get the current animation frame index
74
+ */
75
+ getFrame() {
76
+ const dt = 1e3 / this.fps;
77
+ return Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;
78
+ }
79
+ /**
80
+ * Get the canvas of the current frame
81
+ */
82
+ getCurrentFrameCanvas() {
83
+ if (!this.ready || this.frames.length === 0) {
84
+ return null;
85
+ }
86
+ if (this.frames.length > 1) {
87
+ if (this.animation_start === 0) {
88
+ this.animation_start = Date.now();
89
+ }
90
+ const frame = this.getFrame();
91
+ if (frame >= 0 && frame < this.frames.length) {
92
+ return this.frames[frame].canvas;
93
+ }
94
+ return this.frames[0].canvas;
95
+ }
96
+ return this.frames[0].canvas;
97
+ }
98
+ };
99
+ function resolveFrameCount(imgWidth, imgHeight, requested) {
100
+ if (imgWidth > 0 && imgHeight % imgWidth === 0) {
101
+ const available = imgHeight / imgWidth;
102
+ return Math.min(requested, available);
103
+ }
104
+ return requested;
105
+ }
106
+ __name(resolveFrameCount, "resolveFrameCount");
107
+ function LoadSprite(url, properties, loaded) {
108
+ const sprite = new Sprite(0, 0);
109
+ sprite.ready = 0;
110
+ const img = new window.Image();
111
+ if (location.protocol !== "file:") {
112
+ img.crossOrigin = "Anonymous";
113
+ }
114
+ img.src = url;
115
+ img.onload = () => {
116
+ sprite.ready = 1;
117
+ if (img.width > 0 && img.height > 0) {
118
+ if (properties?.fps) {
119
+ sprite.fps = properties.fps;
120
+ }
121
+ sprite.width = img.width;
122
+ const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
123
+ sprite.height = Math.round(img.height / numframes);
124
+ sprite.frames = [];
125
+ for (let i = 0; i < numframes; i++) {
126
+ const frame = new import_image.Image(sprite.width, sprite.height);
127
+ frame.initContext();
128
+ const ctx = frame.context;
129
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
130
+ ctx.drawImage(img, 0, -i * sprite.height);
131
+ sprite.frames.push(frame);
132
+ }
133
+ sprite.ready = 1;
134
+ }
135
+ if (loaded) {
136
+ loaded();
137
+ }
138
+ };
139
+ img.onerror = () => {
140
+ sprite.ready = 1;
141
+ };
142
+ return sprite;
143
+ }
144
+ __name(LoadSprite, "LoadSprite");
145
+ function UpdateSprite(sprite, img, properties) {
146
+ if (img.width > 0 && img.height > 0) {
147
+ if (properties?.fps) {
148
+ sprite.fps = properties.fps;
149
+ }
150
+ sprite.width = img.width;
151
+ const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
152
+ sprite.height = Math.round(img.height / numframes);
153
+ sprite.frames = [];
154
+ for (let i = 0; i < numframes; i++) {
155
+ const frame = new import_image.Image(sprite.width, sprite.height);
156
+ frame.initContext();
157
+ const ctx = frame.context;
158
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
159
+ ctx.drawImage(img, 0, -i * sprite.height);
160
+ sprite.frames.push(frame);
161
+ }
162
+ sprite.ready = 1;
163
+ }
164
+ }
165
+ __name(UpdateSprite, "UpdateSprite");
166
+ // Annotate the CommonJS export names for ESM import in node:
167
+ 0 && (module.exports = {
168
+ LoadSprite,
169
+ Sprite,
170
+ UpdateSprite
171
+ });
172
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/sprite.ts"],"sourcesContent":["/**\n * @al8b/sprites - Sprite class for animated graphics\n *\n * Provides utilities for creating and manipulating sprites:\n * - Sprite class for animated sprite support\n * - Multi-frame animation support\n */\n\nexport { Sprite, LoadSprite, UpdateSprite } from \"./sprite\";\nexport type { SpriteProperties } from \"./sprite\";\n","/**\n * Sprite - Animated sprite class for game graphics\n *\n * Provides sprite animation support with multiple frames.\n */\n\nimport { Image } from \"@al8b/image\";\n\nexport interface SpriteProperties {\n\tframes?: number;\n\tfps?: number;\n}\n\nexport class Sprite {\n\tpublic name: string = \"\";\n\tpublic frames: Image[] = [];\n\tpublic animation_start: number = 0;\n\tpublic fps: number = 5;\n\tpublic ready: number = 0;\n\tpublic width: number;\n\tpublic height: number;\n\n\tconstructor(width: number, height: number) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\tthis.frames = [];\n\t\tthis.animation_start = 0;\n\t\tthis.fps = 5;\n\n\t\tif (this.width > 0 && this.height > 0) {\n\t\t\tthis.frames.push(new Image(this.width, this.height));\n\t\t\tthis.ready = 1;\n\t\t}\n\t}\n\n\t/**\n\t * Set animation frames per second\n\t * Preserves current animation position when changing FPS\n\t */\n\tsetFPS(fps: number): number {\n\t\tconst dt = 1000 / this.fps;\n\t\tconst frame = ((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t\tthis.fps = fps;\n\t\tconst newDt = 1000 / fps;\n\t\tthis.animation_start = Date.now() - frame * newDt;\n\t\treturn fps;\n\t}\n\n\t/**\n\t * Set the current animation frame\n\t */\n\tsetFrame(frame: number): void {\n\t\tthis.animation_start = Date.now() - (1000 / this.fps) * frame;\n\t}\n\n\t/**\n\t * Get the current animation frame index\n\t */\n\tgetFrame(): number {\n\t\tconst dt = 1000 / this.fps;\n\t\treturn Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t}\n\n\t/**\n\t * Get the canvas of the current frame\n\t */\n\tgetCurrentFrameCanvas(): HTMLCanvasElement | null {\n\t\tif (!this.ready || this.frames.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.frames.length > 1) {\n\t\t\tif (this.animation_start === 0) {\n\t\t\t\tthis.animation_start = Date.now();\n\t\t\t}\n\t\t\tconst frame = this.getFrame();\n\t\t\tif (frame >= 0 && frame < this.frames.length) {\n\t\t\t\treturn this.frames[frame].canvas;\n\t\t\t}\n\t\t\treturn this.frames[0].canvas;\n\t\t}\n\n\t\treturn this.frames[0].canvas;\n\t}\n}\n\n/**\n * Resolve the actual number of animation frames for a spritesheet image.\n *\n * Strategy: if the image height is an exact multiple of its width (square frames),\n * cap the requested frame count to the available rows. Otherwise trust the caller.\n *\n * @param imgWidth - Natural width of the loaded image\n * @param imgHeight - Natural height of the loaded image\n * @param requested - Frame count requested via SpriteProperties (defaults to 1)\n */\nfunction resolveFrameCount(imgWidth: number, imgHeight: number, requested: number): number {\n\tif (imgWidth > 0 && imgHeight % imgWidth === 0) {\n\t\tconst available = imgHeight / imgWidth;\n\t\treturn Math.min(requested, available);\n\t}\n\treturn requested;\n}\n\n/**\n * Load a sprite from a URL\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite {\n\tconst sprite = new Sprite(0, 0);\n\tsprite.ready = 0;\n\n\tconst img = new window.Image();\n\tif (location.protocol !== \"file:\") {\n\t\timg.crossOrigin = \"Anonymous\";\n\t}\n\timg.src = url;\n\n\timg.onload = () => {\n\t\tsprite.ready = 1;\n\n\t\tif (img.width > 0 && img.height > 0) {\n\t\t\tif (properties?.fps) {\n\t\t\t\tsprite.fps = properties.fps;\n\t\t\t}\n\n\t\t\tsprite.width = img.width;\n\t\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\t\tsprite.height = Math.round(img.height / numframes);\n\t\t\tsprite.frames = [];\n\n\t\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\t\tframe.initContext();\n\t\t\t\tconst ctx = frame.context!;\n\t\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\t\tsprite.frames.push(frame);\n\t\t\t}\n\t\t\tsprite.ready = 1;\n\t\t}\n\n\t\tif (loaded) {\n\t\t\tloaded();\n\t\t}\n\t};\n\n\timg.onerror = () => {\n\t\tsprite.ready = 1;\n\t};\n\n\treturn sprite;\n}\n\n/**\n * Update a sprite from an image element\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void {\n\tif (img.width > 0 && img.height > 0) {\n\t\tif (properties?.fps) {\n\t\t\tsprite.fps = properties.fps;\n\t\t}\n\t\tsprite.width = img.width;\n\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\tsprite.height = Math.round(img.height / numframes);\n\t\tsprite.frames = [];\n\n\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\tframe.initContext();\n\t\t\tconst ctx = frame.context!;\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\tsprite.frames.push(frame);\n\t\t}\n\t\tsprite.ready = 1;\n\t}\n}\n"],"mappings":"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","names":["Sprite","name","frames","animation_start","fps","ready","width","height","push","Image","setFPS","dt","frame","Date","now","length","newDt","setFrame","getFrame","Math","floor","getCurrentFrameCanvas","canvas","resolveFrameCount","imgWidth","imgHeight","requested","available","min","LoadSprite","url","properties","loaded","sprite","img","window","location","protocol","crossOrigin","src","onload","numframes","round","i","initContext","ctx","context","setTransform","drawImage","onerror","UpdateSprite"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,145 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/sprite.ts
5
+ import { Image } from "@al8b/image";
6
+ var Sprite = class {
7
+ static {
8
+ __name(this, "Sprite");
9
+ }
10
+ name = "";
11
+ frames = [];
12
+ animation_start = 0;
13
+ fps = 5;
14
+ ready = 0;
15
+ width;
16
+ height;
17
+ constructor(width, height) {
18
+ this.width = width;
19
+ this.height = height;
20
+ this.frames = [];
21
+ this.animation_start = 0;
22
+ this.fps = 5;
23
+ if (this.width > 0 && this.height > 0) {
24
+ this.frames.push(new Image(this.width, this.height));
25
+ this.ready = 1;
26
+ }
27
+ }
28
+ /**
29
+ * Set animation frames per second
30
+ * Preserves current animation position when changing FPS
31
+ */
32
+ setFPS(fps) {
33
+ const dt = 1e3 / this.fps;
34
+ const frame = (Date.now() - this.animation_start) / dt % this.frames.length;
35
+ this.fps = fps;
36
+ const newDt = 1e3 / fps;
37
+ this.animation_start = Date.now() - frame * newDt;
38
+ return fps;
39
+ }
40
+ /**
41
+ * Set the current animation frame
42
+ */
43
+ setFrame(frame) {
44
+ this.animation_start = Date.now() - 1e3 / this.fps * frame;
45
+ }
46
+ /**
47
+ * Get the current animation frame index
48
+ */
49
+ getFrame() {
50
+ const dt = 1e3 / this.fps;
51
+ return Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;
52
+ }
53
+ /**
54
+ * Get the canvas of the current frame
55
+ */
56
+ getCurrentFrameCanvas() {
57
+ if (!this.ready || this.frames.length === 0) {
58
+ return null;
59
+ }
60
+ if (this.frames.length > 1) {
61
+ if (this.animation_start === 0) {
62
+ this.animation_start = Date.now();
63
+ }
64
+ const frame = this.getFrame();
65
+ if (frame >= 0 && frame < this.frames.length) {
66
+ return this.frames[frame].canvas;
67
+ }
68
+ return this.frames[0].canvas;
69
+ }
70
+ return this.frames[0].canvas;
71
+ }
72
+ };
73
+ function resolveFrameCount(imgWidth, imgHeight, requested) {
74
+ if (imgWidth > 0 && imgHeight % imgWidth === 0) {
75
+ const available = imgHeight / imgWidth;
76
+ return Math.min(requested, available);
77
+ }
78
+ return requested;
79
+ }
80
+ __name(resolveFrameCount, "resolveFrameCount");
81
+ function LoadSprite(url, properties, loaded) {
82
+ const sprite = new Sprite(0, 0);
83
+ sprite.ready = 0;
84
+ const img = new window.Image();
85
+ if (location.protocol !== "file:") {
86
+ img.crossOrigin = "Anonymous";
87
+ }
88
+ img.src = url;
89
+ img.onload = () => {
90
+ sprite.ready = 1;
91
+ if (img.width > 0 && img.height > 0) {
92
+ if (properties?.fps) {
93
+ sprite.fps = properties.fps;
94
+ }
95
+ sprite.width = img.width;
96
+ const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
97
+ sprite.height = Math.round(img.height / numframes);
98
+ sprite.frames = [];
99
+ for (let i = 0; i < numframes; i++) {
100
+ const frame = new Image(sprite.width, sprite.height);
101
+ frame.initContext();
102
+ const ctx = frame.context;
103
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
104
+ ctx.drawImage(img, 0, -i * sprite.height);
105
+ sprite.frames.push(frame);
106
+ }
107
+ sprite.ready = 1;
108
+ }
109
+ if (loaded) {
110
+ loaded();
111
+ }
112
+ };
113
+ img.onerror = () => {
114
+ sprite.ready = 1;
115
+ };
116
+ return sprite;
117
+ }
118
+ __name(LoadSprite, "LoadSprite");
119
+ function UpdateSprite(sprite, img, properties) {
120
+ if (img.width > 0 && img.height > 0) {
121
+ if (properties?.fps) {
122
+ sprite.fps = properties.fps;
123
+ }
124
+ sprite.width = img.width;
125
+ const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
126
+ sprite.height = Math.round(img.height / numframes);
127
+ sprite.frames = [];
128
+ for (let i = 0; i < numframes; i++) {
129
+ const frame = new Image(sprite.width, sprite.height);
130
+ frame.initContext();
131
+ const ctx = frame.context;
132
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
133
+ ctx.drawImage(img, 0, -i * sprite.height);
134
+ sprite.frames.push(frame);
135
+ }
136
+ sprite.ready = 1;
137
+ }
138
+ }
139
+ __name(UpdateSprite, "UpdateSprite");
140
+ export {
141
+ LoadSprite,
142
+ Sprite,
143
+ UpdateSprite
144
+ };
145
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sprite.ts"],"sourcesContent":["/**\n * Sprite - Animated sprite class for game graphics\n *\n * Provides sprite animation support with multiple frames.\n */\n\nimport { Image } from \"@al8b/image\";\n\nexport interface SpriteProperties {\n\tframes?: number;\n\tfps?: number;\n}\n\nexport class Sprite {\n\tpublic name: string = \"\";\n\tpublic frames: Image[] = [];\n\tpublic animation_start: number = 0;\n\tpublic fps: number = 5;\n\tpublic ready: number = 0;\n\tpublic width: number;\n\tpublic height: number;\n\n\tconstructor(width: number, height: number) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\tthis.frames = [];\n\t\tthis.animation_start = 0;\n\t\tthis.fps = 5;\n\n\t\tif (this.width > 0 && this.height > 0) {\n\t\t\tthis.frames.push(new Image(this.width, this.height));\n\t\t\tthis.ready = 1;\n\t\t}\n\t}\n\n\t/**\n\t * Set animation frames per second\n\t * Preserves current animation position when changing FPS\n\t */\n\tsetFPS(fps: number): number {\n\t\tconst dt = 1000 / this.fps;\n\t\tconst frame = ((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t\tthis.fps = fps;\n\t\tconst newDt = 1000 / fps;\n\t\tthis.animation_start = Date.now() - frame * newDt;\n\t\treturn fps;\n\t}\n\n\t/**\n\t * Set the current animation frame\n\t */\n\tsetFrame(frame: number): void {\n\t\tthis.animation_start = Date.now() - (1000 / this.fps) * frame;\n\t}\n\n\t/**\n\t * Get the current animation frame index\n\t */\n\tgetFrame(): number {\n\t\tconst dt = 1000 / this.fps;\n\t\treturn Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t}\n\n\t/**\n\t * Get the canvas of the current frame\n\t */\n\tgetCurrentFrameCanvas(): HTMLCanvasElement | null {\n\t\tif (!this.ready || this.frames.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.frames.length > 1) {\n\t\t\tif (this.animation_start === 0) {\n\t\t\t\tthis.animation_start = Date.now();\n\t\t\t}\n\t\t\tconst frame = this.getFrame();\n\t\t\tif (frame >= 0 && frame < this.frames.length) {\n\t\t\t\treturn this.frames[frame].canvas;\n\t\t\t}\n\t\t\treturn this.frames[0].canvas;\n\t\t}\n\n\t\treturn this.frames[0].canvas;\n\t}\n}\n\n/**\n * Resolve the actual number of animation frames for a spritesheet image.\n *\n * Strategy: if the image height is an exact multiple of its width (square frames),\n * cap the requested frame count to the available rows. Otherwise trust the caller.\n *\n * @param imgWidth - Natural width of the loaded image\n * @param imgHeight - Natural height of the loaded image\n * @param requested - Frame count requested via SpriteProperties (defaults to 1)\n */\nfunction resolveFrameCount(imgWidth: number, imgHeight: number, requested: number): number {\n\tif (imgWidth > 0 && imgHeight % imgWidth === 0) {\n\t\tconst available = imgHeight / imgWidth;\n\t\treturn Math.min(requested, available);\n\t}\n\treturn requested;\n}\n\n/**\n * Load a sprite from a URL\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite {\n\tconst sprite = new Sprite(0, 0);\n\tsprite.ready = 0;\n\n\tconst img = new window.Image();\n\tif (location.protocol !== \"file:\") {\n\t\timg.crossOrigin = \"Anonymous\";\n\t}\n\timg.src = url;\n\n\timg.onload = () => {\n\t\tsprite.ready = 1;\n\n\t\tif (img.width > 0 && img.height > 0) {\n\t\t\tif (properties?.fps) {\n\t\t\t\tsprite.fps = properties.fps;\n\t\t\t}\n\n\t\t\tsprite.width = img.width;\n\t\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\t\tsprite.height = Math.round(img.height / numframes);\n\t\t\tsprite.frames = [];\n\n\t\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\t\tframe.initContext();\n\t\t\t\tconst ctx = frame.context!;\n\t\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\t\tsprite.frames.push(frame);\n\t\t\t}\n\t\t\tsprite.ready = 1;\n\t\t}\n\n\t\tif (loaded) {\n\t\t\tloaded();\n\t\t}\n\t};\n\n\timg.onerror = () => {\n\t\tsprite.ready = 1;\n\t};\n\n\treturn sprite;\n}\n\n/**\n * Update a sprite from an image element\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void {\n\tif (img.width > 0 && img.height > 0) {\n\t\tif (properties?.fps) {\n\t\t\tsprite.fps = properties.fps;\n\t\t}\n\t\tsprite.width = img.width;\n\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\tsprite.height = Math.round(img.height / numframes);\n\t\tsprite.frames = [];\n\n\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\tframe.initContext();\n\t\t\tconst ctx = frame.context!;\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\tsprite.frames.push(frame);\n\t\t}\n\t\tsprite.ready = 1;\n\t}\n}\n"],"mappings":"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","names":["Image","Sprite","name","frames","animation_start","fps","ready","width","height","push","Image","setFPS","dt","frame","Date","now","length","newDt","setFrame","getFrame","Math","floor","getCurrentFrameCanvas","canvas","resolveFrameCount","imgWidth","imgHeight","requested","available","min","LoadSprite","url","properties","loaded","sprite","img","window","location","protocol","crossOrigin","src","onload","numframes","round","i","initContext","ctx","context","setTransform","drawImage","onerror","UpdateSprite"]}
@@ -0,0 +1,51 @@
1
+ import { Image } from '@al8b/image';
2
+
3
+ /**
4
+ * Sprite - Animated sprite class for game graphics
5
+ *
6
+ * Provides sprite animation support with multiple frames.
7
+ */
8
+
9
+ interface SpriteProperties {
10
+ frames?: number;
11
+ fps?: number;
12
+ }
13
+ declare class Sprite {
14
+ name: string;
15
+ frames: Image[];
16
+ animation_start: number;
17
+ fps: number;
18
+ ready: number;
19
+ width: number;
20
+ height: number;
21
+ constructor(width: number, height: number);
22
+ /**
23
+ * Set animation frames per second
24
+ * Preserves current animation position when changing FPS
25
+ */
26
+ setFPS(fps: number): number;
27
+ /**
28
+ * Set the current animation frame
29
+ */
30
+ setFrame(frame: number): void;
31
+ /**
32
+ * Get the current animation frame index
33
+ */
34
+ getFrame(): number;
35
+ /**
36
+ * Get the canvas of the current frame
37
+ */
38
+ getCurrentFrameCanvas(): HTMLCanvasElement | null;
39
+ }
40
+ /**
41
+ * Load a sprite from a URL
42
+ * Supports multi-frame spritesheets (vertical stacking)
43
+ */
44
+ declare function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite;
45
+ /**
46
+ * Update a sprite from an image element
47
+ * Supports multi-frame spritesheets (vertical stacking)
48
+ */
49
+ declare function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void;
50
+
51
+ export { LoadSprite, Sprite, type SpriteProperties, UpdateSprite };
@@ -0,0 +1,51 @@
1
+ import { Image } from '@al8b/image';
2
+
3
+ /**
4
+ * Sprite - Animated sprite class for game graphics
5
+ *
6
+ * Provides sprite animation support with multiple frames.
7
+ */
8
+
9
+ interface SpriteProperties {
10
+ frames?: number;
11
+ fps?: number;
12
+ }
13
+ declare class Sprite {
14
+ name: string;
15
+ frames: Image[];
16
+ animation_start: number;
17
+ fps: number;
18
+ ready: number;
19
+ width: number;
20
+ height: number;
21
+ constructor(width: number, height: number);
22
+ /**
23
+ * Set animation frames per second
24
+ * Preserves current animation position when changing FPS
25
+ */
26
+ setFPS(fps: number): number;
27
+ /**
28
+ * Set the current animation frame
29
+ */
30
+ setFrame(frame: number): void;
31
+ /**
32
+ * Get the current animation frame index
33
+ */
34
+ getFrame(): number;
35
+ /**
36
+ * Get the canvas of the current frame
37
+ */
38
+ getCurrentFrameCanvas(): HTMLCanvasElement | null;
39
+ }
40
+ /**
41
+ * Load a sprite from a URL
42
+ * Supports multi-frame spritesheets (vertical stacking)
43
+ */
44
+ declare function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite;
45
+ /**
46
+ * Update a sprite from an image element
47
+ * Supports multi-frame spritesheets (vertical stacking)
48
+ */
49
+ declare function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void;
50
+
51
+ export { LoadSprite, Sprite, type SpriteProperties, UpdateSprite };
package/dist/sprite.js ADDED
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/sprite.ts
22
+ var sprite_exports = {};
23
+ __export(sprite_exports, {
24
+ LoadSprite: () => LoadSprite,
25
+ Sprite: () => Sprite,
26
+ UpdateSprite: () => UpdateSprite
27
+ });
28
+ module.exports = __toCommonJS(sprite_exports);
29
+ var import_image = require("@al8b/image");
30
+ var Sprite = class {
31
+ static {
32
+ __name(this, "Sprite");
33
+ }
34
+ name = "";
35
+ frames = [];
36
+ animation_start = 0;
37
+ fps = 5;
38
+ ready = 0;
39
+ width;
40
+ height;
41
+ constructor(width, height) {
42
+ this.width = width;
43
+ this.height = height;
44
+ this.frames = [];
45
+ this.animation_start = 0;
46
+ this.fps = 5;
47
+ if (this.width > 0 && this.height > 0) {
48
+ this.frames.push(new import_image.Image(this.width, this.height));
49
+ this.ready = 1;
50
+ }
51
+ }
52
+ /**
53
+ * Set animation frames per second
54
+ * Preserves current animation position when changing FPS
55
+ */
56
+ setFPS(fps) {
57
+ const dt = 1e3 / this.fps;
58
+ const frame = (Date.now() - this.animation_start) / dt % this.frames.length;
59
+ this.fps = fps;
60
+ const newDt = 1e3 / fps;
61
+ this.animation_start = Date.now() - frame * newDt;
62
+ return fps;
63
+ }
64
+ /**
65
+ * Set the current animation frame
66
+ */
67
+ setFrame(frame) {
68
+ this.animation_start = Date.now() - 1e3 / this.fps * frame;
69
+ }
70
+ /**
71
+ * Get the current animation frame index
72
+ */
73
+ getFrame() {
74
+ const dt = 1e3 / this.fps;
75
+ return Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;
76
+ }
77
+ /**
78
+ * Get the canvas of the current frame
79
+ */
80
+ getCurrentFrameCanvas() {
81
+ if (!this.ready || this.frames.length === 0) {
82
+ return null;
83
+ }
84
+ if (this.frames.length > 1) {
85
+ if (this.animation_start === 0) {
86
+ this.animation_start = Date.now();
87
+ }
88
+ const frame = this.getFrame();
89
+ if (frame >= 0 && frame < this.frames.length) {
90
+ return this.frames[frame].canvas;
91
+ }
92
+ return this.frames[0].canvas;
93
+ }
94
+ return this.frames[0].canvas;
95
+ }
96
+ };
97
+ function resolveFrameCount(imgWidth, imgHeight, requested) {
98
+ if (imgWidth > 0 && imgHeight % imgWidth === 0) {
99
+ const available = imgHeight / imgWidth;
100
+ return Math.min(requested, available);
101
+ }
102
+ return requested;
103
+ }
104
+ __name(resolveFrameCount, "resolveFrameCount");
105
+ function LoadSprite(url, properties, loaded) {
106
+ const sprite = new Sprite(0, 0);
107
+ sprite.ready = 0;
108
+ const img = new window.Image();
109
+ if (location.protocol !== "file:") {
110
+ img.crossOrigin = "Anonymous";
111
+ }
112
+ img.src = url;
113
+ img.onload = () => {
114
+ sprite.ready = 1;
115
+ if (img.width > 0 && img.height > 0) {
116
+ if (properties?.fps) {
117
+ sprite.fps = properties.fps;
118
+ }
119
+ sprite.width = img.width;
120
+ const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
121
+ sprite.height = Math.round(img.height / numframes);
122
+ sprite.frames = [];
123
+ for (let i = 0; i < numframes; i++) {
124
+ const frame = new import_image.Image(sprite.width, sprite.height);
125
+ frame.initContext();
126
+ const ctx = frame.context;
127
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
128
+ ctx.drawImage(img, 0, -i * sprite.height);
129
+ sprite.frames.push(frame);
130
+ }
131
+ sprite.ready = 1;
132
+ }
133
+ if (loaded) {
134
+ loaded();
135
+ }
136
+ };
137
+ img.onerror = () => {
138
+ sprite.ready = 1;
139
+ };
140
+ return sprite;
141
+ }
142
+ __name(LoadSprite, "LoadSprite");
143
+ function UpdateSprite(sprite, img, properties) {
144
+ if (img.width > 0 && img.height > 0) {
145
+ if (properties?.fps) {
146
+ sprite.fps = properties.fps;
147
+ }
148
+ sprite.width = img.width;
149
+ const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
150
+ sprite.height = Math.round(img.height / numframes);
151
+ sprite.frames = [];
152
+ for (let i = 0; i < numframes; i++) {
153
+ const frame = new import_image.Image(sprite.width, sprite.height);
154
+ frame.initContext();
155
+ const ctx = frame.context;
156
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
157
+ ctx.drawImage(img, 0, -i * sprite.height);
158
+ sprite.frames.push(frame);
159
+ }
160
+ sprite.ready = 1;
161
+ }
162
+ }
163
+ __name(UpdateSprite, "UpdateSprite");
164
+ // Annotate the CommonJS export names for ESM import in node:
165
+ 0 && (module.exports = {
166
+ LoadSprite,
167
+ Sprite,
168
+ UpdateSprite
169
+ });
170
+ //# sourceMappingURL=sprite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sprite.ts"],"sourcesContent":["/**\n * Sprite - Animated sprite class for game graphics\n *\n * Provides sprite animation support with multiple frames.\n */\n\nimport { Image } from \"@al8b/image\";\n\nexport interface SpriteProperties {\n\tframes?: number;\n\tfps?: number;\n}\n\nexport class Sprite {\n\tpublic name: string = \"\";\n\tpublic frames: Image[] = [];\n\tpublic animation_start: number = 0;\n\tpublic fps: number = 5;\n\tpublic ready: number = 0;\n\tpublic width: number;\n\tpublic height: number;\n\n\tconstructor(width: number, height: number) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\tthis.frames = [];\n\t\tthis.animation_start = 0;\n\t\tthis.fps = 5;\n\n\t\tif (this.width > 0 && this.height > 0) {\n\t\t\tthis.frames.push(new Image(this.width, this.height));\n\t\t\tthis.ready = 1;\n\t\t}\n\t}\n\n\t/**\n\t * Set animation frames per second\n\t * Preserves current animation position when changing FPS\n\t */\n\tsetFPS(fps: number): number {\n\t\tconst dt = 1000 / this.fps;\n\t\tconst frame = ((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t\tthis.fps = fps;\n\t\tconst newDt = 1000 / fps;\n\t\tthis.animation_start = Date.now() - frame * newDt;\n\t\treturn fps;\n\t}\n\n\t/**\n\t * Set the current animation frame\n\t */\n\tsetFrame(frame: number): void {\n\t\tthis.animation_start = Date.now() - (1000 / this.fps) * frame;\n\t}\n\n\t/**\n\t * Get the current animation frame index\n\t */\n\tgetFrame(): number {\n\t\tconst dt = 1000 / this.fps;\n\t\treturn Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t}\n\n\t/**\n\t * Get the canvas of the current frame\n\t */\n\tgetCurrentFrameCanvas(): HTMLCanvasElement | null {\n\t\tif (!this.ready || this.frames.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.frames.length > 1) {\n\t\t\tif (this.animation_start === 0) {\n\t\t\t\tthis.animation_start = Date.now();\n\t\t\t}\n\t\t\tconst frame = this.getFrame();\n\t\t\tif (frame >= 0 && frame < this.frames.length) {\n\t\t\t\treturn this.frames[frame].canvas;\n\t\t\t}\n\t\t\treturn this.frames[0].canvas;\n\t\t}\n\n\t\treturn this.frames[0].canvas;\n\t}\n}\n\n/**\n * Resolve the actual number of animation frames for a spritesheet image.\n *\n * Strategy: if the image height is an exact multiple of its width (square frames),\n * cap the requested frame count to the available rows. Otherwise trust the caller.\n *\n * @param imgWidth - Natural width of the loaded image\n * @param imgHeight - Natural height of the loaded image\n * @param requested - Frame count requested via SpriteProperties (defaults to 1)\n */\nfunction resolveFrameCount(imgWidth: number, imgHeight: number, requested: number): number {\n\tif (imgWidth > 0 && imgHeight % imgWidth === 0) {\n\t\tconst available = imgHeight / imgWidth;\n\t\treturn Math.min(requested, available);\n\t}\n\treturn requested;\n}\n\n/**\n * Load a sprite from a URL\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite {\n\tconst sprite = new Sprite(0, 0);\n\tsprite.ready = 0;\n\n\tconst img = new window.Image();\n\tif (location.protocol !== \"file:\") {\n\t\timg.crossOrigin = \"Anonymous\";\n\t}\n\timg.src = url;\n\n\timg.onload = () => {\n\t\tsprite.ready = 1;\n\n\t\tif (img.width > 0 && img.height > 0) {\n\t\t\tif (properties?.fps) {\n\t\t\t\tsprite.fps = properties.fps;\n\t\t\t}\n\n\t\t\tsprite.width = img.width;\n\t\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\t\tsprite.height = Math.round(img.height / numframes);\n\t\t\tsprite.frames = [];\n\n\t\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\t\tframe.initContext();\n\t\t\t\tconst ctx = frame.context!;\n\t\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\t\tsprite.frames.push(frame);\n\t\t\t}\n\t\t\tsprite.ready = 1;\n\t\t}\n\n\t\tif (loaded) {\n\t\t\tloaded();\n\t\t}\n\t};\n\n\timg.onerror = () => {\n\t\tsprite.ready = 1;\n\t};\n\n\treturn sprite;\n}\n\n/**\n * Update a sprite from an image element\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void {\n\tif (img.width > 0 && img.height > 0) {\n\t\tif (properties?.fps) {\n\t\t\tsprite.fps = properties.fps;\n\t\t}\n\t\tsprite.width = img.width;\n\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\tsprite.height = Math.round(img.height / numframes);\n\t\tsprite.frames = [];\n\n\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\tframe.initContext();\n\t\t\tconst ctx = frame.context!;\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\tsprite.frames.push(frame);\n\t\t}\n\t\tsprite.ready = 1;\n\t}\n}\n"],"mappings":"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","names":["Sprite","name","frames","animation_start","fps","ready","width","height","push","Image","setFPS","dt","frame","Date","now","length","newDt","setFrame","getFrame","Math","floor","getCurrentFrameCanvas","canvas","resolveFrameCount","imgWidth","imgHeight","requested","available","min","LoadSprite","url","properties","loaded","sprite","img","window","location","protocol","crossOrigin","src","onload","numframes","round","i","initContext","ctx","context","setTransform","drawImage","onerror","UpdateSprite"]}
@@ -0,0 +1,145 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/sprite.ts
5
+ import { Image } from "@al8b/image";
6
+ var Sprite = class {
7
+ static {
8
+ __name(this, "Sprite");
9
+ }
10
+ name = "";
11
+ frames = [];
12
+ animation_start = 0;
13
+ fps = 5;
14
+ ready = 0;
15
+ width;
16
+ height;
17
+ constructor(width, height) {
18
+ this.width = width;
19
+ this.height = height;
20
+ this.frames = [];
21
+ this.animation_start = 0;
22
+ this.fps = 5;
23
+ if (this.width > 0 && this.height > 0) {
24
+ this.frames.push(new Image(this.width, this.height));
25
+ this.ready = 1;
26
+ }
27
+ }
28
+ /**
29
+ * Set animation frames per second
30
+ * Preserves current animation position when changing FPS
31
+ */
32
+ setFPS(fps) {
33
+ const dt = 1e3 / this.fps;
34
+ const frame = (Date.now() - this.animation_start) / dt % this.frames.length;
35
+ this.fps = fps;
36
+ const newDt = 1e3 / fps;
37
+ this.animation_start = Date.now() - frame * newDt;
38
+ return fps;
39
+ }
40
+ /**
41
+ * Set the current animation frame
42
+ */
43
+ setFrame(frame) {
44
+ this.animation_start = Date.now() - 1e3 / this.fps * frame;
45
+ }
46
+ /**
47
+ * Get the current animation frame index
48
+ */
49
+ getFrame() {
50
+ const dt = 1e3 / this.fps;
51
+ return Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;
52
+ }
53
+ /**
54
+ * Get the canvas of the current frame
55
+ */
56
+ getCurrentFrameCanvas() {
57
+ if (!this.ready || this.frames.length === 0) {
58
+ return null;
59
+ }
60
+ if (this.frames.length > 1) {
61
+ if (this.animation_start === 0) {
62
+ this.animation_start = Date.now();
63
+ }
64
+ const frame = this.getFrame();
65
+ if (frame >= 0 && frame < this.frames.length) {
66
+ return this.frames[frame].canvas;
67
+ }
68
+ return this.frames[0].canvas;
69
+ }
70
+ return this.frames[0].canvas;
71
+ }
72
+ };
73
+ function resolveFrameCount(imgWidth, imgHeight, requested) {
74
+ if (imgWidth > 0 && imgHeight % imgWidth === 0) {
75
+ const available = imgHeight / imgWidth;
76
+ return Math.min(requested, available);
77
+ }
78
+ return requested;
79
+ }
80
+ __name(resolveFrameCount, "resolveFrameCount");
81
+ function LoadSprite(url, properties, loaded) {
82
+ const sprite = new Sprite(0, 0);
83
+ sprite.ready = 0;
84
+ const img = new window.Image();
85
+ if (location.protocol !== "file:") {
86
+ img.crossOrigin = "Anonymous";
87
+ }
88
+ img.src = url;
89
+ img.onload = () => {
90
+ sprite.ready = 1;
91
+ if (img.width > 0 && img.height > 0) {
92
+ if (properties?.fps) {
93
+ sprite.fps = properties.fps;
94
+ }
95
+ sprite.width = img.width;
96
+ const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
97
+ sprite.height = Math.round(img.height / numframes);
98
+ sprite.frames = [];
99
+ for (let i = 0; i < numframes; i++) {
100
+ const frame = new Image(sprite.width, sprite.height);
101
+ frame.initContext();
102
+ const ctx = frame.context;
103
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
104
+ ctx.drawImage(img, 0, -i * sprite.height);
105
+ sprite.frames.push(frame);
106
+ }
107
+ sprite.ready = 1;
108
+ }
109
+ if (loaded) {
110
+ loaded();
111
+ }
112
+ };
113
+ img.onerror = () => {
114
+ sprite.ready = 1;
115
+ };
116
+ return sprite;
117
+ }
118
+ __name(LoadSprite, "LoadSprite");
119
+ function UpdateSprite(sprite, img, properties) {
120
+ if (img.width > 0 && img.height > 0) {
121
+ if (properties?.fps) {
122
+ sprite.fps = properties.fps;
123
+ }
124
+ sprite.width = img.width;
125
+ const numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);
126
+ sprite.height = Math.round(img.height / numframes);
127
+ sprite.frames = [];
128
+ for (let i = 0; i < numframes; i++) {
129
+ const frame = new Image(sprite.width, sprite.height);
130
+ frame.initContext();
131
+ const ctx = frame.context;
132
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
133
+ ctx.drawImage(img, 0, -i * sprite.height);
134
+ sprite.frames.push(frame);
135
+ }
136
+ sprite.ready = 1;
137
+ }
138
+ }
139
+ __name(UpdateSprite, "UpdateSprite");
140
+ export {
141
+ LoadSprite,
142
+ Sprite,
143
+ UpdateSprite
144
+ };
145
+ //# sourceMappingURL=sprite.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/sprite.ts"],"sourcesContent":["/**\n * Sprite - Animated sprite class for game graphics\n *\n * Provides sprite animation support with multiple frames.\n */\n\nimport { Image } from \"@al8b/image\";\n\nexport interface SpriteProperties {\n\tframes?: number;\n\tfps?: number;\n}\n\nexport class Sprite {\n\tpublic name: string = \"\";\n\tpublic frames: Image[] = [];\n\tpublic animation_start: number = 0;\n\tpublic fps: number = 5;\n\tpublic ready: number = 0;\n\tpublic width: number;\n\tpublic height: number;\n\n\tconstructor(width: number, height: number) {\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\tthis.frames = [];\n\t\tthis.animation_start = 0;\n\t\tthis.fps = 5;\n\n\t\tif (this.width > 0 && this.height > 0) {\n\t\t\tthis.frames.push(new Image(this.width, this.height));\n\t\t\tthis.ready = 1;\n\t\t}\n\t}\n\n\t/**\n\t * Set animation frames per second\n\t * Preserves current animation position when changing FPS\n\t */\n\tsetFPS(fps: number): number {\n\t\tconst dt = 1000 / this.fps;\n\t\tconst frame = ((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t\tthis.fps = fps;\n\t\tconst newDt = 1000 / fps;\n\t\tthis.animation_start = Date.now() - frame * newDt;\n\t\treturn fps;\n\t}\n\n\t/**\n\t * Set the current animation frame\n\t */\n\tsetFrame(frame: number): void {\n\t\tthis.animation_start = Date.now() - (1000 / this.fps) * frame;\n\t}\n\n\t/**\n\t * Get the current animation frame index\n\t */\n\tgetFrame(): number {\n\t\tconst dt = 1000 / this.fps;\n\t\treturn Math.floor((Date.now() - this.animation_start) / dt) % this.frames.length;\n\t}\n\n\t/**\n\t * Get the canvas of the current frame\n\t */\n\tgetCurrentFrameCanvas(): HTMLCanvasElement | null {\n\t\tif (!this.ready || this.frames.length === 0) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (this.frames.length > 1) {\n\t\t\tif (this.animation_start === 0) {\n\t\t\t\tthis.animation_start = Date.now();\n\t\t\t}\n\t\t\tconst frame = this.getFrame();\n\t\t\tif (frame >= 0 && frame < this.frames.length) {\n\t\t\t\treturn this.frames[frame].canvas;\n\t\t\t}\n\t\t\treturn this.frames[0].canvas;\n\t\t}\n\n\t\treturn this.frames[0].canvas;\n\t}\n}\n\n/**\n * Resolve the actual number of animation frames for a spritesheet image.\n *\n * Strategy: if the image height is an exact multiple of its width (square frames),\n * cap the requested frame count to the available rows. Otherwise trust the caller.\n *\n * @param imgWidth - Natural width of the loaded image\n * @param imgHeight - Natural height of the loaded image\n * @param requested - Frame count requested via SpriteProperties (defaults to 1)\n */\nfunction resolveFrameCount(imgWidth: number, imgHeight: number, requested: number): number {\n\tif (imgWidth > 0 && imgHeight % imgWidth === 0) {\n\t\tconst available = imgHeight / imgWidth;\n\t\treturn Math.min(requested, available);\n\t}\n\treturn requested;\n}\n\n/**\n * Load a sprite from a URL\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function LoadSprite(url: string, properties?: SpriteProperties, loaded?: () => void): Sprite {\n\tconst sprite = new Sprite(0, 0);\n\tsprite.ready = 0;\n\n\tconst img = new window.Image();\n\tif (location.protocol !== \"file:\") {\n\t\timg.crossOrigin = \"Anonymous\";\n\t}\n\timg.src = url;\n\n\timg.onload = () => {\n\t\tsprite.ready = 1;\n\n\t\tif (img.width > 0 && img.height > 0) {\n\t\t\tif (properties?.fps) {\n\t\t\t\tsprite.fps = properties.fps;\n\t\t\t}\n\n\t\t\tsprite.width = img.width;\n\t\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\t\tsprite.height = Math.round(img.height / numframes);\n\t\t\tsprite.frames = [];\n\n\t\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\t\tframe.initContext();\n\t\t\t\tconst ctx = frame.context!;\n\t\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\t\tsprite.frames.push(frame);\n\t\t\t}\n\t\t\tsprite.ready = 1;\n\t\t}\n\n\t\tif (loaded) {\n\t\t\tloaded();\n\t\t}\n\t};\n\n\timg.onerror = () => {\n\t\tsprite.ready = 1;\n\t};\n\n\treturn sprite;\n}\n\n/**\n * Update a sprite from an image element\n * Supports multi-frame spritesheets (vertical stacking)\n */\nexport function UpdateSprite(sprite: Sprite, img: HTMLImageElement, properties?: SpriteProperties): void {\n\tif (img.width > 0 && img.height > 0) {\n\t\tif (properties?.fps) {\n\t\t\tsprite.fps = properties.fps;\n\t\t}\n\t\tsprite.width = img.width;\n\t\tconst numframes = resolveFrameCount(img.width, img.height, properties?.frames ?? 1);\n\t\tsprite.height = Math.round(img.height / numframes);\n\t\tsprite.frames = [];\n\n\t\tfor (let i = 0; i < numframes; i++) {\n\t\t\tconst frame = new Image(sprite.width, sprite.height);\n\t\t\tframe.initContext();\n\t\t\tconst ctx = frame.context!;\n\t\t\tctx.setTransform(1, 0, 0, 1, 0, 0);\n\t\t\tctx.drawImage(img, 0, -i * sprite.height);\n\t\t\tsprite.frames.push(frame);\n\t\t}\n\t\tsprite.ready = 1;\n\t}\n}\n"],"mappings":"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","names":["Image","Sprite","name","frames","animation_start","fps","ready","width","height","push","Image","setFPS","dt","frame","Date","now","length","newDt","setFrame","getFrame","Math","floor","getCurrentFrameCanvas","canvas","resolveFrameCount","imgWidth","imgHeight","requested","available","min","LoadSprite","url","properties","loaded","sprite","img","window","location","protocol","crossOrigin","src","onload","numframes","round","i","initContext","ctx","context","setTransform","drawImage","onerror","UpdateSprite"]}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@al8b/sprites",
3
+ "version": "0.1.0",
4
+ "sideEffects": false,
5
+ "files": [
6
+ "dist/**/*",
7
+ "README.md",
8
+ "package.json"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "clean": "bun --bun ../../../scripts/clean-package.mjs dist",
13
+ "test": "vitest run --passWithNoTests"
14
+ },
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.mjs",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.js"
23
+ }
24
+ },
25
+ "dependencies": {
26
+ "@al8b/diagnostics": "workspace:*",
27
+ "@al8b/image": "workspace:*"
28
+ },
29
+ "keywords": [
30
+ "sprites"
31
+ ],
32
+ "devDependencies": {
33
+ "@types/jsdom": "^27.0.0",
34
+ "@vitest/ui": "^4.0.9",
35
+ "happy-dom": "^20.0.10",
36
+ "jsdom": "^24.1.3",
37
+ "vitest": "^4.0.9"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ }
42
+ }