npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-image-marker

v1.12.0

Published

Add visible and invisible image watermarks in React Native and React Native Web

Readme

npm version

See what you can make

Compose text and image layers, keep their render order under control, and export the finished image on iOS, Android, or Web.

Install

npm install react-native-image-marker

Install iOS pods and rebuild the native app:

npx pod-install

Expo projects must use a development build because this package's native code is not bundled with Expo Go. See the Expo installation steps.

Quick start

import Marker, { ImageFormat, Position } from 'react-native-image-marker';

const result = await Marker.markText({
  backgroundImage: {
    src: require('./images/background.jpg'),
  },
  watermarkTexts: [
    {
      text: '© Acme Studio',
      alpha: 0.85,
      position: {
        position: Position.bottomRight,
        X: 24,
        Y: 24,
      },
      style: {
        color: '#FFFFFF',
        fontSize: 28,
      },
    },
  ],
  filename: 'marked-photo',
  saveFormat: ImageFormat.jpg,
  quality: 92,
});

On iOS and Android, JPEG and PNG calls resolve with a native cache-file path. On Web, every format resolves with a browser-ready data URL; native ImageFormat.base64 also returns a PNG data URL.

Repeat a readable mark across the image

The same layout and strokeStyle options work on iOS, Android, and Web. Tiled layers can use pixel or percentage gaps and stagger every second row.

const result = await Marker.markText({
  backgroundImage: { src: require('./images/background.jpg') },
  watermarkTexts: [
    {
      text: 'CONFIDENTIAL',
      alpha: 0.55,
      layout: {
        type: 'tile',
        gapX: '8%',
        gapY: '7%',
        offsetX: '-2%',
        stagger: true,
      },
      style: {
        color: '#FFFFFF88',
        fontSizeRatio: 0.024,
        rotate: -24,
        strokeStyle: { color: '#0F172A88', width: 2 },
      },
    },
  ],
  saveFormat: ImageFormat.jpg,
  quality: 92,
});

Use the same layout object on watermarkImages to repeat a logo. A tiled layer cannot also set position; invalid combinations fail with a clear error.

Blend a watermark into the photo

Set blendMode on a text or image layer when a plain overlay looks too flat. The same six modes—normal, multiply, screen, overlay, darken, and lighten—work on iOS, Android, and Web.

const result = await Marker.mark({
  backgroundImage: { src: require('./images/background.jpg') },
  watermarks: [
    {
      type: 'image',
      src: require('./images/logo.png'),
      blendMode: 'multiply',
      position: { position: Position.center },
      scale: 0.7,
      alpha: 0.85,
    },
    {
      type: 'text',
      text: 'SCREEN LIGHT',
      blendMode: 'screen',
      position: { position: Position.bottomCenter, Y: 32 },
      style: { color: '#FFE9B8', fontSize: 36, bold: true },
    },
  ],
  saveFormat: ImageFormat.png,
});

Personalize one recipe across a batch

Save ordered layers once, then inject safe string, number, or boolean variables for each image. Templates also expose {{index}} and {{filename}}; visibleWhen can include or skip a layer without rebuilding the recipe. Results stay in input order, and one failed image does not stop the others.

const recipe = Marker.createRecipe({
  schemaVersion: 1,
  watermarks: [
    {
      type: 'text',
      text: '© {{studio}} · {{filename}} · #{{index}}',
      position: { position: Position.bottomRight, X: 24, Y: 24 },
      style: { color: '#FFFFFF', fontSize: 28 },
    },
    {
      type: 'image',
      src: require('./images/logo.png'),
      visibleWhen: { variable: 'showLogo', equals: true },
      position: { position: Position.topRight, X: 24, Y: 24 },
      scale: 0.25,
    },
  ],
  saveFormat: ImageFormat.jpg,
  quality: 90,
});

const controller = new AbortController();
const results = await recipe.applyMany(
  photos.map((src, index) => ({
    backgroundImage: { src },
    filename: `photo-${index + 1}`,
    variables: { studio: 'Acme Studio', showLogo: index % 2 === 0 },
  })),
  {
    concurrency: 2,
    signal: controller.signal,
    onProgress: ({ settled, total }) => console.log(`${settled}/${total}`),
  }
);

const savedRecipe = JSON.stringify(recipe.toJSON());

On Web, request Blob results explicitly when processing many files without Data URL expansion:

const blobRecipe = Marker.createRecipe(recipeOptions, {
  resultType: 'blob',
});
const blob = await blobRecipe.apply({ backgroundImage: { src: file } });

Blob mode is Web-only and does not create object URLs. The caller controls URL.createObjectURL() and URL.revokeObjectURL() when a preview is needed.

Embed an invisible trace ID

embedInvisible writes a short authenticated locator into the final pixels without adding a visible layer. detectInvisible recovers it later with the same key. The *Many methods create and verify recipient-specific copies while preserving input order. Use random asset or distribution IDs—not names, email addresses, or other personal data.

const key = await loadWatermarkKeyFromTrustedStorage();
const marked = await Marker.embedInvisible({
  image: { src: require('./images/background.jpg') },
  payload: 'asset-42', // 1–12 UTF-8 bytes
  key, // at least 16 UTF-8 bytes
  strength: 'robust',
  saveFormat: ImageFormat.png,
});

const detection = await Marker.detectInvisible({
  image: { src: { uri: marked } },
  key,
  strength: 'robust',
  search: 'robust',
});

if (detection.detected) {
  console.log(detection.payload, detection.confidence);
}

For distribution batches, call Marker.embedInvisibleMany(inputs, { concurrency, signal, onProgress }) and Marker.detectInvisibleMany(inputs). Robust detection covers limited crops and tested 0.9×–1.1× resize hypotheses; a successful resized detection reports scale.

On Web, copy lib/worker/invisible-watermark.js to a trusted same-origin static directory and pass worker: { scriptUrl, signal, onProgress } to move detection off the main thread and make an active detection cancellable.

For trusted services, import createInvisibleWatermarkRuntime only from react-native-image-marker/trace-runtime.js and inject your own RGBA image codec. This explicit subpath is not loaded by the normal React Native entry. The independent Node.js Trace Service example uses Node.js 22 and sharp without adding sharp to this package's production dependencies.

This Beta feature is designed for distribution tracing, not DRM or proof that an image was never edited. JPEG recompression, mild pixel adjustments, and light resizing are covered by the browser test matrix; aggressive crops, resizing outside the tested range, blur, redrawing, and deliberate removal can destroy the mark. Browser code cannot keep a long-lived secret, so production Web workflows should embed on a trusted server or use tightly scoped keys.

For signed provenance, Marker.embedInvisibleWithCredentials() composes the locator with an application-supplied Content Credentials adapter. The optional Node.js C2PA service example keeps the official C2PA runtime and signing material outside the core package. See the invisible watermark guide for the server runtime, Worker, security, and C2PA boundaries.

Live Web SDK

Try the live playground to compose visible layers or embed and verify an invisible trace ID entirely in your browser. Images stay local.

The Web renderer accepts URL strings, { uri }, data URLs, Blob, File, and loaded browser images. In Expo Web, resolve a numeric bundled asset first:

import { Asset } from 'expo-asset';

const background = Asset.fromModule(require('./images/background.jpg'));
const webSource = { uri: background.uri };

Browser Canvas and native graphics stacks share the API and positioning model but are not guaranteed to be pixel-identical. Fonts, decoding, antialiasing, and JPEG encoding can differ.

Choose an API

| Method | Use it for | | --------------------------------------------------- | ------------------------------------------------ | | Marker.markText | One or many text layers | | Marker.markImage | One or many logo, icon, or image layers | | Marker.mark | Ordered text and image layers in one render pass | | Marker.createRecipe | Reusable, personalized batch workflows | | Marker.embedInvisible | Write an authenticated short trace ID | | Marker.detectInvisible | Recover and verify an invisible trace ID | | Marker.embedInvisibleMany / detectInvisibleMany | Process ordered trace batches | | Marker.embedInvisibleWithCredentials | Add a signed provenance adapter workflow |

Layers render in array order, so later layers draw over earlier layers. markText and markImage remain supported; use mark when mixed layer order matters.

Compatibility

  • iOS 13 or newer
  • Android API 24 or newer
  • Modern browsers with Canvas 2D / React Native Web
  • React Native New Architecture support since library v1.3.0
  • Legacy bridge fallback
  • Expo development builds through native autolinking
  • CI coverage for RN 0.73 legacy/new architecture, RN 0.86 New Architecture, and Expo SDK 57 / RN 0.86

See the full compatibility guide when maintaining an older React Native app.

Documentation

Examples

Contributing

See CONTRIBUTING.md for the development and release workflow.

License

MIT