1 | // Aseprite |
2 | // Copyright (C) 2022 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 | // pcx.c - Based on the code of Shawn Hargreaves. |
9 | |
10 | #ifdef HAVE_CONFIG_H |
11 | #include "config.h" |
12 | #endif |
13 | |
14 | #include "app/file/file.h" |
15 | #include "app/file/file_format.h" |
16 | #include "app/file/format_options.h" |
17 | #include "base/cfile.h" |
18 | #include "base/file_handle.h" |
19 | #include "doc/doc.h" |
20 | |
21 | namespace app { |
22 | |
23 | using namespace base; |
24 | |
25 | class PcxFormat : public FileFormat { |
26 | |
27 | const char* onGetName() const override { |
28 | return "pcx" ; |
29 | } |
30 | |
31 | void onGetExtensions(base::paths& exts) const override { |
32 | exts.push_back("pcx" ); |
33 | exts.push_back("pcc" ); |
34 | } |
35 | |
36 | dio::FileFormat onGetDioFormat() const override { |
37 | return dio::FileFormat::PCX_IMAGE; |
38 | } |
39 | |
40 | int onGetFlags() const override { |
41 | return |
42 | FILE_SUPPORT_LOAD | |
43 | FILE_SUPPORT_SAVE | |
44 | FILE_SUPPORT_RGB | |
45 | FILE_SUPPORT_GRAY | |
46 | FILE_SUPPORT_INDEXED | |
47 | FILE_SUPPORT_SEQUENCES | |
48 | FILE_ENCODE_ABSTRACT_IMAGE; |
49 | } |
50 | |
51 | bool onLoad(FileOp* fop) override; |
52 | #ifdef ENABLE_SAVE |
53 | bool onSave(FileOp* fop) override; |
54 | #endif |
55 | }; |
56 | |
57 | FileFormat* CreatePcxFormat() |
58 | { |
59 | return new PcxFormat; |
60 | } |
61 | |
62 | bool PcxFormat::onLoad(FileOp* fop) |
63 | { |
64 | int c, r, g, b; |
65 | int width, height; |
66 | int bpp, bytes_per_line; |
67 | int xx, po; |
68 | int x, y; |
69 | char ch = 0; |
70 | |
71 | FileHandle handle(open_file_with_exception(fop->filename(), "rb" )); |
72 | FILE* f = handle.get(); |
73 | |
74 | fgetc(f); /* skip manufacturer ID */ |
75 | fgetc(f); /* skip version flag */ |
76 | fgetc(f); /* skip encoding flag */ |
77 | |
78 | if (fgetc(f) != 8) { /* we like 8 bit color planes */ |
79 | fop->setError("This PCX doesn't have 8 bit color planes.\n" ); |
80 | return false; |
81 | } |
82 | |
83 | width = -(fgetw(f)); /* xmin */ |
84 | height = -(fgetw(f)); /* ymin */ |
85 | width += fgetw(f) + 1; /* xmax */ |
86 | height += fgetw(f) + 1; /* ymax */ |
87 | |
88 | fgetl(f); /* skip DPI values */ |
89 | |
90 | for (c=0; c<16; c++) { /* read the 16 color palette */ |
91 | r = fgetc(f); |
92 | g = fgetc(f); |
93 | b = fgetc(f); |
94 | fop->sequenceSetColor(c, r, g, b); |
95 | } |
96 | |
97 | fgetc(f); |
98 | |
99 | bpp = fgetc(f) * 8; /* how many color planes? */ |
100 | if ((bpp != 8) && (bpp != 24)) { |
101 | return false; |
102 | } |
103 | |
104 | bytes_per_line = fgetw(f); |
105 | |
106 | for (c=0; c<60; c++) /* skip some more junk */ |
107 | fgetc(f); |
108 | |
109 | ImageRef image = fop->sequenceImage(bpp == 8 ? |
110 | IMAGE_INDEXED: |
111 | IMAGE_RGB, |
112 | width, height); |
113 | if (!image) { |
114 | return false; |
115 | } |
116 | |
117 | if (bpp == 24) |
118 | clear_image(image.get(), rgba(0, 0, 0, 255)); |
119 | |
120 | for (y=0; y<height; y++) { /* read RLE encoded PCX data */ |
121 | x = xx = 0; |
122 | po = rgba_r_shift; |
123 | |
124 | while (x < bytes_per_line*bpp/8) { |
125 | ch = fgetc(f); |
126 | if ((ch & 0xC0) == 0xC0) { |
127 | c = (ch & 0x3F); |
128 | ch = fgetc(f); |
129 | } |
130 | else |
131 | c = 1; |
132 | |
133 | if (bpp == 8) { |
134 | while (c--) { |
135 | if (x < image->width()) |
136 | put_pixel_fast<IndexedTraits>(image.get(), x, y, ch); |
137 | |
138 | x++; |
139 | } |
140 | } |
141 | else { |
142 | while (c--) { |
143 | if (xx < image->width()) |
144 | put_pixel_fast<RgbTraits>(image.get(), xx, y, |
145 | get_pixel_fast<RgbTraits>(image.get(), xx, y) | ((ch & 0xff) << po)); |
146 | |
147 | x++; |
148 | if (x == bytes_per_line) { |
149 | xx = 0; |
150 | po = rgba_g_shift; |
151 | } |
152 | else if (x == bytes_per_line*2) { |
153 | xx = 0; |
154 | po = rgba_b_shift; |
155 | } |
156 | else |
157 | xx++; |
158 | } |
159 | } |
160 | } |
161 | |
162 | fop->setProgress((float)(y+1) / (float)(height)); |
163 | if (fop->isStop()) |
164 | break; |
165 | } |
166 | |
167 | if (!fop->isStop()) { |
168 | if (bpp == 8) { /* look for a 256 color palette */ |
169 | while ((c = fgetc(f)) != EOF) { |
170 | if (c == 12) { |
171 | for (c=0; c<256; c++) { |
172 | r = fgetc(f); |
173 | g = fgetc(f); |
174 | b = fgetc(f); |
175 | fop->sequenceSetColor(c, r, g, b); |
176 | } |
177 | break; |
178 | } |
179 | } |
180 | } |
181 | } |
182 | |
183 | if (ferror(f)) { |
184 | fop->setError("Error reading file.\n" ); |
185 | return false; |
186 | } |
187 | else { |
188 | return true; |
189 | } |
190 | } |
191 | |
192 | #ifdef ENABLE_SAVE |
193 | bool PcxFormat::onSave(FileOp* fop) |
194 | { |
195 | const FileAbstractImage* img = fop->abstractImage(); |
196 | const ImageSpec spec = img->spec(); |
197 | int c, r, g, b; |
198 | int x, y; |
199 | int runcount; |
200 | int depth, planes; |
201 | char runchar; |
202 | char ch = 0; |
203 | |
204 | FileHandle handle(open_file_with_exception_sync_on_close(fop->filename(), "wb" )); |
205 | FILE* f = handle.get(); |
206 | |
207 | if (spec.colorMode() == ColorMode::RGB) { |
208 | depth = 24; |
209 | planes = 3; |
210 | } |
211 | else { |
212 | depth = 8; |
213 | planes = 1; |
214 | } |
215 | |
216 | fputc(10, f); /* manufacturer */ |
217 | fputc(5, f); /* version */ |
218 | fputc(1, f); /* run length encoding */ |
219 | fputc(8, f); /* 8 bits per pixel */ |
220 | fputw(0, f); /* xmin */ |
221 | fputw(0, f); /* ymin */ |
222 | fputw(spec.width()-1, f); /* xmax */ |
223 | fputw(spec.height()-1, f); /* ymax */ |
224 | fputw(320, f); /* HDpi */ |
225 | fputw(200, f); /* VDpi */ |
226 | |
227 | for (c=0; c<16; c++) { |
228 | fop->sequenceGetColor(c, &r, &g, &b); |
229 | fputc(r, f); |
230 | fputc(g, f); |
231 | fputc(b, f); |
232 | } |
233 | |
234 | fputc(0, f); /* reserved */ |
235 | fputc(planes, f); /* one or three color planes */ |
236 | fputw(spec.width(), f); /* number of bytes per scanline */ |
237 | fputw(1, f); /* color palette */ |
238 | fputw(spec.width(), f); /* hscreen size */ |
239 | fputw(spec.height(), f); /* vscreen size */ |
240 | for (c=0; c<54; c++) /* filler */ |
241 | fputc(0, f); |
242 | |
243 | for (y=0; y<spec.height(); y++) { /* for each scanline... */ |
244 | runcount = 0; |
245 | runchar = 0; |
246 | |
247 | const uint8_t* scanline = img->getScanline(y); |
248 | |
249 | for (x=0; x<spec.width()*planes; x++) { /* for each pixel... */ |
250 | if (depth == 8) { |
251 | if (spec.colorMode() == ColorMode::INDEXED) |
252 | ch = scanline[x]; |
253 | else if (spec.colorMode() == ColorMode::GRAYSCALE) { |
254 | c = ((const uint16_t*)scanline)[x]; |
255 | ch = graya_getv(c); |
256 | } |
257 | } |
258 | else { |
259 | if (x < spec.width()) { |
260 | c = ((const uint32_t*)scanline)[x]; |
261 | ch = rgba_getr(c); |
262 | } |
263 | else if (x<spec.width()*2) { |
264 | c = ((const uint32_t*)scanline)[x-spec.width()]; |
265 | ch = rgba_getg(c); |
266 | } |
267 | else { |
268 | c = ((const uint32_t*)scanline)[x-spec.width()*2]; |
269 | ch = rgba_getb(c); |
270 | } |
271 | } |
272 | if (runcount == 0) { |
273 | runcount = 1; |
274 | runchar = ch; |
275 | } |
276 | else { |
277 | if ((ch != runchar) || (runcount >= 0x3f)) { |
278 | if ((runcount > 1) || ((runchar & 0xC0) == 0xC0)) |
279 | fputc(0xC0 | runcount, f); |
280 | fputc(runchar, f); |
281 | runcount = 1; |
282 | runchar = ch; |
283 | } |
284 | else |
285 | runcount++; |
286 | } |
287 | } |
288 | |
289 | if ((runcount > 1) || ((runchar & 0xC0) == 0xC0)) |
290 | fputc(0xC0 | runcount, f); |
291 | |
292 | fputc(runchar, f); |
293 | |
294 | fop->setProgress((float)(y+1) / (float)(spec.height())); |
295 | } |
296 | |
297 | if (depth == 8) { /* 256 color palette */ |
298 | fputc(12, f); |
299 | |
300 | for (c=0; c<256; c++) { |
301 | fop->sequenceGetColor(c, &r, &g, &b); |
302 | fputc(r, f); |
303 | fputc(g, f); |
304 | fputc(b, f); |
305 | } |
306 | } |
307 | |
308 | if (ferror(f)) { |
309 | fop->setError("Error writing file.\n" ); |
310 | return false; |
311 | } |
312 | else { |
313 | return true; |
314 | } |
315 | } |
316 | #endif |
317 | |
318 | } // namespace app |
319 | |