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 <string_view>
13 #include <vector>
14 #include <map>
15 #include <algorithm>
16 #include <iterator>
17 #include <memory>
18 
19 #include "Platform.h"
20 
21 #include "XPM.h"
22 
23 using namespace Scintilla;
24 
25 namespace {
26 
NextField(const char * s)27 const char *NextField(const char *s) noexcept {
28 	// In case there are leading spaces in the string
29 	while (*s == ' ') {
30 		s++;
31 	}
32 	while (*s && *s != ' ') {
33 		s++;
34 	}
35 	while (*s == ' ') {
36 		s++;
37 	}
38 	return s;
39 }
40 
41 // Data lines in XPM can be terminated either with NUL or "
MeasureLength(const char * s)42 size_t MeasureLength(const char *s) noexcept {
43 	size_t i = 0;
44 	while (s[i] && (s[i] != '\"'))
45 		i++;
46 	return i;
47 }
48 
ValueOfHex(const char ch)49 unsigned int ValueOfHex(const char ch) noexcept {
50 	if (ch >= '0' && ch <= '9')
51 		return ch - '0';
52 	else if (ch >= 'A' && ch <= 'F')
53 		return ch - 'A' + 10;
54 	else if (ch >= 'a' && ch <= 'f')
55 		return ch - 'a' + 10;
56 	else
57 		return 0;
58 }
59 
ColourFromHex(const char * val)60 ColourDesired ColourFromHex(const char *val) noexcept {
61 	const unsigned int r = ValueOfHex(val[0]) * 16 + ValueOfHex(val[1]);
62 	const unsigned int g = ValueOfHex(val[2]) * 16 + ValueOfHex(val[3]);
63 	const unsigned int b = ValueOfHex(val[4]) * 16 + ValueOfHex(val[5]);
64 	return ColourDesired(r, g, b);
65 }
66 
67 }
68 
69 
ColourFromCode(int ch) const70 ColourDesired XPM::ColourFromCode(int ch) const noexcept {
71 	return colourCodeTable[ch];
72 }
73 
FillRun(Surface * surface,int code,int startX,int y,int x) const74 void XPM::FillRun(Surface *surface, int code, int startX, int y, int x) const {
75 	if ((code != codeTransparent) && (startX != x)) {
76 		const PRectangle rc = PRectangle::FromInts(startX, y, x, y + 1);
77 		surface->FillRectangle(rc, ColourFromCode(code));
78 	}
79 }
80 
XPM(const char * textForm)81 XPM::XPM(const char *textForm) {
82 	Init(textForm);
83 }
84 
XPM(const char * const * linesForm)85 XPM::XPM(const char *const *linesForm) {
86 	Init(linesForm);
87 }
88 
~XPM()89 XPM::~XPM() {
90 }
91 
Init(const char * textForm)92 void XPM::Init(const char *textForm) {
93 	// Test done is two parts to avoid possibility of overstepping the memory
94 	// if memcmp implemented strangely. Must be 4 bytes at least at destination.
95 	if ((0 == memcmp(textForm, "/* X", 4)) && (0 == memcmp(textForm, "/* XPM */", 9))) {
96 		// Build the lines form out of the text form
97 		std::vector<const char *> linesForm = LinesFormFromTextForm(textForm);
98 		if (!linesForm.empty()) {
99 			Init(&linesForm[0]);
100 		}
101 	} else {
102 		// It is really in line form
103 		Init(reinterpret_cast<const char * const *>(textForm));
104 	}
105 }
106 
Init(const char * const * linesForm)107 void XPM::Init(const char *const *linesForm) {
108 	height = 1;
109 	width = 1;
110 	nColours = 1;
111 	pixels.clear();
112 	codeTransparent = ' ';
113 	if (!linesForm)
114 		return;
115 
116 	std::fill(colourCodeTable, std::end(colourCodeTable), ColourDesired(0));
117 	const char *line0 = linesForm[0];
118 	width = atoi(line0);
119 	line0 = NextField(line0);
120 	height = atoi(line0);
121 	pixels.resize(width*height);
122 	line0 = NextField(line0);
123 	nColours = atoi(line0);
124 	line0 = NextField(line0);
125 	if (atoi(line0) != 1) {
126 		// Only one char per pixel is supported
127 		return;
128 	}
129 
130 	for (int c=0; c<nColours; c++) {
131 		const char *colourDef = linesForm[c+1];
132 		const char code = colourDef[0];
133 		colourDef += 4;
134 		ColourDesired colour(0xff, 0xff, 0xff);
135 		if (*colourDef == '#') {
136 			colour = ColourFromHex(colourDef+1);
137 		} else {
138 			codeTransparent = code;
139 		}
140 		colourCodeTable[static_cast<unsigned char>(code)] = colour;
141 	}
142 
143 	for (int y=0; y<height; y++) {
144 		const char *lform = linesForm[y+nColours+1];
145 		const size_t len = MeasureLength(lform);
146 		for (size_t x = 0; x<len; x++)
147 			pixels[y * width + x] = lform[x];
148 	}
149 }
150 
Draw(Surface * surface,const PRectangle & rc)151 void XPM::Draw(Surface *surface, const PRectangle &rc) {
152 	if (pixels.empty()) {
153 		return;
154 	}
155 	// Centre the pixmap
156 	const int startY = static_cast<int>(rc.top + (rc.Height() - height) / 2);
157 	const int startX = static_cast<int>(rc.left + (rc.Width() - width) / 2);
158 	for (int y=0; y<height; y++) {
159 		int prevCode = 0;
160 		int xStartRun = 0;
161 		for (int x=0; x<width; x++) {
162 			const int code = pixels[y * width + x];
163 			if (code != prevCode) {
164 				FillRun(surface, prevCode, startX + xStartRun, startY + y, startX + x);
165 				xStartRun = x;
166 				prevCode = code;
167 			}
168 		}
169 		FillRun(surface, prevCode, startX + xStartRun, startY + y, startX + width);
170 	}
171 }
172 
PixelAt(int x,int y,ColourDesired & colour,bool & transparent) const173 void XPM::PixelAt(int x, int y, ColourDesired &colour, bool &transparent) const noexcept {
174 	if (pixels.empty() || (x<0) || (x >= width) || (y<0) || (y >= height)) {
175 		colour = ColourDesired(0);
176 		transparent = true;
177 		return;
178 	}
179 	const int code = pixels[y * width + x];
180 	transparent = code == codeTransparent;
181 	if (transparent) {
182 		colour = ColourDesired(0);
183 	} else {
184 		colour = ColourFromCode(code);
185 	}
186 }
187 
LinesFormFromTextForm(const char * textForm)188 std::vector<const char *> XPM::LinesFormFromTextForm(const char *textForm) {
189 	// Build the lines form out of the text form
190 	std::vector<const char *> linesForm;
191 	int countQuotes = 0;
192 	int strings=1;
193 	int j=0;
194 	for (; countQuotes < (2*strings) && textForm[j] != '\0'; j++) {
195 		if (textForm[j] == '\"') {
196 			if (countQuotes == 0) {
197 				// First field: width, height, number of colours, chars per pixel
198 				const char *line0 = textForm + j + 1;
199 				// Skip width
200 				line0 = NextField(line0);
201 				// Add 1 line for each pixel of height
202 				strings += atoi(line0);
203 				line0 = NextField(line0);
204 				// Add 1 line for each colour
205 				strings += atoi(line0);
206 			}
207 			if (countQuotes / 2 >= strings) {
208 				break;	// Bad height or number of colours!
209 			}
210 			if ((countQuotes & 1) == 0) {
211 				linesForm.push_back(textForm + j + 1);
212 			}
213 			countQuotes++;
214 		}
215 	}
216 	if (textForm[j] == '\0' || countQuotes / 2 > strings) {
217 		// Malformed XPM! Height + number of colours too high or too low
218 		linesForm.clear();
219 	}
220 	return linesForm;
221 }
222 
RGBAImage(int width_,int height_,float scale_,const unsigned char * pixels_)223 RGBAImage::RGBAImage(int width_, int height_, float scale_, const unsigned char *pixels_) :
224 	height(height_), width(width_), scale(scale_) {
225 	if (pixels_) {
226 		pixelBytes.assign(pixels_, pixels_ + CountBytes());
227 	} else {
228 		pixelBytes.resize(CountBytes());
229 	}
230 }
231 
RGBAImage(const XPM & xpm)232 RGBAImage::RGBAImage(const XPM &xpm) {
233 	height = xpm.GetHeight();
234 	width = xpm.GetWidth();
235 	scale = 1;
236 	pixelBytes.resize(CountBytes());
237 	for (int y=0; y<height; y++) {
238 		for (int x=0; x<width; x++) {
239 			ColourDesired colour;
240 			bool transparent = false;
241 			xpm.PixelAt(x, y, colour, transparent);
242 			SetPixel(x, y, colour, transparent ? 0 : 255);
243 		}
244 	}
245 }
246 
~RGBAImage()247 RGBAImage::~RGBAImage() {
248 }
249 
CountBytes() const250 int RGBAImage::CountBytes() const noexcept {
251 	return width * height * 4;
252 }
253 
Pixels() const254 const unsigned char *RGBAImage::Pixels() const noexcept {
255 	return &pixelBytes[0];
256 }
257 
SetPixel(int x,int y,ColourDesired colour,int alpha)258 void RGBAImage::SetPixel(int x, int y, ColourDesired colour, int alpha) noexcept {
259 	unsigned char *pixel = &pixelBytes[0] + (y*width+x) * 4;
260 	// RGBA
261 	pixel[0] = colour.GetRed();
262 	pixel[1] = colour.GetGreen();
263 	pixel[2] = colour.GetBlue();
264 	pixel[3] = static_cast<unsigned char>(alpha);
265 }
266 
267 // Transform a block of pixels from RGBA to BGRA with premultiplied alpha.
268 // Used for DrawRGBAImage on some platforms.
BGRAFromRGBA(unsigned char * pixelsBGRA,const unsigned char * pixelsRGBA,size_t count)269 void RGBAImage::BGRAFromRGBA(unsigned char *pixelsBGRA, const unsigned char *pixelsRGBA, size_t count) noexcept {
270 	for (size_t i = 0; i < count; i++) {
271 		const unsigned char alpha = pixelsRGBA[3];
272 		// Input is RGBA, output is BGRA with premultiplied alpha
273 		pixelsBGRA[2] = pixelsRGBA[0] * alpha / 255;
274 		pixelsBGRA[1] = pixelsRGBA[1] * alpha / 255;
275 		pixelsBGRA[0] = pixelsRGBA[2] * alpha / 255;
276 		pixelsBGRA[3] = alpha;
277 		pixelsRGBA += bytesPerPixel;
278 		pixelsBGRA += bytesPerPixel;
279 	}
280 }
281 
RGBAImageSet()282 RGBAImageSet::RGBAImageSet() : height(-1), width(-1) {
283 }
284 
~RGBAImageSet()285 RGBAImageSet::~RGBAImageSet() {
286 	Clear();
287 }
288 
289 /// Remove all images.
Clear()290 void RGBAImageSet::Clear() noexcept {
291 	images.clear();
292 	height = -1;
293 	width = -1;
294 }
295 
296 /// Add an image.
Add(int ident,RGBAImage * image)297 void RGBAImageSet::Add(int ident, RGBAImage *image) {
298 	ImageMap::iterator it=images.find(ident);
299 	if (it == images.end()) {
300 		images[ident] = std::unique_ptr<RGBAImage>(image);
301 	} else {
302 		it->second.reset(image);
303 	}
304 	height = -1;
305 	width = -1;
306 }
307 
308 /// Get image by id.
Get(int ident)309 RGBAImage *RGBAImageSet::Get(int ident) {
310 	ImageMap::iterator it = images.find(ident);
311 	if (it != images.end()) {
312 		return it->second.get();
313 	}
314 	return nullptr;
315 }
316 
317 /// Give the largest height of the set.
GetHeight() const318 int RGBAImageSet::GetHeight() const {
319 	if (height < 0) {
320 		for (const std::pair<const int, std::unique_ptr<RGBAImage>> &image : images) {
321 			if (height < image.second->GetHeight()) {
322 				height = image.second->GetHeight();
323 			}
324 		}
325 	}
326 	return (height > 0) ? height : 0;
327 }
328 
329 /// Give the largest width of the set.
GetWidth() const330 int RGBAImageSet::GetWidth() const {
331 	if (width < 0) {
332 		for (const std::pair<const int, std::unique_ptr<RGBAImage>> &image : images) {
333 			if (width < image.second->GetWidth()) {
334 				width = image.second->GetWidth();
335 			}
336 		}
337 	}
338 	return (width > 0) ? width : 0;
339 }
340