When users upload images from various devices, the orientation metadata (EXIF data) may cause the images to appear rotated incorrectly. This blog post will guide you through fixing image rotation using client-side JavaScript to capture user input and server-side PHP with the Imagick library for processing. We'll provide code snippets and explanations for each step.
Step 1: Client-Side (HTML + JavaScript)
HTML Form
The HTML form allows users to upload an image and provides buttons to rotate the image by 90 degrees clockwise and counterclockwise.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Image Upload with Rotation</title>
<style>
#preview {
display: block;
margin-top: 10px;
max-width: 200px; /* Adjust thumbnail size */
}
</style>
</head>
<body>
<form id="uploadForm" enctype="multipart/form-data">
<input type="file" name="image" id="image" accept="image/png, image/jpeg" onchange="showThumbnail(this)">
<button type="button" onclick="rotateImage(90)">Rotate 90° CW</button>
<input type="hidden" name="rotation" id="rotation" value="0">
<button type="submit">Upload</button>
</form>
<img id="preview" src="" alt="Image preview">
</body>
</html>
JavaScript
The JavaScript handles image rotation and captures the rotation angle to send it along with the image when the form is submitted.
let rotation = 0;
function showThumbnail(input) {
const file = input.files[0];
const reader = new FileReader();
reader.onload = function(e) {
const img = document.getElementById('preview');
img.src = e.target.result;
img.style.transform = 'rotate(0deg)'; // Reset rotation
};
reader.readAsDataURL(file);
}
function rotateImage(angle) {
rotation = (rotation + angle) % 360;
document.getElementById('rotation').value = rotation;
const img = document.getElementById('preview');
img.style.transform = rotate(${rotation}deg);
}
document.getElementById('uploadForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
fetch('upload.php', {
method: 'POST',
body: formData
})
.then(response => response.text())
.then(data => alert(data))
.catch(error => console.error('Error:', error));
});
Explanation
- HTML Form:
<input type="file" name="image" id="image" accept="image/png, image/jpeg" onchange="showThumbnail(this)">
: Allows the user to select an image file and triggers theshowThumbnail
function to display a preview of the image.<button type="button" onclick="rotateImage(90)">Rotate 90° CW</button>
: Button to rotate the image 90 degrees clockwise. TherotateImage
function updates the rotation angle and the image preview.<input type="hidden" name="rotation" id="rotation" value="0">
: Hidden input to store the rotation angle, which is sent to the server when the form is submitted.
- JavaScript:
- showThumbnail(input): This function reads the selected image file and displays a thumbnail preview. It also resets the rotation when a new image is selected.
- rotateImage(angle): This function updates the rotation angle when the user clicks one of the rotation buttons. The angle is stored in the hidden input field named
rotation
and applied to the image preview. - Event Listener: The form submission is intercepted to handle the file upload using JavaScript's Fetch API. The
rotation
value and the image file are sent to the server.
Step 2: Server-Side Code (PHP with Imagick)
In Step 1, we covered the client-side HTML and JavaScript code to capture user input for image rotation and display a thumbnail preview. In this step, we will cover the server-side PHP script using the Imagick library to process the uploaded image, apply the specified rotation, and compress the image before saving it to the server. Additionally, we'll provide information on how to install Imagick on your local development environment, such as XAMPP.
The PHP script processes the uploaded image, applies the specified rotation, and compresses the image before saving it to the server.
<?php
function rotateImage($sourcePath, $destinationPath, $angle) {
try {
$imagick = new Imagick($sourcePath);
$imagick->rotateImage(new ImagickPixel('none'), $angle);
$imagick->setImageFormat('jpeg');
$imagick->writeImage($destinationPath);
$imagick->clear();
$imagick->destroy();
echo "Rotated image by $angle degrees: $destinationPath<br>";
} catch (ImagickException $e) {
echo "Failed to rotate image: " . $e->getMessage() . "<br>";
}
}
function compressImage($sourcePath, $destinationPath, $quality) {
try {
$imagick = new Imagick($sourcePath);
$imagick->setImageCompressionQuality($quality);
$imagick->writeImage($destinationPath);
$imagick->clear();
$imagick->destroy();
echo "Compressed image to $quality quality: $destinationPath<br>";
} catch (ImagickException $e) {
echo "Failed to compress image: " . $e->getMessage() . "<br>";
}
}
// Define directory paths
$uploadDirectory = DIR . '/upload/';
$thumbnailDirectory = DIR . '/thumbnail/';
// Ensure directories exist (create if necessary)
if (!file_exists($uploadDirectory)) {
mkdir($uploadDirectory, 0755, true);
}
if (!file_exists($thumbnailDirectory)) {
mkdir($thumbnailDirectory, 0755, true);
}
// Ensure write permissions
if (!is_writable($uploadDirectory)) {
die('Upload directory is not writable<br>');
}
if (!is_writable($thumbnailDirectory)) {
die('Thumbnail directory is not writable<br>');
}
// Process uploaded file
if (isset($_FILES['image'])) {
$sourcePath = $_FILES['image']['tmp_name'];
$originalFilename = $_FILES['image']['name'];
$rotation = intval($_POST['rotation']);
$mime = mime_content_type($sourcePath);
// Define paths for the processed images
$uniqueFilename = uniqid(time(), true) . '.jpg'; // Use .jpg extension
$lightCompressedPath = $uploadDirectory . $uniqueFilename;
$hardCompressedPath = $thumbnailDirectory . $uniqueFilename;
// Step 1: Rotate the image based on user input
rotateImage($sourcePath, $lightCompressedPath, $rotation);
// Step 2: Create a hard compressed version of the image
compressImage($lightCompressedPath, $hardCompressedPath, 50); // Adjust the quality as needed (e.g., 50)
echo "Image uploaded and processed successfully!";
} else {
echo "No image uploaded.";
}
?>
Explanation
- rotateImage($sourcePath, $destinationPath, $angle): This function uses Imagick to rotate the image by the specified angle and save it as a JPEG.
- compressImage($sourcePath, $destinationPath, $quality): This function uses Imagick to compress the image to the specified quality level.
- Directory Paths: Define paths for the upload and thumbnail directories and ensure they exist and are writable.
- Process Uploaded File: Handle the uploaded file, apply the user-specified rotation, and compress the image before saving it.
Installing Imagick in XAMPP
Imagick is not included by default in local development environments like XAMPP. You need to install it manually. Here's a step-by-step guide on how to do this:
- Download Imagick: Download the appropriate DLL files for your PHP version and architecture from the official Imagick website.
- Copy DLL Files: Copy the downloaded DLL files to the
ext
directory of your PHP installation in XAMPP (e.g.,C:\xampp\php\ext
). - Enable Imagick Extension: Open your
php.ini
file (located in the PHP installation directory) and add the following line to enable the Imagick extension:extension=php_imagick.dll
- Restart XAMPP: Restart your XAMPP server to apply the changes.
For more detailed instructions, check out our blog post on how to install Imagick in XAMPP manually.
Conclusion
By combining client-side JavaScript to capture user input and server-side PHP with Imagick to process images, you can ensure that uploaded images are correctly oriented and compressed. This approach provides a better user experience and ensures consistent image handling across different devices.
Feel free to adapt and expand on this code to suit your specific needs. Happy coding!
For those looking for a solution using the GD library, check out our blog post on how to fix image rotation using JavaScript and PHP with GD Library.
Leave a Reply