Image Processing with Resizing, Cropping and Format Conversion
Image utility that resizes, crops, rotates, and converts between formats using imaging with controllable quality settings.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
package main
import (
"fmt"
"image"
"image/gif"
"image/jpeg"
"image/png"
"os"
"path/filepath"
"github.com/disintegration/imaging"
)
// ImageFormat represents supported image formats
type ImageFormat string
const (
FormatJPEG ImageFormat = "jpeg"
FormatPNG ImageFormat = "png"
FormatGIF ImageFormat = "gif"
)
// ImageProcessor handles image processing operations
type ImageProcessor struct {
inputPath string
outputPath string
image image.Image
}
// NewImageProcessor creates a new processor from an image file
func NewImageProcessor(inputPath string) (*ImageProcessor, error) {
// Open the image file
file, err := os.Open(inputPath)
if err != nil {
return nil, fmt.Errorf("failed to open image file: %w", err)
}
defer file.Close()
// Decode the image
img, _, err := image.Decode(file)
if err != nil {
return nil, fmt.Errorf("failed to decode image: %w", err)
}
return &ImageProcessor{
inputPath: inputPath,
image: img,
}, nil
}
// Resize resizes the image to the specified dimensions with the given resample filter
func (ip *ImageProcessor) Resize(width, height int, filter imaging.ResampleFilter) {
ip.image = imaging.Resize(ip.image, width, height, filter)
}
// Crop crops the image to the specified rectangle (x, y, width, height)
func (ip *ImageProcessor) Crop(x, y, width, height int) error {
bounds := ip.image.Bounds()
cropRect := image.Rect(x, y, x+width, y+height)
// Validate crop rectangle
if !cropRect.In(bounds) {
return fmt.Errorf("crop rectangle %v is outside image bounds %v", cropRect, bounds)
}
ip.image = imaging.Crop(ip.image, cropRect)
return nil
}
// Rotate rotates the image by the specified degrees (counter-clockwise)
func (ip *ImageProcessor) Rotate(degrees float64) {
ip.image = imaging.Rotate(ip.image, degrees, nil)
}
// FlipHorizontal flips the image horizontally
func (ip *ImageProcessor) FlipHorizontal() {
ip.image = imaging.FlipH(ip.image)
}
// FlipVertical flips the image vertically
func (ip *ImageProcessor) FlipVertical() {
ip.image = imaging.FlipV(ip.image)
}
// Save writes the processed image to the output file with specified format and quality
func (ip *ImageProcessor) Save(outputPath string, format ImageFormat, quality int) error {
ip.outputPath = outputPath
// Create output directory if it doesn't exist
dir := filepath.Dir(outputPath)
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
// Create output file
file, err := os.Create(outputPath)
if err != nil {
return fmt.Errorf("failed to create output file: %w", err)
}
defer file.Close()
// Encode and save the image based on format
switch format {
case FormatJPEG:
// Quality should be between 1-100
if quality < 1 || quality > 100 {
quality = 80 // Default quality
}
opts := &jpeg.Options{Quality: quality}
if err := jpeg.Encode(file, ip.image, opts); err != nil {
return fmt.Errorf("failed to encode JPEG: %w", err)
}
case FormatPNG:
if err := png.Encode(file, ip.image); err != nil {
return fmt.Errorf("failed to encode PNG: %w", err)
}
case FormatGIF:
if err := gif.Encode(file, ip.image, nil); err != nil {
return fmt.Errorf("failed to encode GIF: %w", err)
}
default:
return fmt.Errorf("unsupported image format: %s", format)
}
return nil
}
// GetDimensions returns the current image dimensions (width, height)
func (ip *ImageProcessor) GetDimensions() (int, int) {
bounds := ip.image.Bounds()
return bounds.Dx(), bounds.Dy()
}
func main() {
// Example usage
inputFile := "input.jpg"
outputDir := "processed_images"
// Create image processor
processor, err := NewImageProcessor(inputFile)
if err != nil {
fmt.Printf("Failed to create processor: %v\n", err)
return
}
// Get original dimensions
origWidth, origHeight := processor.GetDimensions()
fmt.Printf("Original dimensions: %dx%d\n", origWidth, origHeight)
// Resize to 800x600 (maintaining aspect ratio)
processor.Resize(800, 600, imaging.Lanczos)
resizedWidth, resizedHeight := processor.GetDimensions()
fmt.Printf("Resized dimensions: %dx%d\n", resizedWidth, resizedHeight)
// Crop to 400x400 (center crop)
cropX := (resizedWidth - 400) / 2
cropY := (resizedHeight - 400) / 2
if err := processor.Crop(cropX, cropY, 400, 400); err != nil {
fmt.Printf("Failed to crop image: %v\n", err)
return
}
// Save in different formats
// 1. Save as JPEG with 90% quality
jpegOutput := filepath.Join(outputDir, "output.jpg")
if err := processor.Save(jpegOutput, FormatJPEG, 90); err != nil {
fmt.Printf("Failed to save JPEG: %v\n", err)
return
}
// 2. Save as PNG
pngOutput := filepath.Join(outputDir, "output.png")
if err := processor.Save(pngOutput, FormatPNG, 0); err != nil {
fmt.Printf("Failed to save PNG: %v\n", err)
return
}
// 3. Rotate 90 degrees and save as GIF
processor.Rotate(90)
gifOutput := filepath.Join(outputDir, "output_rotated.gif")
if err := processor.Save(gifOutput, FormatGIF, 0); err != nil {
fmt.Printf("Failed to save GIF: %v\n", err)
return
}
fmt.Println("Image processing completed successfully!")
fmt.Printf("Processed images saved to: %s\n", outputDir)
}How It Works
Image utility that resizes, crops, rotates, and converts between formats using imaging with controllable quality settings.
Opens an input image, applies transformations such as resize, crop center, and rotate, encodes output to JPEG, PNG, or GIF with adjustable quality, and writes to disk while handling errors.
Key Concepts
- 1Supports multiple format codecs with sensible defaults.
- 2Chains transformations to avoid repeated decoding or encoding.
- 3Quality and resize options prevent unnecessary loss or bloat.
When to Use This Pattern
- Preparing thumbnails or avatars server-side.
- Batch processing pipelines for product images.
- Converting uploads into web-friendly formats.
Best Practices
- Validate input dimensions before processing to avoid running out of memory.
- Choose interpolation and quality based on use case (speed versus fidelity).
- Close file handles promptly to avoid descriptor leaks.
Go Version1.18
Difficultyintermediate
Production ReadyYes
Lines of Code192