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