Text, image and tiled watermarks whose padding is measured to the ink, whose rotation keeps the mark on the picture, and whose opacity is source-over: the number is the share of the way from the picture to the mark's own colour.
Initializing in your browser…
Turn an image into a data URL whose payload is decoded again and compared byte for byte with the file, with the media type taken from the file's own signature rather than its extension and the size cost of embedding stated in full.
Compare two images with a perceptual engine (pixelmatch) that ignores anti-aliasing, or an exact byte engine. Groups changes into regions with bounding boxes, reports SSIM, and handles size mismatches with an explicit policy.
Caption a picture with text that is measured, wrapped and kept inside the frame. The preview canvas is the exported file, pixel for pixel.
A flat 600 by 400 ground with a solid mark, so every question about a watermark has an arithmetic answer.
Input
rgb(255, 220, 0) over rgb(64, 96, 160) · 20 px padding · -45 degrees at the bottom right
Measured on the pixels
before after composite at 50% off by 20 off by 1 (source-over says rgb(160, 158, 80)) padding asked 20 px 17 / 28 20 / 20 (left / top) ink after -45 rotation 5,895 8,527 of 9,930 upright tiled row step, 40 px 40 px 44 Arial, 48 Impact
The compositing was already exact with the shadow off, and that is recorded rather than claimed as a fix. What was wrong was around it: the shadow was drawn under the mark at the mark’s own alpha, the padding was measured from the point size and the advance width rather than from the ink, a rotated mark was placed by its UNROTATED box and walked off the picture, and a tiled row stepped by the point size, which is not any font’s line height.
A watermark is a compositing operation, and this page treats it as one you can check. Source-over is defined as out = src * a + dst * (1 - a), so the opacity slider has an arithmetic meaning, and the page prints it: at 40 percent the mark moves a pixel 40 percent of the way to its own colour. The padding is measured to the mark's INK, a rotated mark is placed by its rotated box so it stays on the picture, and a tiled mark steps by the font's own line height rather than by the point size.
A text mark at 30 to 40 percent in a corner, with 20 px of real padding and a shadow so it reads on a busy picture.
An image watermark scaled to taste, placed at a corner with the padding you asked for and no shadow forced on it.
Tiled mode at a diagonal covers the whole picture, corners included, and steps by the font's real line height so the rows do not creep.
The compositing was already right, and that is worth saying before anything else: measured against `out = src * a + dst * (1 - a)` on a flat ground with a solid mark, the previous version was exact to within the single level that 8-bit premultiplied storage costs. What was wrong sat around it, and all of it was measurable.
**The padding was not the padding.** Asked for 20 pixels, the mark landed 17 from the left, 28 from the top, 14 from the right and 13 from the bottom, because the vertical placement used the point size as a text height and the horizontal used `measureText().width`, which is the ADVANCE and includes the side bearings rather than stopping where the glyphs stop. Placement now uses `actualBoundingBoxLeft`, `Right`, `Ascent` and `Descent`, which are the ink, so 20 px really is 20 px on every side and in every font. The page shows both numbers for the string you typed, so the difference is visible rather than assumed.
**A rotated mark walked off the picture.** The Diagonal quick style at the bottom right lost 41 percent of its ink, 9,930 pixels down to 5,895, because the mark was rotated about a point chosen for the unrotated box. The ROTATED bounding box is what gets placed now, so a diagonal mark in a corner keeps its padding and stays whole, and if a mark is too big to fit at all the page says how far it had to move it.
**A tiled mark stepped by the point size.** A font's ascent and descent are properties of the font, not of the size: at 40 px the line height is 44 in Arial, 45 in Courier New, 46 in Georgia and 48 in Impact. The rows now step by the real line height plus the gap, and the lattice is generated over the picture as seen from the ROTATED frame, so a diagonal tiling reaches the corners instead of leaving them bare.
**The shadow spoiled the opacity.** It was drawn under the mark at the mark's own alpha, so a half-transparent mark composited over a ground its own shadow had already darkened: 15 to 20 levels away from the number on the slider. The mark and its shadow are now drawn into one layer and the layer is composited once, which is what a CSS `opacity` on an element with a `text-shadow` does, and which brings the arithmetic back to exact. The shadow toggle is also available in image mode, where it used to apply without being offered.
Beyond the fixes: the preview is the export drawn by the same function at a smaller scale, so nothing on screen can disagree with the file; there is a text outline for a light mark on a light picture; the output can be PNG, JPEG or WebP with a quality slider that appears only where quality does something; and the mark's box, its four real gaps and the composite arithmetic are all printed above the preview.
It is the alpha in source-over: out = src * a + dst * (1 - a). At 40 percent a pixel moves 40 percent of the way from the picture's colour to the mark's. The page works that out for the colour you picked and prints it.
Because the mark was rotated about a point chosen for its unrotated box, so half of it left the picture. Here the rotated box is what gets placed, and the padding is kept on the rotated box's own edges.
Yes, and it is measured to the ink rather than to the advance width, which is what makes it true across fonts. The panel shows both the advance and the ink for the string you typed.
Because a font's line height is not its point size. At 40 px Arial is 44 px of line and Impact is 48, so a tiled mark that stepped by 40 would creep by 4 or 8 px a row.
Not any more. The mark and its shadow go into one layer that is composited once at the opacity, so the shadow darkens the picture around the mark instead of showing through it.
Not here; this tool works on one image with a live preview. The Image Batch Processor handles many files at once.
Images are decoded, edited, and exported entirely inside this browser tab. No originals, exports, or metadata are uploaded.