Image:draw: drawOffset affects sourceRect instead of location

In a simple test, when setDrawOffset is used to pan the "camera" around a simple scene, and Image:draw(x, y, flip, sourcex, sourcey, w, h) is used to draw a small portion of a sprite sheet, the effect is to pan around within the source image, but always draw in the same location on screen. That doesn't seem useful. Adding the offset to both the location and sourceRect seems to make it do the thing you want, but defeats the purpose of drawOffset.

Here's a demonstration, including the (commented) workaround:

local gfx = playdate.graphics

local ox = 0
local oy = 0

local sheet = gfx.image.new("pokemonoverworldspriteslarger")

function playdate.update()
    if playdate.buttonIsPressed(playdate.kButtonLeft) then
        ox -= 1
    elseif playdate.buttonIsPressed(playdate.kButtonRight) then
        ox += 1
    end

    if playdate.buttonIsPressed(playdate.kButtonUp) then
        oy -= 1
    elseif playdate.buttonIsPressed(playdate.kButtonDown) then
        oy += 1
    end

    gfx.setDrawOffset(ox, oy)

    gfx.clear()

    -- A simple rect moves around as expected:
    gfx.drawRect(178, 98, 68, 67)

    -- Image with sourceRect: drawOffset seems to affect `sourceRect`, not `x` and `y`;
    -- a different portion of the image is drawn, but always at the same fixed location
    sheet:draw(180, 100, gfx.kImageUnflipped, 124, 64, 64, 63)

    -- Workaround: add the offset to both the location and sourceRect:
    -- sheet:draw(180+ox, 100+oy, gfx.kImageUnflipped, 124+ox, 64+oy, 64, 63)
end

I grabbed that sprite sheet randomly here

Actual result:
offset-test

Desired result:
offset-test-expected

Testing on macOS with pdc and simulator 1.9.0 (132294).

Note: in the context of my game, the behavior is less clear, with the rendered portion of the image clipping and jumping around in an unpredictable way. I'm hoping this simple demonstration is enough to identify the problem (or my misunderstanding) but I can try to reproduce the stranger behavior if necessary.

3 Likes

I've found this exact same bug. Let me restate it in different terms

if you have an image of width W and height H and are using image:draw() to draw the whole image at x,y the following should draw the exact same thing

-- draw the full image with no sourceRect at an offset
img:draw(x,y)

--- draw the full image at an offset specifying the full sourceRect bounds
img:draw(x,y,kImageUnflipped,0,0,W,H)

-- draw the full image at (0,0) with a drawOffset and no sourceRect
gfx.setDrawOffset(x,y)
img:draw(0,0)

-- draw the full image at (0,0) with a drawOffset and a sourceRect of the full image bounds
gfx.setDrawOffset(x,y)
img:draw(0,0,kImageUnflipped,0,0,W,H)

however that last one clips x off the width and y off the height. This is incorrect, the sourceRect should be relative ONLY to the image bounds and not where it's being drawn and the full image should be drawn

as @mossprescott notes you can 'hack' this by adding the current drawOffset to the width and height of the sourceRect which undoes the transformation going on in draw()

-- specify an incorrect huge sourceRect drawing at (0,0) with a drawOffset() set
gfx.setDrawOffset(x,y)
img:draw(0,0,kImageUnflipped, 0, 0, width + x, height + y) -- ugh!

This is a pretty bad bug as it affects blitting images to the dirty rect of a sprite in their draw methods as setDrawOffset() is always set before sprite:draw() is called. eg the code below which should draw a piece of background image into the sprite's dirty rect does not draw a ( w x h) piece of image as it should and doesn't cover the dirty rect.

function mySpite.draw(self, x,y,w,h)
-- blit the background image into the dirty rect - this fails as too little image is drawn
backgroundImage:draw(x,y,kImageUnflipped,x,y,w,h)
-- do the rest of your drawing here
end

Next post has a small main.lua which illustrates the bug (don't know why I can't just upload the source but I can't). Run the code and hit 'A' to cycle through the draw modes.

import 'CoreLibs/graphics'

--[[
    shows a bug in draw() with a source rect when a drawOrigin() is set.

    makes a 101 x 121 pixel image, draws a grid on it and colors in the top left and bottom right boxes
]]

local gfx=playdate.graphics

local testImage = gfx.image.new(101,121)

gfx.pushContext(testImage)
for i = 0, 100, 10 do
    gfx.drawLine(i,0,i,120)
end
for i = 0, 120, 20 do
    gfx.drawLine(0,i,100,i)
end
-- fill the top left and bottom right corners to show the image better
gfx.fillRect(0,0,10,20)
gfx.fillRect(90,100,10,20)
gfx.popContext()

--[[
state	- 
  0 draw at 40,50 with displayOffset set to (0,0) - no sourcerect
  1 draw at 40,50 with sourceRect equal to the full image
  2 draw at (0,0) with displayOffset set to (40,50) - no sourceRect
  3 draw at (0,0) with displayOffset set to (40,50) - and sourceRect specifying the full image <-- BUG BUG
  4 draw at (0,0) with displayOffset set to (40,50) and hack sourceRect (0,0, w + 40, h + 50) showing adding to     
      width/height hacks around the bug
]]

local state = 0

function playdate.update()
    gfx.clear()
    gfx.pushContext()

    local width, height = testImage:getSize()
    local offX, offY = 40, 30
    local text = ""

    if state == 0 then
	    testImage:draw(offX, offY)
	    text = "testImage:draw(offX, offY)\nnodrawOffset, no sourceRect"
    elseif state == 1 then
	    testImage:draw(offX, offY, gfx.kImageUnflipped, 0, 0, width, height)
	    text = "testImage:draw(offX, offY, gfx.kImageUnflipped, 0, 0, width, height)\nno drawOffset"
    elseif state == 2 then
	    gfx.setDrawOffset(offX,offY)
	    testImage:draw(0, 0)
	    text = "testImage:draw(0, 0)\ndrawOffset(40,30)"
    elseif state == 3 then
	    gfx.setDrawOffset(offX,offY)
	    testImage:draw(0, 0, gfx.kImageUnflipped, 0, 0, width, height)
	    text = "testImage:draw(0, 0, gfx.kImageUnflipped, 0, 0, width, height)\ndrawOffset(40,30) - BUG BUG"
    elseif state == 4 then
    	gfx.setDrawOffset(offX,offY)
        testImage:draw(0, 0, gfx.kImageUnflipped, 0, 0, width + offX, height + offY)
	    text = "testImage:draw(0, 0, gfx.kImageUnflipped, 0, 0, width + offX, height + offY)\ndrawOffset(40,30) - hacked sourceRect"
    end

    gfx.popContext()

    gfx.drawTextInRect(text, 0,180,400,60 )
end

function playdate.AButtonDown()
    state = ( state + 1 ) % 5
end

note .. the workaround I'm actually using for this in my sprite drawing is different from 'pad the rectangle' as that will lead to more being drawn than necessary if this bug is fixed. My workaround is

function MySprite.draw(self,x,y,w,h)
    offX, offY = gfx.getDrawOffset()
    setDrawOffset(0,0)
    img:draw(x+offX, y+offY, x,y,w,h)
    setDrawOffset(offX,offY)
   -- more drawing
end

resetting the drawOffset in this way works around the bug with hopefully very little performance loss

Ooph I just hit this bug as well on 1.10.0, so just hopping in here to +1 a fix.

Oof, I just spent time creating a minimal example for my report, only to discover you guys already did the work. Marked that one as solved; continuing here

1 Like

Might as well post my reproduction here:

local gfx <const> = playdate.graphics

local grid = {0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
local borderedCross = {0x0, 0x3C, 0x5A, 0x66, 0x66, 0x5A, 0x3C, 0x0}

gfx.setPattern(grid)
gfx.fillRect(0,64, 400,240)

-- prepare test image
local testImg = gfx.image.new(8,8)
gfx.pushContext(testImg)
    -- fill with a bordered cross
    gfx.setColor(gfx.kColorBlack)
    gfx.setPattern(borderedCross)
    gfx.fillRect(0,0, 8,8)
gfx.popContext()


gfx.setColor(gfx.kColorBlack)
gfx.drawText("All images should look the same", 16, 24)

gfx.drawText("no offset, no srcRect", 96, 120)
testImg:draw(80, 120)

gfx.setDrawOffset(4,4)

gfx.drawText("img offset, no srcRect", 96, 152)
testImg:draw(80, 152)

gfx.drawText("img offset, srcRect", 96, 184)
testImg:draw(80, 184, gfx.kImageUnFlipped,0,0,8,8)

gfx.drawText("pattern offset", 96, 216)
gfx.setPattern(borderedCross)
gfx.fillRect(80,216, 8,8)


function playdate.update()
end

Output:

Untitled
(open image in new tab to see unscaled version)

New finding fillRect with pattern also seems affected

Is panic aware of this bug @dave ?

1 Like

I've also run into this issue. I'm on version 1.11.1

Bumping this. I took a break from coding and restarted and got bitten by this bug AGAIN. This is a pretty straight up bug with several people having found it and quite a few repro examples; and it's somewhere between kinda of annoying and fairly serious. Can we get a fix for this please?

1 Like

If we all got 1 priority point to put on a bug, I'd pick this one.

1 Like

I have a fix in the queue for this now! Sorry it took so long to get to.

@Nino I believe the behavior of patterns not being affected by the draw offset is intentional, but I'll double check on that

Great! You might be right, as the pattern should be drawn relative to screen coordinates, rather than to the draw call.