Skip to content
Open
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
32 changes: 32 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Tests

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
test:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Deno
uses: denoland/setup-deno@v2
with:
deno-version: v2.1.x

- name: Run tests
run: deno task test

- name: Check formatting
run: deno fmt --check

- name: Run linter
run: deno lint
22 changes: 22 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Deno
.deno/
deno.lock

# Build outputs
*.exe
tile-maker

# Logs
*.log

# OS files
.DS_Store
Thumbs.db

# IDE
.vscode/
.idea/

# Test artifacts
coverage/
*.lcov
160 changes: 106 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@

## Problem Statement

When generating tiles for websites, designers and developers often encounter issues when rotating tiles. Common solutions involve less-than-ideal hacks, such as adding pseudoelements, scaling, and rotating them to achieve the desired effect. These workarounds can complicate code, reduce performance, and make maintenance harder.
When generating tiles for websites, designers and developers often encounter
issues when rotating tiles. Common solutions involve less-than-ideal hacks, such
as adding pseudo-elements, scaling, and rotating them to achieve the desired
effect. These workarounds can complicate code, reduce performance, and make
maintenance harder.

WTC Tile Maker aims to solve this by providing a robust, programmatic solution for generating and manipulating tiles, including rotation, without relying on CSS hacks.
WTC Tile Maker aims to solve this by providing a robust, programmatic solution
for generating and manipulating tiles, including rotation, without relying on
CSS hacks.

---

Expand All @@ -24,10 +30,10 @@ deno run -A main.ts list

# Help
deno run -A main.ts help

```

The output file will be saved in the same directory as the input with the angle appended to the filename (e.g., `input-tile-45.png`).
The output file will be saved in the same directory as the input with the angle
appended to the filename (e.g., `input-tile-45.png`).

---

Expand All @@ -50,6 +56,26 @@ The output file will be saved in the same directory as the input with the angle
deno run -A main.ts
```

### Running Tests

Run the test suite:

```sh
deno task test
```

Check code formatting:

```sh
deno fmt
```

Run the linter:

```sh
deno lint
```

---

## CLI Usage
Expand Down Expand Up @@ -80,47 +106,49 @@ The output file will be saved in the same directory as the input with the angle

## Available Rational Angles

These are the angles where `tan(θ) = m/n` for small integers m, n. These angles produce periodic tilings when used for rotation.

| Index | Label | m | n |
| ----- | ---------------------- | --- | --- |
| 0 | 0° | 0 | 1 |
| 1 | 90° | 1 | 0 |
| 2 | -90° | -1 | 0 |
| 3 | 45° | 1 | 1 |
| 4 | -45° | -1 | 1 |
| 5 | 26.565° (arctan 1/2) | 1 | 2 |
| 6 | -26.565° (arctan -1/2) | -1 | 2 |
| 7 | 63.435° (arctan 2) | 2 | 1 |
| 8 | -63.435° (arctan -2) | -2 | 1 |
| 9 | 18.435° (arctan 1/3) | 1 | 3 |
| 10 | -18.435° (arctan -1/3) | -1 | 3 |
| 11 | 71.565° (arctan 3) | 3 | 1 |
| 12 | -71.565° (arctan -3) | -3 | 1 |
| 13 | 14.036° (arctan 1/4) | 1 | 4 |
| 14 | -14.036° (arctan -1/4) | -1 | 4 |
| 15 | 75.964° (arctan 4) | 4 | 1 |
| 16 | -75.964° (arctan -4) | -4 | 1 |
| 17 | 33.690° (arctan 2/3) | 2 | 3 |
| 18 | -33.690° (arctan -2/3) | -2 | 3 |
| 19 | 56.310° (arctan 3/2) | 3 | 2 |
| 20 | -56.310° (arctan -3/2) | -3 | 2 |
| 21 | 36.870° (arctan 3/4) | 3 | 4 |
| 22 | -36.870° (arctan -3/4) | -3 | 4 |
| 23 | 53.130° (arctan 4/3) | 4 | 3 |
| 24 | -53.130° (arctan -4/3) | -4 | 3 |
| 25 | 11.310° (arctan 1/5) | 1 | 5 |
| 26 | -11.310° (arctan -1/5) | -1 | 5 |
| 27 | 78.690° (arctan 5) | 5 | 1 |
| 28 | -78.690° (arctan -5) | -5 | 1 |
These are the angles where `tan(θ) = m/n` for small integers m, n. These angles
produce periodic tilings when used for rotation.

| Index | Label | m | n |
| ----- | ---------------------- | -- | - |
| 0 | 0° | 0 | 1 |
| 1 | 90° | 1 | 0 |
| 2 | -90° | -1 | 0 |
| 3 | 45° | 1 | 1 |
| 4 | -45° | -1 | 1 |
| 5 | 26.565° (arctan 1/2) | 1 | 2 |
| 6 | -26.565° (arctan -1/2) | -1 | 2 |
| 7 | 63.435° (arctan 2) | 2 | 1 |
| 8 | -63.435° (arctan -2) | -2 | 1 |
| 9 | 18.435° (arctan 1/3) | 1 | 3 |
| 10 | -18.435° (arctan -1/3) | -1 | 3 |
| 11 | 71.565° (arctan 3) | 3 | 1 |
| 12 | -71.565° (arctan -3) | -3 | 1 |
| 13 | 14.036° (arctan 1/4) | 1 | 4 |
| 14 | -14.036° (arctan -1/4) | -1 | 4 |
| 15 | 75.964° (arctan 4) | 4 | 1 |
| 16 | -75.964° (arctan -4) | -4 | 1 |
| 17 | 33.690° (arctan 2/3) | 2 | 3 |
| 18 | -33.690° (arctan -2/3) | -2 | 3 |
| 19 | 56.310° (arctan 3/2) | 3 | 2 |
| 20 | -56.310° (arctan -3/2) | -3 | 2 |
| 21 | 36.870° (arctan 3/4) | 3 | 4 |
| 22 | -36.870° (arctan -3/4) | -3 | 4 |
| 23 | 53.130° (arctan 4/3) | 4 | 3 |
| 24 | -53.130° (arctan -4/3) | -4 | 3 |
| 25 | 11.310° (arctan 1/5) | 1 | 5 |
| 26 | -11.310° (arctan -1/5) | -1 | 5 |
| 27 | 78.690° (arctan 5) | 5 | 1 |
| 28 | -78.690° (arctan -5) | -5 | 1 |

You can also run `deno -A main.ts list` to see these angles.

---

## Recommended Input Aspect Ratios

The output tile size depends on how well the input dimensions align with the m:n ratios of the angles. For best results, use these aspect ratios:
The output tile size depends on how well the input dimensions align with the m:n
ratios of the angles. For best results, use these aspect ratios:

### Best Aspect Ratios

Expand All @@ -136,7 +164,9 @@ The output tile size depends on how well the input dimensions align with the m:n

### Ratios to Avoid

Aspect ratios with **prime numbers > 5** (like 7:3, 11:4, 13:8) or **irrational proportions** will produce larger outputs because the GCD calculations yield smaller divisors.
Aspect ratios with **prime numbers > 5** (like 7:3, 11:4, 13:8) or **irrational
proportions** will produce larger outputs because the GCD calculations yield
smaller divisors.

---

Expand All @@ -156,27 +186,35 @@ deno -A main.ts list

### Generate a Tile

Generate a rotated tile from `Checker.png` using angle index 27, and max size 6000:
Generate a rotated tile from `Checker.png` using angle index 27, and max size
6000:

| Step | Description | Example |
| ------- | ------------------------------------------------------------------------------------- | ------------------------------------------------- |
| Input | Source image to be tiled and rotated. | <img src="Checker.png" width="300" /> |
| Command | Command to generate a rotated tile using angle index 27, margin 3, and max size 6000. | `generate -lv -a 27 -s 6000 Checker.png` |
| Output | Resulting seamlessly tileable image after processing. | <img src="Checker-tile-63.435.png" width="300" /> |

This will also work with images of different aspect ratios (like 3×2). For example. this enerates a rotated tile from `3x2-checker.png` using 45°:
This will also work with images of different aspect ratios (like 3×2). For
example, this generates a rotated tile from `3x2-checker.png` using 45°:

Comment thread
marlonmarcello marked this conversation as resolved.
| Step | Description | Example |
| ------- | ------------------------------------------------ | ------------------------------------------------- |
| Input | Source image with 3×2 aspect ratio. | <img src="3x2-checker.png" width="300" /> |
| Command | Command to generate a rotated tile at 45°. | `generate -d 45 3x2-checker.png` |
| Output | Seamlessly tileable image after rotating at 45°. | <img src="3x2-checker-tile-45.png" width="300" /> |

_N.B._ Notice, here, how there seems to be a piece missing! This is because the default tile margin is too small to make these rotated tiles cover the output dimensions. This command should be updated to `generate -m 2 -d 45 3x2-checker.png`.
_N.B._ Notice, here, how there seems to be a piece missing! This is because the
default tile margin is too small to make these rotated tiles cover the output
dimensions. This command should be updated to
`generate -m 2 -d 45 3x2-checker.png`.

#### A warning about sizes and appropriate aspect ratios

Sometimes, a combination of input size and rotation will produce an output that is either too large or creating an appropriate output tile is just beyong the capabilities of this math. In this case you should likely fall back to hacky methods or get a tile produces in a more predictable aspect ratio.
Sometimes, a combination of input size and rotation will produce an output that
is either too large or creating an appropriate output tile is just beyond the
capabilities of this math. In this case you should likely fall back to hacky
methods or get a tile produced in a more predictable aspect ratio.

### Generate with a Specific Degree

Expand All @@ -192,20 +230,27 @@ The tool will find the closest rational angle and generate the tile accordingly.

## Common Issues

- **allowLargeBuffers**: If you receive an error about `allowLargeBuffers`, use the `-l` flag. Warning: this may affect performance, but enables processing of narrow rotation / large image combinations.
- **Missing pieces**: If the output appears to be missing pieces around the edges, try increasing the `-m` (tileMargin) value, e.g., `-m 3`.
- **allowLargeBuffers**: If you receive an error about `allowLargeBuffers`, use
the `-l` flag. Warning: this may affect performance, but enables processing of
narrow rotation / large image combinations.
- **Missing pieces**: If the output appears to be missing pieces around the
edges, try increasing the `-m` (tileMargin) value, e.g., `-m 3`.

---

## How This Works

### The Problem with Rotating Tiles

When you rotate a regular tiling pattern (like a checkerboard) by an arbitrary angle, the result doesn't tile seamlessly in the x and y direction anymore.
When you rotate a regular tiling pattern (like a checkerboard) by an arbitrary
angle, the result doesn't tile seamlessly in the x and y direction anymore.

### Rational Angles

The key insight is that **rational angles**—angles where `tan(θ) = m/n` for small integers m and n—produce periodic tilings when used for rotation. At these specific angles, the rotated grid aligns back to integer positions, allowing seamless repetition.
The key insight is that **rational angles**—angles where `tan(θ) = m/n` for
small integers m and n—produce periodic tilings when used for rotation. At these
specific angles, the rotated grid aligns back to integer positions, allowing
seamless repetition.

For example:

Expand All @@ -215,21 +260,28 @@ For example:

### Tile Dimension Calculation

For a rational angle with `tan(θ) = m/n`, the rotated pattern repeats at intervals of `√(m² + n²)` times the original period.
For a rational angle with `tan(θ) = m/n`, the rotated pattern repeats at
intervals of `√(m² + n²)` times the original period.

To create a seamlessly tileable output:

- The output dimensions are calculated as: `input dimensions × √(m² + n²) / gcd(m, n)`
- The output dimensions are calculated as:
`input dimensions × √(m² + n²) / gcd(m, n)`
- This ensures the rotated grid aligns back to integer positions

### The Process

1. **Input**: A tileable source image (e.g., a checkerboard pattern)
2. **Tile**: The source image is tiled into a larger canvas to provide enough material for the rotation
2. **Tile**: The source image is tiled into a larger canvas to provide enough
material for the rotation
3. **Rotate**: The tiled canvas is rotated by the selected rational angle
4. **Crop**: A precisely calculated region is extracted that will tile seamlessly
5. **Output**: The resulting image can be used as a CSS background and will tile perfectly at the rotated angle

This approach eliminates the need for CSS hacks like pseudoelements with `transform: rotate()` and `scale()`, resulting in cleaner code, better performance, and more predictable rendering across browsers.
4. **Crop**: A precisely calculated region is extracted that will tile
seamlessly
5. **Output**: The resulting image can be used as a CSS background and will tile
perfectly at the rotated angle

This approach eliminates the need for CSS hacks like pseudo-elements with
`transform: rotate()` and `scale()`, resulting in cleaner code, better
performance, and more predictable rendering across browsers.

---
4 changes: 3 additions & 1 deletion deno.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"imports": {
"@std/assert": "jsr:@std/assert@^1.0.19",
"@std/cli": "jsr:@std/cli@^1.0.27",
"@std/fs": "jsr:@std/fs@^1.0.22",
"@std/path": "jsr:@std/path@^1.1.4",
"sharp": "npm:sharp@^0.34.5"
},
"tasks": {
"compile": "deno compile --allow-all main.ts"
"compile": "deno compile --allow-all main.ts",
"test": "deno test --allow-read --allow-env --allow-ffi"
},
"version": "0.0.1"
}
Loading