CrImage

A comprehensive pure-Crystal image processing library with no external dependencies.

Why CrImage?

A few highlights:

  • Complete image toolkit: formats, drawing, text, filters, effects — all in one shard
  • Built-in TrueType font engine (no FreeType dependency)
  • Animated GIF creation and multi-resolution ICO support
  • QR code generation, blurhash encoding, smart cropping
  • Decompression bomb protection for untrusted images

See Features at a Glance for the full list.

Installation

Add to your shard.yml:

dependencies:
  crimage:
    github: naqvis/crimage

Then run shards install.

Quick Start

require "crimage"

# Read any supported format (auto-detected)
img = CrImage.read("photo.jpg")

# Basic operations
resized = img.resize(800, 600, method: :lanczos)
thumbnail = img.thumb(200)  # 200x200 square

# Apply filters
result = img.brightness(20).contrast(1.2).blur(2)

# Save in any format
CrImage::PNG.write("output.png", result)
CrImage::WEBP.write("output.webp", result)

Features at a Glance

Category Capabilities
Formats PNG, JPEG, GIF (animated), BMP, TIFF, WebP (VP8L), ICO
Color RGBA, NRGBA, Gray, CMYK, YCbCr, HSV, HSL, LAB, Paletted
Transform Resize (nearest/bilinear/bicubic/lanczos), rotate, crop, flip
Filters Blur, sharpen, edge detection (Sobel/Prewitt/Roberts/Scharr)
Effects Sepia, emboss, vignette, temperature, grayscale, invert
Analysis Histogram, CLAHE, SSIM, PSNR, perceptual hash, palette extraction
Drawing Lines, circles, polygons, gradients, Bézier, patterns, chart helpers
Text TrueType/OpenType/WOFF fonts, multi-line, alignment, effects
Utilities Thumbnails, watermarks, sprites, QR codes, blurhash, smart crop

Common Tasks

Resize and Thumbnails
# Fit within bounds (preserves aspect ratio)
fitted = img.fit(800, 600)

# Fill exact dimensions (crops excess)
filled = img.fill(800, 600)

# Square thumbnail
avatar = img.thumb(200)

# High-quality resize
hq = img.resize(1920, 1080, method: :lanczos)
Color Adjustments
# Basic adjustments
brighter = img.brightness(30)      # -255 to 255
contrasted = img.contrast(1.5)     # 0.0 to 2.0
grayscale = img.grayscale
inverted = img.invert

# Effects
vintage = img.sepia
warm = img.temperature(30)         # positive = warmer
cool = img.temperature(-30)        # negative = cooler
Filters
# Blur
blurred = img.blur(radius: 3)
gaussian = img.blur_gaussian(radius: 5, sigma: 2.0)

# Sharpen
sharp = img.sharpen(amount: 1.5)

# Edge detection
edges = img.sobel
edges = img.prewitt
edges = img.detect_edges(threshold: 50)
Drawing
img = CrImage.rgba(400, 400, CrImage::Color::WHITE)

# Shapes
img.draw_line(0, 0, 400, 400, color: CrImage::Color::RED)
img.draw_circle(200, 200, 50, color: CrImage::Color::BLUE, fill: true)
img.draw_rect(50, 50, 100, 80, stroke: CrImage::Color::BLACK)

# Thick anti-aliased lines
style = CrImage::Draw::LineStyle.new(CrImage::Color::RED, thickness: 5, anti_alias: true)
CrImage::Draw.line(img, CrImage::Point.new(50, 50), CrImage::Point.new(350, 350), style)

# Ring slice (for donut charts)
ring_style = CrImage::Draw::RingStyle.new(CrImage::Color::BLUE, fill: true)
CrImage::Draw.ring_slice(img, CrImage::Point.new(200, 200), 40, 80, 0.0, Math::PI, ring_style)

# Clipping regions
img.with_clip(50, 50, 200, 200) do |clipped|
  # Drawing here is restricted to the clip region
  clipped.draw_circle(100, 100, 150, color: CrImage::Color::GREEN, fill: true)
end

# Gradients
stops = [
  CrImage::Draw::ColorStop.new(0.0, CrImage::Color::RED),
  CrImage::Draw::ColorStop.new(1.0, CrImage::Color::BLUE)
]
gradient = CrImage::Draw::LinearGradient.new(
  CrImage::Point.new(0, 0),
  CrImage::Point.new(400, 0),
  stops
)
CrImage::Draw.fill_linear_gradient(img, img.bounds, gradient)
Text Rendering
require "freetype"

font = FreeType::TrueType.load("path/to/font.ttf")
face = FreeType::TrueType.new_face(font, 48.0)

