1 /*****************************************************************************
2  * Copyright (c) 2014-2020 OpenRCT2 developers
3  *
4  * For a complete list of all authors, please refer to contributors.md
5  * Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
6  *
7  * OpenRCT2 is licensed under the GNU General Public License version 3.
8  *****************************************************************************/
9 
10 #include "X8DrawingEngine.h"
11 
12 #include "../Context.h"
13 #include "../Game.h"
14 #include "../Intro.h"
15 #include "../config/Config.h"
16 #include "../core/Numerics.hpp"
17 #include "../interface/Screenshot.h"
18 #include "../interface/Viewport.h"
19 #include "../interface/Window.h"
20 #include "../ui/UiContext.h"
21 #include "../util/Util.h"
22 #include "../world/Climate.h"
23 #include "Drawing.h"
24 #include "IDrawingContext.h"
25 #include "IDrawingEngine.h"
26 #include "LightFX.h"
27 #include "Weather.h"
28 
29 #include <algorithm>
30 #include <cstring>
31 
32 using namespace OpenRCT2;
33 using namespace OpenRCT2::Drawing;
34 using namespace OpenRCT2::Ui;
35 
X8WeatherDrawer()36 X8WeatherDrawer::X8WeatherDrawer()
37 {
38     _weatherPixels = new WeatherPixel[_weatherPixelsCapacity];
39 }
40 
~X8WeatherDrawer()41 X8WeatherDrawer::~X8WeatherDrawer()
42 {
43     delete[] _weatherPixels;
44 }
45 
Draw(rct_drawpixelinfo * dpi,int32_t x,int32_t y,int32_t width,int32_t height,int32_t xStart,int32_t yStart,const uint8_t * weatherpattern)46 void X8WeatherDrawer::Draw(
47     rct_drawpixelinfo* dpi, int32_t x, int32_t y, int32_t width, int32_t height, int32_t xStart, int32_t yStart,
48     const uint8_t* weatherpattern)
49 {
50     const uint8_t* pattern = weatherpattern;
51     auto patternXSpace = *pattern++;
52     auto patternYSpace = *pattern++;
53 
54     uint8_t patternStartXOffset = xStart % patternXSpace;
55     uint8_t patternStartYOffset = yStart % patternYSpace;
56 
57     uint32_t pixelOffset = (dpi->pitch + dpi->width) * y + x;
58     uint8_t patternYPos = patternStartYOffset % patternYSpace;
59 
60     uint8_t* screenBits = dpi->bits;
61 
62     // Stores the colours of changed pixels
63     WeatherPixel* newPixels = &_weatherPixels[_weatherPixelsCount];
64     for (; height != 0; height--)
65     {
66         auto patternX = pattern[patternYPos * 2];
67         if (patternX != 0xFF)
68         {
69             if (_weatherPixelsCount < (_weatherPixelsCapacity - static_cast<uint32_t>(width)))
70             {
71                 uint32_t finalPixelOffset = width + pixelOffset;
72 
73                 uint32_t xPixelOffset = pixelOffset;
74                 xPixelOffset += (static_cast<uint8_t>(patternX - patternStartXOffset)) % patternXSpace;
75 
76                 auto patternPixel = pattern[patternYPos * 2 + 1];
77                 for (; xPixelOffset < finalPixelOffset; xPixelOffset += patternXSpace)
78                 {
79                     uint8_t current_pixel = screenBits[xPixelOffset];
80                     screenBits[xPixelOffset] = patternPixel;
81                     _weatherPixelsCount++;
82 
83                     // Store colour and position
84                     *newPixels++ = { xPixelOffset, current_pixel };
85                 }
86             }
87         }
88 
89         pixelOffset += dpi->pitch + dpi->width;
90         patternYPos++;
91         patternYPos %= patternYSpace;
92     }
93 }
94 
Restore(rct_drawpixelinfo * dpi)95 void X8WeatherDrawer::Restore(rct_drawpixelinfo* dpi)
96 {
97     if (_weatherPixelsCount > 0)
98     {
99         uint32_t numPixels = (dpi->width + dpi->pitch) * dpi->height;
100         uint8_t* bits = dpi->bits;
101         for (uint32_t i = 0; i < _weatherPixelsCount; i++)
102         {
103             WeatherPixel weatherPixel = _weatherPixels[i];
104             if (weatherPixel.Position >= numPixels)
105             {
106                 // Pixel out of bounds, bail
107                 break;
108             }
109 
110             bits[weatherPixel.Position] = weatherPixel.Colour;
111         }
112         _weatherPixelsCount = 0;
113     }
114 }
115 
116 #ifdef __WARN_SUGGEST_FINAL_METHODS__
117 #    pragma GCC diagnostic push
118 #    pragma GCC diagnostic ignored "-Wsuggest-final-methods"
119 #endif
120 
X8DrawingEngine(const std::shared_ptr<Ui::IUiContext> & uiContext)121 X8DrawingEngine::X8DrawingEngine([[maybe_unused]] const std::shared_ptr<Ui::IUiContext>& uiContext)
122 {
123     _drawingContext = new X8DrawingContext(this);
124     _bitsDPI.DrawingEngine = this;
125 #ifdef __ENABLE_LIGHTFX__
126     lightfx_set_available(true);
127     _lastLightFXenabled = (gConfigGeneral.enable_light_fx != 0);
128 #endif
129 }
130 
~X8DrawingEngine()131 X8DrawingEngine::~X8DrawingEngine()
132 {
133     delete _drawingContext;
134     delete[] _dirtyGrid.Blocks;
135     delete[] _bits;
136 }
137 
Initialise()138 void X8DrawingEngine::Initialise()
139 {
140 }
141 
Resize(uint32_t width,uint32_t height)142 void X8DrawingEngine::Resize(uint32_t width, uint32_t height)
143 {
144     uint32_t pitch = width;
145     ConfigureBits(width, height, pitch);
146 }
147 
SetPalette(const GamePalette & palette)148 void X8DrawingEngine::SetPalette([[maybe_unused]] const GamePalette& palette)
149 {
150 }
151 
SetVSync(bool vsync)152 void X8DrawingEngine::SetVSync([[maybe_unused]] bool vsync)
153 {
154     // Not applicable for this engine
155 }
156 
Invalidate(int32_t left,int32_t top,int32_t right,int32_t bottom)157 void X8DrawingEngine::Invalidate(int32_t left, int32_t top, int32_t right, int32_t bottom)
158 {
159     left = std::max(left, 0);
160     top = std::max(top, 0);
161     right = std::min(right, static_cast<int32_t>(_width));
162     bottom = std::min(bottom, static_cast<int32_t>(_height));
163 
164     if (left >= right)
165         return;
166     if (top >= bottom)
167         return;
168 
169     right--;
170     bottom--;
171 
172     left >>= _dirtyGrid.BlockShiftX;
173     right >>= _dirtyGrid.BlockShiftX;
174     top >>= _dirtyGrid.BlockShiftY;
175     bottom >>= _dirtyGrid.BlockShiftY;
176 
177     uint32_t dirtyBlockColumns = _dirtyGrid.BlockColumns;
178     uint8_t* screenDirtyBlocks = _dirtyGrid.Blocks;
179     for (int16_t y = top; y <= bottom; y++)
180     {
181         uint32_t yOffset = y * dirtyBlockColumns;
182         for (int16_t x = left; x <= right; x++)
183         {
184             screenDirtyBlocks[yOffset + x] = 0xFF;
185         }
186     }
187 }
188 
BeginDraw()189 void X8DrawingEngine::BeginDraw()
190 {
191     if (gIntroState == IntroState::None)
192     {
193 #ifdef __ENABLE_LIGHTFX__
194         // HACK we need to re-configure the bits if light fx has been enabled / disabled
195         if (_lastLightFXenabled != (gConfigGeneral.enable_light_fx != 0))
196         {
197             Resize(_width, _height);
198         }
199 #endif
200         _weatherDrawer.Restore(&_bitsDPI);
201     }
202 }
203 
EndDraw()204 void X8DrawingEngine::EndDraw()
205 {
206 }
207 
PaintWindows()208 void X8DrawingEngine::PaintWindows()
209 {
210     window_reset_visibilities();
211 
212     // Redraw dirty regions before updating the viewports, otherwise
213     // when viewports get panned, they copy dirty pixels
214     DrawAllDirtyBlocks();
215     window_update_all_viewports();
216     DrawAllDirtyBlocks();
217 }
218 
PaintWeather()219 void X8DrawingEngine::PaintWeather()
220 {
221     DrawWeather(&_bitsDPI, &_weatherDrawer);
222 }
223 
CopyRect(int32_t x,int32_t y,int32_t width,int32_t height,int32_t dx,int32_t dy)224 void X8DrawingEngine::CopyRect(int32_t x, int32_t y, int32_t width, int32_t height, int32_t dx, int32_t dy)
225 {
226     if (dx == 0 && dy == 0)
227         return;
228 
229     // Originally 0x00683359
230     // Adjust for move off screen
231     // NOTE: when zooming, there can be x, y, dx, dy combinations that go off the
232     // screen; hence the checks. This code should ultimately not be called when
233     // zooming because this function is specific to updating the screen on move
234     int32_t lmargin = std::min(x - dx, 0);
235     int32_t rmargin = std::min(static_cast<int32_t>(_width) - (x - dx + width), 0);
236     int32_t tmargin = std::min(y - dy, 0);
237     int32_t bmargin = std::min(static_cast<int32_t>(_height) - (y - dy + height), 0);
238     x -= lmargin;
239     y -= tmargin;
240     width += lmargin + rmargin;
241     height += tmargin + bmargin;
242 
243     int32_t stride = _bitsDPI.width + _bitsDPI.pitch;
244     uint8_t* to = _bitsDPI.bits + y * stride + x;
245     uint8_t* from = _bitsDPI.bits + (y - dy) * stride + x - dx;
246 
247     if (dy > 0)
248     {
249         // If positive dy, reverse directions
250         to += (height - 1) * stride;
251         from += (height - 1) * stride;
252         stride = -stride;
253     }
254 
255     // Move bytes
256     for (int32_t i = 0; i < height; i++)
257     {
258         memmove(to, from, width);
259         to += stride;
260         from += stride;
261     }
262 }
263 
Screenshot()264 std::string X8DrawingEngine::Screenshot()
265 {
266     return screenshot_dump_png(&_bitsDPI);
267 }
268 
GetDrawingContext()269 IDrawingContext* X8DrawingEngine::GetDrawingContext()
270 {
271     return _drawingContext;
272 }
273 
GetDrawingPixelInfo()274 rct_drawpixelinfo* X8DrawingEngine::GetDrawingPixelInfo()
275 {
276     return &_bitsDPI;
277 }
278 
GetFlags()279 DRAWING_ENGINE_FLAGS X8DrawingEngine::GetFlags()
280 {
281     return static_cast<DRAWING_ENGINE_FLAGS>(DEF_DIRTY_OPTIMISATIONS | DEF_PARALLEL_DRAWING);
282 }
283 
InvalidateImage(uint32_t image)284 void X8DrawingEngine::InvalidateImage([[maybe_unused]] uint32_t image)
285 {
286     // Not applicable for this engine
287 }
288 
GetDPI()289 rct_drawpixelinfo* X8DrawingEngine::GetDPI()
290 {
291     return &_bitsDPI;
292 }
293 
ConfigureBits(uint32_t width,uint32_t height,uint32_t pitch)294 void X8DrawingEngine::ConfigureBits(uint32_t width, uint32_t height, uint32_t pitch)
295 {
296     size_t newBitsSize = pitch * height;
297     uint8_t* newBits = new uint8_t[newBitsSize];
298     if (_bits == nullptr)
299     {
300         std::fill_n(newBits, newBitsSize, 0);
301     }
302     else
303     {
304         if (_pitch == pitch)
305         {
306             std::copy_n(_bits, std::min(_bitsSize, newBitsSize), newBits);
307         }
308         else
309         {
310             uint8_t* src = _bits;
311             uint8_t* dst = newBits;
312 
313             uint32_t minWidth = std::min(_width, width);
314             uint32_t minHeight = std::min(_height, height);
315             for (uint32_t y = 0; y < minHeight; y++)
316             {
317                 std::copy_n(src, minWidth, dst);
318                 if (pitch - minWidth > 0)
319                 {
320                     std::fill_n(dst + minWidth, pitch - minWidth, 0);
321                 }
322                 src += _pitch;
323                 dst += pitch;
324             }
325         }
326         delete[] _bits;
327     }
328 
329     _bits = newBits;
330     _bitsSize = newBitsSize;
331     _width = width;
332     _height = height;
333     _pitch = pitch;
334 
335     rct_drawpixelinfo* dpi = &_bitsDPI;
336     dpi->bits = _bits;
337     dpi->x = 0;
338     dpi->y = 0;
339     dpi->width = width;
340     dpi->height = height;
341     dpi->pitch = _pitch - width;
342 
343     ConfigureDirtyGrid();
344 
345 #ifdef __ENABLE_LIGHTFX__
346     if (lightfx_is_available())
347     {
348         lightfx_update_buffers(dpi);
349     }
350 #endif
351 }
352 
OnDrawDirtyBlock(uint32_t x,uint32_t y,uint32_t columns,uint32_t rows)353 void X8DrawingEngine::OnDrawDirtyBlock(
354     [[maybe_unused]] uint32_t x, [[maybe_unused]] uint32_t y, [[maybe_unused]] uint32_t columns, [[maybe_unused]] uint32_t rows)
355 {
356 }
357 
ConfigureDirtyGrid()358 void X8DrawingEngine::ConfigureDirtyGrid()
359 {
360     _dirtyGrid.BlockShiftX = 7;
361     _dirtyGrid.BlockShiftY = 6;
362     _dirtyGrid.BlockWidth = 1 << _dirtyGrid.BlockShiftX;
363     _dirtyGrid.BlockHeight = 1 << _dirtyGrid.BlockShiftY;
364     _dirtyGrid.BlockColumns = (_width >> _dirtyGrid.BlockShiftX) + 1;
365     _dirtyGrid.BlockRows = (_height >> _dirtyGrid.BlockShiftY) + 1;
366 
367     delete[] _dirtyGrid.Blocks;
368     _dirtyGrid.Blocks = new uint8_t[_dirtyGrid.BlockColumns * _dirtyGrid.BlockRows];
369 }
370 
DrawAllDirtyBlocks()371 void X8DrawingEngine::DrawAllDirtyBlocks()
372 {
373     for (uint32_t x = 0; x < _dirtyGrid.BlockColumns; x++)
374     {
375         for (uint32_t y = 0; y < _dirtyGrid.BlockRows; y++)
376         {
377             uint32_t yOffset = y * _dirtyGrid.BlockColumns;
378             if (_dirtyGrid.Blocks[yOffset + x] == 0)
379             {
380                 continue;
381             }
382 
383             // Determine columns
384             uint32_t xx;
385             for (xx = x; xx < _dirtyGrid.BlockColumns; xx++)
386             {
387                 if (_dirtyGrid.Blocks[yOffset + xx] == 0)
388                 {
389                     break;
390                 }
391             }
392 
393             // Check rows
394             uint32_t columns = xx - x;
395             auto rows = GetNumDirtyRows(x, y, columns);
396             DrawDirtyBlocks(x, y, columns, rows);
397         }
398     }
399 }
400 
GetNumDirtyRows(const uint32_t x,const uint32_t y,const uint32_t columns)401 uint32_t X8DrawingEngine::GetNumDirtyRows(const uint32_t x, const uint32_t y, const uint32_t columns)
402 {
403     uint32_t yy = y;
404 
405     for (yy = y; yy < _dirtyGrid.BlockRows; yy++)
406     {
407         uint32_t yyOffset = yy * _dirtyGrid.BlockColumns;
408         for (uint32_t xx = x; xx < x + columns; xx++)
409         {
410             if (_dirtyGrid.Blocks[yyOffset + xx] == 0)
411             {
412                 return yy - y;
413             }
414         }
415     }
416     return yy - y;
417 }
418 
DrawDirtyBlocks(uint32_t x,uint32_t y,uint32_t columns,uint32_t rows)419 void X8DrawingEngine::DrawDirtyBlocks(uint32_t x, uint32_t y, uint32_t columns, uint32_t rows)
420 {
421     uint32_t dirtyBlockColumns = _dirtyGrid.BlockColumns;
422     uint8_t* screenDirtyBlocks = _dirtyGrid.Blocks;
423 
424     // Unset dirty blocks
425     for (uint32_t top = y; top < y + rows; top++)
426     {
427         uint32_t topOffset = top * dirtyBlockColumns;
428         for (uint32_t left = x; left < x + columns; left++)
429         {
430             screenDirtyBlocks[topOffset + left] = 0;
431         }
432     }
433 
434     // Determine region in pixels
435     uint32_t left = std::max<uint32_t>(0, x * _dirtyGrid.BlockWidth);
436     uint32_t top = std::max<uint32_t>(0, y * _dirtyGrid.BlockHeight);
437     uint32_t right = std::min(_width, left + (columns * _dirtyGrid.BlockWidth));
438     uint32_t bottom = std::min(_height, top + (rows * _dirtyGrid.BlockHeight));
439     if (right <= left || bottom <= top)
440     {
441         return;
442     }
443 
444     // Draw region
445     OnDrawDirtyBlock(x, y, columns, rows);
446     window_draw_all(&_bitsDPI, left, top, right, bottom);
447 }
448 
449 #ifdef __WARN_SUGGEST_FINAL_METHODS__
450 #    pragma GCC diagnostic pop
451 #endif
452 
X8DrawingContext(X8DrawingEngine * engine)453 X8DrawingContext::X8DrawingContext(X8DrawingEngine* engine)
454 {
455     _engine = engine;
456 }
457 
GetEngine()458 IDrawingEngine* X8DrawingContext::GetEngine()
459 {
460     return _engine;
461 }
462 
Clear(rct_drawpixelinfo * dpi,uint8_t paletteIndex)463 void X8DrawingContext::Clear(rct_drawpixelinfo* dpi, uint8_t paletteIndex)
464 {
465     int32_t w = dpi->width / dpi->zoom_level;
466     int32_t h = dpi->height / dpi->zoom_level;
467     uint8_t* ptr = dpi->bits;
468 
469     for (int32_t y = 0; y < h; y++)
470     {
471         std::fill_n(ptr, w, paletteIndex);
472         ptr += w + dpi->pitch;
473     }
474 }
475 
476 /** rct2: 0x0097FF04 */
477 // clang-format off
478 static constexpr const uint16_t Pattern[] = {
479     0b0111111110000000,
480     0b0011111111000000,
481     0b0001111111100000,
482     0b0000111111110000,
483     0b0000011111111000,
484     0b0000001111111100,
485     0b0000000111111110,
486     0b0000000011111111,
487     0b1000000001111111,
488     0b1100000000111111,
489     0b1110000000011111,
490     0b1111000000001111,
491     0b1111100000000111,
492     0b1111110000000011,
493     0b1111111000000001,
494     0b1111111100000000,
495 };
496 
497 /** rct2: 0x0097FF14 */
498 static constexpr const uint16_t PatternInverse[] = {
499     0b1000000001111111,
500     0b1100000000111111,
501     0b1110000000011111,
502     0b1111000000001111,
503     0b1111100000000111,
504     0b1111110000000011,
505     0b1111111000000001,
506     0b1111111100000000,
507     0b0111111110000000,
508     0b0011111111000000,
509     0b0001111111100000,
510     0b0000111111110000,
511     0b0000011111111000,
512     0b0000001111111100,
513     0b0000000111111110,
514     0b0000000011111111,
515 };
516 
517 /** rct2: 0x0097FEFC */
518 static constexpr const uint16_t* Patterns[] = {
519     Pattern,
520     PatternInverse,
521 };
522 // clang-format on
523 
FillRect(rct_drawpixelinfo * dpi,uint32_t colour,int32_t left,int32_t top,int32_t right,int32_t bottom)524 void X8DrawingContext::FillRect(
525     rct_drawpixelinfo* dpi, uint32_t colour, int32_t left, int32_t top, int32_t right, int32_t bottom)
526 {
527     if (left > right)
528         return;
529     if (top > bottom)
530         return;
531     if (dpi->x > right)
532         return;
533     if (left >= dpi->x + dpi->width)
534         return;
535     if (bottom < dpi->y)
536         return;
537     if (top >= dpi->y + dpi->height)
538         return;
539 
540     uint16_t crossPattern = 0;
541 
542     int32_t startX = left - dpi->x;
543     if (startX < 0)
544     {
545         crossPattern ^= startX;
546         startX = 0;
547     }
548 
549     int32_t endX = right - dpi->x + 1;
550     if (endX > dpi->width)
551     {
552         endX = dpi->width;
553     }
554 
555     int32_t startY = top - dpi->y;
556     if (startY < 0)
557     {
558         crossPattern ^= startY;
559         startY = 0;
560     }
561 
562     int32_t endY = bottom - dpi->y + 1;
563     if (endY > dpi->height)
564     {
565         endY = dpi->height;
566     }
567 
568     int32_t width = endX - startX;
569     int32_t height = endY - startY;
570 
571     if (colour & 0x1000000)
572     {
573         // Cross hatching
574         uint8_t* dst = (startY * (dpi->width + dpi->pitch)) + startX + dpi->bits;
575         for (int32_t i = 0; i < height; i++)
576         {
577             uint8_t* nextdst = dst + dpi->width + dpi->pitch;
578             uint32_t p = Numerics::ror32(crossPattern, 1);
579             p = (p & 0xFFFF0000) | width;
580 
581             // Fill every other pixel with the colour
582             for (; (p & 0xFFFF) != 0; p--)
583             {
584                 p = p ^ 0x80000000;
585                 if (p & 0x80000000)
586                 {
587                     *dst = colour & 0xFF;
588                 }
589                 dst++;
590             }
591             crossPattern ^= 1;
592             dst = nextdst;
593         }
594     }
595     else if (colour & 0x2000000)
596     {
597         assert(false);
598     }
599     else if (colour & 0x4000000)
600     {
601         uint8_t* dst = startY * (dpi->width + dpi->pitch) + startX + dpi->bits;
602 
603         // The pattern loops every 15 lines this is which
604         // part the pattern is on.
605         int32_t patternY = (startY + dpi->y) % 16;
606 
607         // The pattern loops every 15 pixels this is which
608         // part the pattern is on.
609         int32_t startPatternX = (startX + dpi->x) % 16;
610         int32_t patternX = startPatternX;
611 
612         const uint16_t* patternsrc = Patterns[colour >> 28]; // or possibly uint8_t)[esi*4] ?
613 
614         for (int32_t numLines = height; numLines > 0; numLines--)
615         {
616             uint8_t* nextdst = dst + dpi->width + dpi->pitch;
617             uint16_t pattern = patternsrc[patternY];
618 
619             for (int32_t numPixels = width; numPixels > 0; numPixels--)
620             {
621                 if (pattern & (1 << patternX))
622                 {
623                     *dst = colour & 0xFF;
624                 }
625                 patternX = (patternX + 1) % 16;
626                 dst++;
627             }
628             patternX = startPatternX;
629             patternY = (patternY + 1) % 16;
630             dst = nextdst;
631         }
632     }
633     else
634     {
635         uint8_t* dst = startY * (dpi->width + dpi->pitch) + startX + dpi->bits;
636         for (int32_t i = 0; i < height; i++)
637         {
638             std::fill_n(dst, width, colour & 0xFF);
639             dst += dpi->width + dpi->pitch;
640         }
641     }
642 }
643 
FilterRect(rct_drawpixelinfo * dpi,FilterPaletteID palette,int32_t left,int32_t top,int32_t right,int32_t bottom)644 void X8DrawingContext::FilterRect(
645     rct_drawpixelinfo* dpi, FilterPaletteID palette, int32_t left, int32_t top, int32_t right, int32_t bottom)
646 {
647     if (left > right)
648         return;
649     if (top > bottom)
650         return;
651     if (dpi->x > right)
652         return;
653     if (left >= dpi->x + dpi->width)
654         return;
655     if (bottom < dpi->y)
656         return;
657     if (top >= dpi->y + dpi->height)
658         return;
659 
660     int32_t startX = left - dpi->x;
661     if (startX < 0)
662     {
663         startX = 0;
664     }
665 
666     int32_t endX = right - dpi->x + 1;
667     if (endX > dpi->width)
668     {
669         endX = dpi->width;
670     }
671 
672     int32_t startY = top - dpi->y;
673     if (startY < 0)
674     {
675         startY = 0;
676     }
677 
678     int32_t endY = bottom - dpi->y + 1;
679     if (endY > dpi->height)
680     {
681         endY = dpi->height;
682     }
683 
684     int32_t width = endX - startX;
685     int32_t height = endY - startY;
686 
687     // 0x2000000
688     // 00678B7E   00678C83
689     // Location in screen buffer?
690     uint8_t* dst = dpi->bits
691         + static_cast<uint32_t>(
692                        (startY / dpi->zoom_level) * ((dpi->width / dpi->zoom_level) + dpi->pitch) + (startX / dpi->zoom_level));
693 
694     // Find colour in colour table?
695     auto paletteMap = GetPaletteMapForColour(EnumValue(palette));
696     if (paletteMap.has_value())
697     {
698         const auto& paletteEntries = paletteMap.value();
699         const int32_t scaled_width = width / dpi->zoom_level;
700         const int32_t step = ((dpi->width / dpi->zoom_level) + dpi->pitch);
701 
702         // Fill the rectangle with the colours from the colour table
703         auto c = height / dpi->zoom_level;
704         for (int32_t i = 0; i < c; i++)
705         {
706             uint8_t* nextdst = dst + step * i;
707             for (int32_t j = 0; j < scaled_width; j++)
708             {
709                 auto index = *(nextdst + j);
710                 *(nextdst + j) = paletteEntries[index];
711             }
712         }
713     }
714 }
715 
DrawLine(rct_drawpixelinfo * dpi,uint32_t colour,const ScreenLine & line)716 void X8DrawingContext::DrawLine(rct_drawpixelinfo* dpi, uint32_t colour, const ScreenLine& line)
717 {
718     gfx_draw_line_software(dpi, line, colour);
719 }
720 
DrawSprite(rct_drawpixelinfo * dpi,uint32_t image,int32_t x,int32_t y,uint32_t tertiaryColour)721 void X8DrawingContext::DrawSprite(rct_drawpixelinfo* dpi, uint32_t image, int32_t x, int32_t y, uint32_t tertiaryColour)
722 {
723     gfx_draw_sprite_software(dpi, ImageId::FromUInt32(image, tertiaryColour), { x, y });
724 }
725 
DrawSpriteRawMasked(rct_drawpixelinfo * dpi,int32_t x,int32_t y,uint32_t maskImage,uint32_t colourImage)726 void X8DrawingContext::DrawSpriteRawMasked(
727     rct_drawpixelinfo* dpi, int32_t x, int32_t y, uint32_t maskImage, uint32_t colourImage)
728 {
729     gfx_draw_sprite_raw_masked_software(dpi, { x, y }, maskImage, colourImage);
730 }
731 
DrawSpriteSolid(rct_drawpixelinfo * dpi,uint32_t image,int32_t x,int32_t y,uint8_t colour)732 void X8DrawingContext::DrawSpriteSolid(rct_drawpixelinfo* dpi, uint32_t image, int32_t x, int32_t y, uint8_t colour)
733 {
734     uint8_t palette[256];
735     std::fill_n(palette, sizeof(palette), colour);
736     palette[0] = 0;
737 
738     const auto spriteCoords = ScreenCoordsXY{ x, y };
739     gfx_draw_sprite_palette_set_software(
740         dpi, ImageId::FromUInt32((image & 0x7FFFF) | IMAGE_TYPE_REMAP), spriteCoords, PaletteMap(palette));
741 }
742 
DrawGlyph(rct_drawpixelinfo * dpi,uint32_t image,int32_t x,int32_t y,const PaletteMap & paletteMap)743 void X8DrawingContext::DrawGlyph(rct_drawpixelinfo* dpi, uint32_t image, int32_t x, int32_t y, const PaletteMap& paletteMap)
744 {
745     gfx_draw_sprite_palette_set_software(dpi, ImageId::FromUInt32(image), { x, y }, paletteMap);
746 }
747