tokimeki-image-editor 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/components/AdjustTool.svelte +317 -0
  2. package/dist/components/AdjustTool.svelte.d.ts +9 -0
  3. package/dist/components/BlurTool.svelte +613 -0
  4. package/dist/components/BlurTool.svelte.d.ts +15 -0
  5. package/dist/components/Canvas.svelte +214 -0
  6. package/dist/components/Canvas.svelte.d.ts +17 -0
  7. package/dist/components/CropTool.svelte +942 -0
  8. package/dist/components/CropTool.svelte.d.ts +14 -0
  9. package/dist/components/ExportTool.svelte +191 -0
  10. package/dist/components/ExportTool.svelte.d.ts +10 -0
  11. package/dist/components/FilterTool.svelte +492 -0
  12. package/dist/components/FilterTool.svelte.d.ts +12 -0
  13. package/dist/components/ImageEditor.svelte +735 -0
  14. package/dist/components/ImageEditor.svelte.d.ts +12 -0
  15. package/dist/components/RotateTool.svelte +157 -0
  16. package/dist/components/RotateTool.svelte.d.ts +9 -0
  17. package/dist/components/StampTool.svelte +678 -0
  18. package/dist/components/StampTool.svelte.d.ts +15 -0
  19. package/dist/components/Toolbar.svelte +136 -0
  20. package/dist/components/Toolbar.svelte.d.ts +10 -0
  21. package/dist/config/stamps.d.ts +2 -0
  22. package/dist/config/stamps.js +22 -0
  23. package/dist/i18n/index.d.ts +1 -0
  24. package/dist/i18n/index.js +9 -0
  25. package/dist/i18n/locales/en.json +68 -0
  26. package/dist/i18n/locales/ja.json +68 -0
  27. package/dist/index.d.ts +4 -0
  28. package/dist/index.js +5 -0
  29. package/dist/types.d.ts +97 -0
  30. package/dist/types.js +1 -0
  31. package/dist/utils/adjustments.d.ts +26 -0
  32. package/dist/utils/adjustments.js +525 -0
  33. package/dist/utils/canvas.d.ts +30 -0
  34. package/dist/utils/canvas.js +293 -0
  35. package/dist/utils/filters.d.ts +18 -0
  36. package/dist/utils/filters.js +114 -0
  37. package/dist/utils/history.d.ts +15 -0
  38. package/dist/utils/history.js +67 -0
  39. package/package.json +1 -1
@@ -0,0 +1,214 @@
1
+ <script lang="ts">import { onMount } from 'svelte';
2
+ import { drawImage, preloadStampImage } from '../utils/canvas';
3
+ let { canvas = $bindable(null), width, height, image, viewport, transform, adjustments, cropArea = null, blurAreas = [], stampAreas = [], onZoom } = $props();
4
+ let canvasElement = $state(null);
5
+ let isPanning = $state(false);
6
+ let lastPanPosition = $state({ x: 0, y: 0 });
7
+ let imageLoadCounter = $state(0); // Trigger redraw when images load
8
+ let initialPinchDistance = $state(0);
9
+ let initialZoom = $state(1);
10
+ let renderRequested = $state(false);
11
+ let pendingRenderFrame = null;
12
+ onMount(() => {
13
+ if (canvasElement) {
14
+ canvas = canvasElement;
15
+ // Add touch event listeners with passive: false to allow preventDefault
16
+ canvasElement.addEventListener('touchstart', handleTouchStart, { passive: false });
17
+ canvasElement.addEventListener('touchmove', handleTouchMove, { passive: false });
18
+ canvasElement.addEventListener('touchend', handleTouchEnd, { passive: false });
19
+ }
20
+ return () => {
21
+ // Cleanup event listeners
22
+ if (canvasElement) {
23
+ canvasElement.removeEventListener('touchstart', handleTouchStart);
24
+ canvasElement.removeEventListener('touchmove', handleTouchMove);
25
+ canvasElement.removeEventListener('touchend', handleTouchEnd);
26
+ }
27
+ };
28
+ });
29
+ // Request a render using requestAnimationFrame
30
+ function requestRender() {
31
+ if (renderRequested)
32
+ return;
33
+ renderRequested = true;
34
+ if (pendingRenderFrame !== null) {
35
+ cancelAnimationFrame(pendingRenderFrame);
36
+ }
37
+ pendingRenderFrame = requestAnimationFrame(() => {
38
+ performRender();
39
+ renderRequested = false;
40
+ pendingRenderFrame = null;
41
+ });
42
+ }
43
+ // Perform the actual render
44
+ function performRender() {
45
+ if (!canvasElement || !image)
46
+ return;
47
+ canvasElement.width = width;
48
+ canvasElement.height = height;
49
+ drawImage(canvasElement, image, viewport, transform, adjustments, cropArea, blurAreas, stampAreas);
50
+ }
51
+ // Preload stamp images
52
+ $effect(() => {
53
+ if (!stampAreas)
54
+ return;
55
+ stampAreas.forEach(stamp => {
56
+ if (stamp.stampType === 'image' || stamp.stampType === 'svg') {
57
+ preloadStampImage(stamp.stampContent).then(() => {
58
+ // Trigger redraw when image loads
59
+ imageLoadCounter++;
60
+ }).catch(console.error);
61
+ }
62
+ });
63
+ });
64
+ // Draw canvas - use requestAnimationFrame for optimal performance
65
+ $effect(() => {
66
+ if (canvasElement && image) {
67
+ requestRender();
68
+ }
69
+ // Include all dependencies
70
+ width;
71
+ height;
72
+ viewport;
73
+ transform;
74
+ adjustments;
75
+ cropArea;
76
+ blurAreas;
77
+ stampAreas;
78
+ imageLoadCounter;
79
+ });
80
+ function handleMouseDown(e) {
81
+ // Left mouse button (0) or Middle mouse button (1) for panning
82
+ if (e.button === 0 || e.button === 1) {
83
+ isPanning = true;
84
+ lastPanPosition = { x: e.clientX, y: e.clientY };
85
+ e.preventDefault();
86
+ }
87
+ }
88
+ function handleMouseMove(e) {
89
+ if (isPanning && image && canvasElement) {
90
+ const deltaX = e.clientX - lastPanPosition.x;
91
+ const deltaY = e.clientY - lastPanPosition.y;
92
+ // Calculate actual image dimensions after crop and scale
93
+ const imgWidth = cropArea ? cropArea.width : image.width;
94
+ const imgHeight = cropArea ? cropArea.height : image.height;
95
+ const totalScale = viewport.scale * viewport.zoom;
96
+ const scaledWidth = imgWidth * totalScale;
97
+ const scaledHeight = imgHeight * totalScale;
98
+ // Allow 20% overflow outside canvas
99
+ const overflowMargin = 0.2;
100
+ const maxOffsetX = (scaledWidth / 2) - (canvasElement.width / 2) + (canvasElement.width * overflowMargin);
101
+ const maxOffsetY = (scaledHeight / 2) - (canvasElement.height / 2) + (canvasElement.height * overflowMargin);
102
+ // Apply limits
103
+ const newOffsetX = viewport.offsetX + deltaX;
104
+ const newOffsetY = viewport.offsetY + deltaY;
105
+ viewport.offsetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, newOffsetX));
106
+ viewport.offsetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, newOffsetY));
107
+ lastPanPosition = { x: e.clientX, y: e.clientY };
108
+ e.preventDefault();
109
+ }
110
+ }
111
+ function handleMouseUp() {
112
+ isPanning = false;
113
+ }
114
+ function handleTouchStart(e) {
115
+ if (e.touches.length === 1) {
116
+ // Single finger panning
117
+ isPanning = true;
118
+ lastPanPosition = { x: e.touches[0].clientX, y: e.touches[0].clientY };
119
+ e.preventDefault();
120
+ }
121
+ else if (e.touches.length === 2) {
122
+ // Pinch zoom - stop panning
123
+ isPanning = false;
124
+ e.preventDefault();
125
+ }
126
+ }
127
+ function handleTouchMove(e) {
128
+ if (e.touches.length === 1 && isPanning && image && canvasElement) {
129
+ // Single finger panning
130
+ e.preventDefault();
131
+ const touch = e.touches[0];
132
+ const deltaX = touch.clientX - lastPanPosition.x;
133
+ const deltaY = touch.clientY - lastPanPosition.y;
134
+ // Calculate actual image dimensions after crop and scale
135
+ const imgWidth = cropArea ? cropArea.width : image.width;
136
+ const imgHeight = cropArea ? cropArea.height : image.height;
137
+ const totalScale = viewport.scale * viewport.zoom;
138
+ const scaledWidth = imgWidth * totalScale;
139
+ const scaledHeight = imgHeight * totalScale;
140
+ // Allow 20% overflow outside canvas
141
+ const overflowMargin = 0.2;
142
+ const maxOffsetX = (scaledWidth / 2) - (canvasElement.width / 2) + (canvasElement.width * overflowMargin);
143
+ const maxOffsetY = (scaledHeight / 2) - (canvasElement.height / 2) + (canvasElement.height * overflowMargin);
144
+ // Apply limits
145
+ const newOffsetX = viewport.offsetX + deltaX;
146
+ const newOffsetY = viewport.offsetY + deltaY;
147
+ viewport.offsetX = Math.max(-maxOffsetX, Math.min(maxOffsetX, newOffsetX));
148
+ viewport.offsetY = Math.max(-maxOffsetY, Math.min(maxOffsetY, newOffsetY));
149
+ lastPanPosition = { x: touch.clientX, y: touch.clientY };
150
+ }
151
+ else if (e.touches.length === 2) {
152
+ // Pinch zoom
153
+ e.preventDefault();
154
+ const touch1 = e.touches[0];
155
+ const touch2 = e.touches[1];
156
+ const distance = Math.hypot(touch2.clientX - touch1.clientX, touch2.clientY - touch1.clientY);
157
+ if (initialPinchDistance === 0) {
158
+ initialPinchDistance = distance;
159
+ initialZoom = viewport.zoom;
160
+ }
161
+ else {
162
+ const scale = distance / initialPinchDistance;
163
+ const newZoom = Math.max(0.1, Math.min(5, initialZoom * scale));
164
+ const delta = newZoom - viewport.zoom;
165
+ const centerX = (touch1.clientX + touch2.clientX) / 2;
166
+ const centerY = (touch1.clientY + touch2.clientY) / 2;
167
+ if (onZoom) {
168
+ onZoom(delta, centerX, centerY);
169
+ }
170
+ }
171
+ }
172
+ }
173
+ function handleTouchEnd(e) {
174
+ if (e.touches.length === 0) {
175
+ isPanning = false;
176
+ initialPinchDistance = 0;
177
+ }
178
+ else if (e.touches.length === 1) {
179
+ // Switched from pinch to pan
180
+ initialPinchDistance = 0;
181
+ isPanning = true;
182
+ lastPanPosition = { x: e.touches[0].clientX, y: e.touches[0].clientY };
183
+ }
184
+ }
185
+ </script>
186
+
187
+ <svelte:window
188
+ onmousemove={handleMouseMove}
189
+ onmouseup={handleMouseUp}
190
+ />
191
+
192
+ <canvas
193
+ bind:this={canvasElement}
194
+ width={width}
195
+ height={height}
196
+ class="editor-canvas"
197
+ class:panning={isPanning}
198
+ style="max-width: 100%; max-height: {height}px;"
199
+ onmousedown={handleMouseDown}
200
+ ></canvas>
201
+
202
+ <style>
203
+ .editor-canvas {
204
+ display: block;
205
+ background: #000;
206
+ cursor: grab;
207
+ touch-action: none;
208
+ user-select: none;
209
+ -webkit-user-select: none;
210
+ }
211
+
212
+ .editor-canvas.panning {
213
+ cursor: grabbing;
214
+ }</style>
@@ -0,0 +1,17 @@
1
+ import type { Viewport, TransformState, CropArea, AdjustmentsState, BlurArea, StampArea } from '../types';
2
+ interface Props {
3
+ canvas?: HTMLCanvasElement | null;
4
+ width: number;
5
+ height: number;
6
+ image: HTMLImageElement | null;
7
+ viewport: Viewport;
8
+ transform: TransformState;
9
+ adjustments: AdjustmentsState;
10
+ cropArea?: CropArea | null;
11
+ blurAreas?: BlurArea[];
12
+ stampAreas?: StampArea[];
13
+ onZoom?: (delta: number, centerX?: number, centerY?: number) => void;
14
+ }
15
+ declare const Canvas: import("svelte").Component<Props, {}, "canvas">;
16
+ type Canvas = ReturnType<typeof Canvas>;
17
+ export default Canvas;