- π Native codegen β uses
--!nativeand--!optimize 2for 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
--!stricttype annotations
Copy PNG.luau into your Roblox project (e.g. ReplicatedStorage).
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 pixelslocal 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-- Pass true as second argument to skip CRC checks (faster)
local image = PNG.decode(imageData, true)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
Creates a new decoder instance.
Returns: PNGDecoderInstance
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 = 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.
| 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 |
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
)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
endlocal 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)Benchmarked on Roblox Studio with --!native enabled:
| Image Size | Color Type | Time |
|---|---|---|
| 1024Γ576 | RGBA 8-bit | ~0.15s |
Tip: Pass
SkipCRC = truefor faster decoding when data integrity is guaranteed.
MIT License. See LICENSE for details.
Made by @MrRoblick