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' ,
2024-05-04 03:24:01 +02:00
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 ) {
2024-05-04 03:24:01 +02:00
seq_header : SequenceHeader
frame_header : FrameHeader
2024-05-03 01:42:45 +02:00
// TODO:
2024-05-04 03:24:01 +02:00
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 )
}
}