Image Operations
Once images are on your server, you can manipulate them using the Image module. For example, you can resize product photos for consistency, crop user avatars to perfect squares, generate multiple sizes for responsive layouts, or scale screenshots for documentation.
Trongate's Image module provides direct access to PHP's GD library. Every operation follows the same stateful pattern: load an image, manipulate it, then either save it back to disk or output it directly to the browser.
The Stateful Workflow Pattern
Unlike functional APIs where you pass images between methods, Trongate's Image module maintains state. You load an image once, perform multiple operations on it, then decide what to do with the result.
// 1. LOAD - Bring an image into memory
$this->image->load('uploads/product.jpg');
// 2. MANIPULATE - Perform any operations
$this->image->resize_to_width(800);
$this->image->crop(800, 600);
// 3. SAVE or OUTPUT - Persist or display the result
$this->image->save('uploads/product_processed.jpg');
This pattern ensures you always know what image you're working with and prevents accidental operations on the wrong image.
State Management: The Image module holds one image in memory at a time. Each call to load() replaces the current image. Operations like resize() and crop() modify the loaded image directly.
Loading Images
Before you can manipulate an image, you need to load it into memory using included-image-load():
// Load from relative path
$this->image->load('uploads/photo.jpg');
// Load from module storage
$this->image->load('../modules/products/images/widget.jpg');
// Load from database reference
$product = $this->db->get_where($id, 'products');
$this->image->load($product->image_path);
The load() method automatically detects the image format (JPEG, PNG, GIF, WEBP) and prepares it for manipulation.
Important: Loading a new image replaces any previously loaded image. If you need to process multiple images, complete all operations on one before loading the next, or use separate Image module instances.
Resizing Images
Resizing is the most common image operation. Trongate provides three methods for different resizing needs, all of which preserve aspect ratios automatically.
Aspect Ratio Preservation: All resizing methods preserve aspect ratios automatically. Unlike some image libraries that require manual calculations or risk image stretching, Trongate handles proportions for you - a 4:3 image remains 4:3, just larger or smaller. You never need to calculate dimensions yourself.
Resize to Width
Use included-image-resize_to_width() when you know the target width but want the height to adjust proportionally:
// Resize a product image to 800px wide
$this->image->load('../modules/products/images/laptop.jpg');
$this->image->resize_to_width(800);
$this->image->save('../modules/products/images/laptop.jpg');
// Original: 2000 x 1500 (4:3 ratio)
// Result: 800 x 600 (aspect ratio preserved)
Use case: Blog post images, product photos, content images where width matters more than height.
Resize to Height
Use included-image-resize_to_height() when you need a specific height with proportional width:
// Create consistent 400px tall sidebar images
$this->image->load('uploads/banner.jpg');
$this->image->resize_to_height(400);
$this->image->save('uploads/banner_sidebar.jpg');
// Original: 1920 x 1080 (16:9 ratio)
// Result: 711 x 400 (aspect ratio preserved)
Use case: Vertical layouts, sidebar images, thumbnail strips with consistent heights.
Scale by Percentage
Use included-image-scale() when you want to resize proportionally by a percentage:
// Reduce image to 50% of original size
$this->image->load('screenshots/desktop.png');
$this->image->scale(50);
$this->image->save('screenshots/desktop_small.png');
// Original: 1920 x 1080
// Result: 960 x 540
// Increase by 150%
$this->image->scale(150);
// Result: 2880 x 1620
Use case: Creating retina/standard versions, generating preview images, bulk resizing operations.
Performance Tip: Resizing operations are fast but not instant. For user-facing uploads, resize during the upload process using the max_width and max_height configuration options rather than as a separate step.
Cropping Images
Cropping removes portions of an image to focus on specific areas or achieve exact dimensions.
Basic Cropping
Use included-image-crop() to extract a specific region:
// Crop to exact dimensions (centered by default)
$this->image->load('uploads/landscape.jpg');
$this->image->crop(800, 600);
$this->image->save('uploads/landscape_cropped.jpg');
Crop Positioning
Control which part of the image is retained using the optional third parameter:
// Crop from the left (preserve left side)
$this->image->crop(800, 600, 'left');
// Crop from the center (default - preserve middle)
$this->image->crop(800, 600, 'center');
// Crop from the right (preserve right side)
$this->image->crop(800, 600, 'right');
Creating Square Avatars
public function create_avatar($user_id) {
$user = $this->db->get_where($user_id, 'users');
// Load the uploaded profile photo
$this->image->load($user->photo_path);
// Create 200x200 square avatar (centered crop)
$this->image->resize_to_width(200);
$this->image->crop(200, 200, 'center');
// Save as avatar
$avatar_path = '../modules/users/avatars/' . $user_id . '.jpg';
$this->image->save($avatar_path);
// Update user record
$data['avatar_path'] = $avatar_path;
$this->db->update($user_id, $data, 'users');
}
Resize and Crop (Perfect Fitting)
Transparency Support: The Image module preserves transparency for PNG and GIF images automatically through all operations:
- PNG alpha channels remain intact during resizing, cropping, and scaling
- GIF transparency colors are preserved
- When saving to JPEG (which doesn't support transparency), transparent areas become white backgrounds
This happens automatically - no configuration needed. PNGs stay crisp with transparent backgrounds, perfect for logos and overlays.
The included-image-resize_and_crop() method combines resizing and cropping to fill exact dimensions while preserving as much of the original image as possible.
// Fill a 400x300 box perfectly
$this->image->load('uploads/photo.jpg');
$this->image->resize_and_crop(400, 300);
$this->image->save('uploads/photo_thumbnail.jpg');
How It Works
The method intelligently determines whether to resize by width or height first:
// Example 1: Wide image (1200 x 600) into 400 x 300
// 1. Image ratio: 2:1, Target ratio: 4:3
// 2. Resizes to height (400 x 300) first
// 3. Crops excess width
// Result: 400 x 300 (no stretching)
// Example 2: Tall image (600 x 1200) into 400 x 300
// 1. Image ratio: 1:2, Target ratio: 4:3
// 2. Resizes to width (400 x 800) first
// 3. Crops excess height
// Result: 400 x 300 (no stretching)
Use case: Product thumbnails, card layouts, grid galleries where every image must be identical dimensions.
Why Use This? Regular crop() only removes excess pixels. resize_and_crop() first scales the image to ensure the target dimensions are filled, then crops. This prevents blank spaces while maintaining aspect ratios.
Generating Multiple Sizes from One Source
A common pattern is creating several versions of an image for different contexts - hero images, thumbnails, mobile versions. Here's how to do it efficiently:
public function process_product_image($product_id) {
$product = $this->db->get_where($product_id, 'products');
$source_path = $product->original_image_path;
// Define target sizes
$sizes = [
'large' => ['width' => 1200, 'height' => 900],
'medium' => ['width' => 800, 'height' => 600],
'thumbnail' => ['width' => 300, 'height' => 225],
'icon' => ['width' => 64, 'height' => 64]
];
$image_paths = [];
foreach ($sizes as $size_name => $dimensions) {
// Reload original for each size
$this->image->load($source_path);
// Apply appropriate transformation
if ($size_name === 'icon') {
// Icons need perfect squares
$this->image->resize_and_crop(
$dimensions['width'],
$dimensions['height']
);
} else {
// Others just need max width
$this->image->resize_to_width($dimensions['width']);
}
// Save with size suffix
$output_path = str_replace(
'.jpg',
"_{$size_name}.jpg",
$source_path
);
$this->image->save($output_path, 85); // 85% quality
$image_paths[$size_name] = $output_path;
}
// Store all paths in database
$update_data = [
'image_large' => $image_paths['large'],
'image_medium' => $image_paths['medium'],
'image_thumbnail' => $image_paths['thumbnail'],
'image_icon' => $image_paths['icon']
];
$this->db->update($product_id, $update_data, 'products');
return $image_paths;
}
Practical Example: Blog Post Image Processor
public function prepare_blog_images($post_id) {
$post = $this->db->get_where($post_id, 'blog_posts');
// Hero image: 1200px wide
$this->image->load($post->uploaded_image);
$this->image->resize_to_width(1200);
$hero_path = '../modules/blog/images/hero_' . $post_id . '.jpg';
$this->image->save($hero_path, 90);
// Thumbnail: 400x300 perfect fit
$this->image->load($post->uploaded_image);
$this->image->resize_and_crop(400, 300);
$thumb_path = '../modules/blog/images/thumb_' . $post_id . '.jpg';
$this->image->save($thumb_path, 85);
// Mobile: 600px wide
$this->image->load($post->uploaded_image);
$this->image->resize_to_width(600);
$mobile_path = '../modules/blog/images/mobile_' . $post_id . '.jpg';
$this->image->save($mobile_path, 80);
// Update post record
$data = [
'hero_image' => $hero_path,
'thumbnail_image' => $thumb_path,
'mobile_image' => $mobile_path
];
$this->db->update($post_id, $data, 'blog_posts');
}
Getting Image Dimensions
Sometimes you need to know an image's size before deciding how to process it:
$this->image->load('uploads/photo.jpg');
$width = $this->image->get_width(); // e.g., 1920
$height = $this->image->get_height(); // e.g., 1080
// Calculate aspect ratio
$ratio = $width / $height; // e.g., 1.78 (16:9)
// Conditional processing
if ($width > 1920) {
$this->image->resize_to_width(1920);
}
if ($ratio < 1) {
// Portrait orientation
$this->image->resize_to_height(800);
} else {
// Landscape orientation
$this->image->resize_to_width(800);
}
Real-World Pattern: Responsive Image Generator
Here's a complete example that generates all the sizes needed for modern responsive web design:
<?php
class Image_processor extends Trongate {
public function generate_responsive_set($source_path, $base_name) {
// Responsive breakpoints
$breakpoints = [
'xs' => 480, // Mobile
'sm' => 768, // Tablet
'md' => 1024, // Desktop
'lg' => 1440, // Large desktop
'xl' => 1920 // Full HD
];
$output_dir = dirname($source_path);
$generated = [];
foreach ($breakpoints as $size => $width) {
$this->image->load($source_path);
// Only resize if image is larger than breakpoint
if ($this->image->get_width() > $width) {
$this->image->resize_to_width($width);
}
$output_path = "{$output_dir}/{$base_name}_{$size}.jpg";
$this->image->save($output_path, 85);
$generated[$size] = [
'path' => $output_path,
'width' => $this->image->get_width(),
'height' => $this->image->get_height()
];
}
return $generated;
}
public function create_srcset($image_paths) {
$srcset_parts = [];
foreach ($image_paths as $size => $info) {
$url = $this->convert_to_url($info['path']);
$srcset_parts[] = "{$url} {$info['width']}w";
}
return implode(', ', $srcset_parts);
}
private function convert_to_url($path) {
if (strpos($path, '../modules/') === 0) {
$parts = explode('/', $path);
if (count($parts) >= 3) {
$module = $parts[2];
return str_replace(
"../modules/{$module}/",
"{$module}_module/",
$path
);
}
}
return $path;
}
}
Using the Responsive Generator
// Generate all sizes
$processor = new Image_processor();
$sizes = $processor->generate_responsive_set(
'../modules/blog/images/article_original.jpg',
'article_123'
);
// Create srcset attribute
$srcset = $processor->create_srcset($sizes);
// In your view:
// <img src="{$sizes['md']['path']}"
// srcset="{$srcset}"
// sizes="(max-width: 768px) 100vw, 50vw">
Operation Reference
| Method |
Parameters |
Purpose |
| included-image-load() |
string $path |
Load image into memory for manipulation |
| included-image-resize_to_width() |
int $width |
Resize to specific width, height adjusts proportionally |
| included-image-resize_to_height() |
int $height |
Resize to specific height, width adjusts proportionally |
| included-image-scale() |
float $percentage |
Scale by percentage (50 = half size, 150 = 1.5x size) |
| included-image-crop() |
int $width, int $height, string $position |
Crop to exact dimensions ('left', 'center', 'right') |
| included-image-resize_and_crop() |
int $width, int $height |
Fill exact dimensions by resizing then cropping |
| included-image-get_width() |
none |
Get current image width in pixels |
| included-image-get_height() |
none |
Get current image height in pixels |
| included-image-save() |
string $path, int $quality, int $permissions |
Save to disk (covered in next page) |
| included-image-output() |
bool $return |
Output to browser (covered in next page) |
Image Compression Quality: JPEG and WEBP images accept a quality parameter from 0-100:
- 100 = maximum quality, largest files (default)
- 85-90 = recommended for web images (excellent quality, good compression)
- 70-80 = noticeable quality loss, smaller files (use for thumbnails)
- Below 70 = significant degradation (avoid for main content)
Note: PNG and GIF use lossless compression, so they don't accept a quality parameter. The quality setting only affects JPEG and WEBP formats.
Image Operation Guidelines:
- ✅ Always
load() before manipulating
- ✅ Reload source image when generating multiple sizes
- ✅ Use
resize_to_width() for most layouts
- ✅ Use
resize_and_crop() for thumbnails and grids
- ✅ Check dimensions with
get_width()/get_height() for conditional logic
- ✅ Generate thumbnails during upload, not on-demand
- ✅ Store all generated paths in your database
- ✅ Use quality 85-90 for web images (good balance)