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-2024 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" // ICO files may have embedded PNGs. 
38#include "Image/tPicture.h" 
39using namespace tSystem
40namespace tImage 
41
42 
43  
44// These structs represent how the icon information is stored in an ICO file. 
45struct IconDirEntry 
46
47 uint8 Width; // Width of the image. 
48 uint8 Height; // Height of the image (times 2). 
49 uint8 ColorCount; // Number of colors in image (0 if >=8bpp). 
50 uint8 Reserved
51 uint16 Planes; // Colour planes. 
52 uint16 BitCount; // Bits per pixel. 
53 uint32 BytesInRes; // How many bytes in this resource? 
54 uint32 ImageOffset; // Where in the file is this image. 
55}; 
56 
57 
58struct IconDir 
59
60 uint16 Reserved
61 uint16 Type; // Resource type (1 for icons). 
62 uint16 Count; // How many images? 
63 //IconDirEntrys follow. One for each image. 
64}; 
65 
66 
67// size - 40 bytes 
68struct BitmapInfoHeader 
69
70 uint32 Size
71 uint32 Width
72 uint32 Height; // Icon Height (added height of XOR-Bitmap and AND-Bitmap). 
73 uint16 Planes
74 uint16 BitCount
75 uint32 Compression
76 int32 SizeImage
77 uint32 XPelsPerMeter
78 uint32 YPelsPerMeter
79 uint32 ClrUsed
80 uint32 ClrImportant
81}; 
82 
83 
84struct IconImage 
85
86 BitmapInfoHeader Header; // DIB header. 
87 uint32 Colours[1]; // Color table (short 4 bytes) //RGBQUAD. 
88 uint8 XOR[1]; // DIB bits for XOR mask. 
89 uint8 AND[1]; // DIB bits for AND mask. 
90}; 
91  
92 
93bool tImageICO::Load(const tString& icoFile
94
95 Clear(); 
96 
97 if (tGetFileType(file: icoFile) != tFileType::ICO
98 return false
99 
100 if (!tFileExists(file: icoFile)) 
101 return false
102 
103 int numBytes = 0
104 uint8* icoFileInMemory = tLoadFile(file: icoFile, buffer: nullptr, fileSize: &numBytes); 
105 bool success = Load(icoFileInMemory, numBytes);  
106 delete[] icoFileInMemory
107 
108 return success
109
110 
111 
112bool tImageICO::Load(const uint8* icoFileInMemory, int numBytes
113
114 Clear(); 
115 IconDir* icoDir = (IconDir*)icoFileInMemory
116 int iconsCount = icoDir->Count
117 
118 if (icoDir->Reserved != 0
119 return false
120  
121 if (icoDir->Type != 1
122 return false
123 
124 if (iconsCount == 0
125 return false
126  
127 if (iconsCount > 20
128 return false
129 
130 const uint8* cursor = icoFileInMemory
131 cursor += 6
132 IconDirEntry* dirEntry = (IconDirEntry*)cursor
133 
134 for (int i = 0; i < iconsCount; i++) 
135
136 int w = dirEntry->Width
137 if (w == 0
138 w = 256
139  
140 int h = dirEntry->Height
141 if (h == 0
142 h = 256
143  
144 int offset = dirEntry->ImageOffset
145 if (!offset || (offset >= numBytes)) 
146 continue
147  
148 tFrame* newFrame = CreateFrame(buffer: icoFileInMemory+offset, width: w, height: h, numBytes); 
149 if (!newFrame
150 continue
151 
152 Frames.Append(item: newFrame); 
153 dirEntry++; 
154
155 if (Frames.IsEmpty()) 
156 return false
157 
158 PixelFormatSrc = GetBestSrcPixelFormat(); 
159 PixelFormat = tPixelFormat::R8G8B8A8
160 
161 // From a file image we always assume it was sRGB. 
162 ColourProfileSrc = tColourProfile::sRGB
163 ColourProfile = tColourProfile::sRGB
164 return true
165
166 
167 
168bool tImage::tImageICO::Set(tList<tFrame>& srcFrames, bool stealFrames
169
170 Clear(); 
171 if (srcFrames.GetNumItems() <= 0
172 return false
173 
174 if (stealFrames
175
176 while (tFrame* frame = srcFrames.Remove()) 
177 Frames.Append(item: frame); 
178
179 else 
180
181 for (tFrame* frame = srcFrames.Head(); frame; frame = frame->Next()) 
182 Frames.Append(item: new tFrame(*frame)); 
183
184 
185 PixelFormatSrc = GetBestSrcPixelFormat(); 
186 PixelFormat = tPixelFormat::R8G8B8A8
187 ColourProfileSrc = tColourProfile::sRGB; // We assume srcFrames must be sRGB. 
188 ColourProfile = tColourProfile::sRGB
189 
190 return true
191
192 
193 
194bool tImageICO::Set(tPixel4b* pixels, int width, int height, bool steal
195
196 Clear(); 
197 if (!pixels || (width <= 0) || (height <= 0)) 
198 return false
199 
200 tFrame* frame = new tFrame(); 
201 if (steal
202 frame->StealFrom(src: pixels, width, height); 
203 else 
204 frame->Set(srcPixels: pixels, width, height); 
205 Frames.Append(item: frame); 
206 
207 PixelFormatSrc = tPixelFormat::R8G8B8A8
208 PixelFormat = tPixelFormat::R8G8B8A8
209 ColourProfileSrc = tColourProfile::sRGB; // We assume pixels must be sRGB. 
210 ColourProfile = tColourProfile::sRGB
211 
212 return true
213
214 
215 
216bool tImageICO::Set(tFrame* frame, bool steal
217
218 Clear(); 
219 if (!frame || !frame->IsValid()) 
220 return false
221 
222 PixelFormatSrc = frame->PixelFormatSrc
223 PixelFormat = tPixelFormat::R8G8B8A8
224 if (steal
225 Frames.Append(item: frame); 
226 else 
227 Frames.Append(item: new tFrame(*frame)); 
228 
229 PixelFormatSrc = frame->PixelFormatSrc
230 PixelFormat = tPixelFormat::R8G8B8A8
231 ColourProfileSrc = tColourProfile::sRGB; // We assume frame must be sRGB. 
232 ColourProfile = tColourProfile::sRGB
233 
234 return true
235
236 
237 
238bool tImageICO::Set(tPicture& picture, bool steal
239
240 Clear(); 
241 if (!picture.IsValid()) 
242 return false
243 
244 PixelFormatSrc = picture.PixelFormatSrc
245 PixelFormat = tPixelFormat::R8G8B8A8
246 // We don't know colour profile of tPicture. 
247 
248 // This is worth some explanation. If steal is true the picture becomes invalid and the 
249 // 'set' call will steal the stolen pixels. If steal is false GetPixels is called and the 
250 // 'set' call will memcpy them out... which makes sure the picture is still valid after and 
251 // no-one is sharing the pixel buffer. We don't check the success of 'set' because it must 
252 // succeed if picture was valid. 
253 tPixel4b* pixels = steal ? picture.StealPixels() : picture.GetPixels(); 
254 bool success = Set(pixels, width: picture.GetWidth(), height: picture.GetHeight(), steal); 
255 tAssert(success); 
256 return true
257
258 
259 
260tFrame* tImageICO::GetFrame(bool steal
261
262 if (!IsValid()) 
263 return nullptr
264 
265 return steal ? Frames.Remove() : new tFrame( *Frames.First() ); 
266
267 
268 
269tFrame* tImageICO::CreateFrame(const uint8* cursor, int width, int height, int numBytes
270
271 IconImage* icon = (IconImage*)cursor
272  
273 // ICO files may have embedded pngs. 
274 if (icon->Header.Size == 0x474e5089
275
276 tImagePNG::LoadParams params
277 tAssert(params.Flags & tImagePNG::LoadFlag_ForceToBpc8); 
278 tImagePNG pngImage(cursor, numBytes, params); 
279 if (!pngImage.IsValid()) 
280 return nullptr
281 
282 width = pngImage.GetWidth(); 
283 height = pngImage.GetHeight(); 
284 tAssert((width > 0) && (height > 0)); 
285 
286 tPixel4b* pixels = pngImage.StealPixels8(); 
287 bool isOpaque = pngImage.IsOpaque(); 
288  
289 tFrame* newFrame = new tFrame
290 newFrame->PixelFormatSrc = isOpaque ? tPixelFormat::R8G8B8 : tPixelFormat::R8G8B8A8
291 newFrame->Width = width
292 newFrame->Height = height
293 newFrame->Pixels = pixels
294 return newFrame
295
296  
297 int realBitsCount = int(icon->Header.BitCount); 
298 bool hasAndMask = (realBitsCount < 32) && (height != icon->Header.Height); 
299 
300 cursor += 40
301 int numPixels = width * height
302  
303 tPixel4b* pixels = new tPixel4b[numPixels]; 
304 uint8* image = (uint8*)pixels
305 tPixelFormat srcPixelFormat = tPixelFormat::Invalid
306  
307 // rgba + vertical swap 
308 if (realBitsCount == 32
309
310 srcPixelFormat = tPixelFormat::R8G8B8A8
311 for (int x = 0; x < width; x++) 
312
313 for (int y = 0; y < height; y++) 
314
315 int shift = 4 * (x + y * width); 
316  
317 // Rows from bottom to top. 
318 // int shift2 = 4 * (x + (height - y - 1) * width); 
319 int shift2 = 4 * (x + y * width); 
320  
321 image[shift] = cursor[shift2 +2]; 
322 image[shift+1] = cursor[shift2 +1]; 
323 image[shift+2] = cursor[shift2 ]; 
324 image[shift+3] = cursor[shift2 +3]; 
325
326
327
328 
329 if (realBitsCount == 24
330
331 srcPixelFormat = tPixelFormat::R8G8B8
332 for (int x = 0; x < width; x++) 
333
334 for (int y = 0; y < height; y++) 
335
336 int shift = 4 * (x + y * width); 
337  
338 // Rows from bottom to top. 
339 // int shift2 = 3 * (x + (height - y - 1) * width); 
340 int shift2 = 3 * (x + y * width); 
341  
342 image[shift] = cursor[shift2 +2]; 
343 image[shift+1] = cursor[shift2 +1]; 
344 image[shift+2] = cursor[shift2 ]; 
345 image[shift+3] = 255
346
347
348
349 
350 if (realBitsCount == 8
351
352 // 256 colour palette. 
353 srcPixelFormat = tPixelFormat::PAL8BIT
354  
355 uint8* colors = (uint8*)cursor
356 cursor += 256 * 4
357 for (int x = 0; x < width; x++) 
358
359 for (int y = 0; y < height; y++) 
360
361 int shift = 4 * (x + y * width); 
362  
363 // Rows from bottom to top. 
364 // int shift2 = (x + (height - y - 1) * width); 
365 int shift2 = (x + y * width); 
366  
367 int index = 4 * cursor[shift2]; 
368 image[shift] = colors[index + 2]; 
369 image[shift+1] = colors[index + 1]; 
370 image[shift+2] = colors[index ]; 
371 image[shift+3] = 255
372
373
374
375 
376 if (realBitsCount == 4
377
378 // 16 colour palette. 
379 srcPixelFormat = tPixelFormat::PAL4BIT
380 
381 uint8* colors = (uint8*)cursor
382 cursor += 16 * 4
383 for (int x = 0; x < width; x++) 
384
385 for (int y = 0; y < height; y++) 
386
387 int shift = 4 * (x + y * width); 
388 
389 // Rows from bottom to top. 
390 // int shift2 = (x + (height - y - 1) * width); 
391 int shift2 = (x + y * width); 
392 
393 uint8 index = cursor[shift2 / 2]; 
394 if (shift2 % 2 == 0
395 index = (index >> 4) & 0xF
396 else 
397 index = index & 0xF
398 index *= 4
399 
400 image[shift] = colors[index + 2]; 
401 image[shift+1] = colors[index + 1]; 
402 image[shift+2] = colors[index ]; 
403 image[shift+3] = 255
404
405
406
407 
408 if (realBitsCount == 1
409
410 // 2 colour palette. 
411 srcPixelFormat = tPixelFormat::PAL1BIT
412 
413 uint8* colors = (uint8*)cursor
414 cursor += 2 * 4
415 
416 int boundary = width; //!!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico) 
417 while (boundary % 32 != 0
418 boundary++; 
419 
420 for (int x = 0; x < width; x++) 
421
422 for (int y = 0; y < height; y++) 
423
424 int shift = 4 * (x + y * width); 
425 
426 // Rows from bottom to top. 
427 // int shift2 = (x + (height - y - 1) * boundary); 
428 int shift2 = (x + y * boundary); 
429  
430 uint8 index = cursor[shift2 / 8]; 
431 
432 // Select 1 bit only. 
433 uint8 bit = 7 - (x % 8); 
434 index = (index >> bit) & 0x01
435 index *= 4
436  
437 image[shift] = colors[index + 2]; 
438 image[shift+1] = colors[index + 1]; 
439 image[shift+2] = colors[index ]; 
440 image[shift+3] = 255
441
442
443
444 
445 // Read AND mask after base color data - 1 BIT MASK 
446 if (hasAndMask
447
448 int boundary = width * realBitsCount; //!!! 32 bit boundary (http://www.daubnet.com/en/file-format-ico) 
449 while (boundary % 32 != 0
450 boundary++; 
451 cursor += boundary * height / 8
452 
453 boundary = width
454 while (boundary % 32 != 0
455 boundary++; 
456 
457 for (int y = 0; y < height; y++) 
458
459 for (int x = 0; x < width; x++) 
460
461 int shift = 4 * (x + y * width) + 3
462 uint8 bit = 7 - (x % 8); 
463 
464 // Rows from bottom to top. 
465 // int shift2 = (x + (height - y - 1) * boundary) / 8; 
466 int shift2 = (x + y * boundary) / 8
467 
468 int mask = (0x01 & ((unsigned char)cursor[shift2] >> bit)); 
469 image[shift] *= 1 - mask
470
471
472
473  
474 tFrame* newFrame = new tFrame
475 newFrame->PixelFormatSrc = srcPixelFormat
476 newFrame->Width = width
477 newFrame->Height = height
478 newFrame->Pixels = pixels
479 
480 return newFrame
481
482 
483 
484
485