diff --git a/core/compress/common.odin b/core/compress/common.odin index 683679566..41f292b6f 100644 --- a/core/compress/common.odin +++ b/core/compress/common.odin @@ -1,5 +1,3 @@ -package compress - /* Copyright 2021 Jeroen van Rijn . Made available under Odin's BSD-3 license. @@ -7,10 +5,11 @@ package compress List of contributors: Jeroen van Rijn: Initial implementation, optimization. */ +package compress import "core:io" -import "core:image" import "core:bytes" +import "core:runtime" /* These settings bound how much compression algorithms will allocate for their output buffer. @@ -51,11 +50,8 @@ Error :: union { ZLIB_Error, GZIP_Error, ZIP_Error, - /* - This is here because png.load will return a this type of error union, - as it may involve an I/O error, a Deflate error, etc. - */ - image.Error, + + runtime.Allocator_Error, } General_Error :: enum { @@ -69,7 +65,6 @@ General_Error :: enum { Incompatible_Options, Unimplemented, - /* Memory errors */ diff --git a/core/image/common.odin b/core/image/common.odin index f30febc26..3ec8e15be 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -1,16 +1,17 @@ -package image - /* Copyright 2021 Jeroen van Rijn . - Made available under Odin's BSD-2 license. + Made available under Odin's BSD-3 license. List of contributors: Jeroen van Rijn: Initial implementation, optimization. Ginger Bill: Cosmetic changes. */ +package image import "core:bytes" import "core:mem" +import "core:compress" +import "core:runtime" Image :: struct { width: int, @@ -25,8 +26,11 @@ Image :: struct { */ background: Maybe([3]u16), - metadata_ptr: rawptr, - metadata_type: typeid, + metadata: Image_Metadata, +} + +Image_Metadata :: union { + ^PNG_Info, } /* @@ -112,31 +116,140 @@ Option :: enum { } Options :: distinct bit_set[Option] -Error :: enum { +Error :: union { + General_Image_Error, + PNG_Error, + + compress.Error, + compress.General_Error, + compress.Deflate_Error, + compress.ZLIB_Error, + runtime.Allocator_Error, +} + +General_Image_Error :: enum { + None = 0, + Invalid_Image_Dimensions, + Image_Dimensions_Too_Large, + Image_Does_Not_Adhere_to_Spec, +} + +PNG_Error :: enum { Invalid_PNG_Signature, IHDR_Not_First_Chunk, IHDR_Corrupt, IDAT_Missing, IDAT_Must_Be_Contiguous, IDAT_Corrupt, - PNG_Does_Not_Adhere_to_Spec, + IDAT_Size_Too_Large, PLTE_Encountered_Unexpectedly, PLTE_Invalid_Length, TRNS_Encountered_Unexpectedly, BKGD_Invalid_Length, - Invalid_Image_Dimensions, Unknown_Color_Type, Invalid_Color_Bit_Depth_Combo, Unknown_Filter_Method, Unknown_Interlace_Method, Requested_Channel_Not_Present, Post_Processing_Error, + Invalid_Chunk_Length, +} + +/* + PNG-specific structs +*/ +PNG_Info :: struct { + header: PNG_IHDR, + chunks: [dynamic]PNG_Chunk, +} + +PNG_Chunk_Header :: struct #packed { + length: u32be, + type: PNG_Chunk_Type, +} + +PNG_Chunk :: struct #packed { + header: PNG_Chunk_Header, + data: []byte, + crc: u32be, +} + +PNG_Chunk_Type :: enum u32be { + // IHDR must come first in a file + IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R', + // PLTE must precede the first IDAT chunk + PLTE = 'P' << 24 | 'L' << 16 | 'T' << 8 | 'E', + bKGD = 'b' << 24 | 'K' << 16 | 'G' << 8 | 'D', + tRNS = 't' << 24 | 'R' << 16 | 'N' << 8 | 'S', + IDAT = 'I' << 24 | 'D' << 16 | 'A' << 8 | 'T', + + iTXt = 'i' << 24 | 'T' << 16 | 'X' << 8 | 't', + tEXt = 't' << 24 | 'E' << 16 | 'X' << 8 | 't', + zTXt = 'z' << 24 | 'T' << 16 | 'X' << 8 | 't', + + iCCP = 'i' << 24 | 'C' << 16 | 'C' << 8 | 'P', + pHYs = 'p' << 24 | 'H' << 16 | 'Y' << 8 | 's', + gAMA = 'g' << 24 | 'A' << 16 | 'M' << 8 | 'A', + tIME = 't' << 24 | 'I' << 16 | 'M' << 8 | 'E', + + sPLT = 's' << 24 | 'P' << 16 | 'L' << 8 | 'T', + sRGB = 's' << 24 | 'R' << 16 | 'G' << 8 | 'B', + hIST = 'h' << 24 | 'I' << 16 | 'S' << 8 | 'T', + cHRM = 'c' << 24 | 'H' << 16 | 'R' << 8 | 'M', + sBIT = 's' << 24 | 'B' << 16 | 'I' << 8 | 'T', + + /* + eXIf tags are not part of the core spec, but have been ratified + in v1.5.0 of the PNG Ext register. + + We will provide unprocessed chunks to the caller if `.return_metadata` is set. + Applications are free to implement an Exif decoder. + */ + eXIf = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f', + + // PNG files must end with IEND + IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D', + + /* + XCode sometimes produces "PNG" files that don't adhere to the PNG spec. + We recognize them only in order to avoid doing further work on them. + + Some tools like PNG Defry may be able to repair them, but we're not + going to reward Apple for producing proprietary broken files purporting + to be PNGs by supporting them. + + */ + iDOT = 'i' << 24 | 'D' << 16 | 'O' << 8 | 'T', + CbGI = 'C' << 24 | 'b' << 16 | 'H' << 8 | 'I', +} + +PNG_IHDR :: struct #packed { + width: u32be, + height: u32be, + bit_depth: u8, + color_type: PNG_Color_Type, + compression_method: u8, + filter_method: u8, + interlace_method: PNG_Interlace_Method, +} +PNG_IHDR_SIZE :: size_of(PNG_IHDR) +#assert (PNG_IHDR_SIZE == 13) + +PNG_Color_Value :: enum u8 { + Paletted = 0, // 1 << 0 = 1 + Color = 1, // 1 << 1 = 2 + Alpha = 2, // 1 << 2 = 4 +} +PNG_Color_Type :: distinct bit_set[PNG_Color_Value; u8] + +PNG_Interlace_Method :: enum u8 { + None = 0, + Adam7 = 1, } /* Functions to help with image buffer calculations */ - compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) { size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height return @@ -145,7 +258,6 @@ compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes /* For when you have an RGB(A) image, but want a particular channel. */ - Channel :: enum u8 { R = 1, G = 2, @@ -207,8 +319,7 @@ return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok res.depth = img.depth res.pixels = t res.background = img.background - res.metadata_ptr = img.metadata_ptr - res.metadata_type = img.metadata_type + res.metadata = img.metadata return res, true } diff --git a/core/image/png/example.odin b/core/image/png/example.odin index e27ea9671..5e7dca4c8 100644 --- a/core/image/png/example.odin +++ b/core/image/png/example.odin @@ -1,9 +1,6 @@ -//+ignore -package png - /* Copyright 2021 Jeroen van Rijn . - Made available under Odin's BSD-2 license. + Made available under Odin's BSD-3 license. List of contributors: Jeroen van Rijn: Initial implementation. @@ -11,8 +8,9 @@ package png An example of how to use `load`. */ +//+ignore +package png -import "core:compress" import "core:image" // import "core:image/png" import "core:bytes" @@ -41,8 +39,8 @@ main :: proc() { demo :: proc() { file: string - options := image.Options{} // {.return_metadata}; - err: compress.Error + options := image.Options{.return_metadata} + err: image.Error img: ^image.Image file = "../../../misc/logo-slim.png" @@ -53,32 +51,33 @@ demo :: proc() { 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) + 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: - t, _ := core_time(c) - fmt.printf("[tIME]: %v\n", t) + if t, t_ok := core_time(c); t_ok { + fmt.printf("[tIME]: %v\n", t) + } case .gAMA: - fmt.printf("[gAMA]: %v\n", gamma(c)) + if gama, gama_ok := gamma(c); gama_ok { + fmt.printf("[gAMA]: %v\n", gama) + } 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) + 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) @@ -93,8 +92,7 @@ demo :: proc() { case .bKGD: fmt.printf("[bKGD] %v\n", img.background) case .eXIf: - res, ok_exif := exif(c) - if ok_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. @@ -102,20 +100,17 @@ demo :: proc() { fmt.printf("[eXIf] %v\n", res) } case .PLTE: - plte, plte_ok := plte(c) - if plte_ok { + if plte, plte_ok := plte(c); plte_ok { fmt.printf("[PLTE] %v\n", plte) } else { fmt.printf("[PLTE] Error\n") } case .hIST: - res, ok_hist := hist(c) - if ok_hist { + if res, ok_hist := hist(c); ok_hist { fmt.printf("[hIST] %v\n", res) } case .cHRM: - res, ok_chrm := chrm(c) - if ok_chrm { + if res, ok_chrm := chrm(c); ok_chrm { fmt.printf("[cHRM] %v\n", res) } case .sPLT: @@ -147,6 +142,8 @@ demo :: proc() { } } + 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.") diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index 4d8cebedf..ecc0183bc 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -1,5 +1,3 @@ -package png - /* Copyright 2021 Jeroen van Rijn . Made available under Odin's BSD-2 license. @@ -10,6 +8,7 @@ package png These are a few useful utility functions to work with PNG images. */ +package png import "core:image" import "core:compress/zlib" @@ -34,15 +33,14 @@ destroy :: proc(img: ^Image) { } bytes.buffer_destroy(&img.pixels) - // Clean up Info. - free(img.metadata_ptr) - /* - We don't need to do anything for the individual chunks. - They're allocated on the temp allocator, as is info.chunks - - See read_chunk. - */ + if v, ok := img.metadata.(^image.PNG_Info); ok { + for chunk in &v.chunks { + delete(chunk.data) + } + delete(v.chunks) + free(v) + } free(img) } @@ -50,46 +48,50 @@ destroy :: proc(img: ^Image) { Chunk helpers */ -gamma :: proc(c: Chunk) -> f32 { - assert(c.header.type == .gAMA) - res := (^gAMA)(raw_data(c.data))^ - when true { - // Returns the wrong result on old backend - // Fixed for -llvm-api - return f32(res.gamma_100k) / 100_000.0 - } else { - return f32(u32(res.gamma_100k)) / 100_000.0 +gamma :: proc(c: image.PNG_Chunk) -> (res: f32, ok: bool) { + if c.header.type != .gAMA || len(c.data) != size_of(gAMA) { + return {}, false } + gama := (^gAMA)(raw_data(c.data))^ + return f32(gama.gamma_100k) / 100_000.0, true } INCHES_PER_METER :: 1000.0 / 25.4 -phys :: proc(c: Chunk) -> pHYs { - assert(c.header.type == .pHYs) - res := (^pHYs)(raw_data(c.data))^ - return res +phys :: proc(c: image.PNG_Chunk) -> (res: pHYs, ok: bool) { + if c.header.type != .pHYs || len(c.data) != size_of(pHYs) { + return {}, false + } + + return (^pHYs)(raw_data(c.data))^, true } phys_to_dpi :: proc(p: pHYs) -> (x_dpi, y_dpi: f32) { return f32(p.ppu_x) / INCHES_PER_METER, f32(p.ppu_y) / INCHES_PER_METER } -time :: proc(c: Chunk) -> tIME { - assert(c.header.type == .tIME) - res := (^tIME)(raw_data(c.data))^ - return res +time :: proc(c: image.PNG_Chunk) -> (res: tIME, ok: bool) { + if c.header.type != .tIME || len(c.data) != size_of(tIME) { + return {}, false + } + + return (^tIME)(raw_data(c.data))^, true } -core_time :: proc(c: Chunk) -> (t: coretime.Time, ok: bool) { - png_time := time(c) - using png_time - return coretime.datetime_to_time( - int(year), int(month), int(day), - int(hour), int(minute), int(second), - ) +core_time :: proc(c: image.PNG_Chunk) -> (t: coretime.Time, ok: bool) { + if png_time, png_ok := time(c); png_ok { + using png_time + return coretime.datetime_to_time( + int(year), int(month), int(day), + int(hour), int(minute), int(second), + ) + } else { + return {}, false + } } -text :: proc(c: Chunk) -> (res: Text, ok: bool) { +text :: proc(c: image.PNG_Chunk) -> (res: Text, ok: bool) { + assert(len(c.data) == int(c.header.length)) #partial switch c.header.type { case .tEXt: ok = true @@ -191,7 +193,7 @@ text_destroy :: proc(text: Text) { delete(text.text) } -iccp :: proc(c: Chunk) -> (res: iCCP, ok: bool) { +iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) { ok = true fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator) @@ -227,10 +229,8 @@ iccp_destroy :: proc(i: iCCP) { } -srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) { - ok = true - - if c.header.type != .sRGB || len(c.data) != 1 { +srgb :: proc(c: image.PNG_Chunk) -> (res: sRGB, ok: bool) { + if c.header.type != .sRGB || len(c.data) != size_of(sRGB_Rendering_Intent) { return {}, false } @@ -238,10 +238,10 @@ srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) { if res.intent > max(sRGB_Rendering_Intent) { ok = false; return } - return + return res, true } -plte :: proc(c: Chunk) -> (res: PLTE, ok: bool) { +plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, ok: bool) { if c.header.type != .PLTE { return {}, false } @@ -255,7 +255,7 @@ plte :: proc(c: Chunk) -> (res: PLTE, ok: bool) { return } -splt :: proc(c: Chunk) -> (res: sPLT, ok: bool) { +splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) { if c.header.type != .sPLT { return {}, false } @@ -306,7 +306,7 @@ splt_destroy :: proc(s: sPLT) { delete(s.name) } -sbit :: proc(c: Chunk) -> (res: [4]u8, ok: bool) { +sbit :: proc(c: image.PNG_Chunk) -> (res: [4]u8, ok: bool) { /* Returns [4]u8 with the significant bits in each channel. A channel will contain zero if not applicable to the PNG color type. @@ -324,7 +324,7 @@ sbit :: proc(c: Chunk) -> (res: [4]u8, ok: bool) { } -hist :: proc(c: Chunk) -> (res: hIST, ok: bool) { +hist :: proc(c: image.PNG_Chunk) -> (res: hIST, ok: bool) { if c.header.type != .hIST { return {}, false } @@ -346,7 +346,7 @@ hist :: proc(c: Chunk) -> (res: hIST, ok: bool) { return } -chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) { +chrm :: proc(c: image.PNG_Chunk) -> (res: cHRM, ok: bool) { ok = true if c.header.length != size_of(cHRM_Raw) { return {}, false @@ -364,7 +364,7 @@ chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) { return } -exif :: proc(c: Chunk) -> (res: Exif, ok: bool) { +exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) { ok = true diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 6e9102aaa..f77bf7519 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -1,13 +1,12 @@ -package png - /* Copyright 2021 Jeroen van Rijn . - Made available under Odin's BSD-2 license. + Made available under Odin's BSD-3 license. List of contributors: Jeroen van Rijn: Initial implementation. Ginger Bill: Cosmetic changes. */ +package png import "core:compress" import "core:compress/zlib" @@ -21,11 +20,29 @@ import "core:io" import "core:mem" import "core:intrinsics" -Error :: compress.Error -E_General :: compress.General_Error -E_PNG :: image.Error -E_Deflate :: compress.Deflate_Error +/* + 67_108_864 pixels max by default. + Maximum allowed dimensions are capped at 65535 * 65535. +*/ +MAX_DIMENSIONS :: min(#config(PNG_MAX_DIMENSIONS, 8192 * 8192), 65535 * 65535) +/* + Limit chunk sizes. + By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes. +*/ +_MAX_IDAT_DEFAULT :: ( 8192 /* Width */ * 8192 /* Height */ * 2 /* 16-bit */) + 8192 /* Filter bytes */ +_MAX_IDAT :: (65535 /* Width */ * 65535 /* Height */ * 2 /* 16-bit */) + 65535 /* Filter bytes */ + +MAX_IDAT_SIZE :: min(#config(PNG_MAX_IDAT_SIZE, _MAX_IDAT_DEFAULT), _MAX_IDAT) + +/* + 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_CHUNK_SIZE :: min(#config(PNG_MAX_CHUNK_SIZE, 16_777_216), 268_435_456) + + +Error :: image.Error Image :: image.Image Options :: image.Options @@ -34,95 +51,6 @@ Signature :: enum u64be { PNG = 0x89 << 56 | 'P' << 48 | 'N' << 40 | 'G' << 32 | '\r' << 24 | '\n' << 16 | 0x1a << 8 | '\n', } -Info :: struct { - header: IHDR, - chunks: [dynamic]Chunk, -} - -Chunk_Header :: struct #packed { - length: u32be, - type: Chunk_Type, -} - -Chunk :: struct #packed { - header: Chunk_Header, - data: []byte, - crc: u32be, -} - -Chunk_Type :: enum u32be { - // IHDR must come first in a file - IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R', - // PLTE must precede the first IDAT chunk - PLTE = 'P' << 24 | 'L' << 16 | 'T' << 8 | 'E', - bKGD = 'b' << 24 | 'K' << 16 | 'G' << 8 | 'D', - tRNS = 't' << 24 | 'R' << 16 | 'N' << 8 | 'S', - IDAT = 'I' << 24 | 'D' << 16 | 'A' << 8 | 'T', - - iTXt = 'i' << 24 | 'T' << 16 | 'X' << 8 | 't', - tEXt = 't' << 24 | 'E' << 16 | 'X' << 8 | 't', - zTXt = 'z' << 24 | 'T' << 16 | 'X' << 8 | 't', - - iCCP = 'i' << 24 | 'C' << 16 | 'C' << 8 | 'P', - pHYs = 'p' << 24 | 'H' << 16 | 'Y' << 8 | 's', - gAMA = 'g' << 24 | 'A' << 16 | 'M' << 8 | 'A', - tIME = 't' << 24 | 'I' << 16 | 'M' << 8 | 'E', - - sPLT = 's' << 24 | 'P' << 16 | 'L' << 8 | 'T', - sRGB = 's' << 24 | 'R' << 16 | 'G' << 8 | 'B', - hIST = 'h' << 24 | 'I' << 16 | 'S' << 8 | 'T', - cHRM = 'c' << 24 | 'H' << 16 | 'R' << 8 | 'M', - sBIT = 's' << 24 | 'B' << 16 | 'I' << 8 | 'T', - - /* - eXIf tags are not part of the core spec, but have been ratified - in v1.5.0 of the PNG Ext register. - - We will provide unprocessed chunks to the caller if `.return_metadata` is set. - Applications are free to implement an Exif decoder. - */ - eXIf = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f', - - // PNG files must end with IEND - IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D', - - /* - XCode sometimes produces "PNG" files that don't adhere to the PNG spec. - We recognize them only in order to avoid doing further work on them. - - Some tools like PNG Defry may be able to repair them, but we're not - going to reward Apple for producing proprietary broken files purporting - to be PNGs by supporting them. - - */ - iDOT = 'i' << 24 | 'D' << 16 | 'O' << 8 | 'T', - CbGI = 'C' << 24 | 'b' << 16 | 'H' << 8 | 'I', -} - -IHDR :: struct #packed { - width: u32be, - height: u32be, - bit_depth: u8, - color_type: Color_Type, - compression_method: u8, - filter_method: u8, - interlace_method: Interlace_Method, -} -IHDR_SIZE :: size_of(IHDR) -#assert (IHDR_SIZE == 13) - -Color_Value :: enum u8 { - Paletted = 0, // 1 << 0 = 1 - Color = 1, // 1 << 1 = 2 - Alpha = 2, // 1 << 2 = 4 -} -Color_Type :: distinct bit_set[Color_Value; u8] - -Interlace_Method :: enum u8 { - None = 0, - Adam7 = 1, -} - Row_Filter :: enum u8 { None = 0, Sub = 1, @@ -135,22 +63,22 @@ PLTE_Entry :: [3]u8 PLTE :: struct #packed { entries: [256]PLTE_Entry, - used: u16, + used: u16, } hIST :: struct #packed { entries: [256]u16, - used: u16, + used: u16, } sPLT :: struct #packed { - name: string, - depth: u8, + name: string, + depth: u8, entries: union { [][4]u8, [][4]u16, }, - used: u16, + used: u16, } // Other chunks @@ -223,14 +151,14 @@ Exif :: struct { } iCCP :: struct { - name: string, + name: string, profile: []u8, } sRGB_Rendering_Intent :: enum u8 { - Perceptual = 0, + Perceptual = 0, Relative_colorimetric = 1, - Saturation = 2, + Saturation = 2, Absolute_colorimetric = 3, } @@ -245,16 +173,30 @@ ADAM7_Y_SPACING := []int{ 8,8,8,4,4,2,2 } // Implementation starts here -read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) { - ch, e := compress.read_data(ctx, Chunk_Header) +read_chunk :: proc(ctx: ^$C) -> (chunk: image.PNG_Chunk, err: Error) { + ch, e := compress.read_data(ctx, image.PNG_Chunk_Header) if e != .None { - return {}, E_General.Stream_Too_Short + return {}, compress.General_Error.Stream_Too_Short } chunk.header = ch + /* + Sanity check chunk size + */ + #partial switch ch.type { + case .IDAT: + if ch.length > MAX_IDAT_SIZE { + return {}, image.PNG_Error.IDAT_Size_Too_Large + } + case: + if ch.length > MAX_CHUNK_SIZE { + return {}, image.PNG_Error.Invalid_Chunk_Length + } + } + chunk.data, e = compress.read_slice(ctx, int(ch.length)) if e != .None { - return {}, E_General.Stream_Too_Short + return {}, compress.General_Error.Stream_Too_Short } // Compute CRC over chunk type + data @@ -264,39 +206,68 @@ read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) { crc, e3 := compress.read_data(ctx, u32be) if e3 != .None { - return {}, E_General.Stream_Too_Short + return {}, compress.General_Error.Stream_Too_Short } chunk.crc = crc if chunk.crc != u32be(computed_crc) { - return {}, E_General.Checksum_Failed + return {}, compress.General_Error.Checksum_Failed } return chunk, nil } -read_header :: proc(ctx: ^$C) -> (IHDR, Error) { +copy_chunk :: proc(src: image.PNG_Chunk, allocator := context.allocator) -> (dest: image.PNG_Chunk, err: Error) { + if int(src.header.length) != len(src.data) { + return {}, .Invalid_Chunk_Length + } + + dest.header = src.header + dest.crc = src.crc + dest.data = make([]u8, dest.header.length, allocator) or_return + + copy(dest.data[:], src.data[:]) + return +} + +append_chunk :: proc(list: ^[dynamic]image.PNG_Chunk, src: image.PNG_Chunk, allocator := context.allocator) -> (err: Error) { + if int(src.header.length) != len(src.data) { + return .Invalid_Chunk_Length + } + + c := copy_chunk(src, allocator) or_return + length := len(list) + append(list, c) + if len(list) != length + 1 { + // Resize during append failed. + return mem.Allocator_Error.Out_Of_Memory + } + + return +} + +read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) { c, e := read_chunk(ctx) if e != nil { return {}, e } - header := (^IHDR)(raw_data(c.data))^ + header := (^image.PNG_IHDR)(raw_data(c.data))^ // Validate IHDR using header - if width == 0 || height == 0 { - return {}, E_PNG.Invalid_Image_Dimensions + if width == 0 || height == 0 || u128(width) * u128(height) > MAX_DIMENSIONS { + return {}, .Invalid_Image_Dimensions } if compression_method != 0 { - return {}, E_General.Unknown_Compression_Method + return {}, compress.General_Error.Unknown_Compression_Method } if filter_method != 0 { - return {}, E_PNG.Unknown_Filter_Method + return {}, .Unknown_Filter_Method } if interlace_method != .None && interlace_method != .Adam7 { - return {}, E_PNG.Unknown_Interlace_Method + return {}, .Unknown_Interlace_Method } @@ -314,7 +285,7 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) { } } if !allowed { - return {}, E_PNG.Invalid_Color_Bit_Depth_Combo + return {}, .Invalid_Color_Bit_Depth_Combo } case 2, 4, 6: /* @@ -322,7 +293,7 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) { Allowed bit depths: 8 and 16 */ if bit_depth != 8 && bit_depth != 16 { - return {}, E_PNG.Invalid_Color_Bit_Depth_Combo + return {}, .Invalid_Color_Bit_Depth_Combo } case 3: /* @@ -337,17 +308,17 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) { } } if !allowed { - return {}, E_PNG.Invalid_Color_Bit_Depth_Combo + return {}, .Invalid_Color_Bit_Depth_Combo } case: - return {}, E_PNG.Unknown_Color_Type + return {}, .Unknown_Color_Type } return header, nil } -chunk_type_to_name :: proc(type: ^Chunk_Type) -> string { +chunk_type_to_name :: proc(type: ^image.PNG_Chunk_Type) -> string { t := transmute(^u8)type return strings.string_from_ptr(t, 4) } @@ -377,7 +348,7 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont return load_from_slice(data, options) } else { img = new(Image) - return img, E_General.File_Not_Found + return img, compress.General_Error.File_Not_Found } } @@ -391,7 +362,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } if .alpha_drop_if_present in options && .alpha_add_if_missing in options { - return {}, E_General.Incompatible_Options + return {}, compress.General_Error.Incompatible_Options } if .do_not_expand_channels in options { @@ -402,27 +373,25 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a img = new(Image) } - info := new(Info) - img.metadata_ptr = info - img.metadata_type = typeid_of(Info) + info := new(image.PNG_Info) + img.metadata = info signature, io_error := compress.read_data(ctx, Signature) if io_error != .None || signature != .PNG { - return img, E_PNG.Invalid_PNG_Signature + return img, .Invalid_PNG_Signature } idat: []u8 idat_b: bytes.Buffer - idat_length := u32be(0) defer bytes.buffer_destroy(&idat_b) - c: Chunk - ch: Chunk_Header + idat_length := u64(0) + + c: image.PNG_Chunk + ch: image.PNG_Chunk_Header e: io.Error - header: IHDR - - info.chunks.allocator = context.temp_allocator + header: image.PNG_IHDR // State to ensure correct chunk ordering. seen_ihdr := false; first := true @@ -433,7 +402,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a seen_iend := false _plte := PLTE{} - trns := Chunk{} + trns := image.PNG_Chunk{} final_image_channels := 0 @@ -443,16 +412,16 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a // Peek at next chunk's length and type. // TODO: Some streams may not provide seek/read_at - ch, e = compress.peek_data(ctx, Chunk_Header) + ch, e = compress.peek_data(ctx, image.PNG_Chunk_Header) if e != .None { - return img, E_General.Stream_Too_Short + return img, compress.General_Error.Stream_Too_Short } // name := chunk_type_to_name(&ch.type); // Only used for debug prints during development. #partial switch ch.type { case .IHDR: if seen_ihdr || !first { - return {}, E_PNG.IHDR_Not_First_Chunk + return {}, .IHDR_Not_First_Chunk } seen_ihdr = true @@ -481,14 +450,14 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } if img.channels == 0 || img.depth == 0 { - return {}, E_PNG.IHDR_Corrupt + return {}, .IHDR_Corrupt } img.width = int(header.width) img.height = int(header.height) using header - h := IHDR{ + h := image.PNG_IHDR{ width = width, height = height, bit_depth = bit_depth, @@ -498,28 +467,30 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a interlace_method = interlace_method, } info.header = h + case .PLTE: seen_plte = true // PLTE must appear before IDAT and can't appear for color types 0, 4. ct := transmute(u8)info.header.color_type if seen_idat || ct == 0 || ct == 4 { - return img, E_PNG.PLTE_Encountered_Unexpectedly + return img, .PLTE_Encountered_Unexpectedly } c = read_chunk(ctx) or_return if c.header.length % 3 != 0 || c.header.length > 768 { - return img, E_PNG.PLTE_Invalid_Length + return img, .PLTE_Invalid_Length } plte_ok: bool _plte, plte_ok = plte(c) if !plte_ok { - return img, E_PNG.PLTE_Invalid_Length + return img, .PLTE_Invalid_Length } if .return_metadata in options { - append(&info.chunks, c) + append_chunk(&info.chunks, c) or_return } + case .IDAT: // If we only want image metadata and don't want the pixel data, we can early out. if .return_metadata not_in options && .do_not_decompress_image in options { @@ -528,11 +499,11 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } // There must be at least 1 IDAT, contiguous if more. if seen_idat { - return img, E_PNG.IDAT_Must_Be_Contiguous + return img, .IDAT_Must_Be_Contiguous } if idat_length > 0 { - return img, E_PNG.IDAT_Must_Be_Contiguous + return img, .IDAT_Must_Be_Contiguous } next := ch.type @@ -540,22 +511,29 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a c = read_chunk(ctx) or_return bytes.buffer_write(&idat_b, c.data) - idat_length += c.header.length + idat_length += u64(c.header.length) - ch, e = compress.peek_data(ctx, Chunk_Header) + if idat_length > MAX_IDAT_SIZE { + return {}, image.PNG_Error.IDAT_Size_Too_Large + } + + ch, e = compress.peek_data(ctx, image.PNG_Chunk_Header) if e != .None { - return img, E_General.Stream_Too_Short + return img, compress.General_Error.Stream_Too_Short } next = ch.type } + idat = bytes.buffer_to_bytes(&idat_b) if int(idat_length) != len(idat) { - return {}, E_PNG.IDAT_Corrupt + return {}, .IDAT_Corrupt } seen_idat = true + case .IEND: c = read_chunk(ctx) or_return seen_iend = true + case .bKGD: // TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be @@ -563,14 +541,14 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a c = read_chunk(ctx) or_return seen_bkgd = true if .return_metadata in options { - append(&info.chunks, c) + append_chunk(&info.chunks, c) or_return } ct := transmute(u8)info.header.color_type switch ct { case 3: // Indexed color if c.header.length != 1 { - return {}, E_PNG.BKGD_Invalid_Length + return {}, .BKGD_Invalid_Length } col := _plte.entries[c.data[0]] img.background = [3]u16{ @@ -580,26 +558,27 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } case 0, 4: // Grayscale, with and without Alpha if c.header.length != 2 { - return {}, E_PNG.BKGD_Invalid_Length + return {}, .BKGD_Invalid_Length } col := u16(mem.slice_data_cast([]u16be, c.data[:])[0]) img.background = [3]u16{col, col, col} case 2, 6: // Color, with and without Alpha if c.header.length != 6 { - return {}, E_PNG.BKGD_Invalid_Length + return {}, .BKGD_Invalid_Length } col := mem.slice_data_cast([]u16be, c.data[:]) img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])} } + case .tRNS: c = read_chunk(ctx) or_return if .Alpha in info.header.color_type { - return img, E_PNG.TRNS_Encountered_Unexpectedly + return img, .TRNS_Encountered_Unexpectedly } if .return_metadata in options { - append(&info.chunks, c) + append_chunk(&info.chunks, c) or_return } /* @@ -622,20 +601,20 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } } trns = c + case .iDOT, .CbGI: /* iPhone PNG bastardization that doesn't adhere to spec with broken IDAT chunk. We're not going to add support for it. If you have the misfortunte of coming - across one of these files, use a utility to defry it.s + across one of these files, use a utility to defry it. */ - return img, E_PNG.PNG_Does_Not_Adhere_to_Spec + return img, .Image_Does_Not_Adhere_to_Spec + case: // Unhandled type c = read_chunk(ctx) or_return - if .return_metadata in options { - // NOTE: Chunk cata is currently allocated on the temp allocator. - append(&info.chunks, c) + append_chunk(&info.chunks, c) or_return } first = false @@ -648,7 +627,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } if !seen_idat { - return img, E_PNG.IDAT_Missing + return img, .IDAT_Missing } /* @@ -685,7 +664,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a buf_len := len(buf.buf) if expected_size != buf_len { - return {}, E_PNG.IDAT_Corrupt + return {}, .IDAT_Corrupt } /* @@ -752,7 +731,9 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a // We need to create a new image buffer dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8) t := bytes.Buffer{} - resize(&t.buf, dest_raw_size) + if !resize(&t.buf, dest_raw_size) { + return {}, mem.Allocator_Error.Out_Of_Memory + } i := 0; j := 0 @@ -831,7 +812,9 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a // We need to create a new image buffer dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 16) t := bytes.Buffer{} - resize(&t.buf, dest_raw_size) + if !resize(&t.buf, dest_raw_size) { + return {}, mem.Allocator_Error.Out_Of_Memory + } p16 := mem.slice_data_cast([]u16, temp.buf[:]) o16 := mem.slice_data_cast([]u16, t.buf[:]) @@ -1028,7 +1011,9 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a // We need to create a new image buffer dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8) t := bytes.Buffer{} - resize(&t.buf, dest_raw_size) + if !resize(&t.buf, dest_raw_size) { + return {}, mem.Allocator_Error.Out_Of_Memory + } p := mem.slice_data_cast([]u8, temp.buf[:]) o := mem.slice_data_cast([]u8, t.buf[:]) @@ -1524,7 +1509,7 @@ defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) { return } -defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, options: Options) -> (err: compress.Error) { +defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IHDR, options: Options) -> (err: Error) { input := bytes.buffer_to_bytes(filter_bytes) width := int(header.width) height := int(header.height) @@ -1535,7 +1520,9 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option bytes_per_channel := depth == 16 ? 2 : 1 num_bytes := compute_buffer_size(width, height, channels, depth == 16 ? 16 : 8) - resize(&img.pixels.buf, num_bytes) + if !resize(&img.pixels.buf, num_bytes) { + return mem.Allocator_Error.Out_Of_Memory + } filter_ok: bool @@ -1560,7 +1547,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option } if !filter_ok { // Caller will destroy buffer for us. - return E_PNG.Unknown_Filter_Method + return .Unknown_Filter_Method } } else { /* @@ -1575,7 +1562,9 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option if x > 0 && y > 0 { temp: bytes.Buffer temp_len := compute_buffer_size(x, y, channels, depth == 16 ? 16 : 8) - resize(&temp.buf, temp_len) + if !resize(&temp.buf, temp_len) { + return mem.Allocator_Error.Out_Of_Memory + } params := Filter_Params{ src = input, @@ -1598,7 +1587,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option if !filter_ok { // Caller will destroy buffer for us. - return E_PNG.Unknown_Filter_Method + return .Unknown_Filter_Method } t := temp.buf[:] diff --git a/core/sys/win32/general.odin b/core/sys/win32/general.odin index 51f8c7d5f..d53bf8a4f 100644 --- a/core/sys/win32/general.odin +++ b/core/sys/win32/general.odin @@ -99,6 +99,57 @@ Rect :: struct { bottom: i32, } +Dev_Mode_A :: struct { + device_name: [32]u8, + spec_version: u16, + driver_version: u16, + size: u16, + driver_extra: u16, + fields: u32, + using _: struct #raw_union { + // Printer only fields. + using _: struct { + orientation: i16, + paper_size: i16, + paper_length: i16, + paper_width: i16, + scale: i16, + copies: i16, + default_source: i16, + print_quality: i16, + }, + // Display only fields. + using _: struct { + position: Point, + display_orientation: u32, + display_fixed_output: u32, + }, + }, + color: i16, + duplex: i16, + y_resolution: i16, + tt_option: i16, + collate: i16, + form_name: [32]u8, + log_pixels: u16, + bits_per_pel: u32, + pels_width: u32, + pels_height: u32, + using _: struct #raw_union { + display_flags: u32, + nup: u32, + }, + display_frequency: u32, + icm_method: u32, + icm_intent: u32, + media_type: u32, + dither_type: u32, + reserved_1: u32, + reserved_2: u32, + panning_width: u32, + panning_height: u32, +} + Filetime :: struct { lo, hi: u32, } @@ -364,6 +415,9 @@ MAPVK_VK_TO_CHAR :: 2 MAPVK_VSC_TO_VK_EX :: 3 //WinUser.h +ENUM_CURRENT_SETTINGS :: u32(4294967295) // (DWORD)-1 +ENUM_REGISTRY_SETTINGS :: u32(4294967294) // (DWORD)-2 + VK_LBUTTON :: 0x01 VK_RBUTTON :: 0x02 VK_CANCEL :: 0x03 @@ -877,7 +931,20 @@ FILE_GENERIC_EXECUTE :: 0x20000000 FILE_GENERIC_WRITE :: 0x40000000 FILE_GENERIC_READ :: 0x80000000 -FILE_APPEND_DATA :: 0x0004 +FILE_READ_DATA :: 0x0001 +FILE_LIST_DIRECTORY :: 0x0001 +FILE_WRITE_DATA :: 0x0002 +FILE_ADD_FILE :: 0x0002 +FILE_APPEND_DATA :: 0x0004 +FILE_ADD_SUBDIRECTORY :: 0x0004 +FILE_CREATE_PIPE_INSTANCE :: 0x0004 +FILE_READ_EA :: 0x0008 +FILE_WRITE_EA :: 0x0010 +FILE_EXECUTE :: 0x0020 +FILE_TRAVERSE :: 0x0020 +FILE_DELETE_CHILD :: 0x0040 +FILE_READ_ATTRIBUTES :: 0x0080 +FILE_WRITE_ATTRIBUTES :: 0x0100 STD_INPUT_HANDLE :: -10 STD_OUTPUT_HANDLE :: -11 diff --git a/core/sys/win32/kernel32.odin b/core/sys/win32/kernel32.odin index 187bcd06f..709964fb4 100644 --- a/core/sys/win32/kernel32.odin +++ b/core/sys/win32/kernel32.odin @@ -106,6 +106,8 @@ foreign kernel32 { bytes_returned: ^u32, overlapped: ^Overlapped, completion: rawptr) -> Bool --- + @(link_name="GetOverlappedResult") get_overlapped_result :: proc(file: Handle, overlapped: ^Overlapped, number_of_bytes_transferred: ^u32, wait: Bool) -> Bool --- + @(link_name="WideCharToMultiByte") wide_char_to_multi_byte :: proc(code_page: u32, flags: u32, wchar_str: Wstring, wchar: i32, multi_str: cstring, multi: i32, diff --git a/core/sys/win32/user32.odin b/core/sys/win32/user32.odin index 57aea88aa..593fdbb8e 100644 --- a/core/sys/win32/user32.odin +++ b/core/sys/win32/user32.odin @@ -201,6 +201,8 @@ foreign user32 { @(link_name="MapVirtualKeyExA") map_virtual_key_ex_a :: proc(code, map_type: u32, hkl: HKL) -> u32 --- @(link_name="EnumDisplayMonitors") enum_display_monitors :: proc(hdc: Hdc, rect: ^Rect, enum_proc: Monitor_Enum_Proc, lparam: Lparam) -> bool --- + + @(link_name="EnumDisplaySettingsA") enum_display_settings_a :: proc(device_name: cstring, mode_number: u32, mode: ^Dev_Mode_A) -> Bool --- } @(default_calling_convention = "std") diff --git a/core/sys/win32/winmm.odin b/core/sys/win32/winmm.odin index 05d099a1a..0f567fbcc 100644 --- a/core/sys/win32/winmm.odin +++ b/core/sys/win32/winmm.odin @@ -6,5 +6,7 @@ foreign import "system:winmm.lib" @(default_calling_convention = "std") foreign winmm { + @(link_name="timeBeginPeriod") time_begin_period :: proc(period: u32) -> u32 --- + @(link_name="timeGetTime") time_get_time :: proc() -> u32 --- } diff --git a/tests/core/image/build.bat b/tests/core/image/build.bat new file mode 100644 index 000000000..03ee6b9a5 --- /dev/null +++ b/tests/core/image/build.bat @@ -0,0 +1,4 @@ +@echo off +pushd .. +odin run image +popd \ No newline at end of file diff --git a/tests/core/image/test_core_image.odin b/tests/core/image/test_core_image.odin index 339faff58..155b69298 100644 --- a/tests/core/image/test_core_image.odin +++ b/tests/core/image/test_core_image.odin @@ -1,5 +1,3 @@ -package test_core_image - /* Copyright 2021 Jeroen van Rijn . Made available under Odin's BSD-3 license. @@ -9,6 +7,7 @@ package test_core_image A test suite for PNG. */ +package test_core_image import "core:testing" @@ -64,7 +63,7 @@ PNG_Test :: struct { file: string, tests: []struct { options: image.Options, - expected_error: compress.Error, + expected_error: image.Error, dims: PNG_Dims, hash: u32, }, @@ -1198,37 +1197,37 @@ Corrupt_PNG_Tests := []PNG_Test{ { "xs1n0g01", // signature byte 1 MSBit reset to zero { - {Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000}, + {Default, .Invalid_PNG_Signature, {}, 0x_0000_0000}, }, }, { "xs2n0g01", // signature byte 2 is a 'Q' { - {Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000}, + {Default, .Invalid_PNG_Signature, {}, 0x_0000_0000}, }, }, { "xs4n0g01", // signature byte 4 lowercase { - {Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000}, + {Default, .Invalid_PNG_Signature, {}, 0x_0000_0000}, }, }, { "xs7n0g01", // 7th byte a space instead of control-Z { - {Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000}, + {Default, .Invalid_PNG_Signature, {}, 0x_0000_0000}, }, }, { "xcrn0g04", // added cr bytes { - {Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000}, + {Default, .Invalid_PNG_Signature, {}, 0x_0000_0000}, }, }, { "xlfn0g04", // added lf bytes { - {Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000}, + {Default, .Invalid_PNG_Signature, {}, 0x_0000_0000}, }, }, { @@ -1240,37 +1239,37 @@ Corrupt_PNG_Tests := []PNG_Test{ { "xc1n0g08", // color type 1 { - {Default, I_Error.Unknown_Color_Type, {}, 0x_0000_0000}, + {Default, .Unknown_Color_Type, {}, 0x_0000_0000}, }, }, { "xc9n2c08", // color type 9 { - {Default, I_Error.Unknown_Color_Type, {}, 0x_0000_0000}, + {Default, .Unknown_Color_Type, {}, 0x_0000_0000}, }, }, { "xd0n2c08", // bit-depth 0 { - {Default, I_Error.Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000}, + {Default, .Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000}, }, }, { "xd3n2c08", // bit-depth 3 { - {Default, I_Error.Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000}, + {Default, .Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000}, }, }, { "xd9n2c08", // bit-depth 99 { - {Default, I_Error.Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000}, + {Default, .Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000}, }, }, { "xdtn0g01", // missing IDAT chunk { - {Default, I_Error.IDAT_Missing, {}, 0x_0000_0000}, + {Default, .IDAT_Missing, {}, 0x_0000_0000}, }, }, { @@ -1505,19 +1504,17 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { passed &= test.hash == hash if .return_metadata in test.options { - v: ^png.Info - if img.metadata_ptr != nil && img.metadata_type == png.Info { - v = (^png.Info)(img.metadata_ptr) + if v, ok := img.metadata.(^image.PNG_Info); ok { for c in v.chunks { #partial switch(c.header.type) { case .gAMA: switch(file.file) { case "pp0n2c16", "pp0n6a08": - gamma := png.gamma(c) + gamma, gamma_ok := png.gamma(c) expected_gamma := f32(1.0) error = fmt.tprintf("%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma) - expect(t, gamma == expected_gamma, error) + expect(t, gamma == expected_gamma && gamma_ok, error) } case .PLTE: switch(file.file) { @@ -1557,25 +1554,25 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { expect(t, expected_chrm == chrm && chrm_ok, error) } case .pHYs: - phys := png.phys(c) + phys, phys_ok := png.phys(c) phys_err := "%v test %v cHRM is %v, expected %v." switch (file.file) { case "cdfn2c08": expected_phys := png.pHYs{ppu_x = 1, ppu_y = 4, unit = .Unknown} error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys, error) + expect(t, expected_phys == phys && phys_ok, error) case "cdhn2c08": expected_phys := png.pHYs{ppu_x = 4, ppu_y = 1, unit = .Unknown} error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys, error) + expect(t, expected_phys == phys && phys_ok, error) case "cdsn2c08": expected_phys := png.pHYs{ppu_x = 1, ppu_y = 1, unit = .Unknown} error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys, error) + expect(t, expected_phys == phys && phys_ok, error) case "cdun2c08": expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter} error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys) - expect(t, expected_phys == phys, error) + expect(t, expected_phys == phys && phys_ok, error) } case .hIST: hist, hist_ok := png.hist(c) @@ -1589,7 +1586,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { expect(t, hist.used == 256 && hist_ok, error) } case .tIME: - png_time := png.time(c) + png_time, png_time_ok := png.time(c) time_err := "%v test %v tIME was %v, expected %v." expected_time: png.tIME @@ -1610,7 +1607,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) { } error = fmt.tprintf(time_err, file.file, count, png_time, expected_time) - expect(t, png_time == expected_time, error) + expect(t, png_time == expected_time && png_time_ok, error) error = fmt.tprintf(time_core_err, file.file, count, core_time, expected_core) expect(t, core_time == expected_core && core_time_ok, error)