LCOV - code coverage report
Current view: top level - src/rendering - window.cc (source / functions) Hit Total Coverage
Test: main_coverage.info Lines: 343 352 97.4 %
Date: 2022-06-15 20:16:21 Functions: 42 51 82.4 %
Branches: 202 293 68.9 %

           Branch data     Line data    Source code
       1                 :            : #ifdef RUN_TESTS
       2                 :            : # include <gtest/gtest.h>
       3                 :            : # include <algorithm>
       4                 :            : # include <set>
       5                 :            : #endif
       6                 :            : /** @file rendering/window.cc
       7                 :            :  * @brief Defines Window, a renderer and manager of screen contents.
       8                 :            :  */
       9                 :            : 
      10                 :            : #include <unordered_map>
      11                 :            : #include <array>
      12                 :            : #include <thread>
      13                 :            : 
      14                 :            : #include "window.hh"
      15                 :            : #include "color.hh"
      16                 :            : #include "person.hh"
      17                 :            : #include "clock.hh"
      18                 :            : #include "ctype.hh"
      19                 :            : #include "font_planner.hh"
      20                 :            : 
      21                 :            : /** Generates an intensity table for the given combination of options:
      22                 :            :  * @param dim    Dim symbols
      23                 :            :  * @param bold   Bold symbols
      24                 :            :  * @param italic A value in range 0-1 representing the slant of the pixel row for italic text
      25                 :            :  */
      26                 :            : static constexpr std::array<unsigned char,16> CalculateIntensityTable(bool dim,bool bold,float italic)
      27                 :            : {
      28                 :            :     std::array<unsigned char,16> result={};
      29                 :            :     auto calc = [=](bool prev,bool cur,bool next) constexpr
      30                 :            :     {
      31                 :            :         float result = cur;
      32                 :            :         if(dim)
      33                 :            :         {
      34                 :            :             if(cur && !next)
      35                 :            :             {
      36                 :            :                 if(prev) result *= float(1.f/3.f); // diminish rightmost pixel
      37                 :            :                 else     result *= float(2.f/3.f); // diminish all pixels
      38                 :            :             }
      39                 :            :         }
      40                 :            :         if(bold)
      41                 :            :         {
      42                 :            :             if(cur || prev) result += float(1.f/4.f); // add dim extra pixel, slightly brighten existing pixels
      43                 :            :         }
      44                 :            :         return result;
      45                 :            :     };
      46                 :            :     for(unsigned value=0; value<16; ++value)
      47                 :            :     {
      48                 :            :         bool values[4] = { bool(value&8), bool(value&4), bool(value&2), bool(value&1) }; // before,current,after,next
      49                 :            :         float thisresult = calc(values[0], values[1], values[2]);
      50                 :            :         float nextresult = calc(values[1], values[2], values[3]);
      51                 :            :         float factor = thisresult + (nextresult-thisresult)*italic;
      52                 :            :         // possible values of factor: 0, 1, 1.5,  0.333, 0.5
      53                 :            :         result[value] = int(factor*127 + 0.5f);
      54                 :            :     }
      55                 :            :     return result;
      56                 :            : }
      57                 :            : 
      58                 :            : /** Intensity tables for different combinations of font rendering.
      59                 :            :  *
      60                 :            :  * For taketables[mode][mask],
      61                 :            :  *   mode is 16 when cell is dim
      62                 :            :  *       plus 8 when cell is bold
      63                 :            :  *       plus 0 when cell is not italic, a range from 0-7 when cell is italic (top to bottom)
      64                 :            :  *   mask is a bitmask of four consecutive bits from the font, horizontally:
      65                 :            :  *       bit 0 is previous
      66                 :            :  *       bit 1 is current
      67                 :            :  *       bit 2 is next
      68                 :            :  *       bit 3 is next after next
      69                 :            :  *  Result is a value in range 0-128,
      70                 :            :  *  where 0 means fully background color,
      71                 :            :  *  128 means fully foreground color,
      72                 :            :  *  and the rest of values are linearly interpolated between.
      73                 :            :  */
      74                 :            : static constexpr std::array<unsigned char,16> taketables[] =
      75                 :            : {
      76                 :            :     #define i(n,i) CalculateIntensityTable(n&2,n&1,i),
      77                 :            :     #define j(n) i(n,0/8.f)i(n,1/8.f)i(n,2/8.f)i(n,3/8.f)i(n,4/8.f)i(n,5/8.f)i(n,6/8.f)i(n,7/8.f)
      78                 :            :     j(0) j(1) j(2) j(3) j(4) j(5) j(6) j(7)
      79                 :            :     #undef j
      80                 :            :     #undef i
      81                 :            : };
      82                 :            : 
      83                 :        269 : void Window::Render(std::size_t fx, std::size_t fy, std::uint32_t* pixels)
      84                 :            : {
      85                 :            :     /* Blinking happens on timer. */
      86                 :        269 :     unsigned timer = unsigned(GetTime() * 60.0);
      87                 :            :     /* Blink settings:
      88                 :            :      *     blink1 = Used for blinking speed 1 (normal):        1.5 Hz with 50% duty (60/40)
      89                 :            :      *     blink2 = Used for blinking speed 2 (fast blinking): 5   Hz with 50% duty (60/12)
      90                 :            :      *     blink3 = Used for cursor blinking:                  6   Hz with 50% duty (60/10)
      91                 :            :      */
      92                 :        269 :     bool old_blink1 = (lasttimer/20)&1, cur_blink1 = (timer/20)&1;
      93                 :        269 :     bool old_blink2 = (lasttimer/ 6)&1, cur_blink2 = (timer/ 6)&1;
      94                 :        269 :     bool old_blink3 = lasttimer%10<5,   cur_blink3 = timer%10<5;
      95                 :            : 
      96                 :            :     /* Fonts are cached for each 0x400 slots in Unicode */
      97                 :        269 :     constexpr std::size_t font_granularity = 0x400;
      98                 :            : 
      99                 :            :     /* Cached fonts. Single-size and double-size. */
     100   [ +  +  +  -  :        271 :     static std::unordered_map<char32_t, FontPlan> fonts[2];
                   +  + ]
     101         [ +  + ]:      11665 :     for(const auto& cell: cells)
     102                 :            :     {
     103         [ +  - ]:      11396 :         unsigned dbl = isdouble(cell.ch) ? 1 : 0;
     104                 :      11396 :         char32_t ch = cell.ch & ~(font_granularity-1);
     105         [ -  + ]:      11396 :         fonts[dbl][ch].Create(dbl ? fx*2 : fx, fy, ch, font_granularity);
     106                 :            :     }
     107                 :            : 
     108                 :            : #ifdef _OPENMP
     109                 :            :     std::size_t num_dirtycells = std::count_if(cells.begin(),cells.end(),
     110                 :            :                                                [](const Cell& c) { return c.dirty; });
     111                 :            :     double dirtiness = num_dirtycells / double(cells.size());
     112                 :            :     // 0.1: 4.17s (nt=4)    5.1s (nt=2)  3.8s (nt=8) 3.58s (nt=16) 8.0s (nt=48)
     113                 :            :     // 0.2: about 4s (nt=4)
     114                 :            :     // 0.5: 4.3s (nt=4)
     115                 :            :     // 1.0: 5.7s
     116                 :            :     unsigned thread_count = std::clamp(std::min(16u, unsigned(dirtiness*16/0.1)),
     117                 :            :                                        1u, std::thread::hardware_concurrency());
     118                 :            : #endif
     119                 :            : 
     120                 :        269 :     std::size_t screen_width  = fx*xsize;
     121                 :            :     #pragma omp parallel for schedule(static) collapse(2) num_threads(thread_count)
     122         [ +  + ]:       1126 :     for(std::size_t y=0; y<ysize; ++y) // cell-row
     123         [ +  + ]:       9649 :         for(std::size_t fr=0; fr<fy; ++fr) // font-row
     124                 :            :         {
     125   [ +  +  -  + ]:       8792 :             bool is_person_row = cells[y*xsize+0].inverse && cells[y*xsize+xsize-1].inverse;
     126                 :            : 
     127                 :       8792 :             std::uint32_t* pix = pixels + (y*fy+fr)*screen_width;
     128                 :       8792 :             std::size_t xroom = xsize;
     129                 :       8792 :             bool cursor_seen = !cursorvis;
     130   [ +  +  +  - ]:       8792 :             if(y != cursy && y != lastcursy) cursor_seen = true;
     131         [ +  + ]:     138320 :             for(std::size_t x=0; x<xroom; ++x) // cell-column
     132                 :            :             {
     133         [ +  + ]:     129528 :                 auto& cell = cells[y * xsize + x];
     134                 :            : 
     135                 :     129528 :                 unsigned xscale = 1;
     136   [ +  +  +  - ]:     129528 :                 if(cell.render_size && (x+1) < xroom) { xscale = 2; --xroom; }
     137                 :     129528 :                 unsigned width = fx * xscale;
     138                 :            : 
     139                 :     129528 :                 auto char_to_render = cell.ch;
     140                 :     129528 :                 bool was_double     = isdouble(char_to_render);
     141         [ -  + ]:     129528 :                 if(was_double) width *= 2;
     142                 :            : 
     143   [ +  +  +  +  :     129528 :                 bool cursorloc      = y == cursy     && (x == cursx     || (was_double && cursx     == x+1));
             -  +  -  - ]
     144   [ -  +  -  -  :     129528 :                 bool prev_cursorloc = y == lastcursy && (x == lastcursx || (was_double && lastcursx == x+1));
             -  -  -  - ]
     145                 :            : 
     146         [ -  + ]:     129528 :                 if(!cell.dirty
     147         [ #  # ]:          0 :                 && !is_person_row
     148         [ #  # ]:          0 :                 && (!(cursorloc || prev_cursorloc)
     149         [ #  # ]:          0 :                     || (cursorloc == prev_cursorloc && old_blink3 == cur_blink3)
     150                 :            :                    )
     151   [ #  #  #  # ]:          0 :                 && (cell.blink!=1 || old_blink1 == cur_blink1)
     152   [ #  #  #  # ]:          0 :                 && (cell.blink!=2 || old_blink2 == cur_blink2)
     153                 :            :                   )
     154                 :            :                 {
     155                 :          0 :                     pix          += width;
     156                 :          0 :                     x            += was_double;
     157                 :          0 :                     continue;
     158                 :            :                 }
     159                 :            : 
     160                 :     129528 :                 unsigned fr_actual = fr;
     161      [ +  +  + ]:     129528 :                 switch(cell.render_size)
     162                 :            :                 {
     163                 :            :                     default: break;
     164                 :         80 :                     case 2: fr_actual /= 2; break;
     165                 :        200 :                     case 3: fr_actual /= 2; fr_actual += fy/2; break;
     166                 :            :                 }
     167                 :            : 
     168         [ +  + ]:          8 :                 bool line = (cell.underline && (fr == (fy-1)))
     169   [ +  +  +  +  :     129527 :                          || (cell.underline2 && (fr == (fy-1) || fr == (fy-3)))
                   +  + ]
     170   [ +  +  +  + ]:     129525 :                          || (cell.overstrike && (fr == (fy/2)))
     171   [ +  +  +  +  :     259052 :                          || (cell.overlined && (fr == 0));
                   +  + ]
     172                 :            : 
     173                 :     129528 :                 bool do_cursor = !cursor_seen && cursorloc;
     174         [ +  + ]:     129528 :                 if(do_cursor)
     175                 :            :                 {
     176                 :        608 :                     unsigned lim = 7;
     177         [ -  + ]:        608 :                     if(cells[4].ch == U'O') lim = 3;
     178   [ +  +  +  + ]:        608 :                     if(fr >= fy*lim/8 && cur_blink3)
     179                 :            :                         {}
     180                 :            :                     else
     181                 :        564 :                         do_cursor = false;
     182                 :            :                     cursor_seen = true;
     183                 :            :                 }
     184                 :            : 
     185                 :     129528 :                 bool is_bold = true;
     186                 :     129528 :                 unsigned long widefont = 0;
     187   [ +  +  +  + ]:     129528 :                 /**/ if(cell.blink == 1 && !cur_blink1) {}
     188   [ +  +  +  + ]:     129192 :                 else if(cell.blink == 2 && !cur_blink2) {}
     189         [ +  - ]:     257872 :                 else { auto r = fonts[was_double ? 1 : 0].find(char_to_render &~ (font_granularity-1))->second
     190                 :     128936 :                                     .LoadGlyph(char_to_render % font_granularity, fr_actual, width);
     191                 :     128936 :                        widefont = r.bitmap;
     192                 :     128936 :                        is_bold  = r.bold; }
     193                 :            : 
     194                 :     129528 :                 unsigned shift = 2 + !cell.italic;
     195   [ +  +  -  + ]:     129528 :                 bool dim = cell.dim && is_bold; // Disable dim on non-bold fonts, because it makes them look bad.
     196                 :            : 
     197                 :     129528 :                 const unsigned mode = cell.italic*(fr*8/fy)
     198                 :     129528 :                                     + 8*cell.bold
     199                 :     129528 :                                     + 16*dim;
     200                 :            : 
     201         [ +  + ]:    1168632 :                 for(std::size_t fc=0; fc<width; ++fc)
     202                 :            :                 {
     203                 :    1039104 :                     auto fg    = cell.fgcolor;
     204                 :    1039104 :                     auto bg    = cell.bgcolor;
     205                 :            : 
     206         [ +  + ]:    1039104 :                     if(cell.inverse ^ inverse)
     207                 :            :                     {
     208                 :     619584 :                         std::swap(fg, bg);
     209                 :            :                     }
     210                 :            : 
     211                 :            :                     // taketables requires four consecutive bits from the font:
     212                 :            :                     // previous, current, next and next2.
     213                 :            :                     // <<2 is so that we can get previous pixel, and also not do -1 in width-1-fc
     214                 :            : 
     215                 :    1039104 :                     unsigned mask = ((widefont << shift) >> (width - fc)) & 0xF;
     216         [ +  + ]:    1039104 :                     int take = taketables[mode][mask];
     217         [ +  + ]:    1039104 :                     unsigned untake = std::max(0,128-take);
     218                 :    1039104 :                     unsigned pre_bg = bg, pre_fg = fg;
     219         [ +  + ]:    1039104 :                     if(cell.inverse)
     220                 :            :                     {
     221                 :     619584 :                         PersonTransform(bg,fg, xsize*fx, x*fx+fc, y*fy+fr,
     222         [ +  + ]:     619584 :                                         (is_person_row && y==0) ? 1
     223         [ +  + ]:     309824 :                                       : y == (ysize-1) ? 2
     224                 :            :                                       : 0);
     225                 :            :                     }
     226         [ +  + ]:    1039104 :                     if(do_cursor)
     227                 :            :                     {
     228                 :        352 :                         unsigned curs = cursorcolor;
     229         [ -  + ]:        352 :                         if(curs == bg) curs = fg;
     230                 :        352 :                         fg = bg;
     231                 :        352 :                         bg = curs;
     232                 :            :                     }
     233                 :    1039104 :                     unsigned color  = Mix(bg,fg, untake, take, 128);
     234                 :            : 
     235   [ +  +  -  +  :    1039104 :                     if(line && take == 0 && (!cell.inverse || color != 0x000000))
                   -  - ]
     236                 :            :                     {
     237                 :         78 :                         auto brightness = [](unsigned rgb)
     238                 :            :                         {
     239                 :         52 :                             auto p = Unpack(rgb);
     240                 :         52 :                             return p[0]*299 + p[1]*587 + p[2]*114;
     241                 :            :                         };
     242         [ -  + ]:         26 :                         if(brightness(pre_fg) < brightness(pre_bg))
     243                 :          0 :                             color = Mix(0x000000, color, 1,1,2);
     244                 :            :                         else
     245                 :         26 :                             color = Mix(0xFFFFFF, color, 1,1,2);
     246                 :            :                     }
     247                 :            : 
     248                 :            :                     /*if(color && use_fx < fx)
     249                 :            :                     {
     250                 :            :                         // Make brighter to compensate for black 9th column
     251                 :            :                         color = Mix(0xFFFFFF, color, fx-use_fx,use_fx, fx);
     252                 :            :                     }*/
     253                 :            : 
     254                 :    1039104 :                     pix[fc] = color;
     255                 :            :                 }
     256                 :     129528 :                 pix += width;
     257                 :     129528 :                 x   += was_double;
     258                 :            :             }
     259                 :            :         }
     260                 :            : 
     261         [ +  + ]:       1126 :     for(std::size_t y=0; y<ysize; ++y) // cell-row
     262         [ +  + ]:      12253 :         for(std::size_t x=0; x<xsize; ++x) // cell-column
     263                 :      11396 :             cells[y * xsize + x].dirty = false;
     264                 :            : 
     265                 :        269 :     lastcursx = cursx;
     266                 :        269 :     lastcursy = cursy;
     267                 :        269 :     lasttimer = timer;
     268                 :        269 : }
     269                 :            : 
     270                 :          2 : void Window::Resize(std::size_t newsx, std::size_t newsy)
     271                 :            : {
     272                 :          2 :     std::vector<Cell> newcells(newsx*newsy, blank);
     273   [ -  +  +  + ]:         31 :     for(std::size_t my=std::min(ysize, newsy), y=0; y<my; ++y)
     274   [ +  +  +  + ]:       1070 :         for(std::size_t mx=std::min(xsize, newsx), x=0; x<mx; ++x)
     275                 :       1016 :             newcells[x + y*newsx] = cells[x + y*xsize];
     276                 :            : 
     277                 :          2 :     cells = std::move(newcells);
     278                 :          2 :     xsize = newsx;
     279                 :          2 :     ysize = newsy;
     280                 :          2 :     Dirtify();
     281         [ -  + ]:          2 :     if(cursy >= ysize) cursy = ysize-1;
     282         [ -  + ]:          2 :     if(cursx >= xsize) cursx = xsize-1;
     283                 :          2 : }
     284                 :            : 
     285                 :        674 : void Window::Dirtify()
     286                 :            : {
     287                 :        674 :     lastcursx = lastcursy = ~std::size_t();
     288                 :        674 :     lasttimer = ~0u;
     289         [ +  + ]:     710899 :     for(auto& c: cells) c.dirty = true;
     290                 :        674 : }
     291                 :            : 
     292                 :         11 : void Window::LineSetRenderSize(unsigned val)
     293                 :            : {
     294         [ +  + ]:        401 :     for(std::size_t x=0; x<xsize; ++x)
     295                 :        390 :         cells[cursy*xsize+x].render_size = val;
     296                 :         11 : }
     297                 :            : 
     298                 :            : #ifdef RUN_TESTS
     299                 :          3 : TEST(window, window_dirty)
     300                 :            : {
     301                 :          1 :     std::cout << "This test will take some time (will load 8x8 fonts for rendering)\n";
     302                 :          1 :     Window w(80, 25);
     303                 :            :     // Initially dirty
     304         [ -  + ]:          1 :     w.cells[0].ch = 'X';
     305                 :        501 :     EXPECT_TRUE( std::all_of(w.cells.begin(), w.cells.end(), [](Cell c){return c.dirty;}) );
     306                 :            :     // Clean after render
     307         [ +  - ]:          2 :     std::vector<std::uint32_t> buffer(80*8 * 25*8);
     308         [ +  - ]:          1 :     w.Render(8,8, &buffer[0]);
     309                 :        501 :     EXPECT_TRUE( std::none_of(w.cells.begin(), w.cells.end(), [](Cell c){return c.dirty;}) );
     310                 :            :     // PutCh dirtifies some but not all cells
     311         [ +  - ]:          1 :     w.PutCh(0,0, U'A');
     312                 :          2 :     EXPECT_FALSE( std::none_of(w.cells.begin(), w.cells.end(), [](Cell c){return c.dirty;}) );
     313                 :          2 :     EXPECT_FALSE( std::all_of(w.cells.begin(), w.cells.end(), [](Cell c){return c.dirty;}) );
     314                 :            :     // Dirty after resize
     315         [ +  - ]:          1 :     w.Resize(40, 25);
     316                 :        252 :     EXPECT_TRUE( std::all_of(w.cells.begin(), w.cells.end(), [](Cell c){return c.dirty;}) );
     317                 :          1 : }
     318                 :          3 : TEST(window, fillbox)
     319                 :            : {
     320                 :          1 :     Window w(80, 25);
     321                 :            :     // Initially blank
     322                 :        501 :     EXPECT_TRUE( std::all_of(w.cells.begin(), w.cells.end(), [](Cell c){return c.ch == U' ';}) );
     323                 :            :     // Fillbox with 'A' fills with 'A'
     324                 :          1 :     Cell a; a.ch = U'A';
     325                 :          1 :     w.FillBox(20,20, 40,3, a);
     326                 :       2001 :     EXPECT_EQ( std::count_if(w.cells.begin(), w.cells.end(), [&](Cell c){return c == a;}), 40*3 );
     327                 :            :     // Fillbox without character fills with space
     328                 :          1 :     w.FillBox(30,21, 20,1);
     329                 :       2001 :     EXPECT_EQ( std::count_if(w.cells.begin(), w.cells.end(), [&](Cell c){return c == a;}),       40*3-20 );
     330                 :       2002 :     EXPECT_EQ( std::count_if(w.cells.begin(), w.cells.end(), [&](Cell c){return c == w.blank;}), 80*25-(40*3-20) );
     331                 :          1 : }
     332                 :          3 : TEST(window, copytext)
     333                 :            : {
     334                 :          1 :     Window w(80, 25);
     335                 :            :     // Create 15x10 'A' box
     336                 :          1 :     Cell a; a.ch = U'A';
     337                 :          1 :     w.FillBox(20,10, 15,10, a);
     338                 :            :     // Copy it
     339                 :          1 :     w.CopyText(60,14, 20,10, 15,10);
     340                 :       2001 :     EXPECT_EQ( std::count_if(w.cells.begin(), w.cells.end(), [&](Cell c){return c == a;}), 2*15*10 );
     341                 :            :     // Test overlap (left) -- should add 10
     342                 :          1 :     w.CopyText(19,10, 20,10, 15,10);
     343                 :       2001 :     EXPECT_EQ( std::count_if(w.cells.begin(), w.cells.end(), [&](Cell c){return c == a;}), 2*15*10 + 10 );
     344                 :            :     // Test overlap (right) -- should add 10
     345                 :          1 :     w.CopyText(21,10, 20,10, 15,10);
     346                 :       2001 :     EXPECT_EQ( std::count_if(w.cells.begin(), w.cells.end(), [&](Cell c){return c == a;}), 2*15*10 + 10+10 );
     347                 :            :     // Test overlap (above) -- should add 15
     348                 :          1 :     w.CopyText(20,9, 20,10, 15,10);
     349                 :       2001 :     EXPECT_EQ( std::count_if(w.cells.begin(), w.cells.end(), [&](Cell c){return c == a;}), 2*15*10 + 10+10+15 );
     350                 :            :     // Test overlap (below) -- should add 15
     351                 :          1 :     w.CopyText(20,11, 20,10, 15,10);
     352                 :       2002 :     EXPECT_EQ( std::count_if(w.cells.begin(), w.cells.end(), [&](Cell c){return c == a;}), 2*15*10 + 10+10+15+15 );
     353                 :          1 : }
     354                 :          3 : TEST(window, font_styles_are_distinct)
     355                 :            : {
     356                 :            :     // Stop timer, so that we do not get blinking cursor
     357                 :          1 :     SetTimeFactor(0.);
     358                 :          1 :     Window w(10,10);
     359                 :          1 :     const std::size_t fx=8, fy=8, npixels = w.xsize*fx * w.ysize*fy;
     360                 :            :     // Place 'B' at (4,4)
     361         [ +  - ]:          1 :     w.PutCh(4,4, U'B');
     362                 :          1 :     unsigned position = 4 * w.xsize + 4;
     363                 :            :     // Backup that cell
     364         [ +  - ]:          1 :     Cell c = w.cells[position];
     365                 :            :     // Create model rendering
     366         [ +  - ]:          1 :     std::vector<std::uint32_t> model_rendering(npixels);
     367         [ +  - ]:          1 :     w.Render(fx,fy, &model_rendering[0]);
     368                 :            :     // Render in different styles
     369                 :         11 :     auto makestyle = [&](auto&& mogrify)
     370                 :            :     {
     371                 :         10 :         std::vector<std::uint32_t> pix(npixels);
     372                 :         10 :         w.Dirtify();
     373         [ +  - ]:         10 :         w.cells[position]=c;
     374                 :         10 :         mogrify(w.cells[position]);
     375                 :         10 :         w.Render(fx,fy, &pix[0]);
     376                 :         10 :         return pix;
     377                 :          1 :     };
     378         [ +  - ]:          2 :     auto bold       = makestyle([&](Cell&t) { t.bold=true; });
     379         [ +  - ]:          2 :     auto dim        = makestyle([&](Cell&t) { t.dim=true; });
     380         [ +  - ]:          2 :     auto underline  = makestyle([&](Cell&t) { t.underline=true; });
     381         [ +  - ]:          2 :     auto underline2 = makestyle([&](Cell&t) { t.underline2=true; });
     382         [ +  - ]:          2 :     auto italic     = makestyle([&](Cell&t) { t.italic=true; });
     383         [ +  - ]:          2 :     auto inverse    = makestyle([&](Cell&t) { t.inverse=true; });
     384         [ +  - ]:          2 :     auto overstrike = makestyle([&](Cell&t) { t.overstrike=true; });
     385         [ +  - ]:          2 :     auto overlined   = makestyle([&](Cell&t) { t.overlined=true; });
     386         [ +  - ]:          2 :     auto whitefg    = makestyle([&](Cell&t) { t.fgcolor=0xFFFFFF; });
     387         [ +  - ]:          2 :     auto whitebg    = makestyle([&](Cell&t) { t.bgcolor=0xFFFFFF; });
     388                 :          1 :     EXPECT_NE(model_rendering, bold);
     389                 :          1 :     EXPECT_NE(model_rendering, dim);
     390                 :          1 :     EXPECT_NE(model_rendering, underline);
     391                 :          1 :     EXPECT_NE(model_rendering, underline2);
     392                 :          1 :     EXPECT_NE(model_rendering, italic);
     393                 :          1 :     EXPECT_NE(model_rendering, inverse);
     394                 :          1 :     EXPECT_NE(model_rendering, overstrike);
     395                 :          1 :     EXPECT_NE(model_rendering, overlined);
     396                 :          1 :     EXPECT_NE(model_rendering, whitefg);
     397                 :          1 :     EXPECT_NE(model_rendering, whitebg);
     398                 :          3 :     EXPECT_NE(bold, dim); EXPECT_NE(bold, underline); EXPECT_NE(bold, italic);
     399                 :          3 :     EXPECT_NE(bold, inverse); EXPECT_NE(bold, overstrike); EXPECT_NE(bold, overlined);
     400                 :          3 :     EXPECT_NE(bold, whitefg); EXPECT_NE(bold, whitebg); EXPECT_NE(bold, underline2);
     401                 :          2 :     EXPECT_NE(dim, underline); EXPECT_NE(dim, italic);
     402                 :          3 :     EXPECT_NE(dim, inverse); EXPECT_NE(dim, overstrike); EXPECT_NE(dim, overlined);
     403                 :          3 :     EXPECT_NE(dim, whitefg); EXPECT_NE(dim, whitebg); EXPECT_NE(dim, underline2);
     404                 :          2 :     EXPECT_NE(underline, italic);  EXPECT_NE(underline, underline2);
     405                 :          3 :     EXPECT_NE(underline, inverse); EXPECT_NE(underline, overstrike); EXPECT_NE(underline, overlined);
     406                 :          2 :     EXPECT_NE(underline, whitefg); EXPECT_NE(underline, whitebg);
     407                 :          3 :     EXPECT_NE(italic, inverse); EXPECT_NE(italic, overstrike); EXPECT_NE(italic, overlined);
     408                 :          3 :     EXPECT_NE(italic, whitefg); EXPECT_NE(italic, whitebg); EXPECT_NE(italic, underline2);
     409                 :          4 :     EXPECT_NE(inverse, overstrike); EXPECT_NE(inverse, overlined); EXPECT_NE(inverse, whitefg); EXPECT_NE(inverse, whitebg);
     410                 :          3 :     EXPECT_NE(overstrike, overlined); EXPECT_NE(overstrike, whitefg); EXPECT_NE(overstrike, whitebg);
     411                 :          3 :     EXPECT_NE(overlined, whitefg); EXPECT_NE(overlined, whitebg); EXPECT_NE(inverse, underline2);
     412                 :          5 :     EXPECT_NE(whitefg, whitebg); EXPECT_NE(overstrike, underline2); EXPECT_NE(whitefg, underline2); EXPECT_NE(whitebg, underline2);
     413                 :          1 : }
     414                 :          3 : TEST(window, rendersize)
     415                 :            : {
     416                 :          1 :     SetTimeFactor(1.);
     417                 :          1 :     Window w(10,4);
     418                 :          1 :     w.cursorvis = false; // Make cursor invisible
     419                 :          1 :     const std::size_t fx=8, fy=8, ncells=w.xsize*w.ysize, npixels = fx*fy*ncells;
     420                 :            : 
     421                 :            :     // Place 'X' at (4,2).
     422                 :            :     // This symbol is chosen because its top half looks like V and bottom half like Λ.
     423                 :            :     // This makes it easy to detect programmatically whether we have rendered it correctly.
     424         [ +  - ]:          1 :     w.PutCh(4,2, U'X');
     425                 :            : 
     426                 :            :     // Create model rendering
     427   [ +  -  +  - ]:          1 :     std::vector<std::uint32_t> model_rendering(npixels), pixels(npixels);
     428         [ +  - ]:          1 :     w.Render(fx,fy, &model_rendering[0]);
     429                 :            : 
     430                 :          8 :     auto makestyle = [&](auto&& mogrify)
     431                 :            :     {
     432                 :          7 :         std::vector<std::uint32_t> pix(npixels);
     433                 :          7 :         w.Dirtify();
     434         [ +  - ]:          7 :         mogrify(w);
     435         [ +  - ]:          7 :         w.Render(fx,fy, &pix[0]);
     436                 :            :         /*unsigned xpix = fx*w.xsize, ypix=fy*w.ysize;
     437                 :            :         for(unsigned p=0,y=0; y<ypix; ++y)
     438                 :            :         {
     439                 :            :             for(unsigned x=0; x<xpix; ++x,++p)
     440                 :            :                 std::cout << (pix[p] ? '#' : ' ');
     441                 :            :             std::cout << '\n';
     442                 :            :         }*/
     443                 :          7 :         return pix;
     444                 :          1 :     };
     445                 :            :     // Since cursor is at (0,0), check that LineSetRenderSize does nothing
     446                 :          1 :     EXPECT_EQ(w.cursx, 0u);
     447                 :          1 :     EXPECT_EQ(w.cursy, 0u);
     448                 :          3 :     EXPECT_EQ(model_rendering, makestyle([](auto&w){ w.LineSetRenderSize(0); }));
     449                 :          3 :     EXPECT_EQ(model_rendering, makestyle([](auto&w){ w.LineSetRenderSize(1); }));
     450                 :          3 :     EXPECT_EQ(model_rendering, makestyle([](auto&w){ w.LineSetRenderSize(2); }));
     451                 :          3 :     EXPECT_EQ(model_rendering, makestyle([](auto&w){ w.LineSetRenderSize(3); }));
     452                 :            : 
     453                 :          5 :     auto analyze_shape = [&](auto&& pixels)
     454                 :            :     {
     455                 :            :         // Find the first and last rows where something is drawn
     456                 :            :         // Find the first and last pixel on both rows
     457                 :          4 :         unsigned first_row=~0u, first_ranges[2]{~0u,0};
     458                 :          4 :         unsigned last_row=~0u,  last_ranges[2]{~0u,0};
     459                 :          4 :         unsigned xpix = fx*w.xsize, ypix=fy*w.ysize;
     460         [ +  + ]:        132 :         for(unsigned p=0,y=0; y<ypix; ++y)
     461                 :            :         {
     462                 :            :             bool first = false;
     463         [ +  + ]:      10368 :             for(unsigned x=0; x<xpix; ++x,++p)
     464         [ +  + ]:      10240 :                 if(pixels[p])
     465                 :            :                 {
     466         [ +  + ]:        189 :                     if(first_row == ~0u) first = true;
     467         [ +  + ]:        185 :                     if(first)
     468                 :            :                     {
     469         [ +  + ]:         28 :                         if(first_ranges[0] == ~0u) { first_row = y; first_ranges[0] = x; }
     470                 :         28 :                         first_ranges[1] = x;
     471                 :            :                     }
     472         [ +  + ]:        189 :                     if(last_row != y) { last_ranges[0] = x; last_row = y; }
     473                 :        189 :                     last_ranges[1] = x;
     474                 :            :                 }
     475                 :            :         }
     476                 :            :         return std::array<std::size_t,6>{
     477                 :          4 :             first_row, last_row,
     478                 :          4 :             first_ranges[0], first_ranges[1],
     479                 :          4 :             last_ranges[0], last_ranges[1]};
     480                 :          1 :     };
     481                 :            : 
     482                 :          1 :     auto model_shape = analyze_shape(model_rendering);
     483                 :          1 :     w.cursx = 4;
     484                 :          1 :     w.cursy = 2;
     485                 :            : 
     486                 :            :     // First, make sure that in the model rendering,
     487                 :            :     // the symbol is strictly within permitted bounds
     488                 :          2 :     EXPECT_GE(model_shape[0], w.cursy*fy); EXPECT_LT(model_shape[1], (w.cursy+1)*fy);
     489                 :          2 :     EXPECT_GE(model_shape[2], w.cursx*fx); EXPECT_LT(model_shape[3], (w.cursx+1)*fx);
     490                 :          2 :     EXPECT_GE(model_shape[4], w.cursx*fx); EXPECT_LT(model_shape[5], (w.cursx+1)*fx);
     491                 :            :     // Symbol 'X' height should be at least 50% of cell height
     492                 :          1 :     EXPECT_GE(model_shape[1], model_shape[0] + fy*2/4);
     493                 :            :     // Symbol 'X' width at top should be at least 75% of cell width
     494                 :          1 :     EXPECT_GE(model_shape[3], model_shape[2] + fx*3/4);
     495                 :            :     // Symbol 'X' width at bottom should be at least 75% of cell width
     496                 :          1 :     EXPECT_GE(model_shape[5], model_shape[4] + fx*3/4);
     497                 :            : 
     498                 :            :     // Size 1: Double width, full symbol (X); height stays the same.
     499         [ +  - ]:          2 :     auto shape1 = analyze_shape( makestyle([](auto&w){ w.LineSetRenderSize(1); }) );
     500                 :          2 :     EXPECT_EQ(shape1[0], model_shape[0]); EXPECT_EQ(shape1[1], model_shape[1]); // Height should be identical
     501                 :          2 :     EXPECT_EQ(shape1[2], model_shape[2]*2); EXPECT_EQ(shape1[3], model_shape[3]*2+1); // Width should be double
     502                 :          2 :     EXPECT_EQ(shape1[4], model_shape[4]*2); EXPECT_EQ(shape1[5], model_shape[5]*2+1); // Width should be double
     503                 :            : 
     504                 :            :     // Size 2: Double width, only top half (V) is displayed at double size
     505         [ +  - ]:          2 :     auto shape2 = analyze_shape( makestyle([](auto&w){ w.LineSetRenderSize(2); }) );
     506                 :          2 :     EXPECT_GE(shape2[0], w.cursy*fy); EXPECT_LT(shape2[1], (w.cursy+1)*fy); // Y coordinates should be in right bounds
     507                 :          2 :     EXPECT_EQ(shape2[2], shape1[2]); EXPECT_EQ(shape2[3], shape1[3]);       // Top line should be identical to shape1
     508                 :          2 :     EXPECT_GT(shape2[4], shape1[4]); EXPECT_LT(shape2[5], shape1[5]);       // Bottom line should be narrower.
     509                 :            : 
     510                 :            :     // Size 2: Double width, only bottom half (Λ) is displayed at double size
     511         [ +  - ]:          2 :     auto shape3 = analyze_shape( makestyle([](auto&w){ w.LineSetRenderSize(3); }) );
     512                 :          2 :     EXPECT_GE(shape3[0], w.cursy*fy); EXPECT_LT(shape3[1], (w.cursy+1)*fy); // Y coordinates should be in right bounds
     513                 :          2 :     EXPECT_GT(shape3[2], shape1[2]); EXPECT_LT(shape3[3], shape1[3]);       // Top line should be narrower
     514                 :          3 :     EXPECT_EQ(shape3[4], shape1[4]); EXPECT_EQ(shape3[5], shape1[5]);       // Bottom line should be identical to shape1.
     515                 :          1 : }
     516                 :          3 : TEST(window, cursor_blinks)
     517                 :            : {
     518                 :          1 :     SetTimeFactor(0.);
     519                 :          1 :     Window w(10,4);
     520                 :          1 :     w.cursorvis = true; // Make cursor visible
     521                 :            : 
     522                 :          1 :     const std::size_t fx=8, fy=8, cell_pixels = fx*fy, npixels = cell_pixels * w.xsize*w.ysize;
     523                 :            : 
     524                 :            :     // Render 1 second at 64 fps, count frames where cursor is visible
     525                 :          1 :     unsigned count_cursor_visible = 0, count_frames = 0;
     526         [ +  + ]:         65 :     for(unsigned frame=0; frame<64; ++frame, ++count_frames)
     527                 :            :     {
     528         [ +  - ]:         64 :         AdvanceTime(1.0 / 64.0);
     529         [ +  - ]:         64 :         std::vector<std::uint32_t> pix(npixels);
     530                 :         64 :         w.Dirtify();
     531         [ +  - ]:         64 :         w.Render(fx,fy, &pix[0]);
     532                 :         64 :         unsigned visible_pixels = std::count_if(pix.begin(), pix.end(), [](auto c){return c; });
     533                 :            :         // Cursor size should be less than 20% of cell size
     534                 :         64 :         EXPECT_LT(visible_pixels, cell_pixels/5);
     535         [ +  + ]:         64 :         if(visible_pixels) ++count_cursor_visible;
     536                 :         64 :     }
     537                 :            :     // Cursor should be visible at least 25% of frames
     538                 :          1 :     EXPECT_GT(count_cursor_visible, count_frames*1/4);
     539                 :            :     // Cursor should be invisible at least 25% of frames
     540                 :          2 :     EXPECT_LT(count_cursor_visible, count_frames*3/4);
     541                 :          1 : }
     542                 :          3 : TEST(window, text_blinks)
     543                 :            : {
     544                 :          1 :     SetTimeFactor(0.);
     545                 :          1 :     Window w(3,3);
     546                 :          1 :     w.cursorvis = false; // Make cursor invisible
     547         [ +  - ]:          1 :     w.blank.blink = 0; w.PutCh(0,0, U'A');
     548         [ +  - ]:          1 :     w.blank.blink = 1; w.PutCh(1,0, U'B');
     549         [ +  - ]:          1 :     w.blank.blink = 2; w.PutCh(2,0, U'C');
     550                 :          1 :     const std::size_t fx=8, fy=8, cell_pixels = fx*fy, npixels = cell_pixels * w.xsize*w.ysize;
     551                 :            :     // Render 1 second at 64 fps
     552         [ +  + ]:         65 :     for(unsigned frame=0; frame<64; ++frame)
     553                 :            :     {
     554         [ +  - ]:         64 :         AdvanceTime(1.0 / 64.0);
     555         [ +  - ]:         64 :         std::vector<std::uint32_t> pix(npixels);
     556                 :         64 :         w.Dirtify();
     557         [ +  - ]:         64 :         w.Render(fx,fy, &pix[0]);
     558                 :         64 :     }
     559                 :          1 : }
     560                 :          3 : TEST(window, person_animation)
     561                 :            : {
     562                 :            :     // Let window width be 20 cells, font height 16 pixels.
     563                 :            :     // Render some text in top row in inverse.
     564                 :          1 :     static const char32_t text[] = U"This is sample text ";
     565                 :          1 :     const unsigned width = sizeof(text)/sizeof(*text)-1, height = 2;
     566                 :          1 :     const unsigned fx = 8, fy = 16, time = 30, fps = 4, npixels=width*height*fx*fy;
     567                 :          1 :     SetTimeFactor(0.);      // Chose manual timer
     568                 :          1 :     Window w(width, height);
     569                 :          1 :     w.cursorvis = false;    // Make cursor invisible
     570                 :          1 :     w.blank.inverse = true; // Choose inverse attribute
     571   [ +  +  +  -  :         22 :     for(auto c: text) if(c) { w.PutCh(w.cursx, height-1, c); w.PutCh(w.cursx++, w.cursy, c); }
             +  -  +  + ]
     572                 :          1 :     EXPECT_EQ(w.cursx, width);
     573                 :            : 
     574                 :            :     // Wait until Person's starting X coordinate is outside window boundaries
     575   [ +  -  +  + ]:        102 :     while(PersonBaseX(width*fx) < int(width*fx))
     576         [ +  - ]:        101 :         AdvanceTime(0.01);
     577                 :            : 
     578         [ +  - ]:          2 :     std::vector<std::uint32_t> model(npixels);
     579         [ +  - ]:          1 :     w.Render(fx,fy, &model[0]);
     580                 :            : 
     581                 :            :     /*{unsigned xpix = fx*w.xsize, ypix=fy*w.ysize;
     582                 :            :     for(unsigned p=0,y=0; y<fy; ++y)
     583                 :            :     {
     584                 :            :         for(unsigned x=0; x<xpix; ++x,++p)
     585                 :            :         {
     586                 :            :             auto q = Unpack(model[p]);
     587                 :            :             char Buf[32]; std::sprintf(Buf, "%X%X%X", q[0]>>4,q[1]>>4,q[2]>>4);
     588                 :            :             std::cout << Buf;
     589                 :            :         }
     590                 :            :         std::cout << '\n';
     591                 :            :     }}*/
     592                 :            : 
     593                 :            :     // Re-render screen for 30 seconds at 4 fps.
     594                 :            :     // Every frame, the following conditions must be true:
     595                 :            :     //   - Most of the text is visible
     596                 :            :     //   - At most 16x16 pixels may be changed at any given time
     597                 :            :     //   - The list of first changed columns should not be constant
     598                 :          2 :     std::set<unsigned> change_columns;
     599         [ +  + ]:        121 :     for(unsigned frame=0; frame<time*fps; ++frame)
     600                 :            :     {
     601                 :        120 :         const unsigned allowed_diff_x = 16, allowed_diff_y = 16;
     602                 :            : 
     603         [ +  - ]:        120 :         AdvanceTime(1.0 / fps);
     604         [ +  - ]:        120 :         std::vector<std::uint32_t> pix(npixels);
     605                 :        120 :         w.Dirtify();
     606         [ +  - ]:        120 :         w.Render(fx,fy, &pix[0]);
     607                 :        120 :         unsigned differences = 0, diffy[2]={~0u,0u}, diffx[2]={~0u,0u}, xpix=width*fx;
     608                 :            : 
     609                 :            :         /*{unsigned xpix = fx*w.xsize, ypix=fy*w.ysize;
     610                 :            :         for(unsigned p=0,y=0; y<fy; ++y)
     611                 :            :         {
     612                 :            :             for(unsigned x=0; x<xpix; ++x,++p)
     613                 :            :             {
     614                 :            :                 auto q = Unpack(pix[p]);
     615                 :            :                 char Buf[32]; std::sprintf(Buf, "%X%X%X", q[0]>>4,q[1]>>4,q[2]>>4);
     616                 :            :                 std::cout << Buf;
     617                 :            :             }
     618                 :            :             std::cout << '\n';
     619                 :            :         }}*/
     620                 :            : 
     621         [ +  + ]:     307320 :         for(unsigned n=0; n<xpix*fy; ++n)
     622         [ +  + ]:     307200 :             if(pix[n] != model[n])
     623                 :            :             {
     624                 :      15210 :                 ++differences;
     625         [ +  + ]:      15210 :                 diffy[0] = std::min(diffy[0], n / xpix);
     626         [ +  + ]:      15210 :                 diffy[1] = std::max(diffy[1], n / xpix);
     627         [ +  + ]:      15210 :                 diffx[0] = std::min(diffx[0], n % xpix);
     628         [ +  + ]:      16073 :                 diffx[1] = std::max(diffx[1], n % xpix);
     629                 :            :             }
     630                 :        120 :         EXPECT_LT(differences, npixels - allowed_diff_x*allowed_diff_y);
     631                 :            :         //printf("Diffs: y(%d..%d) x(%d..%d)\n", diffy[0],diffy[1], diffx[0],diffx[1]);
     632         [ +  + ]:        120 :         if(differences)
     633                 :            :         {
     634                 :         91 :             EXPECT_LE(diffy[1]-diffy[0], allowed_diff_y);
     635                 :         91 :             EXPECT_LE(diffx[1]-diffx[0], allowed_diff_x);
     636         [ +  - ]:         91 :             change_columns.insert(diffx[0]);
     637                 :            :         }
     638                 :        120 :     }
     639                 :            :     // Expect the person to appear in at least 10 different positions
     640                 :          2 :     EXPECT_GT(change_columns.size(), std::size_t(10));
     641                 :          1 : }
     642                 :          3 : TEST(window, coverage)
     643                 :            : {
     644                 :            :     // For coverage, run Cell comparisons
     645                 :          1 :     Window w(10, 10);
     646                 :          1 :     Cell mod; mod.bold = true;
     647                 :          1 :     EXPECT_TRUE(w.blank == w.blank);
     648                 :          1 :     EXPECT_FALSE(w.blank != w.blank);
     649                 :          1 :     EXPECT_FALSE(w.blank == mod);
     650                 :          2 :     EXPECT_TRUE(w.blank != mod);
     651                 :          1 : }
     652                 :            : #endif

Generated by: LCOV version 1.16