New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 600120 link

Starred by 13 users

non-integer transform scale values result in 1px white lines being rendered

Reported by zsolt....@gmail.com, Apr 2 2016

Issue description

Chrome Version       : 49.0.2623.110 (Official Build) (64-bit)
URLs (if applicable) : http://playground-leaflet.rhcloud.com/say/1/edit?html,output
Other browsers tested: Firefox, Safari, IE
  Add OK or FAIL, along with the version, after other browsers where you
have tested this issue:
     Safari: FAIL
    Firefox: OK
         IE: OK

What steps will reproduce the problem?
(1) Open the above URL: http://playground-leaflet.rhcloud.com/say/1/edit?html,output or the attached file.
(2) Look at the rendered black div 


What is the expected result?
The rendered black div should be solid, with no 1px white lines visible.


What happens instead?
There are 1px white lines between the 256x256 image tiles. Firefox / IE renders it correctly, with no white lines.


Please provide any additional information below. Attach a screenshot if
possible.
This is a critical issue for the Leaflet interactive map library, used by millions of users, which we cannot fix without upstream help. 
On non-integer zoom levels (thus non-integer transform scale values) in Webkit browsers, there are 1px white lines appearing on the map. Firefox and IE render the same map perfectly.
This is a minimal, CSS-only reproduction case of the issue.

Attached is a HTML file and screenshot.

 
bug.html
3.1 KB View Download
Screen Shot 2016-04-02 at 13.38.15.png
35.3 KB View Download
Components: Blink>CSS
Labels: M-51 OS-Linux OS-Mac OS-Windows
Status: Untriaged (was: Unconfirmed)
This issue is reproduced on Win 7,Mac 10.11.3 and Ubuntu 14.04 using 49.0.2623.110 and canary 51.0.2698.0.
This is a non-regression issue since 36.0.1985.0 and prior to this the display is region/size is very less which is almost 1/5th of the black screen.
Looped respective dev person to update further on this.

Comment 2 by shans@chromium.org, Apr 4 2016

Labels: -Pri-3 Pri-2
Status: Available (was: Untriaged)
Components: -Blink>CSS Blink>Paint
Paint maybe?
Components: Internals>Skia
Owner: schenney@chromium.org
Status: Assigned (was: Available)
Skia always snaps images to whole pixels. I'm guessing we're rounding down always, leaving the one pixel gaps. It's not clear how to fix this - sometimes it's better to round down, sometimes better to round up, and no clear way to know which.

I'll try to figure out where the rounding is occurring. Given "all WebKit browsers" have the problem, I have to assume it's Blink.

Comment 5 by zsolt....@gmail.com, Apr 24 2016

Just an interesting finding: it doesn't need images to produce this behaviour, it works similarly (but not the same way) with simple divs:
http://playground-leaflet.rhcloud.com/foqi/1/edit?html,output
Cc: chrishtr@chromium.org
Components: -Internals>Skia Internals>Skia>Compositing Blink>Compositing
Owner: ----
Status: Available (was: Assigned)
This is related to our integer snapped layers, it would seem. chrishtr@, can you confirm that?
Mergedinto: 521364
Status: Duplicate (was: Available)

Comment 8 by trchen@chromium.org, Jun 24 2016

Cc: ajuma@chromium.org dbeam@chromium.org dtu@chromium.org hirono@chromium.org reed@chromium.org nyerramilli@chromium.org smokana@chromium.org reed@google.com danakj@chromium.org rponnada@chromium.org pdr@chromium.org bunge...@chromium.org esprehn@chromium.org trchen@chromium.org jbroman@chromium.org aelias@chromium.org mtomasz@chromium.org ajha@chromium.org enne@chromium.org kinaba@chromium.org vollick@chromium.org vangelis@chromium.org fmalita@chromium.org
Status: Available (was: Duplicate)
To the bug reporter:

The test case you uploaded triggered an edge case in Chromium that we handled pixel snapping in a different way. If you add some contents to the tiles you will no longer see seams in the background. We can easily fix the edge case, however, there can still be seams in the tile contents.

This is a hard problem, and the only known perfect solution is to use FSAA, which no browsers do due to the CPU/memory costs. Every vendors develop their own heuristics to make some contents look nicer, at the costs of some contents look incorrect.

For example: http://jsbin.com/wayapiz/

Chrome draws with the right geometry, but edges bleed like a stuck pig.
Firefox draws the first box as 49x49, the second box as 49x50.
Edge draws the first box as 50x50, the second box as 50x49. Both with aliasing.

Also if you add rotation to the container, all implementation bleed the edges.

My recommended workaround is to add some overlap between tiles. You know, just like making a poster with A4 papers. The idea is to make sure each tile overdraw at least one pixel after all the scaling, so each physical pixel is guaranteed to be covered by at least one opaque pixel. The width of the overlap depends on the smallest scale you want to support. For example, if you want to support down to 1/4 size, then add 4 pixels of overlap.
---
Below are the detailed analysis for other developers who wants to tackle this.

The repro uploaded by the reporter wasn't a very good repro because it triggers an optimized code path in cc. When we detected a layer completely solid color, we draw it as a SolidColorLayerImpl, which doesn't have the concept of contents scale. Here is a simplified repro: http://jsbin.com/hodoxew/

The black box will draw as a SolidColorLayerImpl of size (100, 100), which will generate a single solid color quad of size (100, 100), which will be projected to the screen by draw transform to become (99.5, 99.5). With anti-alias, the rightmost column and bottom row of pixels will be drawn in semi-transparent.

On the other hand, if we add something to the layer to trick cc into thinking the layer being non-solid color, now the problem comes in two cases:

1. The last tile being solid color, for example: http://jsbin.com/zexawa/

The contents size will be rounded up, so although the scaled layer size is only (600, 300)*0.995 = (597, 298.5), it will be over-drawn as (597, 299).

2. The list tile being non-solid color, for example: http://jsbin.com/mewaguf/

The contents size will be rounded up. Again the actual scaled contents only covers (600, 300)*0.995 = (597, 298.5), the generated quads will cover (597,299). The last row of pixels are supposed to be semi-transparent due to anti-alias, but we actually fill it with layer background color, which is the background color of the element that created the layer.

There will be no seam in the background, however, background can bleed on the edges when the contents are supposed to cover it. For example: http://jsbin.com/vigufo/

Comment 9 by kinaba@chromium.org, Jun 24 2016

Cc: -kinaba@chromium.org
@trchen, thanks for this detailed report and analysis.

I didn't realise that Chrome handles solid color divs differently, I believe I overly simplified the minimal case this way.

In Leaflet (and raster web maps in general), the div is never a solid color, but an image exactly 256x256 (or 2x). You can see a screenshot of the real case in the attachment.

The images are rendered without overlapping regions on the server side and this is how every map server works. So, it is not possible to add padding to those source images. Duplicating a pixel or two on every img element would be very inefficient to do in JS, so it's not a solution either, unfortunately.

I'll try to make a case which uses real images.



leaflet-bug.png
544 KB View Download
Here is a test case with mostly non-solid color tiles (but some are only containing a solid colors, like the ones on the left row):
http://playground-leaflet.rhcloud.com/giqo/edit?output




I took a look at mobile Google map to see how they handled it. It turns out they are plagued by this problem too, but they did some UI trick to make it less obvious.

The first thing is they used the viewport tag to disallow browser pinch zoom, and implemented pinch zoom in JS. During pinch zoom, you can still see seams between tiles (very subtle on high-res displays). After the zoom gesture finish, they snap the scale factor to the closest tile set they have. So basically tiles are always displayed in native resolution except during zoom gesture.

Honestly we don't have a solid plan yet. I'm going to discuss with other paint people on Monday.

Comment 13 by dbeam@chromium.org, Jun 25 2016

Cc: -dbeam@chromium.org
@trchen thanks for checking it. Leaflet is using exactly the same technique as what you described in mobile Google Maps, we have a zoomSnap value which is by default 1.0, meaning that no matter where the user zooms we always snap to an integer value.

Yes, during zooming between integer levels those lines are not very visible and I guess on high PPI mobile screens even less so. Our problem is that many users would like to have a more refined zoom level, not just integer ones, as it would be important for use cases displaying images, etc. Leaflet's code is now handling these fractional zoom levels fine, the only thing keeping us back is this display issue in WebKit browsers. 
@zsolt.ero is the current layerization important?  I see each tile is forced into its own layer (translate3d), and these layers are scaled by some fractional value independently.

As long as we scale each map tile independently (either using layers or via drawImageRect + fractional CTM), for fractional scale values we get edges which are not pixel-aligned.  This triggers the ole coincident edge anti-aliasing problem where discrete AA causes background color bleed.

The way I think this could work instead is to rasterize the map with a non-fractional scale (say 1.0) and then downscale atomically:

1) avoid individual tile layerization (use "transform: translate()" instead of "transform: translate3d()")
2) force container (whole map) layerization (use "transform: scale3d()" on the container)
3) force container layer rasterization with a scale == 1.0

With these changes, map tile images should be rasterized with a scale of 1.0 (=> edges are pixel-aligned => no seaming artifacts), then the whole map layer should be down-scaled atomically.

Unfortunately #3 is not trivial, and the only hack I can think of is

* start with scale(1) and will-change: transform (see https://groups.google.com/a/chromium.org/forum/#!topic/blink-dev/Ufwrhx_iZ7U for details)
* reset the layer scale to the desired value after the first raster pass

e.g. http://jsbin.com/yaqeru/10/embed?output

chrishtr/danakj did I get the layer raster scale semantics right, and is there a better way to lock the layer rasterization scale to a particular value?
Yeah, that's what we got right now. I think a property to set a specific scale would be nice, but that will take some spec work.

The old harder way is to use 2 divs:
- An inner div that's not composited, using 2d scales.
- An outer dive that is composited, using 3d scales.
You can make the inner 2d scale be the inverse of the outer scale, to get a result of 1. I think that could still work..
Re comment 15: Yes you've gotten it right. Maybe more succinctly in code:

element.style.transform = 'scale(1)';
element.style.willChange = 'transform;
requestAnimationFrame(function() {
  // When this function is called, the element will already be rastered at scale 1.
  element.style.willChange = '';
  animate()
  element.style.transform = 'scale(1.1)'; // start animating, etc.
}

Make that element.style.willChange = 'auto' instead of the empty string. Also,
you'll have to pump a frame with requestAnimationFrame after setting scale(1) if
it's already at some other scale, *then* add will-change: transform.
Project Member

Comment 19 by sheriffbot@chromium.org, Jul 7 2017

Labels: Hotlist-Recharge-Cold
Status: Untriaged (was: Available)
This issue has been Available for over a year. If it's no longer important or seems unlikely to be fixed, please consider closing it out. If it is important, please re-triage the issue.

Sorry for the inconvenience if the bug really should have been left as Available. If you change it back, also remove the "Hotlist-Recharge-Cold" label.

For more details visit https://www.chromium.org/issue-tracking/autotriage - Your friendly Sheriffbot
Owner: chrishtr@chromium.org
Status: Assigned (was: Untriaged)
Should we archive or is there future actions here?
Components: -Blink>Paint -Internals>Skia>Compositing
Owner: ----
Status: Available (was: Assigned)
Labels: -Pri-2 Pri-3
Labels: -Hotlist-Recharge-Cold PaintTeamTriaged-20170707 BugSource-User
Project Member

Comment 24 by sheriffbot@chromium.org, Jul 9

Labels: Hotlist-Recharge-Cold
Status: Untriaged (was: Available)
This issue has been Available for over a year. If it's no longer important or seems unlikely to be fixed, please consider closing it out. If it is important, please re-triage the issue.

Sorry for the inconvenience if the bug really should have been left as Available.

For more details visit https://www.chromium.org/issue-tracking/autotriage - Your friendly Sheriffbot
Status: Available (was: Untriaged)
I think we leave it open, if only to track that we could do better.
Cc: -dtu@chromium.org
Cc: sindhu.chelamcherla@chromium.org
 Issue 825711  has been merged into this issue.

Sign in to add a comment