1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include <cmath>
19
20#include "Rect.hxx"
21#include "FrameBuffer.hxx"
22#include "FBSurface.hxx"
23
24#ifdef GUI_SUPPORT
25 #include "Font.hxx"
26#endif
27
28// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
29FBSurface::FBSurface()
30 : myPixels(nullptr),
31 myPitch(0)
32{
33 // NOTE: myPixels and myPitch MUST be set in child classes that inherit
34 // from this class
35
36 // Set default attributes
37 myAttributes.smoothing = false;
38 myAttributes.blending = false;
39 myAttributes.blendalpha = 100;
40}
41
42// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
43void FBSurface::readPixels(uInt8* buffer, uInt32 pitch, const Common::Rect& rect) const
44{
45 uInt8* src = reinterpret_cast<uInt8*>(myPixels + rect.y() * myPitch + rect.x());
46
47 if(rect.empty())
48 std::copy_n(src, width() * height() * 4, buffer);
49 else
50 {
51 uInt32 w = std::min(rect.w(), width());
52 uInt32 h = std::min(rect.h(), height());
53
54 // Copy 'height' lines of width 'pitch' (in bytes for both)
55 uInt8* dst = buffer;
56 while(h--)
57 {
58 std::copy_n(src, w * 4, dst);
59 src += myPitch * 4;
60 dst += pitch * 4;
61 }
62 }
63}
64
65// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
66void FBSurface::pixel(uInt32 x, uInt32 y, ColorId color)
67{
68 // Note: checkbounds() must be done in calling method
69 uInt32* buffer = myPixels + y * myPitch + x;
70
71 *buffer = uInt32(myPalette[color]);
72}
73
74// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
75void FBSurface::line(uInt32 x, uInt32 y, uInt32 x2, uInt32 y2, ColorId color)
76{
77 if(!checkBounds(x, y) || !checkBounds(x2, y2))
78 return;
79
80 // draw line using Bresenham algorithm
81 Int32 dx = (x2 - x);
82 Int32 dy = (y2 - y);
83
84 if(abs(dx) >= abs(dy))
85 {
86 // x is major axis
87 if(dx < 0)
88 {
89 uInt32 tx = x; x = x2; x2 = tx;
90 uInt32 ty = y; y = y2; y2 = ty;
91 dx = -dx;
92 dy = -dy;
93 }
94 Int32 yd = dy > 0 ? 1 : -1;
95 dy = abs(dy);
96 Int32 err = dx / 2;
97 // now draw the line
98 for(; x <= x2; ++x)
99 {
100 pixel(x, y, color);
101 err -= dy;
102 if(err < 0)
103 {
104 err += dx;
105 y += yd;
106 }
107 }
108 }
109 else
110 {
111 // y is major axis
112 if(dy < 0)
113 {
114 uInt32 tx = x; x = x2; x2 = tx;
115 uInt32 ty = y; y = y2; y2 = ty;
116 dx = -dx;
117 dy = -dy;
118 }
119 Int32 xd = dx > 0 ? 1 : -1;
120 dx = abs(dx);
121 Int32 err = dy / 2;
122 // now draw the line
123 for(; y <= y2; ++y)
124 {
125 pixel(x, y, color);
126 err -= dx;
127 if(err < 0)
128 {
129 err += dy;
130 x += xd;
131 }
132 }
133 }
134}
135
136// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
137void FBSurface::hLine(uInt32 x, uInt32 y, uInt32 x2, ColorId color)
138{
139 if(!checkBounds(x, y) || !checkBounds(x2, 2))
140 return;
141
142 uInt32* buffer = myPixels + y * myPitch + x;
143 while(x++ <= x2)
144 *buffer++ = uInt32(myPalette[color]);
145}
146
147// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
148void FBSurface::vLine(uInt32 x, uInt32 y, uInt32 y2, ColorId color)
149{
150 if(!checkBounds(x, y) || !checkBounds(x, y2))
151 return;
152
153 uInt32* buffer = static_cast<uInt32*>(myPixels + y * myPitch + x);
154 while(y++ <= y2)
155 {
156 *buffer = uInt32(myPalette[color]);
157 buffer += myPitch;
158 }
159}
160
161// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
162void FBSurface::fillRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h, ColorId color)
163{
164 while(h--)
165 hLine(x, y+h, x+w-1, color);
166}
167
168// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
169void FBSurface::drawChar(const GUI::Font& font, uInt8 chr,
170 uInt32 tx, uInt32 ty, ColorId color, ColorId shadowColor)
171{
172#ifdef GUI_SUPPORT
173 if(shadowColor != kNone)
174 {
175 drawChar(font, chr, tx + 1, ty + 0, shadowColor);
176 drawChar(font, chr, tx + 0, ty + 1, shadowColor);
177 drawChar(font, chr, tx + 1, ty + 1, shadowColor);
178 }
179
180 const FontDesc& desc = font.desc();
181
182 // If this character is not included in the font, use the default char.
183 if(chr < desc.firstchar || chr >= desc.firstchar + desc.size)
184 {
185 if (chr == ' ') return;
186 chr = desc.defaultchar;
187 }
188 chr -= desc.firstchar;
189
190 // Get the bounding box of the character
191 int bbw, bbh, bbx, bby;
192 if(!desc.bbx)
193 {
194 bbw = desc.fbbw;
195 bbh = desc.fbbh;
196 bbx = desc.fbbx;
197 bby = desc.fbby;
198 }
199 else
200 {
201 bbw = desc.bbx[chr].w;
202 bbh = desc.bbx[chr].h;
203 bbx = desc.bbx[chr].x;
204 bby = desc.bbx[chr].y;
205 }
206
207 uInt32 cx = tx + bbx;
208 uInt32 cy = ty + desc.ascent - bby - bbh;
209
210 if(!checkBounds(cx , cy) || !checkBounds(cx + bbw - 1, cy + bbh - 1))
211 return;
212
213 const uInt16* tmp = desc.bits + (desc.offset ? desc.offset[chr] : (chr * desc.fbbh));
214 uInt32* buffer = myPixels + cy * myPitch + cx;
215
216 for(int y = 0; y < bbh; y++)
217 {
218 const uInt16 ptr = *tmp++;
219 uInt16 mask = 0x8000;
220
221 for(int x = 0; x < bbw; x++, mask >>= 1)
222 if(ptr & mask)
223 buffer[x] = uInt32(myPalette[color]);
224
225 buffer += myPitch;
226 }
227#endif
228}
229
230// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
231void FBSurface::drawBitmap(uInt32* bitmap, uInt32 tx, uInt32 ty,
232 ColorId color, uInt32 h)
233{
234 drawBitmap(bitmap, tx, ty, color, h, h);
235}
236
237// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
238void FBSurface::drawBitmap(uInt32* bitmap, uInt32 tx, uInt32 ty,
239 ColorId color, uInt32 w, uInt32 h)
240{
241 if(!checkBounds(tx, ty) || !checkBounds(tx + w - 1, ty + h - 1))
242 return;
243
244 uInt32* buffer = myPixels + ty * myPitch + tx;
245
246 for(uInt32 y = 0; y < h; ++y)
247 {
248 uInt32 mask = 1 << (w - 1);
249 for(uInt32 x = 0; x < w; ++x, mask >>= 1)
250 if(bitmap[y] & mask)
251 buffer[x] = uInt32(myPalette[color]);
252
253 buffer += myPitch;
254 }
255}
256
257// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
258void FBSurface::drawPixels(uInt32* data, uInt32 tx, uInt32 ty, uInt32 numpixels)
259{
260 if(!checkBounds(tx, ty) || !checkBounds(tx + numpixels - 1, ty))
261 return;
262
263 uInt32* buffer = myPixels + ty * myPitch + tx;
264
265 for(uInt32 i = 0; i < numpixels; ++i)
266 *buffer++ = data[i];
267}
268
269// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
270void FBSurface::box(uInt32 x, uInt32 y, uInt32 w, uInt32 h,
271 ColorId colorA, ColorId colorB)
272{
273 hLine(x + 1, y, x + w - 2, colorA);
274 hLine(x, y + 1, x + w - 1, colorA);
275 vLine(x, y + 1, y + h - 2, colorA);
276 vLine(x + 1, y, y + h - 1, colorA);
277
278 hLine(x + 1, y + h - 2, x + w - 1, colorB);
279 hLine(x + 1, y + h - 1, x + w - 2, colorB);
280 vLine(x + w - 1, y + 1, y + h - 2, colorB);
281 vLine(x + w - 2, y + 1, y + h - 1, colorB);
282}
283
284// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
285void FBSurface::frameRect(uInt32 x, uInt32 y, uInt32 w, uInt32 h,
286 ColorId color, FrameStyle style)
287{
288 switch(style)
289 {
290 case FrameStyle::Solid:
291 hLine(x, y, x + w - 1, color);
292 hLine(x, y + h - 1, x + w - 1, color);
293 vLine(x, y, y + h - 1, color);
294 vLine(x + w - 1, y, y + h - 1, color);
295 break;
296
297 case FrameStyle::Dashed:
298 for(uInt32 i = x; i < x + w; i += 2)
299 {
300 hLine(i, y, i, color);
301 hLine(i, y + h - 1, i, color);
302 }
303 for(uInt32 i = y; i < y + h; i += 2)
304 {
305 vLine(x, i, i, color);
306 vLine(x + w - 1, i, i, color);
307 }
308 break;
309 }
310}
311
312// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
313void FBSurface::wrapString(const string& inStr, int pos, string& leftStr, string& rightStr) const
314{
315 for(int i = pos; i > 0; --i)
316 {
317 if(isWhiteSpace(inStr[i]))
318 {
319 leftStr = inStr.substr(0, i);
320 if(inStr[i] == ' ') // skip leading space after line break
321 i++;
322 rightStr = inStr.substr(i);
323 return;
324 }
325 }
326 leftStr = inStr.substr(0, pos);
327 rightStr = inStr.substr(pos);
328}
329
330// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
331bool FBSurface::isWhiteSpace(const char s) const
332{
333 const string WHITESPACES = " ,.;:+-";
334
335 for(size_t i = 0; i < WHITESPACES.length(); ++i)
336 if(s == WHITESPACES[i])
337 return true;
338
339 return false;
340}
341
342// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
343int FBSurface::drawString(const GUI::Font& font, const string& s,
344 int x, int y, int w, int h,
345 ColorId color, TextAlign align,
346 int deltax, bool useEllipsis, ColorId shadowColor)
347{
348 int lines = 1;
349
350#ifdef GUI_SUPPORT
351 string inStr = s;
352
353 // draw multiline string
354 while (font.getStringWidth(inStr) > w && h >= font.getFontHeight() * 2)
355 {
356 // String is too wide.
357 uInt32 i;
358 string leftStr, rightStr;
359 int w2 = 0;
360
361 // SLOW algorithm to find the acceptable length. But it is good enough for now.
362 for(i = 0; i < inStr.size(); ++i)
363 {
364 int charWidth = font.getCharWidth(inStr[i]);
365 if(w2 + charWidth > w)
366 break;
367
368 w2 += charWidth;
369 //str += inStr[i];
370 }
371 wrapString(inStr, i, leftStr, rightStr);
372 drawString(font, leftStr, x, y, w, color, align, deltax, false, shadowColor);
373 h -= font.getFontHeight();
374 y += font.getFontHeight();
375 inStr = rightStr;
376 lines++;
377 }
378 drawString(font, inStr, x, y, w, color, align, deltax, useEllipsis, shadowColor);
379#endif
380 return lines;
381}
382
383// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
384void FBSurface::drawString(const GUI::Font& font, const string& s,
385 int x, int y, int w,
386 ColorId color, TextAlign align,
387 int deltax, bool useEllipsis, ColorId shadowColor)
388{
389#ifdef GUI_SUPPORT
390 const string ELLIPSIS = "\x1d"; // "..."
391 const int leftX = x, rightX = x + w;
392 uInt32 i;
393 int width = font.getStringWidth(s);
394 string str;
395
396 if(useEllipsis && width > w)
397 {
398 // String is too wide. So we shorten it "intelligently", by replacing
399 // parts of it by an ellipsis ("..."). There are three possibilities
400 // for this: replace the start, the end, or the middle of the string.
401 // What is best really depends on the context; but most applications
402 // replace the end. So we use that too.
403 int w2 = font.getStringWidth(ELLIPSIS);
404
405 // SLOW algorithm to find the acceptable length. But it is good enough for now.
406 for(i = 0; i < s.size(); ++i)
407 {
408 int charWidth = font.getCharWidth(s[i]);
409 if(w2 + charWidth > w)
410 break;
411
412 w2 += charWidth;
413 str += s[i];
414 }
415 str += ELLIPSIS;
416
417 width = font.getStringWidth(str);
418 }
419 else
420 str = s;
421
422 if(align == TextAlign::Center)
423 x = x + (w - width - 1)/2;
424 else if(align == TextAlign::Right)
425 x = x + w - width;
426
427 x += deltax;
428 for(i = 0; i < str.size(); ++i)
429 {
430 w = font.getCharWidth(str[i]);
431 if(x+w > rightX)
432 break;
433 if(x >= leftX)
434 drawChar(font, str[i], x, y, color, shadowColor);
435
436 x += w;
437 }
438#endif
439}
440
441// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
442bool FBSurface::checkBounds(const uInt32 x, const uInt32 y) const
443{
444 if (x <= width() && y <= height())
445 return true;
446
447 cerr << "FBSurface::checkBounds() failed: "
448 << x << ", " << y << " vs " << width() << ", " << height() << endl;
449 return false;
450}
451
452// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
453const uInt32* FBSurface::myPalette = nullptr;
454