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
21namespace app {
22
23using namespace base;
24
25class 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
57FileFormat* CreatePcxFormat()
58{
59 return new PcxFormat;
60}
61
62bool 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
193bool 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