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
24namespace app {
25
26using namespace base;
27
28class 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
57FileFormat* CreateIcoFormat()
58{
59 return new IcoFormat;
60}
61
62struct ICONDIR {
63 uint16_t reserved;
64 uint16_t type;
65 uint16_t entries;
66};
67
68struct 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
79struct BITMAPINFOHEADER {
80 uint32_t size;
81 uint32_t width;
82 uint32_t height;
83 uint16_t planes;
84 uint16_t bpp;
85 uint32_t compression;
86 uint32_t imageSize;
87 uint32_t xPelsPerMeter;
88 uint32_t yPelsPerMeter;
89 uint32_t clrUsed;
90 uint32_t clrImportant;
91};
92
93bool 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 header;
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 bmpHeader;
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
241bool 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