44#include " AssetLoader.h"
55#include " EventTarget.h"
66#include " ImageSource.h"
7+ #include " platform/DecodeQueue.h"
78#include " platform/WicDecoder.h"
89#include " v8/V8Util.h"
910#include " v8/WeakHandle.h"
1213
1314#include < cstring>
1415#include < iostream>
16+ #include < memory>
17+ #include < utility>
1518#include < vector>
1619
1720namespace next2d {
@@ -49,34 +52,11 @@ bool ImageDecodeBase64(const std::string& in, std::vector<uint8_t>* out)
4952 return true ;
5053}
5154
52- // 遅延発火する load/error イベント (img.src = url; img.onload = fn の順でも拾えるよう
53- // microtask で発火する。メインループの PerformMicrotaskCheckpoint が実行する)
54- struct PendingImageEvent {
55- v8::Isolate* isolate;
56- v8::Global<v8::Object> image;
57- bool ok;
58- };
59-
60- void FireImageEvent (void * data)
61- {
62- auto * p = static_cast <PendingImageEvent*>(data);
63- v8::Isolate* isolate = p->isolate ;
64- v8::HandleScope hs (isolate);
65- v8::Local<v8::Object> img = p->image .Get (isolate);
66- v8::Local<v8::Context> ctx = img->GetCreationContextChecked ();
67- v8::Context::Scope cs (ctx);
68-
69- v8::Local<v8::Object> ev = v8::Object::New (isolate);
70- v8util::SetValue (isolate, ev, " type" , Str (isolate, p->ok ? " load" : " error" ));
71- v8util::SetValue (isolate, ev, " target" , img);
72- DispatchEvent (isolate, img, ev);
73-
74- p->image .Reset ();
75- delete p;
76- }
77-
78- // src に指定された URL (assets 相対 / data:) を読み込み WIC でデコードする。
79- // 成否イベントは microtask で発火する。
55+ // src に指定された URL (assets 相対 / data:) を読み込み、WIC デコードを
56+ // バックグラウンドスレッド (DecodeQueue) へ逃がす。完了はメインループの
57+ // decodequeue::Pump() から反映される (ブラウザ同様、src 設定後に非同期で
58+ // load イベントが届く。img.src = url; img.onload = fn の順でも拾える)。
59+ // 同期デコードは画面遷移時に 1 秒超のフレームヒッチになる (--perf で計測済み)。
8060void LoadImageFromSrc (v8::Isolate* isolate, v8::Local<v8::Object> self, const std::string& url)
8161{
8262 std::vector<uint8_t > input;
@@ -90,45 +70,63 @@ void LoadImageFromSrc(v8::Isolate* isolate, v8::Local<v8::Object> self, const st
9070 }
9171 }
9272 } else {
73+ // pak (メモリ) 読みなので同期で問題ない。重いのはデコードのみ。
9374 AssetLoader* assets = HostContext::From (isolate)->assets ;
9475 auto bytes = assets->ReadBinary (url);
9576 if (bytes) {
9677 input = std::move (*bytes);
9778 }
9879 }
9980
100- auto * img = new DecodedImage ();
101- const bool ok = !input.empty () && DecodeImageWithWIC (input, *img);
102-
103- // «堅牢化» デコード失敗時の扱い。
104- // player の ShapeLoadSrcUseCase は image に "load" ハンドラのみ登録し "error" を
105- // 監視しない。そのため "error" を発火すると Event.COMPLETE が来ず、ImageShapeAtom.load()
106- // の Promise が永久に未解決になり、ページ build (=画面遷移) がローディング画面のまま
107- // フリーズする。デコードできない 1 枚が全体をブリックするのを防ぐため、失敗時は
108- // 1x1 透明のプレースホルダを割り当てて "load" として扱う (該当画像だけが空になる)。
109- if (!ok) {
110- img->width = 1 ;
111- img->height = 1 ;
112- img->rgba .assign (4 , 0 );
113- }
81+ auto image_ref = std::make_shared<v8::Global<v8::Object>>(isolate, self);
82+ auto decoded = std::make_shared<DecodedImage>();
83+ auto ok = std::make_shared<bool >(false );
84+
85+ decodequeue::Submit (
86+ // プールスレッド: WIC デコードのみ (V8 に触れない)
87+ [input = std::move (input), decoded, ok]() {
88+ *ok = !input.empty () && DecodeImageWithWIC (input, *decoded);
89+ },
90+ // メインスレッド: 結果を image オブジェクトへ反映し load を発火
91+ [isolate, image_ref, decoded, ok]() {
92+ v8::HandleScope hs (isolate);
93+ v8::Local<v8::Object> img_obj = image_ref->Get (isolate);
94+ v8::Local<v8::Context> ctx = img_obj->GetCreationContextChecked ();
95+ v8::Context::Scope cs (ctx);
96+
97+ // «堅牢化» デコード失敗時の扱い。
98+ // player の ShapeLoadSrcUseCase は image に "load" ハンドラのみ登録し
99+ // "error" を監視しない。"error" だと Event.COMPLETE が来ず ImageShapeAtom
100+ // .load() の Promise が永久未解決になり、ページ build (=画面遷移) が
101+ // ローディングのままフリーズする。失敗時は 1x1 透明のプレースホルダを
102+ // 割り当てて "load" として扱う (該当画像だけが空になる)。
103+ auto * img = new DecodedImage (std::move (*decoded));
104+ if (!*ok) {
105+ img->width = 1 ;
106+ img->height = 1 ;
107+ img->rgba .assign (4 , 0 );
108+ }
114109
115- self->SetInternalField (0 , v8::External::New (isolate, img));
116- v8util::AttachWeak (isolate, self, img);
117- v8util::SetValue (isolate, self, " width" ,
118- v8::Integer::NewFromUnsigned (isolate, img->width ));
119- v8util::SetValue (isolate, self, " height" ,
120- v8::Integer::NewFromUnsigned (isolate, img->height ));
121- v8util::SetValue (isolate, self, " naturalWidth" ,
122- v8::Integer::NewFromUnsigned (isolate, img->width ));
123- v8util::SetValue (isolate, self, " naturalHeight" ,
124- v8::Integer::NewFromUnsigned (isolate, img->height ));
125- v8util::SetValue (isolate, self, " complete" , v8::Boolean::New (isolate, true ));
126-
127- auto * pending = new PendingImageEvent ();
128- pending->isolate = isolate;
129- pending->image .Reset (isolate, self);
130- pending->ok = true ; // 常に "load" を発火し、遷移を進行させる
131- isolate->EnqueueMicrotask (FireImageEvent, pending);
110+ img_obj->SetInternalField (0 , v8::External::New (isolate, img));
111+ v8util::AttachWeak (isolate, img_obj, img);
112+ v8util::SetValue (isolate, img_obj, " width" ,
113+ v8::Integer::NewFromUnsigned (isolate, img->width ));
114+ v8util::SetValue (isolate, img_obj, " height" ,
115+ v8::Integer::NewFromUnsigned (isolate, img->height ));
116+ v8util::SetValue (isolate, img_obj, " naturalWidth" ,
117+ v8::Integer::NewFromUnsigned (isolate, img->width ));
118+ v8util::SetValue (isolate, img_obj, " naturalHeight" ,
119+ v8::Integer::NewFromUnsigned (isolate, img->height ));
120+ v8util::SetValue (isolate, img_obj, " complete" , v8::Boolean::New (isolate, true ));
121+
122+ // 常に "load" を発火し、遷移を進行させる
123+ v8::Local<v8::Object> ev = v8::Object::New (isolate);
124+ v8util::SetValue (isolate, ev, " type" , Str (isolate, " load" ));
125+ v8util::SetValue (isolate, ev, " target" , img_obj);
126+ DispatchEvent (isolate, img_obj, ev);
127+
128+ image_ref->Reset ();
129+ });
132130}
133131
134132// Image コンストラクタ: src セッターで読み込み+デコードし load イベントを発火する。
@@ -262,14 +260,35 @@ void CreateImageBitmap(const v8::FunctionCallbackInfo<v8::Value>& args)
262260 }
263261 }
264262
265- auto * img = new DecodedImage ();
266- if (input.empty () || !DecodeImageWithWIC (input, *img)) {
267- delete img;
263+ if (input.empty ()) {
268264 resolver->Reject (ctx, v8::Exception::Error (Str (isolate, " Image decode failed" ))).Check ();
269265 return ;
270266 }
271267
272- resolver->Resolve (ctx, WrapImageBitmap (isolate, img)).Check ();
268+ // WIC デコードはバックグラウンドスレッドへ (Promise なので元々非同期契約)
269+ auto resolver_ref =
270+ std::make_shared<v8::Global<v8::Promise::Resolver>>(isolate, resolver);
271+ auto decoded = std::make_shared<DecodedImage>();
272+ auto ok = std::make_shared<bool >(false );
273+
274+ decodequeue::Submit (
275+ [input = std::move (input), decoded, ok]() {
276+ *ok = DecodeImageWithWIC (input, *decoded);
277+ },
278+ [isolate, resolver_ref, decoded, ok]() {
279+ v8::HandleScope hs (isolate);
280+ v8::Local<v8::Promise::Resolver> r = resolver_ref->Get (isolate);
281+ v8::Local<v8::Context> c = r->GetCreationContextChecked ();
282+ v8::Context::Scope cs (c);
283+ if (*ok) {
284+ auto * img = new DecodedImage (std::move (*decoded));
285+ r->Resolve (c, WrapImageBitmap (isolate, img)).Check ();
286+ } else {
287+ r->Reject (c, v8::Exception::Error (
288+ Str (isolate, " Image decode failed" ))).Check ();
289+ }
290+ resolver_ref->Reset ();
291+ });
273292}
274293
275294} // namespace
0 commit comments