421 lines
9.8 KiB
C
421 lines
9.8 KiB
C
#include "graph.h"
|
|
#include "platform.h"
|
|
|
|
#define vram Graph_getVramAddress()
|
|
#define setByte(offset, color, mask) do { \
|
|
if(color==COLOR_BLACK) vram[offset]|=(mask); \
|
|
else if(color==COLOR_WHITE) vram[offset]&=(char) ~(mask); \
|
|
else if(color==COLOR_XOR) vram[offset]^=(mask); \
|
|
else return 0; \
|
|
} while(0)
|
|
#define getOffset(x, y) ((y)*VRAM_BYTES_PER_LINE+(x)/8)
|
|
#define sgn(a) ((a)>0?1:(a)?-1:0)
|
|
#define abs(a) ((a)<0?-(a):(a))
|
|
|
|
int Graph_fill(int color) {
|
|
//check if we have to actually draw
|
|
if(color==COLOR_TRANSPARENT) return 1;
|
|
|
|
//fill everything
|
|
for(int i=0; i<VRAM_LENGTH; i++) setByte(i, color, 0xff);
|
|
return 1;
|
|
}
|
|
|
|
int Graph_pixelSet(int x, int y, int color) {
|
|
//check if we have to actually draw
|
|
if(color==COLOR_TRANSPARENT) return 1;
|
|
|
|
//locate pixel in VRAM
|
|
if(x<0||x>=VRAM_WIDTH) return 1;
|
|
if(y<0||y>=VRAM_HEIGHT) return 1;
|
|
int byte=getOffset(x, y);
|
|
char mask=1<<(8-(x%8)-1);
|
|
|
|
//set pixel
|
|
setByte(byte, color, mask);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int Graph_pixelGet(int x, int y) {
|
|
//locate pixel in VRAM
|
|
if(x<0||x>=VRAM_WIDTH) return 0;
|
|
if(y<0||y>=VRAM_HEIGHT) return 0;
|
|
int byte=getOffset(x, y);
|
|
char mask=1<<(8-(x%8)-1);
|
|
|
|
//get pixel
|
|
return !!(vram[byte]&mask);
|
|
}
|
|
|
|
#define lineLow(x0, y0, x1, y1) do { \
|
|
dx=x1-x0; \
|
|
dy=y1-y0; \
|
|
int yi=1; \
|
|
if(dy<0) { \
|
|
yi=-1; \
|
|
dy=-dy; \
|
|
} \
|
|
int d=2*dy-dx; \
|
|
int y=y0; \
|
|
for(int x=x0; x<=x1; x++) { \
|
|
if(!Graph_pixelSet(x, y, color)) return 0; \
|
|
if(d>0) {\
|
|
y+=yi; \
|
|
d-=2*dx; \
|
|
} \
|
|
d+=2*dy; \
|
|
} \
|
|
} while(0)
|
|
|
|
#define lineHigh(x0, y0, x1, y1) do { \
|
|
dx=x1-x0; \
|
|
dy=y1-y0; \
|
|
int xi=1; \
|
|
if(dx<0) { \
|
|
xi=-1; \
|
|
dx=-dx; \
|
|
} \
|
|
int d=2*dx-dy; \
|
|
int x=x0; \
|
|
for(int y=y0; y<=y1; y++) { \
|
|
if(!Graph_pixelSet(x, y, color)) return 0; \
|
|
if(d>0) {\
|
|
x+=xi; \
|
|
d-=2*dy; \
|
|
} \
|
|
d+=2*dx; \
|
|
} \
|
|
} while(0)
|
|
|
|
int Graph_line(int x1, int y1, int x2, int y2, int color) {
|
|
//check if we have to actually draw
|
|
if(color==COLOR_TRANSPARENT) return 1;
|
|
|
|
//check if we can use horizontal or vertical line
|
|
if(x1==x2) return Graph_lineV(x1, y1, y2, color);
|
|
if(y1==y2) return Graph_lineH(y1, x1, x2, color);
|
|
|
|
//see: https://en.wikipedia.org/wiki/Bresenham's_line_algorithm
|
|
int dx=x2-x1;
|
|
int dy=y2-y1;
|
|
|
|
if(abs(dy)<abs(dx)) {
|
|
if(x1>x2) lineLow(x2, y2, x1, y1);
|
|
else lineLow(x1, y1, x2, y2);
|
|
} else {
|
|
if(x1>y2) lineHigh(x2, y2, x1, y1);
|
|
else lineHigh(x1, y1, x2, y2);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int Graph_lineV(int x, int y1, int y2, int color) {
|
|
//check if we have to actually draw
|
|
if(color==COLOR_TRANSPARENT) return 1;
|
|
|
|
//sanitize coordinates
|
|
if(x<0||x>=VRAM_WIDTH) return 1;
|
|
if(y1<0) y1=0;
|
|
if(y1>=VRAM_HEIGHT) y1=VRAM_HEIGHT-1;
|
|
if(y2<0) y2=0;
|
|
if(y2>=VRAM_HEIGHT) y2=VRAM_HEIGHT-1;
|
|
if(y2<y1) {
|
|
int tmp=y2;
|
|
y2=y1;
|
|
y1=tmp;
|
|
}
|
|
if(y1==y2) return Graph_pixelSet(x, y1, color);
|
|
|
|
//locate byte and calculate first mask
|
|
int byte=getOffset(x, y1);
|
|
char mask=1<<(8-(x%8)-1);
|
|
|
|
//draw the pixels on screen
|
|
for(int i=y1; i<=y2; i++) {
|
|
setByte(byte, color, mask);
|
|
byte+=VRAM_BYTES_PER_LINE;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int Graph_lineH(int y, int x1, int x2, int color) {
|
|
//check if we have to actually draw
|
|
if(color==COLOR_TRANSPARENT) return 1;
|
|
|
|
//sanitize coordinates
|
|
if(y<0||y>=VRAM_HEIGHT) return 1;
|
|
if(x1<0) x1=0;
|
|
if(x1>=VRAM_WIDTH) x1=VRAM_WIDTH-1;
|
|
if(x2<0) x2=0;
|
|
if(x2>=VRAM_WIDTH) x2=VRAM_WIDTH-1;
|
|
if(x2<x1) {
|
|
int tmp=x2;
|
|
x2=x1;
|
|
x1=tmp;
|
|
}
|
|
x2++;
|
|
|
|
//count the number of full bytes
|
|
int count=x2/8-x1/8;
|
|
|
|
//get the offset of the first byte
|
|
int byte=getOffset(x1, y);
|
|
|
|
//handle the case when both are on the same byte
|
|
if(count==0) {
|
|
setByte(byte, color, 0xff^(0xff<<(8-x1%8))^(0xff>>(x2%8)));
|
|
return 1;
|
|
}
|
|
|
|
if(x1%8) count--;
|
|
|
|
//set the left-hand byte
|
|
if(x1%8) setByte(byte++, color, 0xff^(0xff<<(8-x1%8)));
|
|
|
|
//set the middle bytes
|
|
for(int i=0; i<count; i++) setByte(byte++, color, 0xff);
|
|
|
|
//set the right-hand byte
|
|
setByte(byte, color, 0xff^(0xff>>(x2%8)));
|
|
|
|
return 1;
|
|
}
|
|
|
|
int Graph_horizontal(int y, int color) {
|
|
//make sure we need to draw
|
|
if(color==COLOR_TRANSPARENT) return 1;
|
|
if(y<0||y>=VRAM_HEIGHT) return 1;
|
|
|
|
//select the bytes
|
|
int byte=y*VRAM_BYTES_PER_LINE;
|
|
|
|
//fill the line
|
|
for(int i=0; i<VRAM_BYTES_PER_LINE; i++) setByte(byte+i, color, 0xff);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int Graph_vertical(int x, int color) {
|
|
//make sure we need to draw
|
|
if(color==COLOR_TRANSPARENT) return 1;
|
|
if(x<0||x>=VRAM_WIDTH) return 1;
|
|
|
|
//select the bytes and the mask
|
|
int byte=x%8;
|
|
int mask=1<<(8-(x%8)-1);
|
|
|
|
//fill the line
|
|
for(int i=0; i<VRAM_HEIGHT; i++) setByte(byte+i*VRAM_BYTES_PER_LINE, color, mask);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int Graph_rect(int x, int y, int w, int h, int color) {
|
|
//draw the bounding box
|
|
Graph_lineH(y, x, x+w-1, color);
|
|
Graph_lineH(y+h-1, x, x+w-1, color);
|
|
Graph_lineV(x, y+1, y+h-2, color);
|
|
return Graph_lineV(x+w-1, y+1, y+h-2, color);
|
|
}
|
|
|
|
int Graph_rectFill(int x, int y, int w, int h, int color) {
|
|
//check if we have to actually draw
|
|
if(color==COLOR_TRANSPARENT) return 1;
|
|
|
|
//draw horizontal lines
|
|
for(int i=0; i<h; i++) {
|
|
if(!Graph_lineH(y+i, x, x+w-1, color)) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int Graph_circle(int x, int y, int radius, int color) {
|
|
//check if we have to draw
|
|
if(color==COLOR_TRANSPARENT) return 1;
|
|
if(color==COLOR_XOR) return 0;
|
|
|
|
//see: https://fr.wikipedia.org/wiki/Algorithme_de_tracé_d'arc_de_cercle_de_Bresenham
|
|
int x0=0;
|
|
int y0=radius;
|
|
int m=5-4*radius;
|
|
while(x0<=y0) {
|
|
Graph_pixelSet(x0+x, y0+y, color);
|
|
Graph_pixelSet(y0+x, x0+y, color);
|
|
Graph_pixelSet(-x0+x, y0+y, color);
|
|
Graph_pixelSet(-y0+x, x0+y, color);
|
|
Graph_pixelSet(x0+x, -y0+y, color);
|
|
Graph_pixelSet(y0+x, -x0+y, color);
|
|
Graph_pixelSet(-x0+x, -y0+y, color);
|
|
if(!Graph_pixelSet(-y0+x, -x0+y, color)) return 0;
|
|
|
|
if(m>0) {
|
|
y0--;
|
|
m-=8*y0;
|
|
}
|
|
x0++;
|
|
m+=8*x0+4;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int Graph_circleFill(int x, int y, int radius, int color) {
|
|
//check if we have to draw
|
|
if(color==COLOR_TRANSPARENT) return 1;
|
|
if(color==COLOR_XOR) return 0;
|
|
|
|
//see: https://fr.wikipedia.org/wiki/Algorithme_de_tracé_d'arc_de_cercle_de_Bresenham
|
|
int x0=0;
|
|
int y0=radius;
|
|
int m=5-4*radius;
|
|
while(x0<=y0) {
|
|
Graph_lineH(y0+y, -x0+x, x0+x, color);
|
|
Graph_lineH(x0+y, -y0+x, y0+x, color);
|
|
Graph_lineH(-y0+y, -x0+x, x0+x, color);
|
|
if(!Graph_lineH(-x0+y, -y0+x, y0+x, color)) return 0;
|
|
|
|
if(m>0) {
|
|
y0--;
|
|
m-=8*y0;
|
|
}
|
|
x0++;
|
|
m+=8*x0+4;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int Graph_shiftV(int dist, int color) {
|
|
//check if we have something to do
|
|
if(!dist) return 1;
|
|
if(abs(dist)>=VRAM_HEIGHT) return Graph_fill(color);
|
|
|
|
//check the direction
|
|
if(dist>0) {
|
|
//downwards
|
|
for(int y=VRAM_HEIGHT-1; y>=dist; y--) {
|
|
for(int i=0; i<VRAM_BYTES_PER_LINE; i++) {
|
|
vram[y*VRAM_BYTES_PER_LINE+i]=vram[(y-dist)*VRAM_BYTES_PER_LINE+i];
|
|
}
|
|
}
|
|
//fill the gap
|
|
Graph_rectFill(0, 0, VRAM_WIDTH, dist, color);
|
|
} else {
|
|
//upwards
|
|
for(int y=dist; y<VRAM_HEIGHT; y++) {
|
|
for(int i=0; i<VRAM_BYTES_PER_LINE; i++) {
|
|
vram[y*VRAM_BYTES_PER_LINE+i]=vram[(y-dist)*VRAM_BYTES_PER_LINE+i];
|
|
}
|
|
}
|
|
//fill the gap
|
|
Graph_rectFill(0, VRAM_HEIGHT+dist, VRAM_WIDTH, -dist, color);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int Graph_shiftH(int dist, int color) {
|
|
//check if we have something to do
|
|
if(!dist) return 1;
|
|
if(abs(dist)>=VRAM_WIDTH) return Graph_fill(color);
|
|
|
|
int bytes=dist/8;
|
|
int bits=abs(dist)%8;
|
|
|
|
//move whole bytes first
|
|
for(int y=0; y<VRAM_HEIGHT; y++) {
|
|
if(dist>0) {
|
|
for(int x=VRAM_BYTES_PER_LINE-1; x>=bytes; x--) {
|
|
vram[y*VRAM_BYTES_PER_LINE+x]=vram[y*VRAM_BYTES_PER_LINE+x-bytes];
|
|
}
|
|
} else {
|
|
for(int x=0; x<VRAM_BYTES_PER_LINE+bytes; x++) {
|
|
vram[y*VRAM_BYTES_PER_LINE+x]=vram[y*VRAM_BYTES_PER_LINE+x-bytes];
|
|
}
|
|
}
|
|
}
|
|
|
|
//move bits individually
|
|
if(bits) {
|
|
//TODO fix this when I think of an algorithm
|
|
return 0;
|
|
for(int y=0; y<VRAM_HEIGHT; y++) {
|
|
if(dist>0) {
|
|
int mask=0xff^(0xff<<bits);
|
|
for(int x=VRAM_BYTES_PER_LINE-1; x>=0; x--) {
|
|
vram[y*VRAM_BYTES_PER_LINE+x]>>=bits;
|
|
if(x) vram[y*VRAM_BYTES_PER_LINE+x]|=(vram[y*VRAM_BYTES_PER_LINE+x-1]<<(8-bits-1))&mask;
|
|
}
|
|
} else {
|
|
int mask=0xff^(0xff>>8-bits-1);
|
|
for(int x=VRAM_BYTES_PER_LINE-1; x>=0; x--) {
|
|
vram[y*VRAM_BYTES_PER_LINE+x]<<=bits;
|
|
if(x!=VRAM_BYTES_PER_LINE-1) vram[y*VRAM_BYTES_PER_LINE+x]|=(vram[y*VRAM_BYTES_PER_LINE+x+1]>>(8-bits-1))&mask;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//draw the remaining rectangle
|
|
if(dist>0) {
|
|
if(dist==1) return Graph_lineV(0, 0, VRAM_HEIGHT-1, color);
|
|
else return Graph_rectFill(0, 0, dist, VRAM_HEIGHT, color);
|
|
} else {
|
|
if(dist==-1) return Graph_lineH(VRAM_WIDTH-1, 0, VRAM_HEIGHT-1, color);
|
|
else return Graph_rectFill(VRAM_WIDTH+dist, 0, -dist, VRAM_HEIGHT, color);
|
|
}
|
|
}
|
|
|
|
int Graph_copy(int srcX, int srcY, int w, int h, int destX, int destY) {
|
|
//check the width and height
|
|
if(w<=0) return w==0;
|
|
if(h<=0) return h==0;
|
|
|
|
//check if we're in bounds
|
|
if(srcX<0||srcY<0) return 0;
|
|
if(srcX+w>=VRAM_WIDTH||srcY+h>=VRAM_HEIGHT) return 0;
|
|
if(destX<0||destY<0) return 0;
|
|
if(destX+w>=VRAM_WIDTH||destY+h>=VRAM_HEIGHT) return 0;
|
|
|
|
//Note that we don't promise anything if the two regions overlap!
|
|
|
|
//check if we can optimize heavily
|
|
if(srcX%8==0&&destX%8==0) {
|
|
int bytes=w/8;
|
|
int bits=w%8;
|
|
int mask=0xff^(0xff>>bits);
|
|
for(int y=0; y<h; y++) {
|
|
int offsetSrc=getOffset(srcX, y+srcY);
|
|
int offsetDest=getOffset(destX, y+destY);
|
|
|
|
//copy full bytes first
|
|
for(int x=0; x<bytes; x++) vram[offsetDest++]=vram[offsetSrc++];
|
|
|
|
//then copy the remaining bits
|
|
vram[offsetDest]&=(char) ~mask;
|
|
vram[offsetDest]|=vram[offsetSrc]&mask;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
//too bad, we can't
|
|
//TODO implement copy when we can't optimize heavily
|
|
return 0;
|
|
}
|
|
|
|
int Graph_sprite(int x, int y, char* sprite) {
|
|
//read sprite info
|
|
sprite_t info=*((sprite_t*) sprite);
|
|
int width=info.width;
|
|
int height=info.height;
|
|
int format=info.format;
|
|
|
|
//sanitize sprite data
|
|
if(format!=SPRITE_FORMAT_MONOCHROME&&format!=SPRITE_FORMAT_AND&&format!=SPRITE_FORMAT_OR&&format!=SPRITE_FORMAT_FULL) return 0;
|
|
if(width==0||height==0) return 1;
|
|
|
|
//set the sprite offset where the data starts
|
|
sprite+=3;
|
|
|
|
//TODO implement sprite function
|
|
return 0;
|
|
}
|