BoxLang 🚀 A New JVM Dynamic Language Learn More...

BoxLang Fluent Image Library

v1.4.0+15 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
  • ✂️ Crop, resize, rotate, flip, and scale images
  • 🎨 Draw shapes, text, lines, and curves
  • 🎭 Apply filters: blur, sharpen, grayscale, negative
  • 📊 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
img.write()                                  // Write back to original source file
base64 = imageWriteBase64(img)               // Export as Base64
blob = imageGetBlob(img)                     // Export as binary

Note: The parameterless write() method saves the image back to its original source path. This is useful when you want to modify an image in place. For images created from scratch (not loaded from a file), you must provide a path.

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 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");

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
  • resize - Change image dimensions
  • rotate - Rotate image by angle
  • border - Add border around image
  • info - Get image metadata
  • convert - Convert image format

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");

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.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-12-30

1.0.1 - 2024-12-30

1.0.1 - 2024-12-30

1.0.1 - 2024-06-27

1.0.0 - 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("2025-11-12T14:56:03Z") }}
  • 1,939
  • 11,848