Files

506 lines
15 KiB
Odin
Raw Permalink Normal View History

2024-05-02 12:12:13 +02:00
package avif
import "core:bytes"
import "core:fmt"
import "core:image"
import "core:encoding/endian"
import "core:io"
import "core:strings"
2024-05-03 01:42:45 +02:00
//Error :: image.Error
2024-05-02 12:12:13 +02:00
//Image :: image.Image
Options :: image.Options
Image :: struct {
compatible_brands: [dynamic]ISOBMFFBrand,
major_brand: ISOBMFFBrand,
brand_version: u32,
handler_type: HEIFTrack,
handler_name: string,
primary_item_id: u32,
}
// Noticed avif compatible brands, avif, mif1, miaf, MA1A, MA1B
// Files containing MA1A as a compatible brand are expected to list the following brands in any order:
// avif, mif1, miaf, MA1A
// If the file contains a `pict` track compliant with this profile then it's expected to list the following brands in any order:
// avis, msf1, miaf, MA1A
// Files containing MA1B as a compatible brand is expected to list the following brands in any order:
// avif, mif1, miaf, MA1B
// If the file contains a `pict` track compliant with this profile then it's expected to list the following brands in any order:
// avis, msf1, miaf, MA1B
// If the file contains a `pict` track compliant with this profile and made only of samples marked `sync` then it's expected to list the following brands in any order:
// avis, avio, msf1, miaf, MA1B
VersionAndFlags :: bit_field u32 {
flags: u32 | 24,
version: u16 | 8, // should be u8 but compiler fails an assertion that I couldn't reproduce
}
ILOCSize :: bit_field u16 {
index_size_OR_reserved: u8 | 4,
base_offset_size: u8 | 4,
length_size: u8 | 4,
offset_size: u8 | 4,
}
// NOTE: We order the bits in the reverse order because Odin's bit_field is LSB while the spec is MSB.
AV1CodecConfigRecord :: bit_field u32 {
initial_presentation_delay_minus_one_OR_reserved: u8 | 4,
initial_presentation_delay_present: u8 | 1,
reserved: u8 | 3,
chroma_sample_position: u8 | 2,
chroma_subsampling_y: u8 | 1,
chroma_subsampling_x: u8 | 1,
monochrome: u8 | 1,
twelve_bit: u8 | 1,
high_bitdepth: u8 | 1,
seq_tier_0: u8 | 1,
seq_level_idx_0: u8 | 5,
seq_profile: u8 | 3,
version: u8 | 7,
marker: u8 | 1,
}
ISOBMFFBrand :: enum u32 {
AVIF = 'a' << 24 | 'v' << 16 | 'i' << 8 | 'f',
MIF1 = 'm' << 24 | 'i' << 16 | 'f' << 8 | '1',
MIAF = 'm' << 24 | 'i' << 16 | 'a' << 8 | 'f',
MA1A = 'M' << 24 | 'A' << 16 | '1' << 8 | 'A',
MA1B = 'M' << 24 | 'A' << 16 | '1' << 8 | 'B',
AVIS = 'a' << 24 | 'v' << 16 | 'i' << 8 | 's',
AVIO = 'a' << 24 | 'v' << 16 | 'i' << 8 | 'o',
MSF1 = 'm' << 24 | 's' << 16 | 'f' << 8 | '1',
AV01 = 'a' << 24 | 'v' << 16 | '0' << 8 | '1',
ISO8 = 'i' << 24 | 's' << 16 | 'o' << 8 | '8',
2024-05-02 12:12:13 +02:00
}
HEIFTrack :: enum u32 {
PICT = 'p' << 24 | 'i' << 16 | 'c' << 8 | 't', // Picture track
}
save_to_buffer :: proc(
output: ^bytes.Buffer,
img: ^Image,
options := Options{},
allocator := context.allocator,
) -> (
err: Error,
) {
// TODO:
return nil
}
load_from_bytes :: proc(
data: []byte,
options := Options{},
allocator := context.allocator,
) -> (
img: ^Image,
err: Error,
) {
context.allocator = allocator
img = new(Image)
//img.which = .AVIF
reader: Reader
reader_init(&reader, data)
res := read_box(img, &reader)
for res == .None {
res = read_box(img, &reader)
}
if res != .EOF {
fmt.eprintln(res)
return nil, .Invalid_Input_Image
}
return img, nil
}
@(private)
read_box :: proc(img: ^Image, reader: ^Reader) -> (err: io.Error) {
if read_header(reader) == .EOF {
return .EOF
}
// if the Box has a size 0 then it's the last Box, parse it and return EOF
2024-05-03 01:42:45 +02:00
if reader.box.size == 0 {
2024-05-02 12:12:13 +02:00
err = .EOF
}
2024-05-03 01:42:45 +02:00
fmt.println("box size:", reader.box.size)
fmt.println("type:", reader.box.type)
2024-05-02 12:12:13 +02:00
2024-05-03 01:42:45 +02:00
#partial switch reader.box.type {
2024-05-02 12:12:13 +02:00
case .FTYP:
read_ftyp(img, reader)
case .META:
read_meta(reader)
case .HDLR:
read_hdlr(img, reader)
case .PITM:
read_pitm(img, reader)
case .ILOC:
read_iloc(reader)
case .IINF:
read_iinf(reader)
case .INFE:
read_infe(reader)
case .IPRP: // Just serves as a container for the boxes inside it.
case .IPCO: // Same as IPRP
case .ISPE:
read_ispe(reader)
case .PIXI:
read_pixi(reader)
case .AV1C:
read_av1c(reader)
case .COLR:
read_colr(reader)
case .IPMA:
read_ipma(reader)
2024-05-03 01:42:45 +02:00
case .MDAT:
read_mdat(reader)
2024-05-02 12:12:13 +02:00
case:
// Skip unknown boxes
skip_box(reader)
}
return err
}
2024-05-03 01:42:45 +02:00
@(private)
read_mdat :: proc(reader: ^Reader) {
seq_header: SequenceHeader
frame_header: FrameHeader
2024-05-03 01:42:45 +02:00
// TODO:
read_obu(reader, &seq_header, &frame_header)
read_obu(reader, &seq_header, &frame_header)
read_obu(reader, &seq_header, &frame_header)
read_obu(reader, &seq_header, &frame_header)
2024-05-03 01:42:45 +02:00
}
2024-05-02 12:12:13 +02:00
@(private)
read_ipma :: proc(reader: ^Reader) {
vaf, _ := read_bitfield(reader, VersionAndFlags)
entry_count, _ := read_u32be(reader)
fmt.println("association entries:", entry_count)
for i in 0 ..< entry_count {
item_id: u32
if vaf.version == 0 {
t, _ := read_u16be(reader)
item_id = auto_cast t
} else {
item_id, _ = read_u32be(reader)
}
association_count, _ := read_byte(reader)
fmt.println("item_id:", item_id)
fmt.println("association_count:", association_count)
// TODO: something something association, IDFK I DONT HAVE THE LATEST SPEC CUZ ITS PAYWALLED
for c in 0 ..< association_count {
if (vaf.flags & 1) == 1 { // if LSB is 1
// TODO: idfk
//data: [2]u8
//reader_read(reader, data[:])
} else {
data, _ := read_byte(reader)
fmt.println(data)
}
}
}
}
@(private)
read_colr :: proc(reader: ^Reader) {
_color_type: [4]u8
read_slice(reader, _color_type[:])
color_type := string(_color_type[:])
if color_type == "nclx" {
color_primaries, _ := read_u16be(reader)
transfer_characteristics, _ := read_u16be(reader)
matrix_coefficients, _ := read_u16be(reader)
data, _ := read_byte(reader)
// remaining 7 bits are reserved
full_range_flag := data >> 7
fmt.println("color primaries:", color_primaries)
fmt.println("transfer characteristics:", transfer_characteristics)
fmt.println("matrix coefficients:", matrix_coefficients)
fmt.println("full_range_flag", full_range_flag)
} else if color_type == "rICC" {
// TODO: restricted icc profile
fmt.eprintln("rICC color profile is not supported yet")
} else if color_type == "prof" {
fmt.eprintln("prof color profile is not supported yet")
// TODO: icc profile
}
}
@(private)
read_av1c :: proc(reader: ^Reader) {
data, _ := read_bitfield(reader, AV1CodecConfigRecord)
fmt.println(data)
}
@(private)
read_pixi :: proc(reader: ^Reader) {
vaf, _ := read_bitfield(reader, VersionAndFlags)
channel_bits: []u8
if vaf.version == 0 {
channels_count, _ := read_byte(reader)
channel_bits = make([]u8, channels_count)
fmt.printfln("There are %d channels in this image", channels_count)
for i in 0 ..< channels_count {
bits_per_channel, _ := read_byte(reader)
channel_bits[i] = bits_per_channel
}
}
fmt.println(channel_bits)
}
@(private)
read_ispe :: proc(reader: ^Reader) {
vaf, _ := read_bitfield(reader, VersionAndFlags)
if vaf.version == 0 {
width, _ := read_u32be(reader)
height, _ := read_u32be(reader)
fmt.println("Width:", width)
fmt.println("Height:", height)
} else {
// TODO: should we error here??
fmt.eprintln("Unknown ispe version:", vaf.version)
}
}
@(private)
read_infe :: proc(reader: ^Reader) {
vaf, _ := read_bitfield(reader, VersionAndFlags)
item_id: u32
item_protection_index: u16
item_name: string
content_type: string
content_encoding: string
if vaf.version == 0 || vaf.version == 1 {
_item_id, _ := read_u16be(reader)
item_id = auto_cast _item_id
item_protection_index, _ = read_u16be(reader)
item_name, _ = read_string(reader)
content_type, _ = read_string(reader)
content_encoding, _ = read_string(reader)
}
if vaf.version == 1 {
// TODO: ?? not sure about the ItemInfoExtension class
}
if vaf.version >= 2 {
// Reference: https://github.com/strukturag/libheif/blob/master/libheif/box.cc#L1567
hidden_item: bool = (vaf.flags & 1) == 1
if vaf.version == 2 {
t, _ := read_u16be(reader)
item_id = auto_cast t
} else if vaf.version == 3 {
item_id, _ = read_u32be(reader)
}
item_uri_type: string
item_type: [4]u8
item_protection_index, _ = read_u16be(reader)
read_slice(reader, item_type[:])
item_name, _ = read_string(reader)
// This is for ISOBMFF, avif uses av01 and similar named types
if string(item_type[:]) == "mime" {
content_type, _ = read_string(reader)
content_encoding, _ := read_string(reader)
} else if string(item_type[:]) == "uri " {
item_uri_type, _ = read_string(reader)
} else if string(item_type[:]) == "av01" {
// TODO: ??? idfk
}
fmt.println("item type", string(item_type[:]))
fmt.println("item uri type", item_uri_type)
}
fmt.println("VAF:", vaf)
fmt.println("item_id", item_id)
fmt.println("item_protection_index", item_protection_index)
fmt.println("item_name", item_name)
fmt.println("content type", content_type)
fmt.println("content encoding", content_encoding)
}
@(private)
read_iinf :: proc(reader: ^Reader) {
vaf, _ := read_bitfield(reader, VersionAndFlags)
entry_count: u32 = 0
if vaf.version == 0 {
t, _ := read_u16be(reader)
entry_count = auto_cast t
} else {
entry_count, _ = read_u32be(reader)
}
}
@(private)
read_iloc :: proc(reader: ^Reader) {
vaf, _ := read_bitfield(reader, VersionAndFlags)
size, _ := read_bitfield(reader, ILOCSize)
item_count: u32
if vaf.version < 2 {
_item_count, _ := read_u16be(reader)
item_count = auto_cast _item_count
} else if vaf.version == 2 {
item_count, _ = read_u32be(reader)
}
fmt.println("VAF:", vaf)
fmt.println("Size:", size)
fmt.println("Item count", item_count)
for i in 0 ..< item_count {
item_id: u32
construction_method: u8 = 0
if vaf.version < 2 {
_item_id, _ := read_u16be(reader)
item_id = auto_cast _item_id
} else {
item_count, _ = read_u32be(reader)
}
if vaf.version == 1 || vaf.version == 2 {
data, _ := read_u16be(reader)
// First 12 bits are reserved
construction_method = auto_cast (data & 0xF)
}
data_reference_index, _ := read_u16be(reader)
base_offset: u64 = 0
if size.base_offset_size == 4 {
t, _ := read_u32be(reader)
base_offset = auto_cast t
} else if size.base_offset_size == 8 {
base_offset, _ = read_u64be(reader)
}
extent_count, _ := read_u16be(reader)
fmt.println("item id:", item_id)
fmt.println("construction method", construction_method)
fmt.println("data reference index", data_reference_index)
fmt.println("base offset", base_offset)
fmt.println("extent count", extent_count)
for j in 0 ..< extent_count {
if (vaf.version == 1 || vaf.version == 2) && size.index_size_OR_reserved > 0 {
extent_index: u64 = 0
if size.index_size_OR_reserved == 4 {
t, _ := read_u32be(reader)
extent_index = auto_cast t
} else if size.index_size_OR_reserved == 8 {
extent_index, _ = read_u64be(reader)
}
fmt.println("extent index", extent_index)
}
extent_offset: u64 = 0
extent_length: u64 = 0
if size.offset_size == 4 {
t, _ := read_u32be(reader)
extent_offset = auto_cast t
} else if size.offset_size == 8 {
extent_offset, _ = read_u64be(reader)
}
if size.length_size == 4 {
t, _ := read_u32be(reader)
extent_length = auto_cast t
} else if size.length_size == 8 {
extent_length, _ = read_u64be(reader)
}
fmt.println("extent offset", extent_offset)
fmt.println("extent length", extent_length)
}
}
}
@(private)
read_pitm :: proc(img: ^Image, reader: ^Reader) {
vaf, _ := read_bitfield(reader, VersionAndFlags)
if vaf.version == 0 {
item_id, _ := read_u16be(reader)
img.primary_item_id = auto_cast item_id
} else {
img.primary_item_id, _ = read_u32be(reader)
}
}
@(private)
read_hdlr :: proc(img: ^Image, reader: ^Reader) {
vaf, _ := read_bitfield(reader, VersionAndFlags)
reader_seek(reader, 4, .Current) // Field called 'Predefined'. Always 0?
handler_type, err := read_u32be(reader)
if err != nil {
fmt.eprintln(err)
}
reader_seek(reader, 4 * 3, .Current) // array with size 3 of 4byte reserved data. [3]u32
img.handler_type = transmute(HEIFTrack)handler_type
img.handler_name, _ = read_string(reader)
}
@(private)
read_meta :: proc(reader: ^Reader) {
vaf, _ := read_bitfield(reader, VersionAndFlags)
fmt.println("Meta Box VAF:", vaf)
}
@(private)
read_ftyp :: proc(img: ^Image, reader: ^Reader) {
major_brand, _ := read_u32be(reader)
img.major_brand = transmute(ISOBMFFBrand)major_brand
img.brand_version, _ = read_u32be(reader)
num_compatible_brands := remaining_box_size(reader) / size_of(ISOBMFFBrand)
for i in 0 ..< num_compatible_brands {
brand, _ := read_u32be(reader)
append(&img.compatible_brands, transmute(ISOBMFFBrand)brand)
}
}