Image Quality Guard

Image Quality Guard
Detect blurry, poorly lit, and low-contrast images before they enter your upload, scanning, or recognition workflow.

pub.flutter-io.cn version GitHub stars Web Demo Flutter Dart


๐ŸŽฏ What is Image Quality Guard?

image_quality_guard is a platform-independent Dart package for Flutter and Dart applications. It analyzes image bytes to detect quality issues โ€” blur, brightness, and contrast โ€” before images enter your upload, scanning, or recognition pipeline.

No native runtime dependencies. Analysis runs entirely in Dart, with optional background isolate support for smooth UI performance.

๐Ÿ”‘ Key Capabilities

Capability Description
๐Ÿ” Blur Detection Laplacian variance analysis to flag out-of-focus images
๐Ÿ’ก Brightness Analysis Classifies images as too dark, optimal, or too bright
๐ŸŽจ Contrast Measurement luminance standard deviation for depth and clarity
โœ… One-Call Validation Combined pass/fail with a single validate() call
โš™๏ธ 5 Presets Card, document, photo, relaxed, and strict configurations
๐ŸŽ›๏ธ Custom Thresholds Fine-tune every metric for your specific use case
๐Ÿš€ Background Isolate Non-blocking analysis for large images
๐ŸŒ Web Support Runs on Flutter web โ€” desktop, mobile, and browser
๐Ÿ“ฆ Encoded & Decoded Input Accept raw bytes or pre-decoded image package objects

๐Ÿ“ฆ Installation

flutter pub add image_quality_guard
import 'package:image_quality_guard/image_quality_guard.dart';

๐Ÿš€ Quick Start

Pass encoded image bytes from a camera, gallery picker, file, or network response. Analysis runs on a background isolate โ€” your UI stays responsive:

import 'dart:typed_data';
import 'package:image_quality_guard/image_quality_guard.dart';

final result = await ImageQualityGuard.analyze(imageBytes);

if (result.isValid) {
  // โœ… Image quality is acceptable โ€” proceed with upload or processing
} else {
  // โŒ Issues detected
  for (final issue in result.issues) {
    print(issue);
  }
}

โš ๏ธ Invalid or unsupported image bytes throw an ImageDecodeException.


Alternative API: ImageQualityValidator

A convenience wrapper that returns QualityResult with typed sub-results:

final validator = ImageQualityValidator();
final result = await validator.validate(imageBytes);

if (result.isValid) {
  // Proceed with the upload or recognition flow.
} else {
  print(result.errorMessage);
}

๐Ÿท๏ธ Presets

Choose a preset matching your capture scenario:

final guard = ImageQualityGuard.analyze(
  imageBytes,
  config: ImageQualityConfig.documentScanning,
);
Preset Intended Use Blur Min Bright Max Bright Min Contrast
๐Ÿชช ImageQualityConfig.cardScanning IDs, bank cards, licenses 80 35 230 40
๐Ÿ“„ ImageQualityConfig.documentScanning Forms, receipts, printed text 120 45 215 55
๐Ÿ“ท ImageQualityConfig.photoCapture High-quality photo capture 200 30 235 45
๐Ÿ˜Š ImageQualityConfig.relaxed Challenging lighting / low-quality cameras 50 25 240 30
โœ”๏ธ ImageQualityConfig.strict Strict quality requirements 250 50 200 65
๐Ÿ“ฑ ImageQualityConfig.mobile Balanced defaults for mobile (no downsampling limit change) 100 40 220 50

๐ŸŽ›๏ธ Custom Thresholds

Override individual metrics for application-specific needs:

final guard = ImageQualityGuard.analyze(
  imageBytes,
  config: ImageQualityConfig(
    blurThreshold: 150,
    minBrightness: 50,
    maxBrightness: 210,
    minContrast: 60,
  ),
);
Parameter Description Range Default
blurThreshold Higher = sharper image required 1โ€“โˆž 100.0
minBrightness Below = too dark (0โ€“255 luminance) 0โ€“255 40.0
maxBrightness Above = too bright/overexposed (0โ€“255) 0โ€“255 220.0
minContrast Higher = more luminance variation required 0โ€“โˆž 50.0
maxAnalysisDimension Longest side for analysis (px); 0 = full res 0โ€“โˆž 1280

๐Ÿ’ก Tip: Quality thresholds are heuristics. Calibrate them with representative images from your target devices and environments.


๐Ÿ”Ž Individual Checks

Run specific checks independently:

final validator = ImageQualityValidator();

// ๐Ÿ” Blur check
final blur = validator.checkBlur(imageBytes);
print('Variance: ${blur.variance}, Sharp: ${!blur.isBlurry}');

// ๐Ÿ’ก Brightness check
final brightness = validator.checkBrightness(imageBytes);
print('Level: ${brightness.level}, Avg: ${brightness.averageBrightness}');

// ๐ŸŽจ Contrast check
final contrast = validator.checkContrast(imageBytes);
print('Score: ${contrast.contrastScore}, Good: ${contrast.hasGoodContrast}');

For images already decoded with package:image, use the *FromImage variants to avoid re-decoding:

final blur = validator.checkBlurFromImage(decodedImage);
final brightness = validator.checkBrightnessFromImage(decodedImage);
final contrast = validator.checkContrastFromImage(decodedImage);

๐Ÿ“Š Result Data

ImageQualityResult (from ImageQualityGuard)

Property Type Description
isValid bool All checks passed
blurScore double Laplacian variance (higher = sharper)
brightness double Average luminance (0โ€“255)
contrast double Luminance standard deviation
originalWidth / originalHeight int Decoded resolution
analyzedWidth / analyzedHeight int Resolution fed to detectors
processingTimeMs int Analysis time in ms
issues List<String> Human-readable failed checks
wasDownsampled bool Whether the image was resized

QualityResult (from ImageQualityValidator)

Property Type Description
isValid bool All checks passed
issues List<String> All detected issues
errorMessage String? First issue, or null when valid
blurResult BlurResult Variance, confidence, threshold, blur status
brightnessResult BrightnessResult Average brightness, thresholds, classification
contrastResult ContrastResult Contrast score, threshold, pass status

๐Ÿ—๏ธ Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚              Your Flutter UI Isolate              โ”‚
โ”‚                                                    โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚ ImageQualityGuard โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚  Background Isolate โ”‚
โ”‚  โ”‚ .analyze(bytes)   โ”‚ send  โ”‚                    โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚  1. Decode image    โ”‚  โ”‚
โ”‚                             โ”‚  2. Downsample*     โ”‚  โ”‚
โ”‚  โ—€โ”€โ”€ ImageQualityResult โ”€โ”€โ”€โ”€โ”‚  3. Blur analysis   โ”‚  โ”‚
โ”‚     (serializable)          โ”‚  4. Brightness check โ”‚  โ”‚
โ”‚                             โ”‚  5. Contrast check   โ”‚  โ”‚
โ”‚                             โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

*Only when image exceeds maxAnalysisDimension (default 1280px).


๐Ÿงฎ How It Works

All three checks share the same pipeline: decode โ†’ convert to grayscale โ†’ measure. The image is converted to a flat 8-bit luminance buffer (one byte per pixel) and every metric is computed from that buffer alone.

Processing Pipeline

Raw Image Bytes
      โ”‚
      โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Decode (image   โ”‚   โ†’ img.Image (RGBA)
โ”‚  package)        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚
         โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Luminance Extraction    โ”‚   โ†’ Uint8List (0โ€“255, one byte/pixel)
โ”‚  Rec. 601 luma:          โ”‚     0.299R + 0.587G + 0.114B
โ”‚  0.299R + 0.587G + 0.114Bโ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
         โ”‚
    โ”Œโ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
    โ”‚    โ”‚            โ”‚
    โ–ผ    โ–ผ            โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Blur โ”‚โ”‚Brightnessโ”‚โ”‚  Contrast  โ”‚
โ”‚      โ”‚โ”‚          โ”‚โ”‚            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ” Blur Detection โ€” Laplacian Variance

Blur is measured using the Laplacian variance method, a well-established edge-detection technique:

  1. Apply a Laplacian filter โ€” For every non-border pixel, compute the edge response:

    response = 4 ร— center โˆ’ up โˆ’ down โˆ’ left โˆ’ right
    

    This 4-neighbour Laplacian highlights regions of rapid intensity change (edges).

  2. Calculate variance โ€” The variance of all Laplacian response values is computed using Welford's online algorithm (no per-pixel list is allocated, keeping memory constant regardless of image size):

    • High variance โ†’ many strong edges โ†’ image is sharp โœ…
    • Low variance โ†’ edges are smoothed out โ†’ image is blurry โŒ
  3. Compare against threshold โ€” If variance < blurThreshold, the image is classified as blurry. Higher blurThreshold values require a sharper image.

  4. Confidence score โ€” A confidence value (0.0โ€“1.0) indicates how far the variance is from the threshold. Images far from the threshold have high confidence; borderline images have lower confidence.

// Conceptual example of what happens internally:
//
// For a sharp image (many crisp edges):
//   Laplacian responses: [120, 180, 95, 210, ...]  โ†’ Variance: ~8500  โœ… Sharp
//
// For a blurry image (smoothed edges):
//   Laplacian responses: [8, 3, 12, 5, ...]        โ†’ Variance: ~12    โŒ Blurry

Why Laplacian? Unlike simple gradient methods, the Laplacian is isotropic (detects edges in all directions equally) and is widely used in camera auto-focus systems, making it a natural choice for general-purpose sharpness detection.


๐Ÿ’ก Brightness Analysis

Brightness is the mean (average) luminance across all pixels:

  1. Each pixel is converted to grayscale using the Rec. 601 luma formula: 0.299R + 0.587G + 0.114B
  2. The average of all luminance values is calculated on a 0โ€“255 scale
  3. The result is classified:
    • Below minBrightness โ†’ Too dark ๐ŸŒ‘
    • Above maxBrightness โ†’ Too bright โ˜€๏ธ
    • Within range โ†’ Optimal โœ…

๐ŸŽจ Contrast Measurement

Contrast is the population standard deviation of pixel luminance values:

  1. All luminance samples are analyzed in a single pass
  2. Standard deviation is computed incrementally using Welford's algorithm
  3. Classification:
    • Low standard deviation โ†’ Flat histogram, poor contrast (washed out)
    • High standard deviation โ†’ Wide tonal range, good contrast (vivid)

๐Ÿง  Memory Efficiency

Both brightness and contrast (and blur via the isolate path) share a key optimization: Welford's online algorithm accumulates mean and variance incrementally. The package never materializes a per-pixel list of intermediate values, so memory stays constant regardless of image size.


๐Ÿ“ฑ Example App

An interactive Flutter example with camera/gallery input, every preset, custom threshold sliders, image preview, and a focused results view:

cd example
flutter run

๐ŸŒ Web Demo

The example app supports Flutter web. Visit the live demo to explore all features interactively in your browser:

๐Ÿ”— Web Demo

The web version includes image preview, quality profile selection, custom threshold sliders, and real-time results visualization.

Deployment

The web app is automatically deployed to GitHub Pages on every push to main via .github/workflows/deploy.yml.

The workflow uses GitHub's official Pages actions (configure-pages, upload-pages-artifact, deploy-pages) and auto-enables GitHub Pages on the repository.

To deploy manually:

cd example
flutter build web --base-href /image_quality_guard/ --release

Then upload example/build/web/ to GitHub Pages (Settings โ†’ Pages โ†’ Source โ†’ GitHub Actions).


โšก Performance

  • Analysis runs locally and synchronously after image decoding.
  • Very large images can take noticeable time โ†’ resize camera images or use maxAnalysisDimension to auto-downsample.
  • ImageQualityGuard.analyze() runs everything on a background isolate, keeping your UI smooth.
  • Images are decoded once and reused across all checks.

๐Ÿ› ๏ธ Error Handling

All errors extend ImageQualityException:

try {
  final result = await ImageQualityGuard.analyze(imageBytes);
} on ImageDecodeException {
  // โŒ Unsupported or corrupted file
} on ImageAnalysisException {
  // โŒ Unexpected analysis failure
} on ImageIsolateException {
  // โŒ Background isolate could not start
}

When using ImageQualityValidator, decode errors are thrown as ArgumentError for backward compatibility.


๐Ÿ“„ License

BSD 3-Clause License โ€” see LICENSE.

Libraries

image_quality_guard
A Flutter/Dart package to detect blur, brightness, and contrast issues in images with configurable thresholds.