mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-03 14:18:47 +00:00
jpeg: extract Exif data
This commit is contained in:
committed by
Jeroen van Rijn
parent
57a92b14cc
commit
cb820eea4d
@@ -67,6 +67,13 @@ Image_Metadata :: union #shared_nil {
|
|||||||
^JPEG_Info,
|
^JPEG_Info,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Exif :: struct {
|
||||||
|
byte_order: enum {
|
||||||
|
little_endian,
|
||||||
|
big_endian,
|
||||||
|
},
|
||||||
|
data: []u8 `fmt:"-"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -582,6 +589,7 @@ TGA_Info :: struct {
|
|||||||
*/
|
*/
|
||||||
JFIF_Magic := [?]byte{0x4A, 0x46, 0x49, 0x46} // "JFIF"
|
JFIF_Magic := [?]byte{0x4A, 0x46, 0x49, 0x46} // "JFIF"
|
||||||
JFXX_Magic := [?]byte{0x4A, 0x46, 0x58, 0x58} // "JFXX"
|
JFXX_Magic := [?]byte{0x4A, 0x46, 0x58, 0x58} // "JFXX"
|
||||||
|
Exif_Magic := [?]byte{0x45, 0x78, 0x69, 0x66} // "Exif"
|
||||||
|
|
||||||
JPEG_Error :: enum {
|
JPEG_Error :: enum {
|
||||||
None = 0,
|
None = 0,
|
||||||
@@ -704,7 +712,7 @@ JPEG_Info :: struct {
|
|||||||
jfif_app0: Maybe(JFIF_APP0),
|
jfif_app0: Maybe(JFIF_APP0),
|
||||||
jfxx_app0: Maybe(JFXX_APP0),
|
jfxx_app0: Maybe(JFXX_APP0),
|
||||||
comments: [dynamic]string,
|
comments: [dynamic]string,
|
||||||
//exif: Maybe(Exif),
|
exif: [dynamic]Exif,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to help with image buffer calculations
|
// Function to help with image buffer calculations
|
||||||
|
|||||||
+76
-11
@@ -2,7 +2,6 @@ package jpeg
|
|||||||
|
|
||||||
import "core:bytes"
|
import "core:bytes"
|
||||||
import "core:compress"
|
import "core:compress"
|
||||||
import "core:fmt"
|
|
||||||
import "core:math"
|
import "core:math"
|
||||||
import "core:mem"
|
import "core:mem"
|
||||||
import "core:image"
|
import "core:image"
|
||||||
@@ -19,6 +18,7 @@ HUFFMAN_MAX_BITS :: 16
|
|||||||
THUMBNAIL_PALETTE_SIZE :: 768
|
THUMBNAIL_PALETTE_SIZE :: 768
|
||||||
BLOCK_SIZE :: 8
|
BLOCK_SIZE :: 8
|
||||||
COEFFICIENT_COUNT :: BLOCK_SIZE * BLOCK_SIZE
|
COEFFICIENT_COUNT :: BLOCK_SIZE * BLOCK_SIZE
|
||||||
|
SEGMENT_MAX_SIZE :: 65533
|
||||||
|
|
||||||
Coefficient :: enum u8 {
|
Coefficient :: enum u8 {
|
||||||
DC,
|
DC,
|
||||||
@@ -235,7 +235,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
if b == 0x00 {
|
if b == 0x00 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
append(&ident, b)
|
append(&ident, b) or_return
|
||||||
}
|
}
|
||||||
if slice.equal(ident[:], image.JFIF_Magic[:]) {
|
if slice.equal(ident[:], image.JFIF_Magic[:]) {
|
||||||
if length != 14 {
|
if length != 14 {
|
||||||
@@ -343,7 +343,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
}
|
}
|
||||||
img.metadata = info
|
img.metadata = info
|
||||||
}
|
}
|
||||||
case .Thumbnail_1_Byte_Palette: // NOTE: NOT TESTED. Couldn't find a jpeg to test this with.
|
case .Thumbnail_1_Byte_Palette: // NOTE(illusionman1212): NOT TESTED. Couldn't find a jpeg to test this with.
|
||||||
x_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
x_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
||||||
y_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
y_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
||||||
palette := slice.reinterpret([]image.RGB_Pixel, compress.read_slice(ctx, THUMBNAIL_PALETTE_SIZE / 3) or_return)
|
palette := slice.reinterpret([]image.RGB_Pixel, compress.read_slice(ctx, THUMBNAIL_PALETTE_SIZE / 3) or_return)
|
||||||
@@ -380,17 +380,78 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
compress.read_slice(ctx, length - len(ident) - 1) or_return
|
compress.read_slice(ctx, length - len(ident) - 1) or_return
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// case .APP1: // Exif metadata
|
case .APP1: // Metadata
|
||||||
// unimplemented("APP1")
|
length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2)
|
||||||
|
if .return_metadata not_in options {
|
||||||
|
compress.read_slice(ctx, length) or_return
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
info: ^image.JPEG_Info
|
||||||
|
if img.metadata == nil {
|
||||||
|
info = new(image.JPEG_Info) or_return
|
||||||
|
} else {
|
||||||
|
info = img.metadata.(^image.JPEG_Info)
|
||||||
|
}
|
||||||
|
|
||||||
|
ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return
|
||||||
|
for {
|
||||||
|
b := compress.read_u8(ctx) or_return
|
||||||
|
if b == 0x00 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
append(&ident, b) or_return
|
||||||
|
}
|
||||||
|
|
||||||
|
if slice.equal(ident[:], image.Exif_Magic[:]) {
|
||||||
|
// Padding byte according to section 4.7.2.2 in Exif spec 3.0
|
||||||
|
compress.read_u8(ctx) or_return
|
||||||
|
|
||||||
|
exif: image.Exif
|
||||||
|
peek := compress.peek_data(ctx, [4]byte) or_return
|
||||||
|
if peek[0] == 'M' && peek[1] == 'M' {
|
||||||
|
exif.byte_order = .big_endian
|
||||||
|
if peek[2] != 0 || peek[3] != 42 {
|
||||||
|
// - 2 for the NUL byte and padding byte
|
||||||
|
compress.read_slice(ctx, length - len(ident) - 2) or_return
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else if peek[0] == 'I' && peek[1] == 'I' {
|
||||||
|
exif.byte_order = .little_endian
|
||||||
|
if peek[2] != 42 || peek[3] != 0 {
|
||||||
|
compress.read_slice(ctx, length - len(ident) - 2) or_return
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we can't determine the endianness then this Exif data is likely a continuation of the previous
|
||||||
|
// APP1 Exif data
|
||||||
|
|
||||||
|
// We only treat it as such if a previous Exif entry exists and its data length is the max
|
||||||
|
if len(info.exif) > 0 && len(info.exif[len(info.exif) - 1].data) == SEGMENT_MAX_SIZE - len(ident) - 2 {
|
||||||
|
exif.byte_order = info.exif[len(info.exif) - 1].byte_order
|
||||||
|
} else {
|
||||||
|
compress.read_slice(ctx, length - len(ident) - 2) or_return
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// - 2 for the NUL byte and padding byte
|
||||||
|
data := compress.read_slice(ctx, length - len(ident) - 2) or_return
|
||||||
|
exif.data = make([]byte, len(data)) or_return
|
||||||
|
copy(exif.data, data)
|
||||||
|
|
||||||
|
append(&info.exif, exif) or_return
|
||||||
|
img.metadata = info
|
||||||
|
} else {
|
||||||
|
// - 1 for the NUL byte
|
||||||
|
compress.read_slice(ctx, length - len(ident) - 1) or_return
|
||||||
|
continue
|
||||||
|
}
|
||||||
case .COM:
|
case .COM:
|
||||||
length := (compress.read_data(ctx, u16be) or_return) - 2
|
length := (compress.read_data(ctx, u16be) or_return) - 2
|
||||||
comment := string(compress.read_slice(ctx, cast(int)length) or_return)
|
comment := string(compress.read_slice(ctx, cast(int)length) or_return)
|
||||||
if .return_metadata in options {
|
if .return_metadata in options {
|
||||||
if info, ok := img.metadata.(^image.JPEG_Info); ok {
|
if info, ok := img.metadata.(^image.JPEG_Info); ok {
|
||||||
if info.comments == nil {
|
append(&info.comments, strings.clone(comment)) or_return
|
||||||
info.comments = make([dynamic]string, 0, 8, allocator) or_return
|
|
||||||
}
|
|
||||||
append(&info.comments, strings.clone(comment))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .DQT:
|
case .DQT:
|
||||||
@@ -504,7 +565,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
// how many lines in the frame we have.
|
// how many lines in the frame we have.
|
||||||
// ISO/IEC 10918-1: 1993.
|
// ISO/IEC 10918-1: 1993.
|
||||||
// Section B.2.5
|
// Section B.2.5
|
||||||
if width == 0 || height == 0 {
|
if img.width == 0 || img.height == 0 || img.width * img.height > image.MAX_DIMENSIONS {
|
||||||
return img, .Invalid_Image_Dimensions
|
return img, .Invalid_Image_Dimensions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,7 +653,6 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|||||||
case .SOF14: // Differential progressive DCT, Arithmetic coding
|
case .SOF14: // Differential progressive DCT, Arithmetic coding
|
||||||
fallthrough
|
fallthrough
|
||||||
case .SOF15: // Differential lossless (sequential), Arithmetic coding
|
case .SOF15: // Differential lossless (sequential), Arithmetic coding
|
||||||
fmt.println(marker)
|
|
||||||
return img, .Unsupported_Frame_Type
|
return img, .Unsupported_Frame_Type
|
||||||
case .SOS:
|
case .SOS:
|
||||||
if img.channels == 0 && img.depth == 0 && img.width == 0 && img.height == 0 {
|
if img.channels == 0 && img.depth == 0 && img.width == 0 && img.height == 0 {
|
||||||
@@ -936,6 +996,11 @@ destroy :: proc(img: ^Image) {
|
|||||||
}
|
}
|
||||||
delete(v.comments)
|
delete(v.comments)
|
||||||
|
|
||||||
|
for exif in v.exif {
|
||||||
|
delete(exif.data)
|
||||||
|
}
|
||||||
|
delete(v.exif)
|
||||||
|
|
||||||
free(v)
|
free(v)
|
||||||
}
|
}
|
||||||
free(img)
|
free(img)
|
||||||
|
|||||||
@@ -366,7 +366,7 @@ chrm :: proc(c: image.PNG_Chunk) -> (res: cHRM, ok: bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) {
|
exif :: proc(c: image.PNG_Chunk) -> (res: image.Exif, ok: bool) {
|
||||||
|
|
||||||
ok = true
|
ok = true
|
||||||
|
|
||||||
@@ -396,4 +396,4 @@ exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) {
|
|||||||
General helper functions
|
General helper functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
compute_buffer_size :: image.compute_buffer_size
|
compute_buffer_size :: image.compute_buffer_size
|
||||||
|
|||||||
@@ -138,14 +138,6 @@ Text :: struct {
|
|||||||
text: string,
|
text: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
Exif :: struct {
|
|
||||||
byte_order: enum {
|
|
||||||
little_endian,
|
|
||||||
big_endian,
|
|
||||||
},
|
|
||||||
data: []u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
iCCP :: struct {
|
iCCP :: struct {
|
||||||
name: string,
|
name: string,
|
||||||
profile: []u8,
|
profile: []u8,
|
||||||
|
|||||||
Reference in New Issue
Block a user