Skip to content

Latest commit

Β 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

PNG Decoder for Luau

Luau Roblox License

High-performance PNG image decoder written in pure Luau with native codegen support.


✨ Features

  • πŸš€ Native codegen β€” uses --!native and --!optimize 2 for maximum performance
  • 🎨 All color types β€” Grayscale, RGB, Indexed, Grayscale+Alpha, RGBA
  • πŸ“ All bit depths β€” 1, 2, 4, 8, 16 bit per channel
  • πŸ”€ Interlacing β€” Adam7 interlaced images supported
  • πŸ” Transparency β€” tRNS chunk support for all applicable color types
  • βœ… CRC validation β€” optional integrity checking
  • πŸ“¦ Zero dependencies β€” pure Luau, no external modules
  • πŸ›‘οΈ Strict typing β€” full --!strict type annotations

πŸ“¦ Installation

Copy PNG.luau into your Roblox project (e.g. ReplicatedStorage).


πŸš€ Quick Start

Basic Usage

local PNG = require(path.to.PNG)

-- Decode from buffer
local imageData: buffer = -- your PNG file as buffer
local image = PNG.decode(imageData)

print(image.Width)
print(image.Height)
print(image.Pixels)  -- buffer of RGBA pixels

Reading Pixels

local PNG = require(path.to.PNG)

local image = PNG.decode(imageData)

-- Get pixel at (x, y) β€” returns R, G, B, A (0-255)
local decoder = PNG.new()
local r, g, b, a = decoder:GetPixel(image, 10, 20)

print(r, g, b, a)  -- 255, 128, 0, 255

Skip CRC Validation

-- Pass true as second argument to skip CRC checks (faster)
local image = PNG.decode(imageData, true)

πŸ“– API Reference

PNG.decode(Data: buffer, SkipCRC: boolean?): PNGImage

Decodes a PNG image from a buffer.

Parameter Type Description
Data buffer Raw PNG file data
SkipCRC boolean? Skip CRC validation for faster decoding. Default: false

Returns: PNGImage


PNG.new(): PNGDecoderInstance

Creates a new decoder instance.

Returns: PNGDecoderInstance


PNGDecoderInstance:Decode(Data: buffer, SkipCRC: boolean?): PNGImage

Decodes a PNG image using the decoder instance.

Parameter Type Description
Data buffer Raw PNG file data
SkipCRC boolean? Skip CRC validation. Default: false

Returns: PNGImage


PNGDecoderInstance:GetPixel(Image: PNGImage, X: number, Y: number): (number, number, number, number)

Returns the RGBA values of a pixel at the given coordinates.

Parameter Type Description
Image PNGImage Decoded image
X number X coordinate (0-indexed)
Y number Y coordinate (0-indexed)

Returns: r, g, b, a β€” four numbers in range 0-255


Type: PNGImage

type PNGImage = typeof(table.freeze({} :: {
    Width: number,    -- Image width in pixels
    Height: number,   -- Image height in pixels
    Pixels: buffer,   -- Raw RGBA pixel data (4 bytes per pixel)
}))

Pixel buffer layout:

Offset: [0]  [1]  [2]  [3]  [4]  [5]  [6]  [7]  ...
Data:    R    G    B    A    R    G    B    A   ...
         ╰─ Pixel 0 ─╯    ╰─ Pixel 1 ─╯

Total buffer size: Width Γ— Height Γ— 4 bytes.


πŸ“Š Supported PNG Formats

Color Type Bit Depths Description
0 1, 2, 4, 8, 16 Grayscale
2 8, 16 RGB
3 1, 2, 4, 8 Indexed (palette)
4 8, 16 Grayscale + Alpha
6 8, 16 RGBA

πŸ’‘ Examples

Draw to EditableImage

local PNG = require(path.to.PNG)

local image = PNG.decode(pngBuffer, true)

local editableImage = AssetService:CreateEditableImage({
    Size = Vector2.new(image.Width, image.Height)
})

editableImage:WritePixelsBuffer(
    Vector2.zero,
    Vector2.new(image.Width, image.Height),
    image.Pixels
)

Iterate All Pixels

local PNG = require(path.to.PNG)

local image = PNG.decode(pngBuffer)
local pixels = image.Pixels

for y = 0, image.Height - 1 do
    for x = 0, image.Width - 1 do
        local offset = (y * image.Width + x) * 4
        local r = buffer.readu8(pixels, offset)
        local g = buffer.readu8(pixels, offset + 1)
        local b = buffer.readu8(pixels, offset + 2)
        local a = buffer.readu8(pixels, offset + 3)
        -- do something with r, g, b, a
    end
end

Read Pixel as u32

local image = PNG.decode(pngBuffer)

-- Read packed RGBA as single u32 (0xAABBGGRR in little-endian)
local offset = (y * image.Width + x) * 4
local rgba = buffer.readu32(image.Pixels, offset)

local r = bit32.band(rgba, 0xFF)
local g = bit32.band(bit32.rshift(rgba, 8), 0xFF)
local b = bit32.band(bit32.rshift(rgba, 16), 0xFF)
local a = bit32.band(bit32.rshift(rgba, 24), 0xFF)

⚑ Performance

Benchmarked on Roblox Studio with --!native enabled:

Image Size Color Type Time
1024Γ—576 RGBA 8-bit ~0.15s

Tip: Pass SkipCRC = true for faster decoding when data integrity is guaranteed.


πŸ“„ License

MIT License. See LICENSE for details.


Made by @MrRoblick

About

Modern png implementation in luau

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages