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"  |
38 | using namespace tSystem;  |
39 | namespace tImage  |
40 | {  |
41 |   |
42 |   |
43 | // These structs represent how the icon information is stored in an ICO file.  |
44 | struct 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 |   |
57 | struct 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  |
67 | struct   |
68 | {  |
69 | uint32 ;  |
70 | uint32 ;  |
71 | uint32 ; // Icon Height (added height of XOR-Bitmap and AND-Bitmap).  |
72 | uint16 ;  |
73 | uint16 ;  |
74 | uint32 ;  |
75 | int32 ;  |
76 | uint32 ;  |
77 | uint32 ;  |
78 | uint32 ;  |
79 | uint32 ;  |
80 | };  |
81 |   |
82 |   |
83 | struct IconImage  |
84 | {  |
85 | BitmapInfoHeader ; // 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 |   |
92 | bool 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 |   |
111 | bool 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 |   |
158 | tImageICO::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 | |