Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,13 @@ Browser Canvas and native graphics stacks share the API and positioning model bu
## Optional interaction editor

The independently versioned
`react-native-image-marker-editor@0.0.2` package provides Recipe v2 layer
`react-native-image-marker-editor@0.0.3` package provides Recipe v2 layer
selection, drag/scale/rotate/reorder, visibility and locking, snapping,
undo/redo, accessible keyboard controls, preview rendering, and final export.
It requires Core `^2.0.0` and delegates image processing back to Core.

```sh
npm install react-native-image-marker-editor@0.0.2
npm install react-native-image-marker-editor@0.0.3
```

Import `react-native-image-marker-editor/core-adapter` only when the editor
Expand Down
2 changes: 1 addition & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ function App() {
surface === value && styles.surfaceTabTextSelected,
]}
>
{value === 'core' ? 'Core lab' : 'Editor 0.0.2'}
{value === 'core' ? 'Core lab' : 'Editor 0.0.3'}
</Text>
</Pressable>
))}
Expand Down
39 changes: 23 additions & 16 deletions example/src/EditorExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { ImageFormat } from 'react-native-image-marker';
const background = require('./bg.png');
const logo = require('./icon.jpeg');
const adapter = createCoreEditorAdapter(960);
const backgroundSize = { width: 1920, height: 1080 };

function actionStyle(pressed: boolean) {
return [styles.action, pressed && styles.actionPressed];
Expand All @@ -29,7 +30,9 @@ function actionStyle(pressed: boolean) {
export default function EditorExample() {
const { width } = useWindowDimensions();
const canvasWidth = Math.min(Math.max(width - 32, 280), 720);
const canvasHeight = Math.round(canvasWidth * 0.62);
const canvasHeight = Math.round(
canvasWidth * (backgroundSize.height / backgroundSize.width)
);
const controller = React.useMemo(
() =>
new ImageMarkerEditorController({
Expand All @@ -40,16 +43,16 @@ export default function EditorExample() {
name: 'Campaign title',
type: 'text',
text: 'IMAGE MARKER 2.0',
position: { X: 32, Y: 40 },
position: { X: 86, Y: 104 },
style: {
color: '#FFFFFF',
fontSize: 24,
fontSize: 64,
bold: true,
shadowStyle: {
color: '#0F172A',
dx: 1,
dy: 2,
radius: 3,
dx: 3,
dy: 5,
radius: 8,
},
},
},
Expand All @@ -58,8 +61,8 @@ export default function EditorExample() {
name: 'Brand mark',
type: 'image',
src: logo,
position: { X: 190, Y: 130 },
scale: 0.65,
position: { X: 1120, Y: 620 },
scale: 0.32,
},
],
output: {
Expand Down Expand Up @@ -87,6 +90,7 @@ export default function EditorExample() {
const request = {
recipe: controller.exportRecipe(),
input: { backgroundImage: { src: background } },
sourceSize: backgroundSize,
control: {
timeoutMs: 20_000,
onProgress: ({
Expand Down Expand Up @@ -120,17 +124,17 @@ export default function EditorExample() {
controller.addLayer({
type: 'text',
text: 'New text',
position: { X: 80, Y: 90 },
style: { color: '#F8FAFC', fontSize: 20, bold: true },
position: { X: 214, Y: 240 },
style: { color: '#F8FAFC', fontSize: 54, bold: true },
});
}, [controller]);

const addLogo = React.useCallback(() => {
controller.addLayer({
type: 'image',
src: logo,
position: { X: 120, Y: 120 },
scale: 0.5,
position: { X: 640, Y: 320 },
scale: 0.28,
});
}, [controller]);

Expand All @@ -141,7 +145,7 @@ export default function EditorExample() {
>
<View style={styles.heading}>
<View>
<Text style={styles.eyebrow}>OPTIONAL PACKAGE · 0.0.2</Text>
<Text style={styles.eyebrow}>OPTIONAL PACKAGE · 0.0.3</Text>
<Text style={styles.title}>Interactive Recipe v2 editor</Text>
</View>
<Text style={styles.subtitle}>
Expand Down Expand Up @@ -188,7 +192,7 @@ export default function EditorExample() {
<ImageMarkerEditor
background={
<Image
resizeMode="cover"
resizeMode="contain"
source={background}
style={StyleSheet.absoluteFill}
/>
Expand All @@ -197,6 +201,7 @@ export default function EditorExample() {
height={canvasHeight}
onStateChange={setState}
snapThreshold={8}
sourceSize={backgroundSize}
style={styles.canvas}
width={canvasWidth}
/>
Expand All @@ -216,7 +221,10 @@ export default function EditorExample() {
accessibilityLabel="Exported editor result"
resizeMode="contain"
source={{ uri: resultUri }}
style={[styles.result, { width: canvasWidth }]}
style={[
styles.result,
{ width: canvasWidth, height: canvasHeight },
]}
/>
</View>
)}
Expand Down Expand Up @@ -314,7 +322,6 @@ const styles = StyleSheet.create({
result: {
backgroundColor: '#E2E8F0',
borderRadius: 10,
height: 280,
},
recipeCard: {
backgroundColor: '#0F172A',
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions packages/editor/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 0.0.3 (2026-07-29)

- Added an explicit `sourceSize` coordinate space so the interactive surface,
bounded Core preview, and original export preserve the same normalized layer
geometry.
- Added reusable viewport and Recipe projection helpers, including scaling for
text metrics, image layers, shadows, strokes, backgrounds, tiles, and
numeric positioning values.
- Fixed the default Editor surface and Web/React Native examples so font and
image scale are applied exactly once and source images are not cropped.
- Added projection and Core-adapter regression coverage for preview parity.

## 0.0.2 (2026-07-28)

- Added canonical package metadata and direct links to the integration guide,
Expand Down
19 changes: 16 additions & 3 deletions packages/editor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,35 @@ const controller = new ImageMarkerEditorController({
output: { saveFormat: ImageFormat.png },
});
const adapter = createCoreEditorAdapter(1024);

<ImageMarkerEditor controller={controller} width={360} height={240} />;
const sourceSize = { width: 1920, height: 1080 };

<ImageMarkerEditor
controller={controller}
sourceSize={sourceSize}
width={360}
height={240}
/>;
<ImageMarkerEditorToolbar controller={controller} />;

const recipe = controller.exportRecipe();
const result = await adapter.exportOriginal({
recipe,
input: { backgroundImage: { src: imageSource } },
sourceSize,
});
```

Use original-image pixels for numeric Recipe positions and sizes, then pass
the same `sourceSize` to the surface and render adapter. The Editor projects
that source coordinate space into its viewport, and the Core adapter projects
it into bounded previews. Omit `sourceSize` only when the viewport itself is
the intended Recipe coordinate space.

The main entry owns only UI and Recipe state. Import the opt-in `core-adapter`
entry to render previews or final output through Core, or inject your own
adapter for a server renderer.

Run the repository's React Native example and choose **Editor 0.0.2**, or use
Run the repository's React Native example and choose **Editor 0.0.3**, or use
the [browser playground](https://image-marker.corerobin.com/playground/?workflow=editor#editor-playground)
to exercise drag, scale, rotation, ordering, locks, undo/redo, and a real Core
render.
Expand Down
2 changes: 1 addition & 1 deletion packages/editor/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-image-marker-editor",
"version": "0.0.2",
"version": "0.0.3",
"description": "Optional interactive Recipe v2 editor for react-native-image-marker",
"license": "MIT",
"homepage": "https://image-marker.corerobin.com/guides/editor/",
Expand Down
Loading
Loading