570 lines
15 KiB
C
570 lines
15 KiB
C
/*
|
|
* Roo/E, the Kangaroo Punch Portable GUI Toolkit
|
|
* Copyright (C) 2026 Scott Duensing
|
|
*
|
|
* http://kangaroopunch.com
|
|
*
|
|
*
|
|
* This file is part of Roo/E.
|
|
*
|
|
* Roo/E is free software: you can redistribute it and/or modify it under the
|
|
* terms of the GNU Affero General Public License as published by the Free
|
|
* Software Foundation, either version 3 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* Roo/E is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with Roo/E. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
|
|
#include "kpssurf.h"
|
|
|
|
|
|
SurfaceT *__surfaceActive = NULL;
|
|
uint8_t __surfaceBitsPerPixel = 0;
|
|
uint8_t __surfaceBytesPerPixel = 0;
|
|
SurfaceFormatT __surfaceFormat = { 0 };
|
|
|
|
|
|
void (*surfaceLineH)(int16_t x1, int16_t x2, int16_t y, ColorT c);
|
|
void (*surfaceLineV)(int16_t x, int16_t y1, int16_t y2, ColorT c);
|
|
ColorT (*surfacePixelGet)(SurfaceT *surface, int16_t x, int16_t y);
|
|
void (*surfacePixelSet)(uint16_t x, uint16_t y, ColorT color);
|
|
|
|
|
|
static void surfaceLineH8(int16_t x1, int16_t x2, int16_t y, ColorT c);
|
|
static void surfaceLineH16(int16_t x1, int16_t x2, int16_t y, ColorT c);
|
|
static void surfaceLineH32(int16_t x1, int16_t x2, int16_t y, ColorT c);
|
|
|
|
static void surfaceLineV8(int16_t x, int16_t y1, int16_t y2, ColorT c);
|
|
static void surfaceLineV16(int16_t x, int16_t y1, int16_t y2, ColorT c);
|
|
static void surfaceLineV32(int16_t x, int16_t y1, int16_t y2, ColorT c);
|
|
|
|
static ColorT surfacePixelGet8(SurfaceT *surface, int16_t x, int16_t y);
|
|
static ColorT surfacePixelGet16(SurfaceT *surface, int16_t x, int16_t y);
|
|
static ColorT surfacePixelGet32(SurfaceT *surface, int16_t x, int16_t y);
|
|
|
|
static void surfacePixelSet8(uint16_t x, uint16_t y, ColorT color);
|
|
static void surfacePixelSet16(uint16_t x, uint16_t y, ColorT color);
|
|
static void surfacePixelSet32(uint16_t x, uint16_t y, ColorT color);
|
|
|
|
|
|
void surfaceBlit(int16_t targetX, int16_t targetY, int16_t offsetX, int16_t offsetY, int16_t width, int16_t height, SurfaceT *source) {
|
|
int16_t i;
|
|
int16_t x = 0;
|
|
int16_t y = 0;
|
|
size_t bytes;
|
|
size_t offsetTarget;
|
|
size_t offsetSource;
|
|
|
|
// Did they provide a valid width/height?
|
|
if (width <= 0 || width > source->width) width = source->width;
|
|
if (height <= 0 || height > source->height) height = source->height;
|
|
|
|
// Clip on top and left. x1 & y1 are pixel locations inside the source bitmap.
|
|
if (targetX < 0) x = -targetX;
|
|
if (targetY < 0) y = -targetY;
|
|
|
|
// Clip on right and bottom.
|
|
if (targetX + width - offsetX > __surfaceActive->width) width -= targetX + width - offsetX - __surfaceActive->width;
|
|
if (targetY + height - offsetY > __surfaceActive->height) height -= targetY + height - offsetY - __surfaceActive->height;
|
|
|
|
// Are we still on the screen?
|
|
if (x < 0 || y < 0 || width < x || height < y) return;
|
|
|
|
if (targetX == 0 && targetY == 0 && __surfaceActive->width == source->width && __surfaceActive->height == source->height) {
|
|
// Direct blit of entire surface.
|
|
memcpy(__surfaceActive->buffer.bits8, source->buffer.bits8, source->bytes);
|
|
} else {
|
|
// Blit into larger surface.
|
|
offsetTarget = (targetY + y) * __surfaceActive->scanline + (targetX + x) * __surfaceBytesPerPixel;
|
|
offsetSource = (y + offsetY) * source->scanline + (x + offsetX) * __surfaceBytesPerPixel;
|
|
bytes = (width - x) * __surfaceBytesPerPixel;
|
|
for (i=y; i<height; i++) {
|
|
memcpy(&__surfaceActive->buffer.bits8[offsetTarget], &source->buffer.bits8[offsetSource], bytes);
|
|
offsetTarget += __surfaceActive->scanline;
|
|
offsetSource += source->scanline;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void surfaceBlitWithTransparency(int16_t targetX, int16_t targetY, SurfaceT *source, ColorT transparent) {
|
|
int16_t x;
|
|
int16_t y;
|
|
int16_t x1 = 0;
|
|
int16_t y1 = 0;
|
|
int16_t x2 = source->width;
|
|
int16_t y2 = source->height;
|
|
ColorT pixel;
|
|
|
|
// Clip on top and left. x1 & y1 are pixel locations inside the source bitmap. ox & oy offset those into screen coordinates.
|
|
if (targetX < 0) x1 = -targetX;
|
|
if (targetY < 0) y1 = -targetY;
|
|
|
|
// Clip on right and bottom.
|
|
if (targetX + x2 > __surfaceActive->width) x2 -= targetX + x2 - __surfaceActive->width;
|
|
if (targetY + y2 > __surfaceActive->height) y2 -= targetY + y2 - __surfaceActive->height;
|
|
|
|
// Are we still on the screen?
|
|
if (x1 < 0 || y1 < 0 || x2 < x1 || y2 < y1) return;
|
|
|
|
// Blit.
|
|
for (y=y1; y<y2; y++) {
|
|
for (x=x1; x<x2; x++) {
|
|
pixel = surfacePixelGet(source, x, y);
|
|
if (transparent != pixel) {
|
|
surfacePixelSet(targetX + x, targetY + y, pixel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void surfaceClear(ColorT color) {
|
|
uint16_t x;
|
|
size_t offsetTarget;
|
|
|
|
// Draw the top line.
|
|
surfaceLineH(0, __surfaceActive->width - 1, 0, color);
|
|
|
|
// Copy it to the other lines.
|
|
offsetTarget = __surfaceActive->scanline;
|
|
for (x=1; x<__surfaceActive->height; x++) {
|
|
memcpy(&__surfaceActive->buffer.bits8[offsetTarget], &__surfaceActive->buffer.bits8[0], __surfaceActive->scanline);
|
|
offsetTarget += __surfaceActive->scanline;
|
|
}
|
|
}
|
|
|
|
|
|
ColorT surfaceColorMake(uint8_t r, uint8_t g, uint8_t b) {
|
|
return
|
|
(r >> __surfaceFormat.rLoss) << __surfaceFormat.rShift |
|
|
(g >> __surfaceFormat.gLoss) << __surfaceFormat.gShift |
|
|
(b >> __surfaceFormat.bLoss) << __surfaceFormat.bShift |
|
|
((255 >> __surfaceFormat.aLoss) << __surfaceFormat.aShift & __surfaceFormat.aMask);
|
|
}
|
|
|
|
|
|
SurfaceT *surfaceCreate(int16_t width, int16_t height) {
|
|
SurfaceT *surface = NULL;
|
|
|
|
NEW(SurfaceT, surface);
|
|
if (!surface) return NULL;
|
|
|
|
surface->width = width;
|
|
surface->height = height;
|
|
surface->scanline = width * __surfaceBytesPerPixel;
|
|
surface->bytes = surface->scanline * height;
|
|
|
|
surface->buffer.bits8 = malloc(surface->bytes);
|
|
if (!surface->buffer.bits8) {
|
|
free(surface);
|
|
return NULL;
|
|
}
|
|
|
|
return surface;
|
|
}
|
|
|
|
|
|
void surfaceBox(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT c) {
|
|
surfaceLineH(x1, x2, y1, c);
|
|
surfaceLineH(x1, x2, y2, c);
|
|
surfaceLineV(x1, y1, y2, c);
|
|
surfaceLineV(x2, y1, y2, c);
|
|
}
|
|
|
|
|
|
void surfaceBoxFilled(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT c) {
|
|
int16_t i;
|
|
size_t offsetTarget;
|
|
size_t offsetSource;
|
|
uint16_t width;
|
|
|
|
if (x1 > x2) {
|
|
i = x1;
|
|
x1 = x2;
|
|
x2 = i;
|
|
}
|
|
|
|
if (y1 > y2) {
|
|
i = y1;
|
|
y1 = y2;
|
|
y2 = i;
|
|
}
|
|
|
|
width = (x2 - x1 + 1) * __surfaceBytesPerPixel;
|
|
|
|
// Draw the top line.
|
|
surfaceLineH(x1, x2, y1, c);
|
|
|
|
// Copy it to the other lines.
|
|
offsetTarget = __surfaceActive->scanline * (y1 + 1) + (x1 * __surfaceBytesPerPixel);
|
|
offsetSource = __surfaceActive->scanline * y1 + (x1 * __surfaceBytesPerPixel);
|
|
for (i=y1 + 1; i<=y2; i++) {
|
|
memcpy(&__surfaceActive->buffer.bits8[offsetTarget], &__surfaceActive->buffer.bits8[offsetSource], width);
|
|
offsetTarget += __surfaceActive->scanline;
|
|
}
|
|
}
|
|
|
|
|
|
void surfaceBoxHighlight(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT highlight, ColorT shadow) {
|
|
surfaceLineH(x1, x2, y1, highlight);
|
|
surfaceLineV(x1, y1, y2, highlight);
|
|
surfaceLineH(x1, x2, y2, shadow);
|
|
surfaceLineV(x2, y1, y2, shadow);
|
|
}
|
|
|
|
|
|
void surfaceDestroy(SurfaceT **surface) {
|
|
SurfaceT *s = *surface;
|
|
DEL(s->buffer.bits8);
|
|
DEL(s);
|
|
*surface = NULL;
|
|
}
|
|
|
|
|
|
SurfaceT *surfaceGet(void) {
|
|
return __surfaceActive;
|
|
}
|
|
|
|
|
|
int16_t surfaceHeightGet(SurfaceT *surface) {
|
|
return surface->height;
|
|
}
|
|
|
|
|
|
void surfaceLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2, ColorT color) {
|
|
int16_t x;
|
|
int16_t y;
|
|
int16_t dx;
|
|
int16_t dy;
|
|
int16_t incX;
|
|
int16_t incY;
|
|
int16_t 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) {
|
|
surfacePixelSet(x, y, color);
|
|
if (balance >= 0) {
|
|
y += incY;
|
|
balance -= dx;
|
|
}
|
|
balance += dy;
|
|
x += incX;
|
|
}
|
|
surfacePixelSet(x, y, color);
|
|
} else {
|
|
dx <<= 1;
|
|
balance = dx - dy;
|
|
dy <<= 1;
|
|
while (y != y2) {
|
|
surfacePixelSet(x, y, color);
|
|
if (balance >= 0) {
|
|
x += incX;
|
|
balance -= dy;
|
|
}
|
|
balance += dx;
|
|
y += incY;
|
|
}
|
|
surfacePixelSet(x, y, color);
|
|
}
|
|
}
|
|
|
|
|
|
static void surfaceLineH8(int16_t x1, int16_t x2, int16_t y, ColorT c) {
|
|
int16_t i;
|
|
size_t offset;
|
|
|
|
if (x1 > x2) {
|
|
i = x2;
|
|
x2 = x1;
|
|
x1 = i;
|
|
}
|
|
|
|
offset = y * __surfaceActive->width + x1;
|
|
for (i=x1; i<=x2; i++) __surfaceActive->buffer.bits8[offset++] = (uint8_t)c;
|
|
}
|
|
|
|
|
|
static void surfaceLineH16(int16_t x1, int16_t x2, int16_t y, ColorT c) {
|
|
int16_t i;
|
|
size_t offset;
|
|
|
|
if (x1 > x2) {
|
|
i = x2;
|
|
x2 = x1;
|
|
x1 = i;
|
|
}
|
|
|
|
offset = y * __surfaceActive->width + x1;
|
|
for (i=x1; i<=x2; i++) __surfaceActive->buffer.bits16[offset++] = (uint16_t)c;
|
|
}
|
|
|
|
|
|
static void surfaceLineH32(int16_t x1, int16_t x2, int16_t y, ColorT c) {
|
|
int16_t i;
|
|
size_t offset;
|
|
|
|
if (x1 > x2) {
|
|
i = x2;
|
|
x2 = x1;
|
|
x1 = i;
|
|
}
|
|
|
|
offset = y * __surfaceActive->width + x1;
|
|
for (i=x1; i<=x2; i++) __surfaceActive->buffer.bits32[offset++] = (uint32_t)c;
|
|
}
|
|
|
|
|
|
static void surfaceLineV8(int16_t x, int16_t y1, int16_t y2, ColorT c) {
|
|
int16_t i;
|
|
size_t offset;
|
|
|
|
if (y1 > y2) {
|
|
i = y2;
|
|
y2 = y1;
|
|
y1 = i;
|
|
}
|
|
|
|
offset = y1 * __surfaceActive->width + x;
|
|
for (i=y1; i<=y2; i++) {
|
|
__surfaceActive->buffer.bits8[offset] = (uint8_t)c;
|
|
offset += __surfaceActive->width;
|
|
}
|
|
}
|
|
|
|
|
|
static void surfaceLineV16(int16_t x, int16_t y1, int16_t y2, ColorT c) {
|
|
int16_t i;
|
|
size_t offset;
|
|
|
|
if (y1 > y2) {
|
|
i = y2;
|
|
y2 = y1;
|
|
y1 = i;
|
|
}
|
|
|
|
offset = y1 * __surfaceActive->width + x;
|
|
for (i=y1; i<=y2; i++) {
|
|
__surfaceActive->buffer.bits16[offset] = (uint16_t)c;
|
|
offset += __surfaceActive->width;
|
|
}
|
|
}
|
|
|
|
|
|
static void surfaceLineV32(int16_t x, int16_t y1, int16_t y2, ColorT c) {
|
|
int16_t i;
|
|
size_t offset;
|
|
|
|
if (y1 > y2) {
|
|
i = y2;
|
|
y2 = y1;
|
|
y1 = i;
|
|
}
|
|
|
|
offset = y1 * __surfaceActive->width + x;
|
|
for (i=y1; i<=y2; i++) {
|
|
__surfaceActive->buffer.bits32[offset] = (uint32_t)c;
|
|
offset += __surfaceActive->width;
|
|
}
|
|
}
|
|
|
|
|
|
SurfaceT *surfaceLoad(char *filename) {
|
|
char ext[5] = { 0 };
|
|
char *name = NULL;
|
|
FILE *in = NULL;
|
|
SurfaceT *i = NULL;
|
|
|
|
sprintf(ext, "S%d", __surfaceBitsPerPixel);
|
|
name = utilFileExtensionChange(filename, ext);
|
|
|
|
if (!utilFileExists(name)) {
|
|
in = fopen(name, "rb");
|
|
if (in) {
|
|
NEW(SurfaceT, i);
|
|
if (!i) return NULL;
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wunused-result"
|
|
fread(&i->width, sizeof(uint16_t), 1, in);
|
|
fread(&i->height, sizeof(uint16_t), 1, in);
|
|
fread(&i->scanline, sizeof(size_t), 1, in);
|
|
fread(&i->bytes, sizeof(size_t), 1, in);
|
|
i->buffer.bits8 = (uint8_t *)malloc(i->bytes);
|
|
fread(i->buffer.bits8, i->bytes, 1, in);
|
|
#pragma GCC diagnostic pop
|
|
fclose(in);
|
|
}
|
|
}
|
|
|
|
DEL(name);
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
static ColorT surfacePixelGet8(SurfaceT *surface, int16_t x, int16_t y) {
|
|
return surface->buffer.bits8[y * surface->width + x];
|
|
}
|
|
|
|
|
|
static ColorT surfacePixelGet16(SurfaceT *surface, int16_t x, int16_t y) {
|
|
return surface->buffer.bits16[y * surface->width + x];
|
|
}
|
|
|
|
|
|
static ColorT surfacePixelGet32(SurfaceT *surface, int16_t x, int16_t y) {
|
|
return surface->buffer.bits32[y * surface->width + x];
|
|
}
|
|
|
|
|
|
static void surfacePixelSet8(uint16_t x, uint16_t y, ColorT color) {
|
|
__surfaceActive->buffer.bits8[y * __surfaceActive->width + x] = (uint8_t)color;
|
|
}
|
|
|
|
|
|
static void surfacePixelSet16(uint16_t x, uint16_t y, ColorT color) {
|
|
__surfaceActive->buffer.bits16[y * __surfaceActive->width + x] = (uint16_t)color;
|
|
}
|
|
|
|
|
|
static void surfacePixelSet32(uint16_t x, uint16_t y, ColorT color) {
|
|
__surfaceActive->buffer.bits32[y * __surfaceActive->width + x] = color;
|
|
}
|
|
|
|
|
|
void surfaceSave(SurfaceT *surface, char *filename) {
|
|
char ext[5] = { 0 };
|
|
char *name = NULL;
|
|
FILE *out = NULL;
|
|
|
|
sprintf(ext, "S%d", __surfaceBitsPerPixel);
|
|
name = utilFileExtensionChange(filename, ext);
|
|
|
|
out = fopen(name, "wb");
|
|
if (out) {
|
|
fwrite(&surface->width, sizeof(uint16_t), 1, out);
|
|
fwrite(&surface->height, sizeof(uint16_t), 1, out);
|
|
fwrite(&surface->scanline, sizeof(size_t), 1, out);
|
|
fwrite(&surface->bytes, sizeof(size_t), 1, out);
|
|
fwrite(surface->buffer.bits8, surface->bytes, 1, out);
|
|
fclose(out);
|
|
}
|
|
|
|
DEL(name);
|
|
}
|
|
|
|
|
|
void surfaceSet(SurfaceT *surface) {
|
|
__surfaceActive = surface;
|
|
}
|
|
|
|
|
|
void surfaceShutdown(void) {
|
|
// Nada
|
|
}
|
|
|
|
|
|
void surfaceStartup(uint8_t bits) {
|
|
uint8_t redMaskSize;
|
|
uint8_t greenMaskSize;
|
|
uint8_t blueMaskSize;
|
|
uint8_t alphaMaskSize;
|
|
|
|
__surfaceBitsPerPixel = bits;
|
|
__surfaceBytesPerPixel = bits >> 3;
|
|
|
|
switch (bits) {
|
|
case 8:
|
|
// xxx 3:3:2
|
|
alphaMaskSize = 0;
|
|
redMaskSize = 3;
|
|
greenMaskSize = 3;
|
|
blueMaskSize = 2;
|
|
surfaceLineH = surfaceLineH8;
|
|
surfaceLineV = surfaceLineV8;
|
|
surfacePixelSet = surfacePixelSet8;
|
|
surfacePixelGet = surfacePixelGet8;
|
|
break;
|
|
|
|
case 16:
|
|
// xx 5:6:5
|
|
alphaMaskSize = 0;
|
|
redMaskSize = 5;
|
|
greenMaskSize = 6;
|
|
blueMaskSize = 5;
|
|
surfaceLineH = surfaceLineH16;
|
|
surfaceLineV = surfaceLineV16;
|
|
surfacePixelSet = surfacePixelSet16;
|
|
surfacePixelGet = surfacePixelGet16;
|
|
break;
|
|
|
|
default:
|
|
// x 8:8:8
|
|
alphaMaskSize = 8;
|
|
redMaskSize = 8;
|
|
greenMaskSize = 8;
|
|
blueMaskSize = 8;
|
|
surfaceLineH = surfaceLineH32;
|
|
surfaceLineV = surfaceLineV32;
|
|
surfacePixelSet = surfacePixelSet32;
|
|
surfacePixelGet = surfacePixelGet32;
|
|
break;
|
|
}
|
|
|
|
__surfaceFormat.bShift = 0;
|
|
__surfaceFormat.gShift = __surfaceFormat.bShift + blueMaskSize;
|
|
__surfaceFormat.rShift = __surfaceFormat.gShift + greenMaskSize;
|
|
__surfaceFormat.aShift = __surfaceFormat.rShift + redMaskSize;
|
|
|
|
__surfaceFormat.rMask = ((1UL << redMaskSize) - 1) << __surfaceFormat.rShift;
|
|
__surfaceFormat.gMask = ((1UL << greenMaskSize) - 1) << __surfaceFormat.gShift;
|
|
__surfaceFormat.bMask = ((1UL << blueMaskSize) - 1) << __surfaceFormat.bShift;
|
|
__surfaceFormat.aMask = ((1UL << alphaMaskSize) - 1) << __surfaceFormat.aShift;
|
|
|
|
__surfaceFormat.rLoss = 8 - redMaskSize;
|
|
__surfaceFormat.gLoss = 8 - greenMaskSize;
|
|
__surfaceFormat.bLoss = 8 - blueMaskSize;
|
|
__surfaceFormat.aLoss = 8 - alphaMaskSize;
|
|
|
|
/*
|
|
logWrite("Surface Red Mask %u Shift %u Loss %u\n", __surfaceFormat.rMask, __surfaceFormat.rShift, __surfaceFormat.rLoss);
|
|
logWrite("Surface Green Mask %u Shift %u Loss %u\n", __surfaceFormat.gMask, __surfaceFormat.gShift, __surfaceFormat.gLoss);
|
|
logWrite("Surface Blue Mask %u Shift %u Loss %u\n", __surfaceFormat.bMask, __surfaceFormat.bShift, __surfaceFormat.bLoss);
|
|
logWrite("Surface Alpha Mask %u Shift %u Loss %u\n\n", __surfaceFormat.aMask, __surfaceFormat.aShift, __surfaceFormat.aLoss);
|
|
*/
|
|
}
|
|
|
|
|
|
int16_t surfaceWidthGet(SurfaceT *surface) {
|
|
return surface->width;
|
|
}
|