mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-03 22:28:46 +00:00
png: Move metadata.
This commit is contained in:
+99
-6
@@ -26,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -152,10 +155,101 @@ PNG_Error :: enum {
|
|||||||
Invalid_Chunk_Length,
|
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
|
||||||
@@ -164,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,
|
||||||
@@ -226,8 +319,8 @@ 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_ptr = img.metadata_ptr
|
||||||
res.metadata_type = img.metadata_type
|
// res.metadata_type = img.metadata_type
|
||||||
|
|
||||||
return res, true
|
return res, true
|
||||||
}
|
}
|
||||||
|
|||||||
+82
-84
@@ -53,93 +53,91 @@ demo :: proc() {
|
|||||||
} else {
|
} else {
|
||||||
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)
|
||||||
|
|
||||||
assert(img.metadata_ptr != nil && img.metadata_type == Info)
|
if v, ok := img.metadata.(^image.PNG_Info); ok {
|
||||||
|
// Handle ancillary chunks as you wish.
|
||||||
v := (^Info)(img.metadata_ptr)
|
// We provide helper functions for a few types.
|
||||||
|
for c in v.chunks {
|
||||||
// Handle ancillary chunks as you wish.
|
#partial switch c.header.type {
|
||||||
// We provide helper functions for a few types.
|
case .tIME:
|
||||||
for c in v.chunks {
|
if t, t_ok := core_time(c); t_ok {
|
||||||
#partial switch c.header.type {
|
fmt.printf("[tIME]: %v\n", t)
|
||||||
case .tIME:
|
|
||||||
if t, t_ok := core_time(c); t_ok {
|
|
||||||
fmt.printf("[tIME]: %v\n", t)
|
|
||||||
}
|
|
||||||
case .gAMA:
|
|
||||||
if gama, gama_ok := gamma(c); gama_ok {
|
|
||||||
fmt.printf("[gAMA]: %v\n", gama)
|
|
||||||
}
|
|
||||||
case .pHYs:
|
|
||||||
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 .gAMA:
|
||||||
case .iTXt, .zTXt, .tEXt:
|
if gama, gama_ok := gamma(c); gama_ok {
|
||||||
res, ok_text := text(c)
|
fmt.printf("[gAMA]: %v\n", gama)
|
||||||
if ok_text {
|
|
||||||
if c.header.type == .iTXt {
|
|
||||||
fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text)
|
|
||||||
} else {
|
|
||||||
fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text)
|
|
||||||
}
|
}
|
||||||
|
case .pHYs:
|
||||||
|
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)
|
||||||
|
if ok_text {
|
||||||
|
if c.header.type == .iTXt {
|
||||||
|
fmt.printf("[iTXt] %v (%v:%v): %v\n", res.keyword, res.language, res.keyword_localized, res.text)
|
||||||
|
} else {
|
||||||
|
fmt.printf("[tEXt/zTXt] %v: %v\n", res.keyword, res.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer text_destroy(res)
|
||||||
|
case .bKGD:
|
||||||
|
fmt.printf("[bKGD] %v\n", img.background)
|
||||||
|
case .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.
|
||||||
|
*/
|
||||||
|
fmt.printf("[eXIf] %v\n", res)
|
||||||
|
}
|
||||||
|
case .PLTE:
|
||||||
|
if plte, plte_ok := plte(c); plte_ok {
|
||||||
|
fmt.printf("[PLTE] %v\n", plte)
|
||||||
|
} else {
|
||||||
|
fmt.printf("[PLTE] Error\n")
|
||||||
|
}
|
||||||
|
case .hIST:
|
||||||
|
if res, ok_hist := hist(c); ok_hist {
|
||||||
|
fmt.printf("[hIST] %v\n", res)
|
||||||
|
}
|
||||||
|
case .cHRM:
|
||||||
|
if res, ok_chrm := chrm(c); ok_chrm {
|
||||||
|
fmt.printf("[cHRM] %v\n", res)
|
||||||
|
}
|
||||||
|
case .sPLT:
|
||||||
|
res, ok_splt := splt(c)
|
||||||
|
if ok_splt {
|
||||||
|
fmt.printf("[sPLT] %v\n", res)
|
||||||
|
}
|
||||||
|
splt_destroy(res)
|
||||||
|
case .sBIT:
|
||||||
|
if res, ok_sbit := sbit(c); ok_sbit {
|
||||||
|
fmt.printf("[sBIT] %v\n", res)
|
||||||
|
}
|
||||||
|
case .iCCP:
|
||||||
|
res, ok_iccp := iccp(c)
|
||||||
|
if ok_iccp {
|
||||||
|
fmt.printf("[iCCP] %v\n", res)
|
||||||
|
}
|
||||||
|
iccp_destroy(res)
|
||||||
|
case .sRGB:
|
||||||
|
if res, ok_srgb := srgb(c); ok_srgb {
|
||||||
|
fmt.printf("[sRGB] Rendering intent: %v\n", res)
|
||||||
|
}
|
||||||
|
case:
|
||||||
|
type := c.header.type
|
||||||
|
name := chunk_type_to_name(&type)
|
||||||
|
fmt.printf("[%v]: %v\n", name, c.data)
|
||||||
}
|
}
|
||||||
defer text_destroy(res)
|
|
||||||
case .bKGD:
|
|
||||||
fmt.printf("[bKGD] %v\n", img.background)
|
|
||||||
case .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.
|
|
||||||
*/
|
|
||||||
fmt.printf("[eXIf] %v\n", res)
|
|
||||||
}
|
|
||||||
case .PLTE:
|
|
||||||
if plte, plte_ok := plte(c); plte_ok {
|
|
||||||
fmt.printf("[PLTE] %v\n", plte)
|
|
||||||
} else {
|
|
||||||
fmt.printf("[PLTE] Error\n")
|
|
||||||
}
|
|
||||||
case .hIST:
|
|
||||||
if res, ok_hist := hist(c); ok_hist {
|
|
||||||
fmt.printf("[hIST] %v\n", res)
|
|
||||||
}
|
|
||||||
case .cHRM:
|
|
||||||
if res, ok_chrm := chrm(c); ok_chrm {
|
|
||||||
fmt.printf("[cHRM] %v\n", res)
|
|
||||||
}
|
|
||||||
case .sPLT:
|
|
||||||
res, ok_splt := splt(c)
|
|
||||||
if ok_splt {
|
|
||||||
fmt.printf("[sPLT] %v\n", res)
|
|
||||||
}
|
|
||||||
splt_destroy(res)
|
|
||||||
case .sBIT:
|
|
||||||
if res, ok_sbit := sbit(c); ok_sbit {
|
|
||||||
fmt.printf("[sBIT] %v\n", res)
|
|
||||||
}
|
|
||||||
case .iCCP:
|
|
||||||
res, ok_iccp := iccp(c)
|
|
||||||
if ok_iccp {
|
|
||||||
fmt.printf("[iCCP] %v\n", res)
|
|
||||||
}
|
|
||||||
iccp_destroy(res)
|
|
||||||
case .sRGB:
|
|
||||||
if res, ok_srgb := srgb(c); ok_srgb {
|
|
||||||
fmt.printf("[sRGB] Rendering intent: %v\n", res)
|
|
||||||
}
|
|
||||||
case:
|
|
||||||
type := c.header.type
|
|
||||||
name := chunk_type_to_name(&type)
|
|
||||||
fmt.printf("[%v]: %v\n", name, c.data)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-22
@@ -34,16 +34,13 @@ destroy :: proc(img: ^Image) {
|
|||||||
|
|
||||||
bytes.buffer_destroy(&img.pixels)
|
bytes.buffer_destroy(&img.pixels)
|
||||||
|
|
||||||
assert(img.metadata_ptr != nil && img.metadata_type == Info)
|
if v, ok := img.metadata.(^image.PNG_Info); ok {
|
||||||
v := (^Info)(img.metadata_ptr)
|
for chunk in &v.chunks {
|
||||||
|
delete(chunk.data)
|
||||||
for chunk in &v.chunks {
|
}
|
||||||
delete(chunk.data)
|
delete(v.chunks)
|
||||||
|
free(v)
|
||||||
}
|
}
|
||||||
delete(v.chunks)
|
|
||||||
|
|
||||||
// Clean up Info.
|
|
||||||
free(img.metadata_ptr)
|
|
||||||
free(img)
|
free(img)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +48,7 @@ destroy :: proc(img: ^Image) {
|
|||||||
Chunk helpers
|
Chunk helpers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
gamma :: proc(c: Chunk) -> (res: f32, ok: bool) {
|
gamma :: proc(c: image.PNG_Chunk) -> (res: f32, ok: bool) {
|
||||||
if c.header.type != .gAMA || len(c.data) != size_of(gAMA) {
|
if c.header.type != .gAMA || len(c.data) != size_of(gAMA) {
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
@@ -61,7 +58,7 @@ gamma :: proc(c: Chunk) -> (res: f32, ok: bool) {
|
|||||||
|
|
||||||
INCHES_PER_METER :: 1000.0 / 25.4
|
INCHES_PER_METER :: 1000.0 / 25.4
|
||||||
|
|
||||||
phys :: proc(c: Chunk) -> (res: pHYs, ok: bool) {
|
phys :: proc(c: image.PNG_Chunk) -> (res: pHYs, ok: bool) {
|
||||||
if c.header.type != .pHYs || len(c.data) != size_of(pHYs) {
|
if c.header.type != .pHYs || len(c.data) != size_of(pHYs) {
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
@@ -73,7 +70,7 @@ 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) -> (res: tIME, ok: bool) {
|
time :: proc(c: image.PNG_Chunk) -> (res: tIME, ok: bool) {
|
||||||
if c.header.type != .tIME || len(c.data) != size_of(tIME) {
|
if c.header.type != .tIME || len(c.data) != size_of(tIME) {
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
@@ -81,7 +78,7 @@ time :: proc(c: Chunk) -> (res: tIME, ok: bool) {
|
|||||||
return (^tIME)(raw_data(c.data))^, true
|
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) {
|
||||||
if png_time, png_ok := time(c); png_ok {
|
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(
|
||||||
@@ -93,7 +90,7 @@ core_time :: proc(c: Chunk) -> (t: coretime.Time, ok: bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
assert(len(c.data) == int(c.header.length))
|
||||||
#partial switch c.header.type {
|
#partial switch c.header.type {
|
||||||
case .tEXt:
|
case .tEXt:
|
||||||
@@ -196,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)
|
||||||
@@ -232,7 +229,7 @@ iccp_destroy :: proc(i: iCCP) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) {
|
srgb :: proc(c: image.PNG_Chunk) -> (res: sRGB, ok: bool) {
|
||||||
if c.header.type != .sRGB || len(c.data) != size_of(sRGB_Rendering_Intent) {
|
if c.header.type != .sRGB || len(c.data) != size_of(sRGB_Rendering_Intent) {
|
||||||
return {}, false
|
return {}, false
|
||||||
}
|
}
|
||||||
@@ -244,7 +241,7 @@ srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) {
|
|||||||
return res, true
|
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
|
||||||
}
|
}
|
||||||
@@ -258,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
|
||||||
}
|
}
|
||||||
@@ -309,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.
|
||||||
@@ -327,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
|
||||||
}
|
}
|
||||||
@@ -349,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
|
||||||
@@ -367,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
|
||||||
|
|
||||||
|
|||||||
+17
-107
@@ -51,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,
|
||||||
@@ -262,8 +173,8 @@ 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 {}, compress.General_Error.Stream_Too_Short
|
return {}, compress.General_Error.Stream_Too_Short
|
||||||
}
|
}
|
||||||
@@ -305,7 +216,7 @@ read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) {
|
|||||||
return chunk, nil
|
return chunk, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
copy_chunk :: proc(src: Chunk, allocator := context.allocator) -> (dest: Chunk, err: 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) {
|
if int(src.header.length) != len(src.data) {
|
||||||
return {}, .Invalid_Chunk_Length
|
return {}, .Invalid_Chunk_Length
|
||||||
}
|
}
|
||||||
@@ -318,7 +229,7 @@ copy_chunk :: proc(src: Chunk, allocator := context.allocator) -> (dest: Chunk,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
append_chunk :: proc(list: ^[dynamic]Chunk, src: Chunk, allocator := context.allocator) -> (err: Error) {
|
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) {
|
if int(src.header.length) != len(src.data) {
|
||||||
return .Invalid_Chunk_Length
|
return .Invalid_Chunk_Length
|
||||||
}
|
}
|
||||||
@@ -334,13 +245,13 @@ append_chunk :: proc(list: ^[dynamic]Chunk, src: Chunk, allocator := context.all
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
|
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 || u128(width) * u128(height) > MAX_DIMENSIONS {
|
if width == 0 || height == 0 || u128(width) * u128(height) > MAX_DIMENSIONS {
|
||||||
@@ -407,7 +318,7 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
@@ -462,9 +373,8 @@ 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 {
|
||||||
@@ -477,11 +387,11 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
|
|
||||||
idat_length := u64(0)
|
idat_length := u64(0)
|
||||||
|
|
||||||
c: Chunk
|
c: image.PNG_Chunk
|
||||||
ch: Chunk_Header
|
ch: image.PNG_Chunk_Header
|
||||||
e: io.Error
|
e: io.Error
|
||||||
|
|
||||||
header: IHDR
|
header: image.PNG_IHDR
|
||||||
|
|
||||||
// State to ensure correct chunk ordering.
|
// State to ensure correct chunk ordering.
|
||||||
seen_ihdr := false; first := true
|
seen_ihdr := false; first := true
|
||||||
@@ -492,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
|
||||||
|
|
||||||
@@ -502,7 +412,7 @@ 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, compress.General_Error.Stream_Too_Short
|
return img, compress.General_Error.Stream_Too_Short
|
||||||
}
|
}
|
||||||
@@ -547,7 +457,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
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,
|
||||||
@@ -607,7 +517,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
return {}, image.PNG_Error.IDAT_Size_Too_Large
|
return {}, image.PNG_Error.IDAT_Size_Too_Large
|
||||||
}
|
}
|
||||||
|
|
||||||
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, compress.General_Error.Stream_Too_Short
|
return img, compress.General_Error.Stream_Too_Short
|
||||||
}
|
}
|
||||||
@@ -1599,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: 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)
|
||||||
|
|||||||
@@ -1504,10 +1504,8 @@ 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:
|
||||||
|
|||||||
Reference in New Issue
Block a user