luajitos

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

PNG.lua (12006B)


      1 -- PNG.lua - PNG Image Creation and Manipulation Library
      2 -- Requires: "imaging" permission
      3 
      4 local PNG = {}
      5 
      6 -- Check for imaging permission
      7 local function checkPermission()
      8     if not ADMIN_HasPermission then
      9         error("PNG library requires permission system, but ADMIN_HasPermission is not available")
     10     end
     11 
     12     if not ADMIN_HasPermission("imaging") then
     13         error("Permission denied: 'imaging' permission required to use PNG library")
     14     end
     15 end
     16 
     17 -- PNG image object
     18 local PNGImage = {}
     19 PNGImage.__index = PNGImage
     20 PNGImage.__metatable = false  -- Prevent metatable access/modification
     21 
     22 -- Create a new PNG image
     23 -- @param width Image width in pixels
     24 -- @param height Image height in pixels
     25 -- @param hasAlpha Optional, default true - whether image has alpha channel
     26 -- @return PNG image object
     27 function PNG.new(width, height, hasAlpha)
     28     checkPermission()
     29 
     30     if not width or not height then
     31         error("PNG.new requires width and height")
     32     end
     33 
     34     if width <= 0 or width > 4096 then
     35         error("PNG width must be between 1 and 4096")
     36     end
     37 
     38     if height <= 0 or height > 4096 then
     39         error("PNG height must be between 1 and 4096")
     40     end
     41 
     42     hasAlpha = (hasAlpha == nil) and true or hasAlpha
     43 
     44     local self = setmetatable({}, PNGImage)
     45 
     46     -- Create image buffer using C API
     47     -- We'll create a simple RGBA buffer in memory
     48     self.width = width
     49     self.height = height
     50     self.hasAlpha = hasAlpha
     51     self.bpp = hasAlpha and 32 or 24
     52 
     53     -- Allocate pixel data (RGBA format)
     54     -- Each pixel is 4 bytes (R, G, B, A)
     55     local pixelCount = width * height
     56     self.pixels = {}
     57 
     58     -- Initialize to transparent black (or opaque black if no alpha)
     59     local defaultAlpha = hasAlpha and 0 or 255
     60     for i = 1, pixelCount do
     61         self.pixels[i] = {
     62             r = 0,
     63             g = 0,
     64             b = 0,
     65             a = defaultAlpha
     66         }
     67     end
     68 
     69     -- Create C image buffer if available
     70     if ImageCreate then
     71         self.imageBuffer = ImageCreate(width, height, hasAlpha)
     72     end
     73 
     74     return self
     75 end
     76 
     77 -- Write a pixel to the image
     78 -- @param x X coordinate (0-based)
     79 -- @param y Y coordinate (0-based)
     80 -- @param color Color in hex format "RRGGBB" or "RRGGBBAA"
     81 function PNGImage:writePixel(x, y, color)
     82     checkPermission()
     83 
     84     if not x or not y or not color then
     85         error("writePixel requires x, y, and color")
     86     end
     87 
     88     if x < 0 or x >= self.width or y < 0 or y >= self.height then
     89         error("Pixel coordinates out of bounds: (" .. x .. ", " .. y .. ")")
     90     end
     91 
     92     -- Parse color string
     93     local r, g, b, a
     94 
     95     if type(color) == "string" then
     96         -- Remove # if present
     97         color = color:gsub("^#", "")
     98 
     99         if #color == 6 then
    100             -- RRGGBB format
    101             r = tonumber(color:sub(1, 2), 16)
    102             g = tonumber(color:sub(3, 4), 16)
    103             b = tonumber(color:sub(5, 6), 16)
    104             a = 255
    105         elseif #color == 8 then
    106             -- RRGGBBAA format
    107             r = tonumber(color:sub(1, 2), 16)
    108             g = tonumber(color:sub(3, 4), 16)
    109             b = tonumber(color:sub(5, 6), 16)
    110             a = tonumber(color:sub(7, 8), 16)
    111         else
    112             error("Invalid color format. Use 'RRGGBB' or 'RRGGBBAA'")
    113         end
    114     elseif type(color) == "number" then
    115         -- Treat as 0xRRGGBB or 0xRRGGBBAA
    116         if color <= 0xFFFFFF then
    117             -- RGB format
    118             r = bit.rshift(bit.band(color, 0xFF0000), 16)
    119             g = bit.rshift(bit.band(color, 0x00FF00), 8)
    120             b = bit.band(color, 0x0000FF)
    121             a = 255
    122         else
    123             -- RGBA format
    124             r = bit.rshift(bit.band(color, 0xFF000000), 24)
    125             g = bit.rshift(bit.band(color, 0x00FF0000), 16)
    126             b = bit.rshift(bit.band(color, 0x0000FF00), 8)
    127             a = bit.band(color, 0x000000FF)
    128         end
    129     else
    130         error("Color must be a string (hex) or number")
    131     end
    132 
    133     if not r or not g or not b then
    134         error("Failed to parse color: " .. tostring(color))
    135     end
    136 
    137     -- Calculate pixel index (row-major order)
    138     local index = y * self.width + x + 1
    139 
    140     -- Store pixel
    141     self.pixels[index] = {
    142         r = r,
    143         g = g,
    144         b = b,
    145         a = a or 255
    146     }
    147 
    148     -- Update C image buffer if available
    149     if self.imageBuffer and ImageSetPixel then
    150         ImageSetPixel(self.imageBuffer, x, y, r, g, b, a or 255)
    151     end
    152 end
    153 
    154 -- Read a pixel from the image
    155 -- @param x X coordinate (0-based)
    156 -- @param y Y coordinate (0-based)
    157 -- @return Color string in "RRGGBB" or "RRGGBBAA" format
    158 function PNGImage:readPixel(x, y)
    159     checkPermission()
    160 
    161     if not x or not y then
    162         error("readPixel requires x and y coordinates")
    163     end
    164 
    165     if x < 0 or x >= self.width or y < 0 or y >= self.height then
    166         error("Pixel coordinates out of bounds: (" .. x .. ", " .. y .. ")")
    167     end
    168 
    169     -- Calculate pixel index
    170     local index = y * self.width + x + 1
    171 
    172     local pixel = self.pixels[index]
    173 
    174     if not pixel then
    175         return "00000000"
    176     end
    177 
    178     -- Format as hex string
    179     local r = string.format("%02X", pixel.r)
    180     local g = string.format("%02X", pixel.g)
    181     local b = string.format("%02X", pixel.b)
    182 
    183     if self.hasAlpha then
    184         local a = string.format("%02X", pixel.a)
    185         return r .. g .. b .. a
    186     else
    187         return r .. g .. b
    188     end
    189 end
    190 
    191 -- Get pixel as RGBA table
    192 -- @param x X coordinate (0-based)
    193 -- @param y Y coordinate (0-based)
    194 -- @return Table with r, g, b, a fields (0-255)
    195 function PNGImage:getPixel(x, y)
    196     checkPermission()
    197 
    198     if not x or not y then
    199         error("getPixel requires x and y coordinates")
    200     end
    201 
    202     if x < 0 or x >= self.width or y < 0 or y >= self.height then
    203         error("Pixel coordinates out of bounds: (" .. x .. ", " .. y .. ")")
    204     end
    205 
    206     local index = y * self.width + x + 1
    207     local pixel = self.pixels[index]
    208 
    209     if not pixel then
    210         return { r = 0, g = 0, b = 0, a = 0 }
    211     end
    212 
    213     return {
    214         r = pixel.r,
    215         g = pixel.g,
    216         b = pixel.b,
    217         a = pixel.a
    218     }
    219 end
    220 
    221 -- Set pixel from RGBA table
    222 -- @param x X coordinate (0-based)
    223 -- @param y Y coordinate (0-based)
    224 -- @param rgba Table with r, g, b, a fields
    225 function PNGImage:setPixel(x, y, rgba)
    226     checkPermission()
    227 
    228     if not x or not y or not rgba then
    229         error("setPixel requires x, y, and rgba table")
    230     end
    231 
    232     if x < 0 or x >= self.width or y < 0 or y >= self.height then
    233         error("Pixel coordinates out of bounds: (" .. x .. ", " .. y .. ")")
    234     end
    235 
    236     local index = y * self.width + x + 1
    237 
    238     self.pixels[index] = {
    239         r = rgba.r or 0,
    240         g = rgba.g or 0,
    241         b = rgba.b or 0,
    242         a = rgba.a or 255
    243     }
    244 
    245     if self.imageBuffer and ImageSetPixel then
    246         ImageSetPixel(self.imageBuffer, x, y, rgba.r or 0, rgba.g or 0, rgba.b or 0, rgba.a or 255)
    247     end
    248 end
    249 
    250 -- Fill entire image with a color
    251 -- @param color Color in hex format "RRGGBB" or "RRGGBBAA"
    252 function PNGImage:fill(color)
    253     checkPermission()
    254 
    255     for y = 0, self.height - 1 do
    256         for x = 0, self.width - 1 do
    257             self:writePixel(x, y, color)
    258         end
    259     end
    260 end
    261 
    262 -- Clear image to transparent (or opaque black if no alpha)
    263 function PNGImage:clear()
    264     checkPermission()
    265 
    266     local clearColor = self.hasAlpha and "00000000" or "000000FF"
    267     self:fill(clearColor)
    268 end
    269 
    270 -- Get image dimensions
    271 -- @return width, height
    272 function PNGImage:getSize()
    273     return self.width, self.height
    274 end
    275 
    276 -- Get image info
    277 -- @return Table with width, height, hasAlpha, bpp
    278 function PNGImage:getInfo()
    279     return {
    280         width = self.width,
    281         height = self.height,
    282         hasAlpha = self.hasAlpha,
    283         bpp = self.bpp
    284     }
    285 end
    286 
    287 -- Draw a rectangle
    288 -- @param x X coordinate of top-left corner
    289 -- @param y Y coordinate of top-left corner
    290 -- @param width Rectangle width
    291 -- @param height Rectangle height
    292 -- @param color Fill color
    293 function PNGImage:fillRect(x, y, width, height, color)
    294     checkPermission()
    295 
    296     for dy = 0, height - 1 do
    297         for dx = 0, width - 1 do
    298             local px = x + dx
    299             local py = y + dy
    300             if px >= 0 and px < self.width and py >= 0 and py < self.height then
    301                 self:writePixel(px, py, color)
    302             end
    303         end
    304     end
    305 end
    306 
    307 -- Draw a line (Bresenham's algorithm)
    308 -- @param x1 Start X
    309 -- @param y1 Start Y
    310 -- @param x2 End X
    311 -- @param y2 End Y
    312 -- @param color Line color
    313 function PNGImage:drawLine(x1, y1, x2, y2, color)
    314     checkPermission()
    315 
    316     local dx = math.abs(x2 - x1)
    317     local dy = math.abs(y2 - y1)
    318     local sx = x1 < x2 and 1 or -1
    319     local sy = y1 < y2 and 1 or -1
    320     local err = dx - dy
    321 
    322     while true do
    323         if x1 >= 0 and x1 < self.width and y1 >= 0 and y1 < self.height then
    324             self:writePixel(x1, y1, color)
    325         end
    326 
    327         if x1 == x2 and y1 == y2 then
    328             break
    329         end
    330 
    331         local e2 = 2 * err
    332         if e2 > -dy then
    333             err = err - dy
    334             x1 = x1 + sx
    335         end
    336         if e2 < dx then
    337             err = err + dx
    338             y1 = y1 + sy
    339         end
    340     end
    341 end
    342 
    343 -- Convert to C image buffer (if not already)
    344 -- @return C image_t userdata or nil
    345 function PNGImage:toImageBuffer()
    346     checkPermission()
    347 
    348     if self.imageBuffer then
    349         return self.imageBuffer
    350     end
    351 
    352     if not ImageCreate then
    353         return nil
    354     end
    355 
    356     -- Create C image buffer
    357     local img = ImageCreate(self.width, self.height, self.hasAlpha)
    358 
    359     if not img then
    360         return nil
    361     end
    362 
    363     -- Copy all pixels
    364     if ImageSetPixel then
    365         for y = 0, self.height - 1 do
    366             for x = 0, self.width - 1 do
    367                 local index = y * self.width + x + 1
    368                 local p = self.pixels[index]
    369                 ImageSetPixel(img, x, y, p.r, p.g, p.b, p.a)
    370             end
    371         end
    372     end
    373 
    374     self.imageBuffer = img
    375     return img
    376 end
    377 
    378 -- Load PNG from file
    379 -- @param path Path to PNG file
    380 -- @return PNG image object or nil on error
    381 function PNG.load(path)
    382     checkPermission()
    383 
    384     if not CRamdiskExists or not CRamdiskOpen or not CRamdiskRead or not CRamdiskClose then
    385         error("PNG.load requires ramdisk functions")
    386     end
    387 
    388     if not PNGLoad or not ImageGetWidth or not ImageGetHeight or not ImageGetPixel then
    389         error("PNG.load requires PNG decoder functions")
    390     end
    391 
    392     if not CRamdiskExists(path) then
    393         return nil, "File not found: " .. path
    394     end
    395 
    396     local handle = CRamdiskOpen(path, "r")
    397     if not handle then
    398         return nil, "Failed to open file: " .. path
    399     end
    400 
    401     local pngData = CRamdiskRead(handle)
    402     CRamdiskClose(handle)
    403 
    404     if not pngData then
    405         return nil, "Failed to read file: " .. path
    406     end
    407 
    408     -- Decode PNG
    409     local imgBuffer = PNGLoad(pngData)
    410     if not imgBuffer then
    411         return nil, "Failed to decode PNG: " .. path
    412     end
    413 
    414     -- Get dimensions
    415     local width = ImageGetWidth(imgBuffer)
    416     local height = ImageGetHeight(imgBuffer)
    417 
    418     -- Create PNG object
    419     local self = setmetatable({}, PNGImage)
    420     self.width = width
    421     self.height = height
    422     self.hasAlpha = true
    423     self.bpp = 32
    424     self.pixels = {}
    425     self.imageBuffer = imgBuffer
    426 
    427     -- Read all pixels from C buffer
    428     for y = 0, height - 1 do
    429         for x = 0, width - 1 do
    430             local r, g, b, a = ImageGetPixel(imgBuffer, x, y)
    431             local index = y * width + x + 1
    432             self.pixels[index] = {
    433                 r = r or 0,
    434                 g = g or 0,
    435                 b = b or 0,
    436                 a = a or 255
    437             }
    438         end
    439     end
    440 
    441     return self
    442 end
    443 
    444 -- Clone the image
    445 -- @return New PNG image object with copied pixels
    446 function PNGImage:clone()
    447     checkPermission()
    448 
    449     local newImg = PNG.new(self.width, self.height, self.hasAlpha)
    450 
    451     -- Copy all pixels
    452     for i = 1, #self.pixels do
    453         local p = self.pixels[i]
    454         newImg.pixels[i] = {
    455             r = p.r,
    456             g = p.g,
    457             b = p.b,
    458             a = p.a
    459         }
    460     end
    461 
    462     return newImg
    463 end
    464 
    465 return PNG