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(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 	Clear();
65 }
66 
Init(const char * textForm)67 void XPM::Init(const char *textForm) {
68 	Clear();
69 	// Test done is two parts to avoid possibility of overstepping the memory
70 	// if memcmp implemented strangely. Must be 4 bytes at least at destination.
71 	if ((0 == memcmp(textForm, "/* X", 4)) && (0 == memcmp(textForm, "/* XPM */", 9))) {
72 		// Build the lines form out of the text form
73 		std::vector<const char *> linesForm = LinesFormFromTextForm(textForm);
74 		if (!linesForm.empty()) {
75 			Init(&linesForm[0]);
76 		}
77 	} else {
78 		// It is really in line form
79 		Init(reinterpret_cast<const char * const *>(textForm));
80 	}
81 }
82 
Init(const char * const * linesForm)83 void XPM::Init(const char *const *linesForm) {
84 	Clear();
85 	height = 1;
86 	width = 1;
87 	nColours = 1;
88 	pixels.clear();
89 	codeTransparent = ' ';
90 	if (!linesForm)
91 		return;
92 
93 	std::fill(colourCodeTable, colourCodeTable+256, 0);
94 	const char *line0 = linesForm[0];
95 	width = atoi(line0);
96 	line0 = NextField(line0);
97 	height = atoi(line0);
98 	pixels.resize(width*height);
99 	line0 = NextField(line0);
100 	nColours = atoi(line0);
101 	line0 = NextField(line0);
102 	if (atoi(line0) != 1) {
103 		// Only one char per pixel is supported
104 		return;
105 	}
106 
107 	for (int c=0; c<nColours; c++) {
108 		const char *colourDef = linesForm[c+1];
109 		int code = static_cast<unsigned char>(colourDef[0]);
110 		colourDef += 4;
111 		ColourDesired colour(0xff, 0xff, 0xff);
112 		if (*colourDef == '#') {
113 			colour.Set(colourDef);
114 		} else {
115 			codeTransparent = code;
116 		}
117 		colourCodeTable[code] = colour;
118 	}
119 
120 	for (int y=0; y<height; y++) {
121 		const char *lform = linesForm[y+nColours+1];
122 		size_t len = MeasureLength(lform);
123 		for (size_t x = 0; x<len; x++)
124 			pixels[y * width + x] = static_cast<unsigned char>(lform[x]);
125 	}
126 }
127 
Clear()128 void XPM::Clear() {
129 }
130 
Draw(Surface * surface,PRectangle & rc)131 void XPM::Draw(Surface *surface, PRectangle &rc) {
132 	if (pixels.empty()) {
133 		return;
134 	}
135 	// Centre the pixmap
136 	int startY = rc.top + (rc.Height() - height) / 2;
137 	int startX = rc.left + (rc.Width() - width) / 2;
138 	for (int y=0; y<height; y++) {
139 		int prevCode = 0;
140 		int xStartRun = 0;
141 		for (int x=0; x<width; x++) {
142 			int code = pixels[y * width + x];
143 			if (code != prevCode) {
144 				FillRun(surface, prevCode, startX + xStartRun, startY + y, startX + x);
145 				xStartRun = x;
146 				prevCode = code;
147 			}
148 		}
149 		FillRun(surface, prevCode, startX + xStartRun, startY + y, startX + width);
150 	}
151 }
152 
PixelAt(int x,int y,ColourDesired & colour,bool & transparent) const153 void XPM::PixelAt(int x, int y, ColourDesired &colour, bool &transparent) const {
154 	if (pixels.empty() || (x<0) || (x >= width) || (y<0) || (y >= height)) {
155 		colour = 0;
156 		transparent = true;
157 		return;
158 	}
159 	int code = pixels[y * width + x];
160 	transparent = code == codeTransparent;
161 	if (transparent) {
162 		colour = 0;
163 	} else {
164 		colour = ColourFromCode(code).AsLong();
165 	}
166 }
167 
LinesFormFromTextForm(const char * textForm)168 std::vector<const char *> XPM::LinesFormFromTextForm(const char *textForm) {
169 	// Build the lines form out of the text form
170 	std::vector<const char *> linesForm;
171 	int countQuotes = 0;
172 	int strings=1;
173 	int j=0;
174 	for (; countQuotes < (2*strings) && textForm[j] != '\0'; j++) {
175 		if (textForm[j] == '\"') {
176 			if (countQuotes == 0) {
177 				// First field: width, height, number of colors, chars per pixel
178 				const char *line0 = textForm + j + 1;
179 				// Skip width
180 				line0 = NextField(line0);
181 				// Add 1 line for each pixel of height
182 				strings += atoi(line0);
183 				line0 = NextField(line0);
184 				// Add 1 line for each colour
185 				strings += atoi(line0);
186 			}
187 			if (countQuotes / 2 >= strings) {
188 				break;	// Bad height or number of colors!
189 			}
190 			if ((countQuotes & 1) == 0) {
191 				linesForm.push_back(textForm + j + 1);
192 			}
193 			countQuotes++;
194 		}
195 	}
196 	if (textForm[j] == '\0' || countQuotes / 2 > strings) {
197 		// Malformed XPM! Height + number of colors too high or too low
198 		linesForm.clear();
199 	}
200 	return linesForm;
201 }
202 
RGBAImage(int width_,int height_,float scale_,const unsigned char * pixels_)203 RGBAImage::RGBAImage(int width_, int height_, float scale_, const unsigned char *pixels_) :
204 	height(height_), width(width_), scale(scale_) {
205 	if (pixels_) {
206 		pixelBytes.assign(pixels_, pixels_ + CountBytes());
207 	} else {
208 		pixelBytes.resize(CountBytes());
209 	}
210 }
211 
RGBAImage(const XPM & xpm)212 RGBAImage::RGBAImage(const XPM &xpm) {
213 	height = xpm.GetHeight();
214 	width = xpm.GetWidth();
215 	scale = 1;
216 	pixelBytes.resize(CountBytes());
217 	for (int y=0; y<height; y++) {
218 		for (int x=0; x<width; x++) {
219 			ColourDesired colour;
220 			bool transparent = false;
221 			xpm.PixelAt(x, y, colour, transparent);
222 			SetPixel(x, y, colour, transparent ? 0 : 255);
223 		}
224 	}
225 }
226 
~RGBAImage()227 RGBAImage::~RGBAImage() {
228 }
229 
CountBytes() const230 int RGBAImage::CountBytes() const {
231 	return width * height * 4;
232 }
233 
Pixels() const234 const unsigned char *RGBAImage::Pixels() const {
235 	return &pixelBytes[0];
236 }
237 
SetPixel(int x,int y,ColourDesired colour,int alpha)238 void RGBAImage::SetPixel(int x, int y, ColourDesired colour, int alpha) {
239 	unsigned char *pixel = &pixelBytes[0] + (y*width+x) * 4;
240 	// RGBA
241 	pixel[0] = static_cast<unsigned char>(colour.GetRed());
242 	pixel[1] = static_cast<unsigned char>(colour.GetGreen());
243 	pixel[2] = static_cast<unsigned char>(colour.GetBlue());
244 	pixel[3] = static_cast<unsigned char>(alpha);
245 }
246 
RGBAImageSet()247 RGBAImageSet::RGBAImageSet() : height(-1), width(-1){
248 }
249 
~RGBAImageSet()250 RGBAImageSet::~RGBAImageSet() {
251 	Clear();
252 }
253 
254 /// Remove all images.
Clear()255 void RGBAImageSet::Clear() {
256 	for (ImageMap::iterator it=images.begin(); it != images.end(); ++it) {
257 		delete it->second;
258 		it->second = 0;
259 	}
260 	images.clear();
261 	height = -1;
262 	width = -1;
263 }
264 
265 /// Add an image.
Add(int ident,RGBAImage * image)266 void RGBAImageSet::Add(int ident, RGBAImage *image) {
267 	ImageMap::iterator it=images.find(ident);
268 	if (it == images.end()) {
269 		images[ident] = image;
270 	} else {
271 		delete it->second;
272 		it->second = image;
273 	}
274 	height = -1;
275 	width = -1;
276 }
277 
278 /// Get image by id.
Get(int ident)279 RGBAImage *RGBAImageSet::Get(int ident) {
280 	ImageMap::iterator it = images.find(ident);
281 	if (it != images.end()) {
282 		return it->second;
283 	}
284 	return NULL;
285 }
286 
287 /// Give the largest height of the set.
GetHeight() const288 int RGBAImageSet::GetHeight() const {
289 	if (height < 0) {
290 		for (ImageMap::const_iterator it=images.begin(); it != images.end(); ++it) {
291 			if (height < it->second->GetHeight()) {
292 				height = it->second->GetHeight();
293 			}
294 		}
295 	}
296 	return (height > 0) ? height : 0;
297 }
298 
299 /// Give the largest width of the set.
GetWidth() const300 int RGBAImageSet::GetWidth() const {
301 	if (width < 0) {
302 		for (ImageMap::const_iterator it=images.begin(); it != images.end(); ++it) {
303 			if (width < it->second->GetWidth()) {
304 				width = it->second->GetWidth();
305 			}
306 		}
307 	}
308 	return (width > 0) ? width : 0;
309 }
310