Add a `:setImage` function for NineSlice objects

,

I wanted a way to reuse a nineslice, so I hacked it together with the existing Lua code in CoreLibs. Haven't done any testing, but it seems to be performant and works well enough for my prototyping

import 'CoreLibs/graphics'

local floor = math.floor
local newRect = playdate.geometry.rect.new
local loadImage = playdate.graphics.image.new
local newImage = playdate.graphics.image.new
local kColorClear = playdate.graphics.kColorClear
local pushContext = playdate.graphics.pushContext
local popContext = playdate.graphics.popContext
local insert = table.insert

playdate.graphics.ImageNineSlice = { }
playdate.graphics.ImageNineSlice.__index = playdate.graphics.ImageNineSlice

function playdate.graphics.ImageNineSlice.new(image, innerX, innerY, innerWidth, innerHeight)
    local ns = {
        innerRect = nil, -- playdate.geometry.rect
        slices = nil, -- table of playdate.graphics.images
        cache = nil, -- playdate.graphics.image
        cacheWidth = 0,
        cacheHeight = 0,
        minWidth = 0,
        minHeight = 0,
    }

	playdate.graphics.ImageNineSlice.setImage(ns, image, innerX, innerY, innerWidth, innerHeight)

    setmetatable(ns, playdate.graphics.ImageNineSlice)
    return ns
end

local _cache = {}
function playdate.graphics.ImageNineSlice:setImage(image, innerX, innerY, innerWidth, innerHeight)  -- Other arguments are optional
	if innerX ~= nil then
		self.innerRect = newRect(innerX, innerY, innerWidth, innerHeight)
	end
    if _cache[image] == nil then
		local w, h = image:getSize()
		local innerX = self.innerRect.x
		local innerY = self.innerRect.y
		local innerWidth = self.innerRect.width
        local innerHeight = self.innerRect.height

		local leftWidth = innerX
		local topHeight = innerY
		local rightWidth = w - (innerX + innerWidth)
        local bottomHeight = h - (innerY + innerHeight)

		self.minWidth = leftWidth + rightWidth
		self.minHeight = topHeight + bottomHeight
		self.innerRect = newRect(innerX, innerY, innerWidth, innerHeight)

		-- cache slices
		local rects = {
			0, 0, leftWidth, topHeight,                                -- top left
			innerX, 0, innerWidth, topHeight,                          -- top center
			innerX + innerWidth, 0, rightWidth, topHeight,             -- top right

			0, topHeight, leftWidth, innerHeight,                      -- middle left
			innerX, topHeight, innerWidth, innerHeight,                -- middle center
			innerX + innerWidth, topHeight, rightWidth, innerHeight,   -- middle right

			0, topHeight + innerHeight, leftWidth, bottomHeight,       -- bottom left
			innerX, topHeight + innerHeight, innerWidth, bottomHeight, -- bottom center
			innerX + innerWidth, topHeight + innerHeight, rightWidth, bottomHeight, -- bottom right
		}

        local t = #rects
        local slices = {}
        local slice

        for i = 1, t, 4 do
            slice = newImage(rects[i + 2], rects[i + 3], kColorClear)
            pushContext(slice) do
				image:draw(-rects[i], -rects[i + 1])
			end popContext()
            insert(slices, slice)
        end

        _cache[image] = slices
    end

    self.slices = _cache[image]
end

function playdate.graphics.ImageNineSlice:getSize()
	return self.cacheWidth,self.cacheHeight
end

function playdate.graphics.ImageNineSlice:getMinSize()
	return self.minWidth,self.minHeight
end

local function prerender(ns, width, height)
	ns.cacheWidth = width
	ns.cacheHeight = height

	local ix,iy,iw,ih = ns.innerRect:unpack()
	local mw,mh = ns.minWidth,ns.minHeight

	iw = width - mw
	ih = height - mh

	local slices = ns.slices
	local cache = newImage(width,height,kColorClear)
	pushContext(cache) do
		slices[1]:draw(0,0)
		if iw>0 then
			slices[2]:drawTiled(ix,0,iw,iy)
		end
		slices[3]:draw(ix+iw,0)

		if ih>0 then
			slices[4]:drawTiled(0,iy,ix,ih)
			if iw>0 then
				slices[5]:drawTiled(ix,iy,iw,ih)
			end
			slices[6]:drawTiled(ix+iw,iy,width-(ix+iw),ih)
		end

		slices[7]:draw(0,iy+ih)
		if iw>0 then
			slices[8]:drawTiled(ix,iy+ih,iw,height-(iy+ih))
		end
		slices[9]:draw(ix+iw,iy+ih)
	end popContext()
	ns.cache = cache
end


function playdate.graphics.ImageNineSlice:drawInRect(x, ...)
	local y, w, h
	if (type(x) == "userdata") then		-- check if x is a playdate.geometry.rect object
		x, y, w, h = x.x, x.y, x.width, x.height
	else
		y, w, h = select(1, ...)
	end

	local w = floor(w)
	local h = floor(h)

	if w < self.minWidth then w = self.minWidth end
	if h < self.minHeight then h = self.minHeight end

	if w ~= self.cacheWidth or h ~= self.cacheHeight then
		prerender(self, w, h)
	end
	if self.cache then
		self.cache:draw(x,y)
	end
end
2 Likes