Writing Text on an Image in a sprite

I repeatedly have this issue and sometimes I manage to make it work but I feel I am not understanding what is going on when writing text on a sprite.
In a nutshell, I can make text appear just fine if I create an image from scratch but I cannot write on an existing image, is it by design?

I have a simple class showing the name of a building on a sign. This name is updated whenever required by the main loop. The Sign "frame" is displayed when required but the text itself is not. Would anyone understand why?

class("BuildingSign").extends(gfx.sprite)

function BuildingSign:init()
    BuildingSign.super.init(self)

    self:moveTo(106, 145)
    self:setVisible(false)
    self:add()
end

function BuildingSign:updateText(cellType)

    local imgSign = gfx.image.new("images/buildingSign") -- This one doesn't work
    -- local imgSign = gfx.image.new(70, 20, gfx.kColorWhite) -- This one works
    assert(imgSign)
    self:setImage(imgSign)
    
    if (cellType >= 3) then -- building type
        local buildingName = ""

        if (cellType == 3) then buildingName = "POLICE" 
        elseif (cellType == 4) then buildingName = "BANK"
        elseif (cellType == 5) then buildingName = "GARAGE"
        elseif (cellType == 6) then buildingName = "FUEL"
        end

        gfx.pushContext(imgSign)
            gfx.setColor(gfx.kColorXOR)
            gfx.drawTextAligned(buildingName, imgSign.width / 2, 0, kTextAlignment.center)
        gfx.popContext()

        self:setVisible(true)
    else 
        self:setVisible(false)
    end
end```

For this passing by, my mistake is that gfx.setColor is not applied to font/text but to primitives only (not bitmap). It seems we can use the setImageDrawMode instead.
Please, please, correct me if I am wrong on this!

I think you need this line at the end of your function, after you’ve drawn the text on imgSign?

Thanks for looking into this. My mistake was to forget that texts are image, so the setColor has no effect there. We have to set the drawmode instead. It IS in the doc, but I somehow missed / not read this part...
The text was written but in black, on a black background... Classic!

Neat. So does sprite:setImage just pass the sprite a reference to the original image? So I can draw to the original instead of doing sprite:getImage, drawing to that and then doing another sprite:setImage?

So I am not sure that a reference (as in a pointer is actually passed as I am not sure how it works in Lua), nonetheless there is no need to create multiple images indeed. I pasted the code I am using below and will probably change it further to update a text variable, mark the sprite dirty and redefine draw if needed
Nota: still testing so there may be some bugs :slight_smile:

local pd <const> = playdate 
 local gfx <const> = pd.graphics 
  
 class("BuildingSign").extends(gfx.sprite) 
  
 function BuildingSign:init() 
     BuildingSign.super.init(self) 
  
     self:moveTo(106, 145) 
     self:setVisible(false) 
     self:add() 
 end 
  
 function BuildingSign:updateText(cellType) 
  
     if (cellType >= 3) then -- building type 
         local buildingName = "" 
  
         if (cellType == 3) then buildingName = "POLICE"  
         elseif (cellType == 4) then buildingName = "BANK" 
         elseif (cellType == 5) then buildingName = "GARAGE" 
         elseif (cellType == 6) then buildingName = "FUEL" 
         end 
  
         local imgSign = gfx.image.new("images/buildingSign")
        assert(imgSign)
        self:setImage(imgSign)

        gfx.pushContext(imgSign)
            local fnt = gfx.font.new("fonts/Blades of Steel")
            gfx.setFont(fnt)
            gfx.setImageDrawMode(gfx.kDrawModeNXOR)
            gfx.drawTextAligned(buildingName, self.width / 2, 8, kTextAlignment.center)
        gfx.popContext()
  
         self:setVisible(true) 
     else  
         self:setVisible(false) 
     end 
 end

EDIT: I made further tests and as I thought if you are moving from one text to another the previously submitted code will merge the texts as well, which is not the expected behavior. In this instance, we have no choice but to reload the original image to "start fresh" - I updated the code accordingly

I wonder if there is a smarter / more efficient way to do this

1 Like

Loading from disk is expensive. I'd recommend doing this once only in your init.

To achieve your goal create a copy of your loaded image and draw into that, then when you need to reset it do so with the other already loaded version.

https://sdk.play.date/inside-playdate/#m-graphics.image.copy

1 Like

Thanks @matt for the great tip, I didn't know. I haven't paid attention to the optimisation yet but might as well adopt best practices now!
Is there any doc online talking about the optimisation besides just trying to go easy on the CPU?

EDIT: Below is the modified code with the copy for those interested.

local pd <const> = playdate
local gfx <const> = pd.graphics

class("BuildingSign").extends(gfx.sprite)

function BuildingSign:init()
    BuildingSign.super.init(self)

    -- We record the original copy for later use
    self.imgOriginalSign = gfx.image.new("images/buildingSign")
    assert(self.imgOriginalSign)

    self.fntSign = gfx.font.new("fonts/Blades of Steel")
    assert(self.fntSign)
        
    self:setSize(self.imgOriginalSign:getSize())
    self:moveTo(106, 145)
    self:setVisible(false)
    self:add()
end

function BuildingSign:updateText(cellType)

    if (cellType >= 3) then -- building type
        local buildingName = ""

        if (cellType == 3) then buildingName = "POLICE" 
        elseif (cellType == 4) then buildingName = "BANK"
        elseif (cellType == 5) then buildingName = "GARAGE"
        elseif (cellType == 6) then buildingName = "FUEL"
        end

        -- We copy the original Image as disk access is expensive
        local imgSign = self.imgOriginalSign:copy()

        gfx.pushContext(imgSign)
            gfx.setFont(self.fntSign)
            gfx.setImageDrawMode(gfx.kDrawModeNXOR)
            gfx.drawTextAligned(buildingName, self.width / 2, 8, kTextAlignment.center)
        gfx.popContext()

        self:setImage(imgSign)
        self:setVisible(true)
    else 
        self:setVisible(false)
    end
end