mirror of
https://github.com/odin-lang/Odin.git
synced 2026-04-20 05:20:28 +00:00
png + compress: Rearrange error unions.
This commit is contained in:
@@ -9,9 +9,8 @@ package compress
|
||||
*/
|
||||
|
||||
import "core:io"
|
||||
import "core:image"
|
||||
import "core:bytes"
|
||||
import "core:mem"
|
||||
import "core:runtime"
|
||||
|
||||
/*
|
||||
These settings bound how much compression algorithms will allocate for their output buffer.
|
||||
@@ -48,16 +47,12 @@ when size_of(uintptr) == 8 {
|
||||
|
||||
Error :: union {
|
||||
General_Error,
|
||||
mem.Allocator_Error,
|
||||
Deflate_Error,
|
||||
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 {
|
||||
@@ -71,7 +66,6 @@ General_Error :: enum {
|
||||
Incompatible_Options,
|
||||
Unimplemented,
|
||||
|
||||
|
||||
/*
|
||||
Memory errors
|
||||
*/
|
||||
|
||||
@@ -11,6 +11,8 @@ package image
|
||||
|
||||
import "core:bytes"
|
||||
import "core:mem"
|
||||
import "core:compress"
|
||||
import "core:runtime"
|
||||
|
||||
Image :: struct {
|
||||
width: int,
|
||||
@@ -112,19 +114,34 @@ 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_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,
|
||||
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,
|
||||
|
||||
@@ -12,7 +12,6 @@ package png
|
||||
An example of how to use `load`.
|
||||
*/
|
||||
|
||||
import "core:compress"
|
||||
import "core:image"
|
||||
// import "core:image/png"
|
||||
import "core:bytes"
|
||||
@@ -42,7 +41,7 @@ demo :: proc() {
|
||||
file: string
|
||||
|
||||
options := image.Options{.return_metadata}
|
||||
err: compress.Error
|
||||
err: image.Error
|
||||
img: ^image.Image
|
||||
|
||||
file = "../../../misc/logo-slim.png"
|
||||
|
||||
@@ -21,11 +21,7 @@ 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
|
||||
|
||||
Error :: image.Error
|
||||
Image :: image.Image
|
||||
Options :: image.Options
|
||||
|
||||
@@ -248,13 +244,13 @@ ADAM7_Y_SPACING := []int{ 8,8,8,4,4,2,2 }
|
||||
read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) {
|
||||
ch, e := compress.read_data(ctx, Chunk_Header)
|
||||
if e != .None {
|
||||
return {}, E_General.Stream_Too_Short
|
||||
return {}, compress.General_Error.Stream_Too_Short
|
||||
}
|
||||
chunk.header = ch
|
||||
|
||||
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,12 +260,12 @@ 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
|
||||
}
|
||||
@@ -297,7 +293,7 @@ append_chunk :: proc(list: ^[dynamic]Chunk, src: Chunk, allocator := context.all
|
||||
append(list, c)
|
||||
if len(list) != length + 1 {
|
||||
// Resize during append failed.
|
||||
return .Out_Of_Memory
|
||||
return mem.Allocator_Error.Out_Of_Memory
|
||||
}
|
||||
|
||||
return
|
||||
@@ -313,19 +309,19 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
|
||||
// Validate IHDR
|
||||
using header
|
||||
if width == 0 || height == 0 {
|
||||
return {}, E_PNG.Invalid_Image_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
|
||||
|
||||
}
|
||||
|
||||
@@ -343,7 +339,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:
|
||||
/*
|
||||
@@ -351,7 +347,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:
|
||||
/*
|
||||
@@ -366,11 +362,11 @@ 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
|
||||
@@ -406,7 +402,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,7 +416,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 {
|
||||
@@ -437,7 +433,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
|
||||
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
|
||||
@@ -472,14 +468,14 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
|
||||
ch, e = compress.peek_data(ctx, 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
|
||||
|
||||
@@ -508,7 +504,7 @@ 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)
|
||||
@@ -530,18 +526,18 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
// 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 {
|
||||
@@ -555,11 +551,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
|
||||
@@ -571,13 +567,13 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
|
||||
ch, e = compress.peek_data(ctx, 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:
|
||||
@@ -597,7 +593,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
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{
|
||||
@@ -607,13 +603,13 @@ 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])}
|
||||
@@ -622,7 +618,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
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 {
|
||||
@@ -655,7 +651,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
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.
|
||||
*/
|
||||
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
|
||||
@@ -673,7 +669,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
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -710,7 +706,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
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1549,7 +1545,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: ^IHDR, options: Options) -> (err: Error) {
|
||||
input := bytes.buffer_to_bytes(filter_bytes)
|
||||
width := int(header.width)
|
||||
height := int(header.height)
|
||||
@@ -1585,7 +1581,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 {
|
||||
/*
|
||||
@@ -1623,7 +1619,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[:]
|
||||
|
||||
4
tests/core/image/build.bat
Normal file
4
tests/core/image/build.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
pushd ..
|
||||
odin run image
|
||||
popd
|
||||
@@ -64,7 +64,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 +1198,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 +1240,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},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user