mirror of
https://github.com/odin-lang/Odin.git
synced 2026-01-07 05:23:12 +00:00
Merge branch 'master' of https://github.com/odin-lang/Odin
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
package compress
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
@@ -7,10 +5,11 @@ package compress
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation, optimization.
|
||||
*/
|
||||
package compress
|
||||
|
||||
import "core:io"
|
||||
import "core:image"
|
||||
import "core:bytes"
|
||||
import "core:runtime"
|
||||
|
||||
/*
|
||||
These settings bound how much compression algorithms will allocate for their output buffer.
|
||||
@@ -51,11 +50,8 @@ Error :: union {
|
||||
ZLIB_Error,
|
||||
GZIP_Error,
|
||||
ZIP_Error,
|
||||
/*
|
||||
This is here because png.load will return a this type of error union,
|
||||
as it may involve an I/O error, a Deflate error, etc.
|
||||
*/
|
||||
image.Error,
|
||||
|
||||
runtime.Allocator_Error,
|
||||
}
|
||||
|
||||
General_Error :: enum {
|
||||
@@ -69,7 +65,6 @@ General_Error :: enum {
|
||||
Incompatible_Options,
|
||||
Unimplemented,
|
||||
|
||||
|
||||
/*
|
||||
Memory errors
|
||||
*/
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
package image
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation, optimization.
|
||||
Ginger Bill: Cosmetic changes.
|
||||
*/
|
||||
package image
|
||||
|
||||
import "core:bytes"
|
||||
import "core:mem"
|
||||
import "core:compress"
|
||||
import "core:runtime"
|
||||
|
||||
Image :: struct {
|
||||
width: int,
|
||||
@@ -25,8 +26,11 @@ Image :: struct {
|
||||
*/
|
||||
background: Maybe([3]u16),
|
||||
|
||||
metadata_ptr: rawptr,
|
||||
metadata_type: typeid,
|
||||
metadata: Image_Metadata,
|
||||
}
|
||||
|
||||
Image_Metadata :: union {
|
||||
^PNG_Info,
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -112,31 +116,140 @@ Option :: enum {
|
||||
}
|
||||
Options :: distinct bit_set[Option]
|
||||
|
||||
Error :: enum {
|
||||
Error :: union {
|
||||
General_Image_Error,
|
||||
PNG_Error,
|
||||
|
||||
compress.Error,
|
||||
compress.General_Error,
|
||||
compress.Deflate_Error,
|
||||
compress.ZLIB_Error,
|
||||
runtime.Allocator_Error,
|
||||
}
|
||||
|
||||
General_Image_Error :: enum {
|
||||
None = 0,
|
||||
Invalid_Image_Dimensions,
|
||||
Image_Dimensions_Too_Large,
|
||||
Image_Does_Not_Adhere_to_Spec,
|
||||
}
|
||||
|
||||
PNG_Error :: enum {
|
||||
Invalid_PNG_Signature,
|
||||
IHDR_Not_First_Chunk,
|
||||
IHDR_Corrupt,
|
||||
IDAT_Missing,
|
||||
IDAT_Must_Be_Contiguous,
|
||||
IDAT_Corrupt,
|
||||
PNG_Does_Not_Adhere_to_Spec,
|
||||
IDAT_Size_Too_Large,
|
||||
PLTE_Encountered_Unexpectedly,
|
||||
PLTE_Invalid_Length,
|
||||
TRNS_Encountered_Unexpectedly,
|
||||
BKGD_Invalid_Length,
|
||||
Invalid_Image_Dimensions,
|
||||
Unknown_Color_Type,
|
||||
Invalid_Color_Bit_Depth_Combo,
|
||||
Unknown_Filter_Method,
|
||||
Unknown_Interlace_Method,
|
||||
Requested_Channel_Not_Present,
|
||||
Post_Processing_Error,
|
||||
Invalid_Chunk_Length,
|
||||
}
|
||||
|
||||
/*
|
||||
PNG-specific structs
|
||||
*/
|
||||
PNG_Info :: struct {
|
||||
header: PNG_IHDR,
|
||||
chunks: [dynamic]PNG_Chunk,
|
||||
}
|
||||
|
||||
PNG_Chunk_Header :: struct #packed {
|
||||
length: u32be,
|
||||
type: PNG_Chunk_Type,
|
||||
}
|
||||
|
||||
PNG_Chunk :: struct #packed {
|
||||
header: PNG_Chunk_Header,
|
||||
data: []byte,
|
||||
crc: u32be,
|
||||
}
|
||||
|
||||
PNG_Chunk_Type :: enum u32be {
|
||||
// IHDR must come first in a file
|
||||
IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R',
|
||||
// PLTE must precede the first IDAT chunk
|
||||
PLTE = 'P' << 24 | 'L' << 16 | 'T' << 8 | 'E',
|
||||
bKGD = 'b' << 24 | 'K' << 16 | 'G' << 8 | 'D',
|
||||
tRNS = 't' << 24 | 'R' << 16 | 'N' << 8 | 'S',
|
||||
IDAT = 'I' << 24 | 'D' << 16 | 'A' << 8 | 'T',
|
||||
|
||||
iTXt = 'i' << 24 | 'T' << 16 | 'X' << 8 | 't',
|
||||
tEXt = 't' << 24 | 'E' << 16 | 'X' << 8 | 't',
|
||||
zTXt = 'z' << 24 | 'T' << 16 | 'X' << 8 | 't',
|
||||
|
||||
iCCP = 'i' << 24 | 'C' << 16 | 'C' << 8 | 'P',
|
||||
pHYs = 'p' << 24 | 'H' << 16 | 'Y' << 8 | 's',
|
||||
gAMA = 'g' << 24 | 'A' << 16 | 'M' << 8 | 'A',
|
||||
tIME = 't' << 24 | 'I' << 16 | 'M' << 8 | 'E',
|
||||
|
||||
sPLT = 's' << 24 | 'P' << 16 | 'L' << 8 | 'T',
|
||||
sRGB = 's' << 24 | 'R' << 16 | 'G' << 8 | 'B',
|
||||
hIST = 'h' << 24 | 'I' << 16 | 'S' << 8 | 'T',
|
||||
cHRM = 'c' << 24 | 'H' << 16 | 'R' << 8 | 'M',
|
||||
sBIT = 's' << 24 | 'B' << 16 | 'I' << 8 | 'T',
|
||||
|
||||
/*
|
||||
eXIf tags are not part of the core spec, but have been ratified
|
||||
in v1.5.0 of the PNG Ext register.
|
||||
|
||||
We will provide unprocessed chunks to the caller if `.return_metadata` is set.
|
||||
Applications are free to implement an Exif decoder.
|
||||
*/
|
||||
eXIf = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f',
|
||||
|
||||
// PNG files must end with IEND
|
||||
IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D',
|
||||
|
||||
/*
|
||||
XCode sometimes produces "PNG" files that don't adhere to the PNG spec.
|
||||
We recognize them only in order to avoid doing further work on them.
|
||||
|
||||
Some tools like PNG Defry may be able to repair them, but we're not
|
||||
going to reward Apple for producing proprietary broken files purporting
|
||||
to be PNGs by supporting them.
|
||||
|
||||
*/
|
||||
iDOT = 'i' << 24 | 'D' << 16 | 'O' << 8 | 'T',
|
||||
CbGI = 'C' << 24 | 'b' << 16 | 'H' << 8 | 'I',
|
||||
}
|
||||
|
||||
PNG_IHDR :: struct #packed {
|
||||
width: u32be,
|
||||
height: u32be,
|
||||
bit_depth: u8,
|
||||
color_type: PNG_Color_Type,
|
||||
compression_method: u8,
|
||||
filter_method: u8,
|
||||
interlace_method: PNG_Interlace_Method,
|
||||
}
|
||||
PNG_IHDR_SIZE :: size_of(PNG_IHDR)
|
||||
#assert (PNG_IHDR_SIZE == 13)
|
||||
|
||||
PNG_Color_Value :: enum u8 {
|
||||
Paletted = 0, // 1 << 0 = 1
|
||||
Color = 1, // 1 << 1 = 2
|
||||
Alpha = 2, // 1 << 2 = 4
|
||||
}
|
||||
PNG_Color_Type :: distinct bit_set[PNG_Color_Value; u8]
|
||||
|
||||
PNG_Interlace_Method :: enum u8 {
|
||||
None = 0,
|
||||
Adam7 = 1,
|
||||
}
|
||||
|
||||
/*
|
||||
Functions to help with image buffer calculations
|
||||
*/
|
||||
|
||||
compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
|
||||
size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height
|
||||
return
|
||||
@@ -145,7 +258,6 @@ compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes
|
||||
/*
|
||||
For when you have an RGB(A) image, but want a particular channel.
|
||||
*/
|
||||
|
||||
Channel :: enum u8 {
|
||||
R = 1,
|
||||
G = 2,
|
||||
@@ -207,8 +319,7 @@ return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok
|
||||
res.depth = img.depth
|
||||
res.pixels = t
|
||||
res.background = img.background
|
||||
res.metadata_ptr = img.metadata_ptr
|
||||
res.metadata_type = img.metadata_type
|
||||
res.metadata = img.metadata
|
||||
|
||||
return res, true
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
//+ignore
|
||||
package png
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
@@ -11,8 +8,9 @@ package png
|
||||
|
||||
An example of how to use `load`.
|
||||
*/
|
||||
//+ignore
|
||||
package png
|
||||
|
||||
import "core:compress"
|
||||
import "core:image"
|
||||
// import "core:image/png"
|
||||
import "core:bytes"
|
||||
@@ -41,8 +39,8 @@ main :: proc() {
|
||||
demo :: proc() {
|
||||
file: string
|
||||
|
||||
options := image.Options{} // {.return_metadata};
|
||||
err: compress.Error
|
||||
options := image.Options{.return_metadata}
|
||||
err: image.Error
|
||||
img: ^image.Image
|
||||
|
||||
file = "../../../misc/logo-slim.png"
|
||||
@@ -53,32 +51,33 @@ demo :: proc() {
|
||||
if err != nil {
|
||||
fmt.printf("Trying to read PNG file %v returned %v\n", file, err)
|
||||
} else {
|
||||
v: ^Info
|
||||
|
||||
fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth)
|
||||
if img.metadata_ptr != nil && img.metadata_type == Info {
|
||||
v = (^Info)(img.metadata_ptr)
|
||||
|
||||
if v, ok := img.metadata.(^image.PNG_Info); ok {
|
||||
// Handle ancillary chunks as you wish.
|
||||
// We provide helper functions for a few types.
|
||||
for c in v.chunks {
|
||||
#partial switch c.header.type {
|
||||
case .tIME:
|
||||
t, _ := core_time(c)
|
||||
fmt.printf("[tIME]: %v\n", t)
|
||||
if t, t_ok := core_time(c); t_ok {
|
||||
fmt.printf("[tIME]: %v\n", t)
|
||||
}
|
||||
case .gAMA:
|
||||
fmt.printf("[gAMA]: %v\n", gamma(c))
|
||||
if gama, gama_ok := gamma(c); gama_ok {
|
||||
fmt.printf("[gAMA]: %v\n", gama)
|
||||
}
|
||||
case .pHYs:
|
||||
phys := phys(c)
|
||||
if phys.unit == .Meter {
|
||||
xm := f32(img.width) / f32(phys.ppu_x)
|
||||
ym := f32(img.height) / f32(phys.ppu_y)
|
||||
dpi_x, dpi_y := phys_to_dpi(phys)
|
||||
fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y)
|
||||
fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y)
|
||||
fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym)
|
||||
} else {
|
||||
fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y)
|
||||
if phys, phys_ok := phys(c); phys_ok {
|
||||
if phys.unit == .Meter {
|
||||
xm := f32(img.width) / f32(phys.ppu_x)
|
||||
ym := f32(img.height) / f32(phys.ppu_y)
|
||||
dpi_x, dpi_y := phys_to_dpi(phys)
|
||||
fmt.printf("[pHYs] Image resolution is %v x %v pixels per meter.\n", phys.ppu_x, phys.ppu_y)
|
||||
fmt.printf("[pHYs] Image resolution is %v x %v DPI.\n", dpi_x, dpi_y)
|
||||
fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym)
|
||||
} else {
|
||||
fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y)
|
||||
}
|
||||
}
|
||||
case .iTXt, .zTXt, .tEXt:
|
||||
res, ok_text := text(c)
|
||||
@@ -93,8 +92,7 @@ demo :: proc() {
|
||||
case .bKGD:
|
||||
fmt.printf("[bKGD] %v\n", img.background)
|
||||
case .eXIf:
|
||||
res, ok_exif := exif(c)
|
||||
if ok_exif {
|
||||
if res, ok_exif := exif(c); ok_exif {
|
||||
/*
|
||||
Other than checking the signature and byte order, we don't handle Exif data.
|
||||
If you wish to interpret it, pass it to an Exif parser.
|
||||
@@ -102,20 +100,17 @@ demo :: proc() {
|
||||
fmt.printf("[eXIf] %v\n", res)
|
||||
}
|
||||
case .PLTE:
|
||||
plte, plte_ok := plte(c)
|
||||
if plte_ok {
|
||||
if plte, plte_ok := plte(c); plte_ok {
|
||||
fmt.printf("[PLTE] %v\n", plte)
|
||||
} else {
|
||||
fmt.printf("[PLTE] Error\n")
|
||||
}
|
||||
case .hIST:
|
||||
res, ok_hist := hist(c)
|
||||
if ok_hist {
|
||||
if res, ok_hist := hist(c); ok_hist {
|
||||
fmt.printf("[hIST] %v\n", res)
|
||||
}
|
||||
case .cHRM:
|
||||
res, ok_chrm := chrm(c)
|
||||
if ok_chrm {
|
||||
if res, ok_chrm := chrm(c); ok_chrm {
|
||||
fmt.printf("[cHRM] %v\n", res)
|
||||
}
|
||||
case .sPLT:
|
||||
@@ -147,6 +142,8 @@ demo :: proc() {
|
||||
}
|
||||
}
|
||||
|
||||
fmt.printf("Done parsing metadata.\n")
|
||||
|
||||
if err == nil && .do_not_decompress_image not_in options && .info not_in options {
|
||||
if ok := write_image_as_ppm("out.ppm", img); ok {
|
||||
fmt.println("Saved decoded image.")
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
package png
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
@@ -10,6 +8,7 @@ package png
|
||||
|
||||
These are a few useful utility functions to work with PNG images.
|
||||
*/
|
||||
package png
|
||||
|
||||
import "core:image"
|
||||
import "core:compress/zlib"
|
||||
@@ -34,15 +33,14 @@ destroy :: proc(img: ^Image) {
|
||||
}
|
||||
|
||||
bytes.buffer_destroy(&img.pixels)
|
||||
// Clean up Info.
|
||||
free(img.metadata_ptr)
|
||||
|
||||
/*
|
||||
We don't need to do anything for the individual chunks.
|
||||
They're allocated on the temp allocator, as is info.chunks
|
||||
|
||||
See read_chunk.
|
||||
*/
|
||||
if v, ok := img.metadata.(^image.PNG_Info); ok {
|
||||
for chunk in &v.chunks {
|
||||
delete(chunk.data)
|
||||
}
|
||||
delete(v.chunks)
|
||||
free(v)
|
||||
}
|
||||
free(img)
|
||||
}
|
||||
|
||||
@@ -50,46 +48,50 @@ destroy :: proc(img: ^Image) {
|
||||
Chunk helpers
|
||||
*/
|
||||
|
||||
gamma :: proc(c: Chunk) -> f32 {
|
||||
assert(c.header.type == .gAMA)
|
||||
res := (^gAMA)(raw_data(c.data))^
|
||||
when true {
|
||||
// Returns the wrong result on old backend
|
||||
// Fixed for -llvm-api
|
||||
return f32(res.gamma_100k) / 100_000.0
|
||||
} else {
|
||||
return f32(u32(res.gamma_100k)) / 100_000.0
|
||||
gamma :: proc(c: image.PNG_Chunk) -> (res: f32, ok: bool) {
|
||||
if c.header.type != .gAMA || len(c.data) != size_of(gAMA) {
|
||||
return {}, false
|
||||
}
|
||||
gama := (^gAMA)(raw_data(c.data))^
|
||||
return f32(gama.gamma_100k) / 100_000.0, true
|
||||
}
|
||||
|
||||
INCHES_PER_METER :: 1000.0 / 25.4
|
||||
|
||||
phys :: proc(c: Chunk) -> pHYs {
|
||||
assert(c.header.type == .pHYs)
|
||||
res := (^pHYs)(raw_data(c.data))^
|
||||
return res
|
||||
phys :: proc(c: image.PNG_Chunk) -> (res: pHYs, ok: bool) {
|
||||
if c.header.type != .pHYs || len(c.data) != size_of(pHYs) {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
return (^pHYs)(raw_data(c.data))^, true
|
||||
}
|
||||
|
||||
phys_to_dpi :: proc(p: pHYs) -> (x_dpi, y_dpi: f32) {
|
||||
return f32(p.ppu_x) / INCHES_PER_METER, f32(p.ppu_y) / INCHES_PER_METER
|
||||
}
|
||||
|
||||
time :: proc(c: Chunk) -> tIME {
|
||||
assert(c.header.type == .tIME)
|
||||
res := (^tIME)(raw_data(c.data))^
|
||||
return res
|
||||
time :: proc(c: image.PNG_Chunk) -> (res: tIME, ok: bool) {
|
||||
if c.header.type != .tIME || len(c.data) != size_of(tIME) {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
return (^tIME)(raw_data(c.data))^, true
|
||||
}
|
||||
|
||||
core_time :: proc(c: Chunk) -> (t: coretime.Time, ok: bool) {
|
||||
png_time := time(c)
|
||||
using png_time
|
||||
return coretime.datetime_to_time(
|
||||
int(year), int(month), int(day),
|
||||
int(hour), int(minute), int(second),
|
||||
)
|
||||
core_time :: proc(c: image.PNG_Chunk) -> (t: coretime.Time, ok: bool) {
|
||||
if png_time, png_ok := time(c); png_ok {
|
||||
using png_time
|
||||
return coretime.datetime_to_time(
|
||||
int(year), int(month), int(day),
|
||||
int(hour), int(minute), int(second),
|
||||
)
|
||||
} else {
|
||||
return {}, false
|
||||
}
|
||||
}
|
||||
|
||||
text :: proc(c: Chunk) -> (res: Text, ok: bool) {
|
||||
text :: proc(c: image.PNG_Chunk) -> (res: Text, ok: bool) {
|
||||
assert(len(c.data) == int(c.header.length))
|
||||
#partial switch c.header.type {
|
||||
case .tEXt:
|
||||
ok = true
|
||||
@@ -191,7 +193,7 @@ text_destroy :: proc(text: Text) {
|
||||
delete(text.text)
|
||||
}
|
||||
|
||||
iccp :: proc(c: Chunk) -> (res: iCCP, ok: bool) {
|
||||
iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
|
||||
ok = true
|
||||
|
||||
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator)
|
||||
@@ -227,10 +229,8 @@ iccp_destroy :: proc(i: iCCP) {
|
||||
|
||||
}
|
||||
|
||||
srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) {
|
||||
ok = true
|
||||
|
||||
if c.header.type != .sRGB || len(c.data) != 1 {
|
||||
srgb :: proc(c: image.PNG_Chunk) -> (res: sRGB, ok: bool) {
|
||||
if c.header.type != .sRGB || len(c.data) != size_of(sRGB_Rendering_Intent) {
|
||||
return {}, false
|
||||
}
|
||||
|
||||
@@ -238,10 +238,10 @@ srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) {
|
||||
if res.intent > max(sRGB_Rendering_Intent) {
|
||||
ok = false; return
|
||||
}
|
||||
return
|
||||
return res, true
|
||||
}
|
||||
|
||||
plte :: proc(c: Chunk) -> (res: PLTE, ok: bool) {
|
||||
plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, ok: bool) {
|
||||
if c.header.type != .PLTE {
|
||||
return {}, false
|
||||
}
|
||||
@@ -255,7 +255,7 @@ plte :: proc(c: Chunk) -> (res: PLTE, ok: bool) {
|
||||
return
|
||||
}
|
||||
|
||||
splt :: proc(c: Chunk) -> (res: sPLT, ok: bool) {
|
||||
splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
|
||||
if c.header.type != .sPLT {
|
||||
return {}, false
|
||||
}
|
||||
@@ -306,7 +306,7 @@ splt_destroy :: proc(s: sPLT) {
|
||||
delete(s.name)
|
||||
}
|
||||
|
||||
sbit :: proc(c: Chunk) -> (res: [4]u8, ok: bool) {
|
||||
sbit :: proc(c: image.PNG_Chunk) -> (res: [4]u8, ok: bool) {
|
||||
/*
|
||||
Returns [4]u8 with the significant bits in each channel.
|
||||
A channel will contain zero if not applicable to the PNG color type.
|
||||
@@ -324,7 +324,7 @@ sbit :: proc(c: Chunk) -> (res: [4]u8, ok: bool) {
|
||||
|
||||
}
|
||||
|
||||
hist :: proc(c: Chunk) -> (res: hIST, ok: bool) {
|
||||
hist :: proc(c: image.PNG_Chunk) -> (res: hIST, ok: bool) {
|
||||
if c.header.type != .hIST {
|
||||
return {}, false
|
||||
}
|
||||
@@ -346,7 +346,7 @@ hist :: proc(c: Chunk) -> (res: hIST, ok: bool) {
|
||||
return
|
||||
}
|
||||
|
||||
chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) {
|
||||
chrm :: proc(c: image.PNG_Chunk) -> (res: cHRM, ok: bool) {
|
||||
ok = true
|
||||
if c.header.length != size_of(cHRM_Raw) {
|
||||
return {}, false
|
||||
@@ -364,7 +364,7 @@ chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) {
|
||||
return
|
||||
}
|
||||
|
||||
exif :: proc(c: Chunk) -> (res: Exif, ok: bool) {
|
||||
exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) {
|
||||
|
||||
ok = true
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package png
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-2 license.
|
||||
Made available under Odin's BSD-3 license.
|
||||
|
||||
List of contributors:
|
||||
Jeroen van Rijn: Initial implementation.
|
||||
Ginger Bill: Cosmetic changes.
|
||||
*/
|
||||
package png
|
||||
|
||||
import "core:compress"
|
||||
import "core:compress/zlib"
|
||||
@@ -21,11 +20,29 @@ import "core:io"
|
||||
import "core:mem"
|
||||
import "core:intrinsics"
|
||||
|
||||
Error :: compress.Error
|
||||
E_General :: compress.General_Error
|
||||
E_PNG :: image.Error
|
||||
E_Deflate :: compress.Deflate_Error
|
||||
/*
|
||||
67_108_864 pixels max by default.
|
||||
Maximum allowed dimensions are capped at 65535 * 65535.
|
||||
*/
|
||||
MAX_DIMENSIONS :: min(#config(PNG_MAX_DIMENSIONS, 8192 * 8192), 65535 * 65535)
|
||||
|
||||
/*
|
||||
Limit chunk sizes.
|
||||
By default: IDAT = 8k x 8k x 16-bits + 8k filter bytes.
|
||||
*/
|
||||
_MAX_IDAT_DEFAULT :: ( 8192 /* Width */ * 8192 /* Height */ * 2 /* 16-bit */) + 8192 /* Filter bytes */
|
||||
_MAX_IDAT :: (65535 /* Width */ * 65535 /* Height */ * 2 /* 16-bit */) + 65535 /* Filter bytes */
|
||||
|
||||
MAX_IDAT_SIZE :: min(#config(PNG_MAX_IDAT_SIZE, _MAX_IDAT_DEFAULT), _MAX_IDAT)
|
||||
|
||||
/*
|
||||
For chunks other than IDAT with a variable size like `zTXT` and `eXIf`,
|
||||
limit their size to 16 MiB each by default. Max of 256 MiB each.
|
||||
*/
|
||||
MAX_CHUNK_SIZE :: min(#config(PNG_MAX_CHUNK_SIZE, 16_777_216), 268_435_456)
|
||||
|
||||
|
||||
Error :: image.Error
|
||||
Image :: image.Image
|
||||
Options :: image.Options
|
||||
|
||||
@@ -34,95 +51,6 @@ Signature :: enum u64be {
|
||||
PNG = 0x89 << 56 | 'P' << 48 | 'N' << 40 | 'G' << 32 | '\r' << 24 | '\n' << 16 | 0x1a << 8 | '\n',
|
||||
}
|
||||
|
||||
Info :: struct {
|
||||
header: IHDR,
|
||||
chunks: [dynamic]Chunk,
|
||||
}
|
||||
|
||||
Chunk_Header :: struct #packed {
|
||||
length: u32be,
|
||||
type: Chunk_Type,
|
||||
}
|
||||
|
||||
Chunk :: struct #packed {
|
||||
header: Chunk_Header,
|
||||
data: []byte,
|
||||
crc: u32be,
|
||||
}
|
||||
|
||||
Chunk_Type :: enum u32be {
|
||||
// IHDR must come first in a file
|
||||
IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R',
|
||||
// PLTE must precede the first IDAT chunk
|
||||
PLTE = 'P' << 24 | 'L' << 16 | 'T' << 8 | 'E',
|
||||
bKGD = 'b' << 24 | 'K' << 16 | 'G' << 8 | 'D',
|
||||
tRNS = 't' << 24 | 'R' << 16 | 'N' << 8 | 'S',
|
||||
IDAT = 'I' << 24 | 'D' << 16 | 'A' << 8 | 'T',
|
||||
|
||||
iTXt = 'i' << 24 | 'T' << 16 | 'X' << 8 | 't',
|
||||
tEXt = 't' << 24 | 'E' << 16 | 'X' << 8 | 't',
|
||||
zTXt = 'z' << 24 | 'T' << 16 | 'X' << 8 | 't',
|
||||
|
||||
iCCP = 'i' << 24 | 'C' << 16 | 'C' << 8 | 'P',
|
||||
pHYs = 'p' << 24 | 'H' << 16 | 'Y' << 8 | 's',
|
||||
gAMA = 'g' << 24 | 'A' << 16 | 'M' << 8 | 'A',
|
||||
tIME = 't' << 24 | 'I' << 16 | 'M' << 8 | 'E',
|
||||
|
||||
sPLT = 's' << 24 | 'P' << 16 | 'L' << 8 | 'T',
|
||||
sRGB = 's' << 24 | 'R' << 16 | 'G' << 8 | 'B',
|
||||
hIST = 'h' << 24 | 'I' << 16 | 'S' << 8 | 'T',
|
||||
cHRM = 'c' << 24 | 'H' << 16 | 'R' << 8 | 'M',
|
||||
sBIT = 's' << 24 | 'B' << 16 | 'I' << 8 | 'T',
|
||||
|
||||
/*
|
||||
eXIf tags are not part of the core spec, but have been ratified
|
||||
in v1.5.0 of the PNG Ext register.
|
||||
|
||||
We will provide unprocessed chunks to the caller if `.return_metadata` is set.
|
||||
Applications are free to implement an Exif decoder.
|
||||
*/
|
||||
eXIf = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f',
|
||||
|
||||
// PNG files must end with IEND
|
||||
IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D',
|
||||
|
||||
/*
|
||||
XCode sometimes produces "PNG" files that don't adhere to the PNG spec.
|
||||
We recognize them only in order to avoid doing further work on them.
|
||||
|
||||
Some tools like PNG Defry may be able to repair them, but we're not
|
||||
going to reward Apple for producing proprietary broken files purporting
|
||||
to be PNGs by supporting them.
|
||||
|
||||
*/
|
||||
iDOT = 'i' << 24 | 'D' << 16 | 'O' << 8 | 'T',
|
||||
CbGI = 'C' << 24 | 'b' << 16 | 'H' << 8 | 'I',
|
||||
}
|
||||
|
||||
IHDR :: struct #packed {
|
||||
width: u32be,
|
||||
height: u32be,
|
||||
bit_depth: u8,
|
||||
color_type: Color_Type,
|
||||
compression_method: u8,
|
||||
filter_method: u8,
|
||||
interlace_method: Interlace_Method,
|
||||
}
|
||||
IHDR_SIZE :: size_of(IHDR)
|
||||
#assert (IHDR_SIZE == 13)
|
||||
|
||||
Color_Value :: enum u8 {
|
||||
Paletted = 0, // 1 << 0 = 1
|
||||
Color = 1, // 1 << 1 = 2
|
||||
Alpha = 2, // 1 << 2 = 4
|
||||
}
|
||||
Color_Type :: distinct bit_set[Color_Value; u8]
|
||||
|
||||
Interlace_Method :: enum u8 {
|
||||
None = 0,
|
||||
Adam7 = 1,
|
||||
}
|
||||
|
||||
Row_Filter :: enum u8 {
|
||||
None = 0,
|
||||
Sub = 1,
|
||||
@@ -135,22 +63,22 @@ PLTE_Entry :: [3]u8
|
||||
|
||||
PLTE :: struct #packed {
|
||||
entries: [256]PLTE_Entry,
|
||||
used: u16,
|
||||
used: u16,
|
||||
}
|
||||
|
||||
hIST :: struct #packed {
|
||||
entries: [256]u16,
|
||||
used: u16,
|
||||
used: u16,
|
||||
}
|
||||
|
||||
sPLT :: struct #packed {
|
||||
name: string,
|
||||
depth: u8,
|
||||
name: string,
|
||||
depth: u8,
|
||||
entries: union {
|
||||
[][4]u8,
|
||||
[][4]u16,
|
||||
},
|
||||
used: u16,
|
||||
used: u16,
|
||||
}
|
||||
|
||||
// Other chunks
|
||||
@@ -223,14 +151,14 @@ Exif :: struct {
|
||||
}
|
||||
|
||||
iCCP :: struct {
|
||||
name: string,
|
||||
name: string,
|
||||
profile: []u8,
|
||||
}
|
||||
|
||||
sRGB_Rendering_Intent :: enum u8 {
|
||||
Perceptual = 0,
|
||||
Perceptual = 0,
|
||||
Relative_colorimetric = 1,
|
||||
Saturation = 2,
|
||||
Saturation = 2,
|
||||
Absolute_colorimetric = 3,
|
||||
}
|
||||
|
||||
@@ -245,16 +173,30 @@ ADAM7_Y_SPACING := []int{ 8,8,8,4,4,2,2 }
|
||||
|
||||
// Implementation starts here
|
||||
|
||||
read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) {
|
||||
ch, e := compress.read_data(ctx, Chunk_Header)
|
||||
read_chunk :: proc(ctx: ^$C) -> (chunk: image.PNG_Chunk, err: Error) {
|
||||
ch, e := compress.read_data(ctx, image.PNG_Chunk_Header)
|
||||
if e != .None {
|
||||
return {}, E_General.Stream_Too_Short
|
||||
return {}, compress.General_Error.Stream_Too_Short
|
||||
}
|
||||
chunk.header = ch
|
||||
|
||||
/*
|
||||
Sanity check chunk size
|
||||
*/
|
||||
#partial switch ch.type {
|
||||
case .IDAT:
|
||||
if ch.length > MAX_IDAT_SIZE {
|
||||
return {}, image.PNG_Error.IDAT_Size_Too_Large
|
||||
}
|
||||
case:
|
||||
if ch.length > MAX_CHUNK_SIZE {
|
||||
return {}, image.PNG_Error.Invalid_Chunk_Length
|
||||
}
|
||||
}
|
||||
|
||||
chunk.data, e = compress.read_slice(ctx, int(ch.length))
|
||||
if e != .None {
|
||||
return {}, E_General.Stream_Too_Short
|
||||
return {}, compress.General_Error.Stream_Too_Short
|
||||
}
|
||||
|
||||
// Compute CRC over chunk type + data
|
||||
@@ -264,39 +206,68 @@ read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) {
|
||||
|
||||
crc, e3 := compress.read_data(ctx, u32be)
|
||||
if e3 != .None {
|
||||
return {}, E_General.Stream_Too_Short
|
||||
return {}, compress.General_Error.Stream_Too_Short
|
||||
}
|
||||
chunk.crc = crc
|
||||
|
||||
if chunk.crc != u32be(computed_crc) {
|
||||
return {}, E_General.Checksum_Failed
|
||||
return {}, compress.General_Error.Checksum_Failed
|
||||
}
|
||||
return chunk, nil
|
||||
}
|
||||
|
||||
read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
|
||||
copy_chunk :: proc(src: image.PNG_Chunk, allocator := context.allocator) -> (dest: image.PNG_Chunk, err: Error) {
|
||||
if int(src.header.length) != len(src.data) {
|
||||
return {}, .Invalid_Chunk_Length
|
||||
}
|
||||
|
||||
dest.header = src.header
|
||||
dest.crc = src.crc
|
||||
dest.data = make([]u8, dest.header.length, allocator) or_return
|
||||
|
||||
copy(dest.data[:], src.data[:])
|
||||
return
|
||||
}
|
||||
|
||||
append_chunk :: proc(list: ^[dynamic]image.PNG_Chunk, src: image.PNG_Chunk, allocator := context.allocator) -> (err: Error) {
|
||||
if int(src.header.length) != len(src.data) {
|
||||
return .Invalid_Chunk_Length
|
||||
}
|
||||
|
||||
c := copy_chunk(src, allocator) or_return
|
||||
length := len(list)
|
||||
append(list, c)
|
||||
if len(list) != length + 1 {
|
||||
// Resize during append failed.
|
||||
return mem.Allocator_Error.Out_Of_Memory
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) {
|
||||
c, e := read_chunk(ctx)
|
||||
if e != nil {
|
||||
return {}, e
|
||||
}
|
||||
|
||||
header := (^IHDR)(raw_data(c.data))^
|
||||
header := (^image.PNG_IHDR)(raw_data(c.data))^
|
||||
// Validate IHDR
|
||||
using header
|
||||
if width == 0 || height == 0 {
|
||||
return {}, E_PNG.Invalid_Image_Dimensions
|
||||
if width == 0 || height == 0 || u128(width) * u128(height) > MAX_DIMENSIONS {
|
||||
return {}, .Invalid_Image_Dimensions
|
||||
}
|
||||
|
||||
if compression_method != 0 {
|
||||
return {}, E_General.Unknown_Compression_Method
|
||||
return {}, compress.General_Error.Unknown_Compression_Method
|
||||
}
|
||||
|
||||
if filter_method != 0 {
|
||||
return {}, E_PNG.Unknown_Filter_Method
|
||||
return {}, .Unknown_Filter_Method
|
||||
}
|
||||
|
||||
if interlace_method != .None && interlace_method != .Adam7 {
|
||||
return {}, E_PNG.Unknown_Interlace_Method
|
||||
return {}, .Unknown_Interlace_Method
|
||||
|
||||
}
|
||||
|
||||
@@ -314,7 +285,7 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo
|
||||
return {}, .Invalid_Color_Bit_Depth_Combo
|
||||
}
|
||||
case 2, 4, 6:
|
||||
/*
|
||||
@@ -322,7 +293,7 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
|
||||
Allowed bit depths: 8 and 16
|
||||
*/
|
||||
if bit_depth != 8 && bit_depth != 16 {
|
||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo
|
||||
return {}, .Invalid_Color_Bit_Depth_Combo
|
||||
}
|
||||
case 3:
|
||||
/*
|
||||
@@ -337,17 +308,17 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo
|
||||
return {}, .Invalid_Color_Bit_Depth_Combo
|
||||
}
|
||||
|
||||
case:
|
||||
return {}, E_PNG.Unknown_Color_Type
|
||||
return {}, .Unknown_Color_Type
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
chunk_type_to_name :: proc(type: ^Chunk_Type) -> string {
|
||||
chunk_type_to_name :: proc(type: ^image.PNG_Chunk_Type) -> string {
|
||||
t := transmute(^u8)type
|
||||
return strings.string_from_ptr(t, 4)
|
||||
}
|
||||
@@ -377,7 +348,7 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont
|
||||
return load_from_slice(data, options)
|
||||
} else {
|
||||
img = new(Image)
|
||||
return img, E_General.File_Not_Found
|
||||
return img, compress.General_Error.File_Not_Found
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,7 +362,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
}
|
||||
|
||||
if .alpha_drop_if_present in options && .alpha_add_if_missing in options {
|
||||
return {}, E_General.Incompatible_Options
|
||||
return {}, compress.General_Error.Incompatible_Options
|
||||
}
|
||||
|
||||
if .do_not_expand_channels in options {
|
||||
@@ -402,27 +373,25 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
img = new(Image)
|
||||
}
|
||||
|
||||
info := new(Info)
|
||||
img.metadata_ptr = info
|
||||
img.metadata_type = typeid_of(Info)
|
||||
info := new(image.PNG_Info)
|
||||
img.metadata = info
|
||||
|
||||
signature, io_error := compress.read_data(ctx, Signature)
|
||||
if io_error != .None || signature != .PNG {
|
||||
return img, E_PNG.Invalid_PNG_Signature
|
||||
return img, .Invalid_PNG_Signature
|
||||
}
|
||||
|
||||
idat: []u8
|
||||
idat_b: bytes.Buffer
|
||||
idat_length := u32be(0)
|
||||
defer bytes.buffer_destroy(&idat_b)
|
||||
|
||||
c: Chunk
|
||||
ch: Chunk_Header
|
||||
idat_length := u64(0)
|
||||
|
||||
c: image.PNG_Chunk
|
||||
ch: image.PNG_Chunk_Header
|
||||
e: io.Error
|
||||
|
||||
header: IHDR
|
||||
|
||||
info.chunks.allocator = context.temp_allocator
|
||||
header: image.PNG_IHDR
|
||||
|
||||
// State to ensure correct chunk ordering.
|
||||
seen_ihdr := false; first := true
|
||||
@@ -433,7 +402,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
seen_iend := false
|
||||
|
||||
_plte := PLTE{}
|
||||
trns := Chunk{}
|
||||
trns := image.PNG_Chunk{}
|
||||
|
||||
final_image_channels := 0
|
||||
|
||||
@@ -443,16 +412,16 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
// Peek at next chunk's length and type.
|
||||
// TODO: Some streams may not provide seek/read_at
|
||||
|
||||
ch, e = compress.peek_data(ctx, Chunk_Header)
|
||||
ch, e = compress.peek_data(ctx, image.PNG_Chunk_Header)
|
||||
if e != .None {
|
||||
return img, E_General.Stream_Too_Short
|
||||
return img, compress.General_Error.Stream_Too_Short
|
||||
}
|
||||
// name := chunk_type_to_name(&ch.type); // Only used for debug prints during development.
|
||||
|
||||
#partial switch ch.type {
|
||||
case .IHDR:
|
||||
if seen_ihdr || !first {
|
||||
return {}, E_PNG.IHDR_Not_First_Chunk
|
||||
return {}, .IHDR_Not_First_Chunk
|
||||
}
|
||||
seen_ihdr = true
|
||||
|
||||
@@ -481,14 +450,14 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
}
|
||||
|
||||
if img.channels == 0 || img.depth == 0 {
|
||||
return {}, E_PNG.IHDR_Corrupt
|
||||
return {}, .IHDR_Corrupt
|
||||
}
|
||||
|
||||
img.width = int(header.width)
|
||||
img.height = int(header.height)
|
||||
|
||||
using header
|
||||
h := IHDR{
|
||||
h := image.PNG_IHDR{
|
||||
width = width,
|
||||
height = height,
|
||||
bit_depth = bit_depth,
|
||||
@@ -498,28 +467,30 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
interlace_method = interlace_method,
|
||||
}
|
||||
info.header = h
|
||||
|
||||
case .PLTE:
|
||||
seen_plte = true
|
||||
// PLTE must appear before IDAT and can't appear for color types 0, 4.
|
||||
ct := transmute(u8)info.header.color_type
|
||||
if seen_idat || ct == 0 || ct == 4 {
|
||||
return img, E_PNG.PLTE_Encountered_Unexpectedly
|
||||
return img, .PLTE_Encountered_Unexpectedly
|
||||
}
|
||||
|
||||
c = read_chunk(ctx) or_return
|
||||
|
||||
if c.header.length % 3 != 0 || c.header.length > 768 {
|
||||
return img, E_PNG.PLTE_Invalid_Length
|
||||
return img, .PLTE_Invalid_Length
|
||||
}
|
||||
plte_ok: bool
|
||||
_plte, plte_ok = plte(c)
|
||||
if !plte_ok {
|
||||
return img, E_PNG.PLTE_Invalid_Length
|
||||
return img, .PLTE_Invalid_Length
|
||||
}
|
||||
|
||||
if .return_metadata in options {
|
||||
append(&info.chunks, c)
|
||||
append_chunk(&info.chunks, c) or_return
|
||||
}
|
||||
|
||||
case .IDAT:
|
||||
// If we only want image metadata and don't want the pixel data, we can early out.
|
||||
if .return_metadata not_in options && .do_not_decompress_image in options {
|
||||
@@ -528,11 +499,11 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
}
|
||||
// There must be at least 1 IDAT, contiguous if more.
|
||||
if seen_idat {
|
||||
return img, E_PNG.IDAT_Must_Be_Contiguous
|
||||
return img, .IDAT_Must_Be_Contiguous
|
||||
}
|
||||
|
||||
if idat_length > 0 {
|
||||
return img, E_PNG.IDAT_Must_Be_Contiguous
|
||||
return img, .IDAT_Must_Be_Contiguous
|
||||
}
|
||||
|
||||
next := ch.type
|
||||
@@ -540,22 +511,29 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
c = read_chunk(ctx) or_return
|
||||
|
||||
bytes.buffer_write(&idat_b, c.data)
|
||||
idat_length += c.header.length
|
||||
idat_length += u64(c.header.length)
|
||||
|
||||
ch, e = compress.peek_data(ctx, Chunk_Header)
|
||||
if idat_length > MAX_IDAT_SIZE {
|
||||
return {}, image.PNG_Error.IDAT_Size_Too_Large
|
||||
}
|
||||
|
||||
ch, e = compress.peek_data(ctx, image.PNG_Chunk_Header)
|
||||
if e != .None {
|
||||
return img, E_General.Stream_Too_Short
|
||||
return img, compress.General_Error.Stream_Too_Short
|
||||
}
|
||||
next = ch.type
|
||||
}
|
||||
|
||||
idat = bytes.buffer_to_bytes(&idat_b)
|
||||
if int(idat_length) != len(idat) {
|
||||
return {}, E_PNG.IDAT_Corrupt
|
||||
return {}, .IDAT_Corrupt
|
||||
}
|
||||
seen_idat = true
|
||||
|
||||
case .IEND:
|
||||
c = read_chunk(ctx) or_return
|
||||
seen_iend = true
|
||||
|
||||
case .bKGD:
|
||||
|
||||
// TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be
|
||||
@@ -563,14 +541,14 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
c = read_chunk(ctx) or_return
|
||||
seen_bkgd = true
|
||||
if .return_metadata in options {
|
||||
append(&info.chunks, c)
|
||||
append_chunk(&info.chunks, c) or_return
|
||||
}
|
||||
|
||||
ct := transmute(u8)info.header.color_type
|
||||
switch ct {
|
||||
case 3: // Indexed color
|
||||
if c.header.length != 1 {
|
||||
return {}, E_PNG.BKGD_Invalid_Length
|
||||
return {}, .BKGD_Invalid_Length
|
||||
}
|
||||
col := _plte.entries[c.data[0]]
|
||||
img.background = [3]u16{
|
||||
@@ -580,26 +558,27 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
}
|
||||
case 0, 4: // Grayscale, with and without Alpha
|
||||
if c.header.length != 2 {
|
||||
return {}, E_PNG.BKGD_Invalid_Length
|
||||
return {}, .BKGD_Invalid_Length
|
||||
}
|
||||
col := u16(mem.slice_data_cast([]u16be, c.data[:])[0])
|
||||
img.background = [3]u16{col, col, col}
|
||||
case 2, 6: // Color, with and without Alpha
|
||||
if c.header.length != 6 {
|
||||
return {}, E_PNG.BKGD_Invalid_Length
|
||||
return {}, .BKGD_Invalid_Length
|
||||
}
|
||||
col := mem.slice_data_cast([]u16be, c.data[:])
|
||||
img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])}
|
||||
}
|
||||
|
||||
case .tRNS:
|
||||
c = read_chunk(ctx) or_return
|
||||
|
||||
if .Alpha in info.header.color_type {
|
||||
return img, E_PNG.TRNS_Encountered_Unexpectedly
|
||||
return img, .TRNS_Encountered_Unexpectedly
|
||||
}
|
||||
|
||||
if .return_metadata in options {
|
||||
append(&info.chunks, c)
|
||||
append_chunk(&info.chunks, c) or_return
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -622,20 +601,20 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
}
|
||||
}
|
||||
trns = c
|
||||
|
||||
case .iDOT, .CbGI:
|
||||
/*
|
||||
iPhone PNG bastardization that doesn't adhere to spec with broken IDAT chunk.
|
||||
We're not going to add support for it. If you have the misfortunte of coming
|
||||
across one of these files, use a utility to defry it.s
|
||||
across one of these files, use a utility to defry it.
|
||||
*/
|
||||
return img, E_PNG.PNG_Does_Not_Adhere_to_Spec
|
||||
return img, .Image_Does_Not_Adhere_to_Spec
|
||||
|
||||
case:
|
||||
// Unhandled type
|
||||
c = read_chunk(ctx) or_return
|
||||
|
||||
if .return_metadata in options {
|
||||
// NOTE: Chunk cata is currently allocated on the temp allocator.
|
||||
append(&info.chunks, c)
|
||||
append_chunk(&info.chunks, c) or_return
|
||||
}
|
||||
|
||||
first = false
|
||||
@@ -648,7 +627,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
}
|
||||
|
||||
if !seen_idat {
|
||||
return img, E_PNG.IDAT_Missing
|
||||
return img, .IDAT_Missing
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -685,7 +664,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
|
||||
buf_len := len(buf.buf)
|
||||
if expected_size != buf_len {
|
||||
return {}, E_PNG.IDAT_Corrupt
|
||||
return {}, .IDAT_Corrupt
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -752,7 +731,9 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
// We need to create a new image buffer
|
||||
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
|
||||
t := bytes.Buffer{}
|
||||
resize(&t.buf, dest_raw_size)
|
||||
if !resize(&t.buf, dest_raw_size) {
|
||||
return {}, mem.Allocator_Error.Out_Of_Memory
|
||||
}
|
||||
|
||||
i := 0; j := 0
|
||||
|
||||
@@ -831,7 +812,9 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
// We need to create a new image buffer
|
||||
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 16)
|
||||
t := bytes.Buffer{}
|
||||
resize(&t.buf, dest_raw_size)
|
||||
if !resize(&t.buf, dest_raw_size) {
|
||||
return {}, mem.Allocator_Error.Out_Of_Memory
|
||||
}
|
||||
|
||||
p16 := mem.slice_data_cast([]u16, temp.buf[:])
|
||||
o16 := mem.slice_data_cast([]u16, t.buf[:])
|
||||
@@ -1028,7 +1011,9 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
||||
// We need to create a new image buffer
|
||||
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
|
||||
t := bytes.Buffer{}
|
||||
resize(&t.buf, dest_raw_size)
|
||||
if !resize(&t.buf, dest_raw_size) {
|
||||
return {}, mem.Allocator_Error.Out_Of_Memory
|
||||
}
|
||||
|
||||
p := mem.slice_data_cast([]u8, temp.buf[:])
|
||||
o := mem.slice_data_cast([]u8, t.buf[:])
|
||||
@@ -1524,7 +1509,7 @@ defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
||||
return
|
||||
}
|
||||
|
||||
defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, options: Options) -> (err: compress.Error) {
|
||||
defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IHDR, options: Options) -> (err: Error) {
|
||||
input := bytes.buffer_to_bytes(filter_bytes)
|
||||
width := int(header.width)
|
||||
height := int(header.height)
|
||||
@@ -1535,7 +1520,9 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option
|
||||
bytes_per_channel := depth == 16 ? 2 : 1
|
||||
|
||||
num_bytes := compute_buffer_size(width, height, channels, depth == 16 ? 16 : 8)
|
||||
resize(&img.pixels.buf, num_bytes)
|
||||
if !resize(&img.pixels.buf, num_bytes) {
|
||||
return mem.Allocator_Error.Out_Of_Memory
|
||||
}
|
||||
|
||||
filter_ok: bool
|
||||
|
||||
@@ -1560,7 +1547,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option
|
||||
}
|
||||
if !filter_ok {
|
||||
// Caller will destroy buffer for us.
|
||||
return E_PNG.Unknown_Filter_Method
|
||||
return .Unknown_Filter_Method
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
@@ -1575,7 +1562,9 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option
|
||||
if x > 0 && y > 0 {
|
||||
temp: bytes.Buffer
|
||||
temp_len := compute_buffer_size(x, y, channels, depth == 16 ? 16 : 8)
|
||||
resize(&temp.buf, temp_len)
|
||||
if !resize(&temp.buf, temp_len) {
|
||||
return mem.Allocator_Error.Out_Of_Memory
|
||||
}
|
||||
|
||||
params := Filter_Params{
|
||||
src = input,
|
||||
@@ -1598,7 +1587,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option
|
||||
|
||||
if !filter_ok {
|
||||
// Caller will destroy buffer for us.
|
||||
return E_PNG.Unknown_Filter_Method
|
||||
return .Unknown_Filter_Method
|
||||
}
|
||||
|
||||
t := temp.buf[:]
|
||||
|
||||
@@ -99,6 +99,57 @@ Rect :: struct {
|
||||
bottom: i32,
|
||||
}
|
||||
|
||||
Dev_Mode_A :: struct {
|
||||
device_name: [32]u8,
|
||||
spec_version: u16,
|
||||
driver_version: u16,
|
||||
size: u16,
|
||||
driver_extra: u16,
|
||||
fields: u32,
|
||||
using _: struct #raw_union {
|
||||
// Printer only fields.
|
||||
using _: struct {
|
||||
orientation: i16,
|
||||
paper_size: i16,
|
||||
paper_length: i16,
|
||||
paper_width: i16,
|
||||
scale: i16,
|
||||
copies: i16,
|
||||
default_source: i16,
|
||||
print_quality: i16,
|
||||
},
|
||||
// Display only fields.
|
||||
using _: struct {
|
||||
position: Point,
|
||||
display_orientation: u32,
|
||||
display_fixed_output: u32,
|
||||
},
|
||||
},
|
||||
color: i16,
|
||||
duplex: i16,
|
||||
y_resolution: i16,
|
||||
tt_option: i16,
|
||||
collate: i16,
|
||||
form_name: [32]u8,
|
||||
log_pixels: u16,
|
||||
bits_per_pel: u32,
|
||||
pels_width: u32,
|
||||
pels_height: u32,
|
||||
using _: struct #raw_union {
|
||||
display_flags: u32,
|
||||
nup: u32,
|
||||
},
|
||||
display_frequency: u32,
|
||||
icm_method: u32,
|
||||
icm_intent: u32,
|
||||
media_type: u32,
|
||||
dither_type: u32,
|
||||
reserved_1: u32,
|
||||
reserved_2: u32,
|
||||
panning_width: u32,
|
||||
panning_height: u32,
|
||||
}
|
||||
|
||||
Filetime :: struct {
|
||||
lo, hi: u32,
|
||||
}
|
||||
@@ -364,6 +415,9 @@ MAPVK_VK_TO_CHAR :: 2
|
||||
MAPVK_VSC_TO_VK_EX :: 3
|
||||
|
||||
//WinUser.h
|
||||
ENUM_CURRENT_SETTINGS :: u32(4294967295) // (DWORD)-1
|
||||
ENUM_REGISTRY_SETTINGS :: u32(4294967294) // (DWORD)-2
|
||||
|
||||
VK_LBUTTON :: 0x01
|
||||
VK_RBUTTON :: 0x02
|
||||
VK_CANCEL :: 0x03
|
||||
@@ -877,7 +931,20 @@ FILE_GENERIC_EXECUTE :: 0x20000000
|
||||
FILE_GENERIC_WRITE :: 0x40000000
|
||||
FILE_GENERIC_READ :: 0x80000000
|
||||
|
||||
FILE_APPEND_DATA :: 0x0004
|
||||
FILE_READ_DATA :: 0x0001
|
||||
FILE_LIST_DIRECTORY :: 0x0001
|
||||
FILE_WRITE_DATA :: 0x0002
|
||||
FILE_ADD_FILE :: 0x0002
|
||||
FILE_APPEND_DATA :: 0x0004
|
||||
FILE_ADD_SUBDIRECTORY :: 0x0004
|
||||
FILE_CREATE_PIPE_INSTANCE :: 0x0004
|
||||
FILE_READ_EA :: 0x0008
|
||||
FILE_WRITE_EA :: 0x0010
|
||||
FILE_EXECUTE :: 0x0020
|
||||
FILE_TRAVERSE :: 0x0020
|
||||
FILE_DELETE_CHILD :: 0x0040
|
||||
FILE_READ_ATTRIBUTES :: 0x0080
|
||||
FILE_WRITE_ATTRIBUTES :: 0x0100
|
||||
|
||||
STD_INPUT_HANDLE :: -10
|
||||
STD_OUTPUT_HANDLE :: -11
|
||||
|
||||
@@ -106,6 +106,8 @@ foreign kernel32 {
|
||||
bytes_returned: ^u32, overlapped: ^Overlapped,
|
||||
completion: rawptr) -> Bool ---
|
||||
|
||||
@(link_name="GetOverlappedResult") get_overlapped_result :: proc(file: Handle, overlapped: ^Overlapped, number_of_bytes_transferred: ^u32, wait: Bool) -> Bool ---
|
||||
|
||||
@(link_name="WideCharToMultiByte") wide_char_to_multi_byte :: proc(code_page: u32, flags: u32,
|
||||
wchar_str: Wstring, wchar: i32,
|
||||
multi_str: cstring, multi: i32,
|
||||
|
||||
@@ -201,6 +201,8 @@ foreign user32 {
|
||||
@(link_name="MapVirtualKeyExA") map_virtual_key_ex_a :: proc(code, map_type: u32, hkl: HKL) -> u32 ---
|
||||
|
||||
@(link_name="EnumDisplayMonitors") enum_display_monitors :: proc(hdc: Hdc, rect: ^Rect, enum_proc: Monitor_Enum_Proc, lparam: Lparam) -> bool ---
|
||||
|
||||
@(link_name="EnumDisplaySettingsA") enum_display_settings_a :: proc(device_name: cstring, mode_number: u32, mode: ^Dev_Mode_A) -> Bool ---
|
||||
}
|
||||
|
||||
@(default_calling_convention = "std")
|
||||
|
||||
@@ -6,5 +6,7 @@ foreign import "system:winmm.lib"
|
||||
|
||||
@(default_calling_convention = "std")
|
||||
foreign winmm {
|
||||
@(link_name="timeBeginPeriod") time_begin_period :: proc(period: u32) -> u32 ---
|
||||
|
||||
@(link_name="timeGetTime") time_get_time :: proc() -> u32 ---
|
||||
}
|
||||
|
||||
4
tests/core/image/build.bat
Normal file
4
tests/core/image/build.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
pushd ..
|
||||
odin run image
|
||||
popd
|
||||
@@ -1,5 +1,3 @@
|
||||
package test_core_image
|
||||
|
||||
/*
|
||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||
Made available under Odin's BSD-3 license.
|
||||
@@ -9,6 +7,7 @@ package test_core_image
|
||||
|
||||
A test suite for PNG.
|
||||
*/
|
||||
package test_core_image
|
||||
|
||||
import "core:testing"
|
||||
|
||||
@@ -64,7 +63,7 @@ PNG_Test :: struct {
|
||||
file: string,
|
||||
tests: []struct {
|
||||
options: image.Options,
|
||||
expected_error: compress.Error,
|
||||
expected_error: image.Error,
|
||||
dims: PNG_Dims,
|
||||
hash: u32,
|
||||
},
|
||||
@@ -1198,37 +1197,37 @@ Corrupt_PNG_Tests := []PNG_Test{
|
||||
{
|
||||
"xs1n0g01", // signature byte 1 MSBit reset to zero
|
||||
{
|
||||
{Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
"xs2n0g01", // signature byte 2 is a 'Q'
|
||||
{
|
||||
{Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
"xs4n0g01", // signature byte 4 lowercase
|
||||
{
|
||||
{Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
"xs7n0g01", // 7th byte a space instead of control-Z
|
||||
{
|
||||
{Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
"xcrn0g04", // added cr bytes
|
||||
{
|
||||
{Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
"xlfn0g04", // added lf bytes
|
||||
{
|
||||
{Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1240,37 +1239,37 @@ Corrupt_PNG_Tests := []PNG_Test{
|
||||
{
|
||||
"xc1n0g08", // color type 1
|
||||
{
|
||||
{Default, I_Error.Unknown_Color_Type, {}, 0x_0000_0000},
|
||||
{Default, .Unknown_Color_Type, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
"xc9n2c08", // color type 9
|
||||
{
|
||||
{Default, I_Error.Unknown_Color_Type, {}, 0x_0000_0000},
|
||||
{Default, .Unknown_Color_Type, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
"xd0n2c08", // bit-depth 0
|
||||
{
|
||||
{Default, I_Error.Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000},
|
||||
{Default, .Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
"xd3n2c08", // bit-depth 3
|
||||
{
|
||||
{Default, I_Error.Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000},
|
||||
{Default, .Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
"xd9n2c08", // bit-depth 99
|
||||
{
|
||||
{Default, I_Error.Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000},
|
||||
{Default, .Invalid_Color_Bit_Depth_Combo, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
"xdtn0g01", // missing IDAT chunk
|
||||
{
|
||||
{Default, I_Error.IDAT_Missing, {}, 0x_0000_0000},
|
||||
{Default, .IDAT_Missing, {}, 0x_0000_0000},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -1505,19 +1504,17 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
|
||||
|
||||
passed &= test.hash == hash
|
||||
if .return_metadata in test.options {
|
||||
v: ^png.Info
|
||||
|
||||
if img.metadata_ptr != nil && img.metadata_type == png.Info {
|
||||
v = (^png.Info)(img.metadata_ptr)
|
||||
if v, ok := img.metadata.(^image.PNG_Info); ok {
|
||||
for c in v.chunks {
|
||||
#partial switch(c.header.type) {
|
||||
case .gAMA:
|
||||
switch(file.file) {
|
||||
case "pp0n2c16", "pp0n6a08":
|
||||
gamma := png.gamma(c)
|
||||
gamma, gamma_ok := png.gamma(c)
|
||||
expected_gamma := f32(1.0)
|
||||
error = fmt.tprintf("%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma)
|
||||
expect(t, gamma == expected_gamma, error)
|
||||
expect(t, gamma == expected_gamma && gamma_ok, error)
|
||||
}
|
||||
case .PLTE:
|
||||
switch(file.file) {
|
||||
@@ -1557,25 +1554,25 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
|
||||
expect(t, expected_chrm == chrm && chrm_ok, error)
|
||||
}
|
||||
case .pHYs:
|
||||
phys := png.phys(c)
|
||||
phys, phys_ok := png.phys(c)
|
||||
phys_err := "%v test %v cHRM is %v, expected %v."
|
||||
switch (file.file) {
|
||||
case "cdfn2c08":
|
||||
expected_phys := png.pHYs{ppu_x = 1, ppu_y = 4, unit = .Unknown}
|
||||
error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
|
||||
expect(t, expected_phys == phys, error)
|
||||
expect(t, expected_phys == phys && phys_ok, error)
|
||||
case "cdhn2c08":
|
||||
expected_phys := png.pHYs{ppu_x = 4, ppu_y = 1, unit = .Unknown}
|
||||
error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
|
||||
expect(t, expected_phys == phys, error)
|
||||
expect(t, expected_phys == phys && phys_ok, error)
|
||||
case "cdsn2c08":
|
||||
expected_phys := png.pHYs{ppu_x = 1, ppu_y = 1, unit = .Unknown}
|
||||
error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
|
||||
expect(t, expected_phys == phys, error)
|
||||
expect(t, expected_phys == phys && phys_ok, error)
|
||||
case "cdun2c08":
|
||||
expected_phys := png.pHYs{ppu_x = 1000, ppu_y = 1000, unit = .Meter}
|
||||
error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
|
||||
expect(t, expected_phys == phys, error)
|
||||
expect(t, expected_phys == phys && phys_ok, error)
|
||||
}
|
||||
case .hIST:
|
||||
hist, hist_ok := png.hist(c)
|
||||
@@ -1589,7 +1586,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
|
||||
expect(t, hist.used == 256 && hist_ok, error)
|
||||
}
|
||||
case .tIME:
|
||||
png_time := png.time(c)
|
||||
png_time, png_time_ok := png.time(c)
|
||||
time_err := "%v test %v tIME was %v, expected %v."
|
||||
expected_time: png.tIME
|
||||
|
||||
@@ -1610,7 +1607,7 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
|
||||
|
||||
}
|
||||
error = fmt.tprintf(time_err, file.file, count, png_time, expected_time)
|
||||
expect(t, png_time == expected_time, error)
|
||||
expect(t, png_time == expected_time && png_time_ok, error)
|
||||
|
||||
error = fmt.tprintf(time_core_err, file.file, count, core_time, expected_core)
|
||||
expect(t, core_time == expected_core && core_time_ok, error)
|
||||
|
||||
Reference in New Issue
Block a user