1// tImageICO.cpp 
2// 
3// This class knows how to load windows icon (ico) files. It loads the data into multiple tPixel arrays, one for each 
4// part (ico files may be multiple images at different resolutions). These arrays may be 'stolen' by tPictures. The 
5// loading code is a modificaton of code from Victor Laskin. In particular the code now: 
6// a) Loads all parts of an ico, not just the biggest one. 
7// b) Supports embedded png images. 
8// c) Supports widths and heights of 256. 
9// 
10// Copyright (c) 2020 Tristan Grimmer. 
11// Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby 
12// granted, provided that the above copyright notice and this permission notice appear in all copies. 
13// 
14// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL 
15// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
16// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 
17// AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
18// PERFORMANCE OF THIS SOFTWARE. 
19// 
20// Includes modified version of code from Victor Laskin. Here is Victor Laskin's header/licence in the original ico.cpp: 
21 
22// Code by Victor Laskin (victor.laskin@gmail.com) 
23// Rev 2 - 1bit color was added, fixes for bit mask. 
24// 
25// THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 
26// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
27// THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
28// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
29// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
30// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
31// POSSIBILITY OF SUCH DAMAGE. 
32 
33#include <Foundation/tStandard.h> 
34#include <Foundation/tString.h> 
35#include <System/tFile.h> 
36#include "Image/tImageICO.h" 
37#include "Image/tImagePNG.h" 
38using namespace tSystem; 
39namespace tImage 
40{ 
41 
42  
43// These structs represent how the icon information is stored in an ICO file. 
44struct IconDirEntry 
45{ 
46 uint8 Width; // Width of the image. 
47 uint8 Height; // Height of the image (times 2). 
48 uint8 ColorCount; // Number of colors in image (0 if >=8bpp). 
49 uint8 Reserved; 
50 uint16 Planes; // Colour planes. 
51 uint16 BitCount; // Bits per pixel. 
52 uint32 BytesInRes; // How many bytes in this resource? 
53 uint32 ImageOffset; // Where in the file is this image. 
54}; 
55 
56 
57struct IconDir 
58{ 
59 uint16 Reserved; 
60 uint16 Type; // Resource type (1 for icons). 
61 uint16 Count; // How many images? 
62 //IconDirEntrys follow. One for each image. 
63}; 
64 
65 
66// size - 40 bytes 
67struct BitmapInfoHeader 
68{ 
69 uint32 Size; 
70 uint32 Width; 
71 uint32 Height; // Icon Height (added height of XOR-Bitmap and AND-Bitmap). 
72 uint16 Planes; 
73 uint16 BitCount; 
74 uint32 Compression; 
75 int32 SizeImage; 
76 uint32 XPelsPerMeter; 
77 uint32 YPelsPerMeter; 
78 uint32 ClrUsed; 
79 uint32 ClrImportant; 
80}; 
81 
82 
83struct IconImage 
84{ 
85 BitmapInfoHeader Header; // DIB header. 
86 uint32 Colours[1]; // Color table (short 4 bytes) //RGBQUAD. 
87 uint8 XOR[1]; // DIB bits for XOR mask. 
88 uint8 AND[1]; // DIB bits for AND mask. 
89}; 
90  
91 
92bool tImageICO::Load(const tString& icoFile) 
93{ 
94 Clear(); 
95 
96 if (tGetFileType(icoFile) != tFileType::ICO) 
97 return false; 
98 
99 if (!tFileExists(icoFile)) 
100 return false; 
101 
102 int numBytes = 0; 
103 uint8* icoFileInMemory = tLoadFile(icoFile, nullptr, &numBytes); 
104 bool success = PopulateFrames(icoFileInMemory, numBytes);  
105 delete[] icoFileInMemory; 
106 
107 return success; 
108} 
109 
110 
111bool tImageICO::PopulateFrames(const uint8* buffer, int numBytes) 
112{ 
113 IconDir* icoDir = (IconDir*)buffer; 
114 int iconsCount = icoDir->Count; 
115 
116 if (icoDir->Reserved != 0) 
117 return false; 
118  
119 if (icoDir->Type != 1) 
120 return false; 
121 
122 if (iconsCount == 0) 
123 return false; 
124  
125 if (iconsCount > 20) 
126 return false; 
127 
128 const uint8* cursor = buffer; 
129 cursor += 6; 
130 IconDirEntry* dirEntry = (IconDirEntry*)cursor; 
131 
132 for (int i = 0; i < iconsCount; i++) 
133 { 
134 int w = dirEntry->Width; 
135 if (w == 0) 
136 w = 256; 
137  
138 int h = dirEntry->Height; 
139 if (h == 0) 
140 h = 256; 
141  
142 int offset = dirEntry->ImageOffset; 
143 if (!offset || (offset >= numBytes)) 
144 continue; 
145  
146 Frame* newFrame = CreateFrame(buffer+offset, w, h, numBytes); 
147 if (!newFrame) 
148 continue; 
149 
150 Frames.Append(newFrame); 
151 dirEntry++; 
152 } 
153 
154 return !Frames.IsEmpty(); 
155} 
156 
157 
158tImageICO::Frame* tImageICO::CreateFrame(const uint8* cursor, int width, int height, int numBytes) 
159{ 
160 IconImage* icon = (IconImage*)cursor; 
161  
162 // ICO files may have embedded pngs. 
163 if (icon->Header.Size == 0x474e5089) 
164 { 
165 tImagePNG pngImage(cursor, numBytes); 
166 if (!pngImage.IsValid()) 
167 return nullptr; 
168 
169 width = pngImage.GetWidth(); 
170 height = pngImage.GetHeight(); 
171 tAssert((width > 0) && (height > 0)); 
172 
173 tPixel* pixels = pngImage.StealPixels(); 
174 bool isOpaque = pngImage.IsOpaque(); 
175  
176 Frame* newFrame = new Frame; 
177 newFrame->SrcPixelFormat = isOpaque ? tPixelFormat::R8G8B8 : tPixelFormat::R8G8B8A8; 
178 newFrame->Width = width; 
179 newFrame->Height = height; 
180 newFrame->Pixels = pixels; 
181 return newFrame; 
182 } 
183  
184 int realBitsCount = int(icon->Header.BitCount); 
185 bool hasAndMask = (realBitsCount < 32) && (height != icon->Header.Height); 
186 
187 cursor += 40; 
188 int numPixels = width * height; 
189  
190 tPixel* pixels = new tPixel[numPixels]; 
191 uint8* image = (uint8*)pixels; 
192 tPixelFormat srcPixelFormat = tPixelFormat::Invalid; 
193  
194 // rgba + vertical swap 
195 if (realBitsCount == 32) 
196 { 
197 srcPixelFormat = tPixelFormat::R8G8B8A8; 
198 for (int x = 0; x < width; x++) 
199 { 
200 for (int y = 0; y < height; y++) 
201 { 
202 int shift = 4 * (x + y * width); 
203  
204 // Rows from bottom to top. 
205 // int shift2 = 4 * (x + (height - y - 1) * width); 
206 int shift2 = 4 * (x + y * width); 
207  
208 image[shift] = cursor[shift2 +2]; 
209 image[shift+1] = cursor[shift2 +1]; 
210 image[shift+2] = cursor[shift2 ]; 
211 image[shift+3] = cursor[shift2 +3]; 
212 } 
213 } 
214 } 
215 
216 if (realBitsCount == 24) 
217 { 
218 srcPixelFormat = tPixelFormat::R8G8B8; 
219 for (int x = 0; x < width; x++) 
220 { 
221 for (int y = 0; y < height; y++) 
222 { 
223 int shift = 4 * (x + y * width); 
224  
225 // Rows from bottom to top. 
226 // int shift2 = 3 * (x + (height - y - 1) * width); 
227 int shift2 = 3 * (x + y * width); 
228  
229 image[shift] = cursor[shift2 +2]; 
230 image[shift+1] = cursor[shift2 +1]; 
231 image[shift+2] = cursor[shift2 ]; 
232 image[shift+3] = 255; 
233 } 
234 } 
235 } 
236 
237 if (realBitsCount == 8) 
238 { 
239 // 256 colour palette. 
240 srcPixelFormat = tPixelFormat::PAL_8BIT; 
241  
242 uint8* colors = (uint8*)cursor; 
243 cursor += 256 * 4; 
244 for (int x = 0; x < width; x++) 
245 { 
246 for (int y = 0; y < height; y++) 
247 { 
248 int shift = 4 * (x + y * width); 
249  
250 // Rows from bottom to top. 
251 // int shift2 = (x + (height - y - 1) * width); 
252 int shift2 = (x + y * width); 
253  
254 int index = 4 * cursor[shift2]; 
255 image[shift] = colors[index + 2]; 
256 image[shift+1] = colors[index + 1]; 
257 image[shift+2] = colors[index ]; 
258 image[shift+3] = 255; 
259 } 
260 } 
261 } 
262 
263 if (realBitsCount == 4) 
264 { 
265 // 16 colour palette. 
266 srcPixelFormat = tPixelFormat::PAL_4BIT; 
267  
268 uint8* colors = (uint8*)cursor; 
269 cursor += 16 * 4; 
270 for (int x = 0; x < width; x++) 
271 { 
272 for (int y = 0; y < height; y++) 
273 { 
274 int shift = 4 * (x + y * width); 
275 
276 // Rows from bottom to top. 
277 // int shift2 = (x + (height - y - 1) * width); 
278 int shift2 = (x + y * width); 
279 
280 uint8 index = cursor[shift2 / 2]; 
281 if (shift2 % 2 == 0) 
282 index = (index >> 4) & 0xF; 
283 else 
284 index = index & 0xF; 
285 index *= 4; 
286 
287 image[shift] = colors[index + 2]; 
288 image[shift+1] = colors[index + 1]; 
289 image[shift+2] = colors[index ]; 
290 image[shift+3] = 255; 
291 } 
292 } 
293 } 
294 
295 if (realBitsCount == 1) 
296 { 
297 // 2 colour palette. 
298 srcPixelFormat = tPixelFormat::PAL_1BIT; 
299 
300 uint8* colors = (uint8*)cursor; 
301 cursor += 2 * 4; 
302 
303 int boundary = width; //!!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico) 
304 while (boundary % 32 != 0) 
305 boundary++; 
306 
307 for (int x = 0; x < width; x++) 
308 { 
309 for (int y = 0; y < height; y++) 
310 { 
311 int shift = 4 * (x + y * width); 
312 
313 // Rows from bottom to top. 
314 // int shift2 = (x + (height - y - 1) * boundary); 
315 int shift2 = (x + y * boundary); 
316  
317 uint8 index = cursor[shift2 / 8]; 
318 
319 // select 1 bit only 
320 uint8 bit = 7 - (x % 8); 
321 index = (index >> bit) & 0x01; 
322 index *= 4; 
323  
324 image[shift] = colors[index + 2]; 
325 image[shift+1] = colors[index + 1]; 
326 image[shift+2] = colors[index ]; 
327 image[shift+3] = 255; 
328 } 
329 } 
330 } 
331 
332 // Read AND mask after base color data - 1 BIT MASK 
333 if (hasAndMask) 
334 { 
335 int boundary = width * realBitsCount; //!!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico) 
336 while (boundary % 32 != 0) 
337 boundary++; 
338 cursor += boundary * height / 8; 
339 
340 boundary = width; 
341 while (boundary % 32 != 0) 
342 boundary++; 
343 
344 for (int y = 0; y < height; y++) 
345 { 
346 for (int x = 0; x < width; x++) 
347 { 
348 int shift = 4 * (x + y * width) + 3; 
349 uint8 bit = 7 - (x % 8); 
350 
351 // Rows from bottom to top. 
352 // int shift2 = (x + (height - y - 1) * boundary) / 8; 
353 int shift2 = (x + y * boundary) / 8; 
354 
355 int mask = (0x01 & ((unsigned char)cursor[shift2] >> bit)); 
356 image[shift] *= 1 - mask; 
357 } 
358 } 
359 } 
360  
361 Frame* newFrame = new Frame; 
362 newFrame->SrcPixelFormat = srcPixelFormat; 
363 newFrame->Width = width; 
364 newFrame->Height = height; 
365 newFrame->Pixels = pixels; 
366 
367 return newFrame; 
368} 
369 
370 
371} 
372