| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) RedWolf Design |
| 5 | * Copyright (c) 2001, Sven2 |
| 6 | * Copyright (c) 2017-2021, The LegacyClonk Team and contributors |
| 7 | * |
| 8 | * Distributed under the terms of the ISC license; see accompanying file |
| 9 | * "COPYING" for details. |
| 10 | * |
| 11 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 12 | * See accompanying file "TRADEMARK" for details. |
| 13 | * |
| 14 | * To redistribute this file separately, substitute the full license texts |
| 15 | * for the above references. |
| 16 | */ |
| 17 | |
| 18 | // a wrapper class to DirectDraw surfaces |
| 19 | |
| 20 | #include "C4Group.h" |
| 21 | #include <Standard.h> |
| 22 | #include <StdSurface8.h> |
| 23 | #include <Bitmap256.h> |
| 24 | #include <StdPNG.h> |
| 25 | #include <StdDDraw2.h> |
| 26 | #include <CStdFile.h> |
| 27 | #include <Bitmap256.h> |
| 28 | |
| 29 | #include <utility> |
| 30 | |
| 31 | #include "limits.h" |
| 32 | |
| 33 | CSurface8::CSurface8() |
| 34 | { |
| 35 | Wdt = Hgt = Pitch = 0; |
| 36 | ClipX = ClipY = ClipX2 = ClipY2 = 0; |
| 37 | Bits = nullptr; |
| 38 | pPal = nullptr; |
| 39 | } |
| 40 | |
| 41 | CSurface8::CSurface8(int iWdt, int iHgt) |
| 42 | { |
| 43 | Wdt = Hgt = Pitch = 0; |
| 44 | ClipX = ClipY = ClipX2 = ClipY2 = 0; |
| 45 | Bits = nullptr; |
| 46 | pPal = nullptr; |
| 47 | Create(iWdt, iHgt); |
| 48 | } |
| 49 | |
| 50 | CSurface8::~CSurface8() |
| 51 | { |
| 52 | Clear(); |
| 53 | } |
| 54 | |
| 55 | void CSurface8::Clear() |
| 56 | { |
| 57 | // clear bitmap-copy |
| 58 | delete[] Bits; Bits = nullptr; |
| 59 | // clear pal |
| 60 | if (HasOwnPal()) delete pPal; |
| 61 | pPal = nullptr; |
| 62 | } |
| 63 | |
| 64 | bool CSurface8::HasOwnPal() |
| 65 | { |
| 66 | return pPal != lpDDrawPal; |
| 67 | } |
| 68 | |
| 69 | void CSurface8::Box(int iX, int iY, int iX2, int iY2, int iCol) |
| 70 | { |
| 71 | for (int cy = iY; cy <= iY2; cy++) HLine(iX, iX2, iY: cy, iCol); |
| 72 | } |
| 73 | |
| 74 | void CSurface8::NoClip() |
| 75 | { |
| 76 | ClipX = 0; ClipY = 0; ClipX2 = Wdt - 1; ClipY2 = Hgt - 1; |
| 77 | } |
| 78 | |
| 79 | void CSurface8::Clip(int iX, int iY, int iX2, int iY2) |
| 80 | { |
| 81 | ClipX = BoundBy(bval: iX, lbound: 0, rbound: Wdt - 1); ClipY = BoundBy(bval: iY, lbound: 0, rbound: Hgt - 1); |
| 82 | ClipX2 = BoundBy(bval: iX2, lbound: 0, rbound: Wdt - 1); ClipY2 = BoundBy(bval: iY2, lbound: 0, rbound: Hgt - 1); |
| 83 | } |
| 84 | |
| 85 | void CSurface8::HLine(int iX, int iX2, int iY, int iCol) |
| 86 | { |
| 87 | for (int cx = iX; cx <= iX2; cx++) SetPix(iX: cx, iY, byCol: iCol); |
| 88 | } |
| 89 | |
| 90 | bool CSurface8::Create(int iWdt, int iHgt, bool fOwnPal) |
| 91 | { |
| 92 | Clear(); |
| 93 | // check size |
| 94 | if (!iWdt || !iHgt) return false; |
| 95 | Wdt = iWdt; Hgt = iHgt; |
| 96 | |
| 97 | // create/link to pal |
| 98 | if (fOwnPal) |
| 99 | { |
| 100 | pPal = new CStdPalette; |
| 101 | memcpy(dest: pPal, src: &lpDDraw->Pal, n: sizeof(CStdPalette)); |
| 102 | } |
| 103 | else |
| 104 | pPal = &lpDDraw->Pal; |
| 105 | |
| 106 | Bits = new uint8_t[Wdt * Hgt]{}; |
| 107 | Pitch = Wdt; |
| 108 | // update clipping |
| 109 | NoClip(); |
| 110 | return true; |
| 111 | } |
| 112 | |
| 113 | bool CSurface8::Read(C4Group &hGroup, bool fOwnPal) |
| 114 | { |
| 115 | int cnt, lcnt, iLineRest; |
| 116 | CBitmap256Info BitmapInfo; |
| 117 | // read bmpinfo-header |
| 118 | if (!hGroup.Read(pBuffer: &BitmapInfo, iSize: sizeof(CBitmapInfo))) return false; |
| 119 | // is it 8bpp? |
| 120 | if (BitmapInfo.Info.biBitCount == 8) |
| 121 | { |
| 122 | if (!hGroup.Read(pBuffer: (reinterpret_cast<uint8_t *>(&BitmapInfo)) + sizeof(CBitmapInfo), iSize: sizeof(BitmapInfo) - sizeof(CBitmapInfo))) return false; |
| 123 | if (!hGroup.Advance(iOffset: BitmapInfo.FileBitsOffset())) return false; |
| 124 | } |
| 125 | else |
| 126 | { |
| 127 | // read 24bpp |
| 128 | if (BitmapInfo.Info.biBitCount != 24) return false; |
| 129 | if (!hGroup.Advance(iOffset: (static_cast<CBitmapInfo>(BitmapInfo)).FileBitsOffset())) return false; |
| 130 | } |
| 131 | |
| 132 | // Create and lock surface |
| 133 | if (!Create(iWdt: BitmapInfo.Info.biWidth, iHgt: BitmapInfo.Info.biHeight, fOwnPal)) return false; |
| 134 | |
| 135 | if (BitmapInfo.Info.biBitCount == 8) |
| 136 | { |
| 137 | if (HasOwnPal()) |
| 138 | { |
| 139 | // Copy palette |
| 140 | for (cnt = 0; cnt < 256; cnt++) |
| 141 | { |
| 142 | pPal->Colors[cnt * 3 + 0] = BitmapInfo.Colors[cnt].rgbRed; |
| 143 | pPal->Colors[cnt * 3 + 1] = BitmapInfo.Colors[cnt].rgbGreen; |
| 144 | pPal->Colors[cnt * 3 + 2] = BitmapInfo.Colors[cnt].rgbBlue; |
| 145 | pPal->Alpha[cnt] = 0; |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | // create line buffer |
| 151 | int iBufSize = DWordAligned(val: BitmapInfo.Info.biWidth * BitmapInfo.Info.biBitCount / 8); |
| 152 | uint8_t *pBuf = new uint8_t[iBufSize]; |
| 153 | // Read lines |
| 154 | iLineRest = DWordAligned(val: BitmapInfo.Info.biWidth) - BitmapInfo.Info.biWidth; |
| 155 | for (lcnt = Hgt - 1; lcnt >= 0; lcnt--) |
| 156 | { |
| 157 | if (!hGroup.Read(pBuffer: pBuf, iSize: iBufSize)) |
| 158 | { |
| 159 | Clear(); delete[] pBuf; return false; |
| 160 | } |
| 161 | uint8_t *pPix = pBuf; |
| 162 | for (int x = 0; x < BitmapInfo.Info.biWidth; ++x) |
| 163 | switch (BitmapInfo.Info.biBitCount) |
| 164 | { |
| 165 | case 8: |
| 166 | SetPix(iX: x, iY: lcnt, byCol: *pPix++); |
| 167 | break; |
| 168 | case 24: |
| 169 | return false; |
| 170 | break; |
| 171 | } |
| 172 | } |
| 173 | // free buffer again |
| 174 | delete[] pBuf; |
| 175 | |
| 176 | return true; |
| 177 | } |
| 178 | |
| 179 | bool CSurface8::Save(const char *szFilename, uint8_t *bpPalette) |
| 180 | { |
| 181 | CBitmap256Info BitmapInfo; |
| 182 | BitmapInfo.Set(iWdt: Wdt, iHgt: Hgt, bypPalette: bpPalette ? bpPalette : pPal->Colors); |
| 183 | |
| 184 | // Create file & write info |
| 185 | CStdFile hFile; |
| 186 | |
| 187 | if (!hFile.Create(szFileName: szFilename) |
| 188 | || !hFile.Write(pBuffer: &BitmapInfo, iSize: sizeof(BitmapInfo))) |
| 189 | { |
| 190 | return false; |
| 191 | } |
| 192 | |
| 193 | // Write lines |
| 194 | char bpEmpty[4]{}; int iEmpty = DWordAligned(val: Wdt) - Wdt; |
| 195 | for (int cnt = Hgt - 1; cnt >= 0; cnt--) |
| 196 | { |
| 197 | if (!hFile.Write(pBuffer: Bits + (Pitch * cnt), iSize: Wdt)) |
| 198 | { |
| 199 | return false; |
| 200 | } |
| 201 | if (iEmpty) |
| 202 | if (!hFile.Write(pBuffer: bpEmpty, iSize: iEmpty)) |
| 203 | { |
| 204 | return false; |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | // Close file |
| 209 | hFile.Close(); |
| 210 | |
| 211 | // Success |
| 212 | return true; |
| 213 | } |
| 214 | |
| 215 | void CSurface8::GetSurfaceSize(int &irX, int &irY) |
| 216 | { |
| 217 | // simply assign stored values |
| 218 | irX = Wdt; |
| 219 | irY = Hgt; |
| 220 | } |
| 221 | |
| 222 | void CSurface8::ClearBox8Only(int iX, int iY, int iWdt, int iHgt) |
| 223 | { |
| 224 | // clear rect; assume clip already |
| 225 | for (int y = iY; y < iY + iHgt; ++y) |
| 226 | for (int x = iX; x < iX + iWdt; ++x) |
| 227 | Bits[y * Pitch + x] = 0; |
| 228 | // done |
| 229 | } |
| 230 | |
| 231 | void CSurface8::Circle(int x, int y, int r, uint8_t col) |
| 232 | { |
| 233 | for (int ycnt = -r; ycnt < r; ycnt++) |
| 234 | { |
| 235 | int lwdt = static_cast<int>(sqrt(x: float(r * r - ycnt * ycnt))); |
| 236 | for (int xcnt = 2 * lwdt - 1; xcnt >= 0; xcnt--) |
| 237 | SetPix(iX: x - lwdt + xcnt, iY: y + ycnt, byCol: col); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | /* Polygon drawing code extracted from ALLEGRO by Shawn Hargreaves */ |
| 242 | |
| 243 | struct CPolyEdge // An edge for the polygon drawer |
| 244 | { |
| 245 | int y; // Current (starting at the top) y position |
| 246 | int bottom; // bottom y position of this edge |
| 247 | int x; // Fixed point x position |
| 248 | int dx; // Fixed point x gradient |
| 249 | int w; // Width of line segment |
| 250 | struct CPolyEdge *prev; // Doubly linked list |
| 251 | struct CPolyEdge *next; |
| 252 | }; |
| 253 | |
| 254 | #define POLYGON_FIX_SHIFT 16 |
| 255 | |
| 256 | static void fill_edge_structure(CPolyEdge *edge, int *i1, int *i2) |
| 257 | { |
| 258 | if (i2[1] < i1[1]) std::swap(a&: i1, b&: i2); |
| 259 | edge->y = i1[1]; |
| 260 | edge->bottom = i2[1] - 1; |
| 261 | edge->dx = ((i2[0] - i1[0]) << POLYGON_FIX_SHIFT) / (i2[1] - i1[1]); |
| 262 | edge->x = (i1[0] << POLYGON_FIX_SHIFT) + (1 << (POLYGON_FIX_SHIFT - 1)) - 1; |
| 263 | edge->prev = nullptr; |
| 264 | edge->next = nullptr; |
| 265 | if (edge->dx < 0) |
| 266 | edge->x += std::min<int>(a: edge->dx + (1 << POLYGON_FIX_SHIFT), b: 0); |
| 267 | edge->w = std::max<int>(a: Abs(val: edge->dx) - (1 << POLYGON_FIX_SHIFT), b: 0); |
| 268 | } |
| 269 | |
| 270 | static CPolyEdge *add_edge(CPolyEdge *list, CPolyEdge *edge, int sort_by_x) |
| 271 | { |
| 272 | CPolyEdge *pos = list; |
| 273 | CPolyEdge *prev = nullptr; |
| 274 | if (sort_by_x) |
| 275 | { |
| 276 | while ((pos) && (pos->x + pos->w / 2 < edge->x + edge->w / 2)) |
| 277 | { |
| 278 | prev = pos; pos = pos->next; |
| 279 | } |
| 280 | } |
| 281 | else |
| 282 | { |
| 283 | while ((pos) && (pos->y < edge->y)) |
| 284 | { |
| 285 | prev = pos; pos = pos->next; |
| 286 | } |
| 287 | } |
| 288 | edge->next = pos; |
| 289 | edge->prev = prev; |
| 290 | if (pos) pos->prev = edge; |
| 291 | if (prev) { prev->next = edge; return list; } |
| 292 | else return edge; |
| 293 | } |
| 294 | |
| 295 | static CPolyEdge *remove_edge(CPolyEdge *list, CPolyEdge *edge) |
| 296 | { |
| 297 | if (edge->next) edge->next->prev = edge->prev; |
| 298 | if (edge->prev) { edge->prev->next = edge->next; return list; } |
| 299 | else return edge->next; |
| 300 | } |
| 301 | |
| 302 | // Global polygon quick buffer |
| 303 | const int QuickPolyBufSize = 20; |
| 304 | CPolyEdge QuickPolyBuf[QuickPolyBufSize]; |
| 305 | |
| 306 | void CSurface8::Polygon(int iNum, int *ipVtx, int iCol) |
| 307 | { |
| 308 | // Variables for polygon drawer |
| 309 | int c, x1, x2, y; |
| 310 | int top = INT_MAX; |
| 311 | int bottom = INT_MIN; |
| 312 | int *i1, *i2; |
| 313 | CPolyEdge *edge, *next_edge, *edgebuf; |
| 314 | CPolyEdge *active_edges = nullptr; |
| 315 | CPolyEdge *inactive_edges = nullptr; |
| 316 | bool use_qpb = false; |
| 317 | |
| 318 | // Poly Buf |
| 319 | if (iNum <= QuickPolyBufSize) |
| 320 | { |
| 321 | edgebuf = QuickPolyBuf; use_qpb = true; |
| 322 | } |
| 323 | else |
| 324 | { |
| 325 | edgebuf = new CPolyEdge[iNum]; |
| 326 | } |
| 327 | |
| 328 | // Fill the edge table |
| 329 | edge = edgebuf; |
| 330 | i1 = ipVtx; |
| 331 | i2 = ipVtx + (iNum - 1) * 2; |
| 332 | for (c = 0; c < iNum; c++) |
| 333 | { |
| 334 | if (i1[1] != i2[1]) |
| 335 | { |
| 336 | fill_edge_structure(edge, i1, i2); |
| 337 | if (edge->bottom >= edge->y) |
| 338 | { |
| 339 | if (edge->y < top) top = edge->y; |
| 340 | if (edge->bottom > bottom) bottom = edge->bottom; |
| 341 | inactive_edges = add_edge(list: inactive_edges, edge, sort_by_x: false); |
| 342 | edge++; |
| 343 | } |
| 344 | } |
| 345 | i2 = i1; i1 += 2; |
| 346 | } |
| 347 | |
| 348 | // For each scanline in the polygon... |
| 349 | for (c = top; c <= bottom; c++) |
| 350 | { |
| 351 | // Check for newly active edges |
| 352 | edge = inactive_edges; |
| 353 | while ((edge) && (edge->y == c)) |
| 354 | { |
| 355 | next_edge = edge->next; |
| 356 | inactive_edges = remove_edge(list: inactive_edges, edge); |
| 357 | active_edges = add_edge(list: active_edges, edge, sort_by_x: true); |
| 358 | edge = next_edge; |
| 359 | } |
| 360 | |
| 361 | // Draw horizontal line segments |
| 362 | edge = active_edges; |
| 363 | while ((edge) && (edge->next)) |
| 364 | { |
| 365 | x1 = edge->x >> POLYGON_FIX_SHIFT; |
| 366 | x2 = (edge->next->x + edge->next->w) >> POLYGON_FIX_SHIFT; |
| 367 | y = c; |
| 368 | // Fix coordinates |
| 369 | if (x1 > x2) std::swap(a&: x1, b&: x2); |
| 370 | // Set line |
| 371 | for (int xcnt = x2 - x1; xcnt >= 0; xcnt--) SetPix(iX: x1 + xcnt, iY: y, byCol: iCol); |
| 372 | edge = edge->next->next; |
| 373 | } |
| 374 | |
| 375 | // Update edges, sorting and removing dead ones |
| 376 | edge = active_edges; |
| 377 | while (edge) |
| 378 | { |
| 379 | next_edge = edge->next; |
| 380 | if (c >= edge->bottom) |
| 381 | { |
| 382 | active_edges = remove_edge(list: active_edges, edge); |
| 383 | } |
| 384 | else |
| 385 | { |
| 386 | edge->x += edge->dx; |
| 387 | while ((edge->prev) && (edge->x + edge->w / 2 < edge->prev->x + edge->prev->w / 2)) |
| 388 | { |
| 389 | if (edge->next) edge->next->prev = edge->prev; |
| 390 | edge->prev->next = edge->next; |
| 391 | edge->next = edge->prev; |
| 392 | edge->prev = edge->prev->prev; |
| 393 | edge->next->prev = edge; |
| 394 | if (edge->prev) edge->prev->next = edge; |
| 395 | else active_edges = edge; |
| 396 | } |
| 397 | } |
| 398 | edge = next_edge; |
| 399 | } |
| 400 | } |
| 401 | |
| 402 | // Clear scratch memory |
| 403 | if (!use_qpb) delete[] edgebuf; |
| 404 | } |
| 405 | |
| 406 | void CSurface8::AllowColor(uint8_t iRngLo, uint8_t iRngHi, bool fAllowZero) |
| 407 | { |
| 408 | // change colors |
| 409 | int xcnt, ycnt; |
| 410 | if (iRngHi < iRngLo) return; |
| 411 | for (ycnt = 0; ycnt < Hgt; ycnt++) |
| 412 | { |
| 413 | for (xcnt = 0; xcnt < Wdt; xcnt++) |
| 414 | { |
| 415 | uint8_t px = GetPix(iX: xcnt, iY: ycnt); |
| 416 | if (px || !fAllowZero) |
| 417 | if ((px < iRngLo) || (px > iRngHi)) |
| 418 | SetPix(iX: xcnt, iY: ycnt, byCol: iRngLo + px % (iRngHi - iRngLo + 1)); |
| 419 | } |
| 420 | } |
| 421 | } |
| 422 | |