前言
雖然有點懶得寫筆記,但這次工程真的太浩大了,我覺得我要記錄一下。
解析的2026/6/29能取得的libheif-js 1.19.8為主,不考慮其他版本。
##ReadMore##
初階使用
如果去看libheif-js的說明,他會告訴你他的用法簡簡單單:
const decoder = new libheif.HeifDecoder(); const data = decoder.decode(file);
然後告訴你他有實作libheif,但其實深入下去就很難了,網路上也不太會有這樣的資訊,畢竟沒幾個瘋子會繼續玩下去,有生出圖就不錯了。只是很可惜,要把3D圖叫出來還是要深入了解一些東西。
這個頁面的內容不盡正確,只是個人在一個月之內挖log的心得而已。
module=libheif();
我採用的方式是老派的用script src引用,引用進來後頁面就會有個libheif可用,透過libheif()可得到libheif module,module=null且其他東西都不再使用的話似乎可以促使瀏覽器GC掉現在使用的module。
原本的libheif中支援多執行序,但-js 1.19.8沒有包,雖然有一部份的相關方法有進來,但用下去是沒有用的。測試過程中也會發現WASM就是死在一個thread。如果想要加速,兩條路,重新編譯WASM,或是走其他方法。
decoder=new module.HeifDecoder();
這個應該就是最常用的東西之一了,在要讀檔之前一定要先開一個decoder出來,之後才能decoder.decode(file);。
這東西看似簡單,但想挖深功能的時候這東西就很有趣。先看看別的。
module有實作的方法
libheif-js包了非常多原始的api,但真正有被重包的沒幾個,用透過這段code可以印出:
str="";
Object.keys(module)
.filter(x => x.toLowerCase() && typeof module[x] === "function")
.sort()
.forEach((x) => {
const codeStr = module[x].toString();
if (!codeStr.includes("[native code]")) {
str+=(`module.${x}\n`);
str+=(codeStr);
str+=("\n\n");
}
});
console.log(str);
module.BindingError
class extends Error{constructor(A){super(A),this.name="BindingError"}}
module.HeifDecoder
function(){this.decoder=null}
module.HeifImage
function(A){this.handle=A,this.img=null}
module.InternalError
class extends Error{constructor(A){super(A),this.name="InternalError"}}
module.UnboundTypeError
function(e){this.name=j,this.message=e;var l=new Error(e).stack;l!==void 0&&(this.stack=this.toString()+`
`+l.replace(/^Error(:[^\n]*)?\n/,""))}
module.count_emval_handles
()=>Y.length/2-5-kj.length
module.flushPendingDeletes
()=>{for(;dA.length;){var A=dA.pop();A.$$.deleteScheduled=!1,A.delete()}}
module.fourcc
function(A){return A.charCodeAt(0)<<24|A.charCodeAt(1)<<16|A.charCodeAt(2)<<8|A.charCodeAt(3)}
module.getInheritedInstanceCount
()=>Object.keys(uA).length
module.getLiveInheritedInstances
()=>{var A=[];for(var j in uA)uA.hasOwnProperty(j)&&A.push(uA[j]);return A}
module.heif_channel
function l(){}
module.heif_chroma
function l(){}
module.heif_chroma_downsampling_algorithm
function l(){}
module.heif_chroma_upsampling_algorithm
function l(){}
module.heif_colorspace
function l(){}
module.heif_compression_format
function l(){}
module.heif_context
function(...ij){if(Object.getPrototypeOf(this)!==x)throw new GA("Use 'new' to construct "+s);if(c.constructor_body===void 0)throw new GA(s+" has no accessible constructor");var Wj=c.constructor_body[ij.length];if(Wj===void 0)throw new GA(`Tried to invoke ctor of ${s} with invalid number of parameters (${ij.length}) - expected (${Object.keys(c.constructor_body).toString()}) parameters instead!`);return Wj.apply(this,ij)}
module.heif_context_alloc
function(...g){g.length!==s&&v(`function ${A} called with ${g.length} arguments, expected ${s}`),G.length=0;var O;d.length=o?2:1,d[0]=l,o&&(O=j[1].toWireType(G,this),d[1]=O);for(var F=0;F{$A=A,dA.length&&$A&&$A(tj)}
在上表有列的,應該就是liheif-js有特別包的,這之外的,基本上應該就是要用C語言的方式實作了。實作的部分,會是module._????????,API中的方法前面都會加個底線(_),然後沒列在上面表的方法,即使看module內不是底線,例如heif_context_get_entity_groups,他也會是native code,要用C語言方式實作。我自己實作起來覺得code超級髒,然後才開始一直挖說module有沒有作者寫好能用的東西。
這邊有趣的,是_alloc/_free/_get_number_of_top_level_images/_get_list_of_top_level_image_IDs/fourcc。等下說為什麼。
decoder.decode(file)
先看一下裡面長怎樣
console.log(decoder.decode.toString());
function(A){if(this.decoder&&r.heif_context_free(this.decoder),this.decoder=r.heif_context_alloc(),!this.decoder)return console.log("Could not create HEIF context"),[];var j=r.heif_context_read_from_memory(this.decoder,A);if(j.code!==r.heif_error_code.heif_error_Ok)return console.log("Could not parse HEIF file",j.message),[];var t=r.heif_js_context_get_list_of_top_level_image_IDs(this.decoder);if(!t||t.code)return console.log("Error loading image ids",t),[];if(!t.length)return console.log("No images found"),[];for(var e=[],l=0;l<t.length;l++){var _=r.heif_js_context_get_image_handle(this.decoder,t[l]);if(!_||_.code){console.log("Could not get image data for id",t[l],_);continue}e.push(new Q(_))}return e}
一整串東西看不懂,送去問AI就會知道。以為的decoder.decode(file)解圖,其實流程是這樣:
如果有前一decoder就釋放(heif_context_free) → 建立decoder (heif_context_alloc) → 讀檔(heif_context_read_from_memory) → 獲取檔內ID(heif_js_context_get_list_of_top_level_image_IDs) → 獲取影像handle (heif_js_context_get_image_handle) → 回傳Q影像陣列
這遠比之前想像中的複雜的多了。而且看的到code表示這是有包過的,不是libheif來的。
繼續深看decoder.decode(file)
一般的使用只會停在前面,但我卡了多次需求發現,C語言的讀檔其實是ctx=heif_context_alloc→read_from_memory,這時候ctx會變成heif_context,這個ctx超級重要,後面的所有檔案讀寫都靠他,無論你想知道heif文件結構,去調用哪張圖,哪個exif,甚至我在做的tile decode,都需要ctx指標。那libheif-js中,他跑哪去了?
之前看不懂的時候,在我tool大概0038~0041的時候,因為一直找不到ctx,真的去實作c語言的alloc+read_from_memory,越想越不對,多看幾次後才發現,原來imgs=decoder.decode(file)完成的,除了回傳的img以外,還有把decoder.decoder作為ctx,藏在decoder.decode()流程中,檢查釋放後的第一條this.decoder=r.heif_context_alloc()。也就是說,做完會有這樣兩個狀態:
decoder.decoder = yA
decoder.decoder.$$ = {}
decoder.decoder.$$.count = {}
decoder.decoder.$$.count.value = 1
decoder.decoder.$$.ptr = 188360
decoder.decoder.$$.ptrType = OA
decoder.decoder.$$.ptrType.name = "heif_context*"
decoder.decoder.$$.ptrType.registeredClass = Bt
decoder.decoder.$$.ptrType.isReference = false
decoder.decoder.$$.ptrType.isConst = false
decoder.decoder.$$.ptrType.isSmartPointer = false
imgs.length = 3
imgs[0].handle.$$.count.value = 1
imgs[0].handle.$$.ptr = 7683776
imgs[0].handle.$$.ptrType.destructorFunction = null
imgs[0].handle.$$.ptrType.isConst = false
imgs[0].handle.$$.ptrType.isReference = false
imgs[0].handle.$$.ptrType.isSmartPointer = false
imgs[0].handle.$$.ptrType.name = "heif_image_handle*"
imgs[0].handle.$$.ptrType.pointeeType = undefined
imgs[0].handle.$$.ptrType.rawConstructor = undefined
imgs[0].handle.$$.ptrType.rawDestructor = undefined
imgs[0].handle.$$.ptrType.rawGetPointee = undefined
imgs[0].handle.$$.ptrType.rawShare = undefined
imgs[0].handle.$$.ptrType.registeredClass = Bt
imgs[0].handle.$$.ptrType.sharingPolicy = undefined
imgs[0].handle.$$.ptrType.toWireType = ot
imgs[0].img = null
(imgs[1] imgs[2]結構同img[0]不贅述)
前面的decoder.decoder就是context指標,透過decoder.decode(file)後建立,這東西跟用heif_context_alloc出來的東西相同,後面的imgs是建立context後同時組成的image handle陣列。只是如果透過高階方法,decoder.decode其實在回傳image handle前會先確認有沒有壞檔,也就是某些檔案損壞的前提下,透過decoder.decode(file)回傳的影像有可能比heif紀錄的主影像還少,因為損壞圖片會被過濾掉。libheif-js沒提醒你,decoder.decode(file)完之後decoder.decoder就被配置了...。
module.fourcc()
這是個好玩的東西,這個會回傳一串數字對應到4cc,這樣就不用實作例如'ster'/'Exif'轉數字的部分。
用heif_context_read_from_memory讀檔
前面提過,拆解decoder.decode,可以知道正確的順序大概是(heif_context_alloc) → (heif_context_read_from_memory),如果後續會使用到image handle的話,時間上走decoder.decode比較划算,但如果不需要image handle的話,自己用高階的heif_context_alloc + read_from_memory大概可以省個10ms左右,只是要自己記得alloc的是ctx,read回傳的是heif_error要判斷下err.code
讀檔之後要記得heif_context_free。
C語言實做的部分-malloc/free
module._malloc,該死的東西,配置記憶體。大部分查libheif中要丟指標進去的東西都要先透過malloc配置記憶體,例如讀EXIF好了,先假設已知EXIF躲在哪個heif item中,那就要module._heif_item_get_item_data( ePtr, ctxPtr, exifId, compPtr, dataPtr, sizePtr ),五個指標五次malloc,後面還要module._free把記憶體釋放掉,超級骯髒。
就更別提decoder.decode實作的那個讀image,其實更底層順序是int heif_context_get_number_of_top_level_images(heif_context* ctx) → int heif_context_get_list_of_top_level_image_IDs(heif_context* ctx, heif_item_id* ID_array, int count) → heif_error heif_context_get_image_handle(heif_context* ctx, heif_item_id id, heif_image_handle**),解個圖六個星號..。
C語言實做的部分-[ptr >> n]
上面malloc完之後,還沒完,JS用戶已經習慣回傳的物件啦型態啦,媽啦,C語言都沒有。上面那串方法完後回傳的東西,要自己用 module.HEAPU32[ptr >> n]去切出來,資料型態變成要自己實作。code裡面就一排在[ptr >> n],有夠骯髒。
C語言實做的部分-回傳是結構就要再malloc
有注意到這個嗎?heif_error heif_context_get_image_handle,回傳的是heif_error,在WASM使用上,這鬼東西其實也會是一個指標,只要WASM中,C語言原型回傳的是結構,那搬到WASM上,第一個argument就要再配置個記憶體,所以heif_context_get_image_handle在C語言的args是3,用到JS WASM上是4,因為第一個要放errCode指標進去...。第一次遇到時光理解這個我就搞好久,後來是問Google才查到。搞死人。最後變成我需要拿著libheif的API header在看,檢查回傳的是一般型態還是結構。
後話
如之前所提,我現在的code有一部份是AI幫忙debug,但我主力的來源對程式沒那麼熟,自己也沒有SDK之類的可用,現在就是硬上
下面放置一些曾經查詢過的API記錄下
typedef uint32_t heif_entity_group_id;
typedef struct heif_entity_group {
heif_entity_group_id entity_group_id;
uint32_t entity_group_type;
heif_item_id* entities;
uint32_t num_entities;
} heif_entity_group;
typedef enum heif_metadata_compression {
heif_metadata_compression_off = 0,
heif_metadata_compression_auto = 1,
heif_metadata_compression_unknown = 2,
heif_metadata_compression_deflate = 3,
heif_metadata_compression_zlib = 4,
heif_metadata_compression_brotli = 5
} heif_metadata_compression;
typedef enum heif_error_code {
heif_error_Ok = 0,
heif_error_Input_does_not_exist = 1,
heif_error_Invalid_input = 2,
heif_error_Unsupported_filetype = 3,
heif_error_Unsupported_feature = 4,
heif_error_Usage_error = 5,
heif_error_Memory_allocation_error = 6,
heif_error_Decoder_plugin_error = 7,
heif_error_Encoder_plugin_error = 8,
heif_error_Encoding_error = 9,
heif_error_Color_profile_does_not_exist = 10,
heif_error_Plugin_loading_error = 11,
heif_error_Canceled = 12,
heif_error_End_of_sequence = 13
} heif_error_code;
typedef enum heif_suberror_code {
heif_suberror_Unspecified = 0,
heif_suberror_End_of_data = 100,
heif_suberror_Invalid_box_size = 101,
heif_suberror_No_ftyp_box = 102,
heif_suberror_No_idat_box = 103,
heif_suberror_No_meta_box = 104,
heif_suberror_No_hdlr_box = 105,
heif_suberror_No_hvcC_box = 106,
heif_suberror_No_pitm_box = 107,
heif_suberror_No_ipco_box = 108,
heif_suberror_No_ipma_box = 109,
heif_suberror_No_iloc_box = 110,
heif_suberror_No_iinf_box = 111,
heif_suberror_No_iprp_box = 112,
heif_suberror_No_iref_box = 113,
heif_suberror_No_pict_handler = 114,
heif_suberror_Ipma_box_references_nonexisting_property = 115,
heif_suberror_No_properties_assigned_to_item = 116,
heif_suberror_No_item_data = 117,
heif_suberror_Invalid_grid_data = 118,
heif_suberror_Missing_grid_images = 119,
heif_suberror_Invalid_clean_aperture = 120,
heif_suberror_Invalid_overlay_data = 121,
heif_suberror_Overlay_image_outside_of_canvas = 122,
heif_suberror_Auxiliary_image_type_unspecified = 123,
heif_suberror_No_or_invalid_primary_item = 124,
heif_suberror_No_infe_box = 125,
heif_suberror_Unknown_color_profile_type = 126,
heif_suberror_Wrong_tile_image_chroma_format = 127,
heif_suberror_Invalid_fractional_number = 128,
heif_suberror_Invalid_image_size = 129,
heif_suberror_Invalid_pixi_box = 130,
heif_suberror_No_av1C_box = 131,
heif_suberror_Wrong_tile_image_pixel_depth = 132,
heif_suberror_Unknown_NCLX_color_primaries = 133,
heif_suberror_Unknown_NCLX_transfer_characteristics = 134,
heif_suberror_Unknown_NCLX_matrix_coefficients = 135,
heif_suberror_Invalid_region_data = 136,
heif_suberror_No_ispe_property = 137,
heif_suberror_Camera_intrinsic_matrix_undefined = 138,
heif_suberror_Camera_extrinsic_matrix_undefined = 139,
heif_suberror_Invalid_J2K_codestream = 140,
heif_suberror_No_vvcC_box = 141,
heif_suberror_No_icbr_box = 142,
heif_suberror_No_avcC_box = 143,
heif_suberror_Invalid_mini_box = 149,
heif_suberror_Decompression_invalid_data = 150,
heif_suberror_No_moov_box = 151,
heif_suberror_NCLX_colr_VUI_mismatch = 152,
heif_suberror_Security_limit_exceeded = 1000,
heif_suberror_Compression_initialisation_error = 1001,
heif_suberror_Nonexisting_item_referenced = 2000,
heif_suberror_Null_pointer_argument = 2001,
heif_suberror_Nonexisting_image_channel_referenced = 2002,
heif_suberror_Unsupported_plugin_version = 2003,
heif_suberror_Unsupported_writer_version = 2004,
heif_suberror_Unsupported_parameter = 2005,
heif_suberror_Invalid_parameter_value = 2006,
heif_suberror_Invalid_property = 2007,
heif_suberror_Item_reference_cycle = 2008,
heif_suberror_Unsupported_codec = 3000,
heif_suberror_Unsupported_image_type = 3001,
heif_suberror_Unsupported_data_version = 3002,
heif_suberror_Unsupported_color_conversion = 3003,
heif_suberror_Unsupported_item_construction_method = 3004,
heif_suberror_Unsupported_header_compression_method = 3005,
heif_suberror_Unsupported_generic_compression_method = 3006,
heif_suberror_Unsupported_essential_property = 3007,
heif_suberror_Unsupported_track_type = 3008,
heif_suberror_Unsupported_bit_depth = 4000,
heif_suberror_Cannot_write_output_data = 5000,
heif_suberror_Encoder_initialization = 5001,
heif_suberror_Encoder_encoding = 5002,
heif_suberror_Encoder_cleanup = 5003,
heif_suberror_Too_many_regions = 5004,
heif_suberror_Plugin_loading_error = 6000,
heif_suberror_Plugin_is_not_loaded = 6001,
heif_suberror_Cannot_read_plugin_directory = 6002,
heif_suberror_No_matching_decoder_installed = 6003
} heif_suberror_code;
typedef struct heif_error {
heif_error_code code;
heif_suberror_code subcode;
const char* message;
} heif_error;
extern const heif_error heif_error_success;
typedef enum heif_colorspace {
heif_colorspace_YCbCr = 0,
heif_colorspace_RGB = 1,
heif_colorspace_monochrome = 2,
heif_colorspace_custom = 3,
heif_colorspace_filter_array = 4,
heif_colorspace_undefined = 99
} heif_colorspace;
typedef enum heif_chroma {
heif_chroma_planar = 0,
heif_chroma_420 = 1,
heif_chroma_422 = 2,
heif_chroma_444 = 3,
heif_chroma_interleaved_RGB = 10,
heif_chroma_interleaved_RGBA = 11,
heif_chroma_interleaved_RRGGBB_BE = 12,
heif_chroma_interleaved_RRGGBBAA_BE = 13,
heif_chroma_interleaved_RRGGBB_LE = 14,
heif_chroma_interleaved_RRGGBBAA_LE = 15,
heif_chroma_undefined = 99
} heif_chroma;
enum heif_channel {
heif_channel_Y = 0,
heif_channel_Cb = 1,
heif_channel_Cr = 2,
heif_channel_R = 3,
heif_channel_G = 4,
heif_channel_B = 5,
heif_channel_Alpha = 6,
heif_channel_interleaved = 10
};
struct heif_image_tiling {
int version;
int num_columns;
int num_rows;
int tile_width;
int tile_height;
int image_width;
int image_height;
int top_offset;
int left_offset;
};
heif_context* heif_context_alloc(void);
void heif_context_free(heif_context* ctx);
heif_error heif_context_read_from_memory(...);
int heif_context_get_number_of_items(const heif_context* ctx);
int heif_context_get_list_of_item_IDs(
const heif_context* ctx,
heif_item_id* ids,
int count
);
heif_error heif_context_get_primary_image_ID(
heif_context* ctx,
heif_item_id* id
);
heif_entity_group* heif_context_get_entity_groups(
const heif_context* ctx,
uint32_t type_filter,
heif_item_id item_filter,
int* out_num_groups
);
void heif_entity_groups_release(
heif_entity_group* groups,
int num_groups
);
uint32_t heif_item_get_item_type(
const heif_context* ctx,
heif_item_id item_id
);
heif_error heif_item_get_item_data(
const heif_context* ctx,
heif_item_id item_id,
enum heif_metadata_compression* out_compression_format,
uint8_t** out_data,
size_t* out_data_size
);
void heif_release_item_data(
const heif_context* ctx,
uint8_t** item_data
);
heif_item_id heif_image_handle_get_item_id(
const heif_image_handle* handle
);
int heif_image_handle_is_primary_image(
const heif_image_handle* handle
);
heif_error heif_image_handle_get_image_tiling(
heif_error* err,
const heif_image_handle* handle,
int fallback_to_single_tile,
struct heif_image_tiling* out_tiling
);
heif_error heif_image_handle_get_grid_image_tile_id(
const heif_image_handle* handle,
int process_image_transformations,
uint32_t tile_x,
uint32_t tile_y,
heif_item_id* out_tile_item_id
);
heif_error heif_image_handle_decode_image_tile(
heif_error* err,
const heif_image_handle* handle,
struct heif_image** out_image,
int colorspace,
int chroma,
int options,
int tile_x,
int tile_y
);
const uint8_t* heif_image_get_plane_readonly(
const heif_image* image,
enum heif_channel channel,
int* stride
);
int heif_image_get_width(
const heif_image* image,
enum heif_channel channel
);
int heif_image_get_height(
const heif_image* image,
enum heif_channel channel
);
void heif_image_release(
const heif_image* image
);
void heif_context_set_max_decoding_threads(
heif_context* ctx,
int threads
);
沒有留言:
張貼留言