Skip to content

bitcoinaustria/btcat-image-filter

Repository files navigation

Bitcoin Austria Image Filter

Image dithering/filter tool that applies effects to a portion of an image using Bitcoin Austria brand red (#E3000F) and other art styles.

Features

  • Multiple dithering regions: Rectangles, circles, or traditional cut modes
  • Combine shapes: Mix multiple rectangles and circles in a single image
  • Flexible positioning: All coordinates accept any float value (not limited to 0-1)
  • Randomized dithering: Floyd-Steinberg with threshold randomization for organic, less regular patterns
  • Bitcoin Austria brand red: Uses #E3000F for dithered pixels
  • Background modes: Choose white (default) or dark (#222222) background for dithered areas
  • Grayscale mode: Convert entire image to grayscale before applying dithering effects
  • Density control: Uniform fade or angle-based gradient transitions
  • Gradient effects: Create smooth density transitions across the image with any angle (0-360°)
  • Format preservation: Maintains original image format (JPEG/PNG)
  • Smart naming: Automatic output naming with collision avoidance
  • Zero-configuration: Ready to run out of the box with UV
  • Shaded Dithering: Brand dots are shaded proportionally to the underlying image darkness

Examples

Input Image

Original Image

Example 1: Basic Grayscale with Cut at 0.67

./effect.sh --pos=0.67 --grayscale test-image-1920px.jpg

Basic Example

The entire image is converted to grayscale, then the right 33% is dithered in Austrian flag red (#E3000F).

Example 2: Multiple Shapes with Grayscale

./effect.sh --rect=0,0,0.2,1 --rect=0.8,0,1,1 --circle=0.5,0.5,0.2 --jitter=33 --grayscale test-image-1920px.jpg

Shapes Example

Creates two wider vertical strips on the edges (20% width each) plus a circle in the center, all on a grayscale background. The jitter value of 33 adds subtle randomness to the dithering pattern.

Example 3: Subtle Dithering with Dark Background

./effect.sh --pos=0.5 --fade=0.4 --background=dark --grayscale test-image-1920px.jpg

Fade Example

Dithers the right 50% of the image with 40% pixel density on a dark background (#222222), creating a subtle, translucent effect.

Note: Very low fade values (below 0.2) can break the error diffusion algorithm and produce random noise instead of coherent dithering.

Example 4: High Jitter for Organic Texture

./effect.sh --circle=0.5,0.5,0.35 --jitter=100 --grayscale test-image-1920px.jpg

Jitter Example

High jitter (100) creates more randomness and organic texture. The default jitter is 30, and --no-randomize disables it completely for classic Floyd-Steinberg dithering.

Example 5: Satoshi Mode

./effect.sh --circle=0.5,0.5,0.3 --satoshi-mode --grayscale examples/test-image-1920px.jpg

Satoshi Example

“The network self-adjusts.”

Dynamic thresholding adapts to local brightness, preserving more detail in bright and dark areas.

Example 6: Glitch Mode

./effect.sh --glitch=0.02 --grayscale test-image-1920px.jpg

Glitch Example

"Corrupt the Dither."

Introduces controlled data degradation for cyberpunk aesthetics:

  • Swaps random rows
  • Shifts color channels (RGB offset)
  • Repeats error diffusion passes with feedback noise

Example 7: Glitch Mode with Fade

./effect.sh --glitch=0.1 --grayscale --fade=0.9 --jitter=10 test-image-1920px.jpg

Glitch Fade Example

Higher glitch intensity (0.1) combined with heavy 90% fade and low jitter creates an extremely sparse, ghostly corruption effect. The minimal pixel distribution lets the glitching breathe.

Example 8: Gradient Density

./effect.sh --circle=0.25,0.5,0.2 --rect=0.5,0,1,1 --gradient=0,1.0,0.1 --grayscale test-image-1920px.jpg

Gradient Example

Gradient density transitions create smooth fade effects. This example combines a circle on the left with the right half dithered, using a horizontal gradient that goes from 100% density (left) to 10% density (right). The image gradually fades from solid dithering to sparse, creating a natural transition effect.

The --gradient option uses angle,start,end format:

  • : Horizontal left→right
  • 90°: Vertical top→bottom
  • 180°: Horizontal right→left
  • 270°: Vertical bottom→top

Use any angle for diagonal or custom directions.

Example 9: Gradient Density (Reversed)

./effect.sh --circle=0.25,0.5,0.2 --rect=0.5,0,1,1 --gradient=180,1.0,0.1 --grayscale test-image-1920px.jpg

Gradient Reverse Example

Same composition as Example 8, but with the gradient reversed (180°). The gradient now goes from 100% density (right) to 10% density (left), creating the opposite fade direction. This demonstrates how changing the angle parameter reverses the gradient flow while keeping all other parameters identical.

Example 10: Shaded Dithering with Quantization

./effect.sh --shade="1,q=4" --grayscale test-image-1920px.jpg

Shaded Quantized Example

By default, dither dots are shaded proportionally to the darkness of the original image (darker area = darker dot). This creates a "many shaded reds" effect instead of solid flat color.

The --shade parameter allows customization:

  • factor: Float value (default 1). 0 restores the old solid-color behavior.
  • q=N: Quantize the shading into N discrete levels.

In this example, q=4 restricts the red dots to just 4 distinct shades of intensity.

Example 11: Shaded Dithering with Quantization and Glitch

./effect.sh --shade="1,q=4" --glitch=0.02 --grayscale test-image-1920px.jpg

Shaded Quantized Glitch Example

Combines shaded dithering with 4-level quantization and a 10% glitch effect for a corrupt, lo-fi aesthetic.

Original Mode (3-Color Dithering)

See ORIGINAL.md for the full guide on --mode=original, including annotated source code, CLI options, and example images.

Fine-tuning Options

Option Description Example Output
Default Standard settings Default
High Jitter ... --jitter 100
Increases randomness/noise
Jitter
Scaled Dots ... --reference-width 200
Makes dots larger (proportional to width)
Scaled
Darker ... --darkness 50
Draws fewer background pixels (darker appearance)
Darker

Installation

This project uses UV for dependency management. If you don't have UV installed:

curl -LsSf https://astral.sh/uv/install.sh | sh

Then install dependencies:

uv sync

Usage

Basic Usage

# Default: vertical cut at golden ratio (~38%/~62%)
./effect.sh image.webp

# Works with PNG too
./effect.sh image.png

Web UI

Launch the local Web UI to experiment with a visual dashboard that supports zoomable previews and drag-and-drop file uploading.

# Start Web UI on localhost:8050
uv run python webui.py

Dithering Modes

Rectangle Mode

Define rectangular regions for dithering. Coordinates are specified as x1,y1,x2,y2 where (x1,y1) is the top-left corner and (x2,y2) is the bottom-right corner.

# Single rectangle: dither the right half
./effect.sh --rect=0.5,0,1,1 image.webp

# Two vertical strips on the edges
./effect.sh --rect=0,0,0.1,1 --rect=0.9,0,1,1 image.webp

# Top and bottom strips
./effect.sh --rect=0,0,1,0.1 --rect=0,0.9,1,1 image.webp

Circle Mode

Define circular regions for dithering. Coordinates are specified as x,y,radius where (x,y) is the center.

# Circle in the center
./effect.sh --circle=0.5,0.5,0.3 image.webp

# Multiple circles
./effect.sh --circle=0.25,0.25,0.15 --circle=0.75,0.75,0.15 image.webp

Mix Rectangles and Circles

# Top strip plus center circle
./effect.sh --rect=0,0,1,0.1 --circle=0.5,0.5,0.2 image.webp

# Frame effect: strips on all edges plus center circle
./effect.sh --rect=0,0,0.05,1 --rect=0.95,0,1,1 --rect=0,0,1,0.05 --rect=0,0.95,1,1 --circle=0.5,0.5,0.25 image.webp

Traditional Cut Mode

# Vertical cut (default): left part original, right part dithered
./effect.sh --cut=vertical image.webp

# Horizontal cut: top part original, bottom part dithered
./effect.sh --cut=horizontal image.webp

# Custom position (0.0 to 1.0)
# For vertical: --pos=0.4 means left 40% original, right 60% dithered
./effect.sh --cut=vertical --pos=0.4 image.webp

# For horizontal: --pos=0.4 means top 40% original, bottom 60% dithered
./effect.sh --cut=horizontal --pos=0.4 image.webp

Dithering Patterns

Choose different dithering algorithms with --pattern.

Pattern Description Example
Floyd-Steinberg Default error diffusion (randomized) Default
Ordered Bayer 8x8 matrix (clean, grid-like) Ordered
Atkinson Softer error diffusion (vintage Mac feel) Atkinson
Clustered-dot Newspaper-style clusters Clustered
Bitcoin Custom pattern inspired by Bitcoin grid Bitcoin
Hal Tribute to Hal Finney (PGP-era terminal style) Hal
Blue Noise Void-and-cluster 64x64 matrix (uniform, no visible structure) Blue Noise
# Ordered Bayer matrix
./effect.sh --pattern=ordered image.webp

# Vintage Atkinson style
./effect.sh --pattern=atkinson image.webp

# Hal Finney tribute (scanlines + noise)
./effect.sh --pattern=hal --grayscale image.webp

# Blue noise (least visible structure, preserves detail)
./effect.sh --pattern=blue-noise --grayscale image.webp

Branding

Choose different color palettes with --brand to match your project's identity.

Brand Type Color Description Example
btcat Monochrome #E3000F (Red) Bitcoin Austria brand red (default) btcat
lightning Monochrome #F59B1F (Orange) Lightning Network orange lightning
cypherpunk Monochrome #00FF41 (Green) Matrix/terminal green cypherpunk
rgb RGB Full color Dither each RGB channel separately rgb
# Bitcoin Austria red (default)
./effect.sh --brand=btcat image.webp

# Lightning Network orange
./effect.sh --brand=lightning --circle=0.5,0.5,0.3 image.webp

# Cypherpunk/Matrix green
./effect.sh --brand=cypherpunk --grayscale image.webp

# RGB mode: separate dithering for R, G, B channels
./effect.sh --brand=rgb --pattern=ordered image.webp

# Combine branding with patterns and shapes
./effect.sh --brand=lightning --pattern=bitcoin --rect=0.5,0,1,1 image.webp

RGB Mode is special: instead of using a single color, it dithers each color channel (Red, Green, Blue) independently, creating unique color-separation effects. This works with all dithering patterns.

Global Options

Grayscale

Converts the entire image to grayscale before applying dithering effects.

# Grayscale entire image, then dither
./effect.sh --grayscale image.webp

# Combine with shapes
./effect.sh --grayscale --circle=0.5,0.5,0.3 image.webp

Fade/Density Control

Control the density of dithering across all dithered areas.

# Sparse dithering: only 10% of pixels dithered
./effect.sh --fade=0.1 image.webp

# 50% density for subtle effect
./effect.sh --fade=0.5 --rect=0.5,0,1,1 image.webp

# Combine with grayscale
./effect.sh --grayscale --fade=0.3 --circle=0.5,0.5,0.4 image.webp

Gradient Density

Create smooth density transitions across the image using angle-based gradients.

Format: --gradient=angle,start,end

  • Angle: 0-360° (0=left→right, 90=top→bottom, 180=right→left, 270=bottom→top)
  • Start: Density at gradient start (0.0 to 1.0)
  • End: Density at gradient end (0.0 to 1.0)
# Horizontal gradient: sparse on left, dense on right
./effect.sh --gradient=0,0.1,1.0 image.webp

# Vertical gradient: fade from top to bottom
./effect.sh --gradient=90,0.0,1.0 image.webp

# Reverse horizontal: dense on left, sparse on right
./effect.sh --gradient=180,1.0,0.2 image.webp

# Diagonal gradient (45 degrees)
./effect.sh --gradient=45,0.0,1.0 image.webp

# Fade to white: image gradually vanishes
./effect.sh --gradient=0,1.0,0.0 image.webp

# Combine with shapes: gradient only in specific areas
./effect.sh --gradient=0,1.0,0.1 --circle=0.25,0.5,0.2 --rect=0.5,0,1,1 image.webp

Note: Gradient overrides --fade if both are specified. The gradient is computed based on pixel position in the image and applied only to dithered regions.

Background Color

Choose between white or dark background for dithered areas.

# White background (default): red dots on white
./effect.sh --background=white image.webp

# Dark background: red dots on dark gray (#222222)
./effect.sh --background=dark image.webp

# Combine with fade for subtle dark effect
./effect.sh --background=dark --fade=0.4 --pos=0.5 image.webp

Dithering Threshold

# Adjust the dithering threshold (0-255, default: 128)
# Lower values = more red pixels, Higher values = more white pixels
./effect.sh --threshold 100 image.webp

Randomization & Jitter

# Randomization is enabled by default. Control the amount with --jitter (default: 30.0)
./effect.sh --jitter 50 image.webp

# Disable randomization for classic Floyd-Steinberg
./effect.sh --no-randomize image.webp

Point Size Scaling

Make the dither pattern proportional to the image width.

# Set a reference width (default: 1024).
# If input is wider than this, dither points become larger.
./effect.sh --reference-width 800 image.webp

Darkness Control

Adjust the darkness of the dithered output.

# Make the result darker (fewer white pixels)
./effect.sh --darkness 30 image.webp

# Make the result lighter (more white pixels)
./effect.sh --darkness -30 image.webp

Satoshi Mode

Enable dynamic thresholding that adapts per pixel based on local brightness.

# Brighter areas get higher threshold -> fewer red pixels
./effect.sh --satoshi-mode image.webp

Glitch Mode

Introduce controlled digital corruption.

# Mild glitch (0.1): Subtle artifacts, slight color shift
./effect.sh --glitch=0.02 image.webp

# Heavy glitch (0.5+): Strong channel shifting, row swapping, and noise
./effect.sh --glitch=0.5 image.webp

Shaded Dithering

Control how the brand dots are shaded based on the underlying grayscale image.

# Default behavior (factor=1): dots are darker in dark areas
./effect.sh image.webp

# Disable shading (restore old solid-color dots)
./effect.sh --shade=0 image.webp

# Half strength shading
./effect.sh --shade=0.5 image.webp

# Quantized shading (restrict to 3 levels)
./effect.sh --shade="1,q=3" image.webp

Advanced Combinations

# Grayscale + sparse dithering + custom shapes
./effect.sh --grayscale --fade=0.2 --rect=0,0,0.2,1 --circle=0.6,0.5,0.3 image.webp

# High jitter + darkness + circle
./effect.sh --jitter=100 --darkness=50 --circle=0.5,0.5,0.4 image.webp

# Multiple rectangles with threshold adjustment
./effect.sh --threshold=100 --rect=0,0,0.3,1 --rect=0.7,0,1,1 image.webp

Output

The default output format is WebP, which offers excellent quality at smaller file sizes and is supported by all modern browsers. The tool generates output files with the format:

  • [original-name]-dither.webp
  • If the file exists, it appends a number: [original-name]-dither-1.webp
  • When using --output, the format is determined by the file extension (.webp, .jpg, .png)

The recommended input width for best results is 1920px, which covers standard desktop displays and looks sharp on tablets at 2x.

How It Works

  1. Load and prepare: Reads the input image and converts to RGB if needed
  2. Grayscale conversion (optional): If --grayscale is specified, converts the entire image to grayscale
  3. Create dithering masks: Generates boolean masks for rectangles and circles (combined with logical OR)
  4. Dithering: Applies Floyd-Steinberg error diffusion dithering with optional randomization
  5. Density control (optional):
    • If --gradient is specified, creates angle-based gradient density mask across entire image
    • If --fade is specified (and no gradient), creates uniform density mask
    • Density mask probabilistically skips pixels for sparse/fade effects
  6. Colorization: Renders dithered pixels in Bitcoin Austria red (#E3000F) on chosen background (white or dark #222222)
  7. Compositing: Applies dithered regions to the base image using the masks
  8. Save: Outputs the result in the same format as the input with smart naming

Requirements

  • Python 3.10+
  • Pillow (for image processing)
  • NumPy (for array operations)
  • Click (for CLI with automatic validation)

All dependencies are managed automatically by UV.

License

Apache License 2.0

Copyright 2026 Harald Schilly hsy@bitcoin-austria.at

Licensed under the Apache License, Version 2.0. See the LICENSE file for details.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors