Image Quality Guard
Detect blurry, poorly lit, and low-contrast images before they enter your upload, scanning, or recognition workflow.
๐ฏ 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
Main API: ImageQualityGuard (recommended)
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:
-
Apply a Laplacian filter โ For every non-border pixel, compute the edge response:
response = 4 ร center โ up โ down โ left โ rightThis 4-neighbour Laplacian highlights regions of rapid intensity change (edges).
-
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 โ
-
Compare against threshold โ If variance <
blurThreshold, the image is classified as blurry. HigherblurThresholdvalues require a sharper image. -
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:
- Each pixel is converted to grayscale using the Rec. 601 luma formula:
0.299R + 0.587G + 0.114B - The average of all luminance values is calculated on a 0โ255 scale
- The result is classified:
- Below
minBrightnessโ Too dark ๐ - Above
maxBrightnessโ Too bright โ๏ธ - Within range โ Optimal โ
- Below
๐จ Contrast Measurement
Contrast is the population standard deviation of pixel luminance values:
- All luminance samples are analyzed in a single pass
- Standard deviation is computed incrementally using Welford's algorithm
- 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
maxAnalysisDimensionto 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.