2026/08/04

[note] Libheif-js WASM使用的一些心得筆記

前言

雖然有點懶得寫筆記,但這次工程真的太浩大了,我覺得我要記錄一下。

解析的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
);

沒有留言: