mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-06 07:38:48 +00:00
Rebased.
This commit is contained in:
@@ -0,0 +1,652 @@
|
|||||||
|
// package bmp implements a Microsoft BMP image reader
|
||||||
|
package core_image_bmp
|
||||||
|
|
||||||
|
import "core:image"
|
||||||
|
import "core:bytes"
|
||||||
|
import "core:compress"
|
||||||
|
import "core:mem"
|
||||||
|
import "base:intrinsics"
|
||||||
|
import "base:runtime"
|
||||||
|
@(require) import "core:fmt"
|
||||||
|
|
||||||
|
Error :: image.Error
|
||||||
|
Image :: image.Image
|
||||||
|
Options :: image.Options
|
||||||
|
|
||||||
|
RGB_Pixel :: image.RGB_Pixel
|
||||||
|
RGBA_Pixel :: image.RGBA_Pixel
|
||||||
|
|
||||||
|
FILE_HEADER_SIZE :: 14
|
||||||
|
INFO_STUB_SIZE :: FILE_HEADER_SIZE + size_of(image.BMP_Version)
|
||||||
|
|
||||||
|
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
|
ctx := &compress.Context_Memory_Input{
|
||||||
|
input_data = data,
|
||||||
|
}
|
||||||
|
|
||||||
|
img, err = load_from_context(ctx, options, allocator)
|
||||||
|
return img, err
|
||||||
|
}
|
||||||
|
|
||||||
|
@(optimization_mode="speed")
|
||||||
|
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
options := options
|
||||||
|
|
||||||
|
// For compress.read_slice(), until that's rewritten to not use temp allocator
|
||||||
|
runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD()
|
||||||
|
|
||||||
|
if .info in options {
|
||||||
|
options |= {.return_metadata, .do_not_decompress_image}
|
||||||
|
options -= {.info}
|
||||||
|
}
|
||||||
|
|
||||||
|
if .return_header in options && .return_metadata in options {
|
||||||
|
options -= {.return_header}
|
||||||
|
}
|
||||||
|
|
||||||
|
info_buf: [size_of(image.BMP_Header)]u8
|
||||||
|
|
||||||
|
// Read file header (14) + info size (4)
|
||||||
|
stub_data := compress.read_slice(ctx, INFO_STUB_SIZE) or_return
|
||||||
|
copy(info_buf[:], stub_data[:])
|
||||||
|
stub_info := transmute(image.BMP_Header)info_buf
|
||||||
|
|
||||||
|
if stub_info.magic != .Bitmap {
|
||||||
|
for v in image.BMP_Magic {
|
||||||
|
if stub_info.magic == v {
|
||||||
|
return img, .Unsupported_OS2_File
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return img, .Invalid_Signature
|
||||||
|
}
|
||||||
|
|
||||||
|
info: image.BMP_Header
|
||||||
|
switch stub_info.info_size {
|
||||||
|
case .OS2_v1:
|
||||||
|
// Read the remainder of the header
|
||||||
|
os2_data := compress.read_data(ctx, image.OS2_Header) or_return
|
||||||
|
|
||||||
|
info = transmute(image.BMP_Header)info_buf
|
||||||
|
info.width = i32le(os2_data.width)
|
||||||
|
info.height = i32le(os2_data.height)
|
||||||
|
info.planes = os2_data.planes
|
||||||
|
info.bpp = os2_data.bpp
|
||||||
|
|
||||||
|
switch info.bpp {
|
||||||
|
case 1, 4, 8, 24:
|
||||||
|
case:
|
||||||
|
return img, .Unsupported_BPP
|
||||||
|
}
|
||||||
|
|
||||||
|
case .ABBR_16 ..= .V5:
|
||||||
|
// Sizes include V3, V4, V5 and OS2v2 outright, but can also handle truncated headers.
|
||||||
|
// Sometimes called BITMAPV2INFOHEADER or BITMAPV3INFOHEADER.
|
||||||
|
// Let's just try to process it.
|
||||||
|
|
||||||
|
to_read := int(stub_info.info_size) - size_of(image.BMP_Version)
|
||||||
|
info_data := compress.read_slice(ctx, to_read) or_return
|
||||||
|
copy(info_buf[INFO_STUB_SIZE:], info_data[:])
|
||||||
|
|
||||||
|
// Update info struct with the rest of the data we read
|
||||||
|
info = transmute(image.BMP_Header)info_buf
|
||||||
|
|
||||||
|
case:
|
||||||
|
return img, .Unsupported_BMP_Version
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO(Jeroen): Add a "strict" option to catch these non-issues that violate spec?
|
||||||
|
if info.planes != 1 {
|
||||||
|
return img, .Invalid_Planes_Value
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
if img == nil {
|
||||||
|
img = new(Image)
|
||||||
|
}
|
||||||
|
img.which = .BMP
|
||||||
|
|
||||||
|
img.metadata = new_clone(image.BMP_Info{
|
||||||
|
info = info,
|
||||||
|
})
|
||||||
|
|
||||||
|
img.width = abs(int(info.width))
|
||||||
|
img.height = abs(int(info.height))
|
||||||
|
img.channels = 3
|
||||||
|
img.depth = 8
|
||||||
|
|
||||||
|
if img.width == 0 || img.height == 0 {
|
||||||
|
return img, .Invalid_Image_Dimensions
|
||||||
|
}
|
||||||
|
|
||||||
|
total_pixels := abs(img.width * img.height)
|
||||||
|
if total_pixels > image.MAX_DIMENSIONS {
|
||||||
|
return img, .Image_Dimensions_Too_Large
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Jeroen): Handle RGBA.
|
||||||
|
switch info.compression {
|
||||||
|
case .Bit_Fields, .Alpha_Bit_Fields:
|
||||||
|
switch info.bpp {
|
||||||
|
case 16, 32:
|
||||||
|
make_output(img, allocator) or_return
|
||||||
|
decode_rgb(ctx, img, info, allocator) or_return
|
||||||
|
case:
|
||||||
|
if is_os2(info.info_size) {
|
||||||
|
return img, .Unsupported_Compression
|
||||||
|
}
|
||||||
|
return img, .Unsupported_BPP
|
||||||
|
}
|
||||||
|
case .RGB:
|
||||||
|
make_output(img, allocator) or_return
|
||||||
|
decode_rgb(ctx, img, info, allocator) or_return
|
||||||
|
case .RLE4, .RLE8:
|
||||||
|
make_output(img, allocator) or_return
|
||||||
|
decode_rle(ctx, img, info, allocator) or_return
|
||||||
|
case .CMYK, .CMYK_RLE4, .CMYK_RLE8: fallthrough
|
||||||
|
case .PNG, .JPEG: fallthrough
|
||||||
|
case: return img, .Unsupported_Compression
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flipped vertically
|
||||||
|
if info.height < 0 {
|
||||||
|
pixels := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
|
||||||
|
for y in 0..<img.height / 2 {
|
||||||
|
for x in 0..<img.width {
|
||||||
|
top := y * img.width + x
|
||||||
|
bot := (img.height - y - 1) * img.width + x
|
||||||
|
|
||||||
|
pixels[top], pixels[bot] = pixels[bot], pixels[top]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
is_os2 :: proc(version: image.BMP_Version) -> (res: bool) {
|
||||||
|
#partial switch version {
|
||||||
|
case .OS2_v1, .OS2_v2: return true
|
||||||
|
case: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
make_output :: proc(img: ^Image, allocator := context.allocator) -> (err: Error) {
|
||||||
|
assert(img != nil)
|
||||||
|
bytes_needed := img.channels * img.height * img.width
|
||||||
|
img.pixels.buf = make([dynamic]u8, bytes_needed, allocator)
|
||||||
|
if len(img.pixels.buf) != bytes_needed {
|
||||||
|
return .Unable_To_Allocate_Or_Resize
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
write :: proc(img: ^Image, x, y: int, pix: RGB_Pixel) -> (err: Error) {
|
||||||
|
if y >= img.height || x >= img.width {
|
||||||
|
return .Corrupt
|
||||||
|
}
|
||||||
|
out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
|
||||||
|
assert(img.height >= 1 && img.width >= 1)
|
||||||
|
out[(img.height - y - 1) * img.width + x] = pix
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmask :: struct {
|
||||||
|
mask: [4]u32le `fmt:"b"`,
|
||||||
|
shift: [4]u32le,
|
||||||
|
bits: [4]u32le,
|
||||||
|
}
|
||||||
|
|
||||||
|
read_or_make_bit_masks :: proc(ctx: ^$C, info: image.BMP_Header) -> (res: Bitmask, read: int, err: Error) {
|
||||||
|
ctz :: intrinsics.count_trailing_zeros
|
||||||
|
c1s :: intrinsics.count_ones
|
||||||
|
|
||||||
|
#partial switch info.compression {
|
||||||
|
case .RGB:
|
||||||
|
switch info.bpp {
|
||||||
|
case 16:
|
||||||
|
return {
|
||||||
|
mask = {31 << 10, 31 << 5, 31, 0},
|
||||||
|
shift = { 10, 5, 0, 0},
|
||||||
|
bits = { 5, 5, 5, 0},
|
||||||
|
}, int(4 * info.colors_used), nil
|
||||||
|
|
||||||
|
case 32:
|
||||||
|
return {
|
||||||
|
mask = {255 << 16, 255 << 8, 255, 255 << 24},
|
||||||
|
shift = { 16, 8, 0, 24},
|
||||||
|
bits = { 8, 8, 8, 8},
|
||||||
|
}, int(4 * info.colors_used), nil
|
||||||
|
|
||||||
|
case: return {}, 0, .Unsupported_BPP
|
||||||
|
}
|
||||||
|
case .Bit_Fields, .Alpha_Bit_Fields:
|
||||||
|
bf := info.masks
|
||||||
|
alpha_mask := false
|
||||||
|
bit_count: u32le
|
||||||
|
|
||||||
|
#partial switch info.info_size {
|
||||||
|
case .ABBR_52 ..= .V5:
|
||||||
|
// All possible BMP header sizes 52+ bytes long, includes V4 + V5
|
||||||
|
// Bit fields were read as part of the header
|
||||||
|
// V3 header is 40 bytes. We need 56 at a minimum for RGBA bit fields in the next section.
|
||||||
|
if info.info_size >= .ABBR_56 {
|
||||||
|
alpha_mask = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case .V3:
|
||||||
|
// Version 3 doesn't have a bit field embedded, but can still have a 3 or 4 color bit field.
|
||||||
|
// Because it wasn't read as part of the header, we need to read it now.
|
||||||
|
|
||||||
|
if info.compression == .Alpha_Bit_Fields {
|
||||||
|
bf = compress.read_data(ctx, [4]u32le) or_return
|
||||||
|
alpha_mask = true
|
||||||
|
read = 16
|
||||||
|
} else {
|
||||||
|
bf.xyz = compress.read_data(ctx, [3]u32le) or_return
|
||||||
|
read = 12
|
||||||
|
}
|
||||||
|
|
||||||
|
case:
|
||||||
|
// Bit fields are unhandled for this BMP version
|
||||||
|
return {}, 0, .Bitfield_Version_Unhandled
|
||||||
|
}
|
||||||
|
|
||||||
|
if alpha_mask {
|
||||||
|
res = {
|
||||||
|
mask = {bf.r, bf.g, bf.b, bf.a},
|
||||||
|
shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), ctz(bf.a)},
|
||||||
|
bits = {c1s(bf.r), c1s(bf.g), c1s(bf.b), c1s(bf.a)},
|
||||||
|
}
|
||||||
|
|
||||||
|
bit_count = res.bits.r + res.bits.g + res.bits.b + res.bits.a
|
||||||
|
} else {
|
||||||
|
res = {
|
||||||
|
mask = {bf.r, bf.g, bf.b, 0},
|
||||||
|
shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), 0},
|
||||||
|
bits = {c1s(bf.r), c1s(bf.g), c1s(bf.b), 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
bit_count = res.bits.r + res.bits.g + res.bits.b
|
||||||
|
}
|
||||||
|
|
||||||
|
if bit_count > u32le(info.bpp) {
|
||||||
|
err = .Bitfield_Sum_Exceeds_BPP
|
||||||
|
}
|
||||||
|
|
||||||
|
overlapped := res.mask.r | res.mask.g | res.mask.b | res.mask.a
|
||||||
|
if c1s(overlapped) < bit_count {
|
||||||
|
err = .Bitfield_Overlapped
|
||||||
|
}
|
||||||
|
return res, read, err
|
||||||
|
|
||||||
|
case:
|
||||||
|
return {}, 0, .Unsupported_Compression
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scale :: proc(val: $T, mask, shift, bits: u32le) -> (res: u8) {
|
||||||
|
if bits == 0 { return 0 } // Guard against malformed bit fields
|
||||||
|
v := (u32le(val) & mask) >> shift
|
||||||
|
mask_in := u32le(1 << bits) - 1
|
||||||
|
return u8(v * 255 / mask_in)
|
||||||
|
}
|
||||||
|
|
||||||
|
decode_rgb :: proc(ctx: ^$C, img: ^Image, info: image.BMP_Header, allocator := context.allocator) -> (err: Error) {
|
||||||
|
pixel_offset := int(info.pixel_offset)
|
||||||
|
pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE
|
||||||
|
|
||||||
|
palette: [256]RGBA_Pixel
|
||||||
|
|
||||||
|
// Palette size is info.colors_used if populated. If not it's min(1 << bpp, offset to the pixels / channel count)
|
||||||
|
colors_used := min(256, 1 << info.bpp if info.colors_used == 0 else info.colors_used)
|
||||||
|
max_colors := pixel_offset / 3 if info.info_size == .OS2_v1 else pixel_offset / 4
|
||||||
|
colors_used = min(colors_used, u32le(max_colors))
|
||||||
|
|
||||||
|
switch info.bpp {
|
||||||
|
case 1:
|
||||||
|
if info.info_size == .OS2_v1 {
|
||||||
|
// 2 x RGB palette of instead of variable RGBA palette
|
||||||
|
for i in 0..<colors_used {
|
||||||
|
palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return
|
||||||
|
}
|
||||||
|
pixel_offset -= int(3 * colors_used)
|
||||||
|
} else {
|
||||||
|
for i in 0..<colors_used {
|
||||||
|
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
|
||||||
|
}
|
||||||
|
pixel_offset -= int(4 * colors_used)
|
||||||
|
}
|
||||||
|
skip_space(ctx, pixel_offset)
|
||||||
|
|
||||||
|
stride := (img.width + 7) / 8
|
||||||
|
for y in 0..<img.height {
|
||||||
|
data := compress.read_slice(ctx, stride) or_return
|
||||||
|
for x in 0..<img.width {
|
||||||
|
shift := u8(7 - (x & 0x07))
|
||||||
|
p := (data[x / 8] >> shift) & 0x01
|
||||||
|
write(img, x, y, palette[p].bgr) or_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: // Non-standard on modern Windows, but was allowed on WinCE
|
||||||
|
for i in 0..<colors_used {
|
||||||
|
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
|
||||||
|
}
|
||||||
|
pixel_offset -= int(4 * colors_used)
|
||||||
|
skip_space(ctx, pixel_offset)
|
||||||
|
|
||||||
|
stride := (img.width + 3) / 4
|
||||||
|
for y in 0..<img.height {
|
||||||
|
data := compress.read_slice(ctx, stride) or_return
|
||||||
|
for x in 0..<img.width {
|
||||||
|
shift := 6 - (x & 0x03) << 1
|
||||||
|
p := (data[x / 4] >> u8(shift)) & 0x03
|
||||||
|
write(img, x, y, palette[p].bgr) or_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
if info.info_size == .OS2_v1 {
|
||||||
|
// 16 x RGB palette of instead of variable RGBA palette
|
||||||
|
for i in 0..<colors_used {
|
||||||
|
palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return
|
||||||
|
}
|
||||||
|
pixel_offset -= int(3 * colors_used)
|
||||||
|
} else {
|
||||||
|
for i in 0..<colors_used {
|
||||||
|
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
|
||||||
|
}
|
||||||
|
pixel_offset -= int(4 * colors_used)
|
||||||
|
}
|
||||||
|
skip_space(ctx, pixel_offset)
|
||||||
|
|
||||||
|
stride := (img.width + 1) / 2
|
||||||
|
for y in 0..<img.height {
|
||||||
|
data := compress.read_slice(ctx, stride) or_return
|
||||||
|
for x in 0..<img.width {
|
||||||
|
p := data[x / 2] >> 4 if x & 1 == 0 else data[x / 2]
|
||||||
|
write(img, x, y, palette[p & 0x0f].bgr) or_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
if info.info_size == .OS2_v1 {
|
||||||
|
// 256 x RGB palette of instead of variable RGBA palette
|
||||||
|
for i in 0..<colors_used {
|
||||||
|
palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return
|
||||||
|
}
|
||||||
|
pixel_offset -= int(3 * colors_used)
|
||||||
|
} else {
|
||||||
|
for i in 0..<colors_used {
|
||||||
|
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
|
||||||
|
}
|
||||||
|
pixel_offset -= int(4 * colors_used)
|
||||||
|
}
|
||||||
|
skip_space(ctx, pixel_offset)
|
||||||
|
|
||||||
|
stride := align4(img.width)
|
||||||
|
for y in 0..<img.height {
|
||||||
|
data := compress.read_slice(ctx, stride) or_return
|
||||||
|
for x in 0..<img.width {
|
||||||
|
write(img, x, y, palette[data[x]].bgr) or_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 16:
|
||||||
|
bm, read := read_or_make_bit_masks(ctx, info) or_return
|
||||||
|
// Skip optional palette and other data
|
||||||
|
pixel_offset -= read
|
||||||
|
skip_space(ctx, pixel_offset)
|
||||||
|
|
||||||
|
stride := align4(img.width * 2)
|
||||||
|
for y in 0..<img.height {
|
||||||
|
data := compress.read_slice(ctx, stride) or_return
|
||||||
|
pixels := mem.slice_data_cast([]u16le, data)
|
||||||
|
for x in 0..<img.width {
|
||||||
|
v := pixels[x]
|
||||||
|
r := scale(v, bm.mask.r, bm.shift.r, bm.bits.r)
|
||||||
|
g := scale(v, bm.mask.g, bm.shift.g, bm.bits.g)
|
||||||
|
b := scale(v, bm.mask.b, bm.shift.b, bm.bits.b)
|
||||||
|
write(img, x, y, RGB_Pixel{r, g, b}) or_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 24:
|
||||||
|
// Eat useless palette and other padding
|
||||||
|
skip_space(ctx, pixel_offset)
|
||||||
|
|
||||||
|
stride := align4(img.width * 3)
|
||||||
|
for y in 0..<img.height {
|
||||||
|
data := compress.read_slice(ctx, stride) or_return
|
||||||
|
pixels := mem.slice_data_cast([]RGB_Pixel, data)
|
||||||
|
for x in 0..<img.width {
|
||||||
|
write(img, x, y, pixels[x].bgr) or_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 32:
|
||||||
|
bm, read := read_or_make_bit_masks(ctx, info) or_return
|
||||||
|
// Skip optional palette and other data
|
||||||
|
pixel_offset -= read
|
||||||
|
skip_space(ctx, pixel_offset)
|
||||||
|
|
||||||
|
for y in 0..<img.height {
|
||||||
|
data := compress.read_slice(ctx, img.width * size_of(RGBA_Pixel)) or_return
|
||||||
|
pixels := mem.slice_data_cast([]u32le, data)
|
||||||
|
for x in 0..<img.width {
|
||||||
|
v := pixels[x]
|
||||||
|
r := scale(v, bm.mask.r, bm.shift.r, bm.bits.r)
|
||||||
|
g := scale(v, bm.mask.g, bm.shift.g, bm.bits.g)
|
||||||
|
b := scale(v, bm.mask.b, bm.shift.b, bm.bits.b)
|
||||||
|
write(img, x, y, RGB_Pixel{r, g, b}) or_return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case:
|
||||||
|
return .Unsupported_BPP
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
decode_rle :: proc(ctx: ^$C, img: ^Image, info: image.BMP_Header, allocator := context.allocator) -> (err: Error) {
|
||||||
|
pixel_offset := int(info.pixel_offset)
|
||||||
|
pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE
|
||||||
|
|
||||||
|
bytes_needed := size_of(RGB_Pixel) * img.height * img.width
|
||||||
|
if resize(&img.pixels.buf, bytes_needed) != nil {
|
||||||
|
return .Unable_To_Allocate_Or_Resize
|
||||||
|
}
|
||||||
|
out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
|
||||||
|
assert(len(out) == img.height * img.width)
|
||||||
|
|
||||||
|
palette: [256]RGBA_Pixel
|
||||||
|
|
||||||
|
switch info.bpp {
|
||||||
|
case 4:
|
||||||
|
colors_used := info.colors_used if info.colors_used > 0 else 16
|
||||||
|
colors_used = min(colors_used, 16)
|
||||||
|
|
||||||
|
for i in 0..<colors_used {
|
||||||
|
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
|
||||||
|
pixel_offset -= size_of(RGBA_Pixel)
|
||||||
|
}
|
||||||
|
skip_space(ctx, pixel_offset)
|
||||||
|
|
||||||
|
pixel_size := info.size - info.pixel_offset
|
||||||
|
remaining := compress.input_size(ctx) or_return
|
||||||
|
if remaining < i64(pixel_size) {
|
||||||
|
return .Corrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]u8, int(pixel_size) + 4)
|
||||||
|
defer delete(data)
|
||||||
|
|
||||||
|
for i in 0..<pixel_size {
|
||||||
|
data[i] = image.read_u8(ctx) or_return
|
||||||
|
}
|
||||||
|
|
||||||
|
y, x := 0, 0
|
||||||
|
index := 0
|
||||||
|
for {
|
||||||
|
if len(data[index:]) < 2 {
|
||||||
|
return .Corrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[index] > 0 {
|
||||||
|
for count in 0..<data[index] {
|
||||||
|
if count & 1 == 1 {
|
||||||
|
write(img, x, y, palette[(data[index + 1] >> 0) & 0x0f].bgr)
|
||||||
|
} else {
|
||||||
|
write(img, x, y, palette[(data[index + 1] >> 4) & 0x0f].bgr)
|
||||||
|
}
|
||||||
|
x += 1
|
||||||
|
}
|
||||||
|
index += 2
|
||||||
|
} else {
|
||||||
|
switch data[index + 1] {
|
||||||
|
case 0: // EOL
|
||||||
|
x = 0; y += 1
|
||||||
|
index += 2
|
||||||
|
case 1: // EOB
|
||||||
|
return
|
||||||
|
case 2: // MOVE
|
||||||
|
x += int(data[index + 2])
|
||||||
|
y += int(data[index + 3])
|
||||||
|
index += 4
|
||||||
|
case: // Literals
|
||||||
|
run_length := int(data[index + 1])
|
||||||
|
aligned := (align4(run_length) >> 1) + 2
|
||||||
|
|
||||||
|
if index + aligned >= len(data) {
|
||||||
|
return .Corrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
for count in 0..<run_length {
|
||||||
|
val := data[index + 2 + count / 2]
|
||||||
|
if count & 1 == 1 {
|
||||||
|
val &= 0xf
|
||||||
|
} else {
|
||||||
|
val = val >> 4
|
||||||
|
}
|
||||||
|
write(img, x, y, palette[val].bgr)
|
||||||
|
x += 1
|
||||||
|
}
|
||||||
|
index += aligned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
colors_used := info.colors_used if info.colors_used > 0 else 256
|
||||||
|
colors_used = min(colors_used, 256)
|
||||||
|
|
||||||
|
for i in 0..<colors_used {
|
||||||
|
palette[i] = image.read_data(ctx, RGBA_Pixel) or_return
|
||||||
|
pixel_offset -= size_of(RGBA_Pixel)
|
||||||
|
}
|
||||||
|
skip_space(ctx, pixel_offset)
|
||||||
|
|
||||||
|
pixel_size := info.size - info.pixel_offset
|
||||||
|
remaining := compress.input_size(ctx) or_return
|
||||||
|
if remaining < i64(pixel_size) {
|
||||||
|
return .Corrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
data := make([]u8, int(pixel_size) + 4)
|
||||||
|
defer delete(data)
|
||||||
|
|
||||||
|
for i in 0..<pixel_size {
|
||||||
|
data[i] = image.read_u8(ctx) or_return
|
||||||
|
}
|
||||||
|
|
||||||
|
y, x := 0, 0
|
||||||
|
index := 0
|
||||||
|
for {
|
||||||
|
if len(data[index:]) < 2 {
|
||||||
|
return .Corrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[index] > 0 {
|
||||||
|
for _ in 0..<data[index] {
|
||||||
|
write(img, x, y, palette[data[index + 1]].bgr)
|
||||||
|
x += 1
|
||||||
|
}
|
||||||
|
index += 2
|
||||||
|
} else {
|
||||||
|
switch data[index + 1] {
|
||||||
|
case 0: // EOL
|
||||||
|
x = 0; y += 1
|
||||||
|
index += 2
|
||||||
|
case 1: // EOB
|
||||||
|
return
|
||||||
|
case 2: // MOVE
|
||||||
|
x += int(data[index + 2])
|
||||||
|
y += int(data[index + 3])
|
||||||
|
index += 4
|
||||||
|
case: // Literals
|
||||||
|
run_length := int(data[index + 1])
|
||||||
|
aligned := align2(run_length) + 2
|
||||||
|
|
||||||
|
if index + aligned >= len(data) {
|
||||||
|
return .Corrupt
|
||||||
|
}
|
||||||
|
for count in 0..<run_length {
|
||||||
|
write(img, x, y, palette[data[index + 2 + count]].bgr)
|
||||||
|
x += 1
|
||||||
|
}
|
||||||
|
index += aligned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case:
|
||||||
|
return .Unsupported_BPP
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
align2 :: proc(width: int) -> (stride: int) {
|
||||||
|
stride = width
|
||||||
|
if width & 1 != 0 {
|
||||||
|
stride += 2 - (width & 1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
align4 :: proc(width: int) -> (stride: int) {
|
||||||
|
stride = width
|
||||||
|
if width & 3 != 0 {
|
||||||
|
stride += 4 - (width & 3)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
skip_space :: proc(ctx: ^$C, bytes_to_skip: int) -> (err: Error) {
|
||||||
|
if bytes_to_skip < 0 {
|
||||||
|
return .Corrupt
|
||||||
|
}
|
||||||
|
for _ in 0..<bytes_to_skip {
|
||||||
|
image.read_u8(ctx) or_return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup of image-specific data.
|
||||||
|
destroy :: proc(img: ^Image) {
|
||||||
|
if img == nil {
|
||||||
|
// Nothing to do. Load must've returned with an error.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.buffer_destroy(&img.pixels)
|
||||||
|
if v, ok := img.metadata.(^image.BMP_Info); ok {
|
||||||
|
free(v)
|
||||||
|
}
|
||||||
|
free(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(init, private)
|
||||||
|
_register :: proc() {
|
||||||
|
image.register(.BMP, load_from_bytes, destroy)
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
//+build js
|
||||||
|
package core_image_bmp
|
||||||
|
|
||||||
|
load :: proc{load_from_bytes, load_from_context}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//+build !js
|
||||||
|
package core_image_bmp
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||||
|
|
||||||
|
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
|
||||||
|
context.allocator = allocator
|
||||||
|
|
||||||
|
data, ok := os.read_entire_file(filename)
|
||||||
|
defer delete(data)
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return load_from_bytes(data, options)
|
||||||
|
} else {
|
||||||
|
return nil, .Unable_To_Read_File
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ package image
|
|||||||
|
|
||||||
import "core:bytes"
|
import "core:bytes"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
import "core:io"
|
||||||
import "core:compress"
|
import "core:compress"
|
||||||
import "base:runtime"
|
import "base:runtime"
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ Image_Metadata :: union #shared_nil {
|
|||||||
^PNG_Info,
|
^PNG_Info,
|
||||||
^QOI_Info,
|
^QOI_Info,
|
||||||
^TGA_Info,
|
^TGA_Info,
|
||||||
|
^BMP_Info,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -159,11 +161,13 @@ Error :: union #shared_nil {
|
|||||||
Netpbm_Error,
|
Netpbm_Error,
|
||||||
PNG_Error,
|
PNG_Error,
|
||||||
QOI_Error,
|
QOI_Error,
|
||||||
|
BMP_Error,
|
||||||
|
|
||||||
compress.Error,
|
compress.Error,
|
||||||
compress.General_Error,
|
compress.General_Error,
|
||||||
compress.Deflate_Error,
|
compress.Deflate_Error,
|
||||||
compress.ZLIB_Error,
|
compress.ZLIB_Error,
|
||||||
|
io.Error,
|
||||||
runtime.Allocator_Error,
|
runtime.Allocator_Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +200,128 @@ General_Image_Error :: enum {
|
|||||||
Unable_To_Allocate_Or_Resize,
|
Unable_To_Allocate_Or_Resize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
BMP-specific
|
||||||
|
*/
|
||||||
|
BMP_Error :: enum {
|
||||||
|
None = 0,
|
||||||
|
Invalid_File_Size,
|
||||||
|
Unsupported_BMP_Version,
|
||||||
|
Unsupported_OS2_File,
|
||||||
|
Unsupported_Compression,
|
||||||
|
Unsupported_BPP,
|
||||||
|
Invalid_Stride,
|
||||||
|
Invalid_Color_Count,
|
||||||
|
Implausible_File_Size,
|
||||||
|
Bitfield_Version_Unhandled, // We don't (yet) handle bit fields for this BMP version.
|
||||||
|
Bitfield_Sum_Exceeds_BPP, // Total mask bit count > bpp
|
||||||
|
Bitfield_Overlapped, // Channel masks overlap
|
||||||
|
}
|
||||||
|
|
||||||
|
// img.metadata is wrapped in a struct in case we need to add to it later
|
||||||
|
// without putting it in BMP_Header
|
||||||
|
BMP_Info :: struct {
|
||||||
|
info: BMP_Header,
|
||||||
|
}
|
||||||
|
|
||||||
|
BMP_Magic :: enum u16le {
|
||||||
|
Bitmap = 0x4d42, // 'BM'
|
||||||
|
OS2_Bitmap_Array = 0x4142, // 'BA'
|
||||||
|
OS2_Icon = 0x4349, // 'IC',
|
||||||
|
OS2_Color_Icon = 0x4943, // 'CI'
|
||||||
|
OS2_Pointer = 0x5450, // 'PT'
|
||||||
|
OS2_Color_Pointer = 0x5043, // 'CP'
|
||||||
|
}
|
||||||
|
|
||||||
|
// See: http://justsolve.archiveteam.org/wiki/BMP#Well-known_versions
|
||||||
|
BMP_Version :: enum u32le {
|
||||||
|
OS2_v1 = 12, // BITMAPCOREHEADER (Windows V2 / OS/2 version 1.0)
|
||||||
|
OS2_v2 = 64, // BITMAPCOREHEADER2 (OS/2 version 2.x)
|
||||||
|
V3 = 40, // BITMAPINFOHEADER
|
||||||
|
V4 = 108, // BITMAPV4HEADER
|
||||||
|
V5 = 124, // BITMAPV5HEADER
|
||||||
|
|
||||||
|
ABBR_16 = 16, // Abbreviated
|
||||||
|
ABBR_24 = 24, // ..
|
||||||
|
ABBR_48 = 48, // ..
|
||||||
|
ABBR_52 = 52, // ..
|
||||||
|
ABBR_56 = 56, // ..
|
||||||
|
}
|
||||||
|
|
||||||
|
BMP_Header :: struct #packed {
|
||||||
|
// File header
|
||||||
|
magic: BMP_Magic,
|
||||||
|
size: u32le,
|
||||||
|
_res1: u16le, // Reserved; must be zero
|
||||||
|
_res2: u16le, // Reserved; must be zero
|
||||||
|
pixel_offset: u32le, // Offset in bytes, from the beginning of BMP_Header to the pixel data
|
||||||
|
// V3
|
||||||
|
info_size: BMP_Version,
|
||||||
|
width: i32le,
|
||||||
|
height: i32le,
|
||||||
|
planes: u16le,
|
||||||
|
bpp: u16le,
|
||||||
|
compression: BMP_Compression,
|
||||||
|
image_size: u32le,
|
||||||
|
pels_per_meter: [2]u32le,
|
||||||
|
colors_used: u32le,
|
||||||
|
colors_important: u32le, // OS2_v2 is equal up to here
|
||||||
|
// V4
|
||||||
|
masks: [4]u32le `fmt:"32b"`,
|
||||||
|
colorspace: BMP_Logical_Color_Space,
|
||||||
|
endpoints: BMP_CIEXYZTRIPLE,
|
||||||
|
gamma: [3]BMP_GAMMA16_16,
|
||||||
|
// V5
|
||||||
|
intent: BMP_Gamut_Mapping_Intent,
|
||||||
|
profile_data: u32le,
|
||||||
|
profile_size: u32le,
|
||||||
|
reserved: u32le,
|
||||||
|
}
|
||||||
|
#assert(size_of(BMP_Header) == 138)
|
||||||
|
|
||||||
|
OS2_Header :: struct #packed {
|
||||||
|
// BITMAPCOREHEADER minus info_size field
|
||||||
|
width: i16le,
|
||||||
|
height: i16le,
|
||||||
|
planes: u16le,
|
||||||
|
bpp: u16le,
|
||||||
|
}
|
||||||
|
#assert(size_of(OS2_Header) == 8)
|
||||||
|
|
||||||
|
BMP_Compression :: enum u32le {
|
||||||
|
RGB = 0x0000,
|
||||||
|
RLE8 = 0x0001,
|
||||||
|
RLE4 = 0x0002,
|
||||||
|
Bit_Fields = 0x0003, // If Windows
|
||||||
|
Huffman1D = 0x0003, // If OS2v2
|
||||||
|
JPEG = 0x0004, // If Windows
|
||||||
|
RLE24 = 0x0004, // If OS2v2
|
||||||
|
PNG = 0x0005,
|
||||||
|
Alpha_Bit_Fields = 0x0006,
|
||||||
|
CMYK = 0x000B,
|
||||||
|
CMYK_RLE8 = 0x000C,
|
||||||
|
CMYK_RLE4 = 0x000D,
|
||||||
|
}
|
||||||
|
|
||||||
|
BMP_Logical_Color_Space :: enum u32le {
|
||||||
|
CALIBRATED_RGB = 0x00000000,
|
||||||
|
sRGB = 0x73524742, // 'sRGB'
|
||||||
|
WINDOWS_COLOR_SPACE = 0x57696E20, // 'Win '
|
||||||
|
}
|
||||||
|
|
||||||
|
BMP_FXPT2DOT30 :: u32le
|
||||||
|
BMP_CIEXYZ :: [3]BMP_FXPT2DOT30
|
||||||
|
BMP_CIEXYZTRIPLE :: [3]BMP_CIEXYZ
|
||||||
|
BMP_GAMMA16_16 :: [2]u16le
|
||||||
|
|
||||||
|
BMP_Gamut_Mapping_Intent :: enum u32le {
|
||||||
|
INVALID = 0x00000000, // If not V5, this field will just be zero-initialized and not valid.
|
||||||
|
ABS_COLORIMETRIC = 0x00000008,
|
||||||
|
BUSINESS = 0x00000001,
|
||||||
|
GRAPHICS = 0x00000002,
|
||||||
|
IMAGES = 0x00000004,
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Netpbm-specific definitions
|
Netpbm-specific definitions
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
Copyright 2021-2024 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
Made available under Odin's BSD-3 license.
|
Made available under Odin's BSD-3 license.
|
||||||
|
|
||||||
List of contributors:
|
List of contributors:
|
||||||
Jeroen van Rijn: Initial implementation.
|
Jeroen van Rijn: Initial implementation.
|
||||||
|
|
||||||
A test suite for PNG + QOI.
|
A test suite for PNG, TGA, NetPBM, QOI and BMP.
|
||||||
*/
|
*/
|
||||||
package test_core_image
|
package test_core_image
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ import "core:testing"
|
|||||||
|
|
||||||
import "core:compress"
|
import "core:compress"
|
||||||
import "core:image"
|
import "core:image"
|
||||||
|
import "core:image/bmp"
|
||||||
import pbm "core:image/netpbm"
|
import pbm "core:image/netpbm"
|
||||||
import "core:image/png"
|
import "core:image/png"
|
||||||
import "core:image/qoi"
|
import "core:image/qoi"
|
||||||
@@ -24,16 +25,17 @@ import "core:strings"
|
|||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:time"
|
import "core:time"
|
||||||
|
|
||||||
TEST_SUITE_PATH :: ODIN_ROOT + "tests/core/assets/PNG/"
|
TEST_SUITE_PATH_PNG :: ODIN_ROOT + "tests/core/assets/PNG"
|
||||||
|
TEST_SUITE_PATH_BMP :: ODIN_ROOT + "tests/core/assets/BMP"
|
||||||
|
|
||||||
I_Error :: image.Error
|
I_Error :: image.Error
|
||||||
|
|
||||||
PNG_Test :: struct {
|
Test :: struct {
|
||||||
file: string,
|
file: string,
|
||||||
tests: []struct {
|
tests: []struct {
|
||||||
options: image.Options,
|
options: image.Options,
|
||||||
expected_error: image.Error,
|
expected_error: image.Error,
|
||||||
dims: PNG_Dims,
|
dims: Dims,
|
||||||
hash: u32,
|
hash: u32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -46,19 +48,18 @@ Blend_BG_Keep :: image.Options{.blend_background, .alpha_add_if_missing}
|
|||||||
Return_Metadata :: image.Options{.return_metadata}
|
Return_Metadata :: image.Options{.return_metadata}
|
||||||
No_Channel_Expansion :: image.Options{.do_not_expand_channels, .return_metadata}
|
No_Channel_Expansion :: image.Options{.do_not_expand_channels, .return_metadata}
|
||||||
|
|
||||||
PNG_Dims :: struct {
|
Dims :: struct {
|
||||||
width: int,
|
width: int,
|
||||||
height: int,
|
height: int,
|
||||||
channels: int,
|
channels: int,
|
||||||
depth: int,
|
depth: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
Basic_PNG_Tests := []PNG_Test{
|
Basic_PNG_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
Basic format tests:
|
Basic format tests:
|
||||||
http://www.schaik.com/pngsuite/pngsuite_bas_png.html
|
http://www.schaik.com/pngsuite/pngsuite_bas_png.html
|
||||||
*/
|
*/
|
||||||
|
|
||||||
{
|
{
|
||||||
"basn0g01", // Black and white.
|
"basn0g01", // Black and white.
|
||||||
{
|
{
|
||||||
@@ -166,7 +167,7 @@ Basic_PNG_Tests := []PNG_Test{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Interlaced_PNG_Tests := []PNG_Test{
|
Interlaced_PNG_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
Interlaced format tests:
|
Interlaced format tests:
|
||||||
http://www.schaik.com/pngsuite/pngsuite_int_png.html
|
http://www.schaik.com/pngsuite/pngsuite_int_png.html
|
||||||
@@ -284,9 +285,9 @@ Interlaced_PNG_Tests := []PNG_Test{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Odd_Sized_PNG_Tests := []PNG_Test{
|
Odd_Sized_PNG_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
" PngSuite", // Odd sizes / PNG-files:
|
"PngSuite", // Odd sizes / PNG-files:
|
||||||
http://www.schaik.com/pngsuite/pngsuite_siz_png.html
|
http://www.schaik.com/pngsuite/pngsuite_siz_png.html
|
||||||
|
|
||||||
This tests curious sizes with and without interlacing.
|
This tests curious sizes with and without interlacing.
|
||||||
@@ -510,7 +511,7 @@ Odd_Sized_PNG_Tests := []PNG_Test{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
PNG_bKGD_Tests := []PNG_Test{
|
PNG_bKGD_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
" PngSuite", // Background colors / PNG-files:
|
" PngSuite", // Background colors / PNG-files:
|
||||||
http://www.schaik.com/pngsuite/pngsuite_bck_png.html
|
http://www.schaik.com/pngsuite/pngsuite_bck_png.html
|
||||||
@@ -597,7 +598,7 @@ PNG_bKGD_Tests := []PNG_Test{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
PNG_tRNS_Tests := []PNG_Test{
|
PNG_tRNS_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
PngSuite - Transparency:
|
PngSuite - Transparency:
|
||||||
http://www.schaik.com/pngsuite/pngsuite_trn_png.html
|
http://www.schaik.com/pngsuite/pngsuite_trn_png.html
|
||||||
@@ -759,7 +760,7 @@ PNG_tRNS_Tests := []PNG_Test{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
PNG_Filter_Tests := []PNG_Test{
|
PNG_Filter_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
PngSuite - Image filtering:
|
PngSuite - Image filtering:
|
||||||
|
|
||||||
@@ -838,7 +839,7 @@ PNG_Filter_Tests := []PNG_Test{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
PNG_Varied_IDAT_Tests := []PNG_Test{
|
PNG_Varied_IDAT_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
PngSuite - Chunk ordering:
|
PngSuite - Chunk ordering:
|
||||||
|
|
||||||
@@ -897,7 +898,7 @@ PNG_Varied_IDAT_Tests := []PNG_Test{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
PNG_ZLIB_Levels_Tests := []PNG_Test{
|
PNG_ZLIB_Levels_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
PngSuite - Zlib compression:
|
PngSuite - Zlib compression:
|
||||||
|
|
||||||
@@ -938,7 +939,7 @@ PNG_ZLIB_Levels_Tests := []PNG_Test{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
PNG_sPAL_Tests := []PNG_Test{
|
PNG_sPAL_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
PngSuite - Additional palettes:
|
PngSuite - Additional palettes:
|
||||||
|
|
||||||
@@ -985,7 +986,7 @@ PNG_sPAL_Tests := []PNG_Test{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
PNG_Ancillary_Tests := []PNG_Test{
|
PNG_Ancillary_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
PngSuite" - Ancillary chunks:
|
PngSuite" - Ancillary chunks:
|
||||||
|
|
||||||
@@ -1153,7 +1154,7 @@ PNG_Ancillary_Tests := []PNG_Test{
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Corrupt_PNG_Tests := []PNG_Test{
|
Corrupt_PNG_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
PngSuite - Corrupted files / PNG-files:
|
PngSuite - Corrupted files / PNG-files:
|
||||||
|
|
||||||
@@ -1249,7 +1250,7 @@ Corrupt_PNG_Tests := []PNG_Test{
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
No_Postprocesing_Tests := []PNG_Test{
|
No_Postprocesing_Tests := []Test{
|
||||||
/*
|
/*
|
||||||
These are some custom tests where we skip expanding to RGB(A).
|
These are some custom tests where we skip expanding to RGB(A).
|
||||||
*/
|
*/
|
||||||
@@ -1273,8 +1274,6 @@ No_Postprocesing_Tests := []PNG_Test{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Text_Title :: "PngSuite"
|
Text_Title :: "PngSuite"
|
||||||
Text_Software :: "Created on a NeXTstation color using \"pnmtopng\"."
|
Text_Software :: "Created on a NeXTstation color using \"pnmtopng\"."
|
||||||
Text_Descrption :: "A compilation of a set of images created to test the\nvarious color-types of the PNG format. Included are\nblack&white, color, paletted, with alpha channel, with\ntransparency formats. All bit-depths allowed according\nto the spec are present."
|
Text_Descrption :: "A compilation of a set of images created to test the\nvarious color-types of the PNG format. Included are\nblack&white, color, paletted, with alpha channel, with\ntransparency formats. All bit-depths allowed according\nto the spec are present."
|
||||||
@@ -1453,9 +1452,9 @@ png_test_no_postproc :: proc(t: ^testing.T) {
|
|||||||
run_png_suite(t, No_Postprocesing_Tests)
|
run_png_suite(t, No_Postprocesing_Tests)
|
||||||
}
|
}
|
||||||
|
|
||||||
run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
run_png_suite :: proc(t: ^testing.T, suite: []Test) {
|
||||||
for file in suite {
|
for file in suite {
|
||||||
test_file := strings.concatenate({TEST_SUITE_PATH, file.file, ".png"})
|
test_file := strings.concatenate({TEST_SUITE_PATH_PNG, "/", file.file, ".png"}, context.allocator)
|
||||||
defer delete(test_file)
|
defer delete(test_file)
|
||||||
|
|
||||||
img: ^png.Image
|
img: ^png.Image
|
||||||
@@ -1468,20 +1467,19 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
img, err = png.load(test_file, test.options)
|
img, err = png.load(test_file, test.options)
|
||||||
|
|
||||||
passed := (test.expected_error == nil && err == nil) || (test.expected_error == err)
|
passed := (test.expected_error == nil && err == nil) || (test.expected_error == err)
|
||||||
testing.expectf(t, passed, "%v failed with %v.", test_file, err)
|
testing.expectf(t, passed, "%q failed to load with error %v.", file.file, err)
|
||||||
|
|
||||||
if err == nil { // No point in running the other tests if it didn't load.
|
if err == nil { // No point in running the other tests if it didn't load.
|
||||||
pixels := bytes.buffer_to_bytes(&img.pixels)
|
pixels := bytes.buffer_to_bytes(&img.pixels)
|
||||||
|
|
||||||
// This struct compare fails at -opt:2 if PNG_Dims is not #packed.
|
// This struct compare fails at -opt:2 if PNG_Dims is not #packed.
|
||||||
dims := PNG_Dims{img.width, img.height, img.channels, img.depth}
|
dims := Dims{img.width, img.height, img.channels, img.depth}
|
||||||
dims_pass := test.dims == dims
|
dims_pass := test.dims == dims
|
||||||
|
testing.expectf(t, dims_pass, "%v has %v, expected: %v", file.file, dims, test.dims)
|
||||||
testing.expectf(t, dims_pass, "%v has %v, expected: %v.", file.file, dims, test.dims)
|
|
||||||
passed &= dims_pass
|
passed &= dims_pass
|
||||||
|
|
||||||
png_hash := hash.crc32(pixels)
|
png_hash := hash.crc32(pixels)
|
||||||
testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v.", file.file, count, png_hash, test.hash, test.options)
|
testing.expectf(t, test.hash == png_hash, "%v test %v hash is %08x, expected %08x with %v", file.file, count, png_hash, test.hash, test.options)
|
||||||
|
|
||||||
passed &= test.hash == png_hash
|
passed &= test.hash == png_hash
|
||||||
|
|
||||||
@@ -1492,16 +1490,16 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
defer bytes.buffer_destroy(&qoi_buffer)
|
defer bytes.buffer_destroy(&qoi_buffer)
|
||||||
qoi_save_err := qoi.save(&qoi_buffer, img)
|
qoi_save_err := qoi.save(&qoi_buffer, img)
|
||||||
|
|
||||||
testing.expectf(t, qoi_save_err == nil, "%v test %v QOI save failed with %v.", file.file, count, qoi_save_err)
|
testing.expectf(t, qoi_save_err == nil, "%v test %v QOI save failed with %v", file.file, count, qoi_save_err)
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
testing.expectf(t, qoi_load_err == nil, "%v test %v QOI load failed with %v.", file.file, count, qoi_load_err)
|
testing.expectf(t, qoi_load_err == nil, "%v test %v QOI load failed with %v", file.file, count, qoi_load_err)
|
||||||
|
|
||||||
qoi_hash := hash.crc32(qoi_img.pixels.buf[:])
|
qoi_hash := hash.crc32(qoi_img.pixels.buf[:])
|
||||||
testing.expectf(t, qoi_hash == png_hash, "%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)
|
testing.expectf(t, qoi_hash == png_hash, "%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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1511,15 +1509,15 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
defer bytes.buffer_destroy(&tga_buffer)
|
defer bytes.buffer_destroy(&tga_buffer)
|
||||||
tga_save_err := tga.save(&tga_buffer, img)
|
tga_save_err := tga.save(&tga_buffer, img)
|
||||||
|
|
||||||
testing.expectf(t, tga_save_err == nil, "%v test %v TGA save failed with %v.", file.file, count, tga_save_err)
|
testing.expectf(t, tga_save_err == nil, "%v test %v TGA save failed with %v", file.file, count, tga_save_err)
|
||||||
if tga_save_err == nil {
|
if tga_save_err == nil {
|
||||||
tga_img, tga_load_err := tga.load(tga_buffer.buf[:])
|
tga_img, tga_load_err := tga.load(tga_buffer.buf[:])
|
||||||
defer tga.destroy(tga_img)
|
defer tga.destroy(tga_img)
|
||||||
|
|
||||||
testing.expectf(t, tga_load_err == nil, "%v test %v TGA load failed with %v.", file.file, count, tga_load_err)
|
testing.expectf(t, tga_load_err == nil, "%v test %v TGA load failed with %v", file.file, count, tga_load_err)
|
||||||
|
|
||||||
tga_hash := hash.crc32(tga_img.pixels.buf[:])
|
tga_hash := hash.crc32(tga_img.pixels.buf[:])
|
||||||
testing.expectf(t, tga_hash == png_hash, "%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, tga_hash, png_hash, test.options)
|
testing.expectf(t, tga_hash == png_hash, "%v test %v TGA load hash is %08x, expected it match PNG's %08x with %v", file.file, count, tga_hash, png_hash, test.options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1528,18 +1526,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
pbm_buf, pbm_save_err := pbm.save_to_buffer(img)
|
pbm_buf, pbm_save_err := pbm.save_to_buffer(img)
|
||||||
defer delete(pbm_buf)
|
defer delete(pbm_buf)
|
||||||
|
|
||||||
testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err)
|
testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v", file.file, count, pbm_save_err)
|
||||||
|
|
||||||
if pbm_save_err == nil {
|
if pbm_save_err == nil {
|
||||||
// Try to load it again.
|
// Try to load it again.
|
||||||
pbm_img, pbm_load_err := pbm.load(pbm_buf)
|
pbm_img, pbm_load_err := pbm.load(pbm_buf)
|
||||||
defer pbm.destroy(pbm_img)
|
defer pbm.destroy(pbm_img)
|
||||||
|
|
||||||
testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err)
|
testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v", file.file, count, pbm_load_err)
|
||||||
|
|
||||||
if pbm_load_err == nil {
|
if pbm_load_err == nil {
|
||||||
pbm_hash := hash.crc32(pbm_img.pixels.buf[:])
|
pbm_hash := hash.crc32(pbm_img.pixels.buf[:])
|
||||||
testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options)
|
testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v", file.file, count, pbm_hash, png_hash, test.options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1553,18 +1551,18 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
pbm_buf, pbm_save_err := pbm.save_to_buffer(img, pbm_info)
|
pbm_buf, pbm_save_err := pbm.save_to_buffer(img, pbm_info)
|
||||||
defer delete(pbm_buf)
|
defer delete(pbm_buf)
|
||||||
|
|
||||||
testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v.", file.file, count, pbm_save_err)
|
testing.expectf(t, pbm_save_err == nil, "%v test %v PBM save failed with %v", file.file, count, pbm_save_err)
|
||||||
|
|
||||||
if pbm_save_err == nil {
|
if pbm_save_err == nil {
|
||||||
// Try to load it again.
|
// Try to load it again.
|
||||||
pbm_img, pbm_load_err := pbm.load(pbm_buf)
|
pbm_img, pbm_load_err := pbm.load(pbm_buf)
|
||||||
defer pbm.destroy(pbm_img)
|
defer pbm.destroy(pbm_img)
|
||||||
|
|
||||||
testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v.", file.file, count, pbm_load_err)
|
testing.expectf(t, pbm_load_err == nil, "%v test %v PBM load failed with %v", file.file, count, pbm_load_err)
|
||||||
|
|
||||||
if pbm_load_err == nil {
|
if pbm_load_err == nil {
|
||||||
pbm_hash := hash.crc32(pbm_img.pixels.buf[:])
|
pbm_hash := hash.crc32(pbm_img.pixels.buf[:])
|
||||||
testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v.", file.file, count, pbm_hash, png_hash, test.options)
|
testing.expectf(t, pbm_hash == png_hash, "%v test %v PBM load hash is %08x, expected it match PNG's %08x with %v", file.file, count, pbm_hash, png_hash, test.options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1653,7 +1651,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
case "pp0n2c16", "pp0n6a08":
|
case "pp0n2c16", "pp0n6a08":
|
||||||
gamma, gamma_ok := png.gamma(c)
|
gamma, gamma_ok := png.gamma(c)
|
||||||
expected_gamma := f32(1.0)
|
expected_gamma := f32(1.0)
|
||||||
testing.expectf(t, gamma == expected_gamma && gamma_ok, "%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma)
|
testing.expectf(t, gamma == expected_gamma && gamma_ok, "%v test %v gAMA is %v, expected %v", file.file, count, gamma, expected_gamma)
|
||||||
}
|
}
|
||||||
case .PLTE:
|
case .PLTE:
|
||||||
switch(file.file) {
|
switch(file.file) {
|
||||||
@@ -1661,7 +1659,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
plte, plte_ok := png.plte(c)
|
plte, plte_ok := png.plte(c)
|
||||||
|
|
||||||
expected_plte_len := u16(216)
|
expected_plte_len := u16(216)
|
||||||
testing.expectf(t, expected_plte_len == plte.used && plte_ok, "%v test %v PLTE length is %v, expected %v.", file.file, count, plte.used, expected_plte_len)
|
testing.expectf(t, expected_plte_len == plte.used && plte_ok, "%v test %v PLTE length is %v, expected %v", file.file, count, plte.used, expected_plte_len)
|
||||||
}
|
}
|
||||||
case .sPLT:
|
case .sPLT:
|
||||||
switch(file.file) {
|
switch(file.file) {
|
||||||
@@ -1669,10 +1667,10 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
splt, splt_ok := png.splt(c)
|
splt, splt_ok := png.splt(c)
|
||||||
|
|
||||||
expected_splt_len := u16(216)
|
expected_splt_len := u16(216)
|
||||||
testing.expectf(t, expected_splt_len == splt.used && splt_ok, "%v test %v sPLT length is %v, expected %v.", file.file, count, splt.used, expected_splt_len)
|
testing.expectf(t, expected_splt_len == splt.used && splt_ok, "%v test %v sPLT length is %v, expected %v", file.file, count, splt.used, expected_splt_len)
|
||||||
|
|
||||||
expected_splt_name := "six-cube"
|
expected_splt_name := "six-cube"
|
||||||
testing.expectf(t, expected_splt_name == splt.name && splt_ok, "%v test %v sPLT name is %v, expected %v.", file.file, count, splt.name, expected_splt_name)
|
testing.expectf(t, expected_splt_name == splt.name && splt_ok, "%v test %v sPLT name is %v, expected %v", file.file, count, splt.name, expected_splt_name)
|
||||||
|
|
||||||
png.splt_destroy(splt)
|
png.splt_destroy(splt)
|
||||||
}
|
}
|
||||||
@@ -1686,31 +1684,31 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
g = png.CIE_1931{x = 0.3000, y = 0.6000},
|
g = png.CIE_1931{x = 0.3000, y = 0.6000},
|
||||||
b = png.CIE_1931{x = 0.1500, y = 0.0600},
|
b = png.CIE_1931{x = 0.1500, y = 0.0600},
|
||||||
}
|
}
|
||||||
testing.expectf(t, expected_chrm == chrm && chrm_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, chrm, expected_chrm)
|
testing.expectf(t, expected_chrm == chrm && chrm_ok, "%v test %v cHRM is %v, expected %v", file.file, count, chrm, expected_chrm)
|
||||||
}
|
}
|
||||||
case .pHYs:
|
case .pHYs:
|
||||||
phys, phys_ok := png.phys(c)
|
phys, phys_ok := png.phys(c)
|
||||||
switch (file.file) {
|
switch (file.file) {
|
||||||
case "cdfn2c08":
|
case "cdfn2c08":
|
||||||
expected_phys := png.pHYs{ppu_x = 1, ppu_y = 4, unit = .Unknown}
|
expected_phys := png.pHYs{ppu_x = 1, ppu_y = 4, unit = .Unknown}
|
||||||
testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
|
testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys)
|
||||||
case "cdhn2c08":
|
case "cdhn2c08":
|
||||||
expected_phys := png.pHYs{ppu_x = 4, ppu_y = 1, unit = .Unknown}
|
expected_phys := png.pHYs{ppu_x = 4, ppu_y = 1, unit = .Unknown}
|
||||||
testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
|
testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys)
|
||||||
case "cdsn2c08":
|
case "cdsn2c08":
|
||||||
expected_phys := png.pHYs{ppu_x = 1, ppu_y = 1, unit = .Unknown}
|
expected_phys := png.pHYs{ppu_x = 1, ppu_y = 1, unit = .Unknown}
|
||||||
testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
|
testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys)
|
||||||
case "cdun2c08":
|
case "cdun2c08":
|
||||||
expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter}
|
expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter}
|
||||||
testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v.", file.file, count, phys, expected_phys)
|
testing.expectf(t, expected_phys == phys && phys_ok, "%v test %v cHRM is %v, expected %v", file.file, count, phys, expected_phys)
|
||||||
}
|
}
|
||||||
case .hIST:
|
case .hIST:
|
||||||
hist, hist_ok := png.hist(c)
|
hist, hist_ok := png.hist(c)
|
||||||
switch (file.file) {
|
switch (file.file) {
|
||||||
case "ch1n3p04":
|
case "ch1n3p04":
|
||||||
testing.expectf(t, hist.used == 15 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 15)
|
testing.expectf(t, hist.used == 15 && hist_ok, "%v test %v hIST has %v entries, expected %v", file.file, count, hist.used, 15)
|
||||||
case "ch2n3p08":
|
case "ch2n3p08":
|
||||||
testing.expectf(t, hist.used == 256 && hist_ok, "%v test %v hIST has %v entries, expected %v.", file.file, count, hist.used, 256)
|
testing.expectf(t, hist.used == 256 && hist_ok, "%v test %v hIST has %v entries, expected %v", file.file, count, hist.used, 256)
|
||||||
}
|
}
|
||||||
case .tIME:
|
case .tIME:
|
||||||
png_time, png_time_ok := png.time(c)
|
png_time, png_time_ok := png.time(c)
|
||||||
@@ -1731,8 +1729,8 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
expected_core = time.Time{_nsec = 946684799000000000}
|
expected_core = time.Time{_nsec = 946684799000000000}
|
||||||
|
|
||||||
}
|
}
|
||||||
testing.expectf(t, png_time == expected_time && png_time_ok, "%v test %v tIME was %v, expected %v.", file.file, count, png_time, expected_time)
|
testing.expectf(t, png_time == expected_time && png_time_ok, "%v test %v tIME was %v, expected %v", file.file, count, png_time, expected_time)
|
||||||
testing.expectf(t, core_time == expected_core && core_time_ok, "%v test %v tIME->core:time is %v, expected %v.", file.file, count, core_time, expected_core)
|
testing.expectf(t, core_time == expected_core && core_time_ok, "%v test %v tIME->core:time is %v, expected %v", file.file, count, core_time, expected_core)
|
||||||
case .sBIT:
|
case .sBIT:
|
||||||
sbit, sbit_ok := png.sbit(c)
|
sbit, sbit_ok := png.sbit(c)
|
||||||
expected_sbit: [4]u8
|
expected_sbit: [4]u8
|
||||||
@@ -1753,7 +1751,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
case "cdfn2c08", "cdhn2c08", "cdsn2c08", "cdun2c08", "ch1n3p04", "basn3p04":
|
case "cdfn2c08", "cdhn2c08", "cdsn2c08", "cdun2c08", "ch1n3p04", "basn3p04":
|
||||||
expected_sbit = [4]u8{ 4, 4, 4, 0}
|
expected_sbit = [4]u8{ 4, 4, 4, 0}
|
||||||
}
|
}
|
||||||
testing.expectf(t, sbit == expected_sbit && sbit_ok, "%v test %v sBIT was %v, expected %v.", file.file, count, sbit, expected_sbit)
|
testing.expectf(t, sbit == expected_sbit && sbit_ok, "%v test %v sBIT was %v, expected %v", file.file, count, sbit, expected_sbit)
|
||||||
case .tEXt, .zTXt:
|
case .tEXt, .zTXt:
|
||||||
text, text_ok := png.text(c)
|
text, text_ok := png.text(c)
|
||||||
defer png.text_destroy(text)
|
defer png.text_destroy(text)
|
||||||
@@ -1765,7 +1763,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
if file.file in Expected_Text {
|
if file.file in Expected_Text {
|
||||||
if text.keyword in Expected_Text[file.file] {
|
if text.keyword in Expected_Text[file.file] {
|
||||||
test_text := Expected_Text[file.file][text.keyword].text
|
test_text := Expected_Text[file.file][text.keyword].text
|
||||||
testing.expectf(t, text.text == test_text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text.text, test_text)
|
testing.expectf(t, text.text == test_text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text.text, test_text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1778,44 +1776,44 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
if file.file in Expected_Text {
|
if file.file in Expected_Text {
|
||||||
if text.keyword in Expected_Text[file.file] {
|
if text.keyword in Expected_Text[file.file] {
|
||||||
test := Expected_Text[file.file][text.keyword]
|
test := Expected_Text[file.file][text.keyword]
|
||||||
testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "ctfn0g04": // international UTF-8, finnish
|
case "ctfn0g04": // international UTF-8, finnish
|
||||||
if file.file in Expected_Text {
|
if file.file in Expected_Text {
|
||||||
if text.keyword in Expected_Text[file.file] {
|
if text.keyword in Expected_Text[file.file] {
|
||||||
test := Expected_Text[file.file][text.keyword]
|
test := Expected_Text[file.file][text.keyword]
|
||||||
testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "ctgn0g04": // international UTF-8, greek
|
case "ctgn0g04": // international UTF-8, greek
|
||||||
if file.file in Expected_Text {
|
if file.file in Expected_Text {
|
||||||
if text.keyword in Expected_Text[file.file] {
|
if text.keyword in Expected_Text[file.file] {
|
||||||
test := Expected_Text[file.file][text.keyword]
|
test := Expected_Text[file.file][text.keyword]
|
||||||
testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "cthn0g04": // international UTF-8, hindi
|
case "cthn0g04": // international UTF-8, hindi
|
||||||
if file.file in Expected_Text {
|
if file.file in Expected_Text {
|
||||||
if text.keyword in Expected_Text[file.file] {
|
if text.keyword in Expected_Text[file.file] {
|
||||||
test := Expected_Text[file.file][text.keyword]
|
test := Expected_Text[file.file][text.keyword]
|
||||||
testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "ctjn0g04": // international UTF-8, japanese
|
case "ctjn0g04": // international UTF-8, japanese
|
||||||
if file.file in Expected_Text {
|
if file.file in Expected_Text {
|
||||||
if text.keyword in Expected_Text[file.file] {
|
if text.keyword in Expected_Text[file.file] {
|
||||||
test := Expected_Text[file.file][text.keyword]
|
test := Expected_Text[file.file][text.keyword]
|
||||||
testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.text == test.text && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.language == test.language && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'.", file.file, count, text.keyword, text, test)
|
testing.expectf(t, text.keyword_localized == test.keyword_localized && text_ok, "%v test %v text keyword {{%v}}:'%v', expected '%v'", file.file, count, text.keyword, text, test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1823,7 +1821,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
if file.file == "exif2c08" { // chunk with jpeg exif data
|
if file.file == "exif2c08" { // chunk with jpeg exif data
|
||||||
exif, exif_ok := png.exif(c)
|
exif, exif_ok := png.exif(c)
|
||||||
testing.expectf(t, exif.byte_order == .big_endian && exif_ok, "%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order)
|
testing.expectf(t, exif.byte_order == .big_endian && exif_ok, "%v test %v eXIf byte order '%v', expected 'big_endian'.", file.file, count, exif.byte_order)
|
||||||
testing.expectf(t, len(exif.data) == 978 && exif_ok, "%v test %v eXIf data length '%v', expected '%v'.", file.file, len(exif.data), 978)
|
testing.expectf(t, len(exif.data) == 978 && exif_ok, "%v test %v eXIf data length '%v', expected '%v'", file.file, len(exif.data), 978)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1833,4 +1831,516 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) {
|
|||||||
png.destroy(img)
|
png.destroy(img)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Basic format tests:
|
||||||
|
https://entropymine.com/jason/bmpsuite/bmpsuite/html/bmpsuite.html - Version 2.8; 2023-11-28
|
||||||
|
|
||||||
|
The BMP Suite image generator itself is GPL, and isn't included, nor did it have its code referenced.
|
||||||
|
We do thank the author for the well-researched test suite, which we are free to include:
|
||||||
|
|
||||||
|
"Image files generated by this program are not covered by this license, and are
|
||||||
|
in the public domain (except for the embedded ICC profiles)."
|
||||||
|
|
||||||
|
The files with embedded ICC profiles aren't part of Odin's test assets. We don't support BMP metadata.
|
||||||
|
We don't support all "possibly correct" images, and thus only ship a subset of these from the BMP Suite.
|
||||||
|
*/
|
||||||
|
Basic_BMP_Tests := []Test{
|
||||||
|
{
|
||||||
|
"pal1", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal1wb", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal1bg", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_9e91_174a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal4", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_288e_4371},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal4gs", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_452d_a01a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal4rle", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_288e_4371},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8-0", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8gs", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_09c2_7834},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8rle", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8w126", {
|
||||||
|
{Default, nil, {126, 63, 3, 8}, 0x_bb66_4cda},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8w125", {
|
||||||
|
{Default, nil, {125, 62, 3, 8}, 0x_3ab8_f7c5},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8w124", {
|
||||||
|
{Default, nil, {124, 61, 3, 8}, 0x_b53e_e6c8},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8topdown", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8nonsquare", {
|
||||||
|
{Default, nil, {127, 32, 3, 8}, 0x_8409_c689},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8v4", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8v5", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb16", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb16bfdef", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb16-565", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_8c73_a2ff},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb16-565pal", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_8c73_a2ff},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb24", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb24pal", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb32", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb32bf", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb32bfdef", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
OS2_Tests := []Test{
|
||||||
|
{
|
||||||
|
"pal8os2", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8os2-sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8os2-hs", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8os2sp", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8os2v2", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8os2v2-16", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8os2v2-sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8os2v2-40sz", { // An OS/2-style bitmap. This format can be called OS/2 BMPv1, or Windows BMPv2.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// BMP files that aren't 100% to spec. Some we support, some we don't.
|
||||||
|
Questionable_BMP_Tests := []Test{
|
||||||
|
{
|
||||||
|
"pal1p1", { // Spec says 1-bit image has 2 palette entries. This one has 1.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_2b54_2560},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal2", { // 2-bit. Allowed on Windows CE. Irfanview doesn't support it.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_0da2_7594},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal2color", { // 2-bit, with color palette.
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_f0d8_c5d6},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8offs", { // 300 palette entries (yes, only 256 can be used)
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8oversizepal", { // Some padding between palette and image data
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal4rletrns", { // Using palette tricks to skip pixels
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_eed4_e744},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal4rlecut", { // Using palette tricks to skip pixels
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_473fbc7d},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8rletrns", { // Using palette tricks to skip pixels
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_fe1f_e560},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8rlecut", { // Using palette tricks to skip pixels
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_bd04_3619},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb16faketrns", { // Using palette tricks to skip pixels
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb16-231", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_7393_a163},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb16-3103", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3b66_2189},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba16-4444", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_b785_1f9f},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba16-5551", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_8b6f_81a2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba16-1924", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_f038_2bed},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb32-xbgr", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb32fakealpha", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb32-111110", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_b2c7_a8ff},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb32-7187", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_b93a_4291},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba32-1", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_7b67_823d},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba32-2", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_7b67_823d},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba32-1010102", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_aa42_0b16},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba32-81284", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_28a2_4c16},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba32-61754", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_4aae_26ed},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba32abf", { // Custom bit fields
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_7b67_823d},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb32h52", { // Truncated header (RGB bit fields included)
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_025b_ba0a},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba32h56", { // Truncated header (RGBA bit fields included)
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_7b67_823d},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsupported BMP features, or malformed images.
|
||||||
|
Unsupported_BMP_Tests := []Test{
|
||||||
|
{
|
||||||
|
"ba-bm", { // An OS/2 Bitmap array. We don't support this BA format.
|
||||||
|
{Default, .Unsupported_OS2_File, {127, 32, 3, 8}, 0x_0000_0000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal1huffmsb", { // An OS/2 file with Huffman 1D compression
|
||||||
|
{Default, .Unsupported_Compression, {127, 32, 3, 8}, 0x_0000_0000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb24rle24", { // An OS/2 file with RLE24 compression
|
||||||
|
{Default, .Unsupported_Compression, {127, 64, 3, 8}, 0x_0000_0000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgba64", { // An OS/2 file with RLE24 compression
|
||||||
|
{Default, .Unsupported_BPP, {127, 64, 3, 8}, 0x_0000_0000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Malformed / malicious files
|
||||||
|
Known_Bad_BMP_Tests := []Test{
|
||||||
|
{
|
||||||
|
"badbitcount", {
|
||||||
|
{Default, .Unsupported_BPP, {127, 64, 3, 8}, 0x_3ce81fae},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badbitssize", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baddens1", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"baddens2", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badfilesize", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badheadersize", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badpalettesize", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badplanes", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badrle", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_1457_aae4},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badrle4", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_6764_d2ac},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badrle4bis", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_935d_bb37},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badrle4ter", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_f2ba_5b08},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badrlebis", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_07e2_d730},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badrleter", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_a874_2742},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"badwidth", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3ce8_1fae},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pal8badindex", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_0450_0d02},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"reallybig", {
|
||||||
|
{Default, .Image_Dimensions_Too_Large, {3000000, 2000000, 1, 24}, 0x_0000_0000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rgb16-880", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_f1c2_0c73},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"rletopdown", {
|
||||||
|
{Default, nil, {127, 64, 3, 8}, 0x_3845_4155},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"shortfile", {
|
||||||
|
{Default, .Short_Buffer, {127, 64, 1, 1}, 0x_0000_0000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
@test
|
||||||
|
bmp_test_basic :: proc(t: ^testing.T) {
|
||||||
|
run_bmp_suite(t, Basic_BMP_Tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
@test
|
||||||
|
bmp_test_os2 :: proc(t: ^testing.T) {
|
||||||
|
run_bmp_suite(t, OS2_Tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
@test
|
||||||
|
bmp_test_questionable :: proc(t: ^testing.T) {
|
||||||
|
run_bmp_suite(t, Questionable_BMP_Tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
@test
|
||||||
|
bmp_test_unsupported :: proc(t: ^testing.T) {
|
||||||
|
run_bmp_suite(t, Unsupported_BMP_Tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
@test
|
||||||
|
bmp_test_known_bad :: proc(t: ^testing.T) {
|
||||||
|
run_bmp_suite(t, Known_Bad_BMP_Tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
run_bmp_suite :: proc(t: ^testing.T, suite: []Test) {
|
||||||
|
for file in suite {
|
||||||
|
test_file := strings.concatenate({TEST_SUITE_PATH_BMP, "/", file.file, ".bmp"}, context.allocator)
|
||||||
|
defer delete(test_file)
|
||||||
|
|
||||||
|
for test in file.tests {
|
||||||
|
img, err := bmp.load(test_file, test.options)
|
||||||
|
|
||||||
|
passed := (test.expected_error == nil && err == nil) || (test.expected_error == err)
|
||||||
|
testing.expectf(t, passed, "%q failed to load with error %v.", file.file, err)
|
||||||
|
|
||||||
|
if err == nil { // No point in running the other tests if it didn't load.
|
||||||
|
qoi_file := strings.concatenate({TEST_SUITE_PATH_BMP, "/", file.file, ".qoi"}, context.allocator)
|
||||||
|
defer delete(qoi_file)
|
||||||
|
|
||||||
|
qoi.save(qoi_file, img)
|
||||||
|
pixels := bytes.buffer_to_bytes(&img.pixels)
|
||||||
|
|
||||||
|
dims := Dims{img.width, img.height, img.channels, img.depth}
|
||||||
|
testing.expectf(t, test.dims == dims, "%v has %v, expected: %v.", file.file, dims, test.dims)
|
||||||
|
|
||||||
|
img_hash := hash.crc32(pixels)
|
||||||
|
testing.expectf(t, test.hash == img_hash, "%v test #1's hash is %08x, expected %08x with %v.", file.file, img_hash, test.hash, test.options)
|
||||||
|
}
|
||||||
|
bmp.destroy(img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user