AbePralle-FGB/Tools/MakeDiscoLights/gk.cpp

1748 lines
40 KiB
C++

#include "gk.h"
#include <stdlib.h>
extern ofstream logFile;
gkTransparencyTable gkBitmap::tranTable;
struct BMP_header{
gkLONG fSize; //54 byte header + 4*numColors (1-8bit) + (w*h*bpp)/8
gkWORD zero1, zero2; //0,0
gkLONG offsetBytes; //should be header (54) plus Palette Size
gkLONG headerSize; //size of remaining header (40)
gkLONG width, height; //w,h in pixels
gkWORD planes, bpp; //plane=1, bpp=1,2,4, or most commonly 8
gkLONG compression, imageSize; //compression to zero, size is w*h(8bit)
gkLONG xpels, ypels, zero3, zero4; //set to 0,0,0,0
};
#ifdef _WIN32
gkFileInputBuffer::gkFileInputBuffer(char *filename){
buffer = 0;
stin = 0;
exists = 0;
HANDLE handle = CreateFile(filename,GENERIC_READ,FILE_SHARE_READ,0,
OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
if(handle==INVALID_HANDLE_VALUE) return;
int size = GetFileSize(handle,0);
buffer = new char[size];
unsigned long bytesRead;
ReadFile(handle,buffer,size,&bytesRead,0);
CloseHandle(handle);
stin = new istrstream(buffer,size);
exists = 1;
}
gkFileInputBuffer::~gkFileInputBuffer(){
if(stin){
delete stin;
stin = 0;
}
if(buffer) delete buffer;
buffer = 0;
}
gkFileOutputBuffer::gkFileOutputBuffer(char *filename){
exists = 0;
handle = CreateFile(filename,GENERIC_WRITE,FILE_SHARE_READ,0,
CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
if(handle==INVALID_HANDLE_VALUE){
return;
}
exists = 1;
}
gkFileOutputBuffer::~gkFileOutputBuffer(){
if(handle==INVALID_HANDLE_VALUE) return;
unsigned long written;
WriteFile(handle,stout.str(),stout.tellp(),&written,0);
//unfreeze the ostrstream so it will delete the array
stout.rdbuf()->freeze(0);
CloseHandle(handle);
}
#endif
gkList::gkList(void){
head = tail = 0;
numItems = 0;
}
gkList::~gkList(void){
Reset();
}
void gkList::Reset(){
gkListItem *cur, *next;
for(cur=head; cur; cur=next){
next = cur->NextItem();
delete cur;
}
head = tail = 0;
}
gkListItem *gkList::GetItem(int n){
if(n >= numItems) return 0;
gkListItem *cur;
cur = head;
while(n-- > 0){
cur = cur->NextItem();
}
return cur;
}
void gkList::AddItem(gkListItem *li){
numItems++;
if(!head){
head = tail = li;
}else{
tail->SetNextItem(li);
tail = tail->NextItem();
li->SetNextItem(0);
}
}
void gkList::DeleteItem(gkListItem *item){
gkListItem *cur, *prev;
//is it the head?
if(item==head){
head=head->GetNext();
if(!head) tail = 0;
delete item;
return;
}
//must be in body or tail
prev = 0;
for(cur=head; cur; cur=cur->GetNext()){
if(cur==item){
prev->SetNext(cur->GetNext());
if(cur==tail) tail=prev;
delete cur;
break;
}
prev = cur;
}
}
int gkIO::ReadWord(istream &in){
int retval = in.get() << 8;
return retval | (in.get() & 0xff);
}
int gkIO::ReadLong(istream &in){
int retval = ReadWord(in) << 16;
return retval | (ReadWord(in) & 0xffff);
}
char *gkIO::ReadString(istream &in){
static char st[80];
int len = ReadWord(in);
if(!len) return 0;
in.read(st,len);
st[len] = 0;
return st;
}
char *gkIO::ReadNewString(istream &in){
int len = ReadWord(in);
if(!len) return 0;
char *st = new char[len+1];
in.read(st,len);
st[len] = 0;
return st;
}
void gkIO::WriteLong(ostream &out, int n){
WriteWord(out, n>>16);
WriteWord(out, n);
}
void gkIO::WriteWord(ostream &out, int n){
out << (char) ((n>>8)&0xff);
out << (char) (n&0xff);
}
void gkIO::WriteString(ostream &out, char *st){
WriteWord(out,strlen(st));
out.write(st,strlen(st));
}
int gkRGB::Equals(int _r, int _g, int _b){
return _r==color.bytes.r && _g==color.bytes.g && _b==color.bytes.b;
}
void gkRGB::Combine(gkRGB c2, int alpha){
//mix alpha
color.bytes.r +=
gkBitmap::tranTable.LookupTransparencyOffset(c2.GetR()-GetR(), alpha);
color.bytes.g +=
gkBitmap::tranTable.LookupTransparencyOffset(c2.GetG()-GetG(), alpha);
color.bytes.b +=
gkBitmap::tranTable.LookupTransparencyOffset(c2.GetB()-GetB(), alpha);
}
void gkRGB::SetFromGBColor(int gbColor){
color.bytes.r = (gbColor<<3) & 0xf8;
color.bytes.g = (gbColor>>2) & 0xf8;
color.bytes.b = (gbColor>>7) & 0xf8;
}
gkPalGenItem::gkPalGenItem(gkRGB _color){
color = _color;
occurrences = 1;
nextItem = 0;
}
gkPalGenItem::~gkPalGenItem(){
}
gkRGB gkPalGenItem::GetColor(){
return color;
}
void gkPalGenItem::AddOccurrence(){
occurrences++;
}
int gkPalGenItem::GetOccurrences(){
return occurrences;
}
void gkPalGenItem::SetOccurrences(int n){
occurrences = n;
}
void gkPalGenItem::SetNextItem(gkPalGenItem *item){
nextItem = item;
}
gkPalGenItem *gkPalGenItem::GetNextItem(){
return nextItem;
}
int gkPalGenItem::GetCount(){
gkPalGenItem *cur;
int count = 0;
for(cur=this; cur; cur=cur->nextItem){
if(cur->occurrences) count++;
}
return count;
}
int gkPalGenItem::SortCallback(const void *e1, const void *e2){
gkPalGenItem *i1 = *((gkPalGenItem**) e1);
gkPalGenItem *i2 = *((gkPalGenItem**) e2);
if(i1->occurrences > i2->occurrences) return -1;
if(i1->occurrences == i2->occurrences) return 0;
return 1;
}
gkPaletteGenerator::gkPaletteGenerator(){
int i;
for(i=0; i<52; i++){
colorCube[i] = 0;
}
}
gkPaletteGenerator::~gkPaletteGenerator(){
Reset();
}
void gkPaletteGenerator::Reset(){
int i;
for(i=0; i<52; i++){
if(colorCube[i]){
gkPalGenItem *cur, *next;
for(cur=colorCube[i]; cur; cur=next){
next = cur->GetNextItem();
delete cur;
}
colorCube[i] = 0;
}
}
}
void gkPaletteGenerator::AddColor(gkRGB color){
int i = GetHash(color);
if(!colorCube[i]){
colorCube[i] = new gkPalGenItem(color);
}else{
gkPalGenItem *cur, *prev;
for(cur=colorCube[i]; cur; cur=cur->GetNextItem()){
if((cur->GetColor()) == color){
cur->AddOccurrence(); //Color already in list
break;
}
prev = cur;
}
if(!cur){ //color not in list
prev->SetNextItem(new gkPalGenItem(color));
}
}
}
void gkPaletteGenerator::CreatePalette(gkRGB *palette, int numEntries){
if(numEntries<=0) return;
//Set all entries to black
int i;
for(i=0; i<numEntries; i++) palette[i] = gkRGB(0,0,0);
//1 entry from every this many indices
double scaleFactor = 52.0 / numEntries;
int curEntry = 0;
while(curEntry < numEntries){
//Get first & last array indices for this section
int first = (int) (scaleFactor * (curEntry));
int nextEntry = (int) (scaleFactor * (curEntry+1));
int last = nextEntry - 1;
if(last < first) last = first;
//Count total # of colors in this section
int count = 0;
for(i=first; i<=last; i++){
if(colorCube[i]) count += colorCube[i]->GetCount();
}
while(!count){ //if no colors yet expand area of inclusion
if(first==0 && last==51) return; //no colors anywhere!
if(first>0){
first--;
if(colorCube[first]) count += colorCube[first]->GetCount();
}
if(last<51){
last++;
if(colorCube[last]) count += colorCube[last]->GetCount();
}
}
//Create an array to hold all the colors for sorting purposes
gkPalGenItem **colors = new gkPalGenItem*[count];
gkPalGenItem *cur;
i = 0;
int j;
for(j=first; j<=last; j++){
if(colorCube[j]){
for(cur=colorCube[j]; cur; cur=cur->GetNextItem()){
if(cur->GetOccurrences()) colors[i++] = cur;
}
}
}
//figure out how many colors will come from this section of the cube
int numToGrab = 1;
int tempCurEntry = curEntry;
while(nextEntry==first && tempCurEntry<(52-1)){
tempCurEntry++;
nextEntry = (int) (scaleFactor * (tempCurEntry+1));
numToGrab++;
}
if(numToGrab > count) numToGrab = count;
//sort colors into descending order and pick "num" most frequent
qsort(colors, count, sizeof(gkPalGenItem*), gkPalGenItem::SortCallback);
for(i=0; i<numToGrab; i++){
palette[curEntry++] = colors[i]->GetColor();
colors[i]->SetOccurrences(0);
}
//delete sorting table
delete colors;
}
}
int gkPaletteGenerator::GetNumColors(){
int num = 0, i;
for(i=0; i<52; i++)
if(colorCube[i]) num += colorCube[i]->GetCount();
return num;
}
int gkPaletteGenerator::GetHash(gkRGB color){
int r = color.GetR() >> 6; //rough categories 0-3
int g = color.GetG() >> 6;
int b = color.GetB() >> 6;
int highest = r;
if(g > highest) highest = g;
if(b > highest) highest = b;
int hash;
// r > (g < b)
// r > (g = b)
// r > (g > b)
// g > (r < b)
// g > (r = b)
// g > (r > b)
// b > (r < g)
// b > (r = g)
// b > (r > g)
// (r = b) > g
// (r = g) > b
// (g = b) > r
// (r = g) = b
if(r > g && r > b){ //red high
if(g < b) hash = 0; // r > (g < b)
else if(g==b) hash = 1; // r > (g = b)
else hash = 2; // r > (g > b)
}else if(g > r && g > b){ //green high
if(r < b) hash = 3; // g > (r < b)
else if(r==b) hash = 4; // g > (r = b)
else hash = 5; // g > (r > b)
}else if(b > r && b > g){ //blue high
if(r < g) hash = 6; // b > (r < g)
else if(r==g) hash = 7; // b > (r = g)
else hash = 8; // b > (r > g)
}else if(r==b && b==g){ //r = g = b
hash = 9;
}else if(r==b){ //(r = b) > g
hash = 10;
}else if(r==g){ //(r = g) > b
hash = 11;
}else{ //(g = b) > r
hash = 12;
}
//make room in each category for four levels of intensity (0-3)
hash = hash*4 + highest;
return hash;
}
int gkPaletteGenerator::ColorExists(gkRGB color){
int hash = GetHash(color);
if(!colorCube[hash]) return 0;
gkPalGenItem *cur;
for(cur=colorCube[hash]; cur; cur=cur->GetNextItem()){
if(cur->GetColor()==color) return 1; //found exact color
}
return 0;
}
gkRGB gkPaletteGenerator::MatchColor(gkRGB color){
int hash = GetHash(color);
int r = color.GetR();
int g = color.GetG();
int b = color.GetB();
if(colorCube[hash]){ //near colors; search just this section
gkPalGenItem *cur, *bestMatch;
int bestDiff;
bestMatch = colorCube[hash];
int r2, g2, b2;
r2 = abs(r - bestMatch->GetColor().GetR());
g2 = abs(g - bestMatch->GetColor().GetG());
b2 = abs(b - bestMatch->GetColor().GetB());
bestDiff = r2 + g2 + b2;
for(cur=bestMatch->GetNextItem(); cur; cur=cur->GetNextItem()){
r2 = abs(r - cur->GetColor().GetR());
g2 = abs(g - cur->GetColor().GetG());
b2 = abs(b - cur->GetColor().GetB());
int curDiff = r2 + g2 + b2;
if(curDiff < bestDiff){
bestDiff = curDiff;
bestMatch = cur;
if(!curDiff) return bestMatch->GetColor();
}
}
return bestMatch->GetColor();
}else{
//no colors nearby; expand search
//Get it from ~greyscale if possible
int first, last;
first = last = 36 + (hash % 4);
if(!colorCube[first]){
first = 0; //nothing there either; search everything
last = 51;
/*
first = 36;
last = 39;
//first = hash - (hash%4); //different intensities, same color
//last = first + 3;
if(!colorCube[first] && !colorCube[first+1] && !colorCube[first+2]
&& !colorCube[last]){
first = 0; //nothing there either; search everything
last = 51;
}
*/
}
gkPalGenItem *cur, *bestMatch;
int bestDiff = 0x7fffffff;
bestMatch = 0;
int i;
for(i=first; i<=last; i++){
for(cur=colorCube[i]; cur; cur=cur->GetNextItem()){
int r2 = abs(r - cur->GetColor().GetR());
int g2 = abs(g - cur->GetColor().GetG());
int b2 = abs(b - cur->GetColor().GetB());
int curDiff = r2 + g2 + b2;
if(curDiff < bestDiff){
bestDiff = curDiff;
bestMatch = cur;
if(!curDiff) return bestMatch->GetColor();
}
}
}
if(!bestMatch) return gkRGB(0,0,0);
return bestMatch->GetColor();
}
}
gkTransparencyTable::gkTransparencyTable(){
lookup = new gkBYTE[256*256];
int baseOffset, alpha, i;
i = 0;
for(baseOffset=0; baseOffset<256; baseOffset++){
for(alpha=0; alpha<256; alpha++){
lookup[i++] = (baseOffset * alpha) / 255;
}
}
}
gkTransparencyTable::~gkTransparencyTable(){
if(lookup) delete lookup;
lookup = 0;
}
int gkTransparencyTable::LookupTransparencyOffset(int baseOffset, int alpha){
return (baseOffset>=0) ? lookup[(baseOffset<<8)+alpha]
: (-lookup[((-baseOffset)<<8)+alpha]);
}
gkBitmap::gkBitmap(){
data = 0;
width = height = bpp = 0;
}
gkBitmap::~gkBitmap(){
FreeData();
}
void gkBitmap::FreeData(){
if(data){
delete data;
data = 0;
}
width = height = 0;
}
void gkBitmap::Create(int _width, int _height){
if(_width <= 0 || _height <= 0) return;
FreeData();
width = _width;
height = _height;
//round up width to ensure multiple of 4 pixels
width = (width + 3) & (~3);
int size = (width*height)<<2;
data = new gkBYTE[size];
//data = new gkBYTE[(width*height)<<2];
bpp = 32;
}
void gkBitmap::Cls(){
if(!this->data) return;
gkLONG *dest = (gkLONG*) this->data;
int i = this->width * this->height;
while(i--){
*(dest++) = 0xff000000;
}
}
void gkBitmap::Cls(gkRGB color){
if(!this->data) return;
gkLONG *dest = (gkLONG*) this->data;
int i = this->width * this->height;
while(i--){
*(dest++) = color.GetARGB();
}
}
void gkBitmap::Plot(int x, int y, gkRGB color, int testBoundaries){
if(!data) return;
if(testBoundaries){
if(x<0 || x>=width || y<0 || y>=height) return;
}
gkRGB *dest = (gkRGB*) (data + ((y*width + x) << 2));
*dest = color;
}
gkRGB gkBitmap::Point(int x, int y, int testBoundaries){
if(!data) return gkRGB(0,0,0);
if(testBoundaries){
if(x<0 || x>=width || y<0 || y>=height) return gkRGB(0,0,0);
}
gkRGB *color = (gkRGB*) (data + ((y*width + x) << 2));
return *color;
}
void gkBitmap::Line(int x1, int y1, int x2, int y2, gkRGB color){
int temp;
if(y1==y2){ //straight horizontal line
if(x2 < x1) RectFill(x2,y1,(x1-x2)+1,1,color);
else RectFill(x1,y1,(x2-x1)+1,1,color);
return;
}else if(x1==x2){ //straight vertical line
if(y2 < y1) RectFill(x1,y2,1,(y1-y2)+1,color);
else RectFill(x1,y1,1,(y2-y1)+1,color);
return;
}
//clip line to screen
if(y2 < y1){ //orient line to be drawn from top to
temp = x1; x1 = x2; x2 = temp; //bottom for initial clipping tests
temp = y1; y1 = y2; y2 = temp;
}
if(y2 < 0 || y1 >= height) return;
double xdiff = x2-x1, ydiff = y2-y1;
double slopexy = xdiff / ydiff;
int diff;
//perform vertical clipping
diff = 0 - y1;
if(diff > 0){ //y1 is above top boundary
x1 += (int) (slopexy * diff);
y1 = 0;
}
diff = (y2 - height) + 1;
if(diff > 0){ //y2 is below bottom boundary
x2 -= (int) (slopexy * diff);
y2 = height - 1;
}
//reorient line to be drawn from left to right for horizontal clipping tests
if(x2 < x1){
temp = x1; x1 = x2; x2 = temp;
temp = y1; y1 = y2; y2 = temp;
xdiff = x2-x1;
ydiff = y2-y1;
}
double slopeyx = ydiff / xdiff;
if(x2 < 0 || x1 >= width) return;
diff = 0 - x1;
if(diff > 0){ //x1 is to left of left boundary
y1 += (int) (slopeyx * diff);
x1 = 0;
}
diff = (x2 - width) + 1;
if(diff > 0){ //x2 is to right of right boundary
y2 -= (int) (slopeyx * diff);
x2 = width - 1;
}
//draw the line using Bresenham's
//coordinates are now such that x increment is always positive
int xdist = x2-x1;
int ydist = y2-y1;
int pitch = width;
gkRGB *dest = (gkRGB*) (data + (((y1 * width) + x1) <<2));
if(ydist < 0){
ydist = -ydist;
pitch = -pitch;
}
int err, i;
if(xdist >= ydist){ //loop on x, change y every so often
err = 0;
for(i=xdist; i>=0; i--){
*(dest++) = color;
err += ydist;
if(err >= xdist){
err -= xdist;
dest += pitch;
}
}
}else{ //loop on y, change x every so often
err = 0;
for(i=ydist; i>=0; i--){
*dest = color;
dest += pitch;
err += xdist;
if(err >= ydist){
err -= ydist;
dest++;
}
}
}
}
void gkBitmap::LineAlpha(int x1, int y1, int x2, int y2, gkRGB color,
int alpha){
int temp;
if(y1==y2){ //straight horizontal line
if(x2 < x1) RectFill(x2,y1,(x1-x2)+1,1,color);
else RectFill(x1,y1,(x2-x1)+1,1,color);
return;
}else if(x1==x2){ //straight vertical line
if(y2 < y1) RectFill(x1,y2,1,(y1-y2)+1,color);
else RectFill(x1,y1,1,(y2-y1)+1,color);
return;
}
//clip line to screen
if(y2 < y1){ //orient line to be drawn from top to
temp = x1; x1 = x2; x2 = temp; //bottom for initial clipping tests
temp = y1; y1 = y2; y2 = temp;
}
if(y2 < 0 || y1 >= height) return;
double xdiff = x2-x1, ydiff = y2-y1;
double slopexy = xdiff / ydiff;
int diff;
//perform vertical clipping
diff = 0 - y1;
if(diff > 0){ //y1 is above top boundary
x1 += (int) (slopexy * diff);
y1 = 0;
}
diff = (y2 - height) + 1;
if(diff > 0){ //y2 is below bottom boundary
x2 -= (int) (slopexy * diff);
y2 = height - 1;
}
//reorient line to be drawn from left to right for horizontal clipping tests
if(x2 < x1){
temp = x1; x1 = x2; x2 = temp;
temp = y1; y1 = y2; y2 = temp;
xdiff = x2-x1;
ydiff = y2-y1;
}
double slopeyx = ydiff / xdiff;
if(x2 < 0 || x1 >= width) return;
diff = 0 - x1;
if(diff > 0){ //x1 is to left of left boundary
y1 += (int) (slopeyx * diff);
x1 = 0;
}
diff = (x2 - width) + 1;
if(diff > 0){ //x2 is to right of right boundary
y2 -= (int) (slopeyx * diff);
x2 = width - 1;
}
//draw the line using Bresenham's
//coordinates are now such that x increment is always positive
int xdist = x2-x1;
int ydist = y2-y1;
int pitch = width;
gkRGB *dest = (gkRGB*) (data + (((y1 * width) + x1) <<2));
if(ydist < 0){
ydist = -ydist;
pitch = -pitch;
}
int err, i;
if(xdist >= ydist){ //loop on x, change y every so often
err = 0;
for(i=xdist; i>=0; i--){
dest->Combine(color,alpha);
dest++;
err += ydist;
if(err >= xdist){
err -= xdist;
dest += pitch;
}
}
}else{ //loop on y, change x every so often
err = 0;
for(i=ydist; i>=0; i--){
dest->Combine(color,alpha);
dest += pitch;
err += xdist;
if(err >= ydist){
err -= ydist;
dest++;
}
}
}
}
void gkBitmap::RectFrame(int x, int y, int w, int h, gkRGB color){
int x2 = x + w - 1;
int y2 = y + h - 1;
RectFill(x,y,w,1,color);
RectFill(x,y2,w,1,color);
RectFill(x,y,1,h,color);
RectFill(x2,y,1,h,color);
}
void gkBitmap::RectFill(int x, int y, int w, int h, gkRGB color){
int x2 = (x + w) - 1;
int y2 = (y + h) - 1;
//Clip rectangle
if(x < 0) x = 0;
if(y < 0) y = 0;
if(x2 >= width) x2 = width - 1;
if(y2 >= height) y2 = height - 1;
if(x2 < x || y2 < y) return;
//Set pointers and offsets
gkRGB *destStart = (gkRGB*) (data + (((y * width) + x) <<2));
gkRGB *dest;
int numRows = (y2 - y) + 1;
int rowWidth = (x2 - x) + 1;
//do it
while(numRows--){
dest = destStart;
int i;
for(i=0; i<rowWidth; i++){
*(dest++) = color;
}
destStart += width;
}
}
void gkBitmap::RectFillAlpha(int x, int y, int w, int h, gkRGB color,
int alpha){
int x2 = (x + w) - 1;
int y2 = (y + h) - 1;
//Clip rectangle
if(x < 0) x = 0;
if(y < 0) y = 0;
if(x2 >= width) x2 = width - 1;
if(y2 >= height) y2 = height - 1;
if(x2 < x || y2 < y) return;
//Set pointers and offsets
gkRGB *destStart = (gkRGB*) (data + (((y * width) + x) <<2));
gkRGB *dest;
int numRows = (y2 - y) + 1;
int rowWidth = (x2 - x) + 1;
//do it
while(numRows--){
dest = destStart;
int i;
for(i=0; i<rowWidth; i++){
dest->Combine(color,alpha);
dest++;
}
destStart += width;
}
}
void gkBitmap::RectFillChannel(int x, int y, int w, int h,
gkRGB color, int mask){
int x2 = (x + w) - 1;
int y2 = (y + h) - 1;
//Clip rectangle
if(x < 0) x = 0;
if(y < 0) y = 0;
if(x2 >= width) x2 = width - 1;
if(y2 >= height) y2 = height - 1;
if(x2 < x || y2 < y) return;
//Set pointers and offsets
gkLONG *destStart = (gkLONG*) (data + (((y * width) + x) <<2));
gkLONG *dest;
int numRows = (y2 - y) + 1;
int rowWidth = (x2 - x) + 1;
gkLONG srcColor = color.GetARGB() & mask;
gkLONG destMask = ~mask;
//do it
while(numRows--){
dest = destStart;
int i;
for(i=0; i<rowWidth; i++){
*dest = (*dest & destMask) | (srcColor);
dest++;
}
destStart += width;
}
}
struct gkFillItem{
short int x, y;
gkRGB *pos;
inline gkFillItem(){}
inline gkFillItem(int _x, int _y, gkRGB *_pos){
x = _x;
y = _y;
pos = _pos;
}
};
void gkBitmap::FloodFill(int x, int y, gkRGB color){
gkFillItem queue[16384];
int qHead=0, qTail=0;
//not the color we're filling WITH, the color we're filling ON
gkRGB fillColor = Point(x,y);
if(color==fillColor) return;
//enqueue the starting location
queue[qTail++] = gkFillItem(x,y,(gkRGB *)(data + ((y*width + x) * 4)));
//keep looping, filling current location & adding adjacent locations
//until queue is empty
while(qHead != qTail){
//dequeue an item
gkFillItem cur = queue[qHead++];
qHead &= 16383;
//out of bounds check
if(cur.x<0 || cur.y<0 || cur.x>=width || cur.y>=height) continue;
//filling correct color check
if(!(*(cur.pos)==fillColor)) continue;
//fill color & add adjacent
*(cur.pos) = color;
queue[qTail++] = gkFillItem(cur.x+1,cur.y,cur.pos+1);
qTail &= 16383;
queue[qTail++] = gkFillItem(cur.x-1,cur.y,cur.pos-1);
qTail &= 16383;
queue[qTail++] = gkFillItem(cur.x,cur.y+1,cur.pos+width);
qTail &= 16383;
queue[qTail++] = gkFillItem(cur.x,cur.y-1,cur.pos-width);
qTail &= 16383;
}
}
int gkBitmap::GetNumColors(){
gkPaletteGenerator palGen;
int i = width * height;
gkRGB *src = (gkRGB*) data;
while(i--){
palGen.AddColor(*(src++));
}
return palGen.GetNumColors();
}
void gkBitmap::RemapColor(gkRGB oldColor, gkRGB newColor){
if(!data) return;
gkRGB *src = (gkRGB*) data;
int i, j;
for(j=0; j<height; j++){
for(i=0; i<width; i++){
if(oldColor == (*src)){
*src = newColor;
}
src++;
}
}
}
int gkBitmap::GetPalette(gkRGB *palette, int maxColors){
gkPaletteGenerator palGen;
int i = width * height;
gkRGB *src = (gkRGB*) data;
while(i--){
palGen.AddColor(*(src++));
}
int num = palGen.GetNumColors();
palGen.CreatePalette(palette, maxColors);
return num;
}
void gkBitmap::RemapToPalette(gkRGB *palette, int numColors){
//add palette colors to generator for matching purposes
gkPaletteGenerator generator;
int i = numColors;
gkRGB *src = palette;
while(i--){
generator.AddColor(*(src++));
}
//find color in palette that best matches each pixel
i = width * height;
src = (gkRGB*) data;
while(i--){
*src = generator.MatchColor(*src);
src++;
}
}
void gkBitmap::ReduceColors(int numColors){
gkPaletteGenerator generator;
int i = width * height;
gkRGB *src = (gkRGB*) data;
while(i--){
generator.AddColor(*(src++));
}
//gkRGB palette[256];
gkRGB *palette = new gkRGB[numColors];
generator.CreatePalette(palette, numColors);
RemapToPalette(palette, numColors);
delete palette;
}
void gkBitmap::ExchangeColors(gkRGB c1, gkRGB c2){
if(!data) return;
gkRGB *src = (gkRGB*) data;
int i, j;
for(j=0; j<height; j++){
for(i=0; i<width; i++){
if(c1 == (*src)){
*src = c2;
}else if(c2 == (*src)){
*src = c1;
}
src++;
}
}
}
void gkBitmap::SetAlpha(int alpha){
if(!data) return;
gkRGB *src = (gkRGB*) data;
int i = (width * height);
while(i--){
(src++)->SetA(alpha);
}
}
void gkBitmap::SetColorAlpha(gkRGB color, int alpha){
if(!data) return;
gkRGB *src = (gkRGB*) data;
int i, j;
for(j=0; j<height; j++){
for(i=0; i<width; i++){
if(color == (*src)){
src->SetA(alpha);
}
src++;
}
}
}
void gkBitmap::SetAlphaEdges(float opacity, int iterations){
{ //body in block to prevent recursive memory waste
gkBitmap work;
work.GetBitmap(this);
gkRGB *src = (gkRGB*) work.data;
gkRGB *dest = (gkRGB*) data;
gkRGB alpha;
int i, j;
for(j=0; j<height; j++){
for(i=0; i<width; i++){
if(src->GetA()==255){
int alpha = 0;
int samples = 0;
if(i>0 && j>0){
alpha += work.Point(i-1,j-1).GetA();
samples++;
}
if(i>0 && j<height-1){
alpha += work.Point(i-1,j+1).GetA();
samples++;
}
if(i<width-1 && j>0){
alpha += work.Point(i+1,j-1).GetA();
samples++;
}
if(i<width-1 && j<height-1){
alpha += work.Point(i+1,j+1).GetA();
samples++;
}
if(samples==0) samples=1; //would only happen on 1x1
alpha /= samples;
alpha = (int) (alpha * opacity);
dest->SetA(alpha);
}
dest++;
src++;
}
}
}
if(iterations>0) SetAlphaEdges(opacity, iterations-1);
}
void gkBitmap::MirrorH(){
gkRGB *buffer = new gkRGB[width];
if(!buffer) return;
gkRGB *cur = (gkRGB*) data;
int i,j,i2;
for(j=0; j<height; j++){
for(i=0,i2=width-1; i<width; i++,i2--){
buffer[i2] = cur[i];
}
for(i=0; i<width; i++){
cur[i] = buffer[i];
}
cur += width;
}
delete buffer;
}
int gkBitmap::GetBitmap(gkBitmap *srcBitmap, int x, int y, int w, int h){
if(!srcBitmap || !srcBitmap->data) return 0;
//adjust src rectangle until it fits within source data
if(x<0){
w += x;
x = 0;
}
if(y<0){
h += y;
y = 0;
}
if(x + w > srcBitmap->width){
w = srcBitmap->width - x;
}
if(y + h > srcBitmap->height){
h = srcBitmap->height - y;
}
if(w<=0 || h<=0) return 0;
FreeData();
Create(w, h);
gkBYTE *src = srcBitmap->data + ((y*srcBitmap->width + x)<<2);
gkBYTE *dest = this->data;
int srcSkip = srcBitmap->width << 2; //4 bytes per pixel
w <<= 2; //4 bytes per pixel
while(h--){
memcpy(dest, src, w);
dest += w;
src += srcSkip;
}
return 1;
}
int gkBitmap::GetBitmap(gkBitmap *srcBitmap){
if(this->width != srcBitmap->width || this->height != srcBitmap->height
|| (!this->data) || (!srcBitmap->data)){
//mem needs to be reallocated
FreeData();
memcpy(this, srcBitmap, sizeof(gkBitmap));
data = 0;
if(srcBitmap->data){
data = new gkBYTE[(width * height) << 2];
memcpy(data, srcBitmap->data, (width * height) << 2 );
}
}else{
//already got right size mem, just copy data over
memcpy(data, srcBitmap->data, (width * height) << 2);
fontSpacing = srcBitmap->fontSpacing;
fontKerning = srcBitmap->fontKerning;
x_handle = srcBitmap->x_handle;
y_handle = srcBitmap->y_handle;
}
return data!=0;
}
int gkBitmap::SaveBitmap(char *filename){
ofstream outfile;
outfile.open(filename, ios::out | ios::binary);
if(!outfile) return 0;
int result = SaveBitmap(outfile);
outfile.close();
return result;
}
int gkBitmap::SaveBitmap(ostream &out){
int totalSize = ((width * height) << 2);
out << "SHPE";
int skipSize = totalSize + 20;
gkIO::WriteLong(out, skipSize);
//Write shape header
gkIO::WriteWord(out, 3); //type 3
gkIO::WriteWord(out, width);
gkIO::WriteWord(out, height);
gkIO::WriteWord(out, (bpp==8)?8:32);
out << (char) fontSpacing << (char) fontKerning;
gkIO::WriteWord(out, x_handle);
gkIO::WriteWord(out, y_handle);
gkIO::WriteWord(out, 0); //6 bytes of reserved space
gkIO::WriteLong(out, 0);
//Write data
if(data){
int pitch = width << 2;
gkLONG *src;
gkBYTE *srcStart = data;
int i,j;
for(j=0; j<height; j++){
src = (gkLONG*) srcStart;
for(i=0; i<width; i++){
gkIO::WriteLong(out, *(src++));
}
srcStart += pitch;
}
}
return 1;
}
int gkBitmap::LoadBitmap(char *filename){
ifstream infile(filename,ios::in|ios::binary|ios::nocreate);
if(!infile) return 0;
//gkFileInputBuffer gkInfile(filename);
//if(!gkInfile.Exists()) return 0;
//istream &infile = *gkInfile.GetIStream();
int result = this->LoadBitmap(infile);
infile.close();
return result;
}
int gkBitmap::LoadBitmap(istream &infile){
FreeData();
if(infile.get() != 'S') return 0;
if(infile.get() != 'H') return 0;
if(infile.get() != 'P') return 0;
if(infile.get() != 'E') return 0;
gkIO::ReadLong(infile); //discard skipsize
//Read shape header
if(gkIO::ReadWord(infile) != 2) return 0;
width = gkIO::ReadWord(infile);
height = gkIO::ReadWord(infile);
int filebpp = gkIO::ReadWord(infile);
if(!bpp) bpp = filebpp;
if(width && height){
Create(width,height);
}
fontSpacing = infile.get();
fontKerning = infile.get();
x_handle = gkIO::ReadWord(infile);
y_handle = gkIO::ReadWord(infile);
gkIO::ReadWord(infile);
gkIO::ReadLong(infile); //discard reserved space
if(!width || !height) return 1; //nothing to load, null shape
int pitch = width << 2;
gkBYTE *destStart = data;
gkLONG *dest;
int x,y;
for(y=0; y<height; y++){
dest = (gkLONG*) destStart;
for(x=0; x<width; x++){
*(dest++) = gkIO::ReadLong(infile);
}
destStart += pitch;
}
return 1;
}
int gkBitmap::LoadBMP(char *filename){
ifstream infile(filename,ios::in|ios::binary|ios::nocreate);
if(!infile) return 0;
//gkFileInputBuffer gkInfile(filename);
//if(!gkInfile.Exists()) return 0;
//istream &infile = *gkInfile.GetIStream();
int result = this->LoadBMP(infile);
infile.close();
return result;
}
int gkBitmap::LoadBMP(istream &infile){
BMP_header header;
if(gkIO::ReadWord(infile)!=0x424d){ //check for "BM"
return 0;
}
infile.read((char*)&header, sizeof(BMP_header));
if(header.bpp != 24){
cout << "LoadBMP can only handle 24-bit files" << endl;
return 0;
}
FreeData();
width = header.width;
height = header.height;
bpp = (char) header.bpp;
Create(width,height);
// load graphics, coverting every three (B,R,G) bytes to one ARGB value.
// lines padded to even multiple of 4 bytes
int srcPitch = ((header.width * 3) + 3) & (~3);
gkBYTE *buffer = new gkBYTE[srcPitch * height];
gkBYTE *nextBuffPtr = buffer + ((height-1) * srcPitch);
gkBYTE *buffPtr;
infile.read(buffer, srcPitch * height);
gkBYTE *nextDest = data;
gkRGB *dest;
int destPitch = (width << 2);
int i, j;
j = height;
while(j--){
buffPtr = nextBuffPtr;
nextBuffPtr -= srcPitch;
dest = (gkRGB*) nextDest;
nextDest += destPitch;
i = header.width;
while(i--){
dest->SetB(*(buffPtr++));
dest->SetG(*(buffPtr++));
dest->SetR(*(buffPtr++));
dest->SetA(0xff);
dest++;
}
for(i=header.width; i<width; i++){
dest->SetARGB(0);
dest++;
}
}
delete buffer;
return 1;
}
int gkBitmap::SaveBMP(char *filename){
if(!data) return 0;
if(!filename) return 0;
//open file
ofstream outfile;
outfile.open(filename, ios::out | ios::binary);
if(!outfile) return 0;
outfile << 'B' << 'M'; //"BM" (file type)
BMP_header header;
memset(&header,0,sizeof(header)); //clear everything to zero
header.width = width;
header.height = height;
header.bpp = 24;
header.planes = 1;
header.imageSize = (header.width * header.height * header.bpp) / 8;
header.headerSize = 40;
header.offsetBytes = 54; //header + palette size
header.fSize = 54 + header.imageSize;
outfile.write((char*)&header, sizeof(BMP_header));
// save graphics as bottom-to-top, left-to-right //
int y = height - 1;
int pitch = width*4;
gkBYTE *src = data + (y * pitch); //find bottom of buffer
gkBYTE *d;
//write brg bytes out
int i;
for(; y>=0; y--){
d = src;
for(i=0; i<width; i++){
int r,g,b;
b = *(d++); g = *(d++); r = *d;
outfile << (char) b << (char) g << (char) r;
d+=2;
}
src -= pitch;
}
outfile.close();
return 1;
}
void gkBitmap::Blit(gkBitmap *destBitmap, int x, int y, int flags){
if(!data || !destBitmap || !destBitmap->data) return;
gkBYTE *src = 0;
int srcWidth = this->width;
int srcSkip = this->width;
int lines = this->height;
//clip left side
if(x < 0){
src += -x; //times 4 later
srcWidth -= -x;
x = 0;
}
//clip right side
int diff = (x + srcWidth) - destBitmap->width;
if(diff > 0){
srcWidth -= diff;
}
if(srcWidth <= 0) return;
//clip top
if(y<0){
src += (-y * this->width); //times 4 later
lines += y; //lines -= (-y)
y = 0;
}
//clip bottom
diff = (y + lines) - destBitmap->height;
if(diff > 0){
lines -= diff;
}
if(lines <= 0) return;
int destSkip = destBitmap->width;
gkBYTE *dest = destBitmap->data + (((y * destBitmap->width) + x) << 2);
src = this->data + ((int)src << 2);
srcWidth <<= 2;
srcSkip <<= 2;
destSkip <<= 2;
if(flags & GKBLT_TRANSPARENT){
//blit using alpha 0 as fully transparent
while(lines--){
MemCpyTrans(dest, src, srcWidth);
src += srcSkip;
dest += destSkip;
}
}else{
//blit without a transparent color
while(lines--){
memcpy(dest, src, srcWidth);
src += srcSkip;
dest += destSkip;
}
}
}
//////////////////////////////////////////////////////////////////////
// Method: BlitColorToAlpha
// Description: Converts this bitmap's colors into alpha values on
// the destination bitmap. Black becomes transparent,
// white fully opaque.
//////////////////////////////////////////////////////////////////////
void gkBitmap::BlitColorToAlpha(gkBitmap *destBitmap, int x, int y){
if(!data || !destBitmap || !destBitmap->data) return;
gkBYTE *src = 0;
int srcWidth = this->width;
int srcSkip = this->width;
int lines = this->height;
//clip left side
if(x < 0){
src += -x; //times 4 later
srcWidth -= -x;
x = 0;
}
//clip right side
int diff = (x + srcWidth) - destBitmap->width;
if(diff > 0){
srcWidth -= diff;
}
if(srcWidth <= 0) return;
//clip top
if(y<0){
src += (-y * this->width); //times 4 later
lines += y; //lines -= (-y)
y = 0;
}
//clip bottom
diff = (y + lines) - destBitmap->height;
if(diff > 0){
lines -= diff;
}
if(lines <= 0) return;
int destSkip = destBitmap->width;
gkBYTE *dest = destBitmap->data + (((y * destBitmap->width) + x) << 2);
src = this->data + ((int)src << 2);
srcWidth <<= 2;
srcSkip <<= 2;
destSkip <<= 2;
//blit converting color(B) to alpha.
while(lines--){
MemCpyColorToAlpha(dest, src, srcWidth);
src += srcSkip;
dest += destSkip;
}
}
//////////////////////////////////////////////////////////////////////
// Function: BlitScale
// Arguments: destBitmap
// x
// y
// flags
// scale - number of source pixels for every one
// dest pixel, stored in 24:8 fixed point.
// $100=100%, $200=200% (mag x2), $80=50%
//////////////////////////////////////////////////////////////////////
void gkBitmap::BlitScale(gkBitmap *destBitmap, int x, int y,
int scale, int flags){
if(!data || !destBitmap || !destBitmap->data || !scale) return;
gkBYTE *src = 0;
int srcSkip = this->width;
int srcWidth = (this->width * scale) >> 8;
int lines = (this->height * scale) >> 8;
//clip left side
if(x < 0){
src += (-x << 8) / scale;
srcWidth -= -x;
x = 0;
}
//clip right side
int diff = (x + srcWidth) - destBitmap->width;
if(diff > 0){
srcWidth -= diff;
}
if(srcWidth <= 0) return;
//clip top
if(y<0){
src += ((-y * this->width) << 8) / scale;
lines += y; //lines -= (-y)
y = 0;
}
//clip bottom
diff = (y + lines) - destBitmap->height;
if(diff > 0){
lines -= diff;
}
if(lines <= 0) return;
int destSkip = destBitmap->width;
gkBYTE *dest = destBitmap->data + (((y * destBitmap->width) + x) << 2);
src = this->data + ((int)src << 2);
srcSkip <<= 2;
destSkip <<= 2;
int ratio = (int) ((1.0f / (((float) scale) / 256.0f)) * 256.0f);
int lineError = 0;
while(lines--){
BlitLineScale(dest, src, srcWidth, ratio);
lineError += ratio;
src += srcSkip * (lineError >> 8);
lineError &= 0xff;
dest += destSkip;
}
/*
if(flags & GKBLT_TRANSPARENT){
//blit using alpha 0 as fully transparent
while(lines--){
MemCpyTrans(dest, src, srcWidth);
src += srcSkip;
dest += destSkip;
}
}else{
//blit without a transparent color
while(lines--){
memcpy(dest, src, srcWidth);
src += srcSkip;
dest += destSkip;
}
}
*/
}
void gkBitmap::BlitScale(gkBitmap *destBitmap, int x, int y,
float scale, int flags){
BlitScale(destBitmap,x,y,flags,(int) (256.0f * scale));
}
void gkBitmap::BlitChannel(gkBitmap *destBitmap, int x, int y, int mask){
if(!data || !destBitmap || !destBitmap->data) return;
gkBYTE *src = 0;
int srcWidth = this->width;
int srcSkip = this->width;
int lines = this->height;
//clip left side
if(x < 0){
src += -x;
srcWidth -= -x;
x = 0;
}
//clip right side
int diff = (x + srcWidth) - destBitmap->width;
if(diff > 0){
srcWidth -= diff;
}
if(srcWidth <= 0) return;
//clip top
if(y<0){
src += (-y * this->width);
lines += y; //lines -= (-y)
y = 0;
}
//clip bottom
diff = (y + lines) - destBitmap->height;
if(diff > 0){
lines -= diff;
}
if(lines <= 0) return;
int destSkip = destBitmap->width;
gkBYTE *dest = destBitmap->data + (((y * destBitmap->width) + x) << 2);
src = this->data + ((int)src << 2);
srcSkip <<= 2;
destSkip <<= 2;
int inverseMask = ~mask;
while(lines--){
gkLONG *curSrc = (gkLONG*) src;
gkLONG *curDest = (gkLONG*) dest;
int pixels = srcWidth;
while(pixels--){
*curDest = (*curSrc & mask) | (*curDest & inverseMask);
curSrc++;
curDest++;
}
src += srcSkip;
dest += destSkip;
}
}
void gkBitmap::MemCpyTrans(gkBYTE *dest, gkBYTE *src,
int numBytes){
//copies colors using the Alpha for transparency
gkRGB *destColor = (gkRGB*) dest;
gkRGB *srcColor = (gkRGB*) src;
int numColors = numBytes >> 2;
int c1;
while(numColors--){
int alpha;
switch(alpha = (srcColor->GetA())){
case 0: break;
case 255: //Straight copy
*destColor = *srcColor;
break;
default: //mix alpha
c1 = destColor->GetR();
c1 += tranTable.LookupTransparencyOffset(
srcColor->GetR()-c1, alpha);
destColor->SetR(c1);
c1 = destColor->GetG();
c1 += tranTable.LookupTransparencyOffset(
srcColor->GetG()-c1, alpha);
destColor->SetG(c1);
c1 = destColor->GetB();
c1 += tranTable.LookupTransparencyOffset(
srcColor->GetB()-c1, alpha);
destColor->SetB(c1);
break;
}
srcColor++;
destColor++;
}
}
void gkBitmap::MemCpyColorToAlpha(gkBYTE *dest, gkBYTE *src,
int numBytes){
//converts blue to alpha during copy
gkRGB *destColor = (gkRGB*) dest;
gkRGB *srcColor = (gkRGB*) src;
int numColors = numBytes >> 2;
while(numColors--){
destColor->SetA(srcColor->GetB());
srcColor++;
destColor++;
}
}
void gkBitmap::BlitLineScale(gkBYTE *dest,gkBYTE *src,int pixels,int ratio){
gkRGB *srcRGB = (gkRGB*) src;
gkRGB *destRGB = (gkRGB*) dest;
int error = 0;
while(pixels--){
*(destRGB++) = *srcRGB;
error += ratio;
srcRGB += error >> 8;
error &= 0xff;
}
}