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