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
33CSurface8::CSurface8()
34{
35 Wdt = Hgt = Pitch = 0;
36 ClipX = ClipY = ClipX2 = ClipY2 = 0;
37 Bits = nullptr;
38 pPal = nullptr;
39}
40
41CSurface8::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
50CSurface8::~CSurface8()
51{
52 Clear();
53}
54
55void 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
64bool CSurface8::HasOwnPal()
65{
66 return pPal != lpDDrawPal;
67}
68
69void 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
74void CSurface8::NoClip()
75{
76 ClipX = 0; ClipY = 0; ClipX2 = Wdt - 1; ClipY2 = Hgt - 1;
77}
78
79void 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
85void 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
90bool 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
113bool 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
179bool 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
215void CSurface8::GetSurfaceSize(int &irX, int &irY)
216{
217 // simply assign stored values
218 irX = Wdt;
219 irY = Hgt;
220}
221
222void 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
231void 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
243struct 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
256static 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
270static 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
295static 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
303const int QuickPolyBufSize = 20;
304CPolyEdge QuickPolyBuf[QuickPolyBufSize];
305
306void 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
406void 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