@kotaksurat/photobooth-frame-generator 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.
package/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # Photobooth Frame Generator
2
+
3
+ A fast and efficient TypeScript engine to automate placing user photos into a template frame's transparent slots. It analyzes the alpha channel of the frame to dynamically find transparent "slots" and creatively paints the user's photos underneath those areas.
4
+
5
+ ## Features
6
+
7
+ - **Dynamic Slot Detection**: Automatically finds empty (transparent) slots in a template frame using Breadth-First Search (BFS).
8
+ - **Auto Cover/Crop**: Intelligently fits your photos into the detected slots, preserving the aspect ratio (similar to `object-fit: cover`).
9
+ - **Memory Management**: Includes a `reset()` method to free memory by revoking File object URLs and hinting garbage collection.
10
+ - **Browser Ready**: Uses the HTML5 Canvas API and `ImageData` for fast, client-side processing.
11
+
12
+ ## Installation
13
+
14
+ ```sh
15
+ npm install
16
+ ```
17
+
18
+ ## Basic Usage
19
+
20
+ The engine supports inputs as `File` objects or `Base64` data URLs.
21
+
22
+ ```typescript
23
+ import { PhotoboothFrameGenerator } from 'photobooth-frame-generator';
24
+
25
+ // 1. Initialize the engine
26
+ const engine = new PhotoboothFrameGenerator({
27
+ outputFormat: 'image/jpeg',
28
+ quality: 0.95
29
+ });
30
+
31
+ async function processPhotos(frameFile: File, userPhotos: File[]) {
32
+ try {
33
+ // 2. Generate the result
34
+ const result = await engine.create(frameFile, userPhotos);
35
+
36
+ console.log(`Generated image with ${result.slotsFound} slots!`);
37
+
38
+ // Use result.dataUrl as the src for an HTML Image element
39
+ const img = new Image();
40
+ img.src = result.dataUrl;
41
+ document.body.appendChild(img);
42
+
43
+ } catch (error) {
44
+ console.error('Processing failed:', error);
45
+ } finally {
46
+ // 3. Clean up memory to avoid blobs piling up
47
+ engine.reset();
48
+ }
49
+ }
50
+ ```
51
+
52
+ ## Configuration
53
+
54
+ When instantiating `PhotoboothFrameGenerator`, you can pass an optional configuration object:
55
+
56
+ | Property | Type | Default | Description |
57
+ |----------|------|---------|-------------|
58
+ | `alphaThreshold` | `number` | `10` | The alpha channel threshold (0-255) below which a pixel is considered transparent. |
59
+ | `minSlotSize` | `number` | `50` | Minimum width/height in pixels to be considered a valid slot (avoids micro-artifacts being selected). |
60
+ | `outputFormat` | `string` | `'image/png'` | The mime type of the generated image (`image/png`, `image/jpeg`, `image/webp`). |
61
+ | `quality` | `number` | `0.92` | The image quality from `0.0` to `1.0` (applicable for jpeg/webp formats). |
62
+ | `fillEmptySlots` | `boolean` | `true` | If true, intelligently loops through provided photos to fill any remaining empty frame slots. |
63
+ | `slotExpansion` | `number` | `5` | Expansion in pixels added to each detected slot's edges to cover anti-aliased transparency gaps. |
64
+
65
+ ## How It Works
66
+ 1. **Load Assets:** Internally converts `File` objects to blob URLs to be drawn onto a virtual canvas.
67
+ 2. **Scan Alpha:** Performs a BFS scan over the `ImageData` to locate contiguous transparent regions (alpha < `alphaThreshold`).
68
+ 3. **Sort Slots:** Sorts detected regions topologically (top-to-bottom, left-to-right).
69
+ 4. **Draw Composition:** Draws the user photos onto the base layer positioned in the slots, and places the original frame natively as the top layer.
70
+
71
+ ## Packages & Dependencies Used
72
+
73
+ This engine is lightweight and utilizes standard browser APIs internally, but relies on a few core packages for development:
74
+
75
+ - **`typescript` & `@types/node`**: Enables strict static typings and smooth transpilation from `src/` to a publishable package.
76
+ - **`vitest`**: A modern and extremely fast test runner tailored for TypeScript and built natively around Vite syntax.
77
+ - **`jsdom`**: Used alongside Vitest to seamlessly mock essential HTML5 Browser environments (like `URL` & partial native APIs) during runtime testing.
78
+
79
+ ## Running the Example
80
+
81
+ We have provided a demo implementation showcasing how the package works in action globally.
82
+ You can find it under the `example/` directory.
83
+
84
+ To serve and test the example in your browser, run:
85
+ ```sh
86
+ npx vite dev example
87
+ ```
88
+ 1. Open the local link (e.g., `http://localhost:5173/`) in the browser.
89
+ 2. Under "Select Frame", upload a `PNG` containing transparent rectangular slots matching your final format.
90
+ 3. Under "Select Photos", choose as many images as you need corresponding to the holes on the given frame.
91
+ 4. Hit **Generate Result** to fetch the composed canvas snapshot.
92
+
93
+ ## Running Tests
94
+
95
+ We implement unit tests focusing on robust config parsers and safe memory resetting tools using `vitest`.
96
+
97
+ To execute the entire test suite, make sure you have installed standard modules with `npm install`, then run:
98
+ ```sh
99
+ npm run test
100
+ ```
101
+ This will discover scripts inside the `test/` directory using the `vitest.config.ts` rules, executing assertions efficiently on the JSDOM mock environment.
@@ -0,0 +1,19 @@
1
+ import { ImageSource, PhotoboothConfig, RenderResult } from './types';
2
+ export declare class PhotoboothFrameGenerator {
3
+ private config;
4
+ private objectUrls;
5
+ constructor(config?: PhotoboothConfig);
6
+ /**
7
+ * Fungsi utama untuk memproses frame dan foto.
8
+ * Mendukung input berupa Base64 string atau File object.
9
+ */
10
+ create(frameSource: ImageSource, userPhotos: ImageSource[]): Promise<RenderResult>;
11
+ /**
12
+ * Fungsi Reset untuk Manajemen Memori.
13
+ * WAJIB dipanggil setelah proses selesai atau saat komponen di-unmount.
14
+ */
15
+ reset(): void;
16
+ private loadImage;
17
+ private findSlotsBFS;
18
+ private drawCover;
19
+ }
package/dist/index.js ADDED
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PhotoboothFrameGenerator = void 0;
13
+ class PhotoboothFrameGenerator {
14
+ constructor(config) {
15
+ var _a, _b, _c, _d, _e, _f;
16
+ this.objectUrls = []; // Track untuk manajemen memori
17
+ this.config = {
18
+ alphaThreshold: (_a = config === null || config === void 0 ? void 0 : config.alphaThreshold) !== null && _a !== void 0 ? _a : 10,
19
+ minSlotSize: (_b = config === null || config === void 0 ? void 0 : config.minSlotSize) !== null && _b !== void 0 ? _b : 50,
20
+ outputFormat: (_c = config === null || config === void 0 ? void 0 : config.outputFormat) !== null && _c !== void 0 ? _c : 'image/png',
21
+ quality: (_d = config === null || config === void 0 ? void 0 : config.quality) !== null && _d !== void 0 ? _d : 0.92,
22
+ fillEmptySlots: (_e = config === null || config === void 0 ? void 0 : config.fillEmptySlots) !== null && _e !== void 0 ? _e : true,
23
+ slotExpansion: (_f = config === null || config === void 0 ? void 0 : config.slotExpansion) !== null && _f !== void 0 ? _f : 5,
24
+ };
25
+ }
26
+ /**
27
+ * Fungsi utama untuk memproses frame dan foto.
28
+ * Mendukung input berupa Base64 string atau File object.
29
+ */
30
+ create(frameSource, userPhotos) {
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ try {
33
+ const frame = yield this.loadImage(frameSource);
34
+ const canvas = document.createElement('canvas');
35
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
36
+ if (!ctx)
37
+ throw new Error("Canvas context 2D not found");
38
+ canvas.width = frame.width;
39
+ canvas.height = frame.height;
40
+ // 1. Deteksi Slot
41
+ ctx.drawImage(frame, 0, 0);
42
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
43
+ const slots = this.findSlotsBFS(imageData);
44
+ // 2. Render Final
45
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
46
+ // Gambar foto user (Layer bawah)
47
+ for (let i = 0; i < slots.length; i++) {
48
+ let photoSource = userPhotos[i];
49
+ // Jika fillEmptySlots true dan foto yang disuplai lebih sedikit dari slot
50
+ if (!photoSource && this.config.fillEmptySlots && userPhotos.length > 0) {
51
+ photoSource = userPhotos[i % userPhotos.length];
52
+ }
53
+ if (photoSource) {
54
+ const photo = yield this.loadImage(photoSource);
55
+ this.drawCover(ctx, photo, slots[i]);
56
+ }
57
+ }
58
+ // Gambar frame (Layer atas)
59
+ ctx.drawImage(frame, 0, 0);
60
+ return {
61
+ dataUrl: canvas.toDataURL(this.config.outputFormat, this.config.quality),
62
+ slotsFound: slots.length,
63
+ width: canvas.width,
64
+ height: canvas.height
65
+ };
66
+ }
67
+ catch (error) {
68
+ throw new Error(`PhotoboothFrameGenerator Error: ${error}`);
69
+ }
70
+ });
71
+ }
72
+ /**
73
+ * Fungsi Reset untuk Manajemen Memori.
74
+ * WAJIB dipanggil setelah proses selesai atau saat komponen di-unmount.
75
+ */
76
+ reset() {
77
+ // Revoke semua Object URL yang dibuat dari File untuk mengosongkan RAM
78
+ this.objectUrls.forEach(url => URL.revokeObjectURL(url));
79
+ this.objectUrls = [];
80
+ // Memberi sinyal ke GC bahwa referensi bisa dihapus
81
+ console.log("PhotoboothFrameGenerator: Memory cleared.");
82
+ }
83
+ loadImage(source) {
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ return new Promise((resolve, reject) => {
86
+ const img = new Image();
87
+ let url;
88
+ if (source instanceof File) {
89
+ url = URL.createObjectURL(source);
90
+ this.objectUrls.push(url); // Simpan untuk di-reset nanti
91
+ }
92
+ else {
93
+ url = source; // Base64 atau URL string
94
+ }
95
+ img.onload = () => resolve(img);
96
+ img.onerror = () => reject("Failed to load image source");
97
+ img.src = url;
98
+ });
99
+ });
100
+ }
101
+ findSlotsBFS(imageData) {
102
+ const { width, height, data } = imageData;
103
+ const visited = new Uint8Array(width * height);
104
+ const slots = [];
105
+ for (let i = 0; i < width * height; i++) {
106
+ if (data[i * 4 + 3] < this.config.alphaThreshold && !visited[i]) {
107
+ const queue = [i];
108
+ visited[i] = 1;
109
+ let minX = i % width, maxX = minX, minY = Math.floor(i / width), maxY = minY;
110
+ let head = 0;
111
+ while (head < queue.length) {
112
+ const curr = queue[head++];
113
+ const cx = curr % width, cy = Math.floor(curr / width);
114
+ const neighbors = [[cx + 1, cy], [cx - 1, cy], [cx, cy + 1], [cx, cy - 1]];
115
+ for (const [nx, ny] of neighbors) {
116
+ if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
117
+ const nIdx = ny * width + nx;
118
+ if (!visited[nIdx] && data[nIdx * 4 + 3] < this.config.alphaThreshold) {
119
+ visited[nIdx] = 1;
120
+ queue.push(nIdx);
121
+ if (nx < minX)
122
+ minX = nx;
123
+ if (nx > maxX)
124
+ maxX = nx;
125
+ if (ny < minY)
126
+ minY = ny;
127
+ if (ny > maxY)
128
+ maxY = ny;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ if (maxX - minX > this.config.minSlotSize) {
134
+ slots.push({ x: minX, y: minY, width: maxX - minX, height: maxY - minY });
135
+ }
136
+ }
137
+ }
138
+ return slots.sort((a, b) => (a.y - b.y) || (a.x - b.x));
139
+ }
140
+ drawCover(ctx, img, slot) {
141
+ const expansion = this.config.slotExpansion;
142
+ const targetX = slot.x - expansion;
143
+ const targetY = slot.y - expansion;
144
+ const targetW = slot.width + (expansion * 2);
145
+ const targetH = slot.height + (expansion * 2);
146
+ const imgRatio = img.width / img.height;
147
+ const slotRatio = targetW / targetH;
148
+ let sw, sh, sx, sy;
149
+ if (imgRatio > slotRatio) {
150
+ sw = img.height * slotRatio;
151
+ sh = img.height;
152
+ sx = (img.width - sw) / 2;
153
+ sy = 0;
154
+ }
155
+ else {
156
+ sw = img.width;
157
+ sh = img.width / slotRatio;
158
+ sx = 0;
159
+ sy = (img.height - sh) / 2;
160
+ }
161
+ ctx.drawImage(img, sx, sy, sw, sh, targetX, targetY, targetW, targetH);
162
+ }
163
+ }
164
+ exports.PhotoboothFrameGenerator = PhotoboothFrameGenerator;
@@ -0,0 +1,19 @@
1
+ import { ImageSource, PhotoboothConfig, RenderResult } from './types';
2
+ export declare class PhotoboothFrameGenerator {
3
+ private config;
4
+ private objectUrls;
5
+ constructor(config?: PhotoboothConfig);
6
+ /**
7
+ * Fungsi utama untuk memproses frame dan foto.
8
+ * Mendukung input berupa Base64 string atau File object.
9
+ */
10
+ create(frameSource: ImageSource, userPhotos: ImageSource[]): Promise<RenderResult>;
11
+ /**
12
+ * Fungsi Reset untuk Manajemen Memori.
13
+ * WAJIB dipanggil setelah proses selesai atau saat komponen di-unmount.
14
+ */
15
+ reset(): void;
16
+ private loadImage;
17
+ private findSlotsBFS;
18
+ private drawCover;
19
+ }
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PhotoboothFrameGenerator = void 0;
13
+ class PhotoboothFrameGenerator {
14
+ constructor(config) {
15
+ var _a, _b, _c, _d, _e, _f;
16
+ this.objectUrls = []; // Track untuk manajemen memori
17
+ this.config = {
18
+ alphaThreshold: (_a = config === null || config === void 0 ? void 0 : config.alphaThreshold) !== null && _a !== void 0 ? _a : 10,
19
+ minSlotSize: (_b = config === null || config === void 0 ? void 0 : config.minSlotSize) !== null && _b !== void 0 ? _b : 50,
20
+ outputFormat: (_c = config === null || config === void 0 ? void 0 : config.outputFormat) !== null && _c !== void 0 ? _c : 'image/png',
21
+ quality: (_d = config === null || config === void 0 ? void 0 : config.quality) !== null && _d !== void 0 ? _d : 0.92,
22
+ fillEmptySlots: (_e = config === null || config === void 0 ? void 0 : config.fillEmptySlots) !== null && _e !== void 0 ? _e : true,
23
+ slotExpansion: (_f = config === null || config === void 0 ? void 0 : config.slotExpansion) !== null && _f !== void 0 ? _f : 5,
24
+ };
25
+ }
26
+ /**
27
+ * Fungsi utama untuk memproses frame dan foto.
28
+ * Mendukung input berupa Base64 string atau File object.
29
+ */
30
+ create(frameSource, userPhotos) {
31
+ return __awaiter(this, void 0, void 0, function* () {
32
+ try {
33
+ const frame = yield this.loadImage(frameSource);
34
+ const canvas = document.createElement('canvas');
35
+ const ctx = canvas.getContext('2d', { willReadFrequently: true });
36
+ if (!ctx)
37
+ throw new Error("Canvas context 2D not found");
38
+ canvas.width = frame.width;
39
+ canvas.height = frame.height;
40
+ // 1. Deteksi Slot
41
+ ctx.drawImage(frame, 0, 0);
42
+ const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
43
+ const slots = this.findSlotsBFS(imageData);
44
+ // 2. Render Final
45
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
46
+ // Gambar foto user (Layer bawah)
47
+ for (let i = 0; i < slots.length; i++) {
48
+ let photoSource = userPhotos[i];
49
+ // Jika fillEmptySlots true dan foto yang disuplai lebih sedikit dari slot
50
+ if (!photoSource && this.config.fillEmptySlots && userPhotos.length > 0) {
51
+ photoSource = userPhotos[i % userPhotos.length];
52
+ }
53
+ if (photoSource) {
54
+ const photo = yield this.loadImage(photoSource);
55
+ this.drawCover(ctx, photo, slots[i]);
56
+ }
57
+ }
58
+ // Gambar frame (Layer atas)
59
+ ctx.drawImage(frame, 0, 0);
60
+ return {
61
+ dataUrl: canvas.toDataURL(this.config.outputFormat, this.config.quality),
62
+ slotsFound: slots.length,
63
+ width: canvas.width,
64
+ height: canvas.height
65
+ };
66
+ }
67
+ catch (error) {
68
+ throw new Error(`PhotoboothFrameGenerator Error: ${error}`);
69
+ }
70
+ });
71
+ }
72
+ /**
73
+ * Fungsi Reset untuk Manajemen Memori.
74
+ * WAJIB dipanggil setelah proses selesai atau saat komponen di-unmount.
75
+ */
76
+ reset() {
77
+ // Revoke semua Object URL yang dibuat dari File untuk mengosongkan RAM
78
+ this.objectUrls.forEach(url => URL.revokeObjectURL(url));
79
+ this.objectUrls = [];
80
+ // Memberi sinyal ke GC bahwa referensi bisa dihapus
81
+ console.log("PhotoboothFrameGenerator: Memory cleared.");
82
+ }
83
+ loadImage(source) {
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ return new Promise((resolve, reject) => {
86
+ const img = new Image();
87
+ let url;
88
+ if (source instanceof File) {
89
+ url = URL.createObjectURL(source);
90
+ this.objectUrls.push(url); // Simpan untuk di-reset nanti
91
+ }
92
+ else {
93
+ url = source; // Base64 atau URL string
94
+ }
95
+ img.onload = () => resolve(img);
96
+ img.onerror = () => reject("Failed to load image source");
97
+ img.src = url;
98
+ });
99
+ });
100
+ }
101
+ findSlotsBFS(imageData) {
102
+ const { width, height, data } = imageData;
103
+ const visited = new Uint8Array(width * height);
104
+ const slots = [];
105
+ for (let i = 0; i < width * height; i++) {
106
+ if (data[i * 4 + 3] < this.config.alphaThreshold && !visited[i]) {
107
+ const queue = [i];
108
+ visited[i] = 1;
109
+ let minX = i % width, maxX = minX, minY = Math.floor(i / width), maxY = minY;
110
+ let head = 0;
111
+ while (head < queue.length) {
112
+ const curr = queue[head++];
113
+ const cx = curr % width, cy = Math.floor(curr / width);
114
+ const neighbors = [[cx + 1, cy], [cx - 1, cy], [cx, cy + 1], [cx, cy - 1]];
115
+ for (const [nx, ny] of neighbors) {
116
+ if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
117
+ const nIdx = ny * width + nx;
118
+ if (!visited[nIdx] && data[nIdx * 4 + 3] < this.config.alphaThreshold) {
119
+ visited[nIdx] = 1;
120
+ queue.push(nIdx);
121
+ if (nx < minX)
122
+ minX = nx;
123
+ if (nx > maxX)
124
+ maxX = nx;
125
+ if (ny < minY)
126
+ minY = ny;
127
+ if (ny > maxY)
128
+ maxY = ny;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ if (maxX - minX > this.config.minSlotSize) {
134
+ slots.push({ x: minX, y: minY, width: maxX - minX, height: maxY - minY });
135
+ }
136
+ }
137
+ }
138
+ return slots.sort((a, b) => (a.y - b.y) || (a.x - b.x));
139
+ }
140
+ drawCover(ctx, img, slot) {
141
+ const expansion = this.config.slotExpansion;
142
+ const targetX = slot.x - expansion;
143
+ const targetY = slot.y - expansion;
144
+ const targetW = slot.width + (expansion * 2);
145
+ const targetH = slot.height + (expansion * 2);
146
+ const imgRatio = img.width / img.height;
147
+ const slotRatio = targetW / targetH;
148
+ let sw, sh, sx, sy;
149
+ if (imgRatio > slotRatio) {
150
+ sw = img.height * slotRatio;
151
+ sh = img.height;
152
+ sx = (img.width - sw) / 2;
153
+ sy = 0;
154
+ }
155
+ else {
156
+ sw = img.width;
157
+ sh = img.width / slotRatio;
158
+ sx = 0;
159
+ sy = (img.height - sh) / 2;
160
+ }
161
+ ctx.drawImage(img, sx, sy, sw, sh, targetX, targetY, targetW, targetH);
162
+ }
163
+ }
164
+ exports.PhotoboothFrameGenerator = PhotoboothFrameGenerator;
@@ -0,0 +1,21 @@
1
+ export type ImageSource = string | File;
2
+ export interface Slot {
3
+ x: number;
4
+ y: number;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface PhotoboothConfig {
9
+ alphaThreshold?: number;
10
+ minSlotSize?: number;
11
+ outputFormat?: 'image/png' | 'image/jpeg' | 'image/webp';
12
+ quality?: number;
13
+ fillEmptySlots?: boolean;
14
+ slotExpansion?: number;
15
+ }
16
+ export interface RenderResult {
17
+ dataUrl: string;
18
+ slotsFound: number;
19
+ width: number;
20
+ height: number;
21
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,21 @@
1
+ export type ImageSource = string | File;
2
+ export interface Slot {
3
+ x: number;
4
+ y: number;
5
+ width: number;
6
+ height: number;
7
+ }
8
+ export interface PhotoboothConfig {
9
+ alphaThreshold?: number;
10
+ minSlotSize?: number;
11
+ outputFormat?: 'image/png' | 'image/jpeg' | 'image/webp';
12
+ quality?: number;
13
+ fillEmptySlots?: boolean;
14
+ slotExpansion?: number;
15
+ }
16
+ export interface RenderResult {
17
+ dataUrl: string;
18
+ slotsFound: number;
19
+ width: number;
20
+ height: number;
21
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@kotaksurat/photobooth-frame-generator",
3
+ "version": "1.0.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "https://github.com/ahdja/photobooth-frame-generator"
7
+ },
8
+ "description": "A fast and efficient engine to automate placing user photos into a frame's transparent slots.",
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "prepublishOnly": "npm run build",
23
+ "test": "vitest run"
24
+ },
25
+ "keywords": [
26
+ "photobooth",
27
+ "canvas",
28
+ "image-processing",
29
+ "typescript"
30
+ ],
31
+ "author": "Arief Hidayat Djauhar",
32
+ "license": "ISC",
33
+ "type": "commonjs",
34
+ "devDependencies": {
35
+ "@types/node": "^25.5.2",
36
+ "jsdom": "^29.0.1",
37
+ "tslib": "^2.8.1",
38
+ "typescript": "^6.0.2",
39
+ "vitest": "^4.1.2"
40
+ }
41
+ }