How to avoid pixel art texture edge "flashing" while moving the texture

Say I have following simple code, where I try to render some tiles and add a handler to allow user to pan the map.

4JBbi1g.png

config.lua

-- config.lua application = { content = { scale = 'adaptive' , fps = 60 } }

main.lua

-- main.lua -- save some screen space display.setStatusBar(display.HiddenStatusBar) -- we are mostly dealing with pixel asset display.setDefault('minTextureFilter', 'nearest') display.setDefault('magTextureFilter', 'nearest') --display.setDefault('isImageSheetSampledInsideFrame', true) -- use top-left origin display.setDefault('anchorX', 0) display.setDefault('anchorY', 0) local sheet1 = graphics.newImageSheet('tilesets-classic/internal-01.png', { width = 32 , height = 32 , numFrames = 192 }) local sheet2 = graphics.newImageSheet('tilesets-classic/internal-02.png', { width = 32 , height = 32 , numFrames = 168 }) local ts = 32 local tiles = {} local tile local view = display.newContainer(display.contentWidth, display.contentWidth) view.anchorChildren = false local group = display.newGroup() local group1 = display.newGroup() for y = 0, 100 do for x = 0, 100 do tile = display.newImage(sheet1, 49, x \* ts, y \* ts) group1:insert(tile) end end local group2 = display.newGroup() for y = 0, 10, 3 do for x = 0, 10, 3 do tile = display.newImage(sheet2, 25, x \* ts, y \* ts) group2:insert(tile) end end group:insert(group1) group:insert(group2) view:insert(group) local camera\_panning = false local ox, oy function touch (event) if event.phase == 'began' then -- enter panning state camera\_panning = true ox = group.x oy = group.y elseif event.phase == 'moved' and camera\_panning == true then -- distance of movement from initial position group.x = math.floor(ox + event.x - event.xStart) group.y = math.floor(oy + event.y - event.yStart) elseif event.phase == 'ended' or event.phase == 'cancalled' then -- exit panning state camera\_panning = false end end Runtime:addEventListener('touch', touch)

There is a problem I only experience on iOS (iPhone 6) but not in the Corona SDK Simulator: when I pan the map, on certain position, each tile’s edge can appears slightly thinner than usual; the symptom repeats for every a few pixels.

I have tried a few solutions:

  • Extruding / Additional margins between tiles

  • Normalize the movement to integer (so no subpixel positioning)

isImageSheetSampledInsideFrame = true

The first 2 doesn’t solve the issues; the last one solve it by introducing another problem (half-pixel edge, so we always have thin edges, that’s also undesirable, as described here).

What’s the best way to fix this? Why can we only reproduce it on real device? (probably screen related, nonetheless very annoying, because we have to re-build for each attempts to fix it)

A similar problem I have no idea how to solve is this:

9EXGEjC.png

Using similar code as above, I run into this problem, where image sheet tile selection seem to be off-by-1px, you can see the corner is not aligned correctly on iOS, and the white line is caused by selecting adjacent tile on the image sheet (but it works perfectly fine in my Tiled map editor and Corona SDK simulator).

(EDIT: This problem has nothing to do with panning and is persistent whatever the position of the map; extruding doesn’t fix it completely, isImageSheetSampledInsideFrame fixes it, but it is a global value so cause the same problem described above.)

The answer to this is to avoid the adaptive scaling in config.lua , because it could use non-integer scaling factor (say 1.5x) and there is nothing you can do to prevent sub-pixel positioning.

Instead, you should be using something like a smart-pixel config. Adaptive scaling trades integer scaling factor for a consistent virtual pixel unit across devices. We don’t really need that, we just need the content width and height to be closed enough; what we do need is integer scaling factor to prevent sub-pixel rendering.

So here is what I come up with, given we only handle these screen sizes (iOS).

local res = { i4 = { w = 320, h = 480, f = 2 } , i5 = { w = 320, h = 568, f = 2 } , i6 = { w = 375, h = 667, f = 2 } , i6p = { w = 360, h = 640, f = 3 } } local pw, ph = display.pixelWidth, display.pixelHeight local tw, th for name, data in pairs(res) do if pw / data.w == data.f and ph / data.h == data.f then tw, th = data.w, data.h end end application = { content = { scale = 'letterbox' , width = tw , height = th , fps = 60 } }

Your game will look roughly the same across iPhones and have no sub-pixel tiles.

A similar problem I have no idea how to solve is this:

9EXGEjC.png

Using similar code as above, I run into this problem, where image sheet tile selection seem to be off-by-1px, you can see the corner is not aligned correctly on iOS, and the white line is caused by selecting adjacent tile on the image sheet (but it works perfectly fine in my Tiled map editor and Corona SDK simulator).

(EDIT: This problem has nothing to do with panning and is persistent whatever the position of the map; extruding doesn’t fix it completely, isImageSheetSampledInsideFrame fixes it, but it is a global value so cause the same problem described above.)

The answer to this is to avoid the adaptive scaling in config.lua , because it could use non-integer scaling factor (say 1.5x) and there is nothing you can do to prevent sub-pixel positioning.

Instead, you should be using something like a smart-pixel config. Adaptive scaling trades integer scaling factor for a consistent virtual pixel unit across devices. We don’t really need that, we just need the content width and height to be closed enough; what we do need is integer scaling factor to prevent sub-pixel rendering.

So here is what I come up with, given we only handle these screen sizes (iOS).

local res = { i4 = { w = 320, h = 480, f = 2 } , i5 = { w = 320, h = 568, f = 2 } , i6 = { w = 375, h = 667, f = 2 } , i6p = { w = 360, h = 640, f = 3 } } local pw, ph = display.pixelWidth, display.pixelHeight local tw, th for name, data in pairs(res) do if pw / data.w == data.f and ph / data.h == data.f then tw, th = data.w, data.h end end application = { content = { scale = 'letterbox' , width = tw , height = th , fps = 60 } }

Your game will look roughly the same across iPhones and have no sub-pixel tiles.