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