1 | // Aseprite |
2 | // Copyright (C) 2018-2019 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | // |
8 | // ico.c - Based on the code of Elias Pschernig. |
9 | |
10 | #ifdef HAVE_CONFIG_H |
11 | #include "config.h" |
12 | #endif |
13 | |
14 | #include "app/doc.h" |
15 | #include "app/file/file.h" |
16 | #include "app/file/file_format.h" |
17 | #include "app/file/format_options.h" |
18 | #include "app/pref/preferences.h" |
19 | #include "base/cfile.h" |
20 | #include "base/file_handle.h" |
21 | #include "doc/doc.h" |
22 | #include "render/render.h" |
23 | |
24 | namespace app { |
25 | |
26 | using namespace base; |
27 | |
28 | class IcoFormat : public FileFormat { |
29 | |
30 | const char* onGetName() const override { |
31 | return "ico" ; |
32 | } |
33 | |
34 | void onGetExtensions(base::paths& exts) const override { |
35 | exts.push_back("ico" ); |
36 | } |
37 | |
38 | dio::FileFormat onGetDioFormat() const override { |
39 | return dio::FileFormat::ICO_IMAGES; |
40 | } |
41 | |
42 | int onGetFlags() const override { |
43 | return |
44 | FILE_SUPPORT_LOAD | |
45 | FILE_SUPPORT_SAVE | |
46 | FILE_SUPPORT_RGB | |
47 | FILE_SUPPORT_GRAY | |
48 | FILE_SUPPORT_INDEXED; |
49 | } |
50 | |
51 | bool onLoad(FileOp* fop) override; |
52 | #ifdef ENABLE_SAVE |
53 | bool onSave(FileOp* fop) override; |
54 | #endif |
55 | }; |
56 | |
57 | FileFormat* CreateIcoFormat() |
58 | { |
59 | return new IcoFormat; |
60 | } |
61 | |
62 | struct ICONDIR { |
63 | uint16_t reserved; |
64 | uint16_t type; |
65 | uint16_t entries; |
66 | }; |
67 | |
68 | struct ICONDIRENTRY { |
69 | uint8_t width; |
70 | uint8_t height; |
71 | uint8_t color_count; |
72 | uint8_t reserved; |
73 | uint16_t planes; |
74 | uint16_t bpp; |
75 | uint32_t image_size; |
76 | uint32_t image_offset; |
77 | }; |
78 | |
79 | struct { |
80 | uint32_t ; |
81 | uint32_t ; |
82 | uint32_t ; |
83 | uint16_t ; |
84 | uint16_t ; |
85 | uint32_t ; |
86 | uint32_t ; |
87 | uint32_t ; |
88 | uint32_t ; |
89 | uint32_t ; |
90 | uint32_t ; |
91 | }; |
92 | |
93 | bool IcoFormat::onLoad(FileOp* fop) |
94 | { |
95 | FileHandle handle(open_file_with_exception(fop->filename(), "rb" )); |
96 | FILE* f = handle.get(); |
97 | |
98 | // Read the icon header |
99 | ICONDIR ; |
100 | header.reserved = fgetw(f); // Reserved |
101 | header.type = fgetw(f); // Resource type: 1=ICON |
102 | header.entries = fgetw(f); // Number of icons |
103 | |
104 | if (header.type != 1) { |
105 | fop->setError("Invalid ICO file type.\n" ); |
106 | return false; |
107 | } |
108 | |
109 | if (header.entries < 1) { |
110 | fop->setError("This ICO files does not contain images.\n" ); |
111 | return false; |
112 | } |
113 | |
114 | // Read all entries |
115 | std::vector<ICONDIRENTRY> entries; |
116 | entries.reserve(header.entries); |
117 | for (uint16_t n=0; n<header.entries; ++n) { |
118 | ICONDIRENTRY entry; |
119 | entry.width = fgetc(f); // width |
120 | entry.height = fgetc(f); // height |
121 | entry.color_count = fgetc(f); // color count |
122 | entry.reserved = fgetc(f); // reserved |
123 | entry.planes = fgetw(f); // color planes |
124 | entry.bpp = fgetw(f); // bits per pixel |
125 | entry.image_size = fgetl(f); // size in bytes of image data |
126 | entry.image_offset = fgetl(f); // file offset to image data |
127 | entries.push_back(entry); |
128 | } |
129 | |
130 | // Read the first entry |
131 | const ICONDIRENTRY& entry = entries[0]; |
132 | int width = (entry.width == 0 ? 256: entry.width); |
133 | int height = (entry.height == 0 ? 256: entry.height); |
134 | int numcolors = (entry.color_count == 0 ? 256: entry.color_count); |
135 | PixelFormat pixelFormat = IMAGE_INDEXED; |
136 | if (entry.bpp > 8) |
137 | pixelFormat = IMAGE_RGB; |
138 | |
139 | // Create the sprite with one background layer |
140 | Sprite* sprite = new Sprite(ImageSpec((ColorMode)pixelFormat, width, height), numcolors); |
141 | LayerImage* layer = new LayerImage(sprite); |
142 | sprite->root()->addLayer(layer); |
143 | |
144 | // Create the first image/cel |
145 | ImageRef image(Image::create(pixelFormat, width, height)); |
146 | Cel* cel = new Cel(frame_t(0), image); |
147 | layer->addCel(cel); |
148 | clear_image(image.get(), 0); |
149 | |
150 | // Go to the entry start in the file |
151 | fseek(f, entry.image_offset, SEEK_SET); |
152 | |
153 | // Read BITMAPINFOHEADER |
154 | BITMAPINFOHEADER ; |
155 | bmpHeader.size = fgetl(f); |
156 | bmpHeader.width = fgetl(f); |
157 | bmpHeader.height = fgetl(f); // XOR height + AND height |
158 | bmpHeader.planes = fgetw(f); |
159 | bmpHeader.bpp = fgetw(f); |
160 | bmpHeader.compression = fgetl(f); // unused in .ico files |
161 | bmpHeader.imageSize = fgetl(f); |
162 | bmpHeader.xPelsPerMeter = fgetl(f); // unused for ico |
163 | bmpHeader.yPelsPerMeter = fgetl(f); // unused for ico |
164 | bmpHeader.clrUsed = fgetl(f); // unused for ico |
165 | bmpHeader.clrImportant = fgetl(f); // unused for ico |
166 | (void)bmpHeader; // unused |
167 | |
168 | // Read the palette |
169 | if (entry.bpp <= 8) { |
170 | Palette* pal = new Palette(frame_t(0), numcolors); |
171 | |
172 | for (int i=0; i<numcolors; ++i) { |
173 | int b = fgetc(f); |
174 | int g = fgetc(f); |
175 | int r = fgetc(f); |
176 | fgetc(f); |
177 | |
178 | pal->setEntry(i, rgba(r, g, b, 255)); |
179 | } |
180 | |
181 | sprite->setPalette(pal, true); |
182 | delete pal; |
183 | } |
184 | |
185 | // Read XOR MASK |
186 | int x, y, c, r, g, b; |
187 | for (y=image->height()-1; y>=0; --y) { |
188 | for (x=0; x<image->width(); ++x) { |
189 | switch (entry.bpp) { |
190 | |
191 | case 8: |
192 | c = fgetc(f); |
193 | ASSERT(c >= 0 && c < numcolors); |
194 | if (c >= 0 && c < numcolors) |
195 | put_pixel(image.get(), x, y, c); |
196 | else |
197 | put_pixel(image.get(), x, y, 0); |
198 | break; |
199 | |
200 | case 24: |
201 | b = fgetc(f); |
202 | g = fgetc(f); |
203 | r = fgetc(f); |
204 | put_pixel(image.get(), x, y, rgba(r, g, b, 255)); |
205 | break; |
206 | } |
207 | } |
208 | |
209 | // every scanline must be 32-bit aligned |
210 | while (x & 3) { |
211 | fgetc(f); |
212 | x++; |
213 | } |
214 | } |
215 | |
216 | // AND mask |
217 | int m, v; |
218 | for (y=image->height()-1; y>=0; --y) { |
219 | for (x=0; x<(image->width()+7)/8; ++x) { |
220 | m = fgetc(f); |
221 | v = 128; |
222 | for (b=0; b<8; b++) { |
223 | if ((m & v) == v) |
224 | put_pixel(image.get(), x*8+b, y, 0); // TODO mask color |
225 | v >>= 1; |
226 | } |
227 | } |
228 | |
229 | // every scanline must be 32-bit aligned |
230 | while (x & 3) { |
231 | fgetc(f); |
232 | x++; |
233 | } |
234 | } |
235 | |
236 | fop->createDocument(sprite); |
237 | return true; |
238 | } |
239 | |
240 | #ifdef ENABLE_SAVE |
241 | bool IcoFormat::onSave(FileOp* fop) |
242 | { |
243 | const Sprite* sprite = fop->document()->sprite(); |
244 | int bpp, bw, bitsw; |
245 | int size, offset, i; |
246 | int c, x, y, b, m, v; |
247 | frame_t n, num = sprite->totalFrames(); |
248 | |
249 | FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb" )); |
250 | FILE* f = handle.get(); |
251 | |
252 | offset = 6 + num*16; // ICONDIR + ICONDIRENTRYs |
253 | |
254 | // Icon directory |
255 | fputw(0, f); // reserved |
256 | fputw(1, f); // resource type: 1=ICON |
257 | fputw(num, f); // number of icons |
258 | |
259 | // Entries |
260 | for (n=frame_t(0); n<num; ++n) { |
261 | bpp = (sprite->pixelFormat() == IMAGE_INDEXED) ? 8 : 24; |
262 | bw = (((sprite->width() * bpp / 8) + 3) / 4) * 4; |
263 | bitsw = ((((sprite->width() + 7) / 8) + 3) / 4) * 4; |
264 | size = sprite->height() * (bw + bitsw) + 40; |
265 | |
266 | if (bpp == 8) |
267 | size += 256 * 4; |
268 | |
269 | // ICONDIRENTRY |
270 | fputc(sprite->width(), f); // width |
271 | fputc(sprite->height(), f); // height |
272 | fputc(0, f); // color count |
273 | fputc(0, f); // reserved |
274 | fputw(1, f); // color planes |
275 | fputw(bpp, f); // bits per pixel |
276 | fputl(size, f); // size in bytes of image data |
277 | fputl(offset, f); // file offset to image data |
278 | |
279 | offset += size; |
280 | } |
281 | |
282 | std::unique_ptr<Image> image(Image::create( |
283 | sprite->pixelFormat(), |
284 | sprite->width(), |
285 | sprite->height())); |
286 | |
287 | render::Render render; |
288 | render.setNewBlend(fop->newBlend()); |
289 | |
290 | for (n=frame_t(0); n<num; ++n) { |
291 | render.renderSprite(image.get(), sprite, n); |
292 | |
293 | bpp = (sprite->pixelFormat() == IMAGE_INDEXED) ? 8 : 24; |
294 | bw = (((image->width() * bpp / 8) + 3) / 4) * 4; |
295 | bitsw = ((((image->width() + 7) / 8) + 3) / 4) * 4; |
296 | size = image->height() * (bw + bitsw) + 40; |
297 | |
298 | if (bpp == 8) |
299 | size += 256 * 4; |
300 | |
301 | // BITMAPINFOHEADER |
302 | fputl(40, f); // size |
303 | fputl(image->width(), f); // width |
304 | fputl(image->height() * 2, f); // XOR height + AND height |
305 | fputw(1, f); // planes |
306 | fputw(bpp, f); // bitcount |
307 | fputl(0, f); // unused for ico |
308 | fputl(size, f); // size |
309 | fputl(0, f); // unused for ico |
310 | fputl(0, f); // unused for ico |
311 | fputl(0, f); // unused for ico |
312 | fputl(0, f); // unused for ico |
313 | |
314 | // PALETTE |
315 | if (bpp == 8) { |
316 | Palette *pal = sprite->palette(n); |
317 | |
318 | fputl(0, f); // color 0 is black, so the XOR mask works |
319 | |
320 | for (i=1; i<256; i++) { |
321 | fputc(rgba_getb(pal->getEntry(i)), f); |
322 | fputc(rgba_getg(pal->getEntry(i)), f); |
323 | fputc(rgba_getr(pal->getEntry(i)), f); |
324 | fputc(0, f); |
325 | } |
326 | } |
327 | |
328 | // XOR MASK |
329 | for (y=image->height()-1; y>=0; --y) { |
330 | for (x=0; x<image->width(); ++x) { |
331 | switch (image->pixelFormat()) { |
332 | |
333 | case IMAGE_RGB: |
334 | c = get_pixel(image.get(), x, y); |
335 | fputc(rgba_getb(c), f); |
336 | fputc(rgba_getg(c), f); |
337 | fputc(rgba_getr(c), f); |
338 | break; |
339 | |
340 | case IMAGE_GRAYSCALE: |
341 | c = get_pixel(image.get(), x, y); |
342 | fputc(graya_getv(c), f); |
343 | fputc(graya_getv(c), f); |
344 | fputc(graya_getv(c), f); |
345 | break; |
346 | |
347 | case IMAGE_INDEXED: |
348 | c = get_pixel(image.get(), x, y); |
349 | fputc(c, f); |
350 | break; |
351 | } |
352 | } |
353 | |
354 | // every scanline must be 32-bit aligned |
355 | while (x & 3) { |
356 | fputc(0, f); |
357 | x++; |
358 | } |
359 | } |
360 | |
361 | // AND MASK |
362 | for (y=image->height()-1; y>=0; --y) { |
363 | for (x=0; x<(image->width()+7)/8; ++x) { |
364 | m = 0; |
365 | v = 128; |
366 | |
367 | for (b=0; b<8; b++) { |
368 | c = get_pixel(image.get(), x*8+b, y); |
369 | |
370 | switch (image->pixelFormat()) { |
371 | |
372 | case IMAGE_RGB: |
373 | if (rgba_geta(c) == 0) |
374 | m |= v; |
375 | break; |
376 | |
377 | case IMAGE_GRAYSCALE: |
378 | if (graya_geta(c) == 0) |
379 | m |= v; |
380 | break; |
381 | |
382 | case IMAGE_INDEXED: |
383 | if (c == 0) // TODO configurable background color (or nothing as background) |
384 | m |= v; |
385 | break; |
386 | } |
387 | |
388 | v >>= 1; |
389 | } |
390 | |
391 | fputc(m, f); |
392 | } |
393 | |
394 | // every scanline must be 32-bit aligned |
395 | while (x & 3) { |
396 | fputc(0, f); |
397 | x++; |
398 | } |
399 | } |
400 | } |
401 | |
402 | return true; |
403 | } |
404 | #endif |
405 | |
406 | } // namespace app |
407 | |