This commit is contained in:
gingerBill
2021-10-07 13:41:26 +01:00
11 changed files with 465 additions and 299 deletions

View File

@@ -1,5 +1,3 @@
package compress
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
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
*/

View File

@@ -1,16 +1,17 @@
package image
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
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
}

View File

@@ -1,9 +1,6 @@
//+ignore
package png
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
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.")

View File

@@ -1,5 +1,3 @@
package png
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
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

View File

@@ -1,13 +1,12 @@
package png
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
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[:]

View File

@@ -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

View File

@@ -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,

View File

@@ -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")

View File

@@ -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 ---
}

View File

@@ -0,0 +1,4 @@
@echo off
pushd ..
odin run image
popd

View File

@@ -1,5 +1,3 @@
package test_core_image
/*
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
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)