-
-
Notifications
You must be signed in to change notification settings - Fork 194
Description
Description
When applying filters or tune adjustments (especially contrast) to an image and saving as JPEG, a 1-2 pixel white line appears at the bottom edge of the output image. This is particularly visible on dark images.
Steps to Reproduce
- Load a dark-colored image into the editor
- Open the Tune Editor
- Increase contrast by +10 or more (issue also occurs with other adjustments)
- Save/export the image as JPEG
- View the saved image - observe a white line at the bottom edge
Expected Behavior
The saved JPEG should have clean edges with no artifacts, matching exactly what is visible in the editor preview.
Actual Behavior
A 1-2 pixel white/light line appears at the bottom edge of the saved image. The artifact:
- Is most visible on dark images
- Appears after applying
ColorFilter.matrixtransformations (filters, contrast, brightness, etc.) - Does not appear in the editor preview, only in the saved output
- Occurs specifically on Android devices
Environment
- pro_image_editor version: 11.3.0
- Flutter version: 3.32.8
- Platform: Android (physical devices)
- Output format: JPEG (
OutputFormat.jpg)
Root Cause Analysis
After investigation, the issue appears to be caused by a combination of factors:
1. Anti-aliasing at Image Edges
When the image is rendered within the editor, edge pixels may not perfectly align with pixel boundaries. This creates semi-transparent edge pixels due to anti-aliasing.
2. ColorFilter.matrix Affecting Alpha Channel
When tune adjustments or filters are applied via ColorFilter.matrix, the transformation can inadvertently modify alpha values of edge pixels, creating pixels with partial transparency.
3. JPEG Encoding of Semi-Transparent Pixels
JPEG format doesn't support transparency. When semi-transparent edge pixels are encoded to JPEG, they get composited against the jpegBackgroundColor (white by default). This causes:
- Transparent pixels → appear white
- Semi-transparent pixels → appear as a lighter shade
4. Flutter's toImage() Transparency Handling
Flutter's RepaintBoundary.toImage() has documented inconsistencies in how transparent pixels are handled across platforms:
- Related Flutter issue: boundary.toImage().toByteData() handles transparency differently on physical iphone devices with 3.27.1 flutter/flutter#161408
- On some configurations, transparent pixels have RGB values of
(255,255,255,0)instead of(0,0,0,0)
Code Reference
The image capture flow:
// 1. Image is rendered with ColorFilter applied
ColorFiltered(
colorFilter: ColorFilter.matrix(_combinedMatrix),
child: widget.child,
)
// 2. Captured via RepaintBoundary
ui.Image image = await boundary.toImage(pixelRatio: pixelRatio);
// 3. Encoded to JPEG with jpegBackgroundColor for transparent pixelsPossible Solutions
Solution 1: Ensure opaque rendering before capture
Before capturing, ensure the image is rendered on an opaque background layer to eliminate any transparent edge pixels.
Solution 2: Expand crop bounds slightly
When cropToImageBounds calculates the image bounds, it looks for pixels with alpha != 0. A small buffer/padding could ensure edge artifacts are excluded.
Solution 3: Post-process edge pixels
After capture but before JPEG encoding, detect and fix edge pixels that have abnormal alpha values by either:
- Setting them fully opaque (copying from adjacent pixels)
- Or setting them fully transparent (so they composite cleanly)
Solution 4: Use PNG intermediate format
Output as PNG (preserves alpha), then properly composite the entire image against a solid background before JPEG encoding. This ensures consistent handling of all semi-transparent pixels.
Workaround
Currently, setting jpegBackgroundColor to match the dominant edge color of the image reduces visibility of the artifact, but doesn't eliminate it:
ImageGenerationConfigs(
jpegQuality: 100,
jpegBackgroundColor: Color(0xFFFFFFFF), // or match image edge color
)Additional Context
- This issue has been reported by users of the ente Photos app
- Similar issues have been discussed in Flutter's issue tracker regarding
toImage()and transparency handling - The issue does not occur when saving as PNG (which preserves transparency)
Related Issues
- Flutter #161408: toImage() transparency handling differences on iOS
- Flutter #126583: ColorFilter.matrix issues with Impeller
Reproduction Project
If needed, I can provide a minimal reproduction project demonstrating this issue.