Casio_asm/common/graph.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;
}