fix(Android): preserve source EXIF on PNG and WebP output (refs #130) - #395
Merged
Conversation
AlexV525
added a commit
that referenced
this pull request
Jul 16, 2026
…e copies The old "About EXIF information" section listed encoder library names without saying anything about what actually gets preserved. Rewrite it so it points at the per-format-per-platform keepExif matrix immediately above, then briefly names the three coverage tiers (iOS/macOS full passthrough via CGImageDestination; Android ~90 tags via androidx.exifinterface; Web/OHOS not supported) and calls out that Orientation is always normalized to 1 regardless of platform. Also runs \`melos run cp_files\` to sync the root README into every package's local README — required per CLAUDE.md's "root README is the single source of truth" instruction. The per-package diffs accumulated across #394 and #395 land in this one sync commit. Refs #130. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`CommonHandler` on Android has always short-circuited `ExifKeeper`
unless the output was JPEG:
if (keepExif && bitmapFormat == Bitmap.CompressFormat.JPEG) { … }
PNG and WebP output silently dropped source EXIF regardless of the
`keepExif: true` request. Users who asked for `keepExif: true` on
PNG or WebP got a valid image back — with none of the EXIF they
expected preserved.
Fix: replace the `bitmapFormat == JPEG` guard with an explicit
`supportsExifWrite(format)` helper that whitelists JPEG, PNG, and
WebP — exactly the three formats `androidx.exifinterface`'s
`saveAttributes()` supports (per bytecode inspection of the current
release: `"ExifInterface only supports saving attributes for JPEG,
PNG, and WebP formats."` for any other input).
Version bump `androidx.exifinterface` from 1.3.3 → 1.4.2. 1.3.3
pre-dates several WebP-writer correctness fixes we now depend on:
- VP8 / VP8L-only WebP streams (what `Bitmap.compress(WEBP, quality)`
emits when it doesn't prepend a VP8X chunk) were not accepted by
the writer before ~1.3.7.
- `> 8191px` WebP output silently corrupted before the same era.
- The VP8X `E` flag was not set on the RIFF header, so downstream
readers didn't look at the EXIF chunk we just wrote.
Also bump the KEEP-IN-SYNC pin in the example app's build.gradle.kts.
Additional hardening: wrap the `ExifKeeper` construction in a
`runExifKeeperOrRaw` helper that falls back to the raw compressed
bytes (with a log line) if the source is corrupt or a container
`ExifInterface` refuses to read. Previously that path would
propagate `IOException` up to the handler wrapper and turn the whole
call into a `null` return — losing the compressed output that was
already produced. Now `keepExif=true` degrades gracefully to
"compressed but no EXIF" instead of failing.
New integration tests exercise the two now-fixed paths against a
real EXIF-rich source (auto-angle.jpg). Verified on Android emulator
(API 36, exifinterface 1.4.2): source `exif:DateTimeOriginal`,
`exif:DateTimeDigitized`, `tiff:DateTime` all survive in both PNG
and WebP outputs. iOS/macOS keepExif regression tests still pass
(the Android-only tests skip on those platforms).
README's per-format keepExif matrix flips Android PNG and Android
WebP from ❌ to ✅, and the follow-ups section now names the two
remaining gaps still tracked under #130: iOS WebP (ImageIO cannot
author WebP metadata) and Android HEIC (ExifInterface refuses HEIF
write; addressed as docs-only in #394).
Refs #130.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e copies The old "About EXIF information" section listed encoder library names without saying anything about what actually gets preserved. Rewrite it so it points at the per-format-per-platform keepExif matrix immediately above, then briefly names the three coverage tiers (iOS/macOS full passthrough via CGImageDestination; Android ~90 tags via androidx.exifinterface; Web/OHOS not supported) and calls out that Orientation is always normalized to 1 regardless of platform. Also runs \`melos run cp_files\` to sync the root README into every package's local README — required per CLAUDE.md's "root README is the single source of truth" instruction. The per-package diffs accumulated across #394 and #395 land in this one sync commit. Refs #130. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
AlexV525
force-pushed
the
fix/android-png-webp-keep-exif
branch
from
July 16, 2026 05:09
e68f3b2 to
4d5b8c6
Compare
AlexV525
added a commit
that referenced
this pull request
Jul 17, 2026
## Summary Two commits: 1. **`chore(release): publish packages`** — bumps all six packages via `melos version`, then hand-trimmed for two problems: - **Cross-package noise.** `melos run cp_files` syncs the root README into every sub-package, so a commit that only touched the README (e.g. `#395 fix(Android): preserve source EXIF...`) shows up as "changed files" for every package. Melos then lists that commit under every package's CHANGELOG. Trimmed those from `web`, `ohos`, `macos`, and `platform_interface` where the commit doesn't actually touch the package. - **CI/lint-only entries.** Dropped `FIX(*): resolve lints` and `FIX(ci): Restore CI green on main` from the user-facing changelogs — they aren't behavior changes. 2. **`docs: rewrite README for clarity, collapse TOC`** — end-to-end rewrite of the README prose (previous authors were non-native English speakers, phrasing had drifted awkward over versions). No facts, tables, or code samples changed. TOC is now wrapped in `<details>` so the platform-features table sits closer to the top. Per-package copies regenerated via `melos run cp_files`. ## Package bumps | Package | From | To | Kind | |---|---|---|---| | `flutter_image_compress` | 2.4.0 | **2.5.0** | minor | | `flutter_image_compress_common` | 1.0.6 | **1.1.0** | minor | | `flutter_image_compress_macos` | 1.0.3 | **1.1.0** | minor | | `flutter_image_compress_platform_interface` | 1.0.5 | **1.1.0** | minor | | `flutter_image_compress_web` | 0.1.5 | **0.1.5+1** | patch | | `flutter_image_compress_ohos` | 0.0.3 | **0.0.3+1** | patch | ## Housekeeping done outside this PR - Deleted misplaced remote tags `flutter_image_compress_common-v1.1.0` and `flutter_image_compress_macos-v1.1.0` (they pointed to an unrelated old commit and 1.1.0 was never actually published). - Moved `flutter_image_compress_web-v0.1.5` from `3018544` (an old OHOS merge, pubspec was `0.1.4+1` there) to `42e8c51` (the actual `chore(release)` commit where pubspec became `0.1.5`). - Added missing `flutter_image_compress_common-v1.0.6` tag at `b09eac4` — 1.0.6 was published to pub.dev but never tagged. ## Test plan (all run locally on this branch) - [x] `melos run format` - [x] `melos run analyze` - [ ] `melos run test` — n/a; no unit tests exist in this repo (behavior baseline lives in `example/integration_test/`) - [x] `melos run try_build_apk` - [x] `melos run try_build_ios` (CocoaPods) - [x] `melos run try_build_ios_swift_package_manager` - [x] `melos run try_build_macos` - [x] `melos run try_build_web` - [x] `melos run verify_macos_behavior` — 13 passed - [x] `melos run verify_ios_behavior` on iPhone 17 Pro sim (iOS 26.5) Post-merge: - [ ] Create GitHub Releases for each bumped package to trigger `melos publish` via `publish_on_release.yml` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second follow-up to #130 (following #394 for the HEIC docs surface).
Stacked on top of
fix/android-heic-keep-exif(PR #394) since that PR adds the shared Android test-helper channel this one uses; merge order is #394 → this. GitHub will retarget tomainonce #394 lands.Bug
CommonHandleron Android short-circuitsExifKeeperunless the output is JPEG:```kotlin
if (keepExif && bitmapFormat == Bitmap.CompressFormat.JPEG) { … }
```
PNG and WebP output silently drop source EXIF regardless of
keepExif: true.Fix
supportsExifWrite(format)— a private helper that whitelists JPEG, PNG, WebP (the exact three formatsandroidx.exifinterface.saveAttributes()supports; disassembly confirms it throws for anything else).androidx.exifinterface1.3.3 → 1.4.2 in bothflutter_image_compress_common/android/build.gradleand the example app'sbuild.gradle.kts(KEEP-IN-SYNC comment). 1.3.3 predates critical WebP writer fixes:Bitmap.compress(WEBP, quality)emits without a VP8X chunk — weren't accepted before ~1.3.7.> 8191pxWebP output corrupted before the same era.Eflag wasn't set on the RIFF header, so downstream readers didn't look at the EXIF chunk we just wrote.runExifKeeperOrRawhelper: if the source doesn't have readable EXIF (ExifKeeperconstructor throws), fall back to the raw compressed bytes with a log line rather than propagating up to the handler and returningnullfor the whole call.keepExif=truedegrades gracefully to "compressed but no EXIF".Verification
Two new integration tests on Android using the test-helper channel from #394:
Android PNG + keepExif=true: assertsexif:DateTimeOriginal,exif:DateTimeDigitized,tiff:DateTimefromauto-angle.jpgall survive in PNG output.Android WebP + keepExif=true: same assertion for WebP output.Both pass on Android emulator (API 36, exifinterface 1.4.2). Verified iOS still passes — Android-only tests skip cleanly, existing iOS/macOS keepExif assertions unchanged.
README
The per-format keepExif matrix flips Android PNG and WebP from ❌ to ✅. The follow-ups section now enumerates only the two remaining gaps still tracked under #130:
androidx.exifinterfacerefuses HEIF write (addressed as docs-only in docs(Android): surface HEIC + keepExif limitation via Log.w + README matrix (refs #130) #394).Adversarial review
Subagent flagged:
exifinterface 1.3.3predates VP8/VP8L / 8191px / VP8X-E-flag WebP writer fixes (FIXED via 1.4.2 bump); doublesaveAttributes()insideExifKeeperdoubles WebP corruption blast radius on old versions (MITIGATED by version bump); source-parse throw becomes user-visiblenullreturn for weird source containers (FIXED viarunExifKeeperOrRawfallback); misleading1.3.3+comment (FIXED).Test plan
melos run formatmelos run analyzeRefs #130.
🤖 Generated with Claude Code