img = CrImage.rgba(400, 100, CrImage::Color::WHITE)
text_color = CrImage::Uniform.new(CrImage::Color::BLACK)
drawer = CrImage::Font::Drawer.new(img, text_color, face)

# Simple text
drawer.draw_text("Hello, World!", 20, 60)

# With effects
drawer.draw_text("Styled", 20, 60,
  shadow: true,
  outline: true,
  underline: true
)

# Easy text measurement
width = face.measure("Hello")
width, height = face.text_size("Hello")
line_height = face.line_height
Animated GIFs
frames = (0...10).map do |i|
  frame_img = CrImage.rgba(100, 100, CrImage::Color::WHITE)
  frame_img.draw_circle(10 + i * 8, 50, 10, color: CrImage::Color::RED, fill: true)
  CrImage::GIF::Frame.new(frame_img, delay: 10)  # 100ms per frame
end

animation = CrImage::GIF::Animation.new(frames, 100, 100, loop_count: 0)
CrImage::GIF.write_animation("animated.gif", animation)
QR Codes
# Simple
qr = CrImage.qr_code("https://example.com")
CrImage::PNG.write("qr.png", qr)

# With options
qr = CrImage.qr_code("Hello",
  size: 400,
  error_correction: :high,
  margin: 4
)
Blurhash
# Encode image to blurhash string
hash = img.to_blurhash  # => "LKO2?U%2Tw=w]~RBVZRi};RPxuwH"

# Decode to placeholder
placeholder = CrImage::Util::Blurhash.decode(hash, width: 32, height: 32)
Image Comparison
original = CrImage.read("original.png")
modified = CrImage.read("modified.png")

mse = original.mse(modified)        # Mean Squared Error
psnr = original.psnr(modified)      # Peak Signal-to-Noise Ratio (dB)
ssim = original.ssim(modified)      # Structural Similarity (0-1)

# Visual diff for testing
diff = original.visual_diff(modified)
CrImage::PNG.write("diff.png", diff)

Font Support

The library includes a complete TrueType font engine:

Feature Status
TrueType (.ttf) ✅ Full support
OpenType/CFF (.otf) ✅ Full support
WOFF (.woff) ✅ Auto-decompression
TrueType Collections (.ttc) ✅ Multi-font files
Legacy kern table ✅ Pair kerning
GPOS kerning ✅ Modern pair positioning
GSUB ligatures ✅ Standard ligatures (fi/fl/ffi/ffl)
Vertical text ✅ Top-to-bottom layout
Variable fonts ⚠️ Axis detection only
Complex scripts (Arabic, Indic) ❌ Requires external shaper

Note: Text rendering uses left-to-right glyph placement. Complex scripts (Arabic, Hebrew, Thai, Indic) and advanced OpenType features (contextual forms, mark positioning) require an external shaping engine like HarfBuzz.

Architecture

CrImage::Image          # Base interface for all image types
├── CrImage::RGBA       # 8-bit RGBA (premultiplied alpha)
├── CrImage::NRGBA      # 8-bit RGBA (non-premultiplied)
├── CrImage::Gray       # 8-bit grayscale
├── CrImage::Alpha      # 8-bit alpha channel
├── CrImage::CMYK       # Print color model
├── CrImage::YCbCr      # JPEG color model
├── CrImage::Paletted   # Indexed color (GIF)
└── CrImage::Uniform    # Solid color fill

CrImage::Draw           # Drawing primitives
CrImage::Transform      # Resize, rotate, filters
CrImage::Util           # Thumbnails, watermarks, etc.
CrImage::Font           # Text rendering
FreeType::TrueType      # Font parsing

Documentation

Guide Description
Quick Start 10 common tasks with examples
Image Formats Supported formats and adding custom ones
Drawing & Text Primitives, gradients, fonts
Transforms & Filters Resize, rotate, blur, effects
Color Models RGBA, HSV, LAB conversions
Utilities Thumbnails, watermarks, QR, blurhash
Performance Optimization and thread-safety
Security Handling untrusted images

Examples: See examples/ for 60+ working demos.

API Docs: Run crystal docs and open docs/index.html.

Development

crystal spec          # Run tests
crystal docs          # Generate API docs

Contributing

  1. Fork it (https://github.com/naqvis/crimage/fork)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

License

MIT License. See LICENSE for details.

Contributors

Repository

crimage

Owner
Statistic
  • 21
  • 0
  • 1
  • 1
  • 0
  • 5 days ago
  • December 29, 2025
License

Other

Links
Synced at

Wed, 07 Jan 2026 19:44:33 GMT

Languages