Skip to content

White line artifact at image edges when saving JPEG after applying filters or tune adjustments #750

@AmanRajSinghMourya

Description

@AmanRajSinghMourya

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

  1. Load a dark-colored image into the editor
  2. Open the Tune Editor
  3. Increase contrast by +10 or more (issue also occurs with other adjustments)
  4. Save/export the image as JPEG
  5. 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.matrix transformations (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:

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 pixels

Possible 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions