package core:image/png
⌘K
Ctrl+K
or
/
Overview
package png implements a PNG image reader
The PNG specification is at https://www.w3.org/TR/PNG/.
Example:
package main
import "core:image"
// import "core:image/png"
import "core:bytes"
import "core:fmt"
// For PPM writer
import "core:mem"
import "core:os"
main :: proc() {
track := mem.Tracking_Allocator{}
mem.tracking_allocator_init(&track, context.allocator)
context.allocator = mem.tracking_allocator(&track)
demo()
if len(track.allocation_map) > 0 {
fmt.println("Leaks:")
for _, v in track.allocation_map {
fmt.printf("\t%v\n\n", v)
}
}
}
demo :: proc() {
file: string
options := image.Options{.return_metadata}
err: image.Error
img: ^image.Image
file = "../../../misc/logo-slim.png"
img, err = load(file, options)
defer destroy(img)
if err != nil {
fmt.printf("Trying to read PNG file %v returned %v\n", file, err)
} else {
fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth)
if v, ok := img.metadata.(^image.PNG_Info); ok {
// Handle ancillary chunks as you wish.
// We provide helper functions for a few types.
for c in v.chunks {
#partial switch c.header.type {
case .tIME:
if t, t_ok := core_time(c); t_ok {
fmt.printf("[tIME]: %v\n", t)
}
case .gAMA:
if gama, gama_ok := gamma(c); gama_ok {
fmt.printf("[gAMA]: %v\n", gama)
}
case .pHYs:
if phys, phys_ok := phys(c); phys_ok {
if phys.unit == .Meter {
xm := f32(img.width) / f32(phys.ppu_x)
ym := f32(img.height) / f32(phys.ppu_y)
dpi_x, dpi_y := phys_to_dpi(phys)
fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y)
fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y)
fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym)
} else {
fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y)
}
}
case .iTXt, .zTXt, .tEXt:
res, ok_text := text(c)
if ok_text {
if c.header.type == .iTXt {
fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text)
} else {
fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text)
}
}
defer text_destroy(res)
case .bKGD:
fmt.printf("[bKGD] %v\n", img.background)
case .eXIf:
if res, ok_exif := exif(c); ok_exif {
/*
Other than checking the signature and byte order, we don't handle Exif data.
If you wish to interpret it, pass it to an Exif parser.
*/
fmt.printf("[eXIf] %v\n", res)
}
case .PLTE:
if plte, plte_ok := plte(c); plte_ok {
fmt.printf("[PLTE] %v\n", plte)
} else {
fmt.printf("[PLTE] Error\n")
}
case .hIST:
if res, ok_hist := hist(c); ok_hist {
fmt.printf("[hIST] %v\n", res)
}
case .cHRM:
if res, ok_chrm := chrm(c); ok_chrm {
fmt.printf("[cHRM] %v\n", res)
}
case .sPLT:
res, ok_splt := splt(c)
if ok_splt {
fmt.printf("[sPLT] %v\n", res)
}
splt_destroy(res)
case .sBIT:
if res, ok_sbit := sbit(c); ok_sbit {
fmt.printf("[sBIT] %v\n", res)
}
case .iCCP:
res, ok_iccp := iccp(c)
if ok_iccp {
fmt.printf("[iCCP] %v\n", res)
}
iccp_destroy(res)
case .sRGB:
if res, ok_srgb := srgb(c); ok_srgb {
fmt.printf("[sRGB] Rendering intent: %v\n", res)
}
case:
type := c.header.type
name := chunk_type_to_name(&type)
fmt.printf("[%v]: %v\n", name, c.data)
}
}
}
}
fmt.printf("Done parsing metadata.\n")
if err == nil && .do_not_decompress_image not_in options && .info not_in options {
if ok := write_image_as_ppm("out.ppm", img); ok {
fmt.println("Saved decoded image.")
} else {
fmt.println("Error saving out.ppm.")
fmt.println(img)
}
}
}
// Crappy PPM writer used during testing. Don't use in production.
write_image_as_ppm :: proc(filename: string, image: ^image.Image) -> (success: bool) {
_bg :: proc(bg: Maybe([3]u16), x, y: int, high := true) -> (res: [3]u16) {
if v, ok := bg.?; ok {
res = v
} else {
if high {
l := u16(30 * 256 + 30)
if (x & 4 == 0) ~ (y & 4 == 0) {
res = [3]u16{l, 0, l}
} else {
res = [3]u16{l >> 1, 0, l >> 1}
}
} else {
if (x & 4 == 0) ~ (y & 4 == 0) {
res = [3]u16{30, 30, 30}
} else {
res = [3]u16{15, 15, 15}
}
}
}
return
}
// profiler.timed_proc();
using image
using os
flags: int = O_WRONLY|O_CREATE|O_TRUNC
img := image
// PBM 16-bit images are big endian
when ODIN_ENDIAN == .Little {
if img.depth == 16 {
// The pixel components are in Big Endian. Let's byteswap back.
input := mem.slice_data_cast([]u16, img.pixels.buf[:])
output := mem.slice_data_cast([]u16be, img.pixels.buf[:])
#no_bounds_check for v, i in input {
output[i] = u16be(v)
}
}
}
pix := bytes.buffer_to_bytes(&img.pixels)
if len(pix) == 0 || len(pix) < image.width * image.height * int(image.channels) {
return false
}
mode: int = 0
when ODIN_OS == .Linux || ODIN_OS == .Darwin {
// NOTE(justasd): 644 (owner read, write; group read; others read)
mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
}
fd, err := open(filename, flags, mode)
if err != nil {
return false
}
defer close(fd)
write_string(fd,
fmt.tprintf("P6\n%v %v\n%v\n", width, height, uint(1 << uint(depth) - 1)),
)
if channels == 3 {
// We don't handle transparency here...
write_ptr(fd, raw_data(pix), len(pix))
} else {
bpp := depth == 16 ? 2 : 1
bytes_needed := width * height * 3 * bpp
op := bytes.Buffer{}
bytes.buffer_init_allocator(&op, bytes_needed, bytes_needed)
defer bytes.buffer_destroy(&op)
if channels == 1 {
if depth == 16 {
assert(len(pix) == width * height * 2)
p16 := mem.slice_data_cast([]u16, pix)
o16 := mem.slice_data_cast([]u16, op.buf[:])
#no_bounds_check for len(p16) != 0 {
r := u16(p16[0])
o16[0] = r
o16[1] = r
o16[2] = r
p16 = p16[1:]
o16 = o16[3:]
}
} else {
o := 0
for i := 0; i < len(pix); i += 1 {
r := pix[i]
op.buf[o ] = r
op.buf[o+1] = r
op.buf[o+2] = r
o += 3
}
}
write_ptr(fd, raw_data(op.buf), len(op.buf))
} else if channels == 2 {
if depth == 16 {
p16 := mem.slice_data_cast([]u16, pix)
o16 := mem.slice_data_cast([]u16, op.buf[:])
bgcol := img.background
#no_bounds_check for len(p16) != 0 {
r := f64(u16(p16[0]))
bg: f64
if bgcol != nil {
v := bgcol.([3]u16)[0]
bg = f64(v)
}
a := f64(u16(p16[1])) / 65535.0
l := (a * r) + (1 - a) * bg
o16[0] = u16(l)
o16[1] = u16(l)
o16[2] = u16(l)
p16 = p16[2:]
o16 = o16[3:]
}
} else {
o := 0
for i := 0; i < len(pix); i += 2 {
r := pix[i]; a := pix[i+1]; a1 := f32(a) / 255.0
c := u8(f32(r) * a1)
op.buf[o ] = c
op.buf[o+1] = c
op.buf[o+2] = c
o += 3
}
}
write_ptr(fd, raw_data(op.buf), len(op.buf))
} else if channels == 4 {
if depth == 16 {
p16 := mem.slice_data_cast([]u16be, pix)
o16 := mem.slice_data_cast([]u16be, op.buf[:])
#no_bounds_check for len(p16) != 0 {
bg := _bg(img.background, 0, 0)
r := f32(p16[0])
g := f32(p16[1])
b := f32(p16[2])
a := f32(p16[3]) / 65535.0
lr := (a * r) + (1 - a) * f32(bg[0])
lg := (a * g) + (1 - a) * f32(bg[1])
lb := (a * b) + (1 - a) * f32(bg[2])
o16[0] = u16be(lr)
o16[1] = u16be(lg)
o16[2] = u16be(lb)
p16 = p16[4:]
o16 = o16[3:]
}
} else {
o := 0
for i := 0; i < len(pix); i += 4 {
x := (i / 4) % width
y := i / width / 4
_b := _bg(img.background, x, y, false)
bgcol := [3]u8{u8(_b[0]), u8(_b[1]), u8(_b[2])}
r := f32(pix[i])
g := f32(pix[i+1])
b := f32(pix[i+2])
a := f32(pix[i+3]) / 255.0
lr := u8(f32(r) * a + (1 - a) * f32(bgcol[0]))
lg := u8(f32(g) * a + (1 - a) * f32(bgcol[1]))
lb := u8(f32(b) * a + (1 - a) * f32(bgcol[2]))
op.buf[o ] = lr
op.buf[o+1] = lg
op.buf[o+2] = lb
o += 3
}
}
write_ptr(fd, raw_data(op.buf), len(op.buf))
} else {
return false
}
}
return true
}
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-2 license.
List of contributors:
Jeroen van Rijn: Initial implementation.
Ginger Bill: Cosmetic changes.
These are a few useful utility functions to work with PNG images.
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
Made available under Odin's BSD-3 license.
List of contributors:
Jeroen van Rijn: Initial implementation.
Ginger Bill: Cosmetic changes.
Index
Constants (4)
Variables (4)
Procedures (31)
- append_chunk
- chrm
- chunk_type_to_name
- copy_chunk
- core_time
- defilter
- defilter_16
- defilter_8
- defilter_less_than_8
- destroy
- exif
- filter_paeth
- gamma
- hist
- iccp
- iccp_destroy
- load_from_bytes
- load_from_context
- load_from_file
- phys
- phys_to_dpi
- plte
- read_chunk
- read_header
- sbit
- splt
- splt_destroy
- srgb
- text
- text_destroy
- time
Procedure Groups (1)
Types
Exif ¶
Related Procedures With Returns
Filter_Params ¶
Filter_Params :: struct #packed { src: []u8, dest: []u8, width: int, height: int, depth: int, channels: int, rescale: bool, }
Related Procedures With Parameters
PLTE ¶
Related Procedures With Returns
PLTE_Entry ¶
PLTE_Entry :: [3]u8
Row_Filter ¶
Row_Filter :: enum u8 { None = 0, Sub = 1, Up = 2, Average = 3, Paeth = 4, }
Signature ¶
Signature :: enum u64be { // 0x89504e470d0a1a0a PNG = 9894494448401390090, }
cHRM ¶
Related Procedures With Returns
cHRM_Raw ¶
cHRM_Raw :: struct #packed { w: CIE_1931_Raw, r: CIE_1931_Raw, g: CIE_1931_Raw, b: CIE_1931_Raw, }
hIST ¶
Related Procedures With Returns
pHYs_Unit ¶
pHYs_Unit :: enum u8 { Unknown = 0, Meter = 1, }
sPLT ¶
sPLT :: struct #packed { name: string, depth: u8, entries: union { [][4]u8, [][4]u16, }, used: u16, }
Related Procedures With Parameters
Related Procedures With Returns
sRGB_Rendering_Intent ¶
sRGB_Rendering_Intent :: enum u8 { Perceptual = 0, Relative_colorimetric = 1, Saturation = 2, Absolute_colorimetric = 3, }
Constants
INCHES_PER_METER ¶
INCHES_PER_METER :: 1000.0 / 25.4
MAX_CHUNK_SIZE ¶
MAX_CHUNK_SIZE :: min(#config(PNG_MAX_CHUNK_SIZE, 16_777_216), 268_435_456)
For chunks other than IDAT with a variable size like zTXT
and eXIf
,
limit their size to 16 MiB each by default. Max of 256 MiB each.
MAX_IDAT_SIZE ¶
MAX_IDAT_SIZE :: min(#config(PNG_MAX_IDAT_SIZE, _MAX_IDAT_DEFAULT), _MAX_IDAT)
depth_scale_table ¶
depth_scale_table: []u8 : []u8{0, 0xff, 0x55, 0, 0x11, 0, 0, 0, 0x01}
Variables
ADAM7_X_ORIG ¶
ADAM7_X_ORIG: []int = …
ADAM7_X_SPACING ¶
ADAM7_X_SPACING: []int = …
ADAM7_Y_ORIG ¶
ADAM7_Y_ORIG: []int = …
ADAM7_Y_SPACING ¶
ADAM7_Y_SPACING: []int = …
Procedures
append_chunk ¶
append_chunk :: proc(list: ^[dynamic]image.PNG_Chunk, src: image.PNG_Chunk, allocator := context.allocator) -> (err: image.Error) {…}
chunk_type_to_name ¶
chunk_type_to_name :: proc(type: ^image.PNG_Chunk_Type) -> string {…}
copy_chunk ¶
copy_chunk :: proc(src: image.PNG_Chunk, allocator := context.allocator) -> (dest: image.PNG_Chunk, err: image.Error) {…}
defilter_16 ¶
defilter_16 :: proc(params: ^Filter_Params) -> bool {…}
defilter_8 ¶
defilter_8 :: proc(params: ^Filter_Params) -> (ok: bool) {…}
defilter_less_than_8 ¶
defilter_less_than_8 :: proc(params: ^Filter_Params) -> bool {…}
iccp_destroy ¶
iccp_destroy :: proc(i: iCCP) {…}
load_from_bytes ¶
load_from_bytes :: proc(data: []u8, options: image.Options = Options{}, allocator := context.allocator) -> (img: ^image.Image, err: image.Error) {…}
load_from_context ¶
load_from_context :: proc(ctx: ^$T, options: image.Options = Options{}, allocator := context.allocator) -> (img: ^image.Image, err: image.Error) {…}
load_from_file ¶
load_from_file :: proc(filename: string, options: image.Options = Options{}, allocator := context.allocator) -> (img: ^image.Image, err: image.Error) {…}
splt_destroy ¶
splt_destroy :: proc(s: sPLT) {…}
text_destroy ¶
text_destroy :: proc(text: Text) {…}
Procedure Groups
load ¶
load :: proc{ load_from_file, load_from_bytes, load_from_context, }
Source Files
Generation Information
Generated with odin version dev-2024-12 (vendor "odin") Windows_amd64 @ 2024-12-06 21:12:12.454150800 +0000 UTC