Introduction – Image Manipulation
Hey there, fellow developer! 👋
If you’ve ever built a photo upload feature for a website, you know the pain. A user uploads a 5MB photo straight from their iPhone. It’s huge, it’s rotated sideways because of EXIF data, and if you try to display it on your homepage, your layout breaks completely.
This is where CodeIgniter 4 Image Manipulation comes in to save the day.
While CodeIgniter 4 has a built-in image class, sometimes you just want a “drop-in” solution that handles the tricky stuff—like auto-calculating height when you only know the width, or converting images to modern WebP format instantly.
In this guide, I’m going to share a custom library called ThumbnailCreator. It’s robust, easy to read, and perfect for your 2025 projects. Let’s dive in! 🚀
Table of Contents
Why You Need a Custom Library – Image Manipulation
Before we start coding, let’s look at what this library actually does for you. Think of it as your personal image-processing assistant.
- Auto-Resizing: You give it a width, and it figures out the height automatically (so your photos don’t look squashed).
- Smart Cropping: Need a perfect square for a user profile? It crops from the center automatically.
- EXIF Rotation Fix: You know how phone photos sometimes look sideways on a computer? This library reads the orientation data and rotates it back to normal.
- Format Conversion: It can take a heavy PNG and turn it into a lightweight WebP or JPEG on the fly.
Setting Up Your Environment – Image Manipulation
Let’s get our hands dirty! First, we need to make sure your server is ready to handle image processing.
1. Check for PHP GD Library
This library relies on the GD Library, which is a PHP extension for creating and manipulating images.
To check if you have it, run this command in your terminal:
php -m | grep gdIf you see gd in the output, you are golden! 🌟 If not, you’ll need to enable extension=gd in your php.ini file.
2. Create the File Structure
In CodeIgniter 4, custom libraries usually live in the app/Libraries folder. Go ahead and create a new file:
File Path: app/Libraries/ThumbnailCreator.php
We will paste the code into this file later, but for now, just make sure the file exists.
Note: For more on CI4 directory structures, check out our guide on webdevservices.in.
Understanding the Resizing Modes
This is the most important part of CodeIgniter 4 Image Manipulation. How do you want your thumbnail to look? This library supports four specific “modes.”
1. The fit Mode
This puts your image inside a “box.” It ensures the image is never larger than your dimensions, but it keeps the original aspect ratio. It will never cut off parts of the image.
2. The crop Mode
This is the “Cookie Cutter” mode. If you want a 200×200 square, you get a 200×200 square. If the image is a rectangle, the library cuts off the extra bits from the sides (or top/bottom) to make it fit perfectly.
3. The auto Mode
This is for the “lazy” developer (in a good way!). You provide just the width (e.g., 500px), and the library calculates the height for you to keep the proportions correct.
4. The stretch Mode
Warning: ⚠️ This forces the image to the exact size you want, even if it makes the image look like a fun-house mirror. Use this only if you really don’t care about distortion.
Handling Formats & The “Sideways Photo” Bug – Image Manipulation
The iPhone EXIF Problem
Modern phones save orientation data in hidden tags called EXIF. Browsers sometimes ignore this, making your uploaded selfies look sideways.
Our library includes a function called fixExifOrientation(). It checks these tags:
- Tag 3: Rotates 180°
- Tag 6: Rotates -90°
- Tag 8: Rotates 90°
This ensures your users’ uploads always stand upright!
Transparency Support
If you resize a transparent PNG (like a logo) and save it as a JPEG, the background turns black. Yuck! 🤮
This library detects the format. If it’s PNG, GIF, or WebP, it preserves that lovely transparency. If it’s JPEG, it fills the background with white.
Step-by-Step Implementation Guide – Image Manipulation
Okay, let’s see how to actually use this in a Controller.
Imagine you have a form where users upload their avatar. Here is how you process it.
The Controller Code
<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use App\Libraries\ThumbnailCreator; // Import our library
class UploadController extends BaseController
{
public function upload()
{
// 1. Get the file from the request
$file = $this->request->getFile('image');
// 2. Basic Validation
if (!$file->isValid()) {
return $this->response->setJSON([
'success' => false,
'message' => $file->getErrorString()
]);
}
// 3. Move the original file to a safe spot
$newName = $file->getRandomName();
$file->move(WRITEPATH . 'uploads', $newName);
$srcPath = WRITEPATH . 'uploads/' . $newName;
$thumbDir = WRITEPATH . 'uploads/thumbs';
// 4. Initialize our Library
// We set preserveExifOrientation to true to fix phone photos
$tc = new ThumbnailCreator([
'jpegQuality' => 85,
'webpQuality' => 80,
'preserveExifOrientation'=> true,
]);
// 5. Create the Thumbnail!
// Let's make a 300x300 square crop
$res = $tc->createThumbnail(
$srcPath, // Source
$thumbDir . '/thumb_' . $newName, // Destination
300, // Target Width
300, // Target Height
'crop' // Mode
);
if (!$res['success']) {
return $this->response->setJSON(['error' => $res['message']]);
}
// Success! Return the path to the frontend
return $this->response->setJSON([
'success' => true,
'url' => $res['path']
]);
}
}See how clean that is? You don’t have to mess with imagecreatefromjpeg or messy math calculations in your controller. The library handles it all.
The Complete Source Code – Image Manipulation
Here is the full code for the library. Copy this entirely into app/Libraries/ThumbnailCreator.php.
<?php
// app/Libraries/ThumbnailCreator.php
namespace App\Libraries;
/**
* ThumbnailCreator Library
* A reusable image thumbnail generator for CodeIgniter 4.
*/
class ThumbnailCreator
{
protected int $jpegQuality = 85;
protected int $webpQuality = 85;
protected bool $preserveExifOrientation = true;
public function __construct(array $options = [])
{
if (isset($options['jpegQuality'])) $this->jpegQuality = (int)$options['jpegQuality'];
if (isset($options['webpQuality'])) $this->webpQuality = (int)$options['webpQuality'];
if (isset($options['preserveExifOrientation'])) $this->preserveExifOrientation = (bool)$options['preserveExifOrientation'];
}
/**
* Main API to create thumbnail.
* @return array
*/
public function createThumbnail(string $srcPath, string $destPath, ?int $targetW = null, ?int $targetH = null, string $mode = 'auto', ?string $forceFormat = null): array
{
$start = microtime(true);
if (!extension_loaded('gd')) {
return $this->error('GD extension is not enabled on this server.');
}
if (!is_file($srcPath) || !is_readable($srcPath)) {
return $this->error("Source file not found or unreadable: {$srcPath}");
}
$info = @getimagesize($srcPath);
if ($info === false) {
return $this->error("Source is not a valid image: {$srcPath}");
}
[$widthOrig, $heightOrig, $type] = $info;
$mime = $info['mime'] ?? '';
$format = $this->mimeToFormat($mime);
if ($forceFormat !== null) {
$forceFormat = strtolower($forceFormat);
if ($forceFormat === 'jpg') $forceFormat = 'jpeg';
$format = $forceFormat;
}
// If no dimensions given and not cropping, just copy or simple convert
if ($targetW === null && $targetH === null && $mode !== 'crop' && $mode !== 'stretch') {
$this->ensureDir(dirname($destPath));
if ($format === $this->mimeToFormat($mime)) {
if (!@copy($srcPath, $destPath)) {
return $this->error("Failed to copy file to {$destPath}");
}
return $this->ok($destPath, $widthOrig, $heightOrig, $mime, $start);
}
$targetW = $widthOrig;
$targetH = $heightOrig;
$mode = 'fit';
}
$srcImg = $this->imageCreateFromMime($mime, $srcPath);
if (!$srcImg) {
return $this->error("Failed to create image resource. MIME={$mime}");
}
// EXIF rotation
if ($this->preserveExifOrientation && stripos($mime, 'jpeg') !== false) {
$this->fixExifOrientation($srcImg, $srcPath);
}
// Compute dimensions
[$dstW, $dstH, $srcX, $srcY, $srcW, $srcH] =
$this->computeDimensions($widthOrig, $heightOrig, $targetW, $targetH, $mode);
$dstImg = imagecreatetruecolor($dstW, $dstH);
// Transparency handling
if (in_array($format, ['png', 'gif', 'webp'])) {
imagesavealpha($dstImg, true);
imagealphablending($dstImg, false);
$transparent = imagecolorallocatealpha($dstImg, 0, 0, 0, 127);
imagefilledrectangle($dstImg, 0, 0, $dstW, $dstH, $transparent);
} else {
$white = imagecolorallocate($dstImg, 255, 255, 255);
imagefilledrectangle($dstImg, 0, 0, $dstW, $dstH, $white);
}
imagecopyresampled($dstImg, $srcImg, 0, 0, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH);
$this->ensureDir(dirname($destPath));
switch ($format) {
case 'png':
$saved = imagepng($dstImg, $destPath, 6);
$mimeOut = 'image/png';
break;
case 'gif':
$saved = imagegif($dstImg, $destPath);
$mimeOut = 'image/gif';
break;
case 'webp':
$saved = imagewebp($dstImg, $destPath, $this->webpQuality);
$mimeOut = 'image/webp';
break;
case 'jpeg':
default:
$saved = imagejpeg($dstImg, $destPath, $this->jpegQuality);
$mimeOut = 'image/jpeg';
break;
}
imagedestroy($srcImg);
imagedestroy($dstImg);
if (!$saved) {
return $this->error("Failed to save thumbnail to {$destPath}");
}
[$finalW, $finalH] = $this->getImageSizeSafe($destPath);
return $this->ok($destPath, $finalW, $finalH, $mimeOut, $start);
}
// --- Helpers ---
protected function mimeToFormat(string $mime): string
{
$mime = strtolower($mime);
return match (true) {
str_contains($mime, 'png') => 'png',
str_contains($mime, 'gif') => 'gif',
str_contains($mime, 'webp') => 'webp',
default => 'jpeg',
};
}
protected function imageCreateFromMime(string $mime, string $path)
{
$mime = strtolower($mime);
return match (true) {
str_contains($mime, 'jpeg'), str_contains($mime, 'jpg') => @imagecreatefromjpeg($path),
str_contains($mime, 'png') => @imagecreatefrompng($path),
str_contains($mime, 'gif') => @imagecreatefromgif($path),
str_contains($mime, 'webp') && function_exists('imagecreatefromwebp') => @imagecreatefromwebp($path),
default => false,
};
}
protected function computeDimensions(int $srcW, int $srcH, ?int $targetW, ?int $targetH, string $mode): array
{
$mode = strtolower($mode);
if ($mode === 'auto') {
if ($targetW && !$targetH) {
$ratio = $srcH / $srcW;
$targetH = (int)round($targetW * $ratio);
} elseif ($targetH && !$targetW) {
$ratio = $srcW / $srcH;
$targetW = (int)round($targetH * $ratio);
} else {
$targetW = $srcW;
$targetH = $srcH;
}
$mode = 'fit';
}
if ($mode === 'stretch') {
return [$targetW ?? $srcW, $targetH ?? $srcH, 0, 0, $srcW, $srcH];
}
if ($mode === 'fit') {
$targetW ??= $srcW;
$targetH ??= $srcH;
$scale = min($targetW / $srcW, $targetH / $srcH);
return [
(int)floor($srcW * $scale),
(int)floor($srcH * $scale),
0, 0,
$srcW,
$srcH
];
}
if ($mode === 'crop') {
$targetW ??= $srcW;
$targetH ??= $srcH;
$srcAspect = $srcW / $srcH;
$dstAspect = $targetW / $targetH;
if ($srcAspect > $dstAspect) {
$newW = (int)round($srcH * $dstAspect);
$newH = $srcH;
} else {
$newW = $srcW;
$newH = (int)round($srcW / $dstAspect);
}
return [
$targetW, $targetH,
(int)(($srcW - $newW) / 2),
(int)(($srcH - $newH) / 2),
$newW,
$newH
];
}
return $this->computeDimensions($srcW, $srcH, $targetW, $targetH, 'fit');
}
protected function ensureDir(string $dir)
{
if (!is_dir($dir)) {
@mkdir($dir, 0755, true);
}
}
protected function getImageSizeSafe(string $path): array
{
$s = @getimagesize($path);
return $s ? [$s[0], $s[1]] : [0, 0];
}
protected function fixExifOrientation(&$img, string $path)
{
try {
if (!function_exists('exif_read_data')) return;
$exif = @exif_read_data($path);
if (!isset($exif['Orientation'])) return;
$o = $exif['Orientation'];
$img = match ($o) {
3 => imagerotate($img, 180, 0),
6 => imagerotate($img, -90, 0),
8 => imagerotate($img, 90, 0),
default => $img
};
} catch (\Throwable $e) {
}
}
protected function ok(string $path, int $w, int $h, string $mime, float $start): array
{
return [
'success' => true,
'path' => $path,
'width' => $w,
'height' => $h,
'mime' => $mime,
'message' => 'OK',
'elapsed_ms' => (int)((microtime(true) - $start) * 1000)
];
}
protected function error(string $msg): array
{
return [
'success' => false,
'message' => $msg
];
}
}Conclusion
And that’s it! 🎉 You now have a professional-grade CodeIgniter 4 image manipulation setup. No more distorted images, no more sideways selfies, and no more heavy PNG files slowing down your site.
This library is flexible enough to handle user avatars, product galleries, and even full-screen hero banners.
If you want to learn more about optimizing PHP performance or advanced CI4 features, check out the documentation on PHP.net for GD functions.
Did you find this tutorial helpful?
👍 Like this post if it saved you time!
💬 Comment below: Do you prefer WebP or JPEG for thumbnails? Let’s discuss!
🔗 Share with your dev friends who are still struggling with image resizing!
Happy Coding! 💻✨

