texture-generator
Generate random material textures with Python: metal, plastic, wood and paper, as Pillow images or NumPy arrays. Choose a size and seed to create a reproducible texture, then save it as a PNG. Generation runs locally using NumPy and Pillow, without models or runtime downloads. Small scientific lookup tables ship with the galvanised generator; generation needs no network access.
⚠️ Much of this is AI-generated, and not formally reviewed by hand or eye. It's published chiefly for myself: for my own reference, use, and for experimentation with the whole open-source publishing process. I also use this code: I dogfood it. It works, for me. I also write tests, and run them to check that it works, and does what it says.
Installation
Requires Python 3.12 or later. CI covers Python 3.12, 3.13 and 3.14 with locked dependencies and the lowest compatible NumPy/Pillow wheels and Click release.
pip install texture-generator
The distribution is named texture-generator; the Python import is
texture_generators, and the command-line program is texture-gen.
Installation includes NumPy, Pillow and Click (used by the CLI).
Quickstart
from texture_generators import generate
image = generate("wood", size=(640, 480), seed=42, variant="board")
image.save("wood.png")
print(image.mode, image.size)
# RGB (640, 480)
This writes wood.png in the current directory. Sizes are (width, height);
an integer such as size=512 creates a square. Both dimensions must be at least
2 pixels. The same arguments reproduce the same image with the same package,
dependency versions and platform. Omit seed for a fresh texture.
For raw pixels or a labelled sheet of every variant:
from texture_generators import generate_array, sample_sheet
pixels = generate_array("metal", size=256, seed=7, variant="brushed")
print(pixels.shape, pixels.dtype)
# (256, 256, 3) float32
sheet = sample_sheet(size=128, seed=7)
sheet.save("materials.png")
Arrays contain RGB values in [0, 1]. The
API reference
covers all public functions and material parameters.
Galvanised metal
The metal/galvanised variant adds physical millimetre dimensions,
micrometre height maps, independent relighting and lossless material exports.
Its presets are regular, minimised, weathered and wet_storage. It is a
procedural visual approximation; measured-material calibration and a complete
angular reflection fit remain outside this release.
from texture_generators import generate_maps, render_material
from texture_generators.materials.galvanised import GalvanisedConfig
maps = generate_maps(
"metal",
variant="galvanised",
size=(512, 384),
seed=42,
galvanised=GalvanisedConfig(size_mm=(100, 75)),
)
render_material(maps).save("galvanised.png")
See the galvanised guide for map semantics, export and CLI usage and limits. The implementation record tracks the separate calibration and performance ambitions.
Galvanised renders are much slower than the other variants: a 512 × 384 render takes about 20 seconds. That size is the tested envelope for ordinary renders. Larger sizes, including 4096², can be requested but have not been validated for full rich exports; the performance record has the measurements.
Brushing direction
From version 0.4.0, set brush_angle for the brushed metal variant. Angles are
degrees clockwise in image coordinates: 0 is horizontal, 90 is vertical,
and 45 runs from top left to bottom right. Finite angles wrap at 360 degrees.
image = generate("metal", size=(640, 480), seed=42, variant="brushed", brush_angle=90)
image.save("vertical-brushing.png")
The same keyword works with generate_array(), and can be combined with a metal
film override. It controls grooves, scratches, directional shading, sheen and
glints, plus groove diffraction on unfilmed metal. The light itself keeps its
direction. Omit brush_angle or use None to retain the random direction and
existing seeded output. An explicit angle requires material="metal" and
variant="brushed".
These examples share seed 42 and a size of 384 × 256 pixels:
| 0° — horizontal | 45° | 90° — vertical | 135° |
|---|---|---|---|
Command line
texture-gen wood --variant board --size 640x480 --seed 42 -o wood.png
texture-gen list
texture-gen sheet --size 128 --seed 7 -o materials.png
texture-gen wood --help
texture-gen metal --variant brushed --brush-angle 90 --seed 42 -o vertical-brushing.png
python -m texture_generators runs the same CLI. It also supports batches,
JSON reports, shell completion and browser galleries for comparing seeds and
parameters. See the
CLI reference.
--brush-angle is available on metal and requires --variant brushed.
It also works with --count; JSON reports include the angle when it is supplied.
Materials
| Material | Variants |
|---|---|
| Metal | brushed, radial, polished, heat_tinted, oil_film, anodised_titanium, engine_turned, galvanised |
| Plastic | glossy, matte, textured |
| Wood | board, planks |
| Paper | white, kraft, recycled, newsprint, laid, coated |
Both wood variants support pine, maple, ash, oak, cherry, walnut, sapele and
mahogany; select one with species= in Python, and see the
species gallery below or the
parameter reference.
image = generate("wood", size=(640, 480), seed=42, variant="board", species="oak")
The generators combine noise, material anatomy and lighting. Albedo, height and
roughness share underlying fields so visible features also affect shading.
These are procedural approximations with documented calibration assumptions.
Legacy metal, plastic and wood do not tile seamlessly; galvanised uses periodic
physical fields. Paper tiles when creases are
disabled with creases=0.0. Wood's relief is carried in real millimetres, so
the same board shades the same at any render size; the documented workflow is
to render at 1000 px or more and resample down, which is what
the mip test
checks.
Read the technical reference for the algorithms, wood and paper models, core modules, performance estimates and limitations. See the changelog for changes and GitHub issues to report a bug.
Examples
Every variant below is rendered at 384 × 384 pixels with seed 42 and an explicit variant name. The full results gallery includes the notebook's outputs and links to each PNG; the recipe manifest records how they were generated.
Metal
| Brushed | Radial | Polished | Heat tinted |
|---|---|---|---|
| Oil film | Anodised titanium | Engine turned | Galvanised |
The four galvanised presets show the same seed and light with different surface conditions.
Plastic
| Glossy | Matte | Textured |
|---|---|---|
Wood
Variants
| Board | Planks |
|---|---|
Species
Boards and planks are rendered in eight species. These tiles share seed 42 at
384 × 384 pixels and differ only by species; the other draws are pinned to
variant="board", cut="flatsawn", figure="plain", finish="oil",
colour_variation=0, mm_across=180, knots=0 and sapwood=0. Omitting
species makes a uniform seeded choice.
| Pine | Maple | Ash | Oak |
|---|---|---|---|
| Cherry | Walnut | Sapele | Mahogany |
Wood's chatoyance — the way figured grain flashes light and dark — lives in the
reflection, not the pigment, so it only shows up once light_dir moves. The
workflow notebook renders one board six times under different light azimuths
and pastes the tiles into a strip:
curly maple,
quartersawn oak.
Paper
| White | Kraft | Recycled |
|---|---|---|
| Newsprint | Laid | Coated |
Run the notebook
Open the workflow notebook to work through the importable Python API, save images and arrays as PNGs, and build a contact sheet. Preview the standalone wood image, metal array saved as an image and contact sheet.
From the repository root, install the optional notebook tools and launch JupyterLab:
uv sync --group examples
uv run --group examples jupyter lab examples/texture_generator_workflow.ipynb
Choose Run → Run All Cells. The notebook writes the separate PNGs to
examples/images/, records their recipes in examples/manifest.json, and
regenerates examples/results.md. Notebook tools live in the optional
examples dependency group.
Development
Install uv, then:
git clone https://github.com/nmpowell/texture-generator.git
cd texture-generator
uv sync --locked
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv sync --locked installs the project editable and uses the committed lockfile.
The .python-version file selects Python 3.14 for development; pass
--python 3.12 or --python 3.13 to uv sync and uv run to use an older
supported interpreter. See the release guide for
commands to check locked and minimum runtime dependencies on all three versions
in separate environments. Minimum checks preserve the locked development tools
and leave uv.lock unchanged.
Use uv run ruff format . to apply formatting. The tests exercise rendering,
determinism, material physics, the CLI and built-package contents. mypy runs
over the package internals and must pass; CI checks it against Python 3.12, the
oldest supported interpreter. See the
development notes.
The version lives in pyproject.toml; texture_generators.__version__ reads
the installed package metadata. The
release guide
covers artifact checks and Trusted Publishing.
License
The code uses the Apache License 2.0. Packaged galvanised optical data retain their separate CC0-1.0 and CC-BY-SA-4.0 terms; the derived Fresnel and zinc energy tables use CC-BY-SA-4.0. Attributions and resource licences are included in the package and described in the source register.
Metadata
Release files for texture-generator 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| texture_generator-0.7.0.tar.gz | 13.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| texture_generator-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 17.6 MB
Release files / texture_generator-0.7.0.tar.gz
| Download URL | texture_generator-0.7.0.tar.gz |
|---|---|
| Size | 13.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dd55c488daf8e88218c086fdb318d1f6c5ef242f44b45975c9919c6a15f3bb89
|
|
BLAKE2b-256 checksum How to use checksums |
5412e2e74fb700711cc61c3be2cba3bd6155aa80542af45a4a633bddbb74644e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency logRelease files / texture_generator-0.7.0-py3-none-any.whl
| Download URL | texture_generator-0.7.0-py3-none-any.whl |
|---|---|
| Size | 4.0 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5f62084f4089028a5aa5fdc3c767861fb1f6c3e4ddbecebd333487a84d42ae1a
|
|
BLAKE2b-256 checksum How to use checksums |
77bf4386cae00971a1840bb9ee64cd4f7437ef8970c31cd46f9ad749e6bf4667
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.
Transparency log