Build a repeating tiled watermark layout at any angle
A 300×200 image with 100×50 tiles, gaps of 20 and 10 and a 30° tilt needs 15 tile positions, partial edge tiles included. Enter canvas, tile, spacing and angle to get the center of every rotated copy, in a fixed order. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Canvas width
- 300
- Canvas height
- 200
- Tile width
- 100
- Tile height
- 50
- Spacing x
- 20
- Spacing y
- 10
- Quantity
- 15
Build a complete repeating watermark grid without guessing how many copies are needed beyond the visible edges. Provide the image canvas dimensions, watermark tile dimensions, horizontal and vertical spacing, and a rotation angle. The calculator returns stable center coordinates for every rotated tile that touches the canvas, including partial tiles at its boundaries. The layout is centered on the canvas, uses consistent row and column identifiers, and can be passed directly to an image, SVG, PDF, or canvas rendering pipeline.
Define the canvas and watermark tile
Start with the width and height of the destination canvas, then provide the unrotated width and height of one watermark tile. All dimensions use the same unit, which will usually be pixels but can also be points or another coordinate unit when a renderer uses a different system. The tile must fit within the canvas in both dimensions.
That validation catches accidental swaps, incorrect unit conversions, and requests that would produce a single oversized stamp instead of a repeating pattern. Next, set horizontal and vertical spacing. These values are gaps between tile edges along the grid's own axes, not absolute distances on the screen after rotation.
Zero creates edge-to-edge tiles, while larger values create a lighter pattern with more empty space. The returned origin is the canvas center. Row zero and column zero identify the tile centered at that origin, giving downstream code a stable anchor even when the canvas dimensions, spacing, or angle later change.
Understand rotation and edge coverage
The angle rotates the entire lattice clockwise around the center of the canvas. Any finite degree value is accepted and normalized into one revolution, so minus thirty degrees and three hundred thirty degrees produce the same layout. Rotation expands the region that the grid must cover: a tile whose center lies outside the image can still have a corner visible inside it.
The calculator therefore projects the canvas onto the rotated grid axes, creates a bounded set of candidate rows and columns, and tests each rotated tile rectangle against the canvas rectangle. A position is returned whenever those rectangles touch or overlap. This exact intersection rule avoids missing corner tiles and avoids emitting distant tiles that a simple oversized bounding box would include.
Coordinates describe tile centers because center-based rotation is supported consistently by raster libraries, SVG transforms, CSS transforms, and canvas APIs. Partial boundary tiles are intentional; let the destination renderer clip them to the canvas.
Render and reproduce the layout
Process the positions in their returned order, from lower row identifiers to higher ones and then from lower columns to higher columns. The row and column identifiers are useful for debugging, alternating opacity, or selecting every other tile, but they do not replace the coordinates. Numeric coordinates are rounded to six decimal places so serialized results remain stable across calls while retaining subpixel precision for high-resolution output.
If you need a staggered brick pattern, random jitter, individually rotated marks, or text measurement, apply those transformations after receiving this regular grid or use a specialized compositor.
What you can do with it
Protect preview images
Place a logo repeatedly across product, proof, or portfolio previews so a single crop cannot remove every mark.
Generate renderer instructions
Create stable tile centers for Canvas, SVG, PDF, or server-side image pipelines without embedding layout math in each renderer.
Keep exports consistent
Reuse the same angle and spacing rules across several canvas sizes while retaining a centered, deterministic pattern.
FAQ
Are x and y coordinates tile corners or centers?
They are tile centers. Rotate and draw each watermark around that point using the returned angle.
Why are some centers outside the canvas?
A rotated tile can intersect the canvas even when its center is outside. Those positions ensure complete edge and corner coverage and should be clipped while rendering.
Which direction does the angle use?
Positive angles rotate clockwise in the usual image coordinate system where x increases rightward and y increases downward.
Can spacing be zero?
Yes. Zero makes neighboring tile edges meet along that grid axis. Spacing cannot be negative.
Can the watermark tile be larger than the image?
No. The tile has to fit inside the canvas in both directions. That rule catches swapped width and height, a wrong unit conversion, or a request that would give one oversized stamp instead of a repeating pattern.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/image/watermark-tile-repeat-layout \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"canvas_width":800,"canvas_height":600,"tile_width":160,"tile_height":80,"spacing_x":40,"spacing_y":30,"angle_deg":-30}'const res = await fetch("https://api.kit.forhosting.com/image/watermark-tile-repeat-layout", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"canvas_width": 800,
"canvas_height": 600,
"tile_width": 160,
"tile_height": 80,
"spacing_x": 40,
"spacing_y": 30,
"angle_deg": -30
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/image/watermark-tile-repeat-layout",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"canvas_width": 800,
"canvas_height": 600,
"tile_width": 160,
"tile_height": 80,
"spacing_x": 40,
"spacing_y": 30,
"angle_deg": -30
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/image/watermark-tile-repeat-layout", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"canvas_width":800,"canvas_height":600,"tile_width":160,"tile_height":80,"spacing_x":40,"spacing_y":30,"angle_deg":-30}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"canvas_width":800,"canvas_height":600,"tile_width":160,"tile_height":80,"spacing_x":40,"spacing_y":30,"angle_deg":-30}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/image/watermark-tile-repeat-layout", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"canvas_width": 800,
"canvas_height": 600,
"tile_width": 160,
"tile_height": 80,
"spacing_x": 40,
"spacing_y": 30,
"angle_deg": -30
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "image.watermark_tile_repeat_layout",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Limits
max_mb | 15 |
max_megapixels | 12 |
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Render and reproduce the layout
For each record, place the watermark's center at center_x and center_y, rotate it by angle_deg around that center, and draw it at the supplied tile width and height. The algorithm uses no random offset, clock, stored state, or network resource, so identical inputs always return identical JSON. The API price is $0.002 per calculation, independent of the number of positions within the safety limit.
Technical questions
What does the API call cost?
Each calculation costs $0.002. The result contains the complete position list within the documented safety limit.
What happens when the tile is larger than the canvas?
The request returns an invalid_input error because a repeating tile must not exceed the canvas width or height.