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.
    

    Types

    CIE_1931 ¶

    CIE_1931 :: struct #packed {
    	x: f32,
    	y: f32,
    }

    CIE_1931_Raw ¶

    CIE_1931_Raw :: struct #packed {
    	x: u32be,
    	y: u32be,
    }

    Error ¶

    Error :: image.Error

    Exif ¶

    Exif :: struct {
    	byte_order: enum int {
    		little_endian, 
    		big_endian, 
    	},
    	data:       []u8,
    }
    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

    Image ¶

    Image :: image.Image

    Options ¶

    Options :: image.Options

    PLTE ¶

    PLTE :: struct #packed {
    	entries: [256][3]u8,
    	used:    u16,
    }
    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, 
    }

    Text ¶

    Text :: struct {
    	keyword:           string,
    	keyword_localized: string,
    	language:          string,
    	text:              string,
    }
    Related Procedures With Parameters
    Related Procedures With Returns

    cHRM ¶

    cHRM :: struct #packed {
    	w: CIE_1931,
    	r: CIE_1931,
    	g: CIE_1931,
    	b: CIE_1931,
    }
    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,
    }

    gAMA ¶

    gAMA :: struct {
    	gamma_100k: u32be,
    }

    hIST ¶

    hIST :: struct #packed {
    	entries: [256]u16,
    	used:    u16,
    }
    Related Procedures With Returns

    iCCP ¶

    iCCP :: struct {
    	name:    string,
    	profile: []u8,
    }
    Related Procedures With Parameters
    Related Procedures With Returns

    pHYs ¶

    pHYs :: struct #packed {
    	ppu_x: u32be,
    	ppu_y: u32be,
    	unit:  pHYs_Unit,
    }
    Related Procedures With Parameters
    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 ¶

    sRGB :: struct #packed {
    	intent: sRGB_Rendering_Intent,
    }
    Related Procedures With Returns

    sRGB_Rendering_Intent ¶

    sRGB_Rendering_Intent :: enum u8 {
    	Perceptual            = 0, 
    	Relative_colorimetric = 1, 
    	Saturation            = 2, 
    	Absolute_colorimetric = 3, 
    }

    tIME ¶

    tIME :: struct #packed {
    	year:   u16be,
    	month:  u8,
    	day:    u8,
    	hour:   u8,
    	minute: u8,
    	second: u8,
    }
     

    Other chunks

    Related Procedures With Returns

    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) {…}

    chrm ¶

    chrm :: proc(c: image.PNG_Chunk) -> (res: cHRM, ok: bool) {…}

    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) {…}

    core_time ¶

    core_time :: proc(c: image.PNG_Chunk) -> (t: time.Time, ok: bool) {…}

    defilter ¶

    defilter :: proc(img: ^image.Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IHDR, options: image.Options) -> (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 {…}

    destroy ¶

    destroy :: proc(img: ^image.Image) {…}

    exif ¶

    exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) {…}

    filter_paeth ¶

    filter_paeth :: proc(left, up, up_left: u8) -> u8 {…}

    gamma ¶

    gamma :: proc(c: image.PNG_Chunk) -> (res: f32, ok: bool) {…}

    hist ¶

    hist :: proc(c: image.PNG_Chunk) -> (res: hIST, ok: bool) {…}

    iccp ¶

    iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: 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) {…}

    phys ¶

    phys :: proc(c: image.PNG_Chunk) -> (res: pHYs, ok: bool) {…}

    phys_to_dpi ¶

    phys_to_dpi :: proc(p: pHYs) -> (x_dpi, y_dpi: f32) {…}

    plte ¶

    plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, ok: bool) {…}

    read_chunk ¶

    read_chunk :: proc(ctx: ^$T) -> (chunk: image.PNG_Chunk, err: image.Error) {…}

    read_header ¶

    read_header :: proc(ctx: ^$T) -> (image.PNG_IHDR, image.Error) {…}

    sbit ¶

    sbit :: proc(c: image.PNG_Chunk) -> (res: [4]u8, ok: bool) {…}

    splt ¶

    splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {…}

    splt_destroy ¶

    splt_destroy :: proc(s: sPLT) {…}

    srgb ¶

    srgb :: proc(c: image.PNG_Chunk) -> (res: sRGB, ok: bool) {…}

    text ¶

    text :: proc(c: image.PNG_Chunk) -> (res: Text, ok: bool) {…}

    text_destroy ¶

    text_destroy :: proc(text: Text) {…}

    time ¶

    time :: proc(c: image.PNG_Chunk) -> (res: tIME, ok: bool) {…}

    Procedure Groups

    Source Files

    Generation Information

    Generated with odin version dev-2024-12 (vendor "odin") Windows_amd64 @ 2024-12-06 21:12:12.454150800 +0000 UTC