[pbm] Normalize some errors, correct .depth

This commit is contained in:
Jeroen van Rijn
2022-04-30 14:34:07 +02:00
parent ae3deea153
commit d6a8216ce4
6 changed files with 70 additions and 77 deletions
+25 -16
View File
@@ -45,7 +45,7 @@ Image :: struct {
width: int,
height: int,
channels: int,
depth: int,
depth: int, // Channel depth in bits, typically 8 or 16
pixels: bytes.Buffer,
/*
Some image loaders/writers can return/take an optional background color.
@@ -141,13 +141,14 @@ Option :: enum {
alpha_drop_if_present, // Unimplemented for QOI. Returns error.
alpha_premultiply, // Unimplemented for QOI. Returns error.
blend_background, // Ignored for non-PNG formats
// Unimplemented
do_not_expand_grayscale,
do_not_expand_indexed,
do_not_expand_channels,
// SAVE OPTIONS
qoi_all_channels_linear, // QOI, informative info. If not set, defaults to sRGB with linear alpha.
qoi_all_channels_linear, // QOI, informative only. If not set, defaults to sRGB with linear alpha.
}
Options :: distinct bit_set[Option]
@@ -166,12 +167,29 @@ Error :: union #shared_nil {
General_Image_Error :: enum {
None = 0,
// File I/O
Unable_To_Read_File,
Unable_To_Write_File,
// Invalid
Invalid_Signature,
Invalid_Input_Image,
Image_Dimensions_Too_Large,
Invalid_Image_Dimensions,
Invalid_Number_Of_Channels,
Image_Dimensions_Too_Large,
Image_Does_Not_Adhere_to_Spec,
Invalid_Input_Image,
Invalid_Image_Depth,
Invalid_Bit_Depth,
Invalid_Color_Space,
// More data than pixels to decode into, for example.
Corrupt,
// Output buffer is the wrong size
Invalid_Output,
// Allocation
Unable_To_Allocate_Or_Resize,
}
/*
@@ -201,8 +219,6 @@ Netpbm_Error :: enum {
None = 0,
// reading
File_Not_Readable,
Invalid_Signature,
Invalid_Header_Token_Character,
Incomplete_Header,
Invalid_Header_Value,
@@ -212,9 +228,7 @@ Netpbm_Error :: enum {
Invalid_Buffer_Value,
// writing
File_Not_Writable,
Invalid_Format,
Invalid_Image_Depth,
}
/*
@@ -222,7 +236,6 @@ Netpbm_Error :: enum {
*/
PNG_Error :: enum {
None = 0,
Invalid_PNG_Signature,
IHDR_Not_First_Chunk,
IHDR_Corrupt,
IDAT_Missing,
@@ -338,14 +351,10 @@ PNG_Interlace_Method :: enum u8 {
*/
QOI_Error :: enum {
None = 0,
Invalid_QOI_Signature,
Invalid_Bit_Depth, // QOI supports only 8-bit images, error only returned from writer.
Invalid_Color_Space, // QOI allows 0 = sRGB or 1 = linear.
Corrupt, // More data than pixels to decode into, for example.
Missing_Or_Corrupt_Trailer, // Image seemed to have decoded okay, but trailer is missing or corrupt.
}
QOI_Magic :: u32be(0x716f6966) // "qoif"
QOI_Magic :: u32be(0x716f6966) // "qoif"
QOI_Color_Space :: enum u8 {
sRGB = 0,
@@ -1170,10 +1179,10 @@ write_bytes :: proc(buf: ^bytes.Buffer, data: []u8) -> (err: compress.General_Er
return nil
} else if len(data) == 1 {
if bytes.buffer_write_byte(buf, data[0]) != nil {
return compress.General_Error.Resize_Failed
return .Resize_Failed
}
} else if n, _ := bytes.buffer_write(buf, data); n != len(data) {
return compress.General_Error.Resize_Failed
return .Resize_Failed
}
return nil
}
+24 -37
View File
@@ -9,8 +9,6 @@ import "core:strconv"
import "core:strings"
import "core:unicode"
Image :: image.Image
Format :: image.Netpbm_Format
Header :: image.Netpbm_Header
@@ -18,8 +16,6 @@ Info :: image.Netpbm_Info
Error :: image.Error
Format_Error :: image.Netpbm_Error
Formats :: bit_set[Format]
PBM :: Formats{.P1, .P4}
PGM :: Formats{.P2, .P5}
@@ -30,14 +26,12 @@ PFM :: Formats{.Pf, .PF}
ASCII :: Formats{.P1, .P2, .P3}
BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM
read :: proc {
read_from_file,
read_from_buffer,
load :: proc {
load_from_file,
load_from_buffer,
}
read_from_file :: proc(filename: string, allocator := context.allocator) -> (img: Image, err: Error) {
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: Image, err: Error) {
context.allocator = allocator
data, ok := os.read_entire_file(filename); defer delete(data)
@@ -49,7 +43,7 @@ read_from_file :: proc(filename: string, allocator := context.allocator) -> (img
return read_from_buffer(data)
}
read_from_buffer :: proc(data: []byte, allocator := context.allocator) -> (img: Image, err: Error) {
load_from_buffer :: proc(data: []byte, allocator := context.allocator) -> (img: Image, err: Error) {
context.allocator = allocator
header: Header; defer header_destroy(&header)
@@ -70,14 +64,12 @@ read_from_buffer :: proc(data: []byte, allocator := context.allocator) -> (img:
return
}
write :: proc {
write_to_file,
write_to_buffer,
save :: proc {
save_to_file,
save_to_buffer,
}
write_to_file :: proc(filename: string, img: Image, allocator := context.allocator) -> (err: Error) {
save_to_file :: proc(filename: string, img: Image, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator
data: []byte; defer delete(data)
@@ -90,7 +82,7 @@ write_to_file :: proc(filename: string, img: Image, allocator := context.allocat
return Format_Error.None
}
write_to_buffer :: proc(img: Image, allocator := context.allocator) -> (buffer: []byte, err: Error) {
save_to_buffer :: proc(img: Image, allocator := context.allocator) -> (buffer: []byte, err: Error) {
context.allocator = allocator
info, ok := img.metadata.(^image.Netpbm_Info)
@@ -109,12 +101,12 @@ write_to_buffer :: proc(img: Image, allocator := context.allocator) -> (buffer:
}
if header.format in (PNM + PAM) {
if header.maxval <= int(max(u8)) && img.depth != 1 \
|| header.maxval > int(max(u8)) && header.maxval <= int(max(u16)) && img.depth != 2 {
if header.maxval <= int(max(u8)) && img.depth != 8 \
|| header.maxval > int(max(u8)) && header.maxval <= int(max(u16)) && img.depth != 16 {
err = Format_Error.Invalid_Image_Depth
return
}
} else if header.format in PFM && img.depth != 4 {
} else if header.format in PFM && img.depth != 32 {
err = Format_Error.Invalid_Image_Depth
return
}
@@ -179,7 +171,7 @@ write_to_buffer :: proc(img: Image, allocator := context.allocator) -> (buffer:
mem.copy(raw_data(data.buf[len(header_buf):]), raw_data(pixels), len(pixels))
// convert from native endianness
if img.depth == 2 {
if img.depth == 16 {
pixels := mem.slice_data_cast([]u16be, data.buf[len(header_buf):])
for p in &pixels {
p = u16be(transmute(u16) p)
@@ -212,7 +204,7 @@ write_to_buffer :: proc(img: Image, allocator := context.allocator) -> (buffer:
// Token ASCII
case .P2, .P3:
switch img.depth {
case 1:
case 8:
pixels := img.pixels.buf[:]
for y in 0 ..< img.height {
for x in 0 ..< img.width {
@@ -226,7 +218,7 @@ write_to_buffer :: proc(img: Image, allocator := context.allocator) -> (buffer:
fmt.sbprint(&data, "\n")
}
case 2:
case 16:
pixels := mem.slice_data_cast([]u16, img.pixels.buf[:])
for y in 0 ..< img.height {
for x in 0 ..< img.width {
@@ -251,8 +243,6 @@ write_to_buffer :: proc(img: Image, allocator := context.allocator) -> (buffer:
return data.buf[:], Format_Error.None
}
parse_header :: proc(data: []byte, allocator := context.allocator) -> (header: Header, length: int, err: Error) {
context.allocator = allocator
@@ -351,7 +341,7 @@ _parse_header_pnm :: proc(data: []byte) -> (header: Header, length: int, err: Er
// set extra info
header.channels = 3 if header.format in PPM else 1
header.depth = 2 if header.maxval > int(max(u8)) else 1
header.depth = 16 if header.maxval > int(max(u8)) else 8
// limit checking
if current_field < len(header_fields) {
@@ -448,12 +438,11 @@ _parse_header_pam :: proc(data: []byte, allocator := context.allocator) -> (head
}
// extra info
header.depth = 2 if header.maxval > int(max(u8)) else 1
header.depth = 16 if header.maxval > int(max(u8)) else 8
// limit checking
if header.width < 1 \
|| header.height < 1 \
|| header.depth < 1 \
|| header.maxval < 1 \
|| header.maxval > int(max(u16)) {
err = Format_Error.Invalid_Header_Value
@@ -484,7 +473,7 @@ _parse_header_pfm :: proc(data: []byte) -> (header: Header, length: int, err: Er
}
// floating point
header.depth = 4
header.depth = 32
// width
field, ok = strings.fields_iterator(&field_iterator)
@@ -542,8 +531,6 @@ _parse_header_pfm :: proc(data: []byte) -> (header: Header, length: int, err: Er
return
}
decode_image :: proc(header: Header, data: []byte, allocator := context.allocator) -> (img: Image, err: Error) {
context.allocator = allocator
@@ -554,7 +541,7 @@ decode_image :: proc(header: Header, data: []byte, allocator := context.allocato
depth = header.depth,
}
buffer_size := img.width * img.height * img.channels * img.depth
buffer_size := image.compute_buffer_size(img.width, img.height, img.channels, img.depth)
// we can check data size for binary formats
if header.format in BINARY {
@@ -615,7 +602,7 @@ decode_image :: proc(header: Header, data: []byte, allocator := context.allocato
}
}
} else {
if img.depth == 2 {
if img.depth == 16 {
pixels := mem.slice_data_cast([]u16, img.pixels.buf[:])
for p in &pixels {
p = u16(transmute(u16be) p)
@@ -658,9 +645,9 @@ decode_image :: proc(header: Header, data: []byte, allocator := context.allocato
}
switch img.depth {
case 1:
case 8:
bytes.buffer_write_byte(&img.pixels, u8(value))
case 2:
case 16:
vb := transmute([2]u8) u16(value)
bytes.buffer_write(&img.pixels, vb[:])
}
@@ -678,4 +665,4 @@ decode_image :: proc(header: Header, data: []byte, allocator := context.allocato
err = Format_Error.None
return
}
}
+8 -8
View File
@@ -238,7 +238,7 @@ append_chunk :: proc(list: ^[dynamic]image.PNG_Chunk, src: image.PNG_Chunk, allo
append(list, c)
if len(list) != length + 1 {
// Resize during append failed.
return mem.Allocator_Error.Out_Of_Memory
return .Unable_To_Allocate_Or_Resize
}
return
@@ -347,7 +347,7 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont
return load_from_slice(data, options)
} else {
img = new(Image)
return img, compress.General_Error.File_Not_Found
return img, .Unable_To_Read_File
}
}
@@ -381,7 +381,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, .Invalid_PNG_Signature
return img, .Invalid_Signature
}
idat: []u8
@@ -747,7 +747,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
t := bytes.Buffer{}
if !resize(&t.buf, dest_raw_size) {
return {}, mem.Allocator_Error.Out_Of_Memory
return {}, .Unable_To_Allocate_Or_Resize
}
i := 0; j := 0
@@ -828,7 +828,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 16)
t := bytes.Buffer{}
if !resize(&t.buf, dest_raw_size) {
return {}, mem.Allocator_Error.Out_Of_Memory
return {}, .Unable_To_Allocate_Or_Resize
}
p16 := mem.slice_data_cast([]u16, temp.buf[:])
@@ -1027,7 +1027,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
t := bytes.Buffer{}
if !resize(&t.buf, dest_raw_size) {
return {}, mem.Allocator_Error.Out_Of_Memory
return {}, .Unable_To_Allocate_Or_Resize
}
p := mem.slice_data_cast([]u8, temp.buf[:])
@@ -1535,7 +1535,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
num_bytes := compute_buffer_size(width, height, channels, depth == 16 ? 16 : 8)
if !resize(&img.pixels.buf, num_bytes) {
return mem.Allocator_Error.Out_Of_Memory
return .Unable_To_Allocate_Or_Resize
}
filter_ok: bool
@@ -1577,7 +1577,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
temp: bytes.Buffer
temp_len := compute_buffer_size(x, y, channels, depth == 16 ? 16 : 8)
if !resize(&temp.buf, temp_len) {
return mem.Allocator_Error.Out_Of_Memory
return .Unable_To_Allocate_Or_Resize
}
params := Filter_Params{
+5 -7
View File
@@ -12,14 +12,12 @@
// The QOI specification is at https://qoiformat.org.
package qoi
import "core:mem"
import "core:image"
import "core:compress"
import "core:bytes"
import "core:os"
Error :: image.Error
General :: compress.General_Error
Image :: image.Image
Options :: image.Options
@@ -57,7 +55,7 @@ save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
max_size := pixels * (img.channels + 1) + size_of(image.QOI_Header) + size_of(u64be)
if !resize(&output.buf, max_size) {
return General.Resize_Failed
return .Unable_To_Allocate_Or_Resize
}
header := image.QOI_Header{
@@ -177,7 +175,7 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato
save_to_memory(out, img, options) or_return
write_ok := os.write_entire_file(output, out.buf[:])
return nil if write_ok else General.Cannot_Open_File
return nil if write_ok else .Unable_To_Write_File
}
save :: proc{save_to_memory, save_to_file}
@@ -201,7 +199,7 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont
return load_from_slice(data, options)
} else {
img = new(Image)
return img, compress.General_Error.File_Not_Found
return img, .Unable_To_Read_File
}
}
@@ -221,7 +219,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
header := image.read_data(ctx, image.QOI_Header) or_return
if header.magic != image.QOI_Magic {
return img, .Invalid_QOI_Signature
return img, .Invalid_Signature
}
if img == nil {
@@ -264,7 +262,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
bytes_needed := image.compute_buffer_size(int(header.width), int(header.height), img.channels, 8)
if !resize(&img.pixels.buf, bytes_needed) {
return img, mem.Allocator_Error.Out_Of_Memory
return img, .Unable_To_Allocate_Or_Resize
}
/*
+2 -3
View File
@@ -17,7 +17,6 @@ import "core:bytes"
import "core:os"
Error :: image.Error
General :: compress.General_Error
Image :: image.Image
Options :: image.Options
@@ -55,7 +54,7 @@ save_to_memory :: proc(output: ^bytes.Buffer, img: ^Image, options := Options{}
necessary := pixels * img.channels + size_of(image.TGA_Header)
if !resize(&output.buf, necessary) {
return General.Resize_Failed
return .Unable_To_Allocate_Or_Resize
}
header := image.TGA_Header{
@@ -97,7 +96,7 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato
save_to_memory(out, img, options) or_return
write_ok := os.write_entire_file(output, out.buf[:])
return nil if write_ok else General.Cannot_Open_File
return nil if write_ok else .Unable_To_Write_File
}
save :: proc{save_to_memory, save_to_file}
+6 -6
View File
@@ -1199,37 +1199,37 @@ Corrupt_PNG_Tests := []PNG_Test{
{
"xs1n0g01", // signature byte 1 MSBit reset to zero
{
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
{Default, .Invalid_Signature, {}, 0x_0000_0000},
},
},
{
"xs2n0g01", // signature byte 2 is a 'Q'
{
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
{Default, .Invalid_Signature, {}, 0x_0000_0000},
},
},
{
"xs4n0g01", // signature byte 4 lowercase
{
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
{Default, .Invalid_Signature, {}, 0x_0000_0000},
},
},
{
"xs7n0g01", // 7th byte a space instead of control-Z
{
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
{Default, .Invalid_Signature, {}, 0x_0000_0000},
},
},
{
"xcrn0g04", // added cr bytes
{
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
{Default, .Invalid_Signature, {}, 0x_0000_0000},
},
},
{
"xlfn0g04", // added lf bytes
{
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
{Default, .Invalid_Signature, {}, 0x_0000_0000},
},
},
{