1/**
2 * PIC_RW - Qt PIC Support
3 * Copyright (C) 2007 Ruben Lopez <r.lopez@bren.es>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 * ----------------------------------------------------------------------------
19 */
20
21/* This code is based on the GIMP-PIC plugin by Halfdan Ingvarsson,
22 * and relicensed from GPL to LGPL to accomodate the KDE licensing policy
23 * with his permission.
24 * These is the original copyright:
25 * Copyright (C) 1998 Halfdan Ingvarsson
26 */
27
28#include "pic_rw.h"
29#include <netinet/in.h>
30#include <iostream>
31#include <qimage.h>
32#include <algorithm>
33
34/**
35 * Reads the PIC header and checks that it is OK
36 * @param dev The QT device to read from
37 * @param hdr A pointer to the PIC header
38 * @param peek Keep bytes in the device
39 * @return true on success
40 */
41bool picReadHeader(QIODevice *dev, PICHeader *hdr, bool peek) {
42 int result = 0;
43 if (peek) {
44 result = dev->peek((char*) hdr, HEADER_SIZE);
45 } else {
46 result = dev->read((char*) hdr, HEADER_SIZE);
47 }
48
49 hdr->magic = ntohl(hdr->magic);
50 hdr->width = ntohs(hdr->width);
51 hdr->height = ntohs(hdr->height);
52 hdr->fields = ntohs(hdr->fields);
53
54 if (hdr->magic != PIC_MAGIC_NUMBER || strncmp(hdr->id, "PICT", 4)) {
55 return false;
56 }
57
58 return result == HEADER_SIZE;
59}
60
61#define CHANNEL_BYTE(ch, mask) (( ch & mask) ? 1 : 0)
62
63/**
64 * Gets the channels definition and returns the number of bytes per pixel
65 * @param channels The channels bitfield
66 * @return The number of bytes per pixel
67 */
68static int channels2bpp(char channels) {
69 return CHANNEL_BYTE(channels, RED)
70 + CHANNEL_BYTE(channels, GREEN)
71 + CHANNEL_BYTE(channels, BLUE)
72 + CHANNEL_BYTE(channels, ALPHA);
73}
74
75/**
76 * Reads the channels info
77 * @param dev The QT device to read from
78 * @param channels A pointer to 8 channels
79 * @return true on success
80 */
81static bool readChannels(QIODevice *dev, PICChannel *channels, int &bpp) {
82 int c = 0;
83 memset(channels, 0, sizeof ( PICChannel) *8);
84 do {
85 int result = dev->read((char*) & channels[c], CHANNEL_SIZE);
86 if (result != CHANNEL_SIZE) {
87 return false;
88 } else {
89 bpp += channels2bpp(channels[c].channel);
90 c++;
91 }
92 } while (channels[c - 1].chained);
93 return true;
94}
95
96/**
97 * Makes a component map based on the channels info
98 * @param channels The channel information
99 * @param cmap The component map to be built
100 */
101inline static void makeComponentMap(unsigned channel, unsigned char *cmap) {
102 std::fill(cmap, cmap + 8, 0);
103
104 unsigned compos[] = {ALPHA, BLUE, GREEN, RED};
105 unsigned rgba[] = {3, 2, 1, 0};
106 unsigned pos = 0;
107 for (unsigned compo = 0; compo < 4; compo++) {
108 if (CHANNEL_BYTE(channel, compos[compo])) {
109 cmap[pos++] = rgba[compo];
110 }
111 }
112}
113
114/**
115 * Converts a PIC pixel to 32bits RGBA
116 * @param src_pixel The source PIC pixel as readed from file
117 * @param target_pixel The target buffer where to write the pixel info
118 * @param cmap The component map that maps each component in PIC format to RGBA format
119 * @param components The number of components in the source pixel
120 */
121inline static void pic2RGBA(unsigned char *src_pixel, unsigned char *target_pixel, unsigned char *cmap, unsigned components) {
122 for (unsigned i = 0; i < components; i++) {
123 target_pixel[cmap[i]] = src_pixel[i];
124 }
125}
126
127/**
128 * Counts the number of channels in the PICChannel header
129 * @param channels The header
130 * @return The number of used channels
131 */
132inline static unsigned getNumChannels(PICChannel *channels) {
133 unsigned result = 0;
134 for (unsigned i = 0; i < 8; i++) {
135 if (channels[i].channel != 0) {
136 result++;
137 } else {
138 return result;
139 }
140 }
141 return result;
142}
143
144/**
145 * Decodes a Run-lenght encoded chunk
146 * @param dev The device to read from
147 * @param row The row pointer to write to
148 * @param max The maximum length to write
149 * @param channels The channels header
150 * @return The number of generated pixels
151 */
152static int decodeRLE(QIODevice *dev, void *row, unsigned max, unsigned bpp, unsigned channels) {
153 unsigned char buf[512];
154 unsigned *ptr = (unsigned *) row;
155 unsigned char component_map[8];
156 unsigned len = 0;
157
158 makeComponentMap(channels, component_map);
159
160 if (dev->read((char*) buf, 1) != 1) {
161 return -1;
162 }
163
164 /* If last bit is 1, then it is 2 to 127 repetitions */
165 if (buf[0] > 128) {
166 len = buf[0] - 127;
167 if (len > max) {
168 return -1;
169 }
170 unsigned count = dev->read((char*) buf, bpp);
171 if (count != bpp) {
172 return -1;
173 }
174 for (unsigned i = 0; i < len; i++) {
175 pic2RGBA(buf, (unsigned char*) (ptr + i), component_map, bpp);
176 }
177 } /* If the value is exactly 10000000, it means that it is more than 127 repetitions */
178 else if (buf[0] == 128) {
179 unsigned count = dev->read((char*) buf, bpp + 2);
180 if (count != bpp + 2) {
181 return -1;
182 }
183 len = (buf[0] << 8) | buf[1];
184 if (len > max) {
185 return -1;
186 }
187 for (unsigned i = 0; i < len; i++) {
188 pic2RGBA(buf + 2, (unsigned char*) (ptr + i), component_map, bpp);
189 }
190 } /** No repetitions */
191 else {
192 len = buf[0] + 1;
193 if (len > max) {
194 return -1;
195 }
196 unsigned count = dev->read((char*) buf, len * bpp);
197 if (count != len * bpp) {
198 return -1;
199 }
200 for (unsigned i = 0; i < len; i++) {
201 pic2RGBA(buf + (i * bpp), (unsigned char*) (ptr + i), component_map, bpp);
202 }
203 }
204 return len;
205}
206
207/**
208 * Reads a row from the file
209 * @param dev The device to read from
210 * @param row The row pointer to write to
211 * @param width The image width
212 * @param bpp The bytes per pixel
213 * @param channels The channels header info
214 */
215static bool readRow(QIODevice *dev, unsigned *row, unsigned width, PICChannel *channels) {
216 for (int c = 0; channels[c].channel != 0; c++) {
217 unsigned remain = width;
218 unsigned bpp = channels2bpp(channels[c].channel);
219 if (channels[c].type == (int) RLE) {
220 unsigned *rowpos = row;
221 while (remain > 0) {
222 int readed = decodeRLE(dev, rowpos, remain, bpp, channels[c].channel);
223 if (readed < 0) {
224 return false;
225 }
226 remain -= readed;
227 rowpos += readed;
228 }
229 } else {
230 unsigned char component_map[8];
231 unsigned count = dev->read((char*) row, width * bpp);
232 if (count != width * bpp) {
233 return false;
234 }
235
236 makeComponentMap(channels[c].channel, component_map);
237 for (unsigned i = 0; i < width; i++) {
238 pic2RGBA(((unsigned char*) row) + (i * bpp), (unsigned char*) (row + i), component_map, bpp);
239 }
240 }
241 }
242 return true;
243}
244
245#define FAIL() { \
246 std::cout << "ERROR Reading PIC!" << std::endl; \
247 return; \
248}
249
250bool hasAlpha(PICChannel *channels) {
251 int channel = 0;
252 do {
253 if (CHANNEL_BYTE(channels[channel].channel, ALPHA)) {
254 return true;
255 }
256 channel++;
257 } while (channels[channel - 1].chained);
258 return false;
259}
260
261/**
262 * KDE image reading function. Must have this exact name in order to work
263 */
264void pic_read(QIODevice *dev, QImage *result) {
265 PICHeader header;
266 PICChannel channels[8];
267 int bpp = 0;
268 if (!picReadHeader(dev, &header) || !readChannels(dev, channels, bpp)) {
269 FAIL();
270 }
271 QImage img(header.width, header.height, QImage::Format_ARGB32);
272
273 for (int r = 0; r < header.height; r++) {
274 unsigned *row = (unsigned*) img.scanLine(r);
275 std::fill(row, row + header.width, 0);
276 if (!readRow(dev, row, header.width, channels)) {
277 FAIL();
278 }
279 }
280 // img->setAlphaBuffer(hasAlpha(channels));
281 *result = img;
282}
283