1 // Scintilla source code edit control
3 ** Define a class that holds data in the X Pixmap (XPM) format.
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.
13 #include <string_view>
21 #include "ScintillaTypes.h"
23 #include "Debugging.h"
29 using namespace Scintilla
;
30 using namespace Scintilla::Internal
;
34 const char *NextField(const char *s
) noexcept
{
35 // In case there are leading spaces in the string
39 while (*s
&& *s
!= ' ') {
48 // Data lines in XPM can be terminated either with NUL or "
49 size_t MeasureLength(const char *s
) noexcept
{
51 while (s
[i
] && (s
[i
] != '\"'))
56 unsigned int ValueOfHex(const char ch
) noexcept
{
57 if (ch
>= '0' && ch
<= '9')
59 else if (ch
>= 'A' && ch
<= 'F')
61 else if (ch
>= 'a' && ch
<= 'f')
67 ColourRGBA
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
);
77 ColourRGBA
XPM::ColourFromCode(int ch
) const noexcept
{
78 return colourCodeTable
[ch
];
81 void 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
));
88 XPM::XPM(const char *textForm
) {
92 XPM::XPM(const char *const *linesForm
) {
96 void 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());
106 // It is really in line form
107 Init(reinterpret_cast<const char * const *>(textForm
));
111 void XPM::Init(const char *const *linesForm
) {
116 codeTransparent
= ' ';
120 std::fill(colourCodeTable
, std::end(colourCodeTable
), black
);
121 const char *line0
= linesForm
[0];
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
134 for (int c
=0; c
<nColours
; c
++) {
135 const char *colourDef
= linesForm
[c
+1];
136 const char code
= colourDef
[0];
138 ColourRGBA
colour(0, 0, 0, 0);
139 if (*colourDef
== '#') {
140 colour
= ColourFromHex(colourDef
+1);
142 codeTransparent
= code
;
144 colourCodeTable
[static_cast<unsigned char>(code
)] = colour
;
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
];
155 void XPM::Draw(Surface
*surface
, const PRectangle
&rc
) {
156 if (pixels
.empty()) {
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
++) {
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
);
173 FillRun(surface
, prevCode
, startX
+ xStartRun
, startY
+ y
, startX
+ width
);
177 ColourRGBA
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);
182 const int code
= pixels
[y
* width
+ x
];
183 return ColourFromCode(code
);
186 std::vector
<const char *> XPM::LinesFormFromTextForm(const char *textForm
) {
187 // Build the lines form out of the text form
188 std::vector
<const char *> linesForm
;
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;
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
);
205 if (countQuotes
/ 2 >= strings
) {
206 break; // Bad height or number of colours!
208 if ((countQuotes
& 1) == 0) {
209 linesForm
.push_back(textForm
+ j
+ 1);
214 if (textForm
[j
] == '\0' || countQuotes
/ 2 > strings
) {
215 // Malformed XPM! Height + number of colours too high or too low
221 RGBAImage::RGBAImage(int width_
, int height_
, float scale_
, const unsigned char *pixels_
) :
222 height(height_
), width(width_
), scale(scale_
) {
224 pixelBytes
.assign(pixels_
, pixels_
+ CountBytes());
226 pixelBytes
.resize(CountBytes());
230 RGBAImage::RGBAImage(const XPM
&xpm
) {
231 height
= xpm
.GetHeight();
232 width
= xpm
.GetWidth();
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
));
242 float RGBAImage::GetScaledHeight() const noexcept
{
243 return static_cast<float>(height
) / scale
;
246 float RGBAImage::GetScaledWidth() const noexcept
{
247 return static_cast<float>(width
) / scale
;
250 int RGBAImage::CountBytes() const noexcept
{
251 return width
* height
* 4;
254 const unsigned char *RGBAImage::Pixels() const noexcept
{
255 return pixelBytes
.data();
258 void RGBAImage::SetPixel(int x
, int y
, ColourRGBA colour
) noexcept
{
259 unsigned char *pixel
= pixelBytes
.data() + (y
* width
+ x
) * 4;
261 pixel
[0] = colour
.GetRed();
262 pixel
[1] = colour
.GetGreen();
263 pixel
[2] = colour
.GetBlue();
264 pixel
[3] = colour
.GetAlpha();
269 constexpr unsigned char AlphaMultiplied(unsigned char value
, unsigned char alpha
) noexcept
{
270 return (value
* alpha
/ UCHAR_MAX
) & 0xffU
;
275 // Transform a block of pixels from RGBA to BGRA with premultiplied alpha.
276 // Used for DrawRGBAImage on some platforms.
277 void RGBAImage::BGRAFromRGBA(unsigned char *pixelsBGRA
, const unsigned char *pixelsRGBA
, size_t count
) noexcept
{
278 for (size_t i
= 0; i
< count
; i
++) {
279 const unsigned char alpha
= pixelsRGBA
[3];
280 // Input is RGBA, output is BGRA with premultiplied alpha
281 pixelsBGRA
[2] = AlphaMultiplied(pixelsRGBA
[0], alpha
);
282 pixelsBGRA
[1] = AlphaMultiplied(pixelsRGBA
[1], alpha
);
283 pixelsBGRA
[0] = AlphaMultiplied(pixelsRGBA
[2], alpha
);
284 pixelsBGRA
[3] = alpha
;
285 pixelsRGBA
+= bytesPerPixel
;
286 pixelsBGRA
+= bytesPerPixel
;
290 RGBAImageSet::RGBAImageSet() : height(-1), width(-1) {
293 /// Remove all images.
294 void RGBAImageSet::Clear() noexcept
{
301 void RGBAImageSet::AddImage(int ident
, std::unique_ptr
<RGBAImage
> image
) {
302 images
[ident
] = std::move(image
);
308 RGBAImage
*RGBAImageSet::Get(int ident
) {
309 ImageMap::iterator it
= images
.find(ident
);
310 if (it
!= images
.end()) {
311 return it
->second
.get();
316 /// Give the largest height of the set.
317 int RGBAImageSet::GetHeight() const noexcept
{
319 for (const std::pair
<const int, std::unique_ptr
<RGBAImage
>> &image
: images
) {
320 if (height
< image
.second
->GetHeight()) {
321 height
= image
.second
->GetHeight();
325 return (height
> 0) ? height
: 0;
328 /// Give the largest width of the set.
329 int RGBAImageSet::GetWidth() const noexcept
{
331 for (const std::pair
<const int, std::unique_ptr
<RGBAImage
>> &image
: images
) {
332 if (width
< image
.second
->GetWidth()) {
333 width
= image
.second
->GetWidth();
337 return (width
> 0) ? width
: 0;