mirror of
https://github.com/Ed94/Odin.git
synced 2026-07-29 19:00:06 +00:00
[pbm] WIP unit tests.
This commit is contained in:
@@ -14,6 +14,7 @@ destroy :: proc(img: ^image.Image) -> bool {
|
|||||||
header_destroy(&info.header)
|
header_destroy(&info.header)
|
||||||
free(info)
|
free(info)
|
||||||
img.metadata = nil
|
img.metadata = nil
|
||||||
|
free(img)
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
+111
-27
@@ -31,19 +31,19 @@ load :: proc {
|
|||||||
load_from_buffer,
|
load_from_buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
load_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
|
context.allocator = allocator
|
||||||
|
|
||||||
data, ok := os.read_entire_file(filename); defer delete(data)
|
data, ok := os.read_entire_file(filename); defer delete(data)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = .File_Not_Readable
|
err = .Unable_To_Read_File
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return read_from_buffer(data)
|
return load_from_buffer(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
load_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
|
context.allocator = allocator
|
||||||
|
|
||||||
header: Header; defer header_destroy(&header)
|
header: Header; defer header_destroy(&header)
|
||||||
@@ -51,7 +51,9 @@ load_from_buffer :: proc(data: []byte, allocator := context.allocator) -> (img:
|
|||||||
header, header_size = parse_header(data) or_return
|
header, header_size = parse_header(data) or_return
|
||||||
|
|
||||||
img_data := data[header_size:]
|
img_data := data[header_size:]
|
||||||
img = decode_image(header, img_data) or_return
|
|
||||||
|
img = new(Image)
|
||||||
|
decode_image(img, header, img_data) or_return
|
||||||
|
|
||||||
info := new(Info)
|
info := new(Info)
|
||||||
info.header = header
|
info.header = header
|
||||||
@@ -69,27 +71,42 @@ save :: proc {
|
|||||||
save_to_buffer,
|
save_to_buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
save_to_file :: proc(filename: string, img: Image, allocator := context.allocator) -> (err: Error) {
|
save_to_file :: proc(filename: string, img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
data: []byte; defer delete(data)
|
data: []byte; defer delete(data)
|
||||||
data = write_to_buffer(img) or_return
|
data = save_to_buffer(img, custom_info) or_return
|
||||||
|
|
||||||
if ok := os.write_entire_file(filename, data); !ok {
|
if ok := os.write_entire_file(filename, data); !ok {
|
||||||
return .File_Not_Writable
|
return .Unable_To_Write_File
|
||||||
}
|
}
|
||||||
|
|
||||||
return Format_Error.None
|
return Format_Error.None
|
||||||
}
|
}
|
||||||
|
|
||||||
save_to_buffer :: proc(img: Image, allocator := context.allocator) -> (buffer: []byte, err: Error) {
|
save_to_buffer :: proc(img: ^Image, custom_info: Info = {}, allocator := context.allocator) -> (buffer: []byte, err: Error) {
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
info, ok := img.metadata.(^image.Netpbm_Info)
|
info: Info = {}
|
||||||
if !ok {
|
if custom_info.header.width > 0 {
|
||||||
err = image.General_Image_Error.Invalid_Input_Image
|
// Custom info has been set, use it.
|
||||||
return
|
info = custom_info
|
||||||
|
} else {
|
||||||
|
img_info, ok := img.metadata.(^image.Netpbm_Info)
|
||||||
|
if !ok {
|
||||||
|
// image doesn't have .Netpbm info, guess it
|
||||||
|
auto_info, auto_info_found := autoselect_pbm_format_from_image(img)
|
||||||
|
if auto_info_found {
|
||||||
|
info = auto_info
|
||||||
|
} else {
|
||||||
|
return {}, .Invalid_Input_Image
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// use info as stored on image
|
||||||
|
info = img_info^
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// using info so we can just talk about the header
|
// using info so we can just talk about the header
|
||||||
using info
|
using info
|
||||||
|
|
||||||
@@ -103,11 +120,11 @@ save_to_buffer :: proc(img: Image, allocator := context.allocator) -> (buffer: [
|
|||||||
if header.format in (PNM + PAM) {
|
if header.format in (PNM + PAM) {
|
||||||
if header.maxval <= int(max(u8)) && img.depth != 8 \
|
if header.maxval <= int(max(u8)) && img.depth != 8 \
|
||||||
|| header.maxval > int(max(u8)) && header.maxval <= int(max(u16)) && img.depth != 16 {
|
|| header.maxval > int(max(u8)) && header.maxval <= int(max(u16)) && img.depth != 16 {
|
||||||
err = Format_Error.Invalid_Image_Depth
|
err = .Invalid_Image_Depth
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if header.format in PFM && img.depth != 32 {
|
} else if header.format in PFM && img.depth != 32 {
|
||||||
err = Format_Error.Invalid_Image_Depth
|
err = .Invalid_Image_Depth
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,11 +250,11 @@ save_to_buffer :: proc(img: Image, allocator := context.allocator) -> (buffer: [
|
|||||||
}
|
}
|
||||||
|
|
||||||
case:
|
case:
|
||||||
return data.buf[:], Format_Error.Invalid_Image_Depth
|
return data.buf[:], .Invalid_Image_Depth
|
||||||
}
|
}
|
||||||
|
|
||||||
case:
|
case:
|
||||||
return data.buf[:], Format_Error.Invalid_Format
|
return data.buf[:], .Invalid_Format
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.buf[:], Format_Error.None
|
return data.buf[:], Format_Error.None
|
||||||
@@ -263,7 +280,7 @@ parse_header :: proc(data: []byte, allocator := context.allocator) -> (header: H
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = Format_Error.Invalid_Signature
|
err = .Invalid_Signature
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +383,7 @@ _parse_header_pam :: proc(data: []byte, allocator := context.allocator) -> (head
|
|||||||
|
|
||||||
// the spec needs the newline apparently
|
// the spec needs the newline apparently
|
||||||
if string(data[0:3]) != "P7\n" {
|
if string(data[0:3]) != "P7\n" {
|
||||||
err = Format_Error.Invalid_Signature
|
err = .Invalid_Signature
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
header.format = .P7
|
header.format = .P7
|
||||||
@@ -468,7 +485,7 @@ _parse_header_pfm :: proc(data: []byte) -> (header: Header, length: int, err: Er
|
|||||||
header.format = .PF
|
header.format = .PF
|
||||||
header.channels = 3
|
header.channels = 3
|
||||||
case:
|
case:
|
||||||
err = Format_Error.Invalid_Signature
|
err = .Invalid_Signature
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,18 +548,18 @@ _parse_header_pfm :: proc(data: []byte) -> (header: Header, length: int, err: Er
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
decode_image :: proc(header: Header, data: []byte, allocator := context.allocator) -> (img: Image, err: Error) {
|
decode_image :: proc(img: ^Image, header: Header, data: []byte, allocator := context.allocator) -> (err: Error) {
|
||||||
|
assert(img != nil)
|
||||||
context.allocator = allocator
|
context.allocator = allocator
|
||||||
|
|
||||||
img = Image {
|
img.width = header.width
|
||||||
width = header.width,
|
img.height = header.height
|
||||||
height = header.height,
|
img.channels = header.channels
|
||||||
channels = header.channels,
|
img.depth = header.depth
|
||||||
depth = header.depth,
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer_size := image.compute_buffer_size(img.width, img.height, img.channels, img.depth)
|
buffer_size := image.compute_buffer_size(img.width, img.height, img.channels, img.depth)
|
||||||
|
|
||||||
|
when false {
|
||||||
// we can check data size for binary formats
|
// we can check data size for binary formats
|
||||||
if header.format in BINARY {
|
if header.format in BINARY {
|
||||||
if header.format == .P4 {
|
if header.format == .P4 {
|
||||||
@@ -558,6 +575,7 @@ decode_image :: proc(header: Header, data: []byte, allocator := context.allocato
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// for ASCII and P4, we use length for the termination condition, so start at 0
|
// for ASCII and P4, we use length for the termination condition, so start at 0
|
||||||
// BINARY will be a simple memcopy so the buffer length should also be initialised
|
// BINARY will be a simple memcopy so the buffer length should also be initialised
|
||||||
@@ -665,4 +683,70 @@ decode_image :: proc(header: Header, data: []byte, allocator := context.allocato
|
|||||||
|
|
||||||
err = Format_Error.None
|
err = Format_Error.None
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically try to select an appropriate format to save to based on `img.channel` and `img.depth`
|
||||||
|
autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, force_black_and_white := false, pfm_scale := f32(1.0)) -> (res: Info, ok: bool) {
|
||||||
|
/*
|
||||||
|
PBM (P1, P4): Portable Bit Map, stores black and white images (1 channel)
|
||||||
|
PGM (P2, P5): Portable Gray Map, stores greyscale images (1 channel, 1 or 2 bytes per value)
|
||||||
|
PPM (P3, P6): Portable Pixel Map, stores colour images (3 channel, 1 or 2 bytes per value)
|
||||||
|
PAM (P7 ): Portable Arbitrary Map, stores arbitrary channel images (1 or 2 bytes per value)
|
||||||
|
PFM (Pf, PF): Portable Float Map, stores floating-point images (Pf: 1 channel, PF: 3 channel)
|
||||||
|
|
||||||
|
ASCII :: Formats{.P1, .P2, .P3}
|
||||||
|
*/
|
||||||
|
using res.header
|
||||||
|
|
||||||
|
width = img.width
|
||||||
|
height = img.height
|
||||||
|
channels = img.channels
|
||||||
|
depth = img.depth
|
||||||
|
maxval = 255 if img.depth == 8 else 65535
|
||||||
|
little_endian = true if ODIN_ENDIAN == .Little else false
|
||||||
|
|
||||||
|
// Assume we'll find a suitable format
|
||||||
|
ok = true
|
||||||
|
|
||||||
|
switch img.channels {
|
||||||
|
case 1:
|
||||||
|
// Must be Portable Float Map
|
||||||
|
if img.depth == 32 {
|
||||||
|
format = .Pf
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if force_black_and_white {
|
||||||
|
// Portable Bit Map
|
||||||
|
format = .P4 if prefer_binary else .P1
|
||||||
|
maxval = 1
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
// Portable Gray Map
|
||||||
|
format = .P5 if prefer_binary else .P2
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
// Must be Portable Float Map
|
||||||
|
if img.depth == 32 {
|
||||||
|
format = .PF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Portable Pixel Map
|
||||||
|
format = .P6 if prefer_binary else .P3
|
||||||
|
return
|
||||||
|
|
||||||
|
case:
|
||||||
|
// Portable Arbitrary Map
|
||||||
|
if img.depth == 8 || img.depth == 16 {
|
||||||
|
format = .P7
|
||||||
|
scale = pfm_scale
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We couldn't find a suitable format
|
||||||
|
return {}, false
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,7 @@ import "core:testing"
|
|||||||
|
|
||||||
import "core:compress"
|
import "core:compress"
|
||||||
import "core:image"
|
import "core:image"
|
||||||
|
import pbm "core:image/netpbm"
|
||||||
import "core:image/png"
|
import "core:image/png"
|
||||||
import "core:image/qoi"
|
import "core:image/qoi"
|
||||||
|
|
||||||
@@ -1506,26 +1507,53 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
|
|||||||
|
|
||||||
passed &= test.hash == png_hash
|
passed &= test.hash == png_hash
|
||||||
|
|
||||||
// Roundtrip through QOI to test the QOI encoder and decoder.
|
if passed {
|
||||||
if passed && img.depth == 8 && (img.channels == 3 || img.channels == 4) {
|
// Roundtrip through QOI to test the QOI encoder and decoder.
|
||||||
qoi_buffer: bytes.Buffer
|
if img.depth == 8 && (img.channels == 3 || img.channels == 4) {
|
||||||
defer bytes.buffer_destroy(&qoi_buffer)
|
qoi_buffer: bytes.Buffer
|
||||||
qoi_save_err := qoi.save(&qoi_buffer, img)
|
defer bytes.buffer_destroy(&qoi_buffer)
|
||||||
|
qoi_save_err := qoi.save(&qoi_buffer, img)
|
||||||
|
|
||||||
error = fmt.tprintf("%v test %v QOI save failed with %v.", file.file, count, qoi_save_err)
|
error = fmt.tprintf("%v test %v QOI save failed with %v.", file.file, count, qoi_save_err)
|
||||||
expect(t, qoi_save_err == nil, error)
|
expect(t, qoi_save_err == nil, error)
|
||||||
|
|
||||||
if qoi_save_err == nil {
|
if qoi_save_err == nil {
|
||||||
qoi_img, qoi_load_err := qoi.load(qoi_buffer.buf[:])
|
qoi_img, qoi_load_err := qoi.load(qoi_buffer.buf[:])
|
||||||
defer qoi.destroy(qoi_img)
|
defer qoi.destroy(qoi_img)
|
||||||
|
|
||||||
error = fmt.tprintf("%v test %v QOI load failed with %v.", file.file, count, qoi_load_err)
|
error = fmt.tprintf("%v test %v QOI load failed with %v.", file.file, count, qoi_load_err)
|
||||||
expect(t, qoi_load_err == nil, error)
|
expect(t, qoi_load_err == nil, error)
|
||||||
|
|
||||||
qoi_hash := hash.crc32(qoi_img.pixels.buf[:])
|
qoi_hash := hash.crc32(qoi_img.pixels.buf[:])
|
||||||
error = fmt.tprintf("%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options)
|
error = fmt.tprintf("%v test %v QOI load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, qoi_hash, png_hash, test.options)
|
||||||
expect(t, qoi_hash == png_hash, error)
|
expect(t, qoi_hash == png_hash, error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Roundtrip through PBM to test the PBM encoders and decoders - prefer binary
|
||||||
|
pbm_buf, pbm_save_err := pbm.save_to_buffer(img)
|
||||||
|
defer delete(pbm_buf)
|
||||||
|
|
||||||
|
error = fmt.tprintf("%v test %v PBM save failed with %v.", file.file, count, pbm_save_err)
|
||||||
|
expect(t, pbm_save_err == nil, error)
|
||||||
|
|
||||||
|
if pbm_save_err == nil {
|
||||||
|
// Try to load it again.
|
||||||
|
pbm_img, pbm_load_err := pbm.load(pbm_buf)
|
||||||
|
defer pbm.destroy(pbm_img)
|
||||||
|
|
||||||
|
if pbm_load_err == nil {
|
||||||
|
fmt.printf("%v test %v PBM load worked with %v.\n", file.file, count, pbm_load_err)
|
||||||
|
}
|
||||||
|
error = fmt.tprintf("%v test %v PBM load failed with %v.", file.file, count, pbm_load_err)
|
||||||
|
expect(t, pbm_load_err == nil, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roundtrip through PBM to test the PBM encoders and decoders - prefer ASCII
|
||||||
|
// pbm_info, pbm_format_selected = pbm.autoselect_pbm_format_from_image(img, false)
|
||||||
|
// fmt.printf("Autoselect PBM: %v (%v)\n", pbm_info, pbm_format_selected)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if .return_metadata in test.options {
|
if .return_metadata in test.options {
|
||||||
|
|||||||
Reference in New Issue
Block a user