1 // Scintilla source code edit control
2 /** @file XPM.cxx
3  ** Define a class that holds data in the X Pixmap (XPM) format.
4  **/
5 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <cstdlib>
9 #include <cstring>
10 
11 #include <stdexcept>
12 #include <vector>
13 #include <map>
14 #include <algorithm>
15 #include <iterator>
16 #include <memory>
17 
18 #include "Platform.h"
19 
20 #include "XPM.h"
21 
22 using namespace Scintilla;
23 
24 #if defined(PLAT_QT)
25 
XPM(const char * textForm)26 XPM::XPM(const char *textForm)
27 {
28     qpm = *reinterpret_cast<const QPixmap *>(textForm);
29 }
30 
XPM(const char * const * linesForm)31 XPM::XPM(const char *const *linesForm)
32 {
33     qpm = *reinterpret_cast<const QPixmap *>(linesForm);
34 }
35 
Draw(Surface * surface,PRectangle & rc)36 void XPM::Draw(Surface *surface, PRectangle &rc)
37 {
38     surface->DrawXPM(rc, this);
39 }
40 
RGBAImage(int width_,int height_,float scale_,const unsigned char * pixels_)41 RGBAImage::RGBAImage(int width_, int height_, float scale_,
42         const unsigned char *pixels_)
43     : height(height_), width(width_), scale(scale_)
44 {
45     if (pixels_)
46     {
47         qim = new QImage(*reinterpret_cast<const QImage *>(pixels_));
48     }
49     else
50     {
51 #if QT_VERSION >= 0x040000
52         qim = new QImage(width_, height_, QImage::Format_ARGB32);
53 #else
54         qim = new QImage(width_, height_, 32);
55         qim->setAlphaBuffer(true);
56 #endif
57         qim->fill(0);
58     }
59 }
60 
RGBAImage(const XPM & xpm)61 RGBAImage::RGBAImage(const XPM &xpm)
62 {
63 #if QT_VERSION >= 0x040000
64     qim = new QImage(xpm.Pixmap().toImage());
65 #else
66     qim = new QImage(xpm.Pixmap().convertToImage());
67 #endif
68 
69     width = qim->width();
70     height = qim->height();
71 }
72 
~RGBAImage()73 RGBAImage::~RGBAImage()
74 {
75     delete qim;
76 }
77 
Pixels() const78 const unsigned char *RGBAImage::Pixels() const
79 {
80     return reinterpret_cast<const unsigned char *>(qim);
81 }
82 
SetPixel(int x,int y,ColourDesired colour,int alpha)83 void RGBAImage::SetPixel(int x, int y, ColourDesired colour, int alpha)
84 {
85     QRgb rgba = qRgba(colour.GetRed(), colour.GetGreen(), colour.GetBlue(),
86             alpha);
87 
88     uint index_or_rgb;
89 
90 #if QT_VERSION >= 0x040000
91     switch (qim->format())
92     {
93     case QImage::Format_RGB32:
94     case QImage::Format_ARGB32:
95         index_or_rgb = rgba;
96         break;
97 
98     case QImage::Format_ARGB32_Premultiplied:
99         {
100             uint a = alpha;
101 #if QT_POINTER_SIZE == 8
102             quint64 t = (((quint64(rgba)) | ((quint64(rgba)) << 24)) & 0x00ff00ff00ff00ff) * a;
103             t = (t + ((t >> 8) & 0xff00ff00ff00ff) + 0x80008000800080) >> 8;
104             t &= 0x000000ff00ff00ff;
105             index_or_rgb = (uint(t)) | (uint(t >> 24)) | (a << 24);
106 #else
107             uint t = (rgba & 0xff00ff) * a;
108             t = (t + ((t >> 8) & 0xff00ff) + 0x800080) >> 8;
109             t &= 0xff00ff;
110 
111             rgba = ((rgba >> 8) & 0xff) * a;
112             rgba = (rgba + ((rgba >> 8) & 0xff) + 0x80);
113             rgba &= 0xff00;
114             index_or_rgb = rgba | t | (a << 24);
115 #endif
116             break;
117         }
118 
119     default:
120 #if QT_VERSION >= 0x040600
121         index_or_rgb = qim->colorCount();
122 #else
123         index_or_rgb = qim->colorTable().count();
124 #endif
125 
126         qim->setColor(index_or_rgb, rgba);
127     }
128 #else
129     if (qim->depth() == 32)
130     {
131         index_or_rgb = rgba;
132     }
133     else
134     {
135         index_or_rgb = qim->numColors();
136         qim->setNumColors(index_or_rgb + 1);
137 
138         qim->setColor(index_or_rgb, rgba);
139     }
140 #endif
141 
142     qim->setPixel(x, y, index_or_rgb);
143 }
144 
145 #else
146 
147 namespace {
148 
NextField(const char * s)149 const char *NextField(const char *s) {
150 	// In case there are leading spaces in the string
151 	while (*s == ' ') {
152 		s++;
153 	}
154 	while (*s && *s != ' ') {
155 		s++;
156 	}
157 	while (*s == ' ') {
158 		s++;
159 	}
160 	return s;
161 }
162 
163 // Data lines in XPM can be terminated either with NUL or "
MeasureLength(const char * s)164 size_t MeasureLength(const char *s) {
165 	size_t i = 0;
166 	while (s[i] && (s[i] != '\"'))
167 		i++;
168 	return i;
169 }
170 
ValueOfHex(const char ch)171 unsigned int ValueOfHex(const char ch) noexcept {
172 	if (ch >= '0' && ch <= '9')
173 		return ch - '0';
174 	else if (ch >= 'A' && ch <= 'F')
175 		return ch - 'A' + 10;
176 	else if (ch >= 'a' && ch <= 'f')
177 		return ch - 'a' + 10;
178 	else
179 		return 0;
180 }
181 
ColourFromHex(const char * val)182 ColourDesired ColourFromHex(const char *val) noexcept {
183 	const unsigned int r = ValueOfHex(val[0]) * 16 + ValueOfHex(val[1]);
184 	const unsigned int g = ValueOfHex(val[2]) * 16 + ValueOfHex(val[3]);
185 	const unsigned int b = ValueOfHex(val[4]) * 16 + ValueOfHex(val[5]);
186 	return ColourDesired(r, g, b);
187 }
188 
189 }
190 
191 
ColourFromCode(int ch) const192 ColourDesired XPM::ColourFromCode(int ch) const {
193 	return colourCodeTable[ch];
194 }
195 
FillRun(Surface * surface,int code,int startX,int y,int x) const196 void XPM::FillRun(Surface *surface, int code, int startX, int y, int x) const {
197 	if ((code != codeTransparent) && (startX != x)) {
198 		const PRectangle rc = PRectangle::FromInts(startX, y, x, y + 1);
199 		surface->FillRectangle(rc, ColourFromCode(code));
200 	}
201 }
202 
XPM(const char * textForm)203 XPM::XPM(const char *textForm) {
204 	Init(textForm);
205 }
206 
XPM(const char * const * linesForm)207 XPM::XPM(const char *const *linesForm) {
208 	Init(linesForm);
209 }
210 
~XPM()211 XPM::~XPM() {
212 }
213 
Init(const char * textForm)214 void XPM::Init(const char *textForm) {
215 	// Test done is two parts to avoid possibility of overstepping the memory
216 	// if memcmp implemented strangely. Must be 4 bytes at least at destination.
217 	if ((0 == memcmp(textForm, "/* X", 4)) && (0 == memcmp(textForm, "/* XPM */", 9))) {
218 		// Build the lines form out of the text form
219 		std::vector<const char *> linesForm = LinesFormFromTextForm(textForm);
220 		if (!linesForm.empty()) {
221 			Init(&linesForm[0]);
222 		}
223 	} else {
224 		// It is really in line form
225 		Init(reinterpret_cast<const char * const *>(textForm));
226 	}
227 }
228 
Init(const char * const * linesForm)229 void XPM::Init(const char *const *linesForm) {
230 	height = 1;
231 	width = 1;
232 	nColours = 1;
233 	pixels.clear();
234 	codeTransparent = ' ';
235 	if (!linesForm)
236 		return;
237 
238 	std::fill(colourCodeTable, std::end(colourCodeTable), ColourDesired(0));
239 	const char *line0 = linesForm[0];
240 	width = atoi(line0);
241 	line0 = NextField(line0);
242 	height = atoi(line0);
243 	pixels.resize(width*height);
244 	line0 = NextField(line0);
245 	nColours = atoi(line0);
246 	line0 = NextField(line0);
247 	if (atoi(line0) != 1) {
248 		// Only one char per pixel is supported
249 		return;
250 	}
251 
252 	for (int c=0; c<nColours; c++) {
253 		const char *colourDef = linesForm[c+1];
254 		const char code = colourDef[0];
255 		colourDef += 4;
256 		ColourDesired colour(0xff, 0xff, 0xff);
257 		if (*colourDef == '#') {
258 			colour = ColourFromHex(colourDef+1);
259 		} else {
260 			codeTransparent = code;
261 		}
262 		colourCodeTable[static_cast<unsigned char>(code)] = colour;
263 	}
264 
265 	for (int y=0; y<height; y++) {
266 		const char *lform = linesForm[y+nColours+1];
267 		const size_t len = MeasureLength(lform);
268 		for (size_t x = 0; x<len; x++)
269 			pixels[y * width + x] = lform[x];
270 	}
271 }
272 
Draw(Surface * surface,const PRectangle & rc)273 void XPM::Draw(Surface *surface, const PRectangle &rc) {
274 	if (pixels.empty()) {
275 		return;
276 	}
277 	// Centre the pixmap
278 	const int startY = static_cast<int>(rc.top + (rc.Height() - height) / 2);
279 	const int startX = static_cast<int>(rc.left + (rc.Width() - width) / 2);
280 	for (int y=0; y<height; y++) {
281 		int prevCode = 0;
282 		int xStartRun = 0;
283 		for (int x=0; x<width; x++) {
284 			const int code = pixels[y * width + x];
285 			if (code != prevCode) {
286 				FillRun(surface, prevCode, startX + xStartRun, startY + y, startX + x);
287 				xStartRun = x;
288 				prevCode = code;
289 			}
290 		}
291 		FillRun(surface, prevCode, startX + xStartRun, startY + y, startX + width);
292 	}
293 }
294 
PixelAt(int x,int y,ColourDesired & colour,bool & transparent) const295 void XPM::PixelAt(int x, int y, ColourDesired &colour, bool &transparent) const {
296 	if (pixels.empty() || (x<0) || (x >= width) || (y<0) || (y >= height)) {
297 		colour = ColourDesired(0);
298 		transparent = true;
299 		return;
300 	}
301 	const int code = pixels[y * width + x];
302 	transparent = code == codeTransparent;
303 	if (transparent) {
304 		colour = ColourDesired(0);
305 	} else {
306 		colour = ColourFromCode(code);
307 	}
308 }
309 
LinesFormFromTextForm(const char * textForm)310 std::vector<const char *> XPM::LinesFormFromTextForm(const char *textForm) {
311 	// Build the lines form out of the text form
312 	std::vector<const char *> linesForm;
313 	int countQuotes = 0;
314 	int strings=1;
315 	int j=0;
316 	for (; countQuotes < (2*strings) && textForm[j] != '\0'; j++) {
317 		if (textForm[j] == '\"') {
318 			if (countQuotes == 0) {
319 				// First field: width, height, number of colors, chars per pixel
320 				const char *line0 = textForm + j + 1;
321 				// Skip width
322 				line0 = NextField(line0);
323 				// Add 1 line for each pixel of height
324 				strings += atoi(line0);
325 				line0 = NextField(line0);
326 				// Add 1 line for each colour
327 				strings += atoi(line0);
328 			}
329 			if (countQuotes / 2 >= strings) {
330 				break;	// Bad height or number of colors!
331 			}
332 			if ((countQuotes & 1) == 0) {
333 				linesForm.push_back(textForm + j + 1);
334 			}
335 			countQuotes++;
336 		}
337 	}
338 	if (textForm[j] == '\0' || countQuotes / 2 > strings) {
339 		// Malformed XPM! Height + number of colors too high or too low
340 		linesForm.clear();
341 	}
342 	return linesForm;
343 }
344 
RGBAImage(int width_,int height_,float scale_,const unsigned char * pixels_)345 RGBAImage::RGBAImage(int width_, int height_, float scale_, const unsigned char *pixels_) :
346 	height(height_), width(width_), scale(scale_) {
347 	if (pixels_) {
348 		pixelBytes.assign(pixels_, pixels_ + CountBytes());
349 	} else {
350 		pixelBytes.resize(CountBytes());
351 	}
352 }
353 
RGBAImage(const XPM & xpm)354 RGBAImage::RGBAImage(const XPM &xpm) {
355 	height = xpm.GetHeight();
356 	width = xpm.GetWidth();
357 	scale = 1;
358 	pixelBytes.resize(CountBytes());
359 	for (int y=0; y<height; y++) {
360 		for (int x=0; x<width; x++) {
361 			ColourDesired colour;
362 			bool transparent = false;
363 			xpm.PixelAt(x, y, colour, transparent);
364 			SetPixel(x, y, colour, transparent ? 0 : 255);
365 		}
366 	}
367 }
368 
~RGBAImage()369 RGBAImage::~RGBAImage() {
370 }
371 
CountBytes() const372 int RGBAImage::CountBytes() const {
373 	return width * height * 4;
374 }
375 
Pixels() const376 const unsigned char *RGBAImage::Pixels() const {
377 	return &pixelBytes[0];
378 }
379 
SetPixel(int x,int y,ColourDesired colour,int alpha)380 void RGBAImage::SetPixel(int x, int y, ColourDesired colour, int alpha) {
381 	unsigned char *pixel = &pixelBytes[0] + (y*width+x) * 4;
382 	// RGBA
383 	pixel[0] = colour.GetRed();
384 	pixel[1] = colour.GetGreen();
385 	pixel[2] = colour.GetBlue();
386 	pixel[3] = static_cast<unsigned char>(alpha);
387 }
388 
RGBAImageSet()389 RGBAImageSet::RGBAImageSet() : height(-1), width(-1) {
390 }
391 
~RGBAImageSet()392 RGBAImageSet::~RGBAImageSet() {
393 	Clear();
394 }
395 
396 /// Remove all images.
Clear()397 void RGBAImageSet::Clear() {
398 	images.clear();
399 	height = -1;
400 	width = -1;
401 }
402 
403 /// Add an image.
Add(int ident,RGBAImage * image)404 void RGBAImageSet::Add(int ident, RGBAImage *image) {
405 	ImageMap::iterator it=images.find(ident);
406 	if (it == images.end()) {
407 		images[ident] = std::unique_ptr<RGBAImage>(image);
408 	} else {
409 		it->second.reset(image);
410 	}
411 	height = -1;
412 	width = -1;
413 }
414 
415 /// Get image by id.
Get(int ident)416 RGBAImage *RGBAImageSet::Get(int ident) {
417 	ImageMap::iterator it = images.find(ident);
418 	if (it != images.end()) {
419 		return it->second.get();
420 	}
421 	return nullptr;
422 }
423 
424 /// Give the largest height of the set.
GetHeight() const425 int RGBAImageSet::GetHeight() const {
426 	if (height < 0) {
427 		for (const std::pair<const int, std::unique_ptr<RGBAImage>> &image : images) {
428 			if (height < image.second->GetHeight()) {
429 				height = image.second->GetHeight();
430 			}
431 		}
432 	}
433 	return (height > 0) ? height : 0;
434 }
435 
436 /// Give the largest width of the set.
GetWidth() const437 int RGBAImageSet::GetWidth() const {
438 	if (width < 0) {
439 		for (const std::pair<const int, std::unique_ptr<RGBAImage>> &image : images) {
440 			if (width < image.second->GetWidth()) {
441 				width = image.second->GetWidth();
442 			}
443 		}
444 	}
445 	return (width > 0) ? width : 0;
446 }
447 
448 #endif
449