DEV Community

Cover image for Why Your App's Photos Look Weird: A Developer's Guide to Moiré Patterns
wyatt fruit
wyatt fruit

Posted on

Why Your App's Photos Look Weird: A Developer's Guide to Moiré Patterns

You've probably seen it before — strange rainbow-colored waves rippling across a photo of a computer screen, or weird grid-like artifacts in a scanned document. That's called a moiré pattern, and if you're building any application that handles images, it's something you'll inevitably run into.

What Exactly Is a Moiré Pattern?

Moiré patterns occur when two repetitive patterns overlap at slightly different angles or scales. Think of it like this:

Pattern A:  | | | | | | | | | |
Pattern B:   | | | | | | | | | |
Result:     |||  |  |||  |  |||    ← interference pattern
Enter fullscreen mode Exit fullscreen mode

In the physical world, this happens constantly:

  • Screen photography: Your phone camera's pixel grid interferes with the monitor's pixel grid → rainbow waves
  • Scanning printed material: The scanner's sampling grid clashes with the halftone dot pattern → wavy artifacts
  • Fabric photography: The camera sensor grid interacts with the weave pattern → visual noise
  • Video recording: Shooting someone wearing a striped shirt on camera → shimmering patterns

Why Developers Should Care

If you're building any of these, moiré will bite you:

1. Image Upload Platforms

Users upload photos of screens, scanned documents, and product images all the time. Moiré degrades image quality and makes OCR unreliable.

// Your OCR pipeline might fail on moiré-affected scans
const result = await tesseract.recognize(scannedImage);
// result.confidence: 45% 😬 — moiré confused the character recognition
Enter fullscreen mode Exit fullscreen mode

2. E-commerce Product Photos

Photographing textured fabrics, mesh materials, or screens? Moiré makes products look defective. This directly impacts conversion rates.

3. Screen Capture & Recording Tools

Building a screen recording app? If users capture one screen with another device, moiré is guaranteed. Even screenshot tools can produce moiré when downscaling.

4. Document Scanning Apps

Any app that digitizes printed materials needs to handle the halftone-to-pixel conversion problem. Without descreening, your scanned PDFs look amateur.

The Math Behind Moiré

For the curious, moiré is an aliasing artifact — a fundamental concept in signal processing.

When you sample a signal (an image) at a rate lower than twice its highest frequency, you get aliasing. This is the Nyquist-Shannon sampling theorem in action:

f_moiré = |f₁ - f₂|

Where:
  f₁ = frequency of pattern 1 (e.g., screen pixel pitch)
  f₂ = frequency of pattern 2 (e.g., camera sensor pitch)
Enter fullscreen mode Exit fullscreen mode

When f₁ and f₂ are close but not identical, you get a low-frequency interference pattern — that's your moiré.

This is the same principle behind:

  • Audio aliasing in digital music
  • The "wagon wheel effect" in video
  • Temporal aliasing in animation frame rates

How to Fix Moiré: The Technical Approaches

Approach 1: Gaussian Blur (The Brute Force Way)

import cv2
import numpy as np

def remove_moire_blur(image, kernel_size=5):
    """
    Simple but destructive — removes moiré by 
    low-pass filtering, but also kills detail.
    """
    return cv2.GaussianBlur(image, (kernel_size, kernel_size), 0)
Enter fullscreen mode Exit fullscreen mode

Pros: Simple, fast

Cons: Destroys image detail. It's like fixing a headache with a sledgehammer.

Approach 2: Frequency Domain Filtering

def remove_moire_frequency(image):
    """
    Smarter approach: find moiré peaks in frequency 
    domain and notch them out.
    """
    # Convert to frequency domain
    f_transform = np.fft.fft2(image)
    f_shift = np.fft.fftshift(f_transform)

    # Create notch filter to remove moiré frequencies
    # (frequencies identified by spectral analysis)
    magnitude = np.abs(f_shift)

    # Find and suppress anomalous frequency peaks
    threshold = np.mean(magnitude) + 3 * np.std(magnitude)
    mask = magnitude < threshold

    # Apply filter and reconstruct
    filtered = f_shift * mask
    return np.abs(np.fft.ifft2(np.fft.ifftshift(filtered)))
Enter fullscreen mode Exit fullscreen mode

Pros: Preserves more detail

Cons: Requires manual tuning per image, doesn't generalize well.

Approach 3: AI/Deep Learning (The Modern Way)

Modern neural networks can learn to separate moiré patterns from actual image content. This is where the field has moved — models trained on paired moiré/clean image datasets can selectively remove the interference while preserving detail.

The key architectures used:

  • U-Net variants — encoder-decoder with skip connections
  • Multi-scale approaches — process at different resolutions to catch moiré at various frequencies
  • GAN-based methods — adversarial training for more realistic restoration

For most developers, implementing this from scratch isn't practical. Tools like Moire Removal use AI models specifically trained for this, so you can integrate moiré removal into your workflow without building the ML pipeline yourself.

Practical Tips for Your Application

If you're dealing with moiré in your product, here's a decision tree:

Is moiré in your input images?
├── Yes, from screen photos
│   └── Consider: slightly defocus, angle the camera, 
│       or use AI post-processing
├── Yes, from scanned documents  
│   └── Use descreening (most scanner software has this)
│       or try specialized tools like descreening APIs
├── Yes, from fabric/product photos
│   └── Adjust camera distance/angle at capture time
│       or use AI removal in post-processing
└── Yes, from downscaling in your app
    └── Use proper anti-aliasing:
        CSS: image-rendering: auto; (not crisp-edges)
        Canvas: ctx.imageSmoothingEnabled = true;
Enter fullscreen mode Exit fullscreen mode

Quick Win: Prevent Moiré in Canvas Downscaling

function downscaleWithAntiAlias(canvas, targetWidth, targetHeight) {
  // Step-down approach prevents moiré from aggressive downscaling
  const steps = Math.ceil(Math.log2(canvas.width / targetWidth));

  let currentCanvas = canvas;
  for (let i = 0; i < steps; i++) {
    const stepCanvas = document.createElement('canvas');
    stepCanvas.width = currentCanvas.width / 2;
    stepCanvas.height = currentCanvas.height / 2;

    const ctx = stepCanvas.getContext('2d');
    ctx.imageSmoothingEnabled = true;
    ctx.imageSmoothingQuality = 'high';
    ctx.drawImage(currentCanvas, 0, 0, stepCanvas.width, stepCanvas.height);

    currentCanvas = stepCanvas;
  }

  // Final resize to exact target
  const finalCanvas = document.createElement('canvas');
  finalCanvas.width = targetWidth;
  finalCanvas.height = targetHeight;
  const ctx = finalCanvas.getContext('2d');
  ctx.imageSmoothingEnabled = true;
  ctx.drawImage(currentCanvas, 0, 0, targetWidth, targetHeight);

  return finalCanvas;
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Moiré is physics, not a bug — it's aliasing from overlapping patterns
  2. Prevention > Cure — adjust capture conditions when possible
  3. AI removal is now practical — you don't need to implement FFT notch filters from scratch
  4. Think about it in your image pipeline — especially if you handle user-uploaded photos, scans, or screen captures

Resources


Have you dealt with moiré in your projects? I'd love to hear your approach in the comments.

Top comments (0)