account for dpi issues on updated 3d battles

This commit is contained in:
DramaticShape
2026-07-31 13:51:54 -04:00
parent 4da8e5dc3e
commit 775757b2d6
4 changed files with 65 additions and 3 deletions
+22
View File
@@ -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
+16 -1
View File
@@ -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")
+6 -1
View File
@@ -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
+21 -1
View File
@@ -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