openclonk/src/platform/StdSurface8.cpp

425 lines
9.8 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000 Matthes Bender
* Copyright (c) 2002, 2007 Sven Eberhardt
* Copyright (c) 2007, 2009-2010 Günther Brammer
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
*
* Portions might be copyrighted by other authors who have contributed
* to OpenClonk.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
* See isc_license.txt for full license and disclaimer.
*
* "Clonk" is a registered trademark of Matthes Bender.
* See clonk_trademark_license.txt for full license.
*/
// a wrapper class to DirectDraw surfaces
#include "C4Include.h"
#include <StdSurface8.h>
#include <Bitmap256.h>
#include <StdPNG.h>
#include <StdDDraw2.h>
#include <CStdFile.h>
#include <Bitmap256.h>
#include "limits.h"
CSurface8::CSurface8()
{
Wdt=Hgt=Pitch=0;
ClipX=ClipY=ClipX2=ClipY2=0;
Bits=NULL;
pPal=NULL;
}
CSurface8::CSurface8(int iWdt, int iHgt)
{
Wdt=Hgt=Pitch=0;
ClipX=ClipY=ClipX2=ClipY2=0;
Bits=NULL;
pPal=NULL;
Create(iWdt, iHgt);
}
CSurface8::~CSurface8()
{
Clear();
}
void CSurface8::Clear()
{
// clear bitmap-copy
delete [] Bits; Bits=NULL;
// clear pal
delete pPal;
pPal=NULL;
}
void CSurface8::Box(int iX, int iY, int iX2, int iY2, int iCol)
{
for (int cy=iY; cy<=iY2; cy++) HLine(iX,iX2,cy,iCol);
}
void CSurface8::NoClip()
{
ClipX=0; ClipY=0; ClipX2=Wdt-1; ClipY2=Hgt-1;
}
void CSurface8::Clip(int iX, int iY, int iX2, int iY2)
{
ClipX=BoundBy(iX,0,Wdt-1); ClipY=BoundBy(iY,0,Hgt-1);
ClipX2=BoundBy(iX2,0,Wdt-1); ClipY2=BoundBy(iY2,0,Hgt-1);
}
void CSurface8::HLine(int iX, int iX2, int iY, int iCol)
{
for (int cx=iX; cx<=iX2; cx++) SetPix(cx,iY,iCol);
}
bool CSurface8::Create(int iWdt, int iHgt)
{
Clear();
// check size
if (!iWdt || !iHgt) return false;
Wdt=iWdt; Hgt=iHgt;
// create pal
pPal = new CStdPalette;
if (!pPal) return false;
Bits=new BYTE[Wdt*Hgt];
if (!Bits) return false;
ZeroMemory(Bits, Wdt*Hgt);
Pitch=Wdt;
// update clipping
NoClip();
return true;
}
bool CSurface8::Read(CStdStream &hGroup)
{
int cnt,lcnt,iLineRest;
CBitmap256Info BitmapInfo;
// read bmpinfo-header
if (!hGroup.Read(&BitmapInfo,sizeof(CBitmapInfo))) return false;
// is it 8bpp?
if (BitmapInfo.Info.biBitCount == 8)
{
if (!hGroup.Read(((BYTE *) &BitmapInfo)+sizeof(CBitmapInfo),sizeof(BitmapInfo)-sizeof(CBitmapInfo))) return false;
if (!hGroup.Advance(BitmapInfo.FileBitsOffset())) return false;
}
else
{
// read 24bpp
if (BitmapInfo.Info.biBitCount != 24) return false;
if (!hGroup.Advance(((CBitmapInfo) BitmapInfo).FileBitsOffset())) return false;
}
// no 8bpp-surface in newgfx!
// needs to be kept for some special surfaces
//f8BitSfc=false;
// Create and lock surface
if (!Create(BitmapInfo.Info.biWidth,BitmapInfo.Info.biHeight)) return false;
if (BitmapInfo.Info.biBitCount == 8)
{
// Copy palette
for (cnt=0; cnt<256; cnt++)
{
pPal->Colors[cnt*3+0]=BitmapInfo.Colors[cnt].rgbRed;
pPal->Colors[cnt*3+1]=BitmapInfo.Colors[cnt].rgbGreen;
pPal->Colors[cnt*3+2]=BitmapInfo.Colors[cnt].rgbBlue;
pPal->Alpha[cnt]=0xff;
}
}
// create line buffer
int iBufSize=DWordAligned(BitmapInfo.Info.biWidth*BitmapInfo.Info.biBitCount/8);
BYTE *pBuf = new BYTE[iBufSize];
// Read lines
iLineRest = DWordAligned(BitmapInfo.Info.biWidth) - BitmapInfo.Info.biWidth;
for (lcnt=Hgt-1; lcnt>=0; lcnt--)
{
if (!hGroup.Read(pBuf, iBufSize))
{ Clear(); delete [] pBuf; return false; }
BYTE *pPix=pBuf;
for (int x=0; x<BitmapInfo.Info.biWidth; ++x)
switch (BitmapInfo.Info.biBitCount)
{
case 8:
SetPix(x, lcnt, *pPix++);
break;
case 24:
return false;
break;
}
}
// free buffer again
delete [] pBuf;
return true;
}
bool CSurface8::Save(const char *szFilename, BYTE *bpPalette)
{
CBitmap256Info BitmapInfo;
BitmapInfo.Set(Wdt,Hgt,bpPalette ? bpPalette : pPal->Colors);
// Create file & write info
CStdFile hFile;
if ( !hFile.Create(szFilename)
|| !hFile.Write(&BitmapInfo,sizeof(BitmapInfo)) )
{ return false; }
// Write lines
char bpEmpty[4]; int iEmpty = DWordAligned(Wdt)-Wdt;
for (int cnt=Hgt-1; cnt>=0; cnt--)
{
if (!hFile.Write(Bits+(Pitch*cnt),Wdt))
{ return false; }
if (iEmpty)
if (!hFile.Write(bpEmpty,iEmpty))
{ return false; }
}
// Close file
hFile.Close();
// Success
return true;
}
void CSurface8::MapBytes(BYTE *bpMap)
{
if (!bpMap) return;
for (int cnt=0; cnt<Wdt*Hgt; cnt++) SetPix(cnt%Wdt, cnt/Wdt, bpMap[GetPix(cnt%Wdt, cnt/Wdt)]);
}
void CSurface8::GetSurfaceSize(int &irX, int &irY)
{
// simply assign stored values
irX=Wdt;
irY=Hgt;
}
void CSurface8::ClearBox8Only(int iX, int iY, int iWdt, int iHgt)
{
// clear rect; assume clip already
for (int y=iY; y<iY+iHgt; ++y)
for (int x=iX; x<iX+iWdt; ++x)
Bits[y*Pitch+x] = 0;
// done
}
void CSurface8::Circle(int x, int y, int r, BYTE col)
{
for (int ycnt=-r; ycnt<r; ycnt++)
{
int lwdt = (int) sqrt(float(r*r-ycnt*ycnt));
for (int xcnt = 2 * lwdt - 1; xcnt >= 0; xcnt--)
SetPix(x - lwdt + xcnt, y + ycnt, col);
}
}
/* Polygon drawing code extracted from ALLEGRO by Shawn Hargreaves */
struct CPolyEdge // An edge for the polygon drawer
{
int y; // Current (starting at the top) y position
int bottom; // bottom y position of this edge
int x; // Fixed point x position
int dx; // Fixed point x gradient
int w; // Width of line segment
struct CPolyEdge *prev; // Doubly linked list
struct CPolyEdge *next;
};
#define POLYGON_FIX_SHIFT 16
static void fill_edge_structure(CPolyEdge *edge, int *i1, int *i2)
{
if (i2[1] < i1[1]) // Swap
{ int *t=i1; i1=i2; i2=t; }
edge->y = i1[1];
edge->bottom = i2[1] - 1;
edge->dx = ((i2[0] - i1[0]) << POLYGON_FIX_SHIFT) / (i2[1] - i1[1]);
edge->x = (i1[0] << POLYGON_FIX_SHIFT) + (1<<(POLYGON_FIX_SHIFT-1)) - 1;
edge->prev = NULL;
edge->next = NULL;
if (edge->dx < 0)
edge->x += Min<int>(edge->dx+(1<<POLYGON_FIX_SHIFT), 0);
edge->w = Max<int>(Abs(edge->dx)-(1<<POLYGON_FIX_SHIFT), 0);
}
static CPolyEdge *add_edge(CPolyEdge *list, CPolyEdge *edge, int sort_by_x)
{
CPolyEdge *pos = list;
CPolyEdge *prev = NULL;
if (sort_by_x)
{
while ((pos) && (pos->x+pos->w/2 < edge->x+edge->w/2))
{ prev = pos; pos = pos->next; }
}
else
{
while ((pos) && (pos->y < edge->y))
{ prev = pos; pos = pos->next; }
}
edge->next = pos;
edge->prev = prev;
if (pos) pos->prev = edge;
if (prev) { prev->next = edge; return list; }
else return edge;
}
static CPolyEdge *remove_edge(CPolyEdge *list, CPolyEdge *edge)
{
if (edge->next) edge->next->prev = edge->prev;
if (edge->prev) { edge->prev->next = edge->next; return list; }
else return edge->next;
}
// Global polygon quick buffer
const int QuickPolyBufSize = 20;
CPolyEdge QuickPolyBuf[QuickPolyBufSize];
void CSurface8::Polygon(int iNum, int *ipVtx, int iCol, uint8_t *conversion_table)
{
// Variables for polygon drawer
int c,x1,x2,y;
int top = INT_MAX;
int bottom = INT_MIN;
int *i1, *i2;
CPolyEdge *edge, *next_edge, *edgebuf;
CPolyEdge *active_edges = NULL;
CPolyEdge *inactive_edges = NULL;
bool use_qpb=false;
// Poly Buf
if (iNum<=QuickPolyBufSize)
{ edgebuf=QuickPolyBuf; use_qpb=true; }
else if (!(edgebuf = new CPolyEdge [iNum])) { return; }
// Fill the edge table
edge = edgebuf;
i1 = ipVtx;
i2 = ipVtx + (iNum-1) * 2;
for (c=0; c<iNum; c++)
{
if (i1[1] != i2[1])
{
fill_edge_structure(edge, i1, i2);
if (edge->bottom >= edge->y)
{
if (edge->y < top) top = edge->y;
if (edge->bottom > bottom) bottom = edge->bottom;
inactive_edges = add_edge(inactive_edges, edge, false);
edge++;
}
}
i2 = i1; i1 += 2;
}
// For each scanline in the polygon...
for (c=top; c<=bottom; c++)
{
// Check for newly active edges
edge = inactive_edges;
while ((edge) && (edge->y == c))
{
next_edge = edge->next;
inactive_edges = remove_edge(inactive_edges, edge);
active_edges = add_edge(active_edges, edge, true);
edge = next_edge;
}
// Draw horizontal line segments
edge = active_edges;
while ((edge) && (edge->next))
{
x1=edge->x>>POLYGON_FIX_SHIFT;
x2=(edge->next->x+edge->next->w)>>POLYGON_FIX_SHIFT;
y=c;
// Fix coordinates
if (x1>x2) Swap(x1,x2);
// Set line
if (conversion_table)
for (int xcnt=x2-x1; xcnt>=0; xcnt--) SetPix(x1+xcnt, y, conversion_table[uint8_t(GetPix(x1+xcnt, y))]);
else
for (int xcnt=x2-x1; xcnt>=0; xcnt--) SetPix(x1+xcnt, y, iCol);
edge = edge->next->next;
}
// Update edges, sorting and removing dead ones
edge = active_edges;
while (edge)
{
next_edge = edge->next;
if (c >= edge->bottom)
{
active_edges = remove_edge(active_edges, edge);
}
else
{
edge->x += edge->dx;
while ((edge->prev) && (edge->x+edge->w/2 < edge->prev->x+edge->prev->w/2))
{
if (edge->next) edge->next->prev = edge->prev;
edge->prev->next = edge->next;
edge->next = edge->prev;
edge->prev = edge->prev->prev;
edge->next->prev = edge;
if (edge->prev) edge->prev->next = edge;
else active_edges = edge;
}
}
edge = next_edge;
}
}
// Clear scratch memory
if (!use_qpb) delete [] edgebuf;
}
void CSurface8::AllowColor(BYTE iRngLo, BYTE iRngHi, bool fAllowZero)
{
// change colors
int xcnt,ycnt;
if (iRngHi<iRngLo) return;
for (ycnt=0; ycnt<Hgt; ycnt++)
{
for (xcnt=0; xcnt<Wdt; xcnt++)
{
BYTE px=GetPix(xcnt,ycnt);
if (px || !fAllowZero)
if ((px<iRngLo) || (px>iRngHi))
SetPix(xcnt, ycnt, iRngLo + px % (iRngHi-iRngLo+1));
}
}
}
void CSurface8::SetBuffer(BYTE *pbyToBuf, int Wdt, int Hgt, int Pitch)
{
// release old
Clear();
// set new
this->Wdt=Wdt;
this->Hgt=Hgt;
this->Pitch=Pitch;
this->Bits = pbyToBuf;
NoClip();
}
void CSurface8::ReleaseBuffer()
{
this->Bits = NULL;
Clear();
}