joeydev/src/draw.c

452 lines
13 KiB
C

/*
* JoeyDev
* Copyright (C) 2018-2023 Scott Duensing <scott@kangaroopunch.com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
#pragma clang diagnostic push
#pragma ide diagnostic ignored "cppcoreguidelines-narrowing-conversions"
#include "common.h"
#include "draw.h"
typedef struct {
jint16 StartX;
jint16 EndX;
jint16 Y;
signed char Dir; // 'signed' needs to be specified for ORCA/C
jbool ScanLeft;
jbool ScanRight;
jbool padding; // Aligns structure on x86
} _jlScanDataT;
static void _jlDrawCircleClipped(jlContextT *c, jint16 x0, jint16 y0, jint16 radius);
static void _jlDrawFill(jlContextT *c, jint16 x, jint16 y, jbool how);
static void _jlDrawFillAddLine(jlContextT *c, jint16 startX, jint16 endX, jint16 y, jint16 ignoreStart, jint16 ignoreEnd, char dir, jbool isNextInDir, jbool how);
static _jlScanDataT *_jlDrawFillNewSegment(jlContextT *c, jint16 startX, jint16 endX, jint16 y, signed char dir, jbool scanLeft, jbool scanRight);
jlContextT *jlContextNew(jbyte *pixels) {
jlContextT *c = NEW(jlContextT);
// "pixels" is ARGB32 @ 320x200
c->_jlDrawColor = 15;
c->_jlDrawFillColor = 0;
c->_jlFillStackTop = NULL;
c->_pixels = pixels;
jlPaletteDefault(c);
return c;
}
void jlContextDel(jlContextT **context) {
jlContextT *c = (jlContextT *)*context;
DEL(c);
}
void jlDrawBox(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2) {
jlDrawLine(c, x1, y1, x2, y1);
jlDrawLine(c, x2, y1, x2, y2);
jlDrawLine(c, x2, y2, x1, y2);
jlDrawLine(c, x1, y2, x1, y1);
}
void jlDrawBoxFilled(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2) {
jint16 y;
for (y=y1; y<=y2; y++) {
jlDrawLine(c, x1, y, x2, y);
}
}
void _jlDrawCircleClipped(jlContextT *c, jint16 x0, jint16 y0, jint16 radius) {
jint16 x = radius-1;
jint16 y = 0;
jint16 dx = 1;
jint16 dy = 1;
jint16 err = dx - (jint16)(radius << 1);
while (x >= y) {
if ((x0 + x < 320) && (y0 + y < 200)) jlDrawPixelSet(c, x0 + x, y0 + y);
if ((x0 + y < 320) && (y0 + x < 200)) jlDrawPixelSet(c, x0 + y, y0 + x);
if ((x0 - y < 320) && (y0 + x < 200)) jlDrawPixelSet(c, x0 - y, y0 + x);
if ((x0 - x < 320) && (y0 + y < 200)) jlDrawPixelSet(c, x0 - x, y0 + y);
if ((x0 - x < 320) && (y0 - y < 200)) jlDrawPixelSet(c, x0 - x, y0 - y);
if ((x0 - x < 320) && (y0 - x < 200)) jlDrawPixelSet(c, x0 - y, y0 - x);
if ((x0 + y < 320) && (y0 - x < 200)) jlDrawPixelSet(c, x0 + y, y0 - x);
if ((x0 + x < 320) && (y0 - y < 200)) jlDrawPixelSet(c, x0 + x, y0 - y);
if (err <= 0) {
y++;
err += dy;
dy += 2;
}
if (err > 0) {
x--;
dx += 2;
err += dx - (radius << 1);
}
}
}
void jlDrawCircle(jlContextT *c, jint16 x0, jint16 y0, jint16 radius) {
jint16 x = radius-1;
jint16 y = 0;
jint16 dx = 1;
jint16 dy = 1;
jint16 err = dx - (jint16)(radius << 1);
// Is any of this going to be off the screen?
//***TODO*** All our drawing primitives should do this.
if ((x0 + radius > 319) || (x0 - radius < 0) || (y0 + radius > 199) || (y0 - radius < 0)) {
_jlDrawCircleClipped(c, x0, y0, radius);
return;
}
while (x >= y) {
jlDrawPixelSet(c, x0 + x, y0 + y);
jlDrawPixelSet(c, x0 + y, y0 + x);
jlDrawPixelSet(c, x0 - y, y0 + x);
jlDrawPixelSet(c, x0 - x, y0 + y);
jlDrawPixelSet(c, x0 - x, y0 - y);
jlDrawPixelSet(c, x0 - y, y0 - x);
jlDrawPixelSet(c, x0 + y, y0 - x);
jlDrawPixelSet(c, x0 + x, y0 - y);
if (err <= 0) {
y++;
err += dy;
dy += 2;
} else {
x--;
dx += 2;
err += dx - (radius << 1);
}
}
}
void jlDrawClear(jlContextT *c) {
jlDrawBoxFilled(c, 0, 0, 319, 199);
}
void jlDrawColorSet(jlContextT *c, jbyte index) {
c->_jlDrawColor = index;
}
// http://members.chello.at/~easyfilter/bresenham.html
void jlDrawEllipse(jlContextT *c, jint16 x0, jint16 y0, jint16 x1, jint16 y1) {
jint16 a = (jint16)abs(x1-x0), b = (jint16)abs(y1-y0), b1 = b&1; /* values of diameter */
long dx = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; /* error increment */
long err = dx+dy+b1*a*a, e2; /* error of 1.step */
if (x0 > x1) { x0 = x1; x1 += a; } /* if called with swapped points */
if (y0 > y1) y0 = y1; /* .. exchange them */
y0 += (b+1)/2; y1 = y0-b1; /* starting pixel */
a *= 8*a; b1 = 8*b*b;
do {
jlDrawPixelSet(c, x1, y0); /* I. Quadrant */
jlDrawPixelSet(c, x0, y0); /* II. Quadrant */
jlDrawPixelSet(c, x0, y1); /* III. Quadrant */
jlDrawPixelSet(c, x1, y1); /* IV. Quadrant */
e2 = 2*err;
if (e2 <= dy) { y0++; y1--; err += dy += a; } /* y step */
if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } /* x step */
} while (x0 <= x1);
while (y0-y1 < b) { /* too early stop of flat ellipses a=1 */
jlDrawPixelSet(c, x0-1, y0); /* -> finish tip of ellipse */
jlDrawPixelSet(c, x1+1, y0++);
jlDrawPixelSet(c, x0-1, y1);
jlDrawPixelSet(c, x1+1, y1--);
}
}
_jlScanDataT *_jlDrawFillNewSegment(jlContextT *c, jint16 startX, jint16 endX, jint16 y, signed char dir, jbool scanLeft, jbool scanRight) {
_jlScanDataT *s = NEW(_jlScanDataT);
(void)c;
s->StartX = startX;
s->EndX = endX;
s->Y = y;
s->Dir = dir;
s->ScanLeft = scanLeft;
s->ScanRight = scanRight;
return s;
}
void _jlDrawFillAddLine(jlContextT *c, jint16 startX, jint16 endX, jint16 y, jint16 ignoreStart, jint16 ignoreEnd, char dir, jbool isNextInDir, jbool how) {
jint16 regionStart = -1;
jint16 x;
for (x=startX; x<endX; x++) {
if (how) {
// Unrolled contents to reduce comparison complexity.
if ((isNextInDir || x < ignoreStart || x >= ignoreEnd) && (jlDrawPixelGet(c, x, y) == c->_jlDrawFillColor)) {
jlDrawPixelSet(c, x, y);
if (regionStart < 0) regionStart = x;
} else if (regionStart >= 0) {
jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, regionStart, x, y, dir, regionStart == startX, jfalse));
regionStart = -1;
}
} else {
// Unrolled contents to reduce comparison complexity.
if ((isNextInDir || x < ignoreStart || x >= ignoreEnd) && (jlDrawPixelGet(c, x, y) != c->_jlDrawFillColor)) {
jlDrawPixelSet(c, x, y);
if (regionStart < 0) regionStart = x;
} else if (regionStart >= 0) {
jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, regionStart, x, y, dir, regionStart == startX, jfalse));
regionStart = -1;
}
}
if (!isNextInDir && x < ignoreEnd && x >= ignoreStart) x = ignoreEnd-1;
}
if (regionStart >= 0) {
jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, regionStart, x, y, dir, regionStart == startX, jtrue));
}
}
// Stole this from http://www.adammil.net/blog/v126_A_More_Efficient_Flood_Fill.html
void _jlDrawFill(jlContextT *c, jint16 x, jint16 y, jbool how) {
jint16 height = 200;
jint16 width = 320;
_jlScanDataT *r;
jint16 startX;
jint16 endX;
// how == true; Fill on top of _jlDrawFillColor
// how == false; Fill on top of any color until we encounter _jlDrawFillColor
jlDrawPixelSet(c, x, y);
jlUtilStackPush(c, c->_jlFillStackTop, _jlDrawFillNewSegment(c, x, x+1, y, 0, jtrue, jtrue));
while ((r = (_jlScanDataT *)jlUtilStackPop(c, c->_jlFillStackTop)) != NULL) {
startX = r->StartX;
endX = r->EndX;
if (r->ScanLeft) {
if (how) {
while (startX > 0 && (jlDrawPixelGet(c, startX-1, r->Y) == c->_jlDrawFillColor)) jlDrawPixelSet(c, --startX, r->Y);
} else {
while (startX > 0 && (jlDrawPixelGet(c, startX-1, r->Y) != c->_jlDrawFillColor)) jlDrawPixelSet(c, --startX, r->Y);
}
}
if (r->ScanRight) {
if (how) {
while(endX < width && (jlDrawPixelGet(c, endX, r->Y) == c->_jlDrawFillColor)) jlDrawPixelSet(c, endX++, r->Y);
} else {
while(endX < width && (jlDrawPixelGet(c, endX, r->Y) != c->_jlDrawFillColor)) jlDrawPixelSet(c, endX++, r->Y);
}
}
r->StartX--;
r->EndX++;
if (r->Y > 0) _jlDrawFillAddLine(c, startX, endX, r->Y-1, r->StartX, r->EndX, -1, r->Dir <= 0, how);
if (r->Y < height-1) _jlDrawFillAddLine(c, startX, endX, r->Y+1, r->StartX, r->EndX, 1, r->Dir >= 0, how);
DEL(r);
}
}
void jlDrawFill(jlContextT *c, jint16 x, jint16 y) {
c->_jlDrawFillColor = jlDrawPixelGet(c, x, y);
_jlDrawFill(c, x, y, jtrue);
}
void jlDrawFillTo(jlContextT *c, jint16 x, jint16 y, jbyte color) {
c->_jlDrawFillColor = color;
_jlDrawFill(c, x, y, jfalse);
}
void jlDrawLine(jlContextT *c, jint16 x1, jint16 y1, jint16 x2, jint16 y2) {
jint16 x;
jint16 y;
jint16 dx;
jint16 dy;
jint16 incX;
jint16 incY;
jint16 balance;
if (x2 >= x1) {
dx = x2 - x1;
incX = 1;
} else {
dx = x1 - x2;
incX = -1;
}
if (y2 >= y1) {
dy = y2 - y1;
incY = 1;
} else {
dy = y1 - y2;
incY = -1;
}
x = x1;
y = y1;
if (dx >= dy) {
dy <<= 1;
balance = dy - dx;
dx <<= 1;
while (x != x2) {
jlDrawPixelSet(c, x, y);
if (balance >= 0) {
y += incY;
balance -= dx;
}
balance += dy;
x += incX;
}
jlDrawPixelSet(c, x, y);
} else {
dx <<= 1;
balance = dx - dy;
dy <<= 1;
while (y != y2) {
jlDrawPixelSet(c, x, y);
if (balance >= 0) {
x += incX;
balance -= dy;
}
balance += dx;
y += incY;
}
jlDrawPixelSet(c, x, y);
}
}
jbyte jlDrawPixelGet(jlContextT *c, jint16 x, jint16 y) {
unsigned int offset = (x + y * 320) * 4;
int r = c->_pixels[offset + 2];
int g = c->_pixels[offset + 1];
int b = c->_pixels[offset];
int index = 0;
int i;
// Find the palette index for this color.
for (i=0; i<16; i++) {
if (r == c->_jlPalette[i].r && g == c->_jlPalette[i].g && b == c->_jlPalette[i].b) {
index = i;
break;
}
}
return index;
}
void jlDrawPixelSet(jlContextT *c, jint16 x, jint16 y) {
unsigned int offset = (x + y * 320) * 4;
c->_pixels[offset] = c->_jlPalette[c->_jlDrawColor].b;
c->_pixels[offset + 1] = c->_jlPalette[c->_jlDrawColor].g;
c->_pixels[offset + 2] = c->_jlPalette[c->_jlDrawColor].r;
// We're using CAIRO_FORMAT_RGB24 so the upper 8 bits are not used.
c->_pixels[offset + 3] = 255; // This is alpha in CAIRO_FORMAT_ARGB32 mode.
}
void jlPaletteDefault(jlContextT *c) {
jbyte i;
// Set palette.
c->_jlPalette[ 0].r = 0; c->_jlPalette[ 0].g = 0; c->_jlPalette[ 0].b = 0; // 000000 Black
c->_jlPalette[ 1].r = 0; c->_jlPalette[ 1].g = 0; c->_jlPalette[ 1].b = 10; // 0000AA Blue
c->_jlPalette[ 2].r = 0; c->_jlPalette[ 2].g = 10; c->_jlPalette[ 2].b = 0; // 00AA00 Green
c->_jlPalette[ 3].r = 0; c->_jlPalette[ 3].g = 10; c->_jlPalette[ 3].b = 10; // 00AAAA Cyan
c->_jlPalette[ 4].r = 10; c->_jlPalette[ 4].g = 0; c->_jlPalette[ 4].b = 0; // AA0000 Red
c->_jlPalette[ 5].r = 10; c->_jlPalette[ 5].g = 0; c->_jlPalette[ 5].b = 10; // AA00AA Magenta
c->_jlPalette[ 6].r = 10; c->_jlPalette[ 6].g = 5; c->_jlPalette[ 6].b = 0; // AA5500 Brown
c->_jlPalette[ 7].r = 10; c->_jlPalette[ 7].g = 10; c->_jlPalette[ 7].b = 10; // AAAAAA Light Gray
c->_jlPalette[ 8].r = 5; c->_jlPalette[ 8].g = 5; c->_jlPalette[ 8].b = 5; // 555555 Dark Gray
c->_jlPalette[ 9].r = 5; c->_jlPalette[ 9].g = 5; c->_jlPalette[ 9].b = 15; // 5555FF Bright Blue
c->_jlPalette[10].r = 5; c->_jlPalette[10].g = 15; c->_jlPalette[10].b = 5; // 55FF55 Bright Green
c->_jlPalette[11].r = 5; c->_jlPalette[11].g = 15; c->_jlPalette[11].b = 15; // 55FFFF Bright Cyan
c->_jlPalette[12].r = 15; c->_jlPalette[12].g = 5; c->_jlPalette[12].b = 5; // FF5555 Bright Red
c->_jlPalette[13].r = 15; c->_jlPalette[13].g = 5; c->_jlPalette[13].b = 15; // FF55FF Bright Magenta
c->_jlPalette[14].r = 15; c->_jlPalette[14].g = 15; c->_jlPalette[14].b = 5; // FFFF55 Bright Yellow
c->_jlPalette[15].r = 15; c->_jlPalette[15].g = 15; c->_jlPalette[15].b = 15; // FFFFFF White
// Adjust 4 bit color values to 8 bit.
for (i=0; i<16; i++) {
c->_jlPalette[i].r *= 16;
c->_jlPalette[i].g *= 16;
c->_jlPalette[i].b *= 16;
}
}
void jlPaletteSet(jlContextT *c, jbyte index, jbyte r, jbyte g, jbyte b) {
c->_jlPalette[index].r = r * 16;
c->_jlPalette[index].g = g * 16;
c->_jlPalette[index].b = b * 16;
}
void *_jlUtilStackPop(jlContextT *c, jlStackT **stack) {
void *d = NULL;
jlStackT *s;
(void)c;
if (*stack != NULL) {
s = *stack;
d = s->data;
*stack = s->previous;
DEL(s);
}
return d;
}
void _jlUtilStackPush(jlContextT *c, jlStackT **stack, void *data, jint16 line, const char *file) {
jlStackT *s = NULL;
(void)line;
(void)file;
(void)c;
s = NEW(jlStackT);
s->previous = *stack;
s->data = data;
*stack = s;
}
#pragma clang diagnostic pop