ESC
Type to search...
S
Soli Docs

Image Class

Pure-Rust image manipulation for resizing, cropping, transforms, filters, and format conversion. No system dependencies required.

Tip. If your image lives on a model declared with uploader(...), you don't have to write a transform pipeline by hand — the auto-routed GET /<resource>/:id/<field> endpoint accepts query params (w, h, thumb, square, crop, fit=cover|contain, flipx, flipy, rot=90/180/270, blur, bright, contrast, hue, gray, invert, fmt, q) and runs them through the Image class for you. See Image transforms via URL.

Supported Formats

JPEG PNG GIF WebP BMP TIFF ICO

All methods return a new Image instance, enabling fluent chaining.

Loading Images

Image.new(path)

Loads an image from a file path. The format is detected automatically.

Parameters

path : String - Path to the image file

Returns

Image instance

img = Image.new("photo.jpg")
println(img.width)   # 1920
println(img.height)  # 1080
Image.from_buffer(base64_string)

Loads an image from a base64-encoded string. Useful for processing images from HTTP requests, S3, or databases.

Parameters

base64_string : String - Base64-encoded image data

Returns

Image instance

img = Image.from_buffer(base64_data)
println(img.width)

Properties

img.width / img.height

Returns the image dimensions in pixels.

Returns

Int

img = Image.new("photo.jpg")
println("Size: " + str(img.width) + "x" + str(img.height))

Resizing

img.resize(width, height)

Resizes the image to the specified dimensions using high-quality Lanczos3 filtering.

Parameters

width : Int - Target width in pixels
height : Int - Target height in pixels

Returns

New Image instance

resized = Image.new("photo.jpg").resize(800, 600)
resized.to_file("photo_resized.jpg")
img.thumbnail(max_size)

Creates a thumbnail that fits within a square of the given size, preserving the original aspect ratio.

Parameters

max_size : Int - Maximum width or height in pixels

Returns

New Image instance

thumb = Image.new("photo.jpg").thumbnail(200)
thumb.to_file("thumb.jpg")
img.crop(x, y, width, height)

Crops a rectangular region from the image. Coordinates must be non-negative.

Parameters

x : Int - Left offset (>= 0)
y : Int - Top offset (>= 0)
width : Int - Crop width
height : Int - Crop height

Returns

New Image instance

cropped = Image.new("photo.jpg").crop(100, 50, 400, 300)
cropped.to_file("cropped.jpg")

Transforms

img.grayscale()

Convert to grayscale.

img.invert

Invert all colors.

img.flip_horizontal()

Flip horizontally (mirror).

img.flip_vertical()

Flip vertically.

img.rotate90()

Rotate 90° clockwise.

img.rotate180()

Rotate 180°.

img.rotate270()

Rotate 270° clockwise (90° counter-clockwise).

All transform methods return a new Image and can be chained:

img = Image.new("photo.jpg")
  .grayscale()
  .flip_horizontal()
  .rotate90()
img.to_file("transformed.jpg")

Adjustments

img.blur(sigma)

Applies a Gaussian blur. Higher sigma = more blur.

Parameters

sigma : Float or Int - Blur intensity
blurred = Image.new("photo.jpg").blur(3.5)
img.brightness(value)

Adjusts image brightness. Positive values brighten, negative values darken.

Parameters

value : Int - Brightness adjustment
img.contrast(value)

Adjusts image contrast. Positive increases contrast, negative decreases.

Parameters

value : Float or Int - Contrast adjustment
img.hue_rotate(degrees)

Rotates the hue of all pixels by the given number of degrees.

Parameters

degrees : Int - Hue rotation in degrees
adjusted = Image.new("photo.jpg")
  .brightness(20)
  .contrast(1.5)
  .hue_rotate(90)
adjusted.to_file("adjusted.jpg")

Output Settings

img.quality(n)

Sets the output quality for JPEG encoding. Default is 85.

Parameters

n : Int - Quality from 1 (smallest) to 100 (best)
img.format(fmt)

Sets the output format explicitly.

Parameters

fmt : String - One of: "jpeg", "png", "gif", "bmp", "ico", "tiff", "webp"
# Convert PNG to JPEG at 70% quality
img = Image.new("photo.png")
  .format("jpeg")
  .quality(70)
img.to_file("photo.jpg")

Saving & Exporting

img.to_file(path)

Saves the image to a file. Format is determined by: .format() setting, then file extension, then PNG fallback.

Parameters

path : String - Output file path

Returns

Boolean - true on success

Image.new("photo.jpg").thumbnail(200).to_file("thumb.jpg")
img.to_buffer()

Encodes the image to a base64 string. Useful for storing in databases, HTTP responses, or passing to S3.

Returns

String - Base64-encoded image data

img = Image.new("photo.jpg").thumbnail(100)
base64_data = img.to_buffer()

# Store in S3
S3.put_object("my-bucket", "thumb.jpg", base64_data, {
  "content_type": "image/jpeg"
})

Parallel Processing

Image transforms are CPU-bound. To process several images concurrently, build a plan with Image.plan(path), chain the same transform methods you would on a regular image, and pass an array of plans to Image.process_all(...). Each plan runs on its own thread; the call returns when every plan finishes.

A plan is lazy — methods just record operations. Nothing decodes, transforms, or writes until you call plan.run() (one plan, current thread) or Image.process_all([...]) (many plans, in parallel).

Image.plan(path)

Creates a new lazy plan that will read its source from path when executed.

Parameters

path : String - Path to the image file (read at execution time)

Returns

ImagePlan instance

Plan instance methods

A plan supports the same transform methods as Image:

resize(w, h), thumbnail(size), crop(x, y, w, h), grayscale(), flip_horizontal(), flip_vertical(), rotate90(), rotate180(), rotate270(), blur(sigma), brightness(n), contrast(n), invert(), hue_rotate(degrees), format(fmt), quality(n).

Each call returns a new ImagePlan instance with the operation appended.

Plan-only methods

  • save_to(path) — terminal; the plan will save to path when executed.
  • run() — executes the plan synchronously on the current thread. Returns true if save_to was set, otherwise an Image instance.
  • src() — returns the source path.
  • ops_count() — returns the number of recorded operations.
Image.process_all(plans)

Runs an array of plans in parallel (one OS thread per plan) and returns an array of results in the same order.

Parameters

plans : Array<ImagePlan> - Plans to execute concurrently

Returns

Array, where each entry is:

  • true if the plan had save_to(path) and the file was written
  • An Image instance if the plan had no save_to
  • A hash {"error": "..."} if that plan failed (other plans still complete)
# Generate three thumbnail variants in parallel
results = Image.process_all([
  Image.plan("uploads/raw.jpg").thumbnail(800).save_to("public/large.jpg"),
  Image.plan("uploads/raw.jpg").thumbnail(400).save_to("public/medium.jpg"),
  Image.plan("uploads/raw.jpg").thumbnail(100).save_to("public/small.jpg"),
])

# results == [true, true, true] on success

Process many files

let plans = files.map(fn(f) {
  Image.plan(f).resize(1200, 900).quality(80).save_to("processed/" + basename(f))
})
let results = Image.process_all(plans)

# Inspect failures
for r in results
  println("error: " + r.error) if r.error != null
end

Collect transformed images without saving

let images = Image.process_all([
  Image.plan("a.jpg").grayscale(),
  Image.plan("b.jpg").rotate90().resize(200, 200),
])
println(images[0].width)
let buf = images[1].to_buffer()
A single chain on Image.new(...) is fully synchronous and runs on one thread. Use Image.plan(...) + Image.process_all([...]) only when you have multiple images and want them to run concurrently.

Complete Examples

Thumbnail generation in a controller

fn upload
  file = req.files["avatar"]
  # `file["data"]` is a base64-encoded string. Image.from_buffer expects
  # base64, so pass it through directly. Use `file["size"]` for the raw
  # byte count instead of `file["data"].length`.
  img = Image.from_buffer(file["data"])

  # Create multiple sizes
  large = img.resize(800, 800)
  medium = img.thumbnail(400)
  small = img.thumbnail(100)

  large.to_file("public/uploads/avatar_large.jpg")
  medium.to_file("public/uploads/avatar_medium.jpg")
  small.to_file("public/uploads/avatar_small.jpg")

  return { "status": 200, "body": "Upload complete" }
end

Image processing pipeline

img = Image.new("raw_photo.jpg")
  .resize(1200, 900)
  .brightness(10)
  .contrast(1.2)
  .quality(85)

img.to_file("processed.jpg")

# Also create a grayscale thumbnail
img.grayscale().thumbnail(200).to_file("thumb_gray.jpg")

Format conversion with S3

# Convert all PNGs to WebP
files = S3.list_objects("images", "photos/")
for file in files
  if file.ends_with(".png")
    data = S3.get_object("images", file)
    img = Image.from_buffer(data)
      .format("webp")
      .quality(80)
    new_key = file.replace(".png", ".webp")
    S3.put_object("images", new_key, img.to_buffer(), {
      "content_type": "image/webp"
    })
  end
end