Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions client/src/graphics/graphics_util.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local consts = require("common.engine.consts")
local logger = require("common.lib.logger")
local FileUtils = require("client.src.FileUtils")
local system = require("client.src.system")

-- Utility methods for drawing
local GraphicsUtil = {
Expand Down Expand Up @@ -379,9 +380,50 @@ function GraphicsUtil.resetAlignment()
love.graphics.pop()
end

local loveMajor = love.getVersion()
--- A wrapper function to create a love.Texture that persists in memory for drawing
---@param width number Canvas width
---@param height number Canvas height
---@param drawFunc function Function to call inside renderTo
---@param dpiscale number DPI scale for the canvas
---@param filterMin string filter mode
---@param filterMag string filter mode
---@return love.graphics.Texture
function GraphicsUtil.renderToTexture(width, height, drawFunc, dpiscale, filterMin, filterMag)
love.graphics.push("all")
love.graphics.reset()

if loveMajor >= 12 then
local canvas = love.graphics.newCanvas(width, height, {dpiscale = dpiscale})

canvas:setFilter(filterMin, filterMag)

canvas:renderTo(drawFunc)

-- Restore graphics state
love.graphics.pop()

if not system.meetsLoveVersionRequirement(12, 0) then
-- in love 11.5 Image and Canvas are distinct types inheriting from the Texture type that behave differently in a number of things
-- a main difference is that love.window.updateMode and love.window.setMode cause all Canvas objects to be cleared so that they need to be redrawn
-- to avoid this, immediately use the canvas's ImageData to create an Image type object that does not get cleared by window updates
local imageData
imageData = canvas:newImageData()

local image = love.graphics.newImage(imageData, {dpiscale = dpiscale})

-- Preserve filter settings on the image
if filterMin and filterMag then
image:setFilter(filterMin, filterMag)
end

return image
else
-- in love 12.0 the Canvas type does no longer exist, everything declared with newCanvas is a Texture and window updates don't clear these
-- that means we can use the canvas directly
return canvas
end
end

if system.meetsLoveVersionRequirement(12, 0) then
GraphicsUtil.newText = love.graphics.newTextBatch
else
GraphicsUtil.newText = love.graphics.newText
Expand Down
79 changes: 40 additions & 39 deletions client/src/mods/Character.lua
Original file line number Diff line number Diff line change
Expand Up @@ -392,26 +392,35 @@ end

-- bundles without stage icon display up to 4 icons of their substages
function Character:createBundleIcon()
local canvas = love.graphics.newCanvas(2 * 168, 2 * 168)
canvas:renderTo(function()
for i, subCharacterId in ipairs(self.subIds) do
-- only draw up to 4 and only draw sub mods that are actually there unless there are none
if i <= 4 and (characters[subCharacterId] or (allCharacters[subCharacterId] and #self:getSubMods() == 0)) then
local character = allCharacters[subCharacterId]
local x = 0
local y = 0
if i % 2 == 0 then
x = 168
local firstCharacter = allCharacters[self.subIds[1]]
assert(firstCharacter ~= nil, "Expected a valid character in sub IDs")
local filterMin, filterMag = firstCharacter.images.icon:getFilter()
local image = GraphicsUtil.renderToTexture(
2 * 168,
2 * 168,
function()
for i, subCharacterId in ipairs(self.subIds) do
-- only draw up to 4 and only draw sub mods that are actually there unless there are none
if i <= 4 and (characters[subCharacterId] or (allCharacters[subCharacterId] and #self:getSubMods() == 0)) then
local character = allCharacters[subCharacterId]
local x = 0
local y = 0
if i % 2 == 0 then
x = 168
end
if i > 2 then
y = 168
end
local width, height = character.images.icon:getDimensions()
love.graphics.draw(character.images.icon, x, y, 0, 168 / width, 168 / height)
end
if i > 2 then
y = 168
end
local width, height = character.images.icon:getDimensions()
love.graphics.draw(character.images.icon, x, y, 0, 168 / width, 168 / height)
end
end
end)
return canvas
end,
GAME:newCanvasSnappedScale(),
filterMin,
filterMag
)
return image
end

function Character.graphics_uninit(self)
Expand Down Expand Up @@ -581,17 +590,23 @@ function Character:createGarbageTexture(width, height)
local relativeScale = self.images.pop:getWidth() / 16
-- create all canvases as if we were working with the 360x240 resolution but use the canvas dpi scale to use the real resolution
-- that makes it easy to scale later as everything can be treated the same while love handles the dpi scale resolution for us
local canvas = love.graphics.newCanvas(width * 16, height * 16, {dpiscale = self.images.pop:getDPIScale() * relativeScale})
local dpiscale = self.images.pop:getDPIScale() * relativeScale

-- Use the same filter as the garbage images so that upscaling looks right for pixel art
local min, mag = self.images.pop:getFilter()
canvas:setFilter(min, mag)
local filterMin, filterMag = self.images.pop:getFilter()

canvas:renderTo(function()
self:__drawGarbage(width, height)
end)
local image = GraphicsUtil.renderToTexture(
width * 16,
height * 16,
function()
self:__drawGarbage(width, height)
end,
dpiscale,
filterMin,
filterMag
)

return canvas
return image
end

--- returns an existing prerender or if there is none, creates one and caches it for reuse
Expand All @@ -604,21 +619,7 @@ function Character:getGarbageTexture(width, height)
end

if not self.garbagePrerenders[width][height] then
-- canvases are affected by scissors and transformations so we need to make sure to suspend them
local sx, sy, w, h = love.graphics.getScissor()
if sx then
love.graphics.setScissor()
end
love.graphics.push("transform")
love.graphics.origin()

self.garbagePrerenders[width][height] = self:createGarbageTexture(width, height)

-- and then reapply them
love.graphics.pop()
if sx then
love.graphics.setScissor(sx, sy, w, h)
end
end

return self.garbagePrerenders[width][height]
Expand Down
66 changes: 40 additions & 26 deletions client/src/mods/Panels.lua
Original file line number Diff line number Diff line change
Expand Up @@ -190,31 +190,36 @@ function Panels:loadSheets()
self.size = self.sheets[1]:getHeight() / maxRowUsed
end

--
--
function Panels:convertSinglesToSheetTexture(images, animationStates)
local canvas = love.graphics.newCanvas(self.size * 10, self.size * #animationStates, {dpiscale = images[1]:getDPIScale()})
if self.size <= 24 then
-- none of the panels is bigger than 24x24 so we can assume pixel art style panels
canvas:setFilter("nearest", "nearest")
end
canvas:renderTo(function()
local row = 1
-- ipairs over a static table so the ordering is definitely consistent
for _, animationState in ipairs(animationStates) do
local animationConfig = self.animationConfig[animationState]
for frameNumber, imageIndex in ipairs(animationConfig.frames) do
local widthScale = self.size / images[imageIndex]:getWidth()
local heightScale = self.size / images[imageIndex]:getHeight()
if heightScale > 1 or widthScale > 1 then
images[imageIndex]:setFilter("nearest", "nearest")
local dpiscale = images[1]:getDPIScale()
local filterMin, filterMag = images[1]:getFilter()

local image = GraphicsUtil.renderToTexture(
self.size * 10,
self.size * #animationStates,
function()
local row = 1
-- ipairs over a static table so the ordering is definitely consistent
for _, animationState in ipairs(animationStates) do
local animationConfig = self.animationConfig[animationState]
for frameNumber, imageIndex in ipairs(animationConfig.frames) do
local widthScale = self.size / images[imageIndex]:getWidth()
local heightScale = self.size / images[imageIndex]:getHeight()
if heightScale > 1 or widthScale > 1 then
images[imageIndex]:setFilter("nearest", "nearest")
end
love.graphics.draw(images[imageIndex], self.size * (frameNumber - 1), self.size * (row - 1), nil, widthScale, heightScale)
end
love.graphics.draw(images[imageIndex], self.size * (frameNumber - 1), self.size * (row - 1),nil, widthScale, heightScale)
row = row + 1
end
row = row + 1
end
end)
end,
dpiscale,
filterMin,
filterMag
)

return canvas
return image
end

local function validateSingleFilesAgainstConfig(imagesByColorAndIndex, animationConfig)
Expand Down Expand Up @@ -344,12 +349,21 @@ function Panels:load()

self.quad = love.graphics.newQuad(0, 0, self.size, self.size, self.sheets[1]:getDimensions())
self.displayIcons = {}

local dpiscale = self.sheets[1]:getDPIScale()
local filterMin, filterMag = self.sheets[1]:getFilter()

for color = 1, 8 do
local canvas = love.graphics.newCanvas(self.size, self.size)
canvas:renderTo(function()
self:drawPanelFrame(color, "normal", 0, 0)
end)
self.displayIcons[color] = canvas
self.displayIcons[color] = GraphicsUtil.renderToTexture(
self.size,
self.size,
function()
self:drawPanelFrame(color, "normal", 0, 0)
end,
dpiscale,
filterMin,
filterMag
)
--fileUtils.saveTextureToFile(self.sheets[color], self.path .. "/panel-" .. color, "png")
self.batches[color] = love.graphics.newSpriteBatch(self.sheets[color], 100, "stream")
end
Expand Down
43 changes: 26 additions & 17 deletions client/src/mods/Stage.lua
Original file line number Diff line number Diff line change
Expand Up @@ -183,25 +183,34 @@ end

-- bundles without stage thumbnail display up to 4 thumbnails of their substages
function Stage:createBundleThumbnail()
local canvas = love.graphics.newCanvas(2 * 80, 2 * 45)
canvas:renderTo(function()
for i, substageId in ipairs(self.subIds) do
if i <= 4 and (stages[substageId] or (allStages[substageId] and #self:getSubMods() == 0)) then
local stage = allStages[substageId]
local x = 0
local y = 0
if i % 2 == 0 then
x = 80
local firstStage = allStages[self.subIds[1]]
assert(firstStage ~= nil, "Expected a valid character in sub IDs")
local filterMin, filterMag = firstStage.images.thumbnail:getFilter()
local image = GraphicsUtil.renderToTexture(
2 * 80,
2 * 45,
function()
for i, substageId in ipairs(self.subIds) do
if i <= 4 and (stages[substageId] or (allStages[substageId] and #self:getSubMods() == 0)) then
local stage = allStages[substageId]
local x = 0
local y = 0
if i % 2 == 0 then
x = 80
end
if i > 2 then
y = 45
end
local width, height = stage.images.thumbnail:getDimensions()
love.graphics.draw(stage.images.thumbnail, x, y, 0, 80 / width, 45 / height)
end
if i > 2 then
y = 45
end
local width, height = stage.images.thumbnail:getDimensions()
love.graphics.draw(stage.images.thumbnail, x, y, 0, 80 / width, 45 / height)
end
end
end)
return canvas
end,
GAME:newCanvasSnappedScale(),
filterMin,
filterMag
)
return image
end

-- uninits stage graphics
Expand Down
23 changes: 15 additions & 8 deletions client/src/scenes/CharacterSelect.lua
Original file line number Diff line number Diff line change
Expand Up @@ -338,15 +338,22 @@ function CharacterSelect:getCharacterButtons()
end

if character.panels and panels[character.panels] then
-- draw the color 1 normal panel in the left corner
-- it's only available on the sheet so we got to render it to its own canvas first
local panels = panels[character.panels]
local canvas = love.graphics.newCanvas(panels.size, panels.size)
canvas:renderTo(function()
panels:drawPanelFrame(1, "normal", 0, 0, panels.size)
end)

characterButton.panelIcon = ui.ImageContainer({image = canvas, vAlign = "bottom", hAlign = "left", x = 2, y = -2, width = 16, height = 16})
local dpiscale = panels.sheets[1]:getDPIScale()
local filterMin, filterMag = panels.sheets[1]:getFilter()

local panelImage = GraphicsUtil.renderToTexture(
panels.size,
panels.size,
function()
panels:drawPanelFrame(1, "normal", 0, 0, panels.size)
end,
dpiscale,
filterMin,
filterMag
)

characterButton.panelIcon = ui.ImageContainer({image = panelImage, vAlign = "bottom", hAlign = "left", x = 2, y = -2, width = 16, height = 16})
characterButton:addChild(characterButton.panelIcon)
end

Expand Down
Loading