452 lines
13 KiB
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
|