Add additional error checking to helpers.

This commit is contained in:
Jeroen van Rijn
2021-10-06 20:10:37 +02:00
parent 2e9eec156c
commit 21c6d691d8
6 changed files with 176 additions and 143 deletions
+2
View File
@@ -11,6 +11,7 @@ package compress
import "core:io"
import "core:image"
import "core:bytes"
import "core:mem"
/*
These settings bound how much compression algorithms will allocate for their output buffer.
@@ -47,6 +48,7 @@ when size_of(uintptr) == 8 {
Error :: union {
General_Error,
mem.Allocator_Error,
Deflate_Error,
ZLIB_Error,
GZIP_Error,
+1
View File
@@ -131,6 +131,7 @@ Error :: enum {
Unknown_Interlace_Method,
Requested_Channel_Not_Present,
Post_Processing_Error,
Invalid_Chunk_Length,
}
/*
+78 -77
View File
@@ -41,7 +41,7 @@ main :: proc() {
demo :: proc() {
file: string
options := image.Options{} // {.return_metadata};
options := image.Options{.return_metadata}
err: compress.Error
img: ^image.Image
@@ -53,23 +53,26 @@ 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)
// 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)
assert(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:
if t, t_ok := core_time(c); t_ok {
fmt.printf("[tIME]: %v\n", t)
case .gAMA:
fmt.printf("[gAMA]: %v\n", gamma(c))
case .pHYs:
phys := phys(c)
}
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)
@@ -80,73 +83,71 @@ demo :: proc() {
} 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)
}
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.")
+38 -34
View File
@@ -34,15 +34,17 @@ destroy :: proc(img: ^Image) {
}
bytes.buffer_destroy(&img.pixels)
assert(img.metadata_ptr != nil && img.metadata_type == Info)
v := (^Info)(img.metadata_ptr)
for chunk in &v.chunks {
delete(chunk.data)
}
delete(v.chunks)
// 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.
*/
free(img)
}
@@ -50,46 +52,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: 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: 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: 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),
)
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) {
assert(len(c.data) == int(c.header.length))
#partial switch c.header.type {
case .tEXt:
ok = true
@@ -228,9 +234,7 @@ iccp_destroy :: proc(i: iCCP) {
}
srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) {
ok = true
if c.header.type != .sRGB || len(c.data) != 1 {
if c.header.type != .sRGB || len(c.data) != size_of(sRGB_Rendering_Intent) {
return {}, false
}
@@ -238,7 +242,7 @@ 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) {
+48 -23
View File
@@ -100,13 +100,13 @@ Chunk_Type :: enum u32be {
}
IHDR :: struct #packed {
width: u32be,
height: u32be,
bit_depth: u8,
color_type: Color_Type,
width: u32be,
height: u32be,
bit_depth: u8,
color_type: Color_Type,
compression_method: u8,
filter_method: u8,
interlace_method: Interlace_Method,
filter_method: u8,
interlace_method: Interlace_Method,
}
IHDR_SIZE :: size_of(IHDR)
#assert (IHDR_SIZE == 13)
@@ -135,22 +135,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 +223,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,
}
@@ -274,6 +274,35 @@ read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) {
return chunk, nil
}
copy_chunk :: proc(src: Chunk, allocator := context.allocator) -> (dest: 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]Chunk, src: 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 .Out_Of_Memory
}
return
}
read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
c, e := read_chunk(ctx)
if e != nil {
@@ -422,8 +451,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
header: IHDR
info.chunks.allocator = context.temp_allocator
// State to ensure correct chunk ordering.
seen_ihdr := false; first := true
seen_plte := false
@@ -518,7 +545,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
}
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.
@@ -563,7 +590,7 @@ 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
@@ -599,7 +626,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
}
if .return_metadata in options {
append(&info.chunks, c)
append_chunk(&info.chunks, c) or_return
}
/*
@@ -626,16 +653,14 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
/*
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
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
+9 -9
View File
@@ -1514,10 +1514,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
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 +1557,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 +1589,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 +1610,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)