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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
29 | FBSurface::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
43 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
66 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
75 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
137 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
148 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
162 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
169 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
231 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
238 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
258 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
270 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
285 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
313 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
331 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
343 | int 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
384 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
442 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
453 | const uInt32* FBSurface::myPalette = nullptr; |
454 | |