flutter_image_compress_lite 2.9.3 copy "flutter_image_compress_lite: ^2.9.3" to clipboard
flutter_image_compress_lite: ^2.9.3 copied to clipboard

Minimal, legacy-free image compression for Flutter (Android + iOS) — no CocoaPods, SPM-only, minimal native deps. Replacement for flutter_image_compress.

2.9.3 #

  • Android: wide-gamut sources are color-managed to sRGB at decode time (inPreferredColorSpace, API 26+). BitmapFactory kept a Display-P3 or Adobe-RGB source in its own color space and neither Bitmap.compress() nor the HeifWriter/AvifWriter path tagged the output with a matching ICC profile reliably, so a non-color-managed viewer read those pixels as sRGB and showed the result oversaturated — most visible with photos from devices that shoot Display P3 by default. Same fix as upstream #407, applied at our single decode-options helper instead of three call sites.
  • iOS: the render format now pins preferredRange = .standard, so scaling and rotating no longer produce an extended-range bitmap on a wide-gamut device — the same oversaturation as above, and the sRGB output that keepExif's dropped ProfileName (2.9.1) already assumed. Matches upstream #335.
  • iOS: HEIC encoding falls back to sRGB instead of CGColorSpaceCreateDeviceRGB() when the source image carries no color space — a device space has no profile, so the written HEIC held pixel values no viewer could interpret. Matches upstream #358; a HEIC encode that produces no data is now logged under showNativeLog.

2.9.2 #

  • iOS: fix a Swift 6 strict-concurrency error introduced in 2.9.1 — ExifKeeper.sourceOnlyKeys was a stored static let of [CFString], which is not Sendable ("Static property 'sourceOnlyKeys' is not concurrency-safe"), so the package did not compile under swiftLanguageMode: .v6. It is a computed property now, with no static storage.

2.9.1 #

  • Android: keepExif: true skips the same class of tags — dimensions (ImageWidth/ImageLength/PixelXDimension/PixelYDimension/DefaultCropSize), pixel-buffer and colour description (BitsPerSample, ColorSpace, Compression, PhotometricInterpretation, SamplesPerPixel, PlanarConfiguration, YCbCrSubSampling, YCbCrPositioning), and offsets into the source bytes (StripOffsets, StripByteCounts, RowsPerStrip, JPEGInterchangeFormat[Length], thumbnail width/length, sub-file type, and the ORF/DNG raw-container tags). Previously every tag but Orientation was copied, so a scaled output carried the original's dimensions and thumbnail offsets into a file that no longer had them.
  • iOS: keepExif: true no longer copies metadata that describes the source pixel buffer into the re-encoded output — PixelWidth/PixelHeight/FileSize, ProfileName, ColorModel, Depth, HasAlpha, IsFloat, IsIndexed and the EXIF PixelXDimension/PixelYDimension are dropped, and TIFF.Orientation is reset alongside the top-level one. A Display-P3 source wrote its profile name into an sRGB JPEG (wrong colors in color-managed viewers), a transparent PNG source wrote HasAlpha into a JPEG, and a viewer preferring the TIFF orientation tag rotated the image a second time. Matches upstream 2.5.0; no new dependencies (ImageIO only).
  • Documented the Android host-app JVM-target requirement in the README (android.builtInKotlin=true, or set the target for all modules from the root build file) — the plugin deliberately carries no compileOptions/jvmTarget of its own.

2.9.0 #

  • Requires Dart 3.13 / Flutter 3.47 (sdk: ^3.13.0, flutter: >=3.47.0) — the toolchain this package is now built and tested against.
  • Removed android/gradle/wrapper/ — the plugin has no gradlew and no example app, and is built by the consuming app's wrapper, so the file was never read.

2.8.1 #

  • iOS error message parity with Android: BAD_ARGS now carries a message (was null); WRITE_FAILED includes the target path.
  • iOS debug log label parity with Android: "width/height""src width/src height" (matches the "dst width/dst height" lines already emitted by both platforms).

2.8.0 #

  • New: CompressFormat.avif output. Android-only (via androidx.heifwriter.AvifWriter, added in 1.1.0), requires API 34+ (Android 14) for the mandated MediaCodec AV1 encoder; older Android and iOS throw UnsupportedError. iOS has no public AVIF encoder — Apple ships decode only. AVIF decoding already works everywhere on capable devices without plugin changes (Android API 31+, iOS 16+).

2.7.2 #

  • Android: keepExif=true now works for PNG (API 30+ / Android 11) and WebP (API 31+ / Android 12) output, not just JPEG — framework ExifInterface.saveAttributes() gained those formats on those API levels. Combinations we can't honor (HEIC always, PNG/WebP on older devices) now log a Log.w explaining why rather than silently dropping EXIF. No new native deps.
  • Android: HeifWriter.close() moved into a finally block so a HeifWriter failure between start and stop no longer leaks the encoder; final transformed bitmap is now recycled after encode.
  • Both: compressAndGetFile mkdirs the target path's parent directory before writing, so callers don't have to pre-create it.

2.7.1 #

  • Android: internal — thread pool sized to Runtime.availableProcessors() (was hardcoded 8), source bitmap recycled after scale/rotate to lower peak memory, EXIF-read failures now logged under showNativeLog.
  • iOS: internal — combined scale + rotate into a single UIGraphicsImageRenderer pass. Drops the intermediate scaled bitmap when a rotation is also applied and resamples source pixels only once, which slightly improves interpolation quality.

2.7.0 #

  • Breaking: remove autoCorrectionAngle. Both platforms now unconditionally auto-orient from EXIF; the flag was Android-only (iOS's UIImage always auto-orients regardless), so setting it to false on iOS was a silent no-op. Callers on the default (true) are unaffected.
  • Breaking: remove CompressError; Dart-side input-validation failures now throw the standard ArgumentError. Consistent with the UnsupportedError already thrown for platform-support failures — everything Dart-side is now an Error, everything native-side is a PlatformException.

2.6.2+1 #

  • Android internal: drop unused CompressFormat.typeName; move bitmapFormat to the enum constructor.

2.6.2 #

  • Android: JPEG decode now uses ARGB_8888 (was RGB_565) — eliminates gradient banding on the compressed output. Cost: ~2× decode memory; OOM still surfaces as COMPRESS_ERROR.
  • Android: keepExif: true now copies every EXIF tag the framework ExifInterface knows about (reflectively enumerated), matching the iOS behavior. Only TAG_ORIENTATION is skipped since pixels are already rotated. Previously only 18 curated tags were preserved.
  • Android: autoCorrectionAngle: true now honors all 8 EXIF orientations including the compound flip variants (2 FLIP_HORIZONTAL, 4 FLIP_VERTICAL, 5 TRANSPOSE, 7 TRANSVERSE). Previously these were treated as no-rotation. iOS already handled these via UIImage's built-in orientation. Common cases (1/3/6/8) are unchanged.

2.6.1 #

  • Android: fix temp-file leak in ExifKeeper on keepExif: true JPEG calls — the UUID-named cache file was not deleted after re-reading. Minor Kotlin refactor in replyCatching; no behavior change.

2.6.0+2 #

  • Docstring: platform-behavior notes for autoCorrectionAngle and keepExif on the class docstring; misc doc cleanup.

2.6.0+1 #

  • Cosmetic: Dart source cleanup. No behavior change.

2.6.0 #

  • Breaking: compressWithFile, compressAndGetFile, and compressAssetImage return non-nullable types (Uint8List, XFile, Uint8List). The native sides have always thrown PlatformException on failure since 2.5.2 and never delivered null on the happy path — the ? was leftover from the pre-2.5.2 contract. Source-only break for callers using ?? fallback or if (result != null).
  • Breaking: compressAssetImage now throws CompressError for an empty asset, to match the empty-bytes handling in compressWithList (was: returned null).

2.5.5 #

  • iOS: Xcode floor raised to 26.4.1 (Swift 6.3 toolchain). Package.swift declares swift-tools-version: 6.3; no code change.
  • Build: Gradle wrapper 9.6.0 → 9.6.1.

2.5.4+1 #

  • README: added Android SDK to the list of "latest toolchains" the package is built against, and reordered the list to mirror the comparison table (Flutter → Android trio → Xcode).

2.5.4 #

  • Environment: declared minima moved to Flutter 3.44.0 and Dart 3.12.0. The plugin's android/build.gradle.kts applies only com.android.library (no kotlin-android), which already required AGP 9's built-in Kotlin support — AGP 9 is the default in Flutter 3.44+, not earlier. The previous >=3.41.0 floor was honored by pub.flutter-io.cn but not actually buildable without manual AGP 9 opt-in on the host. The new floor matches what the build always required. Follows the Flutter built-in Kotlin migration guide for plugin authors.
  • iOS internal: replaced NSLog with the modern os.Logger API (subsystem com.qeepcologne.flutter_image_compress_lite, categories compress and scale). When showNativeLog is on, output is now filterable by subsystem/category in Console.app and goes through the ring-buffered unified logging system. No observable behavior change.
  • Android internal: routed all exception logging through Log.w(tag, msg, throwable) instead of e.printStackTrace() (which writes to stderr and often gets swallowed by zygote). Also pulled the "flutter_image_compress" tag literal into a LOG_TAG constant. Stack traces from replyCatching and the EXIF copy fallback now appear in logcat under the same tag as everything else.

2.5.3 #

  • Android: fix 2.5.2 build break — ExifInterface.TAG_PHOTOGRAPHIC_SENSITIVITY was used by the EXIF keeper, but that constant lives only on androidx.exifinterface.media.ExifInterface; the framework android.media.ExifInterface (which we use since 2.2.0) only exposes the deprecated TAG_ISO_SPEED_RATINGS. Reverted to that with @Suppress("DEPRECATION"). Same tag-id (34855), same wire bytes.

2.5.2 #

  • iOS: encoder-returns-nil edge cases (missing cgImage, HEIF encoder failure) now surface as COMPRESS_ERROR PlatformException instead of resolving the Dart Future to nil — keeps the non-nullable Future<Uint8List> return type of compressWithList honest. Unreachable in practice for normal inputs.
  • Internal: swapped EXIF string literals for framework constants in both EXIF keepers (Kotlin ExifInterface.TAG_*, Swift CGImagePropertyOrientation.up.rawValue) and dropped the now-unreachable Outcome.null enum case on iOS. No observable behavior change.

2.5.1 #

  • iOS: fix compression producing output 4–9× larger than the requested minWidth/minHeight (and JPEG encoding correspondingly slower) on Retina devices. The resize and rotate steps used UIGraphicsImageRenderer(size:) without a format, which defaulted to the main screen's UIKit scale (2× / 3×) — so the renderer's actual pixel bitmap was target × screenScale. Now forces format.scale = 1. Reported in #4.
  • Android: bumped the Gradle wrapper 9.5.1 → 9.6.0.

2.5.0 #

  • BREAKING: removed the inSampleSize parameter from compressWithList, compressWithFile, and compressAndGetFile. It was an Android-only BitmapFactory.Options knob (iOS ignored it) from the 32-bit ART / 128 MB-heap era. On modern Android (API 26+) bitmaps live in native heap and devices with 50 MP cameras have 8–12 GB RAM, so the memory savings no longer pay for the leaky abstraction or the silent quality loss when callers pick a value that under-samples small inputs. The OutOfMemoryError catch added in 2.4.0 still surfaces decode-time OOM as a COMPRESS_ERROR PlatformException. Callers that passed the argument need to drop it.
  • BREAKING: removed CompressFormat.nativeValue. It was a getter that just returned index — the wire value is the enum's built-in ordinal. Callers reading format.nativeValue need to switch to format.index.
  • BREAKING: CompressError now implements Exception instead of extending Error. The failures it carries (empty bytes, missing source file, same source-and-target path) are recoverable user-input conditions, not programming bugs. Code using try { … } on Exception will now catch it (previously had to use on Error or the bare catch (e)).
  • Internal: inlined the FlutterImageCompressValidator class into a private top-level function in the main library — one less file, one less indirection, no public-API change.
  • iOS internal: dropped the dead @objc on ImageCompressPlugin.showLog. No Obj-C consumer existed; the annotation was carried over from the 2.2.0 Obj-C→Swift rewrite. The class itself is still @objc(ImageCompressPlugin) because Flutter's plugin discovery requires it.

2.4.4 #

  • Android: unified the wire-format error code with iOS — UNKNOWN_FORMAT is renamed to BAD_ARGS, and a malformed channel argument list (wrong type, missing element, null) now also surfaces as BAD_ARGS instead of crashing the executor thread and hanging the Dart Future. Real callers can't trip this — the Dart side constrains both shape and enum range — so the rename is documentation-only in practice.

2.4.3 #

  • Docs-only — added the missing library / CompressFormat / CompressError doc comments, an example/main.dart, and a note on the class-level doc that minWidth / minHeight are lower bounds on the output (image is downscaled with aspect ratio preserved so both axes end up ≥ the requested minimum; never upscaled). No API or behavior change.

2.4.2 #

  • Removed the debug-mode filename-extension assert (and the CompressFormat.suffixes field that backed it). The check only fired in debug; release builds were never affected.

2.4.1 #

  • Accept .heif as a valid target extension for HEIC encoding alongside .heic — same container bytes, different naming convention. Previously tripped the debug-mode filename assert.

2.4.0 #

  • BREAKING: removed the androidOomRetries parameter from compressWithFile and compressAndGetFile. The old retry-on-OOM logic was Android-only and silently produced an empty result when retries exhausted; now an OutOfMemoryError surfaces as a COMPRESS_ERROR PlatformException like other native failures. Callers that passed the argument need to drop it.

2.3.1 #

  • Docs and metadata only — README cleanup, package description, and a fork copyright line. No API or behavior changes.

2.3.0 #

Internal cleanup — no public API changes.

  • Android (behavior change): read/decode/write failures now throw a PlatformException instead of silently resolving to null, matching iOS. New wire codes: FILE_NOT_FOUND, BAD_IMAGE, WRITE_FAILED, plus a catch-all COMPRESS_ERROR. Callers that previously branched on a null result will now see an exception for genuinely broken input. See the README "Errors" section.
  • Android: the three compress* handlers now share a single index-driven CompressArgs parser (mirroring the iOS CompressParams) and a replyCatching helper, replacing the per-handler positional unpacking and try/catch. No wire-format change.
  • Dart: compressWithFile / compressAndGetFile now check source existence with async File.exists() instead of existsSync(), so the entry points no longer block the isolate on filesystem I/O.
  • iOS: migrated the method-channel handlers to Swift structured concurrency (Swift 6.2). The manual DispatchQueue.global(qos:).async { … } hop is replaced by a Task calling a single @concurrent run(_:) worker; the three near-identical handlers collapse into a Sendable Request parser plus that one worker. Arguments are now read out of FlutterMethodCall synchronously on the calling thread, so no non-Sendable Flutter type crosses the concurrency boundary. Builds under the Swift 6 language mode with strict concurrency.
  • iOS BUILD REQUIREMENT: building for iOS now requires Xcode 26+ (Swift 6.2 toolchain). The runtime floor is unchanged — still iOS 15+ (Swift concurrency back-deploys; no iOS-18-only APIs such as Mutex are used).

2.2.0 #

Internal cleanup — no public API or behavior changes.

  • iOS: rewrote the plugin in Swift. The 6 Obj-C .m files + 7 .h headers are replaced by 4 Swift files (ImageCompressPlugin.swift, Compressor.swift, ExifKeeper.swift, UIImage+Scale.swift). No __bridge casts, no manual CFRelease, no include/ public-headers folder, no // Created by cjl … fork comments. The two near-identical compressWithUIImage: / compressDataWithUIImage: methods collapse into one. File-existence and image-decode failures now surface as FlutterError (FILE_NOT_FOUND, BAD_IMAGE) instead of propagating nil. UIGraphicsBeginImageContext (deprecated since iOS 10) is replaced with UIGraphicsImageRenderer; the rotated bounding box is now computed via CGRect.applying(_:) instead of allocating a UIView.
  • Android: ResultHandler's thread pool is now owned by the plugin instance and shutdown() in onDetachedFromEngine (was a companion object Executors.newFixedThreadPool(8) that lived for the process lifetime). Re-armed on re-attach.
  • Android: dropped the androidx.exifinterface dependency in favor of the platform android.media.ExifInterface (available since API 24, our minSdk). EXIF reads/writes go through the framework class with a 6-line orientation→degrees mapping. androidx.heifwriter:heifwriter:1.1.0 remains — HEIC encoding still goes through it (no platform-native HEIC encoder exists in Bitmap.CompressFormat).
  • Android: collapsed 13 Kotlin files into 3 — ImageCompressPlugin.kt, Compressor.kt, Exif.kt — to mirror the iOS Swift layout. Dropped the unused FormatHandler interface, FormatRegister map, and the never-thrown CompressError.
  • Android: BitmapFactory decode now uses ARGB_8888 for PNG/WebP/HEIC and keeps RGB_565 for JPEG. Previously all formats decoded as RGB_565, silently dropping the alpha channel for transparency-capable outputs.
  • Android: CompressFileHandler.handle reads EXIF rotation from File(path) directly instead of loading the full file into a ByteArray first.
  • Android: unknown format index now responds with result.error("UNKNOWN_FORMAT", …) instead of result.success(null). In practice unreachable because the Dart enum can't produce an unknown index, but no longer fails invisibly if the wire format ever desyncs.
  • Android: bumped compileSdk 36 → 37 (Android 17). No minSdk change. CI environments may need the API 37 platform package installed.

2.1.1 #

  • iOS: fix iOS build failure introduced in 2.1.0 — corrected selector capitalization to HEIFRepresentationOfImage:format:colorSpace:options: (was heifRepresentationOfImage:, which doesn't exist on CIContext).

2.1.0 #

User-visible fixes:

  • BREAKING: numberOfRetries parameter on compressWithFile / compressAndGetFile renamed to androidOomRetries. The retry behavior was always Android-only (decode OOM → double inSampleSize and recurse); the new name reflects that. iOS ignores the value as before.
  • iOS: WebP encoding now throws UnsupportedError up front instead of silently returning null (decoding still works on iOS 14+).
  • iOS: HEIC encoding no longer writes through NSTemporaryDirectory() — uses heifRepresentationOfImage: directly. Removes a per-call temp-file leak.
  • Dart: validator contract is now consistent — every entry point throws UnsupportedError for unsupported encodings (previously some returned null). The validator only checks the output format; input formats are auto-detected by the native decoder.

Internal cleanup:

  • Android: introduced CompressFormat enum to replace 0/1/2/3 magic numbers throughout the handlers and FormatRegister.
  • Android: ExifKeeper ported from Java to Kotlin; settings.gradlesettings.gradle.kts.
  • Android: bumped Gradle wrapper to 9.5.0, compileSdk to 36.
  • Android: removed dead code paths (ResultHandler.replyError, ExifKeeper.copyExifToFile, duplicate Bitmap.compress extensions, System.gc() in OOM retry, pre-Marshmallow inDither branch).
  • iOS: introduced ImageCompressFormat NS_ENUM mirroring the Dart/Android enums.
  • iOS: removed dead getSystemVersion Obj-C handler (Dart only calls Android for the API 28 check).
  • Dart: dropped part/part of in favor of regular libraries with import/export; CompressFormat.nativeValue getter replaces the private _convertTypeToInt helper; default param values centralized in a private _Defaults class.

2.0.3 #

Merged flutter_image_compress + flutter_image_compress_common into a single standalone package. No federated plugin architecture, no transitive dependencies with podspecs, no CocoaPods required.

  • BREAKING: New package name flutter_image_compress_lite — change import
  • BREAKING: Remove WebP encoding on iOS (decoding works natively on iOS 14+)
  • BREAKING: Require Dart ^3.11.0, Flutter >=3.41.0, iOS 15.0+, Android minSdk 24, AGP 9+
  • iOS: SPM only, zero third-party deps
  • iOS: keepExif via native ImageIO (no Mantle)
  • Android: Kotlin DSL, removed commons-io, bumped exifinterface 1.4.2, heifwriter 1.1.0
4
likes
160
points
1.36k
downloads

Documentation

API reference

Publisher

verified publisherecift.com

Weekly Downloads

Minimal, legacy-free image compression for Flutter (Android + iOS) — no CocoaPods, SPM-only, minimal native deps. Replacement for flutter_image_compress.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

cross_file, flutter

More

Packages that depend on flutter_image_compress_lite

Packages that implement flutter_image_compress_lite