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 <[email protected]> 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 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 " 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 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 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 70 ColourDesired XPM::ColourFromCode(int ch) const noexcept { 71 return colourCodeTable[ch]; 72 } 73 74 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 81 XPM::XPM(const char *textForm) { 82 Init(textForm); 83 } 84 85 XPM::XPM(const char *const *linesForm) { 86 Init(linesForm); 87 } 88 89 XPM::~XPM() { 90 } 91 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 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 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 173 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 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 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 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 247 RGBAImage::~RGBAImage() { 248 } 249 250 int RGBAImage::CountBytes() const noexcept { 251 return width * height * 4; 252 } 253 254 const unsigned char *RGBAImage::Pixels() const noexcept { 255 return &pixelBytes[0]; 256 } 257 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. 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 282 RGBAImageSet::RGBAImageSet() : height(-1), width(-1) { 283 } 284 285 RGBAImageSet::~RGBAImageSet() { 286 Clear(); 287 } 288 289 /// Remove all images. 290 void RGBAImageSet::Clear() noexcept { 291 images.clear(); 292 height = -1; 293 width = -1; 294 } 295 296 /// Add an 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. 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. 318 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. 330 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