@yogiswara/honcho-editor-ui 2.7.13 → 2.7.14
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/package.json +1 -1
- package/dist/color.d.ts +0 -9
- package/dist/color.js +0 -9
- package/dist/components/editor/GalleryAlbum/AlbumImageGallery.d.ts +0 -8
- package/dist/components/editor/GalleryAlbum/AlbumImageGallery.js +0 -28
- package/dist/components/editor/GalleryAlbum/ImageItem.d.ts +0 -10
- package/dist/components/editor/GalleryAlbum/ImageItem.js +0 -81
- package/dist/components/editor/HAccordionAspectRatio.d.ts +0 -14
- package/dist/components/editor/HAccordionAspectRatio.js +0 -102
- package/dist/components/editor/HAccordionColor.d.ts +0 -16
- package/dist/components/editor/HAccordionColor.js +0 -282
- package/dist/components/editor/HAccordionColorAdjustment.d.ts +0 -35
- package/dist/components/editor/HAccordionColorAdjustment.js +0 -31
- package/dist/components/editor/HAccordionDetails.d.ts +0 -12
- package/dist/components/editor/HAccordionDetails.js +0 -157
- package/dist/components/editor/HAccordionLight.d.ts +0 -20
- package/dist/components/editor/HAccordionLight.js +0 -414
- package/dist/components/editor/HAccordionPreset.d.ts +0 -23
- package/dist/components/editor/HAccordionPreset.js +0 -50
- package/dist/components/editor/HAlertBox.d.ts +0 -8
- package/dist/components/editor/HAlertBox.js +0 -55
- package/dist/components/editor/HAspectRatioMobile.d.ts +0 -0
- package/dist/components/editor/HAspectRatioMobile.js +0 -1
- package/dist/components/editor/HBulkAccordionColorAdjustment.d.ts +0 -55
- package/dist/components/editor/HBulkAccordionColorAdjustment.js +0 -31
- package/dist/components/editor/HBulkAccordionColorAdjustmentColors.d.ts +0 -20
- package/dist/components/editor/HBulkAccordionColorAdjustmentColors.js +0 -121
- package/dist/components/editor/HBulkAccordionColorAdjustmentDetails.d.ts +0 -12
- package/dist/components/editor/HBulkAccordionColorAdjustmentDetails.js +0 -65
- package/dist/components/editor/HBulkAccordionColorAdjustmentLight.d.ts +0 -28
- package/dist/components/editor/HBulkAccordionColorAdjustmentLight.js +0 -177
- package/dist/components/editor/HBulkColorAdjustmentMobile.d.ts +0 -53
- package/dist/components/editor/HBulkColorAdjustmentMobile.js +0 -16
- package/dist/components/editor/HBulkColorMobile.d.ts +0 -20
- package/dist/components/editor/HBulkColorMobile.js +0 -121
- package/dist/components/editor/HBulkDetailsMobile.d.ts +0 -12
- package/dist/components/editor/HBulkDetailsMobile.js +0 -65
- package/dist/components/editor/HBulkLightMobile.d.ts +0 -28
- package/dist/components/editor/HBulkLightMobile.js +0 -192
- package/dist/components/editor/HBulkPreset.d.ts +0 -24
- package/dist/components/editor/HBulkPreset.js +0 -43
- package/dist/components/editor/HBulkPresetMobile.d.ts +0 -15
- package/dist/components/editor/HBulkPresetMobile.js +0 -26
- package/dist/components/editor/HDialogBox.d.ts +0 -18
- package/dist/components/editor/HDialogBox.js +0 -51
- package/dist/components/editor/HDialogCopy.d.ts +0 -40
- package/dist/components/editor/HDialogCopy.js +0 -80
- package/dist/components/editor/HFooter.d.ts +0 -12
- package/dist/components/editor/HFooter.js +0 -24
- package/dist/components/editor/HHeaderEditor.d.ts +0 -17
- package/dist/components/editor/HHeaderEditor.js +0 -36
- package/dist/components/editor/HImageEditorBulkDekstop.d.ts +0 -15
- package/dist/components/editor/HImageEditorBulkDekstop.js +0 -29
- package/dist/components/editor/HImageEditorBulkMobile.d.ts +0 -72
- package/dist/components/editor/HImageEditorBulkMobile.js +0 -81
- package/dist/components/editor/HImageEditorDekstop.d.ts +0 -15
- package/dist/components/editor/HImageEditorDekstop.js +0 -29
- package/dist/components/editor/HImageEditorMobile.d.ts +0 -51
- package/dist/components/editor/HImageEditorMobile.js +0 -92
- package/dist/components/editor/HImageEditorMobileLayout.d.ts +0 -14
- package/dist/components/editor/HImageEditorMobileLayout.js +0 -58
- package/dist/components/editor/HModalEditorDekstop.d.ts +0 -13
- package/dist/components/editor/HModalEditorDekstop.js +0 -22
- package/dist/components/editor/HModalMobile.d.ts +0 -13
- package/dist/components/editor/HModalMobile.js +0 -7
- package/dist/components/editor/HPresetDelete.d.ts +0 -7
- package/dist/components/editor/HPresetDelete.js +0 -7
- package/dist/components/editor/HPresetOptionMenu.d.ts +0 -9
- package/dist/components/editor/HPresetOptionMenu.js +0 -20
- package/dist/components/editor/HSliderColorMobile.d.ts +0 -16
- package/dist/components/editor/HSliderColorMobile.js +0 -270
- package/dist/components/editor/HSliderDetailsMobile.d.ts +0 -12
- package/dist/components/editor/HSliderDetailsMobile.js +0 -154
- package/dist/components/editor/HSliderLightMobile.d.ts +0 -20
- package/dist/components/editor/HSliderLightMobile.js +0 -420
- package/dist/components/editor/HTabAspectRatioMobile.d.ts +0 -0
- package/dist/components/editor/HTabAspectRatioMobile.js +0 -1
- package/dist/components/editor/HTabColorAdjustmentMobile.d.ts +0 -33
- package/dist/components/editor/HTabColorAdjustmentMobile.js +0 -16
- package/dist/components/editor/HTabPresetMobile.d.ts +0 -14
- package/dist/components/editor/HTabPresetMobile.js +0 -10
- package/dist/components/editor/HTextField.d.ts +0 -14
- package/dist/components/editor/HTextField.js +0 -51
- package/dist/components/editor/HWatermarkView.d.ts +0 -6
- package/dist/components/editor/HWatermarkView.js +0 -16
- package/dist/components/editor/svg/Tick.d.ts +0 -2
- package/dist/components/editor/svg/Tick.js +0 -6
- package/dist/components/modal/HModalDialog.d.ts +0 -12
- package/dist/components/modal/HModalDialog.js +0 -18
- package/dist/components/modal/HModalRename.d.ts +0 -14
- package/dist/components/modal/HModalRename.js +0 -35
- package/dist/hooks/demo/HonchoEditorBulkDemo.d.ts +0 -3
- package/dist/hooks/demo/HonchoEditorBulkDemo.js +0 -410
- package/dist/hooks/demo/HonchoEditorSingleCleanDemo.d.ts +0 -3
- package/dist/hooks/demo/HonchoEditorSingleCleanDemo.js +0 -354
- package/dist/hooks/demo/index.d.ts +0 -2
- package/dist/hooks/demo/index.js +0 -2
- package/dist/hooks/editor/type.d.ts +0 -174
- package/dist/hooks/editor/type.js +0 -1
- package/dist/hooks/editor/useHonchoEditorBulk.d.ts +0 -96
- package/dist/hooks/editor/useHonchoEditorBulk.js +0 -427
- package/dist/hooks/editor/useHonchoEditorSingle.d.ts +0 -44
- package/dist/hooks/editor/useHonchoEditorSingle.js +0 -162
- package/dist/hooks/useAdjustmentHistory.d.ts +0 -97
- package/dist/hooks/useAdjustmentHistory.js +0 -493
- package/dist/hooks/useAdjustmentHistoryBatch.d.ts +0 -177
- package/dist/hooks/useAdjustmentHistoryBatch.js +0 -1189
- package/dist/hooks/useGallerySwipe.d.ts +0 -36
- package/dist/hooks/useGallerySwipe.js +0 -344
- package/dist/hooks/usePaging.d.ts +0 -89
- package/dist/hooks/usePaging.js +0 -211
- package/dist/hooks/usePreset.d.ts +0 -82
- package/dist/hooks/usePreset.js +0 -344
- package/dist/index.d.ts +0 -41
- package/dist/index.js +0 -44
- package/dist/lib/context/EditorContext.d.ts +0 -28
- package/dist/lib/context/EditorContext.js +0 -60
- package/dist/lib/context/EditorProcessingService.d.ts +0 -36
- package/dist/lib/context/EditorProcessingService.js +0 -249
- package/dist/lib/editor/honcho-editor.d.ts +0 -324
- package/dist/lib/editor/honcho-editor.js +0 -825
- package/dist/lib/hooks/useEditor.d.ts +0 -22
- package/dist/lib/hooks/useEditor.js +0 -35
- package/dist/lib/hooks/useEditorHeadless.d.ts +0 -34
- package/dist/lib/hooks/useEditorHeadless.js +0 -207
- package/dist/lib/hooks/useImageProcessor.d.ts +0 -18
- package/dist/lib/hooks/useImageProcessor.js +0 -113
- package/dist/setupTests.d.ts +0 -1
- package/dist/setupTests.js +0 -1
- package/dist/themes/colors.d.ts +0 -12
- package/dist/themes/colors.js +0 -12
- package/dist/themes/honchoTheme.d.ts +0 -25
- package/dist/themes/honchoTheme.js +0 -94
- package/dist/utils/adjustment.d.ts +0 -6
- package/dist/utils/adjustment.js +0 -48
- package/dist/utils/imageLoader.d.ts +0 -11
- package/dist/utils/imageLoader.js +0 -48
- package/dist/utils/isMobile.d.ts +0 -1
- package/dist/utils/isMobile.js +0 -5
|
@@ -1,825 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Honcho Photo Editor - JavaScript Wrapper
|
|
4
|
-
*
|
|
5
|
-
* This wrapper provides a clean interface to the C++ image processing engine.
|
|
6
|
-
*
|
|
7
|
-
* ARCHITECTURE: Dual-Path Processing
|
|
8
|
-
* ================================
|
|
9
|
-
*
|
|
10
|
-
* FRONTEND (Real-time Preview):
|
|
11
|
-
* - Uses GPU-to-GPU rendering via renderToCanvas()
|
|
12
|
-
* - Zero memory copies for 60fps performance
|
|
13
|
-
* - Optimized for interactive UI sliders
|
|
14
|
-
*
|
|
15
|
-
* BACKEND (Server Processing):
|
|
16
|
-
* - Uses CPU memory path via getProcessedImageData()
|
|
17
|
-
* - Full quality data export for server workflows
|
|
18
|
-
* - Headless processing without WebGL context
|
|
19
|
-
* - Batch processing and API endpoints
|
|
20
|
-
*
|
|
21
|
-
* Both paths use the same C++ processing core with identical results.
|
|
22
|
-
* JavaScript handles all UI and canvas rendering.
|
|
23
|
-
* C++ handles all image processing algorithms.
|
|
24
|
-
*/
|
|
25
|
-
// Adjustment ranges for UI components
|
|
26
|
-
const ADJUSTMENT_RANGES = {
|
|
27
|
-
temperature: { min: -100, max: 100, default: 0, step: 1 },
|
|
28
|
-
tint: { min: -100, max: 100, default: 0, step: 1 },
|
|
29
|
-
saturation: { min: -100, max: 100, default: 0, step: 1 },
|
|
30
|
-
vibrance: { min: -100, max: 100, default: 0, step: 1 },
|
|
31
|
-
exposure: { min: -100, max: 100, default: 0, step: 1 },
|
|
32
|
-
contrast: { min: -100, max: 100, default: 0, step: 1 },
|
|
33
|
-
highlights: { min: -100, max: 100, default: 0, step: 1 },
|
|
34
|
-
shadows: { min: -100, max: 100, default: 0, step: 1 },
|
|
35
|
-
whites: { min: -100, max: 100, default: 0, step: 1 },
|
|
36
|
-
blacks: { min: -100, max: 100, default: 0, step: 1 },
|
|
37
|
-
clarity: { min: -100, max: 100, default: 0, step: 1 },
|
|
38
|
-
sharpness: { min: -100, max: 100, default: 0, step: 1 }
|
|
39
|
-
};
|
|
40
|
-
class HonchoEditor {
|
|
41
|
-
constructor() {
|
|
42
|
-
this.wasmModule = null;
|
|
43
|
-
this.isInitialized = false;
|
|
44
|
-
this.currentImageData = null;
|
|
45
|
-
this.canvas = null;
|
|
46
|
-
this.currentWidth = 0;
|
|
47
|
-
this.currentHeight = 0;
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Initialize the WASM module
|
|
51
|
-
* @param {boolean} verbose - Enable verbose logging (default: false)
|
|
52
|
-
*/
|
|
53
|
-
async initialize(verbose = false) {
|
|
54
|
-
try {
|
|
55
|
-
console.log('Initializing Honcho Photo Editor...');
|
|
56
|
-
// Create a hidden canvas for WebGL context that C++ can find
|
|
57
|
-
this.canvas = document.createElement('canvas');
|
|
58
|
-
this.canvas.width = 1;
|
|
59
|
-
this.canvas.height = 1;
|
|
60
|
-
this.canvas.style.display = 'none';
|
|
61
|
-
this.canvas.id = 'canvas'; // Use standard ID that C++ looks for
|
|
62
|
-
document.body.appendChild(this.canvas);
|
|
63
|
-
// Load the WASM module - Module is a factory function
|
|
64
|
-
if (typeof Module !== 'undefined') {
|
|
65
|
-
// Configure the module with the canvas
|
|
66
|
-
this.wasmModule = await Module({
|
|
67
|
-
canvas: this.canvas,
|
|
68
|
-
noExitRuntime: true,
|
|
69
|
-
noInitialRun: true
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
else {
|
|
73
|
-
throw new Error('WASM module not found - make sure to load honcho-editor.js first');
|
|
74
|
-
}
|
|
75
|
-
// Create the processor instance with verbose flag (this will initialize OpenGL)
|
|
76
|
-
console.log('Creating processor...');
|
|
77
|
-
// Check if the function exists first
|
|
78
|
-
if (this.wasmModule && typeof this.wasmModule._createProcessor === 'function') {
|
|
79
|
-
this.wasmModule._createProcessor(verbose);
|
|
80
|
-
console.log('Successfully created processor with verbose =', verbose);
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
console.error('_createProcessor function not found!');
|
|
84
|
-
if (this.wasmModule) {
|
|
85
|
-
console.log('Available functions:', Object.keys(this.wasmModule));
|
|
86
|
-
}
|
|
87
|
-
throw new Error('_createProcessor function not available');
|
|
88
|
-
}
|
|
89
|
-
this.isInitialized = true;
|
|
90
|
-
console.log('Honcho Photo Editor initialized successfully');
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
console.error('Failed to initialize Honcho Photo Editor:', error);
|
|
95
|
-
throw error;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Clean up resources before loading a new image
|
|
100
|
-
*/
|
|
101
|
-
cleanupForNewImage() {
|
|
102
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
try {
|
|
106
|
-
// Reset any internal state
|
|
107
|
-
this.currentImageData = null;
|
|
108
|
-
this.currentWidth = 0;
|
|
109
|
-
this.currentHeight = 0;
|
|
110
|
-
// Call C++ cleanup if available
|
|
111
|
-
if (typeof this.wasmModule._resetProcessor === 'function') {
|
|
112
|
-
this.wasmModule._resetProcessor();
|
|
113
|
-
}
|
|
114
|
-
console.log('Cleaned up for new image');
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
console.warn('Warning during cleanup:', error);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Load image from File object (drag & drop, file input)
|
|
122
|
-
*/
|
|
123
|
-
async loadImageFromFile(file) {
|
|
124
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
125
|
-
throw new Error('Editor not initialized');
|
|
126
|
-
}
|
|
127
|
-
// Clean up before loading new image
|
|
128
|
-
this.cleanupForNewImage();
|
|
129
|
-
return new Promise((resolve, reject) => {
|
|
130
|
-
const reader = new FileReader();
|
|
131
|
-
reader.onload = (e) => {
|
|
132
|
-
const img = new Image();
|
|
133
|
-
img.onload = () => {
|
|
134
|
-
try {
|
|
135
|
-
const size = this.loadImageFromImageElement(img);
|
|
136
|
-
resolve(size);
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
reject(error);
|
|
140
|
-
}
|
|
141
|
-
};
|
|
142
|
-
img.onerror = () => {
|
|
143
|
-
reject(new Error('Failed to load image'));
|
|
144
|
-
};
|
|
145
|
-
img.src = e.target.result;
|
|
146
|
-
};
|
|
147
|
-
reader.onerror = () => {
|
|
148
|
-
reject(new Error('Failed to read file'));
|
|
149
|
-
};
|
|
150
|
-
reader.readAsDataURL(file);
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Load image from URL
|
|
155
|
-
*/
|
|
156
|
-
async loadImageFromUrl(url) {
|
|
157
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
158
|
-
throw new Error('Editor not initialized');
|
|
159
|
-
}
|
|
160
|
-
// Clean up before loading new image
|
|
161
|
-
this.cleanupForNewImage();
|
|
162
|
-
return new Promise((resolve, reject) => {
|
|
163
|
-
const img = new Image();
|
|
164
|
-
img.onload = () => {
|
|
165
|
-
try {
|
|
166
|
-
const size = this.loadImageFromImageElement(img);
|
|
167
|
-
resolve(size);
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
reject(error);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
img.onerror = () => {
|
|
174
|
-
reject(new Error('Failed to load image from URL'));
|
|
175
|
-
};
|
|
176
|
-
img.crossOrigin = 'anonymous'; // Enable CORS
|
|
177
|
-
img.src = url;
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Load image from HTML Image element
|
|
182
|
-
*/
|
|
183
|
-
loadImageFromImageElement(img) {
|
|
184
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
185
|
-
throw new Error('Editor not initialized');
|
|
186
|
-
}
|
|
187
|
-
// Clean up before loading new image
|
|
188
|
-
this.cleanupForNewImage();
|
|
189
|
-
// Create canvas to extract image data
|
|
190
|
-
const canvas = document.createElement('canvas');
|
|
191
|
-
canvas.width = img.width;
|
|
192
|
-
canvas.height = img.height;
|
|
193
|
-
const ctx = canvas.getContext('2d');
|
|
194
|
-
if (!ctx) {
|
|
195
|
-
throw new Error('Failed to get 2D context');
|
|
196
|
-
}
|
|
197
|
-
ctx.drawImage(img, 0, 0);
|
|
198
|
-
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
|
199
|
-
return this.loadImageFromImageData(imageData);
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* Load image from ImageData
|
|
203
|
-
*/
|
|
204
|
-
loadImageFromImageData(imageData) {
|
|
205
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
206
|
-
throw new Error('Editor not initialized');
|
|
207
|
-
}
|
|
208
|
-
// Clean up before loading new image (only if we have existing image data)
|
|
209
|
-
if (this.currentImageData) {
|
|
210
|
-
this.cleanupForNewImage();
|
|
211
|
-
}
|
|
212
|
-
try {
|
|
213
|
-
// Validate input
|
|
214
|
-
if (!imageData || !imageData.data || imageData.width <= 0 || imageData.height <= 0) {
|
|
215
|
-
throw new Error('Invalid image data provided');
|
|
216
|
-
}
|
|
217
|
-
this.currentImageData = imageData;
|
|
218
|
-
this.currentWidth = imageData.width;
|
|
219
|
-
this.currentHeight = imageData.height;
|
|
220
|
-
// Allocate memory in WASM
|
|
221
|
-
const dataSize = imageData.data.length;
|
|
222
|
-
const expectedSize = imageData.width * imageData.height * 4; // RGBA
|
|
223
|
-
if (dataSize !== expectedSize) {
|
|
224
|
-
throw new Error(`Image data size mismatch: expected ${expectedSize}, got ${dataSize}`);
|
|
225
|
-
}
|
|
226
|
-
console.log(`Allocating ${dataSize} bytes for ${imageData.width}x${imageData.height} image`);
|
|
227
|
-
const dataPtr = this.wasmModule._malloc(dataSize);
|
|
228
|
-
if (!dataPtr) {
|
|
229
|
-
throw new Error('Failed to allocate memory in WASM');
|
|
230
|
-
}
|
|
231
|
-
// Copy image data to WASM memory
|
|
232
|
-
this.wasmModule.HEAPU8.set(imageData.data, dataPtr);
|
|
233
|
-
// Set image data in C++
|
|
234
|
-
const result = this.wasmModule._setImageData(dataPtr, imageData.width, imageData.height, 4);
|
|
235
|
-
// Free allocated memory
|
|
236
|
-
this.wasmModule._free(dataPtr);
|
|
237
|
-
if (!result) {
|
|
238
|
-
throw new Error('C++ setImageData failed');
|
|
239
|
-
}
|
|
240
|
-
console.log(`Image loaded successfully: ${imageData.width}x${imageData.height}`);
|
|
241
|
-
return {
|
|
242
|
-
width: imageData.width,
|
|
243
|
-
height: imageData.height
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
console.error('Failed to load image data:', error);
|
|
248
|
-
throw error;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
// Adjustment methods (all auto-trigger re-render)
|
|
252
|
-
setExposure(value) {
|
|
253
|
-
this.validateAdjustment('exposure', value);
|
|
254
|
-
this.wasmModule?._setExposure(value);
|
|
255
|
-
}
|
|
256
|
-
setContrast(value) {
|
|
257
|
-
this.validateAdjustment('contrast', value);
|
|
258
|
-
this.wasmModule?._setContrast(value);
|
|
259
|
-
}
|
|
260
|
-
setHighlights(value) {
|
|
261
|
-
this.validateAdjustment('highlights', value);
|
|
262
|
-
this.wasmModule?._setHighlights(value);
|
|
263
|
-
}
|
|
264
|
-
setShadows(value) {
|
|
265
|
-
this.validateAdjustment('shadows', value);
|
|
266
|
-
this.wasmModule?._setShadows(value);
|
|
267
|
-
}
|
|
268
|
-
setSaturation(value) {
|
|
269
|
-
this.validateAdjustment('saturation', value);
|
|
270
|
-
this.wasmModule?._setSaturation(value);
|
|
271
|
-
}
|
|
272
|
-
setVibrance(value) {
|
|
273
|
-
this.validateAdjustment('vibrance', value);
|
|
274
|
-
this.wasmModule?._setVibrance(value);
|
|
275
|
-
}
|
|
276
|
-
setTemperature(value) {
|
|
277
|
-
this.validateAdjustment('temperature', value);
|
|
278
|
-
this.wasmModule?._setTemperature(value);
|
|
279
|
-
}
|
|
280
|
-
setTint(value) {
|
|
281
|
-
this.validateAdjustment('tint', value);
|
|
282
|
-
this.wasmModule?._setTint(value);
|
|
283
|
-
}
|
|
284
|
-
setBlacks(value) {
|
|
285
|
-
this.validateAdjustment('blacks', value);
|
|
286
|
-
if (this.wasmModule && typeof this.wasmModule._setBlacks === 'function') {
|
|
287
|
-
this.wasmModule._setBlacks(value);
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
console.warn('setBlacks not available in current WASM build');
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
setWhites(value) {
|
|
294
|
-
this.validateAdjustment('whites', value);
|
|
295
|
-
if (this.wasmModule && typeof this.wasmModule._setWhites === 'function') {
|
|
296
|
-
this.wasmModule._setWhites(value);
|
|
297
|
-
}
|
|
298
|
-
else {
|
|
299
|
-
console.warn('setWhites not available in current WASM build');
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
setClarity(value) {
|
|
303
|
-
this.validateAdjustment('clarity', value);
|
|
304
|
-
if (this.wasmModule && typeof this.wasmModule._setClarity === 'function') {
|
|
305
|
-
this.wasmModule._setClarity(value);
|
|
306
|
-
}
|
|
307
|
-
else {
|
|
308
|
-
console.warn('setClarity not available in current WASM build');
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
setSharpness(value) {
|
|
312
|
-
this.validateAdjustment('sharpness', value);
|
|
313
|
-
if (this.wasmModule && typeof this.wasmModule._setSharpness === 'function') {
|
|
314
|
-
this.wasmModule._setSharpness(value);
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
console.warn('setSharpness not available in current WASM build');
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
/**
|
|
321
|
-
* Set multiple adjustments at once
|
|
322
|
-
*/
|
|
323
|
-
setAdjustments(adjustments) {
|
|
324
|
-
Object.entries(adjustments).forEach(([key, value]) => {
|
|
325
|
-
if (value !== undefined) {
|
|
326
|
-
const methodName = `set${key.charAt(0).toUpperCase() + key.slice(1)}`;
|
|
327
|
-
if (typeof this[methodName] === 'function') {
|
|
328
|
-
this[methodName](value);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
/**
|
|
334
|
-
* Reset all adjustments to default values
|
|
335
|
-
*/
|
|
336
|
-
resetAdjustments() {
|
|
337
|
-
this.wasmModule?._resetAdjustments();
|
|
338
|
-
}
|
|
339
|
-
// ==========================================
|
|
340
|
-
// FRAME FUNCTIONALITY - NEW FEATURE
|
|
341
|
-
// ==========================================
|
|
342
|
-
/**
|
|
343
|
-
* Set a frame overlay from raw image data
|
|
344
|
-
* @param {Uint8Array} data - Raw RGBA image data
|
|
345
|
-
* @param {number} width - Frame width
|
|
346
|
-
* @param {number} height - Frame height
|
|
347
|
-
* @param {number} channels - Number of channels (usually 4 for RGBA)
|
|
348
|
-
* @returns {boolean} - true if frame was set successfully
|
|
349
|
-
*/
|
|
350
|
-
setFrame(data, width, height, channels = 4) {
|
|
351
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
352
|
-
throw new Error('Editor not initialized');
|
|
353
|
-
}
|
|
354
|
-
if (!this.currentImageData) {
|
|
355
|
-
throw new Error('No image loaded. Load an image first before setting frame.');
|
|
356
|
-
}
|
|
357
|
-
try {
|
|
358
|
-
// Allocate memory in WASM for frame data
|
|
359
|
-
const frameSize = width * height * channels;
|
|
360
|
-
const framePtr = this.wasmModule._malloc(frameSize);
|
|
361
|
-
if (!framePtr) {
|
|
362
|
-
throw new Error('Failed to allocate memory for frame data');
|
|
363
|
-
}
|
|
364
|
-
// Copy frame data to WASM memory
|
|
365
|
-
this.wasmModule.HEAPU8.set(data, framePtr);
|
|
366
|
-
// Call native setFrame function
|
|
367
|
-
const success = this.wasmModule._setFrame(framePtr, width, height, channels);
|
|
368
|
-
// Free allocated memory
|
|
369
|
-
this.wasmModule._free(framePtr);
|
|
370
|
-
if (success) {
|
|
371
|
-
console.log(`✅ Frame set: ${width}x${height}`);
|
|
372
|
-
return true;
|
|
373
|
-
}
|
|
374
|
-
else {
|
|
375
|
-
console.error('❌ Failed to set frame');
|
|
376
|
-
return false;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
catch (error) {
|
|
380
|
-
console.error('Error setting frame:', error);
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
/**
|
|
385
|
-
* Set a frame overlay from an ImageData object
|
|
386
|
-
* The frame will be scaled to match the image size and composited on top
|
|
387
|
-
* @param {ImageData} frameImageData - Frame image data (preferably with alpha channel)
|
|
388
|
-
* @returns {boolean} - true if frame was set successfully
|
|
389
|
-
*/
|
|
390
|
-
setFrameFromImageData(frameImageData) {
|
|
391
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
392
|
-
throw new Error('Editor not initialized');
|
|
393
|
-
}
|
|
394
|
-
if (!this.currentImageData) {
|
|
395
|
-
throw new Error('No image loaded. Load an image first before setting frame.');
|
|
396
|
-
}
|
|
397
|
-
try {
|
|
398
|
-
const { width, height, data } = frameImageData;
|
|
399
|
-
// Allocate memory in WASM for frame data
|
|
400
|
-
const frameSize = width * height * 4; // RGBA
|
|
401
|
-
const framePtr = this.wasmModule._malloc(frameSize);
|
|
402
|
-
if (!framePtr) {
|
|
403
|
-
throw new Error('Failed to allocate memory for frame data');
|
|
404
|
-
}
|
|
405
|
-
// Copy frame data to WASM memory
|
|
406
|
-
this.wasmModule.HEAPU8.set(data, framePtr);
|
|
407
|
-
// Call native setFrame function
|
|
408
|
-
const success = this.wasmModule._setFrame(framePtr, width, height, 4);
|
|
409
|
-
// Free allocated memory
|
|
410
|
-
this.wasmModule._free(framePtr);
|
|
411
|
-
if (success) {
|
|
412
|
-
console.log(`✅ Frame set: ${width}x${height}`);
|
|
413
|
-
return true;
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
console.error('❌ Failed to set frame');
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
catch (error) {
|
|
421
|
-
console.error('Error setting frame:', error);
|
|
422
|
-
return false;
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Set a frame overlay from an HTML Image element
|
|
427
|
-
* @param {HTMLImageElement} frameImage - Frame image element
|
|
428
|
-
* @returns {boolean} - true if frame was set successfully
|
|
429
|
-
*/
|
|
430
|
-
setFrameFromImage(frameImage) {
|
|
431
|
-
if (!frameImage.complete) {
|
|
432
|
-
throw new Error('Frame image not loaded. Wait for image.onload before calling this method.');
|
|
433
|
-
}
|
|
434
|
-
// Create a canvas to extract ImageData
|
|
435
|
-
const tempCanvas = document.createElement('canvas');
|
|
436
|
-
const tempCtx = tempCanvas.getContext('2d');
|
|
437
|
-
tempCanvas.width = frameImage.width || frameImage.naturalWidth;
|
|
438
|
-
tempCanvas.height = frameImage.height || frameImage.naturalHeight;
|
|
439
|
-
// Draw frame image to canvas
|
|
440
|
-
tempCtx.drawImage(frameImage, 0, 0);
|
|
441
|
-
// Extract ImageData
|
|
442
|
-
const frameImageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
|
443
|
-
return this.setFrameFromImageData(frameImageData);
|
|
444
|
-
}
|
|
445
|
-
/**
|
|
446
|
-
* Set a frame overlay from a Canvas element
|
|
447
|
-
* @param {HTMLCanvasElement} frameCanvas - Frame canvas element
|
|
448
|
-
* @returns {boolean} - true if frame was set successfully
|
|
449
|
-
*/
|
|
450
|
-
setFrameFromCanvas(frameCanvas) {
|
|
451
|
-
const ctx = frameCanvas.getContext('2d');
|
|
452
|
-
const frameImageData = ctx.getImageData(0, 0, frameCanvas.width, frameCanvas.height);
|
|
453
|
-
return this.setFrameFromImageData(frameImageData);
|
|
454
|
-
}
|
|
455
|
-
/**
|
|
456
|
-
* Clear the current frame overlay
|
|
457
|
-
*/
|
|
458
|
-
clearFrame() {
|
|
459
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
460
|
-
throw new Error('Editor not initialized');
|
|
461
|
-
}
|
|
462
|
-
if (this.wasmModule._clearFrame) {
|
|
463
|
-
this.wasmModule._clearFrame();
|
|
464
|
-
console.log('🧹 Frame cleared');
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
/**
|
|
468
|
-
* Check if a frame is currently set
|
|
469
|
-
* @returns {boolean} - true if a frame is set
|
|
470
|
-
*/
|
|
471
|
-
hasFrame() {
|
|
472
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
475
|
-
return this.wasmModule._hasFrame ? this.wasmModule._hasFrame() : false;
|
|
476
|
-
}
|
|
477
|
-
// ==========================================
|
|
478
|
-
// CONVENIENCE METHODS
|
|
479
|
-
// ==========================================
|
|
480
|
-
/**
|
|
481
|
-
* One-shot processing: load image, apply adjustments, get result
|
|
482
|
-
* Most efficient for single image processing or batch operations
|
|
483
|
-
*
|
|
484
|
-
* @param {File|ImageData|HTMLImageElement} imageSource - Image source (File, ImageData, or Image element)
|
|
485
|
-
* @param {Object} adjustments - Object containing adjustment values (-100 to 100)
|
|
486
|
-
* @param {File|ImageData|HTMLImageElement|null} frameSource - Optional frame source
|
|
487
|
-
* @returns {ImageData} - Processed image data
|
|
488
|
-
*
|
|
489
|
-
* @example
|
|
490
|
-
* // Basic usage
|
|
491
|
-
* const result = await editor.processImageOneShot(imageFile, {
|
|
492
|
-
* exposure: 20,
|
|
493
|
-
* contrast: 15,
|
|
494
|
-
* saturation: 10
|
|
495
|
-
* });
|
|
496
|
-
*
|
|
497
|
-
* // With frame
|
|
498
|
-
* const result = await editor.processImageOneShot(imageFile, {
|
|
499
|
-
* exposure: 20,
|
|
500
|
-
* contrast: 15
|
|
501
|
-
* }, frameFile);
|
|
502
|
-
*
|
|
503
|
-
* // All adjustments
|
|
504
|
-
* const result = await editor.processImageOneShot(imageFile, {
|
|
505
|
-
* temperature: 10,
|
|
506
|
-
* tint: -5,
|
|
507
|
-
* exposure: 20,
|
|
508
|
-
* contrast: 15,
|
|
509
|
-
* highlights: -20,
|
|
510
|
-
* shadows: 30,
|
|
511
|
-
* saturation: 25,
|
|
512
|
-
* vibrance: 20,
|
|
513
|
-
* whites: 10,
|
|
514
|
-
* blacks: -10,
|
|
515
|
-
* clarity: 15,
|
|
516
|
-
* sharpness: 20
|
|
517
|
-
* });
|
|
518
|
-
*/
|
|
519
|
-
async processImageOneShot(imageSource, adjustments = {}, frameSource = null) {
|
|
520
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
521
|
-
throw new Error('Editor not initialized');
|
|
522
|
-
}
|
|
523
|
-
try {
|
|
524
|
-
// Step 1: Load the image
|
|
525
|
-
console.log('📁 Loading image for one-shot processing...');
|
|
526
|
-
let imageSize;
|
|
527
|
-
if (imageSource instanceof File) {
|
|
528
|
-
imageSize = await this.loadImageFromFile(imageSource);
|
|
529
|
-
}
|
|
530
|
-
else if (imageSource instanceof ImageData) {
|
|
531
|
-
imageSize = this.loadImageFromImageData(imageSource);
|
|
532
|
-
}
|
|
533
|
-
else if (imageSource instanceof HTMLImageElement) {
|
|
534
|
-
imageSize = this.loadImageFromImageElement(imageSource);
|
|
535
|
-
}
|
|
536
|
-
else {
|
|
537
|
-
throw new Error('Unsupported image source type. Use File, ImageData, or HTMLImageElement.');
|
|
538
|
-
}
|
|
539
|
-
console.log(`✅ Image loaded: ${imageSize.width}×${imageSize.height}`);
|
|
540
|
-
// Step 2: Apply frame if provided
|
|
541
|
-
if (frameSource) {
|
|
542
|
-
console.log('🖼️ Applying frame...');
|
|
543
|
-
if (frameSource instanceof File) {
|
|
544
|
-
await this.setFrameFromFile(frameSource);
|
|
545
|
-
}
|
|
546
|
-
else if (frameSource instanceof ImageData) {
|
|
547
|
-
this.setFrameFromImageData(frameSource);
|
|
548
|
-
}
|
|
549
|
-
else if (frameSource instanceof HTMLImageElement) {
|
|
550
|
-
this.setFrameFromImage(frameSource);
|
|
551
|
-
}
|
|
552
|
-
else {
|
|
553
|
-
throw new Error('Unsupported frame source type. Use File, ImageData, or HTMLImageElement.');
|
|
554
|
-
}
|
|
555
|
-
console.log('✅ Frame applied successfully');
|
|
556
|
-
}
|
|
557
|
-
// Step 3: Apply all adjustments
|
|
558
|
-
console.log('🎛️ Applying adjustments:', adjustments);
|
|
559
|
-
const validAdjustments = [
|
|
560
|
-
'temperature', 'tint', 'exposure', 'contrast', 'highlights',
|
|
561
|
-
'shadows', 'saturation', 'vibrance', 'whites', 'blacks',
|
|
562
|
-
'clarity', 'sharpness'
|
|
563
|
-
];
|
|
564
|
-
validAdjustments.forEach(name => {
|
|
565
|
-
if (adjustments[name] !== undefined) {
|
|
566
|
-
const methodName = `set${name.charAt(0).toUpperCase() + name.slice(1)}`;
|
|
567
|
-
if (typeof this[methodName] === 'function') {
|
|
568
|
-
this[methodName](adjustments[name]);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
});
|
|
572
|
-
// Step 4: Process the image
|
|
573
|
-
console.log('⚙️ Processing image...');
|
|
574
|
-
this.processImage();
|
|
575
|
-
// Step 5: Get the processed result
|
|
576
|
-
console.log('📤 Exporting processed image data...');
|
|
577
|
-
const result = this.getProcessedImageData();
|
|
578
|
-
console.log('✅ One-shot processing complete');
|
|
579
|
-
return result;
|
|
580
|
-
}
|
|
581
|
-
catch (error) {
|
|
582
|
-
console.error('❌ One-shot processing failed:', error);
|
|
583
|
-
throw error;
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Helper method to load frame from File (for processImageOneShot)
|
|
588
|
-
*/
|
|
589
|
-
async setFrameFromFile(frameFile) {
|
|
590
|
-
return new Promise((resolve, reject) => {
|
|
591
|
-
const img = new Image();
|
|
592
|
-
img.onload = () => {
|
|
593
|
-
try {
|
|
594
|
-
this.setFrameFromImage(img);
|
|
595
|
-
resolve();
|
|
596
|
-
}
|
|
597
|
-
catch (error) {
|
|
598
|
-
reject(error);
|
|
599
|
-
}
|
|
600
|
-
};
|
|
601
|
-
img.onerror = () => reject(new Error('Failed to load frame image'));
|
|
602
|
-
const reader = new FileReader();
|
|
603
|
-
reader.onload = (e) => {
|
|
604
|
-
img.src = e.target.result;
|
|
605
|
-
};
|
|
606
|
-
reader.onerror = () => reject(new Error('Failed to read frame file'));
|
|
607
|
-
reader.readAsDataURL(frameFile);
|
|
608
|
-
});
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Manual processing trigger - call this after setting adjustments
|
|
612
|
-
*/
|
|
613
|
-
processImage() {
|
|
614
|
-
if (this.wasmModule && typeof this.wasmModule._processImage === 'function') {
|
|
615
|
-
this.wasmModule._processImage();
|
|
616
|
-
}
|
|
617
|
-
else {
|
|
618
|
-
console.warn('processImage not available in current WASM build');
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
/**
|
|
622
|
-
* GPU Path: Render to canvas for real-time preview (<1ms)
|
|
623
|
-
*/
|
|
624
|
-
renderToCanvas(canvas) {
|
|
625
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
626
|
-
throw new Error('Editor not initialized');
|
|
627
|
-
}
|
|
628
|
-
if (!this.currentImageData) {
|
|
629
|
-
throw new Error('No image loaded');
|
|
630
|
-
}
|
|
631
|
-
try {
|
|
632
|
-
// Resize canvas to match image if needed
|
|
633
|
-
if (canvas.width !== this.currentWidth || canvas.height !== this.currentHeight) {
|
|
634
|
-
canvas.width = this.currentWidth;
|
|
635
|
-
canvas.height = this.currentHeight;
|
|
636
|
-
}
|
|
637
|
-
// Update the C++ canvas size to match our target canvas
|
|
638
|
-
if (this.canvas) {
|
|
639
|
-
this.canvas.width = this.currentWidth;
|
|
640
|
-
this.canvas.height = this.currentHeight;
|
|
641
|
-
}
|
|
642
|
-
// Trigger GPU rendering in C++
|
|
643
|
-
this.wasmModule._renderToDisplay();
|
|
644
|
-
// Copy from C++ canvas to target canvas
|
|
645
|
-
const ctx = canvas.getContext('2d');
|
|
646
|
-
if (ctx && this.canvas) {
|
|
647
|
-
ctx.drawImage(this.canvas, 0, 0);
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
catch (error) {
|
|
651
|
-
console.error('Failed to render to canvas:', error);
|
|
652
|
-
throw error;
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
/**
|
|
656
|
-
* CPU Path: Get processed image data for export (50-100ms)
|
|
657
|
-
*/
|
|
658
|
-
getProcessedImageData() {
|
|
659
|
-
if (!this.isInitialized || !this.wasmModule) {
|
|
660
|
-
throw new Error('Editor not initialized');
|
|
661
|
-
}
|
|
662
|
-
if (!this.currentImageData) {
|
|
663
|
-
throw new Error('No image loaded');
|
|
664
|
-
}
|
|
665
|
-
try {
|
|
666
|
-
// Get processed image data from C++
|
|
667
|
-
const dataPtr = this.wasmModule._getProcessedImageData();
|
|
668
|
-
if (dataPtr === 0) {
|
|
669
|
-
throw new Error('Failed to get processed image data');
|
|
670
|
-
}
|
|
671
|
-
// Copy data from WASM memory
|
|
672
|
-
const dataSize = this.currentWidth * this.currentHeight * 4;
|
|
673
|
-
const processedData = new Uint8ClampedArray(dataSize);
|
|
674
|
-
processedData.set(this.wasmModule.HEAPU8.subarray(dataPtr, dataPtr + dataSize));
|
|
675
|
-
// Create ImageData object
|
|
676
|
-
const imageData = new ImageData(processedData, this.currentWidth, this.currentHeight);
|
|
677
|
-
return imageData;
|
|
678
|
-
}
|
|
679
|
-
catch (error) {
|
|
680
|
-
console.error('Failed to get processed image data:', error);
|
|
681
|
-
throw error;
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
/**
|
|
685
|
-
* Get current image dimensions
|
|
686
|
-
*/
|
|
687
|
-
getImageSize() {
|
|
688
|
-
return {
|
|
689
|
-
width: this.currentWidth,
|
|
690
|
-
height: this.currentHeight
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
/**
|
|
694
|
-
* Get current image width
|
|
695
|
-
*/
|
|
696
|
-
getWidth() {
|
|
697
|
-
return this.currentWidth || 0;
|
|
698
|
-
}
|
|
699
|
-
/**
|
|
700
|
-
* Get current image height
|
|
701
|
-
*/
|
|
702
|
-
getHeight() {
|
|
703
|
-
return this.currentHeight || 0;
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Check if editor is initialized
|
|
707
|
-
*/
|
|
708
|
-
getInitialized() {
|
|
709
|
-
return this.isInitialized;
|
|
710
|
-
}
|
|
711
|
-
/**
|
|
712
|
-
* Get raw WASM module (advanced usage)
|
|
713
|
-
*/
|
|
714
|
-
getRawModule() {
|
|
715
|
-
return this.wasmModule;
|
|
716
|
-
}
|
|
717
|
-
/**
|
|
718
|
-
* Validate adjustment value against range
|
|
719
|
-
*/
|
|
720
|
-
validateAdjustment(name, value) {
|
|
721
|
-
const range = ADJUSTMENT_RANGES[name];
|
|
722
|
-
if (!range) {
|
|
723
|
-
throw new Error(`Unknown adjustment: ${name}`);
|
|
724
|
-
}
|
|
725
|
-
if (value < range.min || value > range.max) {
|
|
726
|
-
throw new Error(`${name} value ${value} out of range [${range.min}, ${range.max}]`);
|
|
727
|
-
}
|
|
728
|
-
}
|
|
729
|
-
/**
|
|
730
|
-
* Cleanup resources
|
|
731
|
-
*/
|
|
732
|
-
cleanup() {
|
|
733
|
-
if (this.wasmModule) {
|
|
734
|
-
this.wasmModule._destroyProcessor();
|
|
735
|
-
}
|
|
736
|
-
if (this.canvas && this.canvas.parentNode) {
|
|
737
|
-
this.canvas.parentNode.removeChild(this.canvas);
|
|
738
|
-
}
|
|
739
|
-
this.isInitialized = false;
|
|
740
|
-
this.wasmModule = null;
|
|
741
|
-
this.currentImageData = null;
|
|
742
|
-
this.canvas = null;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
// Export for convenience
|
|
746
|
-
// export default HonchoEditor; // Disabled for browser compatibility
|
|
747
|
-
// Helper functions for common operations
|
|
748
|
-
const HonchoEditorUtils = {
|
|
749
|
-
/**
|
|
750
|
-
* Convert ImageData to Blob for download
|
|
751
|
-
*/
|
|
752
|
-
async imageDataToBlob(imageData, format = 'jpeg', quality = 0.9) {
|
|
753
|
-
const canvas = document.createElement('canvas');
|
|
754
|
-
canvas.width = imageData.width;
|
|
755
|
-
canvas.height = imageData.height;
|
|
756
|
-
const ctx = canvas.getContext('2d');
|
|
757
|
-
if (!ctx) {
|
|
758
|
-
throw new Error('Failed to get 2D context');
|
|
759
|
-
}
|
|
760
|
-
ctx.putImageData(imageData, 0, 0);
|
|
761
|
-
const mimeType = `image/${format}`;
|
|
762
|
-
return new Promise((resolve, reject) => {
|
|
763
|
-
canvas.toBlob((blob) => {
|
|
764
|
-
if (blob) {
|
|
765
|
-
resolve(blob);
|
|
766
|
-
}
|
|
767
|
-
else {
|
|
768
|
-
reject(new Error('Failed to create blob'));
|
|
769
|
-
}
|
|
770
|
-
}, mimeType, quality);
|
|
771
|
-
});
|
|
772
|
-
},
|
|
773
|
-
/**
|
|
774
|
-
* Download blob as file
|
|
775
|
-
*/
|
|
776
|
-
downloadBlob(blob, filename) {
|
|
777
|
-
const url = URL.createObjectURL(blob);
|
|
778
|
-
const a = document.createElement('a');
|
|
779
|
-
a.href = url;
|
|
780
|
-
a.download = filename;
|
|
781
|
-
a.click();
|
|
782
|
-
URL.revokeObjectURL(url);
|
|
783
|
-
},
|
|
784
|
-
/**
|
|
785
|
-
* Create debounced function for performance optimization
|
|
786
|
-
*/
|
|
787
|
-
debounce(func, wait) {
|
|
788
|
-
let timeout;
|
|
789
|
-
return function executedFunction(...args) {
|
|
790
|
-
const later = () => {
|
|
791
|
-
clearTimeout(timeout);
|
|
792
|
-
func(...args);
|
|
793
|
-
};
|
|
794
|
-
clearTimeout(timeout);
|
|
795
|
-
timeout = setTimeout(later, wait);
|
|
796
|
-
};
|
|
797
|
-
},
|
|
798
|
-
/**
|
|
799
|
-
* Validate image file type
|
|
800
|
-
*/
|
|
801
|
-
isValidImageFile(file) {
|
|
802
|
-
const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif', 'image/bmp'];
|
|
803
|
-
return validTypes.includes(file.type);
|
|
804
|
-
},
|
|
805
|
-
/**
|
|
806
|
-
* Get file size in human readable format
|
|
807
|
-
*/
|
|
808
|
-
formatFileSize(bytes) {
|
|
809
|
-
if (bytes === 0)
|
|
810
|
-
return '0 Bytes';
|
|
811
|
-
const k = 1024;
|
|
812
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
813
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
814
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
815
|
-
}
|
|
816
|
-
};
|
|
817
|
-
// Export for both Node.js and browser
|
|
818
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
819
|
-
module.exports = { HonchoEditor, HonchoEditorUtils, ADJUSTMENT_RANGES };
|
|
820
|
-
}
|
|
821
|
-
else {
|
|
822
|
-
window.HonchoEditor = HonchoEditor;
|
|
823
|
-
window.HonchoEditorUtils = HonchoEditorUtils;
|
|
824
|
-
window.ADJUSTMENT_RANGES = ADJUSTMENT_RANGES;
|
|
825
|
-
}
|