BoxLang πŸš€ A New JVM Dynamic Language Learn More...

BoxLang Fluent Image Library

v1.7.0+21 BoxLang Modules

⚑︎ BoxLang Module: BoxLang Image Library

|:------------------------------------------------------:  |
| ⚑︎ B o x L a n g ⚑︎
| Dynamic : Modular : Productive
|:------------------------------------------------------:  |
Copyright Since 2023 by Ortus Solutions, Corp
www.boxlang.io | www.ortussolutions.com

Β 

Introduction

The BoxLang Image module provides comprehensive image manipulation functionality with a fluent, chainable API. Read, create, transform, filter, and save images with ease. This module brings CFML-compatible image functions to BoxLang while adding modern enhancements and conveniences.

Key Features:

  • πŸ“Έ Read/write images from files, URLs, or Base64
  • πŸ–ΌοΈ Broad format support: PNG, JPEG, WebP, GIF, BMP, TIFF
  • βœ‚οΈ Crop, resize, rotate, flip, scale, split, and shear images
  • 🎨 Draw shapes, text, lines, bezier curves, and points
  • 🎭 Apply filters: blur, sharpen, grayscale, negative
  • πŸ€– Generate CAPTCHA images with configurable difficulty
  • πŸ“Š Extract EXIF and IPTC metadata
  • ⚑ Fluent API with method chaining
  • πŸ”§ 50+ built-in functions and member methods

πŸ“– Full Documentation

Why BoxLang Image?

BoxLang Image brings a fluent, modern API to image manipulation. Unlike traditional tag-based or function-based approaches, BoxLang Image emphasizes method chaining for readable, maintainable code:

// ✨ The Fluent Way (Recommended)
imageRead("photo.jpg")
    .scaleToFit(800, 600)
    .blur(2)
    .sharpen(1)
    .grayScale()
    .write();  // Saves back to original file

// πŸ“¦ Traditional BIF Approach
img = imageRead("photo.jpg");
imageResize(img, 800, 600);
imageBlur(img, 2);
imageSharpen(img, 1);
imageGrayScale(img);
imageWrite(img, "photo.jpg");

// 🏷️ Component Approach
<bx:image action="read" source="photo.jpg" name="img" />
<bx:image action="resize" source="#img#" width="800" height="600" />
<bx:image action="write" source="#img#" destination="photo.jpg" />

The fluent API provides:

  • πŸ”— Method chaining - Write transformations in a natural, sequential flow
  • 🎯 Less boilerplate - No need to repeat variable names
  • πŸ“– Better readability - Code reads like a pipeline of transformations
  • ⚑ Immediate feedback - Each method returns the image for further manipulation

Quick Start

Installation

Install via CommandBox:

# Install the module
install bx-image

# Or add to box.json
box install bx-image --save

Basic Usage

// Read and manipulate an image
img = imageRead("photo.jpg");
img.scaleToFit(800, 600)
   .blur(2)
   .sharpen(1)
	.write("photo-optimized.jpg");

// Create a new image with drawing
canvas = imageNew("", 400, 300, "rgb", "white");
canvas.setDrawingColor("blue")
      .drawRect(50, 50, 300, 200, true)
      .setDrawingColor("red")
      .drawText("Hello BoxLang!", 150, 150)
	  .write("greeting.png");

// Chain operations
imageRead("logo.png")
    .crop(10, 10, 200, 200)
    .grayScale()
    .rotate(45)
    .write("logo-transformed.png");

Quick Reference

Fluent BoxImage API

The BoxImage class provides a fluent interface where most methods return this for chaining:

Image Creation & I/O

img = imageRead("path/to/file.jpg")          // Load from file
img = imageRead("https://example.com/img")   // Load from URL
img = imageReadBase64(base64String)          // Load from Base64
img = imageNew("", 800, 600, "rgb", "white") // Create blank canvas

img.write("output.png")                      // Write to specified file (format from extension)
img.write()                                  // Write back to original source file
base64 = imageWriteBase64(img)               // Export as Base64
base64 = imageWriteBase64(img, "jpg")        // Export as Base64 with format
blob = imageGetBlob(img)                     // Export as binary
imageWriteToBrowser(img)                     // Stream directly to HTTP response
formats = GetReadableImageFormats()          // List readable formats
formats = GetWriteableImageFormats()         // List writable formats

Note: The write() and ImageWrite() methods auto-detect the output format from the file extension (e.g., .png, .jpg, .webp, .gif, .bmp, .tiff). For formats that don't support transparency (JPEG, BMP), images with alpha channels are automatically composited onto a white background.

Transformations

img.resize(width, height)                    // Exact dimensions
img.resize(width, height, interpolation)     // With interpolation method
img.scaleToFit(size)                         // Fit to width, maintain aspect ratio
img.scaleToFit(width, height)                // Fit within rectangular bounds
img.scaleToFit(width, height, interpolation) // With custom interpolation
img.crop(x, y, width, height)                // Extract region
img.rotate(angle)                            // Rotate degrees
img.flip("horizontal")                       // Flip horizontal
img.flip("vertical")                         // Flip vertical
img.flip("diagonal")                         // Flip along main diagonal (transpose)
img.flip("antidiagonal")                     // Flip along anti-diagonal
img.flip("90")                               // Rotate 90Β° clockwise
img.flip("180")                              // Rotate 180Β°
img.flip("270")                              // Rotate 270Β° clockwise (90Β° CCW)
img.shear(shearX, shearY)                    // Shear transform
img.rotateDrawingAxis(angle)                 // Rotate drawing axis
img.translateDrawingAxis(x, y)               // Translate drawing axis
img.shearDrawingAxis(shearX, shearY)         // Shear drawing axis

Filters & Effects

img.blur(radius)                             // Apply Gaussian blur
img.sharpen(gain)                            // Sharpen image
img.grayScale()                              // Convert to grayscale
img.negative()                               // Invert colors
img.addBorder(thickness, color)              // Add colored border

Drawing Setup

img.setDrawingColor("red")                   // Set foreground color
img.setDrawingColor("#FF0000")               // Use hex colors
img.setBackgroundColor("white")              // Set background
img.setAntiAliasing(true)                    // Enable anti-aliasing
img.setDrawingTransparency(50)               // Set transparency (0-100)
img.setDrawingStroke({                       // Set stroke properties
    width: 2.0,                              // Stroke width in pixels
    endCaps: "round",                        // "butt", "round", or "square"
    lineJoins: "miter",                      // "miter", "round", or "bevel"
    miterLimit: 10.0,                        // Miter limit for mitered joins
    dashArray: [10, 5],                      // Dash pattern (on, off, on, off...)
    dashPhase: 0                             // Offset to start dash pattern
})

Drawing Shapes

img.drawRect(x, y, width, height, filled)    // Rectangle
img.drawRoundRect(x, y, w, h, aw, ah, fill)  // Rounded rectangle
img.drawBeveledRect(x, y, w, h, raised, fill)// Beveled rectangle
img.drawOval(x, y, width, height, filled)    // Oval/circle
img.drawArc(x, y, w, h, start, arc, filled)  // Arc segment
img.drawLine(x1, y1, x2, y2)                 // Straight line
img.drawLines(pointArray, isPolygon, filled) // Multiple lines/polygon
img.drawPoint(x, y)                          // Single pixel
img.drawCubicCurve(x1, y1, cx1, cy1, ...)    // Bezier curve
img.drawQuadraticCurve(x1, y1, cx, cy, ...)  // Quadratic curve
img.clearRect(x, y, width, height)           // Clear region

Drawing Text

img.drawText(text, x, y)                     // Draw text at position
img.drawText(text, x, y, attributes)         // With font attributes
// attributes: { font, size, style, alpha, underline, strikethrough }

Image Composition

img.overlay(topImage)                        // Overlay another image
img.paste(source, x, y)                      // Paste at position
img.copy(x, y, width, height)                // Copy region to new image

Image Splitting

// Split into a grid of tiles (returns array of arrays)
tiles = img.splitGrid(columns, rows)          // 2D array: tiles[row][col]
tiles = ImageSplitGrid(img, columns, rows)    // BIF equivalent

// Example: split a 600Γ—400 image into 3Γ—2 grid (6 tiles, each 200Γ—200)
grid = ImageRead("panorama.jpg").splitGrid(3, 2);
// grid[1][1] = top-left, grid[1][2] = top-center, etc.
eachTile = grid[2][1];  // second row, first column

Image Information

width = img.getWidth()                       // Get width in pixels
height = img.getHeight()                     // Get height in pixels
info = imageInfo(img)                        // Full image info struct
exif = imageGetExifMetadata(img)             // EXIF metadata
tag = imageGetExifTag(img, tagName)          // Specific EXIF tag
iptc = imageGetIPTCMetadata(img)             // IPTC metadata
tag = imageGetIPTCTag(img, tagName)          // Specific IPTC tag
buffered = imageGetBufferedImage(img)        // Java BufferedImage

Creating Images

// From file
img = imageRead("path/to/image.jpg");

// From URL
img = imageRead("https://example.com/image.png");

// From Base64
img = imageReadBase64(base64String);

// New blank canvas
img = imageNew("", 800, 600, "rgb", "white");

CAPTCHA Generation

// Basic CAPTCHA (args: height, width, text)
captcha = ImageGenerateCaptcha( 75, 200, "A3X9K2" );
ImageWrite( captcha, "/path/to/captcha.png" );

// With difficulty and fonts (ColdFusion-compatible arg order)
captcha = ImageGenerateCaptcha( 35, 400, "loner" );
captcha = ImageGenerateCaptcha( 35, 400, "loner", "high" );
captcha = ImageGenerateCaptcha( 35, 400, "loner", "high", "serif,sansserif", 24 );

// Via component β€” auto-streams to browser when no name/destination is given
<bx:image action="captcha" text="A3X9K2" width="200" height="75" difficulty="medium" />

// Store in variable for further processing
<bx:image action="captcha" text="A3X9K2" width="200" height="75" name="captchaImg" />

// Write directly to file
<bx:image action="captcha" text="A3X9K2" destination="/path/to/captcha.png" />

Difficulty levels:

  • low β€” minimal character rotation, clean background
  • medium β€” moderate rotation, noise dots, crossing lines
  • high β€” heavy rotation, dense noise, wavy lines, random character colors

Member Functions vs BIFs

Most functions work both ways:

// As BIF (Built-In Function)
imageBlur(img, 5);
imageCrop(img, 10, 10, 200, 200);

// As member function (chainable!)
img.blur(5)
   .crop(10, 10, 200, 200)
   .write();

BIFs

This module contributes the following BIFs:

Most of these BIFs are also implemented as member functions on the BoxImage type, so imageGrayScale( myImage ) can also be written as myImage.grayScale().

Components

This module provides the <bx:image> component for tag-based image manipulation.

Quick Reference

The <bx:image> component supports the following actions:

// Read an image
<bx:image action="read" source="photo.jpg" name="myImage" />

// Resize
<bx:image action="resize" source="#myImage#" width="800" height="600" />

// Rotate
<bx:image action="rotate" source="#myImage#" angle="45" destination="rotated.jpg" />

// Add border
<bx:image action="border" source="#myImage#" color="black" thickness="5" />

// Write to file
<bx:image action="write" source="#myImage#" destination="output.jpg" />

// Get image info
<bx:image action="info" source="#myImage#" structName="imageInfo" />

// Write to browser
<bx:image action="writeToBrowser" source="#myImage#" />

Supported Actions:

  • read - Load an image from file or URL
  • write - Save image to file
  • writeToBrowser - Stream image to HTTP response (supports format attribute)
  • resize - Change image dimensions
  • rotate - Rotate image by angle
  • border - Add border around image
  • info - Get image metadata
  • convert - Convert image format
  • captcha - Generate a CAPTCHA image (v1.6.0+)

Common Attributes:

  • source - Path to image file, URL, or BoxImage variable
  • name - Variable name to store the image
  • destination - Output file path for write operations
  • width, height - Dimensions for resize
  • angle - Rotation angle in degrees
  • color - Color name or hex code
  • thickness - Border thickness in pixels
  • overwrite - Boolean, allow overwriting existing files (default: false)
  • isBase64 - Boolean, indicates if source is Base64-encoded

πŸ’‘ Tip: For complex image manipulation workflows, consider using the fluent API instead of components for better readability and maintainability.

Important Notes

File Handling

The module properly manages file handles when reading images. After loading an image with imageRead() or imageNew(), the underlying file stream is automatically closed, allowing you to safely delete or move the source file:

// This works correctly - file can be deleted after reading
img = imageRead("photo.jpg");
img.resize(800, 600).write("photo-resized.jpg");
fileDelete("photo.jpg");  // βœ… Works - no file lock

This is especially important on Windows, where file locks can prevent file operations.

Directory Creation

When writing images, parent directories are automatically created if they don't exist:

// Creates 'output/thumbs/' directory if needed
img.write("output/thumbs/photo.jpg");

Format Detection

The write() and ImageWrite() methods now auto-detect the output format from the file extension rather than defaulting to PNG. For example:

// Correctly writes JPEG, WebP, GIF, BMP, or TIFF based on extension
img.write("photo.jpg");   // β†’ JPEG
img.write("photo.webp");  // β†’ WebP
img.write("photo.gif");   // β†’ GIF
img.write("photo.bmp");   // β†’ BMP
img.write("photo.tiff");  // β†’ TIFF

Alpha Channel Handling

When writing to formats that do not support transparency (JPEG, BMP), images with alpha channels are automatically composited onto a white background. This prevents write failures and produces visually correct output.

Examples

Blur, crop, and grayscale a png image before saving it back to disk:

var updatedLogo = ImageRead( "src/test/resources/logo.png" )
    .blur( 5 )
    .crop( x = 50, y = 50, width = 150, height = 100 )
    .grayScale();
imageWrite( updatedLogo, "src/test/resources/logoNew.png" );

Ortus Sponsors

BoxLang is a professional open-source project and it is completely funded by the community and Ortus Solutions, Corp. Ortus Patreons get many benefits like a cfcasts account, a FORGEBOX Pro account and so much more. If you are interested in becoming a sponsor, please visit our patronage page: https://patreon.com/ortussolutions

THE DAILY BREAD

"I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" Jn 14:1-12

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.


Unreleased

1.7.0 - 2026-06-01

Added

  • WebP image format support for reading and writing via the org.sejda.imageio:webp-imageio ImageIO plugin. All BIFs that read or write images (ImageRead, ImageWrite, ImageWriteBase64, ImageReadBase64, IsImageFile) now handle WebP natively.
  • GIF image format support for reading and writing. Java's built-in ImageIO GIF codec is now fully exposed through ImageRead, ImageWrite, and ImageWriteBase64.
  • BMP image format support for reading and writing, with automatic ARGBβ†’RGB conversion on write (BMP does not support alpha channels).
  • TIFF image format support for reading and writing via Java 9+ built-in ImageIO plugin.
  • GetReadableImageFormats() and GetWriteableImageFormats() now include webp, gif, bmp, and tiff in their results.

Fixed

  • ImageWrite and img.write(path) were hardcoded to always produce PNG data regardless of the destination file extension. Writing to .jpg, .webp, or any non-PNG path now correctly encodes in the target format.
  • Writing an image with an alpha channel to JPEG or BMP no longer fails β€” the image is automatically composited onto a white background before encoding, since these formats do not support transparency.

Updated

  • Replaced org.apache.commons.imaging.Imaging.writeImage() with javax.imageio.ImageIO.write() as the underlying write mechanism, enabling format detection from the file extension and support for any registered ImageIO plugin (JPG, PNG, WebP, GIF, BMP, TIFF).
  • Added com.twelvemonkeys.imageio:imageio-webp as a pure-Java WebP reader, ensuring WebP reading works cross-platform including macOS ARM64 (Apple Silicon). WebP writing still requires the native webp-imageio library which supports Linux and Intel macOS; on unsupported platforms a clear BoxRuntimeException is thrown instead of a raw UnsatisfiedLinkError.

1.6.0 - 2026-05-25

Added

  • ImageGenerateCaptcha( height, width, text [, difficulty [, fonts [, fontSize]]] ) BIF for generating CAPTCHA images with configurable dimensions, font size, difficulty level (low/medium/high), and font list. Argument order is ColdFusion-compatible.
  • <bx:image action="captcha"> component support with text, width, height, fontSize, difficulty, fonts, destination, overwrite, and name attributes. When neither name nor destination is specified, the image is automatically streamed to the browser.

Fixed

  • javaxt.com has been down for weeks, moving to single compiled jar and looking for alternatives.
  • ModuleConfig.bx version was not dynamic.

1.5.0 - 2026-02-18

  • BLMODULES-139 Update writeToBrowser to accept format attribute
  • BLMODULES-138 Improve base64 generation and auto detect format

1.4.0 - 2025-11-12

Added

  • Updated all GitHub actions to latest according to templates
  • Updated templates to latest module template
  • Bump javaxt:javaxt-core from 2.1.9 to 2.1.11
  • Generate AI Instructions
  • Dependabot updates
  • Updated Gradle wrapper to 8.14.1
  • Updated gradle build to latest module template
  • Added documentation to classes

Changed

  • All tests to inherit from BaseIntegrationTest for consistency
  • Refactored internal classes into functional packaging
  • Rewrote the ImageDrawTextTest to use more reliable image size assertions and work on all Operating Systems

Fixed

  • Resource leak when reading images into the input stream and not closing it.
  • Added jaxt library to gradle dependencies
  • Fixed writing of images to directories that don't exist. Now creates parent directories as needed.
  • image.scaleToFit() now works with a single value and more.
  • write() now works with no provided path, uses internally read source path.

1.3.2 - 2025-07-25

Changed

  • Removed logging from ImageService startup/shutdown

1.3.1 - 2025-07-24

Changed

  • Version bump maintenance release

1.3.0 - 2025-07-23

Fixed

  • BL-1216 Fix ImageScaleToFit BIF
  • BL-1217 Fix invoking resize as a member function

1.1.0 - 2025-02-13

1.0.1 - 2024-06-27

1.0.0 => 2024-APR-05

  • First iteration of this module

$ box install bx-image

No collaborators yet.
     
  • {{ getFullDate("2024-05-15T00:58:48Z") }}
  • {{ getFullDate("2026-06-01T08:47:39Z") }}
  • 3,164
  • 23,503