react-native-image-editor-skia 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.
Files changed (299) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/lib/commonjs/ImageEditor.js +141 -0
  4. package/lib/commonjs/ImageEditor.js.map +1 -0
  5. package/lib/commonjs/annotations/AnnotationView.js +42 -0
  6. package/lib/commonjs/annotations/AnnotationView.js.map +1 -0
  7. package/lib/commonjs/annotations/ArrowAnnotation.js +29 -0
  8. package/lib/commonjs/annotations/ArrowAnnotation.js.map +1 -0
  9. package/lib/commonjs/annotations/CircleAnnotation.js +31 -0
  10. package/lib/commonjs/annotations/CircleAnnotation.js.map +1 -0
  11. package/lib/commonjs/annotations/FreehandAnnotation.js +29 -0
  12. package/lib/commonjs/annotations/FreehandAnnotation.js.map +1 -0
  13. package/lib/commonjs/annotations/MarkerAnnotation.js +27 -0
  14. package/lib/commonjs/annotations/MarkerAnnotation.js.map +1 -0
  15. package/lib/commonjs/annotations/RotatedGroup.js +34 -0
  16. package/lib/commonjs/annotations/RotatedGroup.js.map +1 -0
  17. package/lib/commonjs/annotations/TextAnnotation.js +40 -0
  18. package/lib/commonjs/annotations/TextAnnotation.js.map +1 -0
  19. package/lib/commonjs/annotations/geometry.js +73 -0
  20. package/lib/commonjs/annotations/geometry.js.map +1 -0
  21. package/lib/commonjs/annotations/geometryPure.js +104 -0
  22. package/lib/commonjs/annotations/geometryPure.js.map +1 -0
  23. package/lib/commonjs/canvas/AnnotationLayer.js +58 -0
  24. package/lib/commonjs/canvas/AnnotationLayer.js.map +1 -0
  25. package/lib/commonjs/canvas/BaseImageLayer.js +27 -0
  26. package/lib/commonjs/canvas/BaseImageLayer.js.map +1 -0
  27. package/lib/commonjs/canvas/CropOverlay.js +135 -0
  28. package/lib/commonjs/canvas/CropOverlay.js.map +1 -0
  29. package/lib/commonjs/canvas/EditorCanvas.js +91 -0
  30. package/lib/commonjs/canvas/EditorCanvas.js.map +1 -0
  31. package/lib/commonjs/canvas/InFlightLayer.js +152 -0
  32. package/lib/commonjs/canvas/InFlightLayer.js.map +1 -0
  33. package/lib/commonjs/canvas/SelectionOverlay.js +90 -0
  34. package/lib/commonjs/canvas/SelectionOverlay.js.map +1 -0
  35. package/lib/commonjs/constants.js +56 -0
  36. package/lib/commonjs/constants.js.map +1 -0
  37. package/lib/commonjs/context/EditorContext.js +132 -0
  38. package/lib/commonjs/context/EditorContext.js.map +1 -0
  39. package/lib/commonjs/export/drawScene.js +97 -0
  40. package/lib/commonjs/export/drawScene.js.map +1 -0
  41. package/lib/commonjs/export/exportImage.js +92 -0
  42. package/lib/commonjs/export/exportImage.js.map +1 -0
  43. package/lib/commonjs/gestures/applyTransform.js +79 -0
  44. package/lib/commonjs/gestures/applyTransform.js.map +1 -0
  45. package/lib/commonjs/gestures/createAnnotation.js +73 -0
  46. package/lib/commonjs/gestures/createAnnotation.js.map +1 -0
  47. package/lib/commonjs/gestures/handles.js +53 -0
  48. package/lib/commonjs/gestures/handles.js.map +1 -0
  49. package/lib/commonjs/gestures/hitTest.js +72 -0
  50. package/lib/commonjs/gestures/hitTest.js.map +1 -0
  51. package/lib/commonjs/gestures/useCropGesture.js +149 -0
  52. package/lib/commonjs/gestures/useCropGesture.js.map +1 -0
  53. package/lib/commonjs/gestures/useEditorGestures.js +289 -0
  54. package/lib/commonjs/gestures/useEditorGestures.js.map +1 -0
  55. package/lib/commonjs/image/disposeRegistry.js +63 -0
  56. package/lib/commonjs/image/disposeRegistry.js.map +1 -0
  57. package/lib/commonjs/image/useLoadedImage.js +121 -0
  58. package/lib/commonjs/image/useLoadedImage.js.map +1 -0
  59. package/lib/commonjs/index.js +52 -0
  60. package/lib/commonjs/index.js.map +1 -0
  61. package/lib/commonjs/package.json +1 -0
  62. package/lib/commonjs/state/history.js +85 -0
  63. package/lib/commonjs/state/history.js.map +1 -0
  64. package/lib/commonjs/state/selectors.js +19 -0
  65. package/lib/commonjs/state/selectors.js.map +1 -0
  66. package/lib/commonjs/state/useEditorReducer.js +83 -0
  67. package/lib/commonjs/state/useEditorReducer.js.map +1 -0
  68. package/lib/commonjs/toolbar/ColorPicker.js +84 -0
  69. package/lib/commonjs/toolbar/ColorPicker.js.map +1 -0
  70. package/lib/commonjs/toolbar/CropControls.js +65 -0
  71. package/lib/commonjs/toolbar/CropControls.js.map +1 -0
  72. package/lib/commonjs/toolbar/RotationSlider.js +73 -0
  73. package/lib/commonjs/toolbar/RotationSlider.js.map +1 -0
  74. package/lib/commonjs/toolbar/TextInputOverlay.js +108 -0
  75. package/lib/commonjs/toolbar/TextInputOverlay.js.map +1 -0
  76. package/lib/commonjs/toolbar/ToolButton.js +56 -0
  77. package/lib/commonjs/toolbar/ToolButton.js.map +1 -0
  78. package/lib/commonjs/toolbar/Toolbar.js +137 -0
  79. package/lib/commonjs/toolbar/Toolbar.js.map +1 -0
  80. package/lib/commonjs/types.js +47 -0
  81. package/lib/commonjs/types.js.map +1 -0
  82. package/lib/commonjs/utils/color.js +37 -0
  83. package/lib/commonjs/utils/color.js.map +1 -0
  84. package/lib/commonjs/utils/id.js +14 -0
  85. package/lib/commonjs/utils/id.js.map +1 -0
  86. package/lib/commonjs/utils/math.js +277 -0
  87. package/lib/commonjs/utils/math.js.map +1 -0
  88. package/lib/module/ImageEditor.js +138 -0
  89. package/lib/module/ImageEditor.js.map +1 -0
  90. package/lib/module/annotations/AnnotationView.js +39 -0
  91. package/lib/module/annotations/AnnotationView.js.map +1 -0
  92. package/lib/module/annotations/ArrowAnnotation.js +26 -0
  93. package/lib/module/annotations/ArrowAnnotation.js.map +1 -0
  94. package/lib/module/annotations/CircleAnnotation.js +27 -0
  95. package/lib/module/annotations/CircleAnnotation.js.map +1 -0
  96. package/lib/module/annotations/FreehandAnnotation.js +25 -0
  97. package/lib/module/annotations/FreehandAnnotation.js.map +1 -0
  98. package/lib/module/annotations/MarkerAnnotation.js +23 -0
  99. package/lib/module/annotations/MarkerAnnotation.js.map +1 -0
  100. package/lib/module/annotations/RotatedGroup.js +29 -0
  101. package/lib/module/annotations/RotatedGroup.js.map +1 -0
  102. package/lib/module/annotations/TextAnnotation.js +37 -0
  103. package/lib/module/annotations/TextAnnotation.js.map +1 -0
  104. package/lib/module/annotations/geometry.js +56 -0
  105. package/lib/module/annotations/geometry.js.map +1 -0
  106. package/lib/module/annotations/geometryPure.js +100 -0
  107. package/lib/module/annotations/geometryPure.js.map +1 -0
  108. package/lib/module/canvas/AnnotationLayer.js +55 -0
  109. package/lib/module/canvas/AnnotationLayer.js.map +1 -0
  110. package/lib/module/canvas/BaseImageLayer.js +23 -0
  111. package/lib/module/canvas/BaseImageLayer.js.map +1 -0
  112. package/lib/module/canvas/CropOverlay.js +131 -0
  113. package/lib/module/canvas/CropOverlay.js.map +1 -0
  114. package/lib/module/canvas/EditorCanvas.js +88 -0
  115. package/lib/module/canvas/EditorCanvas.js.map +1 -0
  116. package/lib/module/canvas/InFlightLayer.js +149 -0
  117. package/lib/module/canvas/InFlightLayer.js.map +1 -0
  118. package/lib/module/canvas/SelectionOverlay.js +85 -0
  119. package/lib/module/canvas/SelectionOverlay.js.map +1 -0
  120. package/lib/module/constants.js +52 -0
  121. package/lib/module/constants.js.map +1 -0
  122. package/lib/module/context/EditorContext.js +126 -0
  123. package/lib/module/context/EditorContext.js.map +1 -0
  124. package/lib/module/export/drawScene.js +93 -0
  125. package/lib/module/export/drawScene.js.map +1 -0
  126. package/lib/module/export/exportImage.js +88 -0
  127. package/lib/module/export/exportImage.js.map +1 -0
  128. package/lib/module/gestures/applyTransform.js +75 -0
  129. package/lib/module/gestures/applyTransform.js.map +1 -0
  130. package/lib/module/gestures/createAnnotation.js +65 -0
  131. package/lib/module/gestures/createAnnotation.js.map +1 -0
  132. package/lib/module/gestures/handles.js +49 -0
  133. package/lib/module/gestures/handles.js.map +1 -0
  134. package/lib/module/gestures/hitTest.js +69 -0
  135. package/lib/module/gestures/hitTest.js.map +1 -0
  136. package/lib/module/gestures/useCropGesture.js +145 -0
  137. package/lib/module/gestures/useCropGesture.js.map +1 -0
  138. package/lib/module/gestures/useEditorGestures.js +285 -0
  139. package/lib/module/gestures/useEditorGestures.js.map +1 -0
  140. package/lib/module/image/disposeRegistry.js +57 -0
  141. package/lib/module/image/disposeRegistry.js.map +1 -0
  142. package/lib/module/image/useLoadedImage.js +117 -0
  143. package/lib/module/image/useLoadedImage.js.map +1 -0
  144. package/lib/module/index.js +8 -0
  145. package/lib/module/index.js.map +1 -0
  146. package/lib/module/package.json +1 -0
  147. package/lib/module/state/history.js +76 -0
  148. package/lib/module/state/history.js.map +1 -0
  149. package/lib/module/state/selectors.js +14 -0
  150. package/lib/module/state/selectors.js.map +1 -0
  151. package/lib/module/state/useEditorReducer.js +79 -0
  152. package/lib/module/state/useEditorReducer.js.map +1 -0
  153. package/lib/module/toolbar/ColorPicker.js +80 -0
  154. package/lib/module/toolbar/ColorPicker.js.map +1 -0
  155. package/lib/module/toolbar/CropControls.js +62 -0
  156. package/lib/module/toolbar/CropControls.js.map +1 -0
  157. package/lib/module/toolbar/RotationSlider.js +69 -0
  158. package/lib/module/toolbar/RotationSlider.js.map +1 -0
  159. package/lib/module/toolbar/TextInputOverlay.js +105 -0
  160. package/lib/module/toolbar/TextInputOverlay.js.map +1 -0
  161. package/lib/module/toolbar/ToolButton.js +52 -0
  162. package/lib/module/toolbar/ToolButton.js.map +1 -0
  163. package/lib/module/toolbar/Toolbar.js +133 -0
  164. package/lib/module/toolbar/Toolbar.js.map +1 -0
  165. package/lib/module/types.js +43 -0
  166. package/lib/module/types.js.map +1 -0
  167. package/lib/module/utils/color.js +33 -0
  168. package/lib/module/utils/color.js.map +1 -0
  169. package/lib/module/utils/id.js +10 -0
  170. package/lib/module/utils/id.js.map +1 -0
  171. package/lib/module/utils/math.js +258 -0
  172. package/lib/module/utils/math.js.map +1 -0
  173. package/lib/typescript/src/ImageEditor.d.ts +9 -0
  174. package/lib/typescript/src/ImageEditor.d.ts.map +1 -0
  175. package/lib/typescript/src/annotations/AnnotationView.d.ts +6 -0
  176. package/lib/typescript/src/annotations/AnnotationView.d.ts.map +1 -0
  177. package/lib/typescript/src/annotations/ArrowAnnotation.d.ts +5 -0
  178. package/lib/typescript/src/annotations/ArrowAnnotation.d.ts.map +1 -0
  179. package/lib/typescript/src/annotations/CircleAnnotation.d.ts +5 -0
  180. package/lib/typescript/src/annotations/CircleAnnotation.d.ts.map +1 -0
  181. package/lib/typescript/src/annotations/FreehandAnnotation.d.ts +5 -0
  182. package/lib/typescript/src/annotations/FreehandAnnotation.d.ts.map +1 -0
  183. package/lib/typescript/src/annotations/MarkerAnnotation.d.ts +5 -0
  184. package/lib/typescript/src/annotations/MarkerAnnotation.d.ts.map +1 -0
  185. package/lib/typescript/src/annotations/RotatedGroup.d.ts +13 -0
  186. package/lib/typescript/src/annotations/RotatedGroup.d.ts.map +1 -0
  187. package/lib/typescript/src/annotations/TextAnnotation.d.ts +10 -0
  188. package/lib/typescript/src/annotations/TextAnnotation.d.ts.map +1 -0
  189. package/lib/typescript/src/annotations/geometry.d.ts +11 -0
  190. package/lib/typescript/src/annotations/geometry.d.ts.map +1 -0
  191. package/lib/typescript/src/annotations/geometryPure.d.ts +14 -0
  192. package/lib/typescript/src/annotations/geometryPure.d.ts.map +1 -0
  193. package/lib/typescript/src/canvas/AnnotationLayer.d.ts +11 -0
  194. package/lib/typescript/src/canvas/AnnotationLayer.d.ts.map +1 -0
  195. package/lib/typescript/src/canvas/BaseImageLayer.d.ts +12 -0
  196. package/lib/typescript/src/canvas/BaseImageLayer.d.ts.map +1 -0
  197. package/lib/typescript/src/canvas/CropOverlay.d.ts +10 -0
  198. package/lib/typescript/src/canvas/CropOverlay.d.ts.map +1 -0
  199. package/lib/typescript/src/canvas/EditorCanvas.d.ts +18 -0
  200. package/lib/typescript/src/canvas/EditorCanvas.d.ts.map +1 -0
  201. package/lib/typescript/src/canvas/InFlightLayer.d.ts +12 -0
  202. package/lib/typescript/src/canvas/InFlightLayer.d.ts.map +1 -0
  203. package/lib/typescript/src/canvas/SelectionOverlay.d.ts +11 -0
  204. package/lib/typescript/src/canvas/SelectionOverlay.d.ts.map +1 -0
  205. package/lib/typescript/src/constants.d.ts +25 -0
  206. package/lib/typescript/src/constants.d.ts.map +1 -0
  207. package/lib/typescript/src/context/EditorContext.d.ts +66 -0
  208. package/lib/typescript/src/context/EditorContext.d.ts.map +1 -0
  209. package/lib/typescript/src/export/drawScene.d.ts +10 -0
  210. package/lib/typescript/src/export/drawScene.d.ts.map +1 -0
  211. package/lib/typescript/src/export/exportImage.d.ts +23 -0
  212. package/lib/typescript/src/export/exportImage.d.ts.map +1 -0
  213. package/lib/typescript/src/gestures/applyTransform.d.ts +17 -0
  214. package/lib/typescript/src/gestures/applyTransform.d.ts.map +1 -0
  215. package/lib/typescript/src/gestures/createAnnotation.d.ts +11 -0
  216. package/lib/typescript/src/gestures/createAnnotation.d.ts.map +1 -0
  217. package/lib/typescript/src/gestures/handles.d.ts +17 -0
  218. package/lib/typescript/src/gestures/handles.d.ts.map +1 -0
  219. package/lib/typescript/src/gestures/hitTest.d.ts +9 -0
  220. package/lib/typescript/src/gestures/hitTest.d.ts.map +1 -0
  221. package/lib/typescript/src/gestures/useCropGesture.d.ts +7 -0
  222. package/lib/typescript/src/gestures/useCropGesture.d.ts.map +1 -0
  223. package/lib/typescript/src/gestures/useEditorGestures.d.ts +8 -0
  224. package/lib/typescript/src/gestures/useEditorGestures.d.ts.map +1 -0
  225. package/lib/typescript/src/image/disposeRegistry.d.ts +25 -0
  226. package/lib/typescript/src/image/disposeRegistry.d.ts.map +1 -0
  227. package/lib/typescript/src/image/useLoadedImage.d.ts +23 -0
  228. package/lib/typescript/src/image/useLoadedImage.d.ts.map +1 -0
  229. package/lib/typescript/src/index.d.ts +6 -0
  230. package/lib/typescript/src/index.d.ts.map +1 -0
  231. package/lib/typescript/src/state/history.d.ts +23 -0
  232. package/lib/typescript/src/state/history.d.ts.map +1 -0
  233. package/lib/typescript/src/state/selectors.d.ts +5 -0
  234. package/lib/typescript/src/state/selectors.d.ts.map +1 -0
  235. package/lib/typescript/src/state/useEditorReducer.d.ts +32 -0
  236. package/lib/typescript/src/state/useEditorReducer.d.ts.map +1 -0
  237. package/lib/typescript/src/toolbar/ColorPicker.d.ts +7 -0
  238. package/lib/typescript/src/toolbar/ColorPicker.d.ts.map +1 -0
  239. package/lib/typescript/src/toolbar/CropControls.d.ts +3 -0
  240. package/lib/typescript/src/toolbar/CropControls.d.ts.map +1 -0
  241. package/lib/typescript/src/toolbar/RotationSlider.d.ts +8 -0
  242. package/lib/typescript/src/toolbar/RotationSlider.d.ts.map +1 -0
  243. package/lib/typescript/src/toolbar/TextInputOverlay.d.ts +9 -0
  244. package/lib/typescript/src/toolbar/TextInputOverlay.d.ts.map +1 -0
  245. package/lib/typescript/src/toolbar/ToolButton.d.ts +7 -0
  246. package/lib/typescript/src/toolbar/ToolButton.d.ts.map +1 -0
  247. package/lib/typescript/src/toolbar/Toolbar.d.ts +4 -0
  248. package/lib/typescript/src/toolbar/Toolbar.d.ts.map +1 -0
  249. package/lib/typescript/src/types.d.ts +170 -0
  250. package/lib/typescript/src/types.d.ts.map +1 -0
  251. package/lib/typescript/src/utils/color.d.ts +8 -0
  252. package/lib/typescript/src/utils/color.d.ts.map +1 -0
  253. package/lib/typescript/src/utils/id.d.ts +3 -0
  254. package/lib/typescript/src/utils/id.d.ts.map +1 -0
  255. package/lib/typescript/src/utils/math.d.ts +68 -0
  256. package/lib/typescript/src/utils/math.d.ts.map +1 -0
  257. package/package.json +90 -0
  258. package/src/ImageEditor.tsx +133 -0
  259. package/src/annotations/AnnotationView.tsx +24 -0
  260. package/src/annotations/ArrowAnnotation.tsx +26 -0
  261. package/src/annotations/CircleAnnotation.tsx +22 -0
  262. package/src/annotations/FreehandAnnotation.tsx +22 -0
  263. package/src/annotations/MarkerAnnotation.tsx +20 -0
  264. package/src/annotations/RotatedGroup.tsx +28 -0
  265. package/src/annotations/TextAnnotation.tsx +42 -0
  266. package/src/annotations/geometry.ts +62 -0
  267. package/src/annotations/geometryPure.ts +73 -0
  268. package/src/canvas/AnnotationLayer.tsx +43 -0
  269. package/src/canvas/BaseImageLayer.tsx +28 -0
  270. package/src/canvas/CropOverlay.tsx +92 -0
  271. package/src/canvas/EditorCanvas.tsx +70 -0
  272. package/src/canvas/InFlightLayer.tsx +140 -0
  273. package/src/canvas/SelectionOverlay.tsx +92 -0
  274. package/src/constants.ts +46 -0
  275. package/src/context/EditorContext.tsx +229 -0
  276. package/src/export/drawScene.ts +120 -0
  277. package/src/export/exportImage.ts +111 -0
  278. package/src/gestures/applyTransform.ts +76 -0
  279. package/src/gestures/createAnnotation.ts +92 -0
  280. package/src/gestures/handles.ts +50 -0
  281. package/src/gestures/hitTest.ts +79 -0
  282. package/src/gestures/useCropGesture.ts +123 -0
  283. package/src/gestures/useEditorGestures.ts +308 -0
  284. package/src/image/disposeRegistry.ts +59 -0
  285. package/src/image/useLoadedImage.ts +131 -0
  286. package/src/index.ts +32 -0
  287. package/src/state/history.ts +71 -0
  288. package/src/state/selectors.ts +16 -0
  289. package/src/state/useEditorReducer.ts +93 -0
  290. package/src/toolbar/ColorPicker.tsx +72 -0
  291. package/src/toolbar/CropControls.tsx +46 -0
  292. package/src/toolbar/RotationSlider.tsx +56 -0
  293. package/src/toolbar/TextInputOverlay.tsx +104 -0
  294. package/src/toolbar/ToolButton.tsx +46 -0
  295. package/src/toolbar/Toolbar.tsx +110 -0
  296. package/src/types.ts +203 -0
  297. package/src/utils/color.ts +34 -0
  298. package/src/utils/id.ts +7 -0
  299. package/src/utils/math.ts +222 -0
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Public + internal types for the Skia image editor.
3
+ *
4
+ * COORDINATE-SPACE CONTRACT (read this first):
5
+ * Every annotation's geometry is stored in **image space** — the base image's
6
+ * full-resolution pixel coordinate system (0..imageWidth, 0..imageHeight).
7
+ * It is NEVER stored in screen space. A single `imageToScreen` affine matrix
8
+ * maps image space onto the on-screen canvas; its inverse maps touches back.
9
+ * Because of this, export can render the exact same numbers off-screen at native
10
+ * resolution with no rescaling. See `utils/math.ts`.
11
+ */
12
+ export interface Vec2 {
13
+ x: number;
14
+ y: number;
15
+ }
16
+ export interface Rect {
17
+ x: number;
18
+ y: number;
19
+ width: number;
20
+ height: number;
21
+ }
22
+ export type ToolType = 'select' | 'circle' | 'arrow' | 'marker' | 'freehand' | 'text' | 'crop';
23
+ export type AnnotationType = 'circle' | 'arrow' | 'marker' | 'freehand' | 'text';
24
+ /**
25
+ * Colors are stored as CSS-style strings ("#RRGGBB" / "#RRGGBBAA" / named).
26
+ * Skia accepts these directly, and they are trivially serializable for history.
27
+ */
28
+ export type ColorString = string;
29
+ interface BaseAnnotation {
30
+ id: string;
31
+ type: AnnotationType;
32
+ /** Rotation in radians about the annotation's own center. */
33
+ rotation: number;
34
+ /** Paint order; higher = drawn on top / hit-tested first. */
35
+ z: number;
36
+ }
37
+ export interface CircleAnnotation extends BaseAnnotation {
38
+ type: 'circle';
39
+ center: Vec2;
40
+ radius: number;
41
+ strokeColor: ColorString;
42
+ strokeWidth: number;
43
+ /** Optional fill; undefined = outline only. */
44
+ fill?: ColorString;
45
+ }
46
+ export interface ArrowAnnotation extends BaseAnnotation {
47
+ type: 'arrow';
48
+ start: Vec2;
49
+ end: Vec2;
50
+ /** Length of the arrowhead barbs, in image pixels. */
51
+ headSize: number;
52
+ strokeColor: ColorString;
53
+ strokeWidth: number;
54
+ }
55
+ export interface MarkerAnnotation extends BaseAnnotation {
56
+ type: 'marker';
57
+ /** Highlight rectangle, in image space. */
58
+ rect: Rect;
59
+ color: ColorString;
60
+ /** 0..1 — highlighters are semi-transparent. */
61
+ opacity: number;
62
+ }
63
+ export interface FreehandAnnotation extends BaseAnnotation {
64
+ type: 'freehand';
65
+ points: Vec2[];
66
+ strokeColor: ColorString;
67
+ strokeWidth: number;
68
+ }
69
+ export interface TextAnnotation extends BaseAnnotation {
70
+ type: 'text';
71
+ /** Top-left origin of the text box, in image space. */
72
+ origin: Vec2;
73
+ text: string;
74
+ color: ColorString;
75
+ fontSize: number;
76
+ /** Wrap width of the text box, in image pixels. */
77
+ width: number;
78
+ }
79
+ export type Annotation = CircleAnnotation | ArrowAnnotation | MarkerAnnotation | FreehandAnnotation | TextAnnotation;
80
+ /**
81
+ * Non-destructive image transforms. Nothing is baked into pixels until export,
82
+ * so all of these are undoable.
83
+ */
84
+ export interface SceneTransform {
85
+ /** Free-angle rotation of the whole image + its annotations, in radians. */
86
+ rotation: number;
87
+ /** Uniform resize factor applied at export. */
88
+ scale: number;
89
+ /** Crop region in image space, or null for the full image. */
90
+ cropRect: Rect | null;
91
+ }
92
+ export declare const IDENTITY_SCENE: SceneTransform;
93
+ /** The serializable editor document — everything undo/redo tracks. */
94
+ export interface EditorDocument {
95
+ annotations: Annotation[];
96
+ scene: SceneTransform;
97
+ }
98
+ export type OutputFormat = 'png' | 'jpeg';
99
+ /**
100
+ * Callback that persists an encoded image to disk. The library has no
101
+ * filesystem access of its own, so the consumer supplies this using whatever fs
102
+ * module they already have (react-native-fs, expo-file-system, etc.). It
103
+ * receives the destination path and the RAW base64 payload (no data-URI prefix).
104
+ */
105
+ export type WriteFileFn = (path: string, base64: string) => Promise<void>;
106
+ export interface ExportOptions {
107
+ format?: OutputFormat;
108
+ /** JPEG quality 0..100 (ignored for PNG). Default 100. */
109
+ quality?: number;
110
+ /**
111
+ * Clamp the longest output edge to this many pixels (keeps aspect ratio).
112
+ * Guards against OOM when snapshotting very large images. Default: no clamp.
113
+ */
114
+ maxExportSize?: number;
115
+ /**
116
+ * `'base64'` (default) resolves to a base64 string. `'file'` writes the image
117
+ * to `filePath` using `writeFile` and resolves to that path.
118
+ */
119
+ output?: 'base64' | 'file';
120
+ /** Destination path/URI when `output: 'file'`. */
121
+ filePath?: string;
122
+ /** Required when `output: 'file'` — writes the base64 payload to disk. */
123
+ writeFile?: WriteFileFn;
124
+ /**
125
+ * For `output: 'base64'` only. When true (default), returns a
126
+ * `data:image/...;base64,...` URI. When false, returns raw base64 (no prefix).
127
+ */
128
+ dataUri?: boolean;
129
+ }
130
+ /** Imperative handle exposed via `ref`. */
131
+ export interface ImageEditorRef {
132
+ /**
133
+ * Render the current scene off-screen at full resolution. Resolves to base64
134
+ * (default) or, with `output: 'file'`, to the written file path.
135
+ */
136
+ export: (options?: ExportOptions) => Promise<string>;
137
+ undo: () => void;
138
+ redo: () => void;
139
+ canUndo: () => boolean;
140
+ canRedo: () => boolean;
141
+ /** Remove all annotations and reset scene transforms (keeps the base image). */
142
+ reset: () => void;
143
+ }
144
+ /** Source image: a base64 string (raw or data-URI) or a file/remote URI. */
145
+ export type ImageSource = {
146
+ base64: string;
147
+ } | {
148
+ uri: string;
149
+ };
150
+ export interface ImageEditorProps {
151
+ /** Base image to edit. */
152
+ source: ImageSource;
153
+ /** Fired when the user taps the built-in export/done control. */
154
+ onExport?: (base64: string) => void;
155
+ /** Default export options for the built-in export control. */
156
+ exportOptions?: ExportOptions;
157
+ /** Initial stroke color for new shapes. Default "#FF3B30". */
158
+ initialStrokeColor?: ColorString;
159
+ /** Initial text color. Default "#FFFFFF". */
160
+ initialTextColor?: ColorString;
161
+ /** Palette shown in the color picker. */
162
+ palette?: ColorString[];
163
+ /** Hide the built-in toolbar (drive tools yourself via ref/context). */
164
+ hideToolbar?: boolean;
165
+ /** Called whenever the loaded image fails to decode. */
166
+ onError?: (error: Error) => void;
167
+ style?: import('react-native').StyleProp<import('react-native').ViewStyle>;
168
+ }
169
+ export {};
170
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,QAAQ,GAChB,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,UAAU,GACV,MAAM,GACN,MAAM,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,CAAC;AAEjF;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAEjC,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,cAAc,CAAC;IACrB,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,WAAW,eAAgB,SAAQ,cAAc;IACrD,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,IAAI,CAAC;IACZ,GAAG,EAAE,IAAI,CAAC;IACV,sDAAsD;IACtD,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAiB,SAAQ,cAAc;IACtD,IAAI,EAAE,QAAQ,CAAC;IACf,2CAA2C;IAC3C,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,EAAE,WAAW,CAAC;IACnB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,kBAAmB,SAAQ,cAAc;IACxD,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,IAAI,EAAE,CAAC;IACf,WAAW,EAAE,WAAW,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAe,SAAQ,cAAc;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,MAAM,EAAE,IAAI,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,UAAU,GAClB,gBAAgB,GAChB,eAAe,GACf,gBAAgB,GAChB,kBAAkB,GAClB,cAAc,CAAC;AAEnB;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,cAAc,EAAE,cAI5B,CAAC;AAEF,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,KAAK,EAAE,cAAc,CAAC;CACvB;AAED,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAE1E,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,0DAA0D;IAC1D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,2CAA2C;AAC3C,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,aAAa,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,OAAO,EAAE,MAAM,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,OAAO,CAAC;IACvB,gFAAgF;IAChF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,4EAA4E;AAC5E,MAAM,MAAM,WAAW,GACnB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAEpB,MAAM,WAAW,gBAAgB;IAC/B,0BAA0B;IAC1B,MAAM,EAAE,WAAW,CAAC;IACpB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,8DAA8D;IAC9D,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,WAAW,CAAC;IACjC,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC/B,yCAAyC;IACzC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,wEAAwE;IACxE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wDAAwD;IACxD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,KAAK,CAAC,EAAE,OAAO,cAAc,EAAE,SAAS,CAAC,OAAO,cAAc,EAAE,SAAS,CAAC,CAAC;CAC5E"}
@@ -0,0 +1,8 @@
1
+ import type { ColorString } from '../types';
2
+ /**
3
+ * Apply an opacity (0..1) to a hex color string, returning an 8-digit hex
4
+ * ("#RRGGBBAA"). Used by the marker/highlighter which draws semi-transparently.
5
+ * Non-hex inputs (named colors, rgba(...)) are returned unchanged.
6
+ */
7
+ export declare function withOpacity(color: ColorString, opacity: number): ColorString;
8
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../../../src/utils/color.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAE5C;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CA0B5E"}
@@ -0,0 +1,3 @@
1
+ /** Small unique id generator (JS thread only — avoids a nanoid dependency). */
2
+ export declare function genId(prefix?: string): string;
3
+ //# sourceMappingURL=id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"id.d.ts","sourceRoot":"","sources":["../../../../src/utils/id.ts"],"names":[],"mappings":"AAEA,+EAA+E;AAC/E,wBAAgB,KAAK,CAAC,MAAM,SAAM,GAAG,MAAM,CAG1C"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Pure 2D affine-matrix math. Every function is marked `worklet` so it can run
3
+ * on the UI thread inside gesture handlers as well as on the JS thread.
4
+ *
5
+ * A matrix `Mat` maps a point p to p' via:
6
+ * x' = a*x + c*y + e
7
+ * y' = b*x + d*y + f
8
+ * (the canonical CSS/canvas affine convention).
9
+ */
10
+ import type { Rect, SceneTransform, Vec2 } from '../types';
11
+ export interface Mat {
12
+ a: number;
13
+ b: number;
14
+ c: number;
15
+ d: number;
16
+ e: number;
17
+ f: number;
18
+ }
19
+ export declare function identity(): Mat;
20
+ /** Returns m1 ∘ m2 (applies m2 first, then m1). */
21
+ export declare function multiply(m1: Mat, m2: Mat): Mat;
22
+ export declare function translation(tx: number, ty: number): Mat;
23
+ export declare function scaling(sx: number, sy: number): Mat;
24
+ export declare function rotation(theta: number): Mat;
25
+ export declare function invert(m: Mat): Mat;
26
+ export declare function applyToPoint(m: Mat, p: Vec2): Vec2;
27
+ /** Rotate point `p` by `theta` radians about `center`. */
28
+ export declare function rotatePoint(p: Vec2, center: Vec2, theta: number): Vec2;
29
+ export interface Layout {
30
+ width: number;
31
+ height: number;
32
+ }
33
+ export interface ImageSize {
34
+ width: number;
35
+ height: number;
36
+ }
37
+ /**
38
+ * The base fit scale used to "contain" the image inside the on-screen layout,
39
+ * before any user scene scale is applied.
40
+ */
41
+ export declare function fitScale(image: ImageSize, layout: Layout): number;
42
+ /**
43
+ * Compose the image→screen matrix:
44
+ * M = T(center) · R(rotation) · S(fit·userScale) · T(-imageCenter)
45
+ *
46
+ * The on-screen Skia `<Group>` uses `sceneTransforms2d` (below), built from the
47
+ * SAME parameters, so the preview and this matrix stay in lock-step. Gestures
48
+ * use `invert(M)` to map screen touches back into image space.
49
+ */
50
+ export declare function imageToScreenMatrix(scene: SceneTransform, image: ImageSize, layout: Layout): Mat;
51
+ /**
52
+ * Skia `Transforms2d` array for the on-screen scene `<Group>`. Applied in order,
53
+ * this reproduces `imageToScreenMatrix` exactly.
54
+ */
55
+ export declare function sceneTransforms2d(scene: SceneTransform, image: ImageSize, layout: Layout): {
56
+ translateX: number;
57
+ }[] | object[];
58
+ export declare function clampSizeToMax(width: number, height: number, maxSize?: number): {
59
+ width: number;
60
+ height: number;
61
+ };
62
+ /** Axis-aligned bounding box of a set of points. */
63
+ export declare function boundsOfPoints(points: Vec2[]): Rect;
64
+ export declare function rectCenter(r: Rect): Vec2;
65
+ export declare function distance(a: Vec2, b: Vec2): number;
66
+ /** Shortest distance from point `p` to segment `a`→`b`. */
67
+ export declare function distanceToSegment(p: Vec2, a: Vec2, b: Vec2): number;
68
+ //# sourceMappingURL=math.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"math.d.ts","sourceRoot":"","sources":["../../../../src/utils/math.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAE3D,MAAM,WAAW,GAAG;IAClB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,wBAAgB,QAAQ,IAAI,GAAG,CAG9B;AAED,mDAAmD;AACnD,wBAAgB,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,GAAG,GAAG,CAU9C;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,GAAG,CAGvD;AAED,wBAAgB,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,GAAG,CAGnD;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAK3C;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,GAAG,GAAG,CAelC;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,CAMlD;AAED,0DAA0D;AAC1D,wBAAgB,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAUtE;AAED,MAAM,WAAW,MAAM;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAMjE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,GACb,GAAG,CAUL;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,MAAM,GACb;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,MAAM,EAAE,CAYrC;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAWnC;AAED,oDAAoD;AACpD,wBAAgB,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAiBnD;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,CAGxC;AAED,wBAAgB,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAGjD;AAED,2DAA2D;AAC3D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,GAAG,MAAM,CAWnE"}
package/package.json ADDED
@@ -0,0 +1,90 @@
1
+ {
2
+ "name": "react-native-image-editor-skia",
3
+ "version": "0.1.0",
4
+ "description": "A Skia-powered image annotation & editing component for React Native (draw circles, arrows, markers, freehand, text; crop, resize, free-angle rotate; base64 in/out).",
5
+ "main": "lib/commonjs/index.js",
6
+ "module": "lib/module/index.js",
7
+ "types": "lib/typescript/src/index.d.ts",
8
+ "react-native": "src/index.ts",
9
+ "source": "src/index.ts",
10
+ "files": [
11
+ "src",
12
+ "lib",
13
+ "!**/__tests__",
14
+ "!**/__fixtures__",
15
+ "!**/__mocks__"
16
+ ],
17
+ "scripts": {
18
+ "typecheck": "tsc --noEmit",
19
+ "lint": "eslint \"**/*.{js,ts,tsx}\"",
20
+ "test": "jest",
21
+ "clean": "del-cli lib",
22
+ "prepublishOnly": "npm run typecheck",
23
+ "prepack": "bob build"
24
+ },
25
+ "keywords": [
26
+ "react-native",
27
+ "skia",
28
+ "image-editor",
29
+ "annotation",
30
+ "crop",
31
+ "draw",
32
+ "canvas",
33
+ "base64"
34
+ ],
35
+ "license": "MIT",
36
+ "author": "NG JUN KAI <jzng436@gmail.com>",
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/YOUR_GITHUB_USERNAME/react-native-image-editor-skia.git"
40
+ },
41
+ "homepage": "https://github.com/YOUR_GITHUB_USERNAME/react-native-image-editor-skia#readme",
42
+ "bugs": {
43
+ "url": "https://github.com/YOUR_GITHUB_USERNAME/react-native-image-editor-skia/issues"
44
+ },
45
+ "peerDependencies": {
46
+ "@shopify/react-native-skia": ">=2.6.0",
47
+ "react": "*",
48
+ "react-native": "*",
49
+ "react-native-gesture-handler": ">=2.0.0",
50
+ "react-native-reanimated": ">=3.6.0",
51
+ "react-native-worklets": "*"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "react-native-worklets": {
55
+ "optional": true
56
+ }
57
+ },
58
+ "devDependencies": {
59
+ "@react-native/babel-preset": "^0.81.6",
60
+ "@react-native/eslint-config": "^0.81.0",
61
+ "@shopify/react-native-skia": "^2.6.6",
62
+ "@types/jest": "^29.5.12",
63
+ "@types/react": "^19.0.0",
64
+ "del-cli": "^5.1.0",
65
+ "eslint": "^8.57.0",
66
+ "jest": "^29.7.0",
67
+ "react": "19.1.0",
68
+ "react-native": "0.81.0",
69
+ "react-native-builder-bob": "^0.30.0",
70
+ "react-native-gesture-handler": "^2.20.0",
71
+ "react-native-reanimated": "^4.0.0",
72
+ "react-native-worklets": "^0.6.0",
73
+ "typescript": "^5.5.0"
74
+ },
75
+ "react-native-builder-bob": {
76
+ "source": "src",
77
+ "output": "lib",
78
+ "targets": [
79
+ "commonjs",
80
+ "module",
81
+ "typescript"
82
+ ]
83
+ },
84
+ "jest": {
85
+ "preset": "react-native",
86
+ "modulePathIgnorePatterns": [
87
+ "<rootDir>/lib/"
88
+ ]
89
+ }
90
+ }
@@ -0,0 +1,133 @@
1
+ import {
2
+ forwardRef,
3
+ useCallback,
4
+ useEffect,
5
+ useImperativeHandle,
6
+ } from 'react';
7
+ import {
8
+ ActivityIndicator,
9
+ StyleSheet,
10
+ Text,
11
+ View,
12
+ } from 'react-native';
13
+ import { GestureHandlerRootView } from 'react-native-gesture-handler';
14
+ import type { SkImage } from '@shopify/react-native-skia';
15
+
16
+ import type { ExportOptions, ImageEditorProps, ImageEditorRef } from './types';
17
+ import { EditorProvider, useEditor } from './context/EditorContext';
18
+ import { useLoadedImage } from './image/useLoadedImage';
19
+ import { EditorCanvas } from './canvas/EditorCanvas';
20
+ import { TextInputOverlay } from './toolbar/TextInputOverlay';
21
+ import { Toolbar } from './toolbar/Toolbar';
22
+ import { exportImage } from './export/exportImage';
23
+
24
+ /**
25
+ * Interactive Skia image editor. Loads a base64/URI image, lets the user draw
26
+ * circles/arrows/markers/freehand/text, crop, free-rotate and resize, then
27
+ * exports full-resolution base64 (via ref `export()` or the built-in Done
28
+ * button + `onExport`).
29
+ */
30
+ export const ImageEditor = forwardRef<ImageEditorRef, ImageEditorProps>(
31
+ function ImageEditor(props, ref) {
32
+ const { source, onError, style } = props;
33
+ const { image, width, height, loading, error } = useLoadedImage(source);
34
+
35
+ useEffect(() => {
36
+ if (error) {
37
+ onError?.(error);
38
+ }
39
+ }, [error, onError]);
40
+
41
+ if (error) {
42
+ return (
43
+ <View style={[styles.center, style]}>
44
+ <Text style={styles.errorText}>Failed to load image</Text>
45
+ </View>
46
+ );
47
+ }
48
+
49
+ if (loading || !image) {
50
+ return (
51
+ <View style={[styles.center, style]}>
52
+ <ActivityIndicator color="#FFFFFF" />
53
+ </View>
54
+ );
55
+ }
56
+
57
+ return (
58
+ <GestureHandlerRootView style={[styles.root, style]}>
59
+ <EditorProvider
60
+ imageSize={{ width, height }}
61
+ initialStrokeColor={props.initialStrokeColor}
62
+ initialTextColor={props.initialTextColor}
63
+ palette={props.palette}
64
+ >
65
+ <EditorBody ref={ref} image={image} {...props} />
66
+ </EditorProvider>
67
+ </GestureHandlerRootView>
68
+ );
69
+ }
70
+ );
71
+
72
+ interface EditorBodyProps extends ImageEditorProps {
73
+ image: SkImage;
74
+ }
75
+
76
+ const EditorBody = forwardRef<ImageEditorRef, EditorBodyProps>(
77
+ function EditorBody({ image, onExport, exportOptions, hideToolbar }, ref) {
78
+ const editor = useEditor();
79
+
80
+ const runExport = useCallback(
81
+ (options?: ExportOptions) =>
82
+ exportImage({
83
+ image,
84
+ annotations: editor.doc.annotations,
85
+ scene: editor.doc.scene,
86
+ imageWidth: editor.imageSize.width,
87
+ imageHeight: editor.imageSize.height,
88
+ options: options ?? exportOptions,
89
+ }),
90
+ [image, editor.doc, editor.imageSize, exportOptions]
91
+ );
92
+
93
+ useImperativeHandle(
94
+ ref,
95
+ (): ImageEditorRef => ({
96
+ export: runExport,
97
+ undo: () => editor.dispatch({ type: 'UNDO' }),
98
+ redo: () => editor.dispatch({ type: 'REDO' }),
99
+ canUndo: () => editor.canUndo,
100
+ canRedo: () => editor.canRedo,
101
+ reset: () => editor.dispatch({ type: 'RESET' }),
102
+ }),
103
+ [runExport, editor]
104
+ );
105
+
106
+ const onDone = useCallback(async () => {
107
+ const base64 = await runExport();
108
+ onExport?.(base64);
109
+ }, [runExport, onExport]);
110
+
111
+ return (
112
+ <View style={styles.root}>
113
+ <View style={styles.canvasWrap}>
114
+ <EditorCanvas image={image} />
115
+ <TextInputOverlay />
116
+ </View>
117
+ {hideToolbar ? null : <Toolbar onDone={onExport ? onDone : undefined} />}
118
+ </View>
119
+ );
120
+ }
121
+ );
122
+
123
+ const styles = StyleSheet.create({
124
+ root: { flex: 1, backgroundColor: '#000000' },
125
+ canvasWrap: { flex: 1, position: 'relative' },
126
+ center: {
127
+ flex: 1,
128
+ alignItems: 'center',
129
+ justifyContent: 'center',
130
+ backgroundColor: '#000000',
131
+ },
132
+ errorText: { color: '#FF6B6B', fontSize: 16 },
133
+ });
@@ -0,0 +1,24 @@
1
+ import type { Annotation } from '../types';
2
+ import { CircleAnnotationView } from './CircleAnnotation';
3
+ import { ArrowAnnotationView } from './ArrowAnnotation';
4
+ import { MarkerAnnotationView } from './MarkerAnnotation';
5
+ import { FreehandAnnotationView } from './FreehandAnnotation';
6
+ import { TextAnnotationView } from './TextAnnotation';
7
+
8
+ /** Render any annotation by dispatching on its discriminant. */
9
+ export function AnnotationView({ a }: { a: Annotation }) {
10
+ switch (a.type) {
11
+ case 'circle':
12
+ return <CircleAnnotationView a={a} />;
13
+ case 'arrow':
14
+ return <ArrowAnnotationView a={a} />;
15
+ case 'marker':
16
+ return <MarkerAnnotationView a={a} />;
17
+ case 'freehand':
18
+ return <FreehandAnnotationView a={a} />;
19
+ case 'text':
20
+ return <TextAnnotationView a={a} />;
21
+ default:
22
+ return null;
23
+ }
24
+ }
@@ -0,0 +1,26 @@
1
+ import { useMemo } from 'react';
2
+ import { Path } from '@shopify/react-native-skia';
3
+
4
+ import type { ArrowAnnotation as ArrowAnnotationType } from '../types';
5
+ import { buildArrowPath } from './geometry';
6
+ import { annotationCenter } from './geometry';
7
+ import { RotatedGroup } from './RotatedGroup';
8
+
9
+ export function ArrowAnnotationView({ a }: { a: ArrowAnnotationType }) {
10
+ const path = useMemo(
11
+ () => buildArrowPath(a.start, a.end, a.headSize),
12
+ [a.start, a.end, a.headSize]
13
+ );
14
+ return (
15
+ <RotatedGroup center={annotationCenter(a)} rotation={a.rotation}>
16
+ <Path
17
+ path={path}
18
+ color={a.strokeColor}
19
+ style="stroke"
20
+ strokeWidth={a.strokeWidth}
21
+ strokeCap="round"
22
+ strokeJoin="round"
23
+ />
24
+ </RotatedGroup>
25
+ );
26
+ }
@@ -0,0 +1,22 @@
1
+ import { Circle } from '@shopify/react-native-skia';
2
+
3
+ import type { CircleAnnotation as CircleAnnotationType } from '../types';
4
+ import { RotatedGroup } from './RotatedGroup';
5
+
6
+ export function CircleAnnotationView({ a }: { a: CircleAnnotationType }) {
7
+ return (
8
+ <RotatedGroup center={a.center} rotation={a.rotation}>
9
+ {a.fill ? (
10
+ <Circle cx={a.center.x} cy={a.center.y} r={a.radius} color={a.fill} />
11
+ ) : null}
12
+ <Circle
13
+ cx={a.center.x}
14
+ cy={a.center.y}
15
+ r={a.radius}
16
+ color={a.strokeColor}
17
+ style="stroke"
18
+ strokeWidth={a.strokeWidth}
19
+ />
20
+ </RotatedGroup>
21
+ );
22
+ }
@@ -0,0 +1,22 @@
1
+ import { useMemo } from 'react';
2
+ import { Path } from '@shopify/react-native-skia';
3
+
4
+ import type { FreehandAnnotation as FreehandAnnotationType } from '../types';
5
+ import { annotationCenter, buildFreehandPath } from './geometry';
6
+ import { RotatedGroup } from './RotatedGroup';
7
+
8
+ export function FreehandAnnotationView({ a }: { a: FreehandAnnotationType }) {
9
+ const path = useMemo(() => buildFreehandPath(a.points), [a.points]);
10
+ return (
11
+ <RotatedGroup center={annotationCenter(a)} rotation={a.rotation}>
12
+ <Path
13
+ path={path}
14
+ color={a.strokeColor}
15
+ style="stroke"
16
+ strokeWidth={a.strokeWidth}
17
+ strokeCap="round"
18
+ strokeJoin="round"
19
+ />
20
+ </RotatedGroup>
21
+ );
22
+ }
@@ -0,0 +1,20 @@
1
+ import { Rect } from '@shopify/react-native-skia';
2
+
3
+ import type { MarkerAnnotation as MarkerAnnotationType } from '../types';
4
+ import { annotationCenter } from './geometry';
5
+ import { RotatedGroup } from './RotatedGroup';
6
+ import { withOpacity } from '../utils/color';
7
+
8
+ export function MarkerAnnotationView({ a }: { a: MarkerAnnotationType }) {
9
+ return (
10
+ <RotatedGroup center={annotationCenter(a)} rotation={a.rotation}>
11
+ <Rect
12
+ x={a.rect.x}
13
+ y={a.rect.y}
14
+ width={a.rect.width}
15
+ height={a.rect.height}
16
+ color={withOpacity(a.color, a.opacity)}
17
+ />
18
+ </RotatedGroup>
19
+ );
20
+ }
@@ -0,0 +1,28 @@
1
+ import React from 'react';
2
+ import { Group } from '@shopify/react-native-skia';
3
+
4
+ import type { Vec2 } from '../types';
5
+
6
+ /**
7
+ * Wraps children in a Skia Group rotated by `rotation` radians about `center`.
8
+ * Every annotation renderer uses this so its stored `rotation` is applied
9
+ * consistently (and identically in the off-screen export path).
10
+ */
11
+ export function RotatedGroup({
12
+ center,
13
+ rotation,
14
+ children,
15
+ }: {
16
+ center: Vec2;
17
+ rotation: number;
18
+ children: React.ReactNode;
19
+ }) {
20
+ if (!rotation) {
21
+ return <>{children}</>;
22
+ }
23
+ return (
24
+ <Group origin={center} transform={[{ rotate: rotation }]}>
25
+ {children}
26
+ </Group>
27
+ );
28
+ }