Skip to content
MrRoblickPublic

About

Modern png implementation in luau

Resources

Stars

4 stars

Watchers

0 watching

Forks

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