Browse Source

png: Move metadata.

Jeroen van Rijn 3 years ago
parent
commit
c4b4a841d6

+ 99 - 6
core/image/common.odin

@@ -26,8 +26,11 @@ Image :: struct {
 	*/
 	background:    Maybe([3]u16),
 
-	metadata_ptr:  rawptr,
-	metadata_type: typeid,
+	metadata:      Image_Metadata,
+}
+
+Image_Metadata :: union {
+	^PNG_Info,
 }
 
 /*
@@ -153,9 +156,100 @@ PNG_Error :: enum {
 }
 
 /*
-	Functions to help with image buffer calculations
+	PNG-specific structs
 */
+PNG_Info :: struct {
+	header: PNG_IHDR,
+	chunks: [dynamic]PNG_Chunk,
+}
+
+PNG_Chunk_Header :: struct #packed {
+	length: u32be,
+	type:   PNG_Chunk_Type,
+}
+
+PNG_Chunk :: struct #packed {
+	header: PNG_Chunk_Header,
+	data:   []byte,
+	crc:    u32be,
+}
+
+PNG_Chunk_Type :: enum u32be {
+	// IHDR must come first in a file
+	IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R',
+	// PLTE must precede the first IDAT chunk
+	PLTE = 'P' << 24 | 'L' << 16 | 'T' << 8 | 'E',
+	bKGD = 'b' << 24 | 'K' << 16 | 'G' << 8 | 'D',
+	tRNS = 't' << 24 | 'R' << 16 | 'N' << 8 | 'S',
+	IDAT = 'I' << 24 | 'D' << 16 | 'A' << 8 | 'T',
+
+	iTXt = 'i' << 24 | 'T' << 16 | 'X' << 8 | 't',
+	tEXt = 't' << 24 | 'E' << 16 | 'X' << 8 | 't',
+	zTXt = 'z' << 24 | 'T' << 16 | 'X' << 8 | 't',
+
+	iCCP = 'i' << 24 | 'C' << 16 | 'C' << 8 | 'P',
+	pHYs = 'p' << 24 | 'H' << 16 | 'Y' << 8 | 's',
+	gAMA = 'g' << 24 | 'A' << 16 | 'M' << 8 | 'A',
+	tIME = 't' << 24 | 'I' << 16 | 'M' << 8 | 'E',
+
+	sPLT = 's' << 24 | 'P' << 16 | 'L' << 8 | 'T',
+	sRGB = 's' << 24 | 'R' << 16 | 'G' << 8 | 'B',
+	hIST = 'h' << 24 | 'I' << 16 | 'S' << 8 | 'T',
+	cHRM = 'c' << 24 | 'H' << 16 | 'R' << 8 | 'M',
+	sBIT = 's' << 24 | 'B' << 16 | 'I' << 8 | 'T',
+
+	/*
+		eXIf tags are not part of the core spec, but have been ratified
+		in v1.5.0 of the PNG Ext register.
+
+		We will provide unprocessed chunks to the caller if `.return_metadata` is set.
+		Applications are free to implement an Exif decoder.
+	*/
+	eXIf = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f',
+
+	// PNG files must end with IEND
+	IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D',
+
+	/*
+		XCode sometimes produces "PNG" files that don't adhere to the PNG spec.
+		We recognize them only in order to avoid doing further work on them.
+
+		Some tools like PNG Defry may be able to repair them, but we're not
+		going to reward Apple for producing proprietary broken files purporting
+		to be PNGs by supporting them.
+
+	*/
+	iDOT = 'i' << 24 | 'D' << 16 | 'O' << 8 | 'T',
+	CbGI = 'C' << 24 | 'b' << 16 | 'H' << 8 | 'I',
+}
+
+PNG_IHDR :: struct #packed {
+	width:              u32be,
+	height:             u32be,
+	bit_depth:          u8,
+	color_type:         PNG_Color_Type,
+	compression_method: u8,
+	filter_method:      u8,
+	interlace_method:   PNG_Interlace_Method,
+}
+PNG_IHDR_SIZE :: size_of(PNG_IHDR)
+#assert (PNG_IHDR_SIZE == 13)
 
+PNG_Color_Value :: enum u8 {
+	Paletted = 0, // 1 << 0 = 1
+	Color    = 1, // 1 << 1 = 2
+	Alpha    = 2, // 1 << 2 = 4
+}
+PNG_Color_Type :: distinct bit_set[PNG_Color_Value; u8]
+
+PNG_Interlace_Method :: enum u8 {
+	None  = 0,
+	Adam7 = 1,
+}
+
+/*
+	Functions to help with image buffer calculations
+*/
 compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
 	size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height
 	return
