Skip to content

feature: [ANDROSDK-2371] revisit image compression logic - #2699

Open
taridepaco wants to merge 2 commits into
developfrom
ANDROSDK-2371
Open

feature: [ANDROSDK-2371] revisit image compression logic#2699
taridepaco wants to merge 2 commits into
developfrom
ANDROSDK-2371

Conversation

@taridepaco

@taridepaco taridepaco commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

The image compression added earlier was based on a PoC. This reworks it to be production ready: it no longer trusts the file extension, supports a broader set of input formats, bounds the memory it uses, preserves the EXIF orientation, and converges on the target size by measuring instead of guessing.

Format handling. The format is now resolved from the image header rather than from the file name, so a JPEG named .png is no longer re-encoded as a PNG (which made it grow instead of shrink). The output format is chosen by scanning whether the bitmap actually contains translucent pixels — PNG when it does, JPEG otherwise — because Bitmap.hasAlpha() only reports the presence of an alpha channel, not its use. Formats the server rejects (webp, heif, avif) are always converted, even when they are already below the target size. Since the resulting extension may differ from the input, FileResourceCollectionRepository now builds the resource name from the original name with the extension actually produced, keeping contentType — which is derived from the name — in agreement with the bytes.

Memory. Decoding is subsampled through inSampleSize and bounded to 4096 px on the largest side, retrying with a larger sample size on OutOfMemoryError. Previously the full sized bitmap was decoded, which for a 50 MP picture means around 200 MB as ARGB_8888.

EXIF orientation. Re-encoding a bitmap drops the metadata, so an image that was only upright thanks to its orientation tag was being uploaded rotated. The rotation is now baked into the pixels. This uses the AndroidX ExifInterface rather than the platform one, because the latter only parses Jpeg metadata and heif images — which this path converts on purpose, as the server rejects them — would silently be reported as not rotated.

Convergence. The previous strategy estimated a single scale from the size ratio and then decayed it by 20% for up to 10 iterations, writing the file to disk on every attempt. Now the image is encoded once at full resolution and, if that does not fit, a scale search runs: every attempt is encoded in memory and the next scale is predicted from that measurement, using the square root of the size ratio since the encoded size grows roughly with the pixel count. The prediction aims at 95% of the target so that an attempt landing just above it still makes progress instead of converging on the boundary from above; it settles in one or two attempts and is capped at four. Only the accepted result is written to disk. The quality is fixed at 85 and never lowered to reach the target: JPEG artifacts below that survive every later downscaling, while resolution above the dimension the image is displayed at is not used, so the budget is better spent on quality than on pixels. A floor of 256 px on the largest side keeps an unreachable target from degrading the image indefinitely.

No-ops. The original file is now returned untouched when it cannot be decoded, when it is already below the target in a format the server accepts, or when compressing it would not make it any smaller. When a new file is produced it is written to the SDK cache directory with File.createTempFile, so that two images with the same name being added concurrently cannot overwrite each other, and the repository deletes it once the add completes.

FileResizerHelper. It shares the new decoding primitives, so it also gets subsampled decoding and EXIF orientation. It now raises a D2Error when the file is not a decodable image instead of throwing an NPE, and its final downscaling pass is filtered.

Tests cover the format conversion, the preservation of transparency, the conversion of a webp already below the target, the EXIF rotation, the minimum dimension floor, landing close to the target rather than well under it, and the absence of leftover temporary files after an add. There are no public API signature changes, so the API dump is unchanged.

Related task: ANDROSDK-2371

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant