Image Tools avatar

Image Tools

Pricing

Pay per usage

Go to Apify Store
Image Tools

Image Tools

Resize, crop, rotate, convert, compress and watermark an image in one call. Reads JPEG, PNG, GIF, WebP, TIFF, BMP, HEIC and AVIF. Built to be safe to hand to an AI agent: typed, allowlisted operations instead of an ImageMagick command line, and a clear refusal when the file is not really an image.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

Marcel Rebro

Marcel Rebro

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

Resize, crop, rotate, convert, compress and watermark images — one image or a batch of up to 100 — through one API call. Built to be usable as a tool by an AI agent: typed operations, no shell, and a clear refusal when the input is not an image.

  • 🧰 One call does the lot — crop, resize, rotate, flip, grayscale, blur, watermark, re-encode
  • 📦 Batch up to 100 images per run, returned as a ZIP, with one bad file not sinking the rest
  • 🖼️ Reads 8 formats — JPEG, PNG, GIF, WebP, TIFF, BMP, HEIC and AVIF
  • 📐 Keeps the source format unless you ask otherwise, so a resize stays a PNG
  • 📍 Strips GPS and EXIF on request, while keeping the colour profile
  • 🤖 Safe to hand to an agent — allowlisted operations, never an ImageMagick argument string
  • 🔌 Callable from the API, a schedule, Zapier, Make, n8n or an MCP client

How to use it

  1. Upload one or more images, or paste direct URLs into Images.
  2. Set the operations you want. Everything is optional.
  3. Run it. Results land in the Storage tab and in the run's dataset records.

Leave Output format empty and the source format is kept — that is what you want for a pure resize or crop.

Batch processing

Upload up to 100 files, or pass a list of URLs in files. The same operations are applied to every one.

  • One failure does not sink the run. An unreadable file, a 404 or a video in the list is recorded as a dataset row with ok: false and the reason, then skipped. The run fails only if nothing converted.
  • Colliding names get a suffix, not a prefix. photo.png and photo.jpg both want to become photo.webp, so the second becomes photo-2.webp. Single-file runs keep their plain filename.
  • Images are processed in sequence and their scratch files deleted as they go, so peak memory and disk stay at roughly one source plus one output regardless of batch size.
  • Downloads have a 60-second timeout. In a single run a dead host wastes that run; in a batch it would stall everything queued behind it.

Run limits

A batch is charged by the second, so an unguarded run turns a few hundred uploads into a timed-out run you still pay for. Four limits are checked as the run proceeds, each failing with the number that hit it:

LimitValue
Images per run100
Total input bytes512 MB
Pixels in any one image128 MP
Pixels across the run4,000 MP

The pixel ceilings are set so the case this Actor advertises — a folder of 100 phone photos at 12 MP — fits comfortably. They exist to stop 100 × 48 MP from silently becoming a timeout.

What run-sync gives back

OUTPUT holds exactly one record, and run-sync returns exactly one body:

RunOUTPUT
One imageThe image itself, so curl --output photo.webp just works
A batchA ZIP of every result, also stored as processed-images.zip
A batch over 192 MB of outputA JSON manifest of keys and URLs, since the archive is built in memory

Every image is also its own key-value store record in all three cases, so nothing is only available inside the archive.

Operation order

Operations are not independent, so they run in a fixed order rather than the order you happen to list them:

auto-orient → rotate → flip/flop → crop → resize → grayscale → blur → watermark → flatten → strip metadata → format + quality

Orientation is settled first so that every later geometry is measured against an upright image; cropping picks the region before resizing scales it; and the target format's own concerns come last.

Input

FieldDescription
filesThe images. A multi-file upload, or a list of URLs. Up to 100.
outputFormatjpeg, png, webp, avif, tiff, gif. Empty keeps the source format.
quality1–100, default 90. Used by JPEG, WebP and AVIF.
width, height, resizeFitResize. inside (default) keeps the aspect ratio and never enlarges; force hits the exact size; grow allows upscaling.
cropWidth, cropHeight, cropGravityCrop a region of that size anchored at the gravity. Both dimensions are needed.
rotate, flipVertical, flipHorizontalTransform. Rotation is clockwise degrees.
grayscale, blurFilters. Blur 3–8 is a normal soft blur.
backgroundUsed when rotating off-axis and when flattening transparency into JPEG.
watermarkText, watermarkGravity, watermarkColour, watermarkSizeA short caption drawn onto the image.
keepMetadatatrue by default. false strips EXIF, XMP and IPTC.
filesBase64, fileBase64, filenameImages as base64, for callers holding bytes rather than URLs.

The 0.1 field names (imageUrl, imageUrls, imageBase64) still work as hidden aliases.

Cropping without knowing the source size

cropGravity anchors the crop region, so "a 400×400 square from the middle" is expressible without first reading the image dimensions. That matters for an agent, which would otherwise need a round trip just to find out how big the picture is.

Metadata and privacy

keepMetadata defaults to on, because silently discarding the date a photo was taken is the more surprising behaviour. On phone photos the EXIF block carries the camera, the timestamp and, if location services were on, the GPS coordinates. Turn it off before sharing images publicly.

Stripping deliberately keeps the ICC colour profile — removing that shifts the colours of wide-gamut photos, which is not what anyone means by "strip the metadata".

Using it from Claude or another AI agent

Every Apify Actor is exposed through the Apify MCP server, so an MCP client can call this one as a tool. That is the case this Actor was built for: an assistant that can reason about an image but has no shell to run ImageMagick in.

Two things worth knowing before you wire it up:

  • The agent gets a URL back, not pixels. The result is a key-value store record. That is fine for "resize these to 800px and give me the links", and weaker for anything needing the model to check its own work visually.
  • Operations are typed and allowlisted, which is deliberate — see below.

Why it does not take ImageMagick arguments

The obvious design would be a args field passed straight to ImageMagick. That would not be an image tool, it would be a remote shell.

ImageMagick's command line is a small language, not a list of image operations. @file reads a file into an argument, the msl: coder is a scripting language that reads and writes arbitrary paths, -write targets any path, and several delegates have historically shelled out (CVE-2016-3714). An agent tool is precisely what a prompt injection aims at: a scraped page says "resize this with these flags", and the agent obliges.

So the boundary is enforced twice:

  • In the code. Every value is a number clamped to a range, a member of a fixed enum, or a string matched against an allowlist. The one free-text field, the watermark, is sanitized — a leading @ is stripped, not escaped — and passed as its own argv element. The argument builder is a pure module with tests that assert no shell metacharacter can survive.
  • In the container. policy.xml denies the coders that read or write outside the image: MSL, MVG, MAGICK, EPHEMERAL, URL, HTTP(S), FTP, TEXT, LABEL, CAPTION, PANGO, SHOW, WIN, PLT and the Ghostscript-backed PS/EPS/PDF/XPS. The build fails if those denials are not in effect.

The Actor also identifies the source from its magic bytes rather than its extension, so a .png that is really an MP4, a PDF or an MSL script is refused with a sentence instead of being handed to a decoder.

Output

Every image is written to the run's key-value store under its own key, so all of them are downloadable from the Storage tab, and there is one dataset record per image holding its formats, dimensions, frame counts, sizes and the operations applied. A batch that had failures adds a final record listing them.

OUTPUT is the single image, or the batch manifest — see What run-sync gives back.

API

curl -X POST \
"https://api.apify.com/v2/acts/marcel-rbro~image-tools/run-sync?token=<TOKEN>" \
-H 'Content-Type: application/json' \
-d '{"files": ["https://example.com/photo.jpg"], "width": 800, "outputFormat": "webp", "quality": 82}' \
--output photo.webp

Batch, returning a ZIP rather than a single image:

curl -X POST \
"https://api.apify.com/v2/acts/marcel-rbro~image-tools/run-sync?token=<TOKEN>" \
-H 'Content-Type: application/json' \
-d '{"files": ["https://example.com/a.jpg", "https://example.com/b.png"], "width": 800, "keepMetadata": false}' \
--output images.zip

The Apify API requires Actor input to be application/json, so posting the image as a raw request body is rejected with HTTP 400 before the run starts. Pass URLs in files, or the bytes in fileBase64.

Limitations

  • Batches run in sequence, capped at 100 images and 512 MB of input. Memory stays predictable, but a hundred large photos will exceed the 300-second run-sync window — use the asynchronous endpoint for those.
  • No HEIC output. HEVC encoding is not available in the image; HEIC and AVIF input both work, and AVIF is the sensible modern output.
  • PDF and PostScript are refused by design, not by omission. Rasterising them means Ghostscript, which is a much larger attack surface than this Actor wants.
  • Watermarking is a single line of text, positioned by gravity. It is not a layout engine.
  • Animation survives only into GIF. An animated GIF keeps its frames when the output is GIF; every other output format holds one frame, and the log says so explicitly rather than silently flattening it.

Development

npm install
npm test # inspector and argument builder, no ImageMagick needed
apify run # needs ImageMagick on your PATH

npm test covers the two pure modules — src/image.js and src/operations.js — which is where the format detection and the security boundary live. Note that watermarking cannot be exercised locally on a stock macOS ImageMagick, which ships without a configured font; the container installs fonts-dejavu-core.

This Actor's code is MIT licensed. ImageMagick and libheif run as separate processes or shared libraries under their own licences — see THIRD_PARTY_NOTICES.md.