Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Antialiasing behaviour when same-colour #14288

Open
radzish opened this issue Jan 26, 2018 · 96 comments
Open

Antialiasing behaviour when same-colour #14288

radzish opened this issue Jan 26, 2018 · 96 comments
Labels
a: gamedev Issues related to game development with Flutter c: rendering UI glitches reported at the engine/skia rendering level customer: crowd Affects or could affect many people, though not necessarily a specific customer. engine flutter/engine repository. See also e: labels. found in release: 3.3 Found to occur in 3.3 found in release: 3.4 Found to occur in 3.4 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P3 Issues that are less important to the Flutter project team-engine Owned by Engine team triaged-engine Triaged by Engine team workaround available There is a workaround available to overcome the issue

Comments

@radzish
Copy link

radzish commented Jan 26, 2018

Latest status update: #14288 (comment); some work around suggestions: #14288 (comment)


Steps to Reproduce

Following source code:

import 'package:flutter/material.dart';

const Color color = const Color.fromARGB(255, 100, 100, 100);

void main() =>
    runApp(
      new Container(
        color: const Color.fromARGB(255, 0, 0, 0),
        child: new Row(
          mainAxisAlignment: MainAxisAlignment.end,
          textDirection: TextDirection.ltr,
          children: [
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
            new Expanded(
              child: new Container(
                color: color,
              ),
            ),
          ],
        ),
      ),
    );

produces following result:

Looks like background of the container is popping out and we see vertical lines. That should not be the case as all children of the row are Expanded and thus should fill the whole area.
If we remove one child lines are gone.

Logs

Launching lib/main.dart on Android SDK built for x86 in debug mode...
Initializing gradle...
Resolving dependencies...
Running 'gradlew assembleDebug'...
Built build/app/outputs/apk/app-debug.apk (22.4MB).
I/FlutterActivityDelegate( 8398): onResume setting current activity to this
D/EGL_emulation( 8398): eglMakeCurrent: 0xaad2c640: ver 3 1 (tinfo 0xa057c5b0)
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x000082da
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x000082da
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008cdf
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008cdf
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008824
E/eglCodecCommon( 8398): glUtilsParamSize: unknow param 0x00008824
D/        ( 8398): HostConnection::get() New Host Connection established 0xa31a3640, tid 8416
D/EGL_emulation( 8398): eglMakeCurrent: 0xaad2c640: ver 3 1 (tinfo 0xa3183790)
D/EGL_emulation( 8398): eglMakeCurrent: 0xaad2c760: ver 3 1 (tinfo 0xa057cc10)
Syncing files to device Android SDK built for x86...

Flutter Doctor

[✓] Flutter (on Linux, locale en_US.UTF-8, channel master)
    • Flutter version unknown at <path_to_flutter>
    • Framework revision 5ae770345a (3 days ago), 2018-01-23 13:46:14 -0800
    • Engine revision 171d032f86
    • Tools Dart version 2.0.0-dev.16.0
    • Engine Dart version 2.0.0-edge.93d8c9fe2a2c22dc95ec85866af108cfab71ad06

[✓] Android toolchain - develop for Android devices (Android SDK 27.0.3)
    • Android SDK at <path_to_android>
    • Android NDK location not configured (optional; useful for native profiling support)
    • Platform android-27, build-tools 27.0.3
    • ANDROID_HOME = <path_to_android>
    • Java binary at: <path_to_android-studio>/jre/bin/java
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-915-b01)

[✓] Android Studio (version 3.0)
    • Android Studio at <path_to_android-studio>
    • Java version OpenJDK Runtime Environment (build 1.8.0_152-release-915-b01)

[✓] Connected devices
    • Android SDK built for x86 • emulator-5554 • android-x86 • Android 8.1.0 (API 27) (emulator)
@radzish
Copy link
Author

radzish commented Jan 26, 2018

Another example (I believe it is somehow related: )

import 'package:flutter/material.dart';

const Color grey = const Color.fromARGB(255, 100, 100, 100);
const Color black = const Color.fromARGB(255, 0, 0, 0);

void main() =>
    runApp(
      new Container(
        color: grey,
        child: new Center(
          child: new Container(
            width: 151.0,
            height: 151.0,
            color: black,
            child: new Container(
              color: grey,
            ),
          ),
        ),
      ),
    );

We should not see border here. If widht/height are changed to 150.0, square is gone.

@Hixie
Copy link
Contributor

Hixie commented Jan 29, 2018

This is normal behaviour. What's happening is that the boxes are not quite aligned with pixel boundaries, so there's some anti-aliasing happening on the boundaries, which involves transparency, which means that for those pixels the two grays are overlapping and looking darker.

As a general rule when doing anti-aliasing you want to avoid putting identically-coloured boxes adjacent or over each other unless you can guarantee physical pixel alignment.

Alternatively, you can use saveLayer (or RepaintBoundary) to cause a bunch of paint operations to get merged into one and composited as one. Not sure that that would help in these cases specifically but it is a tool that can be useful in this kind of situation.

@radzish
Copy link
Author

radzish commented Jan 30, 2018

This is not boxes overlapping, but rather spare space between boxes, so color of background is popping up. I was changing background to different color and this color was popping out.

@radzish
Copy link
Author

radzish commented Feb 22, 2018

Root cause is that boxes can not be aligned with physical pixels. I would not call it "normal", I would rather call it "expected".
On android similar (semantically) case is handled properly:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal"
    android:background="#000000"
    tools:context="com.radzish.android_lines_bug.MainActivity">

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

    <FrameLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:background="#646464"
        android:layout_height="match_parent"/>

</LinearLayout>

So I think flutter should improve in this case.
The only workaround I found for me at the moment is sizing children manually like this:

    int CHILDREN_COUNT = 7;
    List<Widget> children = new List(CHILDREN_COUNT);

    MediaQueryData mediaQueryData = MediaQuery.of(context);
    int physicalWidth = (mediaQueryData.size.width * mediaQueryData.devicePixelRatio).floor();

    for (int i = 0, pixelsLeft = physicalWidth; i < CHILDREN_COUNT; i++) {
      int columnWidth = (pixelsLeft / (CHILDREN_COUNT - i)).floor();

      children[i] = new Container(
        width: columnWidth / mediaQueryData.devicePixelRatio,
        color: color,
      );

      pixelsLeft -= columnWidth;
    }

@Hixie Hixie changed the title Expanded elements not taking the whole are of their parent Expanded elements not taking the whole area of their parent May 29, 2018
@Hixie Hixie changed the title Expanded elements not taking the whole area of their parent Document the issue of antialiasing when same-colour blocks abut, and some workarounds May 29, 2018
@Hixie Hixie added the d: api docs Issues with https://api.flutter.dev/ label May 29, 2018
@Hixie Hixie added this to the Goals milestone May 29, 2018
@Hixie Hixie changed the title Document the issue of antialiasing when same-colour blocks abut, and some workarounds Document the issue of antialiasing when same-colour blocks abut, and some workarounds (API docs, FAQ) May 29, 2018
@zoechi zoechi added framework flutter/packages/flutter repository. See also f: labels. and removed framework flutter/packages/flutter repository. See also f: labels. labels Dec 4, 2018
@Hixie
Copy link
Contributor

Hixie commented Feb 15, 2019

@radzish what is Android doing to avoid the problem?

@Hixie
Copy link
Contributor

Hixie commented Feb 15, 2019

These comments have suggestions for things to put in documentation:
#14288 (comment)
#17084 (comment)
#15035 (comment)

@DenisBogatirov
Copy link

Any updates here? Because this is very annoying((
Or any examples how to use saveLayer or RepaintBoundary?

@NoschdarSindy
Copy link

I had this problem in my SliverAppBar and my workaround was hiding the line by using a shadow of the same color:

SliverAppBar(
  ...
  bottom: PreferredSize(
    preferredSize: const Size.fromHeight(2),
    child: Container(
        height: 2,
        decoration: BoxDecoration(boxShadow: [
          BoxShadow(
            color: Colors.blue, # or whatever the color of your AppBar is
            offset: const Offset(0, 1),
          ),
        ]))),
  ...
)
                      

@amarell
Copy link

amarell commented Sep 4, 2023

Unfortunately for me, the workaround where we're supposed to put all children in one container with the same background color is not ok, because I have a column with three children, the middle child has a special ClipPath and it's not possible for me to clip the parent container so that it matches that child. Hopefully this gets resolved sometime soon.

Edit: Also I don't think that this has anything to do with the same color as it is said in the title of the issue. I tried using different colors and the issue persists.

@TungLM
Copy link

TungLM commented Sep 26, 2023

This issue happens to me when I apply Zoom Drawer library. Any idea for this?
Screenshot_20230926_093912

@DukePeoPlus
Copy link

Is there an update? I'm still experiencing errors on version 3.13.4.

@ulisseshen
Copy link

Yet persist

@fyxtc
Copy link

fyxtc commented Dec 25, 2023

Same issue on 3.13.7

Update: I found a solution, below SizedBox is a item in ListView
SizedBox(height: h, child: images[index]); White Gap
SizedBox(height: h.toInt().toDouble(), child: images[index]); No Gap

I don't know why, but it works well for me

@79121262
Copy link

image
3.16.3 版本仍然存在, 这种问题什么时候能解决,是不打算修复了? 导致项目体验非常差

@ledjoncili
Copy link

Any update on this issue? I tried all above workaround and none did the trick for me. I still get the white line between the app bar and body.

@Hixie
Copy link
Contributor

Hixie commented Jan 13, 2024

Latest status is that we believe Impeller fixes this for most cases. Impeller is enabled by default on iOS, and we hope it will be enabled this year in Android, and sometime in the next 2 or 3 years for other platforms, though there's some question about exactly what we should do on desktop. (The schedule is not set in stone; this is experimental work being built by people from various companies and by volunteers so scheduling is non-trivial.)

For those cases not fixed by Impeller this will probably never be fixed because it is a fundamental artifact of how computer graphics work, but if there are specific cases you are running into that still have this artifact please file specific bugs with test cases demonstrating the problem so we can make sure we understand them fully before saying "no" for good. As I noted in an earlier comment, while we could create APIs that help you work around this (e.g. Flash had a way of specifying an "outer" color for a line so that it would antialias with the "right" color), those would not be automatic and if one is willing to adjust one's code to avoid this problem then it is already possible to do so by refactoring the code to not have the seams in the first place.

@erlangparasu
Copy link

erlangparasu commented Jan 13, 2024

... because it is a fundamental artifact of how computer graphics work,

@Hixie sorry, but how native android view works without got this issue?

@knopp
Copy link
Member

knopp commented Jan 13, 2024

I somewhat disagree with @Hixie here. Yes, MSAA (impeller) will fix some of the cases, but fundamentally the issue here is that nothing in Flutter is snapped to physical pixels. Unlike web browsers for example, which snap both layout and dimensions - i.e. 1px solid black in browser will always be 1, 2 or 3 physical pixels exactly, even with fraction pixel ratio like 175%. Other frameworks, such as WPF have ways to snap layout to physical pixels as well (SnapsToDevicePixels).

We discussed it here but ultimately I failed to convince @Hixie that this is the way to for Flutter. It is also possible the change would be too disruptive at this stage anyway.

So instead I made pixel snap. It should solve pretty much all the issues in this thread, and doesn't require any changes in Flutter. The downside is that for pixel snapping to work, all widgets that affect layout in the application must be pixel snapped. Depending on application that might require some extra work.

@sapphire008
Copy link

sapphire008 commented Jan 21, 2024

@knopp Thank you for creating pixel_snap. Can you help show an example on how to use it with CustomScrollView with Slivers? (Related to: #37578). I tried something like the following, but the thin line still exists between my list of slivers:

import 'package:pixel_snap/pixel_snap.dart';
import 'package:pixel_snap/material.dart';

...

CustomScrollView(
  controller: PixelSnapScrollController(),
  slivers: [
    SliverAppBar(
        pinned: true,
        expandedHeight: ps(MediaQuery.of(context).size.height * 2 / 3),
        ...
    ),
    SliverPadding(
        padding: const EdgeInsets.all(16.0).pixelSnap(ps),
        sliver: SliverToBoxAdapter(child: Text(...)),
    ),
  ],
)

@knopp
Copy link
Member

knopp commented Jan 21, 2024

@knopp Thank you for creating pixel_snap. Can you help show an example on how to use it with CustomScrollView with Slivers? (Related to: #37578). I tried something like the following, but the thin line still exists between my list of slivers:

import 'package:pixel_snap/pixel_snap.dart';
import 'package:pixel_snap/material.dart';

...

CustomScrollView(
  controller: PixelSnapScrollController(),
  slivers: [
    SliverAppBar(
        pinned: true,
        expandedHeight: ps(MediaQuery.of(context).size.height * 2 / 3),
        ...
    ),
    SliverPadding(
        padding: const EdgeInsets.all(16.0).pixelSnap(ps),
        sliver: SliverToBoxAdapter(child: Text(...)),
    ),
  ],
)

If you can provide a reproducible example please create issue in pixel_snap repository and I'll take a look. The snippet that you have posted here looks good, but it is possible that the CustomScrollView itself is not pixel snapped. I don't know what widgets are around it that affect layout that's why a complete example is needed.

@ltOgt
Copy link

ltOgt commented Feb 12, 2024

There are many cases where adjacent boxes dont have the same color, and can not be merged.
(E.g. list items with selection state, or some highlighted items, or items with different heights)

Example with alternating color, and a timer that auto scrolls to show the gaps:

import 'dart:async';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      home: const Scaffold(
        body: MyWidget(),
      ),
    );
  }
}

class MyWidget extends StatefulWidget {
  const MyWidget({Key? key}) : super(key: key);

  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late ScrollController _controller;
  late Timer _timer;
  double _scrollOffset = 0.0;

  @override
  void initState() {
    super.initState();
    _controller = ScrollController();
    _timer = Timer.periodic(const Duration(seconds: 1), _scroll);
  }

  @override
  void dispose() {
    _timer.cancel();
    _controller.dispose();
    super.dispose();
  }

  void _scroll(Timer timer) {
    _scrollOffset += 0.25;
    _controller.jumpTo(_scrollOffset);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.black,
      child: SingleChildScrollView(
        controller: _controller,
        child: Column(
          children: [
            for (int i = 0; i < 1000; i++) //
              Container(
                color: i % 2 == 0 ? Colors.blue : Colors.grey,
                height: 20,
                width: 100,
                child: Text("$i"),
              ),
          ],
        ),
      ),
    );
  }
}
Screen.Recording.2024-02-12.at.19.53.31.mov

And without auto scroll:

Screen.Recording.2024-02-12.at.19.54.02.mov

And without alternating colors, showing the color from behind:

Screen.Recording.2024-02-12.at.19.56.51.mov

@casey977
Copy link

casey977 commented Feb 15, 2024

Hello,

I'm dealing with the same problems as mention above, thin white lines between widgets (rows in my case), when rendering to web. It looks like an anti-alias or not-pixel-perfect issue, also as already mentioned above.

Have I understood it correctly that there is no proper/native solution to this, and the best option is to use pixel_snap?

UPDATE: I also tried building/running for Linux desktop, and I get the same white lines. But using a Scaffold with backgroundColor works as a workaround, which happens to be usable in this case.

@erlangparasu
Copy link

Hello,

I'm dealing with the same problems as mention above, thin white lines between widgets (rows in my case), when rendering to web. It looks like an anti-alias or not-pixel-perfect issue, also as already mentioned above.

Have I understood it correctly that there is no proper/native solution to this, and the best option is to use pixel_snap?

UPDATE: I also tried building/running for Linux desktop, and I get the same white lines. But using a Scaffold with backgroundColor works as a workaround, which happens to be usable in this case.

Also you may try space_fixer https://pub.dev/packages/space_fixer

Example:

          Container(
            width: MediaQuery.of(context).size.width,
            height: 50,
            color: Colors.black,
          ),
          SpaceFixerHorizontalLine(
            context: context,
            overflowHeight: 3,
            overflowColor: Colors.black,
          ),
          Container(
            width: MediaQuery.of(context).size.width,
            height: 50,
            color: Colors.black,
          ),

https://github.com/erlangparasu/space_fixer/blob/e3a5b8f158f64507a486c1f8904431cb8c6ce773/example/example1.dart#L59

You can use this widget as a divider in the list

@knopp
Copy link
Member

knopp commented Feb 16, 2024

There are many cases where adjacent boxes dont have the same color, and can not be merged. (E.g. list items with selection state, or some highlighted items, or items with different heights)

[removed]

flutter pub add pixel_snap

Replace import 'package:flutter/material.dart' with import 'package:pixel_snap/material.dart'. Gaps disappear. In this case it would be beacuse the scroll controller from pixel snap snaps the position to physical pixels.

pixel_snap.mov

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: gamedev Issues related to game development with Flutter c: rendering UI glitches reported at the engine/skia rendering level customer: crowd Affects or could affect many people, though not necessarily a specific customer. engine flutter/engine repository. See also e: labels. found in release: 3.3 Found to occur in 3.3 found in release: 3.4 Found to occur in 3.4 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P3 Issues that are less important to the Flutter project team-engine Owned by Engine team triaged-engine Triaged by Engine team workaround available There is a workaround available to overcome the issue
Projects
None yet
Development

No branches or pull requests