Skip to content
ashokkmtPublic

About

A modern Android image optimizer powered by Flutter and Rust.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Shrinky: Native Image Optimization Pipeline

An Android-first, performance-focused image optimization and editing pipeline built with Flutter and Rust. Inspired by Squoosh, engineered for mobile.

Shrinky is not just an image editor—it is a robust image processing engine designed to deliver instant previews, deferred high-quality exports, and aggressive memory management. By combining a beautiful cross-platform Flutter UI with a blazingly fast, multi-threaded Rust backend, it brings desktop-class image processing directly to local device hardware without relying on the cloud.


Project Vision

Modern mobile devices handle multi-megabyte images constantly, yet performing basic crop, resize, and compression operations locally often results in sluggish UIs, Out-Of-Memory (OOM) crashes, or poor output quality.

Shrinky solves this through a Proxy-Image Architecture:

  1. Local Only: No cloud servers. All processing stays on-device for total privacy and zero latency.
  2. Rust Engine: The heavy lifting (decoding, encoding, scaling, compressing) is written in Rust, leveraging memory safety and aggressive multi-threading.
  3. Flutter UI: The interface is built in Flutter for smooth, 60fps interaction and cross-platform flexibility.
  4. Proxy Workflow: When an image loads, a lightweight proxy is generated. The UI manipulates this proxy instantly. Only when the user hits "Save" does the Rust engine apply the operations to the full-resolution original image using high-quality (Lanczos3) resampling.

Current production efforts focus heavily on delivering the highest quality JPEG optimization experience before expanding codec support.


Features

  • Geometric Edits: Crop, Resize, Rotate, Flip, and Straighten.
  • Advanced Compression: MozJPEG (Production), OxiPNG, WebP, and AVIF encoding (Experimental).
  • Target Size Optimization: A binary-search optimization loop to dynamically compress an image to hit a specific file size (e.g., "Compress to exactly 500 KB").
  • Iterative Workflow: Infinite Undo/Redo architecture without memory bloat.
  • Export & Share: Export directly to device storage or trigger native sharing.
  • Professional Diagnostics: Deep, configurable telemetry, JSONL performance logging, and structured device benchmarking suites.

Codec Support Status

Format Status Notes
JPEG (MozJPEG) Production Ready Primary optimized workflow
PNG (OxiPNG) Experimental Internal builds only
WebP Experimental Internal builds only
AVIF Experimental Internal builds only
  • JPEG is the primary production optimization pipeline.
  • Experimental codecs remain available for internal testing and benchmarking.
  • Additional optimization work (compression speed, memory efficiency, quality tuning) is ongoing before production release.

Release Philosophy

Production builds expose only features that satisfy stringent criteria:

  • Performance targets
  • Memory targets
  • Device validation
  • Benchmark coverage
  • Stability requirements

Experimental codecs remain available in development builds until they meet these production standards.


Architecture Overview

User
↓
Flutter UI
↓
Application Layer (Riverpod)
↓
FFI Bridge (flutter_rust_bridge)
↓
Rust Processing Engine
↓
Proxy Pipeline (Triangle Resample) -> Preview
Export Pipeline (Lanczos3 Resample) -> Export Cache
↓
Storage / Native Share
  • Flutter UI: Handles user input, gesture recognition (panning, zooming), and displays the proxy preview.
  • Application Layer: Managed via Riverpod. Orchestrates edit sessions, undo/redo history stacks, and background export queues.
  • FFI Bridge: Generated by flutter_rust_bridge. Maps Dart classes to Rust structs with zero-copy memory transfers where possible.
  • Rust Processing Engine: Owns the EditorEngine state, holding the original_image and proxy_image.
  • Export Cache: Prevents duplicate processing by caching final outputs until the edit stack changes.

Native Processing APIs

The Rust backend exposes a powerful Native Processing API. These APIs run off the UI thread and utilize the image and custom codec crates to bypass slow Dart implementations.

EditorEngine

The core stateful engine. Kept alive in Rust memory, referenced via an opaque pointer in Dart.

  • load_from_path(path: String): Instantiates the engine, parses EXIF orientation, decodes the original, and generates the lightweight UI proxy.
  • apply_operations(operations: Vec<EditOperation>, is_export: bool): Executes the edit chain. If is_export is false, it uses fast Triangle scaling on the proxy. If true, it uses Lanczos3 scaling on the full-resolution original.
  • encode_image(codec: ImageCodec, quality: f32): Encodes the current state buffer into the final format.
  • encode_image_to_target_size(codec: ImageCodec, target_kb: i32): Executes a multi-pass compression loop to hone in on the requested target file size.

Image Processing Pipeline

  1. Selection: User selects an image.
  2. Proxy Generation: The Rust engine decodes the JPEG, applies EXIF rotation, and creates a downscaled proxy bounded to 1440px.
  3. Editing Session: As the user crops or resizes, apply_operations(..., is_export: false) is called. The proxy updates instantly.
  4. Preview Rendering: The proxy is quickly encoded to JPEG at 70% quality and piped back over FFI to the Flutter Image.memory widget.
  5. Export Request: Upon saving or sharing, a background isolate triggers apply_operations(..., is_export: true) on the original image, followed by high-quality encoding.

Editing Architecture

The application utilizes an immutable history stack.

Instead of storing full image buffers for every Undo step, the application stores an array of EditOperation enums. When the user clicks "Undo", the state pointer moves back, and the Rust engine simply replays the operations from the original proxy baseline. This allows infinite undo/redo without causing an OOM crash.


Telemetry & Diagnostics

The project features a mature, event-driven telemetry architecture designed to measure real-world mobile performance.

  • Event Model: Telemetry is dispatched to a global Stream.
  • JSONL Logs: Persisted to disk to allow deep analysis of cache hit rates, memory consumption, and encoding times.
  • Rust Tracing: The Rust engine utilizes the tracing crate, seamlessly forwarding native spans (like jpeg_decode_ms) back to Dart.
  • Session IDs & Schemas: Every event contains a schema version and a unique Session ID to correlate operations across long benchmarks.

Build Modes & The Makefile

The project provides a unified Makefile as the single operational entry point. Do not guess commands. Use the Makefile.

Command Purpose
make setup Installs Flutter/Rust dependencies and generates FFI bindings.
make run Daily development with hot-reload. Clean UI, no overhead.
make internal Engineering mode. Debug UI overlay enabled with basic metrics.
make profiling Deep investigation mode. Heavy JSONL logging and Rust tracing enabled.
make test Runs all unit tests.
make integration-test Runs standard integration tests.
make benchmark Runs the automated device benchmark suite (Core + Stress + Soak).
make release Builds both the production APK and App Bundle.
make logs Pulls performance telemetry logs from the connected Android device.
make clean Cleans Flutter build directories and temporary outputs.
make rebuild Performs a clean-all, setup, and dependency regeneration.

Note: You can pass a specific device using the DEVICE flag: make run DEVICE=emulator-5554.

Manual Equivalents (Without Make)

If you must run manually, the Telemetry architecture is driven by the --dart-define=TELEMETRY_MODE flag.

  • Internal Mode: flutter run --debug --dart-define=TELEMETRY_MODE=internal
  • Profiling Mode: flutter run --profile --dart-define=TELEMETRY_MODE=profiling
  • FRB Generation: flutter_rust_bridge_codegen generate
  • Production Build: flutter build apk --release (No custom product flavors exist)

Testing & Benchmarks

Because this project is heavily performance-focused, it relies on an ADB-powered automated on-device benchmarking framework to prevent regressions, measure real-world performance on physical Android devices, and validate cache, history, and memory behavior.

Unit & Integration Tests

  • Unit Tests: Run via make test. Validates UI logic, Riverpod providers, and routing.
  • Integration Tests: Run via make integration-test. Standard Flutter integration_test scripts to validate complete user flows on a device. Alternatively, run directly on a specific device:
    flutter test integration_test/device_benchmark_test.dart -d <device_id>

Automated Benchmark Pipeline

The Benchmark Framework acts as a headless driver. It loads a massive test dataset and forces the Rust engine through thousands of operations while tracking memory, RSS peaks, and execution times.

The recommended way to run benchmarks is through the automated pipeline, which handles building the APK, pushing the dataset, running the tests via ADB, pulling the results, and generating a report:

dart run scripts/adb_benchmark_runner.dart

Flags:

  • --skip-build — Skip dataset generation, APK build, and push (use when re-running for faster iteration)
  • --stress — Include edit chain stress + undo/redo torture tests
  • --soak — Include 5-minute long session soak test to detect memory leaks

Examples:

# Re-run without rebuilding
dart run scripts/adb_benchmark_runner.dart --skip-build

# Full stress test run
dart run scripts/adb_benchmark_runner.dart --stress --soak

Note: You can also trigger these via the Makefile with make benchmark, make stress, and make soak.

What Gets Tested

  • Core Benchmarks: Image Load, Preview Generation, Undo/Redo latency, Export Generation, Target Size optimization, Resize downscaling, and Cache Validation.
  • Stress Tests: Rapid edit chains (to track latency scaling and memory growth) and Undo/Redo torture cycles.
  • Soak Tests: A 5-minute continuous loop of edits, undos, redos, and exports to detect performance degradation over time.

Regression Detection & Reports

The framework automatically compares new results against the stored baseline (Core_Pipeline_latest.json). It fails if:

  • P95 Latency increases by > 15%
  • Peak Memory increases by > 20%

To reset the baseline, simply delete Core_Pipeline_latest.json from the output directory.

Reports are generated in logs/adb_benchmarks/ in JSON, CSV, and Markdown formats. A combined Device_Benchmark_Report.md is also generated which includes device specs and thermal data (polled every 15 seconds via ADB to identify thermal throttling).

Generating Datasets

To synthesize test images without running benchmarks, use:

dart run scripts/generate_dataset.dart

This generates images (1MP to 64MP JPEGs, and PNGs) in integration_test/assets/dataset/.

Note on Codecs: Experimental codecs are benchmarked internally and may be visible during make run, make internal, and make profiling. Benchmark results for these codecs should not be interpreted as production guarantees.


Maintenance & Cleanup

If the build cache becomes corrupted, utilize the modular cleanup commands:

  • make clean-flutter: Runs flutter clean.
  • make clean-rust: Cleans Cargo targets.
  • make clean-build: Deletes .dart_tool, build/, .flutter-plugins, and generated plugin registries.
  • make clean-all: Performs a total repository wipe.
  • make logs-clear: Wipes all accumulated telemetry JSONL logs from the connected physical device via ADB.

Troubleshooting

FRB Generation Failures

Ensure you have the flutter_rust_bridge_codegen binary installed globally via cargo install flutter_rust_bridge_codegen.

Telemetry Missing

Telemetry is disabled in standard make run. You must execute make profiling to generate JSONL logs, and then execute make logs to pull them to your laptop.

Android Gradle Issues

If you encounter weird build cache issues on Android, run make rebuild.


Developer Cheat Sheet

Task Command
Setup Project make setup
Run Clean make run
Run w/ Diagnostics make internal
Run w/ Deep Tracing make profiling
Build Production make release
Test & Benchmark make benchmark
Pull Device Logs make logs
Reset Project make rebuild

License

Copyright © 2026 Ashok Kumawat

Licensed under the Apache License, Version 2.0. See the LICENSE file for details.

About

A modern Android image optimizer powered by Flutter and Rust.

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages