-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.coffee
More file actions
180 lines (145 loc) · 6.18 KB
/
Copy pathindex.coffee
File metadata and controls
180 lines (145 loc) · 6.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
path = require 'path'
ffi = require 'ffi-napi'
ref = require 'ref'
struct = require 'ref-struct'
fs = require 'fs'
jimp = require 'jimp'
bmp_js = require 'bmp-js'
IntPtr = ref.refType ref.types.int
HANDLE = ref.refType ref.types.void
lpctstr =
indirection: 1
name: 'lpctstr'
size: ref.sizeof.pointer
ffi_type: ffi.types.CString.ffi_type
get: (buffer, offset) ->
_buf = buffer.readPointer offset
return null if _buf.isNull()
_buf.readCString 0
set: (buffer, offset, value) ->
_buf = Buffer.alloc Buffer.byteLength(value, 'ucs2')+2
_buf.write value, 'ucs2'
_buf[_buf.length-2] = 0
_buf[_buf.length-1] = 0
buffer.writePointer _buf, offset
iconInfo = struct
fIcon: ref.types.bool
xHotspot: ref.types.ulong
yHotspot: ref.types.ulong
hbmMask: HANDLE
hbmColor: HANDLE
bitmapInfoHeader = struct
biSize: ref.types.ulong
biWidth: ref.types.long
biHeight: ref.types.long
biPlanes: ref.types.ushort
biBitCount: ref.types.ushort
biCompression: ref.types.ulong
biSizeImage: ref.types.ulong
biXPelsPerMeter: ref.types.long
biYPelsPerMeter: ref.types.long
biClrUsed: ref.types.ulong
biClrImportant: ref.types.ulong
palleteColor = struct
red: ref.types.uint8
greed: ref.types.uint8
blue: ref.types.uint8
void: ref.types.uint8
bitmapInfo = struct bmiHeader:bitmapInfoHeader
# Allocate color table for 16 colors
# The table size is dynamic, but needs to be preallocated
# After we load the actual table size, we slice unused part off
for i in [0...16]
bitmapInfo.defineProperty 'color' + i, palleteColor
shell32 = ffi.Library 'shell32', ExtractAssociatedIconW: ["void *", [IntPtr, lpctstr, IntPtr]]
gdi32 = ffi.Library 'gdi32', GetDIBits: [ref.types.int32, [IntPtr, IntPtr, 'uint32', 'uint32', IntPtr, ref.refType(bitmapInfo), 'uint32'] ]
user32 = ffi.Library 'user32',
GetIconInfo: ['bool', [IntPtr, ref.refType(iconInfo)]]
GetDC: [HANDLE, [IntPtr]]
DestroyIcon: ['bool', [HANDLE]]
loadBitmap = (hbitmap, ident) ->
bitmap = new bitmapInfo()
# Clear bitmap info
bitmap['ref.buffer'].fill 0
# Save the bmiheader size
bitmap.bmiHeader.biSize = 40
# Load bitmap details
dc = user32.GetDC null
if dc.deref() == 0
throw new Error "Failed to get screen DC."
if gdi32.GetDIBits(dc, hbitmap, 0, 0, null, bitmap.ref(), 0) == 0
throw new Error "Failed to load BITMAP (" + ident + ") info."
# Slice off the unused color table
colors = bitmap.bmiHeader.biBitCount < 24 and ((1 << bitmap.bmiHeader.biBitCount) * 4) or 0
bitmap['ref.buffer'] = bitmap['ref.buffer'].slice 0, bitmap.bmiHeader.biSize + colors
# Disable compression
bitmap.bmiHeader.biCompression = 0
# Load bitmap data
data = Buffer.alloc bitmap.bmiHeader.biSizeImage
if gdi32.GetDIBits(dc, hbitmap, 0, bitmap.bmiHeader.biHeight, data, bitmap.ref(), 0) == 0
throw new Error "Failed to load BITMAP data."
# Prepare BMP header
header = Buffer.alloc 2 + 4 + 4 + 4
# BMP signature (BM)
header.writeUInt8 66, 0
header.writeUInt8 77, 1
# Size fo the BMP file, HEADER + COLOR_TABLE + DATA
header.writeUInt32LE data.byteLength + 54 + colors, 2
# Reserved
header.writeUInt32LE 0, 6
# Offset of actual image data HEADER + COLOR_TABLE
header.writeUInt32LE 54 + colors, 10
# Return resulting BMP file
data: Buffer.concat [header, bitmap.ref(), data]
depth: bitmap.bmiHeader.biBitCount
module.exports = (target) ->
new Promise (resolve, reject) =>
# Make sure the path is absolute
target = path.resolve target
# Load icon data
iconIndex = ref.alloc ref.types.int32, 0
info = new iconInfo()
# Clear info struct
info['ref.buffer'].fill 0
result = shell32.ExtractAssociatedIconW null, target, iconIndex
if not user32.GetIconInfo result, info.ref()
throw new Error "Failed to load icon info."
# Load icon bitmaps
colored = loadBitmap info.hbmColor, 'colored'
mask = loadBitmap info.hbmMask, 'mask'
# Remove icon from memory
user32.DestroyIcon result
# Load bitmaps into standardized formats
colored_bmp = bmp_js.decode colored.data
mask_bmp = bmp_js.decode mask.data
# Load the colored bmp
# Little hack has to be applied, jimp currently doesn't support 32 bit BMP
# Encoder uses 24 bit, so it loads fine
jimp.read bmp_js.encode(colored_bmp).data, (err, colored_img) =>
if (err) then return reject err
# Bitmap can have 32 bits per color, but ignore the aplha channel
has_alpha = false
# 32 bit BMP can have alpha encoded, so we may not need the mask
if colored.depth > 24
# Scan the original BMP image, if any pixel has > 0 alpha, the mask wont be needed
for xx in [0...colored_bmp.width]
for yy in [0...colored_bmp.height]
index = colored_img.getPixelIndex xx, yy
if colored_bmp.data[index + 3] != 0
has_alpha = true
break
# Ignore mask, if the colored icon has alpha encoded already (most does)
if has_alpha
# Little hack again, assign actual RGBA data to image
colored_img.bitmap = colored_bmp
colored_img.getBase64 jimp.MIME_PNG, (error, base64) =>
if (err) then return reject err
resolve base64
else
# Load mask and apply it
jimp.read bmp_js.encode(mask_bmp).data, (err, mask_img) =>
if (err) then return reject err
masked_img = colored_img.mask(mask_img.invert(), 0, 0)
masked_img.getBase64 jimp.MIME_PNG, (error, base64) =>
if (err) then return reject err
resolve base64