BoxLang π A New JVM Dynamic Language Learn More...
|:------------------------------------------------------: |
| β‘οΈ B o x L a n g β‘οΈ
| Dynamic : Modular : Productive
|:------------------------------------------------------: |
Copyright Since 2023 by Ortus Solutions, Corp
www.boxlang.io | www.ortussolutions.com
Β
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:
π Full Documentation
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:
Install via CommandBox:
# Install the module
install bx-image
# Or add to box.json
box install bx-image --save
// 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");
The BoxImage class provides a fluent interface where
most methods return this for chaining:
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.
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
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
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
})
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
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 }
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
// 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
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
// 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");
// 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 backgroundmedium β moderate rotation, noise dots, crossing lineshigh β heavy rotation, dense noise, wavy lines, random
character colorsMost 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();
This module contributes the following BIFs:
height,
width, text [, difficulty [, fonts [, fontSize]]]
ImageGreyScale()
for you britsimagePaste()
tiles[row][col]
Most of these BIFs are also implemented as member functions on the
BoxImage type, so imageGrayScale( myImage )
can also be written as myImage.grayScale().
This module provides the
<bx:image>
component for tag-based image manipulation.
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 URLwrite - Save image to filewriteToBrowser - Stream image to HTTP response
(supports format attribute)resize - Change image dimensionsrotate - Rotate image by angleborder - Add border around imageinfo - Get image metadataconvert - Convert image formatcaptcha - Generate a CAPTCHA image (v1.6.0+)Common Attributes:
source - Path to image file, URL, or BoxImage variablename - Variable name to store the imagedestination - Output file path for write operationswidth, height - Dimensions for resizeangle - Rotation angle in degreescolor - Color name or hex codethickness - Border thickness in pixelsoverwrite - 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.
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.
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");
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
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.
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" );
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
"I am the way, and the truth, and the life; no one comes to the Father, but by me (JESUS)" Jn 14:1-12
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.
org.sejda.imageio:webp-imageio ImageIO plugin. All BIFs that read or write images (ImageRead, ImageWrite, ImageWriteBase64, ImageReadBase64, IsImageFile) now handle WebP natively.ImageRead, ImageWrite, and ImageWriteBase64.GetReadableImageFormats() and GetWriteableImageFormats() now include webp, gif, bmp, and tiff in their results.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.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).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.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.ModuleConfig.bx version was not dynamic.
$
box install bx-image