Skip to content

Commit 74e333c

Browse files
committed
feat: save as bmp
1 parent 52707ed commit 74e333c

File tree

2 files changed

+68
-20
lines changed

2 files changed

+68
-20
lines changed

index.d.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
/**
2-
* Extracts the associated windows icon from a file and saves it as a PNG.
2+
* Extracts the associated windows icon from a file and saves it as a PNG or BMP.
3+
* saving as png is not recommended when called from worker or child process
34
* @param inputPath Absolute path to the file (e.g., `.exe`, `.lnk`)
45
* @param outputPath Absolute path to save the PNG
56
* @param size Size of the icon (e.g., 256)
67
*/
78
export function getIcon(inputPath: string, outputPath: string, size: number): void;
89

910
/**
10-
* Extracts a thumbnail provided by applications using IThumbnailProvider and saves it as a PNG.
11+
* Extracts a thumbnail provided by applications using IThumbnailProvider and saves it as a BMP.
12+
* saving as png is not recommended when called from worker or child process
1113
* @param inputPath Absolute path to the file (e.g., `.pdf`, `.docx`)
1214
* @param outputPath Absolute path to save the PNG
1315
* @param size Size of the thumbnail

src/winicon.cc

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <gdiplus.h>
99
#include <thumbcache.h>
1010
#include <memory>
11+
#include <fstream>
1112

1213
#pragma comment(lib, "Ole32.lib")
1314
#pragma comment(lib, "Shlwapi.lib")
@@ -98,9 +99,52 @@ void SaveBitmapAsPNG(HBITMAP hBitmap, const std::wstring& outputPath) {
9899
GdiplusShutdown(gdiplusToken);
99100
}
100101

102+
void SaveBitmapAsBMP(HBITMAP hBitmap, const std::wstring& outputPath) {
103+
BITMAP bmp;
104+
if (!GetObject(hBitmap, sizeof(BITMAP), &bmp)) return;
105+
106+
BITMAPFILEHEADER bmfHeader = {};
107+
BITMAPINFOHEADER bi = {};
108+
109+
bi.biSize = sizeof(BITMAPINFOHEADER);
110+
bi.biWidth = bmp.bmWidth;
111+
bi.biHeight = bmp.bmHeight;
112+
bi.biPlanes = 1;
113+
bi.biBitCount = 32;
114+
bi.biCompression = BI_RGB;
115+
116+
int bmpSize = ((bmp.bmWidth * bi.biBitCount + 31) / 32) * 4 * bmp.bmHeight;
117+
118+
std::unique_ptr<BYTE[]> pixels(new BYTE[bmpSize]);
119+
120+
HDC hdc = GetDC(nullptr);
121+
if (!GetDIBits(hdc, hBitmap, 0, bmp.bmHeight, pixels.get(), (BITMAPINFO*)&bi, DIB_RGB_COLORS)) {
122+
ReleaseDC(nullptr, hdc);
123+
return;
124+
}
125+
ReleaseDC(nullptr, hdc);
126+
127+
bmfHeader.bfType = 0x4D42;
128+
bmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
129+
bmfHeader.bfSize = bmfHeader.bfOffBits + bmpSize;
130+
131+
std::ofstream file(outputPath, std::ios::out | std::ios::binary);
132+
if (!file) return;
133+
file.write(reinterpret_cast<const char*>(&bmfHeader), sizeof(bmfHeader));
134+
file.write(reinterpret_cast<const char*>(&bi), sizeof(bi));
135+
file.write(reinterpret_cast<const char*>(pixels.get()), bmpSize);
136+
file.close();
137+
}
138+
139+
bool ShouldSaveAsPng(const std::wstring& outputPath) {
140+
std::wstring ext = outputPath.substr(outputPath.find_last_of(L".") + 1);
141+
std::transform(ext.begin(), ext.end(), ext.begin(), ::towlower);
142+
return (ext == L"png");
143+
}
144+
101145
void getImage(const Napi::CallbackInfo& info, bool useThumbnail) {
102146
Napi::Env env = info.Env();
103-
if (info.Length() != 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsNumber()) {
147+
if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsNumber()) {
104148
Napi::TypeError::New(env, "Expected (inputPath: string, outputPath: string, size: number)").ThrowAsJavaScriptException();
105149
return;
106150
}
@@ -109,33 +153,35 @@ void getImage(const Napi::CallbackInfo& info, bool useThumbnail) {
109153
std::wstring outputPath = std::wstring(info[1].As<Napi::String>().Utf8Value().begin(), info[1].As<Napi::String>().Utf8Value().end());
110154
int size = info[2].As<Napi::Number>().Int32Value();
111155

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;
117-
}
156+
bool saveAsPng = ShouldSaveAsPng(outputPath);
118157

119158
if (FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED))) {
120-
GdiplusShutdown(gdiplusToken);
121159
Napi::Error::New(env, "Failed to initialize COM").ThrowAsJavaScriptException();
122160
return;
123161
}
124162

125163
HBITMAP hBitmap = nullptr;
126-
HRESULT hr = useThumbnail
127-
? GetThumbnailImage(inputPath, size, hBitmap)
128-
: GetIconImage(inputPath, size, hBitmap);
129-
130-
if (SUCCEEDED(hr) && hBitmap) {
131-
SaveBitmapAsPNG(hBitmap, outputPath);
132-
DeleteObject(hBitmap);
133-
} else {
134-
Napi::Error::New(env, "Failed to retrieve image").ThrowAsJavaScriptException();
164+
try {
165+
HRESULT hr = useThumbnail
166+
? GetThumbnailImage(inputPath, size, hBitmap)
167+
: GetIconImage(inputPath, size, hBitmap);
168+
169+
if (SUCCEEDED(hr) && hBitmap) {
170+
if (saveAsPng) {
171+
SaveBitmapAsPNG(hBitmap, outputPath);
172+
} else {
173+
SaveBitmapAsBMP(hBitmap, outputPath);
174+
}
175+
DeleteObject(hBitmap);
176+
} else {
177+
Napi::Error::New(env, "Failed to retrieve image").ThrowAsJavaScriptException();
178+
}
179+
} catch (...) {
180+
if (hBitmap) DeleteObject(hBitmap);
181+
Napi::Error::New(env, "Native exception in image processing").ThrowAsJavaScriptException();
135182
}
136183

137184
CoUninitialize();
138-
GdiplusShutdown(gdiplusToken);
139185
}
140186

141187
Napi::Value getIcon(const Napi::CallbackInfo& info) {

0 commit comments

Comments
 (0)