Blog

Luis Majano

June 01, 2026

Spread the word


Share your thoughts

The BoxLang image module just landed two back-to-back releases that make it significantly more capable. 1.6.0 brought CAPTCHA generation. 1.7.0 adds four new image formats, fixes a silent write bug that has been producing PNG files regardless of what extension you asked for, and adds proper alpha channel handling for formats that don't support transparency. Let's dig in. πŸš€

What's New at a Glance

1.6.01.7.0
CAPTCHA generationβœ…βœ…
WebP read/write βœ…
GIF read/write βœ…
BMP read/write βœ…
TIFF read/write βœ…
Extension-based format detection βœ…
Alpha-channel compositing for JPEG/BMP βœ…

πŸ–ΌοΈ Four New Formats: WebP, GIF, BMP & TIFF

WebP

WebP is the dominant modern format for web delivery - smaller files, better quality, browser-native. bx-image 1.7.0 adds full read/write support via the ImageIO plugin.

// Resize and convert to WebP in one fluent chain
imageRead( "hero.png" )
    .scaleToFit( 1200, 630 )
    .sharpen( 1 )
    .write( "hero.webp" );
// Read a WebP from a URL, watermark it, save as WebP
imageRead( "https://cdn.example.com/photo.webp" )
    .setDrawingColor( "white" )
    .setDrawingTransparency( 40 )
    .drawText( "Β© 2026 My Company", 20, 30, { font: "Arial", size: 18, style: "bold" } )
    .write( "watermarked.webp" );

GIF

Java's built-in ImageIO GIF codec is now fully exposed. Read animated GIFs, output GIF thumbnails - no extra library needed.

// Create an optimized GIF thumbnail from a photo
imageRead( "photo.jpg" )
    .scaleToFit( 200, 200 )
    .write( "thumbnail.gif" );

BMP

BMP support comes with automatic ARGB-to-RGB conversion on write, since the BMP format has no alpha channel concept.

imageRead( "logo.png" )
    .resize( 300, 300 )
    .write( "logo.bmp" );  // Alpha composited onto white automatically

TIFF

TIFF support arrives via Java 9+'s built-in ImageIO plugin - lossless, widely used in print and archival workflows.

imageRead( "scan.jpg" )
    .grayScale()
    .write( "scan-archive.tiff" );

πŸ› The Write Bug Fix

Before 1.7.0, ImageWrite and img.write( path ) were hardcoded to produce PNG data regardless of the destination file extension. So calling .write( "output.jpg" ) was quietly writing a PNG with a .jpg extension - a valid image, but the wrong format with the wrong byte footprint.

Before (broken behavior):

// This produced a PNG file, not a JPEG
imageRead( "input.jpg" )
    .scaleToFit( 800, 600 )
    .write( "output.jpg" );  // πŸ› Actually wrote PNG bytes

After (fixed in 1.7.0):

// Each extension now correctly encodes in the target format
imageRead( "input.jpg" ).scaleToFit( 800, 600 ).write( "output.jpg" );   // JPEG βœ…
imageRead( "input.jpg" ).scaleToFit( 800, 600 ).write( "output.webp" );  // WebP βœ…
imageRead( "input.jpg" ).scaleToFit( 800, 600 ).write( "output.gif" );   // GIF  βœ…
imageRead( "input.jpg" ).scaleToFit( 800, 600 ).write( "output.bmp" );   // BMP  βœ…
imageRead( "input.jpg" ).scaleToFit( 800, 600 ).write( "output.tiff" );  // TIFF βœ…
imageRead( "input.jpg" ).scaleToFit( 800, 600 ).write( "output.png" );   // PNG  βœ…

Format detection is automatic from the file extension. No configuration, no extra arguments.


🎭 Alpha Channel Compositing for JPEG & BMP

JPEG and BMP have no alpha channel. Before 1.7.0, writing a PNG with transparency to either format would fail or produce garbled output. Now, if an image carries an alpha channel and you write it to JPEG or BMP, the module automatically composites it onto a white background first.

// logo.png has transparency - now writes a clean JPEG without errors
imageRead( "logo.png" )
    .resize( 400, 400 )
    .write( "logo.jpg" );  // Alpha composited onto white βœ…

This means you can safely pipeline PNG logos and other transparent assets into JPEG workflows without any pre-processing step.


πŸ” Updated Format Discovery

GetReadableImageFormats() and GetWriteableImageFormats() now reflect the full expanded list including webp, gif, bmp, and tiff. Useful if you're building dynamic format selection in your apps:

// Check what's available at runtime
readable = GetReadableImageFormats();
writable = GetWriteableImageFormats();

writeDump( readable );  // [ "png", "jpg", "jpeg", "webp", "gif", "bmp", "tiff", "wbmp" ]
writeDump( writable );  // [ "png", "jpg", "jpeg", "webp", "gif", "bmp", "tiff", "wbmp" ]

πŸ€– CAPTCHA Generation (Since 1.6.0)

If you missed the 1.6.0 release, it shipped ImageGenerateCaptcha() - a full CAPTCHA generator with configurable difficulty, font size, and font families. The argument order matches ColdFusion for easy migration.

// Basic CAPTCHA - height, width, text
captcha = ImageGenerateCaptcha( 75, 200, "X9K2A3" );
imageWrite( captcha, "/path/to/captcha.png" );

// High difficulty with custom fonts
captcha = ImageGenerateCaptcha( 35, 400, "loner", "high", "serif,sansserif", 24 );
imageWrite( captcha, "/path/to/captcha.png" );

Difficulty levels control how distorted the output gets:

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

The <bx:image> component supports it too, and will auto-stream to the browser when neither name nor destination is specified:

{{-- Auto-streams directly to browser --}}
<bx:image action="captcha" text="X9K2A3" width="200" height="75" difficulty="medium" />

{{-- Store in a variable for further processing --}}
<bx:image action="captcha" text="X9K2A3" width="200" height="75" name="myCaptcha" />

{{-- Write to file --}}
<bx:image action="captcha" text="X9K2A3" destination="/path/to/captcha.png" overwrite="true" />

πŸ“¦ Install / Upgrade

# CommandBox
box install bx-image

# BoxLang OS install
install-bx-module bx-image

No breaking changes. Drop-in upgrade from any prior 1.x version.


πŸ“š Resources

Add Your Comment

Recent Entries

MatchBox and WebAssembly: Running BoxLang in the Browser and at the Edge

MatchBox and WebAssembly: Running BoxLang in the Browser and at the Edge

The MatchBox open beta is live at https://boxlang.ortusbooks.com/boxlang-framework/matchbox, and it brings something genuinely new to the BoxLang ecosystem: a path into WebAssembly.

That means BoxLang code can now move into browser applications, static-site deployments, edge runtimes, and WASI-style containers - without requiring a JVM. The feature is still beta, but the core direction is already useful: write BoxLang, compile it with MatchBox, and ship the generated WASM artifact to wherever a small portable runtime makes sense.

Jacob Beers
Jacob Beers
June 04, 2026
One Language, Every Runtime: BoxLang Expands Beyond the Server

One Language, Every Runtime: BoxLang Expands Beyond the Server

Discover how BoxLang’s multi-runtime architecture helps developers build beyond the server with support for serverless functions, desktop applications, CI/CD workflows, Java integrations, containers, runtime management, and more.

Maria Jose Herrera
Maria Jose Herrera
June 04, 2026
BoxLang 1.14.0 : Introducing Inner Classes

BoxLang 1.14.0 : Introducing Inner Classes

BoxLang has always embraced a simple truth: the way you organize code shapes the way you think about problems. For a long time, if you needed a helper class, you needed a file. One class, one .bx file, no exceptions. That's clean and predictable, but it creates real friction when a class is tightly coupled to exactly one caller and has no business existing anywhere else.

Luis Majano
Luis Majano
June 03, 2026