| 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 | |