//+ignore package png /* Copyright 2021 Jeroen van Rijn . Made available under Odin's BSD-2 license. List of contributors: Jeroen van Rijn: Initial implementation. Ginger Bill: Cosmetic changes. An example of how to use `load`. */ import "core:compress" 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: compress.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 { v: ^Info; fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth); if img.metadata_ptr != nil && img.metadata_type == Info { v = (^Info)(img.metadata_ptr); // 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: t, _ := core_time(c); fmt.printf("[tIME]: %v\n", t); case .gAMA: fmt.printf("[gAMA]: %v\n", gamma(c)); case .pHYs: phys := phys(c); 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: res, ok_exif := exif(c); if 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: plte, plte_ok := plte(c); if plte_ok { fmt.printf("[PLTE] %v\n", plte); } else { fmt.printf("[PLTE] Error\n"); } case .hIST: res, ok_hist := hist(c); if ok_hist { fmt.printf("[hIST] %v\n", res); } case .cHRM: res, ok_chrm := chrm(c); if 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); } } } } 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 != 0 { return false; } defer close(fd); write_string(fd, fmt.tprintf("P6\n%v %v\n%v\n", width, height, (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; }