mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-05 15:18:49 +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>.
|
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
Made available under Odin's BSD-3 license.
|
Made available under Odin's BSD-3 license.
|
||||||
@@ -7,10 +5,11 @@ package compress
|
|||||||
List of contributors:
|
List of contributors:
|
||||||
Jeroen van Rijn: Initial implementation, optimization.
|
Jeroen van Rijn: Initial implementation, optimization.
|
||||||
*/
|
*/
|
||||||
|
package compress
|
||||||
|
|
||||||
import "core:io"
|
import "core:io"
|
||||||
import "core:image"
|
|
||||||
import "core:bytes"
|
import "core:bytes"
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
These settings bound how much compression algorithms will allocate for their output buffer.
|
These settings bound how much compression algorithms will allocate for their output buffer.
|
||||||
@@ -51,11 +50,8 @@ Error :: union {
|
|||||||
ZLIB_Error,
|
ZLIB_Error,
|
||||||
GZIP_Error,
|
GZIP_Error,
|
||||||
ZIP_Error,
|
ZIP_Error,
|
||||||
/*
|
|
||||||
This is here because png.load will return a this type of error union,
|
runtime.Allocator_Error,
|
||||||
as it may involve an I/O error, a Deflate error, etc.
|
|
||||||
*/
|
|
||||||
image.Error,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
General_Error :: enum {
|
General_Error :: enum {
|
||||||
@@ -69,7 +65,6 @@ General_Error :: enum {
|
|||||||
Incompatible_Options,
|
Incompatible_Options,
|
||||||
Unimplemented,
|
Unimplemented,
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Memory errors
|
Memory errors
|
||||||
*/
|
*/
|
||||||
|
|||||||
+123
-12
@@ -1,16 +1,17 @@
|
|||||||
package image
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
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:
|
List of contributors:
|
||||||
Jeroen van Rijn: Initial implementation, optimization.
|
Jeroen van Rijn: Initial implementation, optimization.
|
||||||
Ginger Bill: Cosmetic changes.
|
Ginger Bill: Cosmetic changes.
|
||||||
*/
|
*/
|
||||||
|
package image
|
||||||
|
|
||||||
import "core:bytes"
|
import "core:bytes"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
|
import "core:compress"
|
||||||
|
import "core:runtime"
|
||||||
|
|
||||||
Image :: struct {
|
Image :: struct {
|
||||||
width: int,
|
width: int,
|
||||||
@@ -25,8 +26,11 @@ Image :: struct {
|
|||||||
*/
|
*/
|
||||||
background: Maybe([3]u16),
|
background: Maybe([3]u16),
|
||||||
|
|
||||||
metadata_ptr: rawptr,
|
metadata: Image_Metadata,
|
||||||
metadata_type: typeid,
|
}
|
||||||
|
|
||||||
|
Image_Metadata :: union {
|
||||||
|
^PNG_Info,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -112,31 +116,140 @@ Option :: enum {
|
|||||||
}
|
}
|
||||||
Options :: distinct bit_set[Option]
|
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,
|
Invalid_PNG_Signature,
|
||||||
IHDR_Not_First_Chunk,
|
IHDR_Not_First_Chunk,
|
||||||
IHDR_Corrupt,
|
IHDR_Corrupt,
|
||||||
IDAT_Missing,
|
IDAT_Missing,
|
||||||
IDAT_Must_Be_Contiguous,
|
IDAT_Must_Be_Contiguous,
|
||||||
IDAT_Corrupt,
|
IDAT_Corrupt,
|
||||||
PNG_Does_Not_Adhere_to_Spec,
|
IDAT_Size_Too_Large,
|
||||||
PLTE_Encountered_Unexpectedly,
|
PLTE_Encountered_Unexpectedly,
|
||||||
PLTE_Invalid_Length,
|
PLTE_Invalid_Length,
|
||||||
TRNS_Encountered_Unexpectedly,
|
TRNS_Encountered_Unexpectedly,
|
||||||
BKGD_Invalid_Length,
|
BKGD_Invalid_Length,
|
||||||
Invalid_Image_Dimensions,
|
|
||||||
Unknown_Color_Type,
|
Unknown_Color_Type,
|
||||||
Invalid_Color_Bit_Depth_Combo,
|
Invalid_Color_Bit_Depth_Combo,
|
||||||
Unknown_Filter_Method,
|
Unknown_Filter_Method,
|
||||||
Unknown_Interlace_Method,
|
Unknown_Interlace_Method,
|
||||||
Requested_Channel_Not_Present,
|
Requested_Channel_Not_Present,
|
||||||
Post_Processing_Error,
|
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
|
Functions to help with image buffer calculations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
|
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
|
size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height
|
||||||
return
|
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.
|
For when you have an RGB(A) image, but want a particular channel.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Channel :: enum u8 {
|
Channel :: enum u8 {
|
||||||
R = 1,
|
R = 1,
|
||||||
G = 2,
|
G = 2,
|
||||||
@@ -207,8 +319,7 @@ return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok
|
|||||||
res.depth = img.depth
|
res.depth = img.depth
|
||||||
res.pixels = t
|
res.pixels = t
|
||||||
res.background = img.background
|
res.background = img.background
|
||||||
res.metadata_ptr = img.metadata_ptr
|
res.metadata = img.metadata
|
||||||
res.metadata_type = img.metadata_type
|
|
||||||
|
|
||||||
return res, true
|
return res, true
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-32
@@ -1,9 +1,6 @@
|
|||||||
//+ignore
|
|
||||||
package png
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
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:
|
List of contributors:
|
||||||
Jeroen van Rijn: Initial implementation.
|
Jeroen van Rijn: Initial implementation.
|
||||||
@@ -11,8 +8,9 @@ package png
|
|||||||
|
|
||||||
An example of how to use `load`.
|
An example of how to use `load`.
|
||||||
*/
|
*/
|
||||||
|
//+ignore
|
||||||
|
package png
|
||||||
|
|
||||||
import "core:compress"
|
|
||||||
import "core:image"
|
import "core:image"
|
||||||
// import "core:image/png"
|
// import "core:image/png"
|
||||||
import "core:bytes"
|
import "core:bytes"
|
||||||
@@ -41,8 +39,8 @@ main :: proc() {
|
|||||||
demo :: proc() {
|
demo :: proc() {
|
||||||
file: string
|
file: string
|
||||||
|
|
||||||
options := image.Options{} // {.return_metadata};
|
options := image.Options{.return_metadata}
|
||||||
err: compress.Error
|
err: image.Error
|
||||||
img: ^image.Image
|
img: ^image.Image
|
||||||
|
|
||||||
file = "../../../misc/logo-slim.png"
|
file = "../../../misc/logo-slim.png"
|
||||||
@@ -53,32 +51,33 @@ demo :: proc() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.printf("Trying to read PNG file %v returned %v\n", file, err)
|
fmt.printf("Trying to read PNG file %v returned %v\n", file, err)
|
||||||
} else {
|
} else {
|
||||||
v: ^Info
|
|
||||||
|
|
||||||
fmt.printf("Image: %vx%vx%v, %v-bit.\n", img.width, img.height, img.channels, img.depth)
|
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.
|
// Handle ancillary chunks as you wish.
|
||||||
// We provide helper functions for a few types.
|
// We provide helper functions for a few types.
|
||||||
for c in v.chunks {
|
for c in v.chunks {
|
||||||
#partial switch c.header.type {
|
#partial switch c.header.type {
|
||||||
case .tIME:
|
case .tIME:
|
||||||
t, _ := core_time(c)
|
if t, t_ok := core_time(c); t_ok {
|
||||||
fmt.printf("[tIME]: %v\n", t)
|
fmt.printf("[tIME]: %v\n", t)
|
||||||
|
}
|
||||||
case .gAMA:
|
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:
|
case .pHYs:
|
||||||
phys := phys(c)
|
if phys, phys_ok := phys(c); phys_ok {
|
||||||
if phys.unit == .Meter {
|
if phys.unit == .Meter {
|
||||||
xm := f32(img.width) / f32(phys.ppu_x)
|
xm := f32(img.width) / f32(phys.ppu_x)
|
||||||
ym := f32(img.height) / f32(phys.ppu_y)
|
ym := f32(img.height) / f32(phys.ppu_y)
|
||||||
dpi_x, dpi_y := phys_to_dpi(phys)
|
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 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 resolution is %v x %v DPI.\n", dpi_x, dpi_y)
|
||||||
fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym)
|
fmt.printf("[pHYs] Image dimensions are %v x %v meters.\n", xm, ym)
|
||||||
} else {
|
} else {
|
||||||
fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y)
|
fmt.printf("[pHYs] x: %v, y: %v pixels per unknown unit.\n", phys.ppu_x, phys.ppu_y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case .iTXt, .zTXt, .tEXt:
|
case .iTXt, .zTXt, .tEXt:
|
||||||
res, ok_text := text(c)
|
res, ok_text := text(c)
|
||||||
@@ -93,8 +92,7 @@ demo :: proc() {
|
|||||||
case .bKGD:
|
case .bKGD:
|
||||||
fmt.printf("[bKGD] %v\n", img.background)
|
fmt.printf("[bKGD] %v\n", img.background)
|
||||||
case .eXIf:
|
case .eXIf:
|
||||||
res, ok_exif := exif(c)
|
if res, ok_exif := exif(c); ok_exif {
|
||||||
if ok_exif {
|
|
||||||
/*
|
/*
|
||||||
Other than checking the signature and byte order, we don't handle Exif data.
|
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.
|
If you wish to interpret it, pass it to an Exif parser.
|
||||||
@@ -102,20 +100,17 @@ demo :: proc() {
|
|||||||
fmt.printf("[eXIf] %v\n", res)
|
fmt.printf("[eXIf] %v\n", res)
|
||||||
}
|
}
|
||||||
case .PLTE:
|
case .PLTE:
|
||||||
plte, plte_ok := plte(c)
|
if plte, plte_ok := plte(c); plte_ok {
|
||||||
if plte_ok {
|
|
||||||
fmt.printf("[PLTE] %v\n", plte)
|
fmt.printf("[PLTE] %v\n", plte)
|
||||||
} else {
|
} else {
|
||||||
fmt.printf("[PLTE] Error\n")
|
fmt.printf("[PLTE] Error\n")
|
||||||
}
|
}
|
||||||
case .hIST:
|
case .hIST:
|
||||||
res, ok_hist := hist(c)
|
if res, ok_hist := hist(c); ok_hist {
|
||||||
if ok_hist {
|
|
||||||
fmt.printf("[hIST] %v\n", res)
|
fmt.printf("[hIST] %v\n", res)
|
||||||
}
|
}
|
||||||
case .cHRM:
|
case .cHRM:
|
||||||
res, ok_chrm := chrm(c)
|
if res, ok_chrm := chrm(c); ok_chrm {
|
||||||
if ok_chrm {
|
|
||||||
fmt.printf("[cHRM] %v\n", res)
|
fmt.printf("[cHRM] %v\n", res)
|
||||||
}
|
}
|
||||||
case .sPLT:
|
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 err == nil && .do_not_decompress_image not_in options && .info not_in options {
|
||||||
if ok := write_image_as_ppm("out.ppm", img); ok {
|
if ok := write_image_as_ppm("out.ppm", img); ok {
|
||||||
fmt.println("Saved decoded image.")
|
fmt.println("Saved decoded image.")
|
||||||
|
|||||||
+47
-47
@@ -1,5 +1,3 @@
|
|||||||
package png
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
Made available under Odin's BSD-2 license.
|
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.
|
These are a few useful utility functions to work with PNG images.
|
||||||
*/
|
*/
|
||||||
|
package png
|
||||||
|
|
||||||
import "core:image"
|
import "core:image"
|
||||||
import "core:compress/zlib"
|
import "core:compress/zlib"
|
||||||
@@ -34,15 +33,14 @@ destroy :: proc(img: ^Image) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bytes.buffer_destroy(&img.pixels)
|
bytes.buffer_destroy(&img.pixels)
|
||||||
// Clean up Info.
|
|
||||||
free(img.metadata_ptr)
|
|
||||||
|
|
||||||
/*
|
if v, ok := img.metadata.(^image.PNG_Info); ok {
|
||||||
We don't need to do anything for the individual chunks.
|
for chunk in &v.chunks {
|
||||||
They're allocated on the temp allocator, as is info.chunks
|
delete(chunk.data)
|
||||||
|
}
|
||||||
See read_chunk.
|
delete(v.chunks)
|
||||||
*/
|
free(v)
|
||||||
|
}
|
||||||
free(img)
|
free(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,46 +48,50 @@ destroy :: proc(img: ^Image) {
|
|||||||
Chunk helpers
|
Chunk helpers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
gamma :: proc(c: Chunk) -> f32 {
|
gamma :: proc(c: image.PNG_Chunk) -> (res: f32, ok: bool) {
|
||||||
assert(c.header.type == .gAMA)
|
if c.header.type != .gAMA || len(c.data) != size_of(gAMA) {
|
||||||
res := (^gAMA)(raw_data(c.data))^
|
return {}, false
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
gama := (^gAMA)(raw_data(c.data))^
|
||||||
|
return f32(gama.gamma_100k) / 100_000.0, true
|
||||||
}
|
}
|
||||||
|
|
||||||
INCHES_PER_METER :: 1000.0 / 25.4
|
INCHES_PER_METER :: 1000.0 / 25.4
|
||||||
|
|
||||||
phys :: proc(c: Chunk) -> pHYs {
|
phys :: proc(c: image.PNG_Chunk) -> (res: pHYs, ok: bool) {
|
||||||
assert(c.header.type == .pHYs)
|
if c.header.type != .pHYs || len(c.data) != size_of(pHYs) {
|
||||||
res := (^pHYs)(raw_data(c.data))^
|
return {}, false
|
||||||
return res
|
}
|
||||||
|
|
||||||
|
return (^pHYs)(raw_data(c.data))^, true
|
||||||
}
|
}
|
||||||
|
|
||||||
phys_to_dpi :: proc(p: pHYs) -> (x_dpi, y_dpi: f32) {
|
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
|
return f32(p.ppu_x) / INCHES_PER_METER, f32(p.ppu_y) / INCHES_PER_METER
|
||||||
}
|
}
|
||||||
|
|
||||||
time :: proc(c: Chunk) -> tIME {
|
time :: proc(c: image.PNG_Chunk) -> (res: tIME, ok: bool) {
|
||||||
assert(c.header.type == .tIME)
|
if c.header.type != .tIME || len(c.data) != size_of(tIME) {
|
||||||
res := (^tIME)(raw_data(c.data))^
|
return {}, false
|
||||||
return res
|
}
|
||||||
|
|
||||||
|
return (^tIME)(raw_data(c.data))^, true
|
||||||
}
|
}
|
||||||
|
|
||||||
core_time :: proc(c: Chunk) -> (t: coretime.Time, ok: bool) {
|
core_time :: proc(c: image.PNG_Chunk) -> (t: coretime.Time, ok: bool) {
|
||||||
png_time := time(c)
|
if png_time, png_ok := time(c); png_ok {
|
||||||
using png_time
|
using png_time
|
||||||
return coretime.datetime_to_time(
|
return coretime.datetime_to_time(
|
||||||
int(year), int(month), int(day),
|
int(year), int(month), int(day),
|
||||||
int(hour), int(minute), int(second),
|
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 {
|
#partial switch c.header.type {
|
||||||
case .tEXt:
|
case .tEXt:
|
||||||
ok = true
|
ok = true
|
||||||
@@ -191,7 +193,7 @@ text_destroy :: proc(text: Text) {
|
|||||||
delete(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
|
ok = true
|
||||||
|
|
||||||
fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator)
|
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) {
|
srgb :: proc(c: image.PNG_Chunk) -> (res: sRGB, ok: bool) {
|
||||||
ok = true
|
if c.header.type != .sRGB || len(c.data) != size_of(sRGB_Rendering_Intent) {
|
||||||
|
|
||||||
if c.header.type != .sRGB || len(c.data) != 1 {
|
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,10 +238,10 @@ srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) {
|
|||||||
if res.intent > max(sRGB_Rendering_Intent) {
|
if res.intent > max(sRGB_Rendering_Intent) {
|
||||||
ok = false; return
|
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 {
|
if c.header.type != .PLTE {
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ plte :: proc(c: Chunk) -> (res: PLTE, ok: bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
splt :: proc(c: Chunk) -> (res: sPLT, ok: bool) {
|
splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
|
||||||
if c.header.type != .sPLT {
|
if c.header.type != .sPLT {
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
@@ -306,7 +306,7 @@ splt_destroy :: proc(s: sPLT) {
|
|||||||
delete(s.name)
|
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.
|
Returns [4]u8 with the significant bits in each channel.
|
||||||
A channel will contain zero if not applicable to the PNG color type.
|
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 {
|
if c.header.type != .hIST {
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
@@ -346,7 +346,7 @@ hist :: proc(c: Chunk) -> (res: hIST, ok: bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) {
|
chrm :: proc(c: image.PNG_Chunk) -> (res: cHRM, ok: bool) {
|
||||||
ok = true
|
ok = true
|
||||||
if c.header.length != size_of(cHRM_Raw) {
|
if c.header.length != size_of(cHRM_Raw) {
|
||||||
return {}, false
|
return {}, false
|
||||||
@@ -364,7 +364,7 @@ chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
exif :: proc(c: Chunk) -> (res: Exif, ok: bool) {
|
exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) {
|
||||||
|
|
||||||
ok = true
|
ok = true
|
||||||
|
|
||||||
|
|||||||
+160
-171
@@ -1,13 +1,12 @@
|
|||||||
package png
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
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:
|
List of contributors:
|
||||||
Jeroen van Rijn: Initial implementation.
|
Jeroen van Rijn: Initial implementation.
|
||||||
Ginger Bill: Cosmetic changes.
|
Ginger Bill: Cosmetic changes.
|
||||||
*/
|
*/
|
||||||
|
package png
|
||||||
|
|
||||||
import "core:compress"
|
import "core:compress"
|
||||||
import "core:compress/zlib"
|
import "core:compress/zlib"
|
||||||
@@ -21,11 +20,29 @@ import "core:io"
|
|||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:intrinsics"
|
import "core:intrinsics"
|
||||||
|
|
||||||
Error :: compress.Error
|
/*
|
||||||
E_General :: compress.General_Error
|
67_108_864 pixels max by default.
|
||||||
E_PNG :: image.Error
|
Maximum allowed dimensions are capped at 65535 * 65535.
|
||||||
E_Deflate :: compress.Deflate_Error
|
*/
|
||||||
|
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
|
Image :: image.Image
|
||||||
Options :: image.Options
|
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',
|
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 {
|
Row_Filter :: enum u8 {
|
||||||
None = 0,
|
None = 0,
|
||||||
Sub = 1,
|
Sub = 1,
|
||||||
@@ -135,22 +63,22 @@ PLTE_Entry :: [3]u8
|
|||||||
|
|
||||||
PLTE :: struct #packed {
|
PLTE :: struct #packed {
|
||||||
entries: [256]PLTE_Entry,
|
entries: [256]PLTE_Entry,
|
||||||
used: u16,
|
used: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
hIST :: struct #packed {
|
hIST :: struct #packed {
|
||||||
entries: [256]u16,
|
entries: [256]u16,
|
||||||
used: u16,
|
used: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
sPLT :: struct #packed {
|
sPLT :: struct #packed {
|
||||||
name: string,
|
name: string,
|
||||||
depth: u8,
|
depth: u8,
|
||||||
entries: union {
|
entries: union {
|
||||||
[][4]u8,
|
[][4]u8,
|
||||||
[][4]u16,
|
[][4]u16,
|
||||||
},
|
},
|
||||||
used: u16,
|
used: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other chunks
|
// Other chunks
|
||||||
@@ -223,14 +151,14 @@ Exif :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iCCP :: struct {
|
iCCP :: struct {
|
||||||
name: string,
|
name: string,
|
||||||
profile: []u8,
|
profile: []u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
sRGB_Rendering_Intent :: enum u8 {
|
sRGB_Rendering_Intent :: enum u8 {
|
||||||
Perceptual = 0,
|
Perceptual = 0,
|
||||||
Relative_colorimetric = 1,
|
Relative_colorimetric = 1,
|
||||||
Saturation = 2,
|
Saturation = 2,
|
||||||
Absolute_colorimetric = 3,
|
Absolute_colorimetric = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,16 +173,30 @@ ADAM7_Y_SPACING := []int{ 8,8,8,4,4,2,2 }
|
|||||||
|
|
||||||
// Implementation starts here
|
// Implementation starts here
|
||||||
|
|
||||||
read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) {
|
read_chunk :: proc(ctx: ^$C) -> (chunk: image.PNG_Chunk, err: Error) {
|
||||||
ch, e := compress.read_data(ctx, Chunk_Header)
|
ch, e := compress.read_data(ctx, image.PNG_Chunk_Header)
|
||||||
if e != .None {
|
if e != .None {
|
||||||
return {}, E_General.Stream_Too_Short
|
return {}, compress.General_Error.Stream_Too_Short
|
||||||
}
|
}
|
||||||
chunk.header = ch
|
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))
|
chunk.data, e = compress.read_slice(ctx, int(ch.length))
|
||||||
if e != .None {
|
if e != .None {
|
||||||
return {}, E_General.Stream_Too_Short
|
return {}, compress.General_Error.Stream_Too_Short
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute CRC over chunk type + data
|
// 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)
|
crc, e3 := compress.read_data(ctx, u32be)
|
||||||
if e3 != .None {
|
if e3 != .None {
|
||||||
return {}, E_General.Stream_Too_Short
|
return {}, compress.General_Error.Stream_Too_Short
|
||||||
}
|
}
|
||||||
chunk.crc = crc
|
chunk.crc = crc
|
||||||
|
|
||||||
if chunk.crc != u32be(computed_crc) {
|
if chunk.crc != u32be(computed_crc) {
|
||||||
return {}, E_General.Checksum_Failed
|
return {}, compress.General_Error.Checksum_Failed
|
||||||
}
|
}
|
||||||
return chunk, nil
|
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)
|
c, e := read_chunk(ctx)
|
||||||
if e != nil {
|
if e != nil {
|
||||||
return {}, e
|
return {}, e
|
||||||
}
|
}
|
||||||
|
|
||||||
header := (^IHDR)(raw_data(c.data))^
|
header := (^image.PNG_IHDR)(raw_data(c.data))^
|
||||||
// Validate IHDR
|
// Validate IHDR
|
||||||
using header
|
using header
|
||||||
if width == 0 || height == 0 {
|
if width == 0 || height == 0 || u128(width) * u128(height) > MAX_DIMENSIONS {
|
||||||
return {}, E_PNG.Invalid_Image_Dimensions
|
return {}, .Invalid_Image_Dimensions
|
||||||
}
|
}
|
||||||
|
|
||||||
if compression_method != 0 {
|
if compression_method != 0 {
|
||||||
return {}, E_General.Unknown_Compression_Method
|
return {}, compress.General_Error.Unknown_Compression_Method
|
||||||
}
|
}
|
||||||
|
|
||||||
if filter_method != 0 {
|
if filter_method != 0 {
|
||||||
return {}, E_PNG.Unknown_Filter_Method
|
return {}, .Unknown_Filter_Method
|
||||||
}
|
}
|
||||||
|
|
||||||
if interlace_method != .None && interlace_method != .Adam7 {
|
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 {
|
if !allowed {
|
||||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo
|
return {}, .Invalid_Color_Bit_Depth_Combo
|
||||||
}
|
}
|
||||||
case 2, 4, 6:
|
case 2, 4, 6:
|
||||||
/*
|
/*
|
||||||
@@ -322,7 +293,7 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
|
|||||||
Allowed bit depths: 8 and 16
|
Allowed bit depths: 8 and 16
|
||||||
*/
|
*/
|
||||||
if bit_depth != 8 && bit_depth != 16 {
|
if bit_depth != 8 && bit_depth != 16 {
|
||||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo
|
return {}, .Invalid_Color_Bit_Depth_Combo
|
||||||
}
|
}
|
||||||
case 3:
|
case 3:
|
||||||
/*
|
/*
|
||||||
@@ -337,17 +308,17 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !allowed {
|
if !allowed {
|
||||||
return {}, E_PNG.Invalid_Color_Bit_Depth_Combo
|
return {}, .Invalid_Color_Bit_Depth_Combo
|
||||||
}
|
}
|
||||||
|
|
||||||
case:
|
case:
|
||||||
return {}, E_PNG.Unknown_Color_Type
|
return {}, .Unknown_Color_Type
|
||||||
}
|
}
|
||||||
|
|
||||||
return header, nil
|
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
|
t := transmute(^u8)type
|
||||||
return strings.string_from_ptr(t, 4)
|
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)
|
return load_from_slice(data, options)
|
||||||
} else {
|
} else {
|
||||||
img = new(Image)
|
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 {
|
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 {
|
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)
|
img = new(Image)
|
||||||
}
|
}
|
||||||
|
|
||||||
info := new(Info)
|
info := new(image.PNG_Info)
|
||||||
img.metadata_ptr = info
|
img.metadata = info
|
||||||
img.metadata_type = typeid_of(Info)
|
|
||||||
|
|
||||||
signature, io_error := compress.read_data(ctx, Signature)
|
signature, io_error := compress.read_data(ctx, Signature)
|
||||||
if io_error != .None || signature != .PNG {
|
if io_error != .None || signature != .PNG {
|
||||||
return img, E_PNG.Invalid_PNG_Signature
|
return img, .Invalid_PNG_Signature
|
||||||
}
|
}
|
||||||
|
|
||||||
idat: []u8
|
idat: []u8
|
||||||
idat_b: bytes.Buffer
|
idat_b: bytes.Buffer
|
||||||
idat_length := u32be(0)
|
|
||||||
defer bytes.buffer_destroy(&idat_b)
|
defer bytes.buffer_destroy(&idat_b)
|
||||||
|
|
||||||
c: Chunk
|
idat_length := u64(0)
|
||||||
ch: Chunk_Header
|
|
||||||
|
c: image.PNG_Chunk
|
||||||
|
ch: image.PNG_Chunk_Header
|
||||||
e: io.Error
|
e: io.Error
|
||||||
|
|
||||||
header: IHDR
|
header: image.PNG_IHDR
|
||||||
|
|
||||||
info.chunks.allocator = context.temp_allocator
|
|
||||||
|
|
||||||
// State to ensure correct chunk ordering.
|
// State to ensure correct chunk ordering.
|
||||||
seen_ihdr := false; first := true
|
seen_ihdr := false; first := true
|
||||||
@@ -433,7 +402,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
seen_iend := false
|
seen_iend := false
|
||||||
|
|
||||||
_plte := PLTE{}
|
_plte := PLTE{}
|
||||||
trns := Chunk{}
|
trns := image.PNG_Chunk{}
|
||||||
|
|
||||||
final_image_channels := 0
|
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.
|
// Peek at next chunk's length and type.
|
||||||
// TODO: Some streams may not provide seek/read_at
|
// 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 {
|
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.
|
// name := chunk_type_to_name(&ch.type); // Only used for debug prints during development.
|
||||||
|
|
||||||
#partial switch ch.type {
|
#partial switch ch.type {
|
||||||
case .IHDR:
|
case .IHDR:
|
||||||
if seen_ihdr || !first {
|
if seen_ihdr || !first {
|
||||||
return {}, E_PNG.IHDR_Not_First_Chunk
|
return {}, .IHDR_Not_First_Chunk
|
||||||
}
|
}
|
||||||
seen_ihdr = true
|
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 {
|
if img.channels == 0 || img.depth == 0 {
|
||||||
return {}, E_PNG.IHDR_Corrupt
|
return {}, .IHDR_Corrupt
|
||||||
}
|
}
|
||||||
|
|
||||||
img.width = int(header.width)
|
img.width = int(header.width)
|
||||||
img.height = int(header.height)
|
img.height = int(header.height)
|
||||||
|
|
||||||
using header
|
using header
|
||||||
h := IHDR{
|
h := image.PNG_IHDR{
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = height,
|
||||||
bit_depth = bit_depth,
|
bit_depth = bit_depth,
|
||||||
@@ -498,28 +467,30 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
interlace_method = interlace_method,
|
interlace_method = interlace_method,
|
||||||
}
|
}
|
||||||
info.header = h
|
info.header = h
|
||||||
|
|
||||||
case .PLTE:
|
case .PLTE:
|
||||||
seen_plte = true
|
seen_plte = true
|
||||||
// PLTE must appear before IDAT and can't appear for color types 0, 4.
|
// PLTE must appear before IDAT and can't appear for color types 0, 4.
|
||||||
ct := transmute(u8)info.header.color_type
|
ct := transmute(u8)info.header.color_type
|
||||||
if seen_idat || ct == 0 || ct == 4 {
|
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
|
c = read_chunk(ctx) or_return
|
||||||
|
|
||||||
if c.header.length % 3 != 0 || c.header.length > 768 {
|
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_ok: bool
|
||||||
_plte, plte_ok = plte(c)
|
_plte, plte_ok = plte(c)
|
||||||
if !plte_ok {
|
if !plte_ok {
|
||||||
return img, E_PNG.PLTE_Invalid_Length
|
return img, .PLTE_Invalid_Length
|
||||||
}
|
}
|
||||||
|
|
||||||
if .return_metadata in options {
|
if .return_metadata in options {
|
||||||
append(&info.chunks, c)
|
append_chunk(&info.chunks, c) or_return
|
||||||
}
|
}
|
||||||
|
|
||||||
case .IDAT:
|
case .IDAT:
|
||||||
// If we only want image metadata and don't want the pixel data, we can early out.
|
// 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 {
|
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.
|
// There must be at least 1 IDAT, contiguous if more.
|
||||||
if seen_idat {
|
if seen_idat {
|
||||||
return img, E_PNG.IDAT_Must_Be_Contiguous
|
return img, .IDAT_Must_Be_Contiguous
|
||||||
}
|
}
|
||||||
|
|
||||||
if idat_length > 0 {
|
if idat_length > 0 {
|
||||||
return img, E_PNG.IDAT_Must_Be_Contiguous
|
return img, .IDAT_Must_Be_Contiguous
|
||||||
}
|
}
|
||||||
|
|
||||||
next := ch.type
|
next := ch.type
|
||||||
@@ -540,22 +511,29 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
c = read_chunk(ctx) or_return
|
c = read_chunk(ctx) or_return
|
||||||
|
|
||||||
bytes.buffer_write(&idat_b, c.data)
|
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 {
|
if e != .None {
|
||||||
return img, E_General.Stream_Too_Short
|
return img, compress.General_Error.Stream_Too_Short
|
||||||
}
|
}
|
||||||
next = ch.type
|
next = ch.type
|
||||||
}
|
}
|
||||||
|
|
||||||
idat = bytes.buffer_to_bytes(&idat_b)
|
idat = bytes.buffer_to_bytes(&idat_b)
|
||||||
if int(idat_length) != len(idat) {
|
if int(idat_length) != len(idat) {
|
||||||
return {}, E_PNG.IDAT_Corrupt
|
return {}, .IDAT_Corrupt
|
||||||
}
|
}
|
||||||
seen_idat = true
|
seen_idat = true
|
||||||
|
|
||||||
case .IEND:
|
case .IEND:
|
||||||
c = read_chunk(ctx) or_return
|
c = read_chunk(ctx) or_return
|
||||||
seen_iend = true
|
seen_iend = true
|
||||||
|
|
||||||
case .bKGD:
|
case .bKGD:
|
||||||
|
|
||||||
// TODO: Make sure that 16-bit bKGD + tRNS chunks return u16 instead of u16be
|
// 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
|
c = read_chunk(ctx) or_return
|
||||||
seen_bkgd = true
|
seen_bkgd = true
|
||||||
if .return_metadata in options {
|
if .return_metadata in options {
|
||||||
append(&info.chunks, c)
|
append_chunk(&info.chunks, c) or_return
|
||||||
}
|
}
|
||||||
|
|
||||||
ct := transmute(u8)info.header.color_type
|
ct := transmute(u8)info.header.color_type
|
||||||
switch ct {
|
switch ct {
|
||||||
case 3: // Indexed color
|
case 3: // Indexed color
|
||||||
if c.header.length != 1 {
|
if c.header.length != 1 {
|
||||||
return {}, E_PNG.BKGD_Invalid_Length
|
return {}, .BKGD_Invalid_Length
|
||||||
}
|
}
|
||||||
col := _plte.entries[c.data[0]]
|
col := _plte.entries[c.data[0]]
|
||||||
img.background = [3]u16{
|
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
|
case 0, 4: // Grayscale, with and without Alpha
|
||||||
if c.header.length != 2 {
|
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])
|
col := u16(mem.slice_data_cast([]u16be, c.data[:])[0])
|
||||||
img.background = [3]u16{col, col, col}
|
img.background = [3]u16{col, col, col}
|
||||||
case 2, 6: // Color, with and without Alpha
|
case 2, 6: // Color, with and without Alpha
|
||||||
if c.header.length != 6 {
|
if c.header.length != 6 {
|
||||||
return {}, E_PNG.BKGD_Invalid_Length
|
return {}, .BKGD_Invalid_Length
|
||||||
}
|
}
|
||||||
col := mem.slice_data_cast([]u16be, c.data[:])
|
col := mem.slice_data_cast([]u16be, c.data[:])
|
||||||
img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])}
|
img.background = [3]u16{u16(col[0]), u16(col[1]), u16(col[2])}
|
||||||
}
|
}
|
||||||
|
|
||||||
case .tRNS:
|
case .tRNS:
|
||||||
c = read_chunk(ctx) or_return
|
c = read_chunk(ctx) or_return
|
||||||
|
|
||||||
if .Alpha in info.header.color_type {
|
if .Alpha in info.header.color_type {
|
||||||
return img, E_PNG.TRNS_Encountered_Unexpectedly
|
return img, .TRNS_Encountered_Unexpectedly
|
||||||
}
|
}
|
||||||
|
|
||||||
if .return_metadata in options {
|
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
|
trns = c
|
||||||
|
|
||||||
case .iDOT, .CbGI:
|
case .iDOT, .CbGI:
|
||||||
/*
|
/*
|
||||||
iPhone PNG bastardization that doesn't adhere to spec with broken IDAT chunk.
|
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
|
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:
|
case:
|
||||||
// Unhandled type
|
// Unhandled type
|
||||||
c = read_chunk(ctx) or_return
|
c = read_chunk(ctx) or_return
|
||||||
|
|
||||||
if .return_metadata in options {
|
if .return_metadata in options {
|
||||||
// NOTE: Chunk cata is currently allocated on the temp allocator.
|
append_chunk(&info.chunks, c) or_return
|
||||||
append(&info.chunks, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
first = false
|
first = false
|
||||||
@@ -648,7 +627,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !seen_idat {
|
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)
|
buf_len := len(buf.buf)
|
||||||
if expected_size != buf_len {
|
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
|
// We need to create a new image buffer
|
||||||
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
|
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
|
||||||
t := bytes.Buffer{}
|
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
|
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
|
// We need to create a new image buffer
|
||||||
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 16)
|
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 16)
|
||||||
t := bytes.Buffer{}
|
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[:])
|
p16 := mem.slice_data_cast([]u16, temp.buf[:])
|
||||||
o16 := mem.slice_data_cast([]u16, t.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
|
// We need to create a new image buffer
|
||||||
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
|
dest_raw_size := compute_buffer_size(int(header.width), int(header.height), out_image_channels, 8)
|
||||||
t := bytes.Buffer{}
|
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[:])
|
p := mem.slice_data_cast([]u8, temp.buf[:])
|
||||||
o := mem.slice_data_cast([]u8, t.buf[:])
|
o := mem.slice_data_cast([]u8, t.buf[:])
|
||||||
@@ -1524,7 +1509,7 @@ defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
|
|||||||
return
|
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)
|
input := bytes.buffer_to_bytes(filter_bytes)
|
||||||
width := int(header.width)
|
width := int(header.width)
|
||||||
height := int(header.height)
|
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
|
bytes_per_channel := depth == 16 ? 2 : 1
|
||||||
|
|
||||||
num_bytes := compute_buffer_size(width, height, channels, depth == 16 ? 16 : 8)
|
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
|
filter_ok: bool
|
||||||
|
|
||||||
@@ -1560,7 +1547,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option
|
|||||||
}
|
}
|
||||||
if !filter_ok {
|
if !filter_ok {
|
||||||
// Caller will destroy buffer for us.
|
// Caller will destroy buffer for us.
|
||||||
return E_PNG.Unknown_Filter_Method
|
return .Unknown_Filter_Method
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
@@ -1575,7 +1562,9 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option
|
|||||||
if x > 0 && y > 0 {
|
if x > 0 && y > 0 {
|
||||||
temp: bytes.Buffer
|
temp: bytes.Buffer
|
||||||
temp_len := compute_buffer_size(x, y, channels, depth == 16 ? 16 : 8)
|
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{
|
params := Filter_Params{
|
||||||
src = input,
|
src = input,
|
||||||
@@ -1598,7 +1587,7 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, option
|
|||||||
|
|
||||||
if !filter_ok {
|
if !filter_ok {
|
||||||
// Caller will destroy buffer for us.
|
// Caller will destroy buffer for us.
|
||||||
return E_PNG.Unknown_Filter_Method
|
return .Unknown_Filter_Method
|
||||||
}
|
}
|
||||||
|
|
||||||
t := temp.buf[:]
|
t := temp.buf[:]
|
||||||
|
|||||||
@@ -99,6 +99,57 @@ Rect :: struct {
|
|||||||
bottom: i32,
|
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 {
|
Filetime :: struct {
|
||||||
lo, hi: u32,
|
lo, hi: u32,
|
||||||
}
|
}
|
||||||
@@ -364,6 +415,9 @@ MAPVK_VK_TO_CHAR :: 2
|
|||||||
MAPVK_VSC_TO_VK_EX :: 3
|
MAPVK_VSC_TO_VK_EX :: 3
|
||||||
|
|
||||||
//WinUser.h
|
//WinUser.h
|
||||||
|
ENUM_CURRENT_SETTINGS :: u32(4294967295) // (DWORD)-1
|
||||||
|
ENUM_REGISTRY_SETTINGS :: u32(4294967294) // (DWORD)-2
|
||||||
|
|
||||||
VK_LBUTTON :: 0x01
|
VK_LBUTTON :: 0x01
|
||||||
VK_RBUTTON :: 0x02
|
VK_RBUTTON :: 0x02
|
||||||
VK_CANCEL :: 0x03
|
VK_CANCEL :: 0x03
|
||||||
@@ -877,7 +931,20 @@ FILE_GENERIC_EXECUTE :: 0x20000000
|
|||||||
FILE_GENERIC_WRITE :: 0x40000000
|
FILE_GENERIC_WRITE :: 0x40000000
|
||||||
FILE_GENERIC_READ :: 0x80000000
|
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_INPUT_HANDLE :: -10
|
||||||
STD_OUTPUT_HANDLE :: -11
|
STD_OUTPUT_HANDLE :: -11
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ foreign kernel32 {
|
|||||||
bytes_returned: ^u32, overlapped: ^Overlapped,
|
bytes_returned: ^u32, overlapped: ^Overlapped,
|
||||||
completion: rawptr) -> Bool ---
|
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,
|
@(link_name="WideCharToMultiByte") wide_char_to_multi_byte :: proc(code_page: u32, flags: u32,
|
||||||
wchar_str: Wstring, wchar: i32,
|
wchar_str: Wstring, wchar: i32,
|
||||||
multi_str: cstring, multi: 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="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="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")
|
@(default_calling_convention = "std")
|
||||||
|
|||||||
@@ -6,5 +6,7 @@ foreign import "system:winmm.lib"
|
|||||||
|
|
||||||
@(default_calling_convention = "std")
|
@(default_calling_convention = "std")
|
||||||
foreign winmm {
|
foreign winmm {
|
||||||
|
@(link_name="timeBeginPeriod") time_begin_period :: proc(period: u32) -> u32 ---
|
||||||
|
|
||||||
@(link_name="timeGetTime") time_get_time :: proc() -> u32 ---
|
@(link_name="timeGetTime") time_get_time :: proc() -> u32 ---
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>.
|
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
||||||
Made available under Odin's BSD-3 license.
|
Made available under Odin's BSD-3 license.
|
||||||
@@ -9,6 +7,7 @@ package test_core_image
|
|||||||
|
|
||||||
A test suite for PNG.
|
A test suite for PNG.
|
||||||
*/
|
*/
|
||||||
|
package test_core_image
|
||||||
|
|
||||||
import "core:testing"
|
import "core:testing"
|
||||||
|
|
||||||
@@ -64,7 +63,7 @@ PNG_Test :: struct {
|
|||||||
file: string,
|
file: string,
|
||||||
tests: []struct {
|
tests: []struct {
|
||||||
options: image.Options,
|
options: image.Options,
|
||||||
expected_error: compress.Error,
|
expected_error: image.Error,
|
||||||
dims: PNG_Dims,
|
dims: PNG_Dims,
|
||||||
hash: u32,
|
hash: u32,
|
||||||
},
|
},
|
||||||
@@ -1198,37 +1197,37 @@ Corrupt_PNG_Tests := []PNG_Test{
|
|||||||
{
|
{
|
||||||
"xs1n0g01", // signature byte 1 MSBit reset to zero
|
"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'
|
"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
|
"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
|
"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
|
"xcrn0g04", // added cr bytes
|
||||||
{
|
{
|
||||||
{Default, I_Error.Invalid_PNG_Signature, {}, 0x_0000_0000},
|
{Default, .Invalid_PNG_Signature, {}, 0x_0000_0000},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"xlfn0g04", // added lf bytes
|
"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
|
"xc1n0g08", // color type 1
|
||||||
{
|
{
|
||||||
{Default, I_Error.Unknown_Color_Type, {}, 0x_0000_0000},
|
{Default, .Unknown_Color_Type, {}, 0x_0000_0000},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"xc9n2c08", // color type 9
|
"xc9n2c08", // color type 9
|
||||||
{
|
{
|
||||||
{Default, I_Error.Unknown_Color_Type, {}, 0x_0000_0000},
|
{Default, .Unknown_Color_Type, {}, 0x_0000_0000},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"xd0n2c08", // bit-depth 0
|
"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
|
"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
|
"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
|
"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
|
passed &= test.hash == hash
|
||||||
if .return_metadata in test.options {
|
if .return_metadata in test.options {
|
||||||
v: ^png.Info
|
|
||||||
|
|
||||||
if img.metadata_ptr != nil && img.metadata_type == png.Info {
|
if v, ok := img.metadata.(^image.PNG_Info); ok {
|
||||||
v = (^png.Info)(img.metadata_ptr)
|
|
||||||
for c in v.chunks {
|
for c in v.chunks {
|
||||||
#partial switch(c.header.type) {
|
#partial switch(c.header.type) {
|
||||||
case .gAMA:
|
case .gAMA:
|
||||||
switch(file.file) {
|
switch(file.file) {
|
||||||
case "pp0n2c16", "pp0n6a08":
|
case "pp0n2c16", "pp0n6a08":
|
||||||
gamma := png.gamma(c)
|
gamma, gamma_ok := png.gamma(c)
|
||||||
expected_gamma := f32(1.0)
|
expected_gamma := f32(1.0)
|
||||||
error = fmt.tprintf("%v test %v gAMA is %v, expected %v.", file.file, count, gamma, expected_gamma)
|
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:
|
case .PLTE:
|
||||||
switch(file.file) {
|
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)
|
expect(t, expected_chrm == chrm && chrm_ok, error)
|
||||||
}
|
}
|
||||||
case .pHYs:
|
case .pHYs:
|
||||||
phys := png.phys(c)
|
phys, phys_ok := png.phys(c)
|
||||||
phys_err := "%v test %v cHRM is %v, expected %v."
|
phys_err := "%v test %v cHRM is %v, expected %v."
|
||||||
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}
|
||||||
error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
|
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":
|
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}
|
||||||
error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
|
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":
|
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}
|
||||||
error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
|
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":
|
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}
|
||||||
error = fmt.tprintf(phys_err, file.file, count, phys, expected_phys)
|
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:
|
case .hIST:
|
||||||
hist, hist_ok := png.hist(c)
|
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)
|
expect(t, hist.used == 256 && hist_ok, error)
|
||||||
}
|
}
|
||||||
case .tIME:
|
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."
|
time_err := "%v test %v tIME was %v, expected %v."
|
||||||
expected_time: png.tIME
|
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)
|
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)
|
error = fmt.tprintf(time_core_err, file.file, count, core_time, expected_core)
|
||||||
expect(t, core_time == expected_core && core_time_ok, error)
|
expect(t, core_time == expected_core && core_time_ok, error)
|
||||||
|
|||||||
Reference in New Issue
Block a user