@@ -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.
 */
-
 Channel :: enum u8 {
 	R = 1,
 	G = 2,
@@ -226,8 +319,8 @@ return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok
 	res.depth         = img.depth
 	res.pixels        = t
 	res.background    = img.background
-	res.metadata_ptr  = img.metadata_ptr
-	res.metadata_type = img.metadata_type
+	// res.metadata_ptr  = img.metadata_ptr
+	// res.metadata_type = img.metadata_type
 
 	return res, true
 }

+ 81 - 83
core/image/png/example.odin

@@ -53,93 +53,91 @@ demo :: proc() {
 	} else {
 		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)
-
-		v := (^Info)(img.metadata_ptr)
-
-		// Handle ancillary chunks as you wish.
-		// We provide helper functions for a few types.
-		for c in v.chunks {
-			#partial switch c.header.type {
-			case .tIME:
-				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)
+		if v, ok := img.metadata.(^image.PNG_Info); ok {
+			// Handle ancillary chunks as you wish.
+			// We provide helper functions for a few types.
+			for c in v.chunks {
+				#partial switch c.header.type {
+				case .tIME:
+					if t, t_ok := core_time(c); t_ok {
+						fmt.printf("[tIME]: %v\n", t)
 					}
-				}
-			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)
+				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 .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("[tEXt/zTXt] %v: %v\n", res.keyword, res.text)
+						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
core/image/png/helpers.odin

@@ -34,16 +34,13 @@ destroy :: proc(img: ^Image) {
 
 	bytes.buffer_destroy(&img.pixels)
 
-	assert(img.metadata_ptr != nil && img.metadata_type == Info)
-	v := (^Info)(img.metadata_ptr)
-
-	for chunk in &v.chunks {
-		delete(chunk.data)
+	if v, ok := img.metadata.(^image.PNG_Info); ok {
+		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)
 }
 
@@ -51,7 +48,7 @@ destroy :: proc(img: ^Image) {
 	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) {
 		return {}, false
 	}
@@ -61,7 +58,7 @@ gamma :: proc(c: Chunk) -> (res: f32, ok: bool) {
 
 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) {
 		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
 }
 
-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) {
 		return {}, false
 	}
@@ -81,7 +78,7 @@ time :: proc(c: Chunk) -> (res: tIME, ok: bool) {
 	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 {
 		using png_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))
 	#partial switch c.header.type {
 	case .tEXt:
@@ -196,7 +193,7 @@ text_destroy :: proc(text: Text) {
 	delete(text.text)
 }
 
-iccp :: proc(c: Chunk) -> (res: iCCP, ok: bool) {
+iccp :: proc(c: image.PNG_Chunk) -> (res: iCCP, ok: bool) {
 	ok = true
 
 	fields := bytes.split_n(s=c.data, sep=[]u8{0}, n=3, allocator=context.temp_allocator)
@@ -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) {
 		return {}, false
 	}
@@ -244,7 +241,7 @@ srgb :: proc(c: Chunk) -> (res: sRGB, ok: bool) {
 	return res, true
 }
 
-plte :: proc(c: Chunk) -> (res: PLTE, ok: bool) {
+plte :: proc(c: image.PNG_Chunk) -> (res: PLTE, ok: bool) {
 	if c.header.type != .PLTE {
 		return {}, false
 	}
@@ -258,7 +255,7 @@ plte :: proc(c: Chunk) -> (res: PLTE, ok: bool) {
 	return
 }
 
-splt :: proc(c: Chunk) -> (res: sPLT, ok: bool) {
+splt :: proc(c: image.PNG_Chunk) -> (res: sPLT, ok: bool) {
 	if c.header.type != .sPLT {
 		return {}, false
 	}
@@ -309,7 +306,7 @@ splt_destroy :: proc(s: sPLT) {
 	delete(s.name)
 }
 
-sbit :: proc(c: Chunk) -> (res: [4]u8, ok: bool) {
+sbit :: proc(c: image.PNG_Chunk) -> (res: [4]u8, ok: bool) {
 	/*
 		Returns [4]u8 with the significant bits in each channel.
 		A channel will contain zero if not applicable to the PNG color type.
@@ -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 {
 		return {}, false
 	}
@@ -349,7 +346,7 @@ hist :: proc(c: Chunk) -> (res: hIST, ok: bool) {
 	return
 }
 
-chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) {
+chrm :: proc(c: image.PNG_Chunk) -> (res: cHRM, ok: bool) {
 	ok = true
 	if c.header.length != size_of(cHRM_Raw) {
 		return {}, false
@@ -367,7 +364,7 @@ chrm :: proc(c: Chunk) -> (res: cHRM, ok: bool) {
 	return
 }
 
-exif :: proc(c: Chunk) -> (res: Exif, ok: bool) {
+exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) {
 
 	ok = true
 

+ 17 - 107
core/image/png/png.odin

@@ -51,95 +51,6 @@ Signature :: enum u64be {
 	PNG = 0x89 << 56 | 'P' << 48 | 'N' << 40 | 'G' << 32 | '\r' << 24 | '\n' << 16 | 0x1a << 8 | '\n',
 }
 
-Info :: struct {
-	header: IHDR,
-	chunks: [dynamic]Chunk,
-}
-
-Chunk_Header :: struct #packed {
-	length: u32be,
-	type:   Chunk_Type,
-}
-
-Chunk :: struct #packed {
-	header: Chunk_Header,
-	data:   []byte,
-	crc:    u32be,
-}
-
-Chunk_Type :: enum u32be {
-	// IHDR must come first in a file
-	IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R',
-	// PLTE must precede the first IDAT chunk
-	PLTE = 'P' << 24 | 'L' << 16 | 'T' << 8 | 'E',
-	bKGD = 'b' << 24 | 'K' << 16 | 'G' << 8 | 'D',
-	tRNS = 't' << 24 | 'R' << 16 | 'N' << 8 | 'S',
-	IDAT = 'I' << 24 | 'D' << 16 | 'A' << 8 | 'T',
-
-	iTXt = 'i' << 24 | 'T' << 16 | 'X' << 8 | 't',
-	tEXt = 't' << 24 | 'E' << 16 | 'X' << 8 | 't',
-	zTXt = 'z' << 24 | 'T' << 16 | 'X' << 8 | 't',
-
-	iCCP = 'i' << 24 | 'C' << 16 | 'C' << 8 | 'P',
-	pHYs = 'p' << 24 | 'H' << 16 | 'Y' << 8 | 's',
-	gAMA = 'g' << 24 | 'A' << 16 | 'M' << 8 | 'A',
-	tIME = 't' << 24 | 'I' << 16 | 'M' << 8 | 'E',
-
-	sPLT = 's' << 24 | 'P' << 16 | 'L' << 8 | 'T',
-	sRGB = 's' << 24 | 'R' << 16 | 'G' << 8 | 'B',
-	hIST = 'h' << 24 | 'I' << 16 | 'S' << 8 | 'T',
-	cHRM = 'c' << 24 | 'H' << 16 | 'R' << 8 | 'M',
-	sBIT = 's' << 24 | 'B' << 16 | 'I' << 8 | 'T',
-
-	/*
-		eXIf tags are not part of the core spec, but have been ratified
-		in v1.5.0 of the PNG Ext register.
-
-		We will provide unprocessed chunks to the caller if `.return_metadata` is set.
-		Applications are free to implement an Exif decoder.
-	*/
-	eXIf = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f',
-
-	// PNG files must end with IEND
-	IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D',
-
-	/*
-		XCode sometimes produces "PNG" files that don't adhere to the PNG spec.
-		We recognize them only in order to avoid doing further work on them.
-
-		Some tools like PNG Defry may be able to repair them, but we're not
-		going to reward Apple for producing proprietary broken files purporting
-		to be PNGs by supporting them.
-
-	*/
-	iDOT = 'i' << 24 | 'D' << 16 | 'O' << 8 | 'T',
-	CbGI = 'C' << 24 | 'b' << 16 | 'H' << 8 | 'I',
-}
-
-IHDR :: struct #packed {
-	width:              u32be,
-	height:             u32be,
-	bit_depth:          u8,
-	color_type:         Color_Type,
-	compression_method: u8,
-	filter_method:      u8,
-	interlace_method:   Interlace_Method,
-}
-IHDR_SIZE :: size_of(IHDR)
-#assert (IHDR_SIZE == 13)
-
-Color_Value :: enum u8 {
-	Paletted = 0, // 1 << 0 = 1
-	Color    = 1, // 1 << 1 = 2
-	Alpha    = 2, // 1 << 2 = 4
-}
-Color_Type :: distinct bit_set[Color_Value; u8]
-
-Interlace_Method :: enum u8 {
-	None  = 0,
-	Adam7 = 1,
-}
-
 Row_Filter :: enum u8 {
 	None    = 0,
 	Sub     = 1,
@@ -262,8 +173,8 @@ ADAM7_Y_SPACING := []int{ 8,8,8,4,4,2,2 }
 
 // Implementation starts here
 
-read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) {
-	ch, e := compress.read_data(ctx, Chunk_Header)
+read_chunk :: proc(ctx: ^$C) -> (chunk: image.PNG_Chunk, err: Error) {
+	ch, e := compress.read_data(ctx, image.PNG_Chunk_Header)
 	if e != .None {
 		return {}, compress.General_Error.Stream_Too_Short
 	}
@@ -305,7 +216,7 @@ read_chunk :: proc(ctx: ^$C) -> (chunk: Chunk, err: Error) {
 	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) {
 		return {}, .Invalid_Chunk_Length
 	}
@@ -318,7 +229,7 @@ copy_chunk :: proc(src: Chunk, allocator := context.allocator) -> (dest: Chunk,
 	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) {
 		return .Invalid_Chunk_Length
 	}
@@ -334,13 +245,13 @@ append_chunk :: proc(list: ^[dynamic]Chunk, src: Chunk, allocator := context.all
 	return
 }
 
-read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
+read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) {
 	c, e := read_chunk(ctx)
 	if e != nil {
 		return {}, e
 	}
 
-	header := (^IHDR)(raw_data(c.data))^
+	header := (^image.PNG_IHDR)(raw_data(c.data))^
 	// Validate IHDR
 	using header
 	if width == 0 || height == 0 || u128(width) * u128(height) > MAX_DIMENSIONS {
@@ -407,7 +318,7 @@ read_header :: proc(ctx: ^$C) -> (IHDR, Error) {
 	return header, nil
 }
 
-chunk_type_to_name :: proc(type: ^Chunk_Type) -> string {
+chunk_type_to_name :: proc(type: ^image.PNG_Chunk_Type) -> string {
 	t := transmute(^u8)type
 	return strings.string_from_ptr(t, 4)
 }
@@ -462,9 +373,8 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 		img = new(Image)
 	}
 
-	info := new(Info)
-	img.metadata_ptr  = info
-	img.metadata_type = typeid_of(Info)
+	info := new(image.PNG_Info)
+	img.metadata = info
 
 	signature, io_error := compress.read_data(ctx, Signature)
 	if io_error != .None || signature != .PNG {
@@ -477,11 +387,11 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 
 	idat_length := u64(0)
 
-	c:		Chunk
-	ch:     Chunk_Header
+	c:		image.PNG_Chunk
+	ch:     image.PNG_Chunk_Header
 	e:      io.Error
 
-	header:	IHDR
+	header:	image.PNG_IHDR
 
 	// State to ensure correct chunk ordering.
 	seen_ihdr := false; first := true
@@ -492,7 +402,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 	seen_iend := false
 
 	_plte := PLTE{}
-	trns := Chunk{}
+	trns := image.PNG_Chunk{}
 
 	final_image_channels := 0
 
@@ -502,7 +412,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 		// Peek at next chunk's length and type.
 		// TODO: Some streams may not provide seek/read_at
 
-		ch, e = compress.peek_data(ctx, Chunk_Header)
+		ch, e = compress.peek_data(ctx, image.PNG_Chunk_Header)
 		if e != .None {
 			return img, 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)
 
 			using header
-			h := IHDR{
+			h := image.PNG_IHDR{
 				width              = width,
 				height             = height,
 				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
 				}
 
-				ch, e = compress.peek_data(ctx, Chunk_Header)
+				ch, e = compress.peek_data(ctx, image.PNG_Chunk_Header)
 				if e != .None {
 					return img, compress.General_Error.Stream_Too_Short
 				}
@@ -1599,7 +1509,7 @@ defilter_16 :: proc(params: ^Filter_Params) -> (ok: bool) {
 	return
 }
 
-defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^IHDR, options: Options) -> (err: Error) {
+defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IHDR, options: Options) -> (err: Error) {
 	input    := bytes.buffer_to_bytes(filter_bytes)
 	width    := int(header.width)
 	height   := int(header.height)

+ 1 - 3
tests/core/image/test_core_image.odin

@@ -1504,10 +1504,8 @@ run_png_suite :: proc(t: ^testing.T, suite: []PNG_Test) -> (subtotal: int) {
 
 				passed &= test.hash == hash
 				if .return_metadata in test.options {
-					v: ^png.Info
 
-					if img.metadata_ptr != nil && img.metadata_type == png.Info {
-						v = (^png.Info)(img.metadata_ptr)
+					if v, ok := img.metadata.(^image.PNG_Info); ok {
 						for c in v.chunks {
 							#partial switch(c.header.type) {
 							case .gAMA: