77#include < shlwapi.h>
88#include < gdiplus.h>
99#include < thumbcache.h>
10- #include < memory>
11- #include < exception>
12- #include < fstream>
13- #include < algorithm>
14- #include < iostream>
1510
1611#pragma comment(lib, "Ole32.lib")
1712#pragma comment(lib, "Shlwapi.lib")
@@ -67,98 +62,92 @@ HRESULT GetThumbnailImage(const std::wstring& filePath, int size, HBITMAP& hBitm
6762 return hr;
6863}
6964
70- Napi::Value getImageBuffer (const Napi::CallbackInfo& info, bool useThumbnail) {
71- Napi::Env env = info.Env ();
72- if (info.Length () < 2 || !info[0 ].IsString () || !info[1 ].IsNumber ()) {
73- Napi::TypeError::New (env, " Expected (inputPath: string, size: number)" ).ThrowAsJavaScriptException ();
74- return env.Null ();
65+ void SaveBitmapAsPNG (HBITMAP hBitmap, const std::wstring& outputPath) {
66+ GdiplusStartupInput gdiplusStartupInput;
67+ ULONG_PTR gdiplusToken;
68+ if (GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, nullptr ) != Ok) return ;
69+
70+ BITMAP bm;
71+ if (GetObject (hBitmap, sizeof (BITMAP), &bm) && bm.bmBits ) {
72+ Bitmap bitmap (bm.bmWidth , bm.bmHeight , PixelFormat32bppARGB);
73+
74+ Gdiplus::BitmapData bmpData;
75+ Rect rect (0 , 0 , bm.bmWidth , bm.bmHeight );
76+
77+ if (bitmap.LockBits (&rect, ImageLockModeWrite, PixelFormat32bppARGB, &bmpData) == Ok) {
78+ int bytesPerPixel = 4 ;
79+ BYTE* srcData = (BYTE*)bm.bmBits ;
80+ BYTE* destData = (BYTE*)bmpData.Scan0 ;
81+
82+ for (int y = 0 ; y < bm.bmHeight ; y++) {
83+ BYTE* srcRow = srcData + (bm.bmHeight - 1 - y) * bm.bmWidth * bytesPerPixel;
84+ BYTE* destRow = destData + y * bmpData.Stride ;
85+ memcpy (destRow, srcRow, bm.bmWidth * bytesPerPixel);
86+ }
87+
88+ bitmap.UnlockBits (&bmpData);
89+ }
90+
91+ CLSID clsidPng;
92+ CLSIDFromString (L" {557CF406-1A04-11D3-9A73-0000F81EF32E}" , &clsidPng);
93+ bitmap.Save (outputPath.c_str (), &clsidPng, nullptr );
7594 }
7695
77- std::wstring inputPath = std::wstring (info[0 ].As <Napi::String>().Utf8Value ().begin (), info[0 ].As <Napi::String>().Utf8Value ().end ());
78- int size = info[1 ].As <Napi::Number>().Int32Value ();
96+ GdiplusShutdown (gdiplusToken);
97+ }
98+
99+ void getImage (const Napi::CallbackInfo& info, bool useThumbnail) {
100+ Napi::Env env = info.Env ();
101+ if (info.Length () != 3 || !info[0 ].IsString () || !info[1 ].IsString () || !info[2 ].IsNumber ()) {
102+ Napi::TypeError::New (env, " Expected (inputPath: string, outputPath: string, size: number)" ).ThrowAsJavaScriptException ();
103+ return ;
104+ }
79105
80- if (FAILED (CoInitializeEx (nullptr , COINIT_APARTMENTTHREADED))) {
81- Napi::Error::New (env, " Failed to initialize COM" ).ThrowAsJavaScriptException ();
82- return env.Null ();
106+ std::u16string inputUtf16 = info[0 ].As <Napi::String>().Utf16Value ();
107+ std::u16string outputUtf16 = info[1 ].As <Napi::String>().Utf16Value ();
108+ std::wstring inputPath (inputUtf16.begin (), inputUtf16.end ());
109+ std::wstring outputPath (outputUtf16.begin (), outputUtf16.end ());
110+ int size = info[2 ].As <Napi::Number>().Int32Value ();
111+
112+ GdiplusStartupInput gdiplusStartupInput;
113+ ULONG_PTR gdiplusToken;
114+ if (GdiplusStartup (&gdiplusToken, &gdiplusStartupInput, nullptr ) != Ok) {
115+ Napi::Error::New (env, " Failed to initialize GDI+" ).ThrowAsJavaScriptException ();
116+ return ;
83117 }
84118
119+ HRESULT coHr = CoInitializeEx (nullptr , COINIT_APARTMENTTHREADED);
120+ bool comInitialized = SUCCEEDED (coHr);
121+
85122 HBITMAP hBitmap = nullptr ;
86- Napi::Value result = env.Null ();
87- try {
88- HRESULT hr = useThumbnail
89- ? GetThumbnailImage (inputPath, size, hBitmap)
90- : GetIconImage (inputPath, size, hBitmap);
91-
92- if (SUCCEEDED (hr) && hBitmap) {
93- BITMAP bmp;
94- if (GetObject (hBitmap, sizeof (BITMAP), &bmp) && bmp.bmWidth > 0 && bmp.bmHeight > 0 ) {
95- std::wcerr << L" Bitmap: " << bmp.bmWidth << L" x" << bmp.bmHeight << std::endl;
96-
97- int width = bmp.bmWidth ;
98- int height = bmp.bmHeight ;
99- int bmpSize = width * 4 * height;
100-
101- BITMAPINFO bmi = {};
102- bmi.bmiHeader .biSize = sizeof (BITMAPINFOHEADER);
103- bmi.bmiHeader .biWidth = width;
104- bmi.bmiHeader .biHeight = -height; // top-down
105- bmi.bmiHeader .biPlanes = 1 ;
106- bmi.bmiHeader .biBitCount = 32 ;
107- bmi.bmiHeader .biCompression = BI_RGB;
108-
109- BYTE* bits = nullptr ;
110- HDC hdc = GetDC (nullptr );
111- HBITMAP hDib = CreateDIBSection (hdc, &bmi, DIB_RGB_COLORS, reinterpret_cast <void **>(&bits), nullptr , 0 );
112- HDC hMemDC = CreateCompatibleDC (hdc);
113- ReleaseDC (nullptr , hdc);
114-
115- if (hMemDC && hDib && bits) {
116- HGDIOBJ old = SelectObject (hMemDC, hDib);
117- HDC hSrcDC = CreateCompatibleDC (nullptr );
118- if (hSrcDC) {
119- SelectObject (hSrcDC, hBitmap);
120- BitBlt (hMemDC, 0 , 0 , width, height, hSrcDC, 0 , 0 , SRCCOPY);
121- DeleteDC (hSrcDC);
122-
123- result = Napi::Buffer<BYTE>::Copy (env, bits, bmpSize);
124- } else {
125- Napi::Error::New (env, " Failed to create source DC" ).ThrowAsJavaScriptException ();
126- }
127- SelectObject (hMemDC, old);
128- DeleteDC (hMemDC);
129- DeleteObject (hDib);
130- } else {
131- Napi::Error::New (env, " Failed to create DIB section" ).ThrowAsJavaScriptException ();
132- }
133- } else {
134- std::wcerr << L" Invalid bitmap dimensions." << std::endl;
135- Napi::Error::New (env, " Invalid bitmap" ).ThrowAsJavaScriptException ();
136- }
137- DeleteObject (hBitmap);
138- } else {
139- std::wcerr << L" Failed to get icon/thumbnail. HRESULT=" << hr << std::endl;
140- Napi::Error::New (env, " Failed to retrieve image" ).ThrowAsJavaScriptException ();
141- }
142- } catch (...) {
143- if (hBitmap) DeleteObject (hBitmap);
144- Napi::Error::New (env, " Native exception in image processing" ).ThrowAsJavaScriptException ();
123+ HRESULT hr = useThumbnail
124+ ? GetThumbnailImage (inputPath, size, hBitmap)
125+ : GetIconImage (inputPath, size, hBitmap);
126+
127+ if (SUCCEEDED (hr) && hBitmap) {
128+ SaveBitmapAsPNG (hBitmap, outputPath);
129+ DeleteObject (hBitmap);
130+ } else {
131+ Napi::Error::New (env, " Failed to retrieve image." ).ThrowAsJavaScriptException ();
145132 }
146133
147- CoUninitialize ();
148- return result ;
134+ if (comInitialized) CoUninitialize ();
135+ GdiplusShutdown (gdiplusToken) ;
149136}
150137
151- Napi::Value getIconBuffer (const Napi::CallbackInfo& info) {
152- return getImageBuffer (info, false );
138+ Napi::Value getIcon (const Napi::CallbackInfo& info) {
139+ getImage (info, false );
140+ return info.Env ().Undefined ();
153141}
154142
155- Napi::Value getThumbnailBuffer (const Napi::CallbackInfo& info) {
156- return getImageBuffer (info, true );
143+ Napi::Value getThumbnail (const Napi::CallbackInfo& info) {
144+ getImage (info, true );
145+ return info.Env ().Undefined ();
157146}
158147
159148Napi::Object Init (Napi::Env env, Napi::Object exports) {
160- exports.Set (" getIconBuffer " , Napi::Function::New (env, getIconBuffer ));
161- exports.Set (" getThumbnailBuffer " , Napi::Function::New (env, getThumbnailBuffer ));
149+ exports.Set (" getIcon " , Napi::Function::New (env, getIcon ));
150+ exports.Set (" getThumbnail " , Napi::Function::New (env, getThumbnail ));
162151 return exports;
163152}
164153
@@ -172,8 +161,8 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
172161 Napi::TypeError::New (env, " winicon only works on Windows." ).ThrowAsJavaScriptException ();
173162 });
174163
175- exports.Set (" getIconBuffer " , stub);
176- exports.Set (" getThumbnailBuffer " , stub);
164+ exports.Set (" getIcon " , stub);
165+ exports.Set (" getThumbnail " , stub);
177166 return exports;
178167}
179168
0 commit comments