diff --git a/CHANGELOG.md b/CHANGELOG.md index e340500..eb6d286 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,28 @@ ### Fixed +- **A staged battle on a phone stood some Pokémon three times the size of the + square they were on.** A Pidgey towered over the arena while the mon beside + it was the right size, which reads as a bug in one species and is not one. + + Putting the paper back inside a battle pic (BattlePics, below) needs the + pic's pixels, and a LOVE Image does not hand them back -- so the pic is drawn + into a canvas of its own size and the canvas is read. `newCanvas` takes the + SURFACE's dpi scale when it is not told otherwise, `conf.lua` turns highdpi + on for Android and iOS, and Android's display density is routinely 2.75. So + `newCanvas(56, 56)` allocated a 154x154 texture there, the pic was magnified + into it, and the readback came back at the magnified size. The rebuilt pic + was 2.75x the artwork, the engine's pics layer drew it 1:1 because it trusts + `getWidth()`, and the mon stood on its tile nearly three times too big. + + Only a pic with an enclosed hole in it is rebuilt at all -- the rest are + handed straight back untouched -- which is why it hit some species and not + others, and why it never showed on desktop, where the dpi scale is already 1. + The readback now asks for one texel per pic pixel, the way the engine's own + `PixelCanvas` does for the same reason. The animated-tile atlas readback took + the same fix: on a phone it would have come back magnified too, and every + tile coordinate in it counts in eights from the top-left. + - **Battle pics were see-through, and it took a back sprite on a tiled floor to make it obvious.** Gen 1 pics are two-bit art whose lightest shade is white, and the decoded PNGs key that shade to alpha 0 -- which cost nothing diff --git a/lib/BattlePics.lua b/lib/BattlePics.lua index f40d816..d74ec24 100644 --- a/lib/BattlePics.lua +++ b/lib/BattlePics.lua @@ -88,6 +88,21 @@ local CUT = 0.5 -- its data back, so it is drawn into a canvas of its own size and the canvas -- is read -- which is also what makes this work for every path that produces -- a pic, without knowing which one produced this one. +-- +-- The canvas is forced to dpiscale = 1, and that is the whole difference +-- between a pic and a MONSTER. love.graphics.newCanvas defaults its dpiscale +-- to the surface's, conf.lua turns highdpi on for Android and iOS, and +-- Android's density is routinely 2.75 -- so newCanvas(56, 56) hands back a +-- 154x154 texture there, the pic is drawn into it magnified to fill it, and +-- newImageData reads the magnified copy back at its own PIXEL size. The image +-- built from that is 2.75x the artwork, drawPicsLayer draws it at 1:1 because +-- it trusts getWidth(), and the mon stands on the map nearly three times the +-- size of the square it is supposed to cover. Desktop never saw it: dpiscale +-- is already 1 there. Nor did every species, because only a pic with an +-- enclosed hole in it comes back through here at all (see `changed` below) -- +-- so a Pidgey came out giant and the mon beside it did not, which is what +-- makes this read as a sprite bug rather than a scale one. See the engine's +-- own src/render/PixelCanvas.lua, which exists for exactly this reason. local function readBack(img) local w, h = img:getDimensions() if w <= 0 or h <= 0 then return nil end @@ -96,7 +111,7 @@ local function readBack(img) local prevR, prevG, prevB, prevA = love.graphics.getColor() local data = nil local ok = pcall(function() - local canvas = love.graphics.newCanvas(w, h) + local canvas = love.graphics.newCanvas(w, h, { dpiscale = 1 }) love.graphics.setCanvas(canvas) love.graphics.clear(0, 0, 0, 0) love.graphics.setBlendMode("replace", "premultiplied") diff --git a/lib/TerrainAtlas.lua b/lib/TerrainAtlas.lua index 8d12e36..3ebfde6 100644 --- a/lib/TerrainAtlas.lua +++ b/lib/TerrainAtlas.lua @@ -270,7 +270,12 @@ local function readback(image) local prev = love.graphics.getCanvas() local ok, data = pcall(function() local w, h = image:getDimensions() - local canvas = love.graphics.newCanvas(w, h) + -- dpiscale = 1, or this is not a copy. On a highdpi surface (Android, + -- iOS -- see conf.lua) newCanvas takes the surface's scale by default, + -- so the atlas would be drawn into a texture 2.75x its size and read + -- back magnified -- and every tile coordinate below, which counts in + -- eights from the top-left, would land somewhere between two tiles. + local canvas = love.graphics.newCanvas(w, h, { dpiscale = 1 }) love.graphics.setCanvas(canvas) love.graphics.clear(0, 0, 0, 0) -- straight copy: no blending against the cleared target, no tint from diff --git a/tests/dramatic_shape_test.lua b/tests/dramatic_shape_test.lua index 0249247..95b969d 100644 --- a/tests/dramatic_shape_test.lua +++ b/tests/dramatic_shape_test.lua @@ -2170,6 +2170,7 @@ local BattlePics = run.loader.exports.DRAMATIC_SHAPE.lib.require("BattlePics") -- Run one hand-drawn figure through the real BattlePics and hand back a -- reader over what came out. The pic is faked at the readback seam, which is -- the only thing between this and the pixels the engine would have blitted. +local lastCanvas = nil -- what the readback asked newCanvas for local function fill(rows) local W, H = #rows[1], #rows local built = nil @@ -2189,7 +2190,8 @@ local function fill(rows) end local realNewCanvas, realNewImage = love.graphics.newCanvas, love.graphics.newImage - love.graphics.newCanvas = function() + love.graphics.newCanvas = function(cw, ch, opts) + lastCanvas = { w = cw, h = ch, opts = opts } return { setFilter = function() end, release = function() end, newImageData = fakeData } end @@ -2285,6 +2287,24 @@ local drainOut, drainPic, drain = fill({ T.check(drainOut ~= drainPic and drain and drain(8, 4), "a narrow one is where the drawing ran out, and is paper") +-- ------- and the readback is measured in PIXELS, which is what kept the mons +-- the size of the squares they stand on +-- +-- love.graphics.newCanvas takes the SURFACE's dpi scale when it is not told +-- otherwise, conf.lua turns highdpi on for Android and iOS, and Android's +-- density is routinely 2.75. So an untold newCanvas(56, 56) allocated a +-- 154x154 texture on a phone, the pic was magnified into it, and newImageData +-- read the magnified copy back at its own size -- an image 2.75x the artwork, +-- which drawPicsLayer then drew at 1:1 because it trusts getWidth(). The mon +-- stood on the map three times the size of its tile. +-- +-- Only for a pic with paper to put back, which is why it read as a bug in +-- particular Pokemon (a giant Pidgey beside a normal mon) rather than as a +-- scale that was wrong everywhere. +T.check(lastCanvas and lastCanvas.opts and lastCanvas.opts.dpiscale == 1, + "the readback canvas is one texel per pic pixel, on a highdpi phone too") +T.eq(lastCanvas.w, 18, "and it is the size of the pic, in those pixels") + -- the answer is cached on the image, so a pic costs one readback a session -- rather than one a frame -- checked on the first figure, which is still in -- there because fill() does not clear it