/* * JoeyDev * Copyright (C) 2018-2023 Scott Duensing * * 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" //***TODO*** This entire thing should render to a 4 bit buffer and only // convert to truecolor when needed. That way we can implement palette // side effects. 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 int32_t _jlSeed = 0; 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 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); 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= 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; // Clip at edge. We can do this on modern machines! if (x < 0) return 0; if (x > 319) return 0; if (y < 0) return 0; if (y > 199) return 0; // 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; // Clip at edge. We can do this on modern machines! if (x < 0) return; if (x > 319) return; if (y < 0) return; if (y > 199) return; c->_pixels[offset] = c->_jlPalette[c->_jlDrawColor].b * 16; c->_pixels[offset + 1] = c->_jlPalette[c->_jlDrawColor].g * 16; c->_pixels[offset + 2] = c->_jlPalette[c->_jlDrawColor].r * 16; // 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) { // 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 } void jlPaletteGet(jlContextT *c, jbyte index, jbyte *r, jbyte *g, jbyte *b) { // This is not a standard JoeyLib API. It returns color values in 0-255 instead of 0-15. *r = c->_jlPalette[index].r; *g = c->_jlPalette[index].g; *b = c->_jlPalette[index].b; } void jlPaletteSet(jlContextT *c, jbyte index, jbyte r, jbyte g, jbyte b) { c->_jlPalette[index].r = r; c->_jlPalette[index].g = g; c->_jlPalette[index].b = b; } int16_t jlUtilRandom(void) { _jlSeed = _jlSeed * 1103515245 + 12345; return _jlSeed / 65536; } int jlUtilRandomSeedGet(void) { return _jlSeed; } void jlUtilRandomSeedSet(int seed) { _jlSeed = seed; } 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