Toolmingo
Guides6 min read

How to Flip an Image (JavaScript, Python, Go, PHP)

Learn how to flip images horizontally or vertically with working code examples in JavaScript (Canvas API, Sharp), Python (Pillow), Go, and PHP (GD, Imagick). Covers EXIF pitfalls and common mistakes.

How to Flip an Image

Flipping an image — horizontal (left–right mirror) or vertical (top–bottom) — is one of the most common image operations. It sounds trivial, but EXIF orientation tags, alpha channel handling, and library-specific direction conventions introduce real gotchas. This guide covers both flip axes with working implementations in JavaScript, Python, Go, and PHP.


Horizontal vs Vertical Flip

Flip Also called Effect
Horizontal Mirror, flip-X Left side becomes right side
Vertical Flip-Y, upside-down Top becomes bottom
Original        Horizontal flip   Vertical flip
┌───────┐       ┌───────┐         ┌───────┐
│ A   B │  →    │ B   A │    →    │ C   D │
│ C   D │       │ D   C │         │ A   B │
└───────┘       └───────┘         └───────┘

Output dimensions never change — flipping is a pixel rearrangement, not a resize.


EXIF Orientation — Apply Before Flipping

Smartphone photos often carry an EXIF orientation tag. If you flip a JPEG without first correcting its EXIF orientation, the result will be wrong (the image appears to flip on the wrong axis).

Raw JPEG bytes: landscape (3024 × 4032)
EXIF tag 6   : "rotate 90° CW for display"
You flip     : horizontally
Result       : appears flipped vertically in viewers that auto-apply EXIF

Fix: strip or apply EXIF orientation before flipping — use Sharp's .rotate() (no argument), Pillow's ImageOps.exif_transpose(), or Imagick's autoOrient().


JavaScript

Browser: Canvas API

/**
 * Flip an image element horizontally or vertically.
 * @param {HTMLImageElement} img
 * @param {'horizontal' | 'vertical'} direction
 * @returns {HTMLCanvasElement}
 */
function flipImage(img, direction = 'horizontal') {
  const canvas = document.createElement('canvas');
  canvas.width  = img.naturalWidth;
  canvas.height = img.naturalHeight;

  const ctx = canvas.getContext('2d');

  if (direction === 'horizontal') {
    ctx.translate(canvas.width, 0);
    ctx.scale(-1, 1);
  } else {
    ctx.translate(0, canvas.height);
    ctx.scale(1, -1);
  }

  ctx.drawImage(img, 0, 0);
  return canvas;
}

// Usage
const img = document.getElementById('myImage');
const flipped = flipImage(img, 'horizontal');
document.body.appendChild(flipped);

// Download the result
flipped.toBlob(blob => {
  const a = document.createElement('a');
  a.href = URL.createObjectURL(blob);
  a.download = 'flipped.png';
  a.click();
}, 'image/png');

Node.js: Sharp

import sharp from 'sharp';

// Horizontal flip (mirror)
await sharp('input.jpg')
  .rotate()        // auto-apply EXIF orientation first
  .flop()          // horizontal flip (left–right)
  .toFile('flipped-h.jpg');

// Vertical flip (upside-down)
await sharp('input.jpg')
  .rotate()
  .flip()          // vertical flip (top–bottom)
  .toFile('flipped-v.jpg');

// Both axes at once (same as 180° rotation)
await sharp('input.jpg')
  .rotate()
  .flip()
  .flop()
  .toFile('flipped-both.jpg');

Sharp naming: .flip() = vertical (Y axis), .flop() = horizontal (X axis). Counterintuitive — remember: "flop" sounds like flipping sideways.


Python

Pillow

from PIL import Image, ImageOps

def flip_image(input_path: str, output_path: str, direction: str = 'horizontal') -> None:
    """
    Flip an image horizontally or vertically.
    Applies EXIF orientation before flipping.
    """
    with Image.open(input_path) as img:
        # Apply EXIF orientation first (critical for smartphone photos)
        img = ImageOps.exif_transpose(img)

        if direction == 'horizontal':
            flipped = ImageOps.mirror(img)   # left–right
        elif direction == 'vertical':
            flipped = ImageOps.flip(img)     # top–bottom
        else:
            raise ValueError("direction must be 'horizontal' or 'vertical'")

        flipped.save(output_path)

# Examples
flip_image('photo.jpg', 'mirrored.jpg', 'horizontal')
flip_image('photo.jpg', 'upside-down.jpg', 'vertical')

Pillow naming:

  • ImageOps.mirror() → horizontal flip (left–right mirror)
  • ImageOps.flip() → vertical flip (upside-down)
# Alternative using the transpose method directly
from PIL import Image

with Image.open('photo.jpg') as img:
    # TRANSPOSE constants
    h_flip = img.transpose(Image.FLIP_LEFT_RIGHT)
    v_flip = img.transpose(Image.FLIP_TOP_BOTTOM)
    h_flip.save('h-flipped.jpg')
    v_flip.save('v-flipped.jpg')

Go

Go's standard library has no built-in flip, but it is straightforward to implement:

package main

import (
    "image"
    "image/jpeg"
    "image/png"
    "os"
    "path/filepath"
    "strings"
)

// FlipHorizontal mirrors an image left-to-right.
func FlipHorizontal(src image.Image) *image.NRGBA {
    bounds := src.Bounds()
    dst := image.NewNRGBA(bounds)
    for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
        for x := bounds.Min.X; x < bounds.Max.X; x++ {
            // Source pixel comes from the mirrored X position
            srcX := bounds.Max.X - 1 - (x - bounds.Min.X) + bounds.Min.X
            dst.Set(x, y, src.At(srcX, y))
        }
    }
    return dst
}

// FlipVertical mirrors an image top-to-bottom.
func FlipVertical(src image.Image) *image.NRGBA {
    bounds := src.Bounds()
    dst := image.NewNRGBA(bounds)
    for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
        srcY := bounds.Max.Y - 1 - (y - bounds.Min.Y) + bounds.Min.Y
        for x := bounds.Min.X; x < bounds.Max.X; x++ {
            dst.Set(x, y, src.At(x, srcY))
        }
    }
    return dst
}

func main() {
    f, _ := os.Open("photo.jpg")
    defer f.Close()
    src, _, _ := image.Decode(f)

    flipped := FlipHorizontal(src)

    out, _ := os.Create("flipped.jpg")
    defer out.Close()
    jpeg.Encode(out, flipped, &jpeg.Options{Quality: 90})
}

For production use, consider the disintegration/imaging library which provides imaging.FlipH() and imaging.FlipV().

// With imaging library
import "github.com/disintegration/imaging"

src, _ := imaging.Open("photo.jpg", imaging.AutoOrientation(true))
flippedH := imaging.FlipH(src)  // horizontal
flippedV := imaging.FlipV(src)  // vertical
imaging.Save(flippedH, "flipped.jpg")

PHP

GD

<?php
/**
 * Flip an image file horizontally or vertically using GD.
 * Preserves PNG transparency.
 */
function flipImage(
    string $inputPath,
    string $outputPath,
    string $direction = 'horizontal'
): void {
    $info = getimagesize($inputPath);
    $mime = $info['mime'];

    // Load source image
    $src = match ($mime) {
        'image/jpeg' => imagecreatefromjpeg($inputPath),
        'image/png'  => imagecreatefrompng($inputPath),
        'image/webp' => imagecreatefromwebp($inputPath),
        default      => throw new InvalidArgumentException("Unsupported: $mime"),
    };

    // Preserve PNG alpha
    imagesavealpha($src, true);

    // GD 5.5+ has imageflip() built in
    $mode = $direction === 'vertical' ? IMG_FLIP_VERTICAL : IMG_FLIP_HORIZONTAL;
    imageflip($src, $mode);

    // Save result
    match ($mime) {
        'image/jpeg' => imagejpeg($src, $outputPath, 90),
        'image/png'  => imagepng($src, $outputPath),
        'image/webp' => imagewebp($src, $outputPath, 90),
    };

    imagedestroy($src);
}

flipImage('photo.jpg', 'mirrored.jpg', 'horizontal');
flipImage('photo.png', 'upside-down.png', 'vertical');

Imagick

<?php
$imagick = new Imagick('photo.jpg');
$imagick->autoOrient();            // apply EXIF orientation first

// Horizontal flip
$imagick->flopImage();             // left–right mirror

// Or vertical flip
// $imagick->flipImage();          // top–bottom

$imagick->writeImage('flipped.jpg');
$imagick->clear();

Imagick naming matches Pillow: flipImage() = vertical, flopImage() = horizontal.


Quick Reference

Library Horizontal flip Vertical flip Auto EXIF
Canvas API scale(-1, 1) scale(1, -1) No — manual
Sharp (Node) .flop() .flip() .rotate()
Pillow ImageOps.mirror() ImageOps.flip() exif_transpose()
Go imaging imaging.FlipH() imaging.FlipV() AutoOrientation(true)
PHP GD imageflip(IMG_FLIP_HORIZONTAL) imageflip(IMG_FLIP_VERTICAL) No
PHP Imagick flopImage() flipImage() autoOrient()

6 Common Mistakes

  1. Skipping EXIF correction on JPEGs — the most frequent bug. Always call exif_transpose() / .rotate() / autoOrient() before flipping smartphone photos.

  2. Confusing Sharp's .flip() and .flop() — Sharp's .flip() is vertical (top–bottom), .flop() is horizontal (left–right). The opposite of what many developers expect.

  3. Forgetting to preserve PNG alpha in PHP GD — call imagesavealpha($src, true) before imageflip(), otherwise transparent areas turn black.

  4. Re-encoding a JPEG unnecessarily — each save-as-JPEG introduces generation loss. If the source is JPEG and you only need to flip, consider lossless JPEG flip tools (jpegtran -flip horizontal) that manipulate DCT coefficients directly without full decode/encode.

  5. Applying both flips when you wanted 180° rotation — flipping both axes produces the same pixel result as a 180° rotation, but uses two passes instead of one. Use .rotate(180) or img.transpose(Image.ROTATE_180) instead.

  6. Not testing with non-square images — flip logic is correct for any aspect ratio, but a common manual pixel-loop bug is using width where height should be used in the vertical case. Always test with a rectangular image.


Frequently Asked Questions

Does flipping change the image file size? Slightly. The pixel content changes, which affects compression ratios. A JPEG of a mirrored photo will be very close to the original size, but not byte-identical.

Is horizontal flip the same as a 180° rotation? No. A horizontal flip mirrors left-to-right; a 180° rotation also flips upside-down. Applying both a horizontal and a vertical flip equals a 180° rotation.

How do I create a CSS mirror effect without processing the image? Use transform: scaleX(-1) for horizontal or scaleY(-1) for vertical. This is GPU-accelerated and requires no server-side processing.

.mirror { transform: scaleX(-1); }
.upside-down { transform: scaleY(-1); }

Can I flip a GIF without losing animation? Not with the techniques above — they work on a single frame. For animated GIFs, use FFmpeg: ffmpeg -i in.gif -vf hflip out.gif.

Why does my flipped selfie look wrong? Front-facing cameras mirror the preview so you see yourself as in a mirror. The saved photo is already corrected (un-mirrored). If you flip the saved photo, it becomes a mirror image again, which can look unnatural. This is expected behaviour.

Does lossless JPEG flip exist? Yes. jpegtran -flip horizontal input.jpg -outfile output.jpg manipulates DCT blocks directly without decoding, so there is zero quality loss. Only supported for images whose dimensions are multiples of the DCT block size (usually 8 or 16 pixels).

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools