modularizing
This commit is contained in:
177
flexlove/NinePatchParser.lua
Normal file
177
flexlove/NinePatchParser.lua
Normal file
@@ -0,0 +1,177 @@
|
||||
--[[
|
||||
NinePatchParser.lua - 9-patch PNG parser for FlexLove
|
||||
Parses Android-style 9-patch images to extract stretch regions and content padding
|
||||
]]
|
||||
|
||||
-- ====================
|
||||
-- Error Handling Utilities
|
||||
-- ====================
|
||||
|
||||
--- Standardized error message formatter
|
||||
---@param module string -- Module name (e.g., "Color", "Theme", "Units")
|
||||
---@param message string -- Error message
|
||||
---@return string -- Formatted error message
|
||||
local function formatError(module, message)
|
||||
return string.format("[FlexLove.%s] %s", module, message)
|
||||
end
|
||||
|
||||
-- ====================
|
||||
-- Dependencies
|
||||
-- ====================
|
||||
|
||||
local ImageDataReader = require((...):match("(.-)[^%.]+$") .. "ImageDataReader")
|
||||
|
||||
-- ====================
|
||||
-- NinePatchParser
|
||||
-- ====================
|
||||
|
||||
local NinePatchParser = {}
|
||||
|
||||
--- Find all continuous runs of black pixels in a pixel array
|
||||
---@param pixels table -- Array of {r, g, b, a} pixel values
|
||||
---@return table -- Array of {start, end} pairs (1-based indices, inclusive)
|
||||
local function findBlackPixelRuns(pixels)
|
||||
local runs = {}
|
||||
local inRun = false
|
||||
local runStart = nil
|
||||
|
||||
for i = 1, #pixels do
|
||||
local pixel = pixels[i]
|
||||
local isBlack = ImageDataReader.isBlackPixel(pixel.r, pixel.g, pixel.b, pixel.a)
|
||||
|
||||
if isBlack and not inRun then
|
||||
-- Start of a new run
|
||||
inRun = true
|
||||
runStart = i
|
||||
elseif not isBlack and inRun then
|
||||
-- End of current run
|
||||
table.insert(runs, { start = runStart, ["end"] = i - 1 })
|
||||
inRun = false
|
||||
runStart = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle case where run extends to end of array
|
||||
if inRun then
|
||||
table.insert(runs, { start = runStart, ["end"] = #pixels })
|
||||
end
|
||||
|
||||
return runs
|
||||
end
|
||||
|
||||
--- Parse a 9-patch PNG image to extract stretch regions and content padding
|
||||
---@param imagePath string -- Path to the 9-patch image file
|
||||
---@return table|nil, string|nil -- Returns {insets, stretchX, stretchY} or nil, error message
|
||||
function NinePatchParser.parse(imagePath)
|
||||
if not imagePath then
|
||||
return nil, "Image path cannot be nil"
|
||||
end
|
||||
|
||||
local success, imageData = pcall(function()
|
||||
return ImageDataReader.loadImageData(imagePath)
|
||||
end)
|
||||
|
||||
if not success then
|
||||
return nil, "Failed to load image data: " .. tostring(imageData)
|
||||
end
|
||||
|
||||
local width = imageData:getWidth()
|
||||
local height = imageData:getHeight()
|
||||
|
||||
-- Validate minimum size (must be at least 3x3 with 1px border)
|
||||
if width < 3 or height < 3 then
|
||||
return nil, string.format("Invalid 9-patch dimensions: %dx%d (minimum 3x3)", width, height)
|
||||
end
|
||||
|
||||
-- Extract border pixels (0-based indexing, but we convert to 1-based for processing)
|
||||
local topBorder = ImageDataReader.getRow(imageData, 0)
|
||||
local leftBorder = ImageDataReader.getColumn(imageData, 0)
|
||||
local bottomBorder = ImageDataReader.getRow(imageData, height - 1)
|
||||
local rightBorder = ImageDataReader.getColumn(imageData, width - 1)
|
||||
|
||||
-- Remove corner pixels from borders (they're not part of the stretch/content markers)
|
||||
-- Top and bottom borders: remove first and last pixel
|
||||
local topStretchPixels = {}
|
||||
local bottomContentPixels = {}
|
||||
for i = 2, #topBorder - 1 do
|
||||
table.insert(topStretchPixels, topBorder[i])
|
||||
end
|
||||
for i = 2, #bottomBorder - 1 do
|
||||
table.insert(bottomContentPixels, bottomBorder[i])
|
||||
end
|
||||
|
||||
-- Left and right borders: remove first and last pixel
|
||||
local leftStretchPixels = {}
|
||||
local rightContentPixels = {}
|
||||
for i = 2, #leftBorder - 1 do
|
||||
table.insert(leftStretchPixels, leftBorder[i])
|
||||
end
|
||||
for i = 2, #rightBorder - 1 do
|
||||
table.insert(rightContentPixels, rightBorder[i])
|
||||
end
|
||||
|
||||
-- Find stretch regions (top and left borders)
|
||||
local stretchX = findBlackPixelRuns(topStretchPixels)
|
||||
local stretchY = findBlackPixelRuns(leftStretchPixels)
|
||||
|
||||
-- Find content padding regions (bottom and right borders)
|
||||
local contentX = findBlackPixelRuns(bottomContentPixels)
|
||||
local contentY = findBlackPixelRuns(rightContentPixels)
|
||||
|
||||
-- Validate that we have at least one stretch region
|
||||
if #stretchX == 0 or #stretchY == 0 then
|
||||
return nil, "No stretch regions found (top or left border has no black pixels)"
|
||||
end
|
||||
|
||||
-- Calculate stretch insets from stretch regions (top/left guides)
|
||||
-- Use the first stretch region's start and last stretch region's end
|
||||
local firstStretchX = stretchX[1]
|
||||
local lastStretchX = stretchX[#stretchX]
|
||||
local firstStretchY = stretchY[1]
|
||||
local lastStretchY = stretchY[#stretchY]
|
||||
|
||||
-- Stretch insets define the 9-slice regions
|
||||
local stretchLeft = firstStretchX.start
|
||||
local stretchRight = #topStretchPixels - lastStretchX["end"]
|
||||
local stretchTop = firstStretchY.start
|
||||
local stretchBottom = #leftStretchPixels - lastStretchY["end"]
|
||||
|
||||
-- Calculate content padding from content guides (bottom/right guides)
|
||||
-- If content padding is defined, use it; otherwise use stretch regions
|
||||
local contentLeft, contentRight, contentTop, contentBottom
|
||||
|
||||
if #contentX > 0 then
|
||||
contentLeft = contentX[1].start
|
||||
contentRight = #topStretchPixels - contentX[#contentX]["end"]
|
||||
else
|
||||
contentLeft = stretchLeft
|
||||
contentRight = stretchRight
|
||||
end
|
||||
|
||||
if #contentY > 0 then
|
||||
contentTop = contentY[1].start
|
||||
contentBottom = #leftStretchPixels - contentY[#contentY]["end"]
|
||||
else
|
||||
contentTop = stretchTop
|
||||
contentBottom = stretchBottom
|
||||
end
|
||||
|
||||
return {
|
||||
insets = {
|
||||
left = stretchLeft,
|
||||
top = stretchTop,
|
||||
right = stretchRight,
|
||||
bottom = stretchBottom,
|
||||
},
|
||||
contentPadding = {
|
||||
left = contentLeft,
|
||||
top = contentTop,
|
||||
right = contentRight,
|
||||
bottom = contentBottom,
|
||||
},
|
||||
stretchX = stretchX,
|
||||
stretchY = stretchY,
|
||||
}
|
||||
end
|
||||
|
||||
return NinePatchParser
|
||||
Reference in New Issue
Block a user