flutter_image_compress_lite 2.9.3
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+).BitmapFactorykept a Display-P3 or Adobe-RGB source in its own color space and neitherBitmap.compress()nor theHeifWriter/AvifWriterpath 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 thatkeepExif's droppedProfileName(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 undershowNativeLog.
2.9.2 #
- iOS: fix a Swift 6 strict-concurrency error introduced in 2.9.1 —
ExifKeeper.sourceOnlyKeyswas a storedstatic letof[CFString], which is notSendable("Static property 'sourceOnlyKeys' is not concurrency-safe"), so the package did not compile underswiftLanguageMode: .v6. It is a computed property now, with no static storage.
2.9.1 #
- Android:
keepExif: trueskips 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 butOrientationwas copied, so a scaled output carried the original's dimensions and thumbnail offsets into a file that no longer had them. - iOS:
keepExif: trueno longer copies metadata that describes the source pixel buffer into the re-encoded output —PixelWidth/PixelHeight/FileSize,ProfileName,ColorModel,Depth,HasAlpha,IsFloat,IsIndexedand the EXIFPixelXDimension/PixelYDimensionare dropped, andTIFF.Orientationis 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 wroteHasAlphainto 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 nocompileOptions/jvmTargetof 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 nogradlewand 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_ARGSnow carries a message (wasnull);WRITE_FAILEDincludes 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.avifoutput. Android-only (viaandroidx.heifwriter.AvifWriter, added in 1.1.0), requires API 34+ (Android 14) for the mandated MediaCodec AV1 encoder; older Android and iOS throwUnsupportedError. 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=truenow works for PNG (API 30+ / Android 11) and WebP (API 31+ / Android 12) output, not just JPEG — frameworkExifInterface.saveAttributes()gained those formats on those API levels. Combinations we can't honor (HEIC always, PNG/WebP on older devices) now log aLog.wexplaining why rather than silently dropping EXIF. No new native deps. - Android:
HeifWriter.close()moved into afinallyblock so aHeifWriterfailure betweenstartandstopno longer leaks the encoder; final transformed bitmap is now recycled after encode. - Both:
compressAndGetFilemkdirsthe 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 undershowNativeLog. - iOS: internal — combined scale + rotate into a single
UIGraphicsImageRendererpass. 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'sUIImagealways auto-orients regardless), so setting it tofalseon iOS was a silent no-op. Callers on the default (true) are unaffected. - Breaking: remove
CompressError; Dart-side input-validation failures now throw the standardArgumentError. Consistent with theUnsupportedErroralready thrown for platform-support failures — everything Dart-side is now anError, everything native-side is aPlatformException.
2.6.2+1 #
- Android internal: drop unused
CompressFormat.typeName; movebitmapFormatto the enum constructor.
2.6.2 #
- Android: JPEG decode now uses
ARGB_8888(wasRGB_565) — eliminates gradient banding on the compressed output. Cost: ~2× decode memory; OOM still surfaces asCOMPRESS_ERROR. - Android:
keepExif: truenow copies every EXIF tag the frameworkExifInterfaceknows about (reflectively enumerated), matching the iOS behavior. OnlyTAG_ORIENTATIONis skipped since pixels are already rotated. Previously only 18 curated tags were preserved. - Android:
autoCorrectionAngle: truenow honors all 8 EXIF orientations including the compound flip variants (2FLIP_HORIZONTAL, 4FLIP_VERTICAL, 5TRANSPOSE, 7TRANSVERSE). Previously these were treated as no-rotation. iOS already handled these viaUIImage's built-in orientation. Common cases (1/3/6/8) are unchanged.
2.6.1 #
- Android: fix temp-file leak in
ExifKeeperonkeepExif: trueJPEG calls — the UUID-named cache file was not deleted after re-reading. Minor Kotlin refactor inreplyCatching; no behavior change.
2.6.0+2 #
- Docstring: platform-behavior notes for
autoCorrectionAngleandkeepExifon the class docstring; misc doc cleanup.
2.6.0+1 #
- Cosmetic: Dart source cleanup. No behavior change.
2.6.0 #
- Breaking:
compressWithFile,compressAndGetFile, andcompressAssetImagereturn non-nullable types (Uint8List,XFile,Uint8List). The native sides have always thrownPlatformExceptionon failure since 2.5.2 and never deliverednullon the happy path — the?was leftover from the pre-2.5.2 contract. Source-only break for callers using?? fallbackorif (result != null). - Breaking:
compressAssetImagenow throwsCompressErrorfor an empty asset, to match the empty-bytes handling incompressWithList(was: returnednull).
2.5.5 #
- iOS: Xcode floor raised to 26.4.1 (Swift 6.3 toolchain).
Package.swiftdeclaresswift-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.0and Dart3.12.0. The plugin'sandroid/build.gradle.ktsapplies onlycom.android.library(nokotlin-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.0floor 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
NSLogwith the modernos.LoggerAPI (subsystemcom.qeepcologne.flutter_image_compress_lite, categoriescompressandscale). WhenshowNativeLogis 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 ofe.printStackTrace()(which writes to stderr and often gets swallowed by zygote). Also pulled the"flutter_image_compress"tag literal into aLOG_TAGconstant. Stack traces fromreplyCatchingand 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_SENSITIVITYwas used by the EXIF keeper, but that constant lives only onandroidx.exifinterface.media.ExifInterface; the frameworkandroid.media.ExifInterface(which we use since 2.2.0) only exposes the deprecatedTAG_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 asCOMPRESS_ERRORPlatformExceptioninstead of resolving the Dart Future to nil — keeps the non-nullableFuture<Uint8List>return type ofcompressWithListhonest. Unreachable in practice for normal inputs. - Internal: swapped EXIF string literals for framework constants in both EXIF keepers (Kotlin
ExifInterface.TAG_*, SwiftCGImagePropertyOrientation.up.rawValue) and dropped the now-unreachableOutcome.nullenum 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 usedUIGraphicsImageRenderer(size:)without a format, which defaulted to the main screen's UIKit scale (2× / 3×) — so the renderer's actual pixel bitmap wastarget × screenScale. Now forcesformat.scale = 1. Reported in #4. - Android: bumped the Gradle wrapper 9.5.1 → 9.6.0.
2.5.0 #
- BREAKING: removed the
inSampleSizeparameter fromcompressWithList,compressWithFile, andcompressAndGetFile. It was an Android-onlyBitmapFactory.Optionsknob (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. TheOutOfMemoryErrorcatch added in 2.4.0 still surfaces decode-time OOM as aCOMPRESS_ERRORPlatformException. Callers that passed the argument need to drop it. - BREAKING: removed
CompressFormat.nativeValue. It was a getter that just returnedindex— the wire value is the enum's built-in ordinal. Callers readingformat.nativeValueneed to switch toformat.index. - BREAKING:
CompressErrornow implementsExceptioninstead of extendingError. The failures it carries (empty bytes, missing source file, same source-and-target path) are recoverable user-input conditions, not programming bugs. Code usingtry { … } on Exceptionwill now catch it (previously had to useon Erroror the barecatch (e)). - Internal: inlined the
FlutterImageCompressValidatorclass 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
@objconImageCompressPlugin.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_FORMATis renamed toBAD_ARGS, and a malformed channel argument list (wrong type, missing element, null) now also surfaces asBAD_ARGSinstead 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/CompressErrordoc comments, anexample/main.dart, and a note on the class-level doc thatminWidth/minHeightare 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.suffixesfield that backed it). The check only fired in debug; release builds were never affected.
2.4.1 #
- Accept
.heifas 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
androidOomRetriesparameter fromcompressWithFileandcompressAndGetFile. The old retry-on-OOM logic was Android-only and silently produced an empty result when retries exhausted; now anOutOfMemoryErrorsurfaces as aCOMPRESS_ERRORPlatformExceptionlike 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
PlatformExceptioninstead of silently resolving tonull, matching iOS. New wire codes:FILE_NOT_FOUND,BAD_IMAGE,WRITE_FAILED, plus a catch-allCOMPRESS_ERROR. Callers that previously branched on anullresult will now see an exception for genuinely broken input. See the README "Errors" section. - Android: the three
compress*handlers now share a single index-drivenCompressArgsparser (mirroring the iOSCompressParams) and areplyCatchinghelper, replacing the per-handler positional unpacking and try/catch. No wire-format change. - Dart:
compressWithFile/compressAndGetFilenow check source existence with asyncFile.exists()instead ofexistsSync(), 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 aTaskcalling a single@concurrentrun(_:)worker; the three near-identical handlers collapse into aSendableRequestparser plus that one worker. Arguments are now read out ofFlutterMethodCallsynchronously on the calling thread, so no non-SendableFlutter 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
Mutexare used).
2.2.0 #
Internal cleanup — no public API or behavior changes.
- iOS: rewrote the plugin in Swift. The 6 Obj-C
.mfiles + 7.hheaders are replaced by 4 Swift files (ImageCompressPlugin.swift,Compressor.swift,ExifKeeper.swift,UIImage+Scale.swift). No__bridgecasts, no manualCFRelease, noinclude/public-headers folder, no// Created by cjl …fork comments. The two near-identicalcompressWithUIImage:/compressDataWithUIImage:methods collapse into one. File-existence and image-decode failures now surface asFlutterError(FILE_NOT_FOUND,BAD_IMAGE) instead of propagating nil.UIGraphicsBeginImageContext(deprecated since iOS 10) is replaced withUIGraphicsImageRenderer; the rotated bounding box is now computed viaCGRect.applying(_:)instead of allocating aUIView. - Android:
ResultHandler's thread pool is now owned by the plugin instance andshutdown()inonDetachedFromEngine(was acompanion objectExecutors.newFixedThreadPool(8)that lived for the process lifetime). Re-armed on re-attach. - Android: dropped the
androidx.exifinterfacedependency in favor of the platformandroid.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.0remains — HEIC encoding still goes through it (no platform-native HEIC encoder exists inBitmap.CompressFormat). - Android: collapsed 13 Kotlin files into 3 —
ImageCompressPlugin.kt,Compressor.kt,Exif.kt— to mirror the iOS Swift layout. Dropped the unusedFormatHandlerinterface,FormatRegistermap, and the never-thrownCompressError. - Android:
BitmapFactorydecode now usesARGB_8888for PNG/WebP/HEIC and keepsRGB_565for JPEG. Previously all formats decoded asRGB_565, silently dropping the alpha channel for transparency-capable outputs. - Android:
CompressFileHandler.handlereads EXIF rotation fromFile(path)directly instead of loading the full file into aByteArrayfirst. - Android: unknown format index now responds with
result.error("UNKNOWN_FORMAT", …)instead ofresult.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
compileSdk36 → 37 (Android 17). NominSdkchange. 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:(washeifRepresentationOfImage:, which doesn't exist onCIContext).
2.1.0 #
User-visible fixes:
- BREAKING:
numberOfRetriesparameter oncompressWithFile/compressAndGetFilerenamed toandroidOomRetries. The retry behavior was always Android-only (decode OOM → doubleinSampleSizeand recurse); the new name reflects that. iOS ignores the value as before. - iOS: WebP encoding now throws
UnsupportedErrorup front instead of silently returningnull(decoding still works on iOS 14+). - iOS: HEIC encoding no longer writes through
NSTemporaryDirectory()— usesheifRepresentationOfImage:directly. Removes a per-call temp-file leak. - Dart: validator contract is now consistent — every entry point throws
UnsupportedErrorfor unsupported encodings (previously some returnednull). The validator only checks the output format; input formats are auto-detected by the native decoder.
Internal cleanup:
- Android: introduced
CompressFormatenum to replace0/1/2/3magic numbers throughout the handlers andFormatRegister. - Android:
ExifKeeperported from Java to Kotlin;settings.gradle→settings.gradle.kts. - Android: bumped Gradle wrapper to 9.5.0,
compileSdkto 36. - Android: removed dead code paths (
ResultHandler.replyError,ExifKeeper.copyExifToFile, duplicateBitmap.compressextensions,System.gc()in OOM retry, pre-MarshmallowinDitherbranch). - iOS: introduced
ImageCompressFormatNS_ENUMmirroring the Dart/Android enums. - iOS: removed dead
getSystemVersionObj-C handler (Dart only calls Android for the API 28 check). - Dart: dropped
part/part ofin favor of regular libraries withimport/export;CompressFormat.nativeValuegetter replaces the private_convertTypeToInthelper; default param values centralized in a private_Defaultsclass.
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