Refactor HUD model to allow scaling font glyphs

- Should be less pixelation of touch keyboard on large tablet screens
This commit is contained in:
Aaron Culliney 2016-01-01 14:11:27 -08:00
parent a54a69efcc
commit f5bbda4c6e
4 changed files with 203 additions and 80 deletions

View File

@ -12,6 +12,106 @@
#include "glhudmodel.h" #include "glhudmodel.h"
#include "glvideo.h" #include "glvideo.h"
// . . .
// . x .
// . . .
typedef struct EightPatchArgs_s {
GLModel *parent;
unsigned int pixelSize;
unsigned int glyphScale;
unsigned int fb_h;
unsigned int fb_w;
unsigned int srcIdx;
unsigned int dstIdx;
} EightPatchArgs_s;
// Generates a semi-opaque halo effect around each glyph
static void _eightpatch_opaquePixelHaloFilter(const EightPatchArgs_s args) {
#if USE_RGBA4444
# define SEMI_OPAQUE (0x0C << SHIFT_A)
#else
# define SEMI_OPAQUE (0xC0 << SHIFT_A)
#endif
const unsigned int pixelSize = args.pixelSize;
const unsigned int glyphScale = args.glyphScale;
const unsigned int fb_w = args.fb_w;
const unsigned int fb_h = args.fb_h;
const unsigned int srcIdx = args.srcIdx;
const unsigned int srcCol = (srcIdx % fb_w);
const unsigned int lastCol = (fb_w-1);
const unsigned int dstPointStride = pixelSize * glyphScale;
const unsigned int dstRowStride = fb_w * dstPointStride;
const unsigned int texRowStride = ((fb_w * glyphScale) * (glyphScale/*1 row*/ * glyphScale) * pixelSize);
const unsigned int lastPoint = ((fb_w * glyphScale) * (fb_h * glyphScale) * pixelSize);
uint8_t *texPixels = args.parent->texPixels;
const int dstIdx0 = (int)args.dstIdx;
const int dstPre0 = dstIdx0 - texRowStride; // negative is okay
const int dstPost0 = dstIdx0 + texRowStride;
// scale glyph data 1x, 2x, ...
// north pixels
if (dstPre0 >= 0) {
int dstPre = dstPre0;
for (unsigned int k=0; k<glyphScale; k++, dstPre+=dstRowStride) {
for (unsigned int l=0; l<glyphScale; l++, dstPre+=pixelSize) {
if (srcCol != 0) {
*((PIXEL_TYPE*)(texPixels + dstPre - dstPointStride)) |= SEMI_OPAQUE;
}
*((PIXEL_TYPE*)(texPixels + dstPre)) |= SEMI_OPAQUE;
if (srcCol < lastCol) {
*((PIXEL_TYPE*)(texPixels + dstPre + dstPointStride)) |= SEMI_OPAQUE;
}
}
dstPre -= dstPointStride;
}
}
// west pixel
if (srcCol != 0) {
int dstIdx = dstIdx0;
for (unsigned int k=0; k<glyphScale; k++, dstIdx+=dstRowStride) {
for (unsigned int l=0; l<glyphScale; l++, dstIdx+=pixelSize) {
*((PIXEL_TYPE*)(texPixels + dstIdx - dstPointStride)) |= SEMI_OPAQUE;
}
dstIdx -= dstPointStride;
}
}
// east pixel
if (srcCol < lastCol) {
int dstIdx = dstIdx0;
for (unsigned int k=0; k<glyphScale; k++, dstIdx+=dstRowStride) {
for (unsigned int l=0; l<glyphScale; l++, dstIdx+=pixelSize) {
*((PIXEL_TYPE*)(texPixels + dstIdx + dstPointStride)) |= SEMI_OPAQUE;
}
dstIdx -= dstPointStride;
}
}
// south pixels
if (dstPost0 < lastPoint) {
int dstPost = dstPost0;
for (unsigned int k=0; k<glyphScale; k++, dstPost+=dstRowStride) {
for (unsigned int l=0; l<glyphScale; l++, dstPost+=pixelSize) {
if (srcCol != 0) {
*((PIXEL_TYPE*)(texPixels + dstPost - dstPointStride)) |= SEMI_OPAQUE;
}
*((PIXEL_TYPE*)(texPixels + dstPost)) |= SEMI_OPAQUE;
if (srcCol < lastCol) {
*((PIXEL_TYPE*)(texPixels + dstPost + dstPointStride)) |= SEMI_OPAQUE;
}
}
dstPost -= dstPointStride;
}
}
}
// ----------------------------------------------------------------------------
void *glhud_createDefault(void) { void *glhud_createDefault(void) {
return glhud_createCustom(sizeof(GLModelHUDElement)); return glhud_createCustom(sizeof(GLModelHUDElement));
} }
@ -20,6 +120,7 @@ void *glhud_createCustom(unsigned int sizeofModel) {
assert(sizeof(GLModelHUDElement) <= sizeofModel); assert(sizeof(GLModelHUDElement) <= sizeofModel);
GLModelHUDElement *hudElement = (GLModelHUDElement *)CALLOC(sizeofModel, 1); GLModelHUDElement *hudElement = (GLModelHUDElement *)CALLOC(sizeofModel, 1);
if (hudElement) { if (hudElement) {
hudElement->glyphMultiplier = 1;
hudElement->colorScheme = RED_ON_BLACK; hudElement->colorScheme = RED_ON_BLACK;
} }
return hudElement; return hudElement;
@ -28,6 +129,8 @@ void *glhud_createCustom(unsigned int sizeofModel) {
void glhud_setupDefault(GLModel *parent) { void glhud_setupDefault(GLModel *parent) {
GLModelHUDElement *hudElement = (GLModelHUDElement *)parent->custom; GLModelHUDElement *hudElement = (GLModelHUDElement *)parent->custom;
assert(hudElement->glyphMultiplier > 0);
char *submenu = (char *)(hudElement->tpl); char *submenu = (char *)(hudElement->tpl);
const unsigned int cols = hudElement->tplWidth; const unsigned int cols = hudElement->tplWidth;
const unsigned int rows = hudElement->tplHeight; const unsigned int rows = hudElement->tplHeight;
@ -36,71 +139,68 @@ void glhud_setupDefault(GLModel *parent) {
// render template into indexed fb // render template into indexed fb
interface_plotMessage(fb, hudElement->colorScheme, submenu, cols, rows); interface_plotMessage(fb, hudElement->colorScheme, submenu, cols, rows);
// Generate OpenGL color from indexed color // generate OpenGL texture/color from indexed color
const unsigned int fb_w = hudElement->pixWidth; const unsigned int fb_w = hudElement->pixWidth;
const unsigned int fb_h = hudElement->pixHeight; const unsigned int fb_h = hudElement->pixHeight;
const unsigned int count = fb_w * fb_h; const unsigned int pixelSize = sizeof(PIXEL_TYPE);
const unsigned int countOut = count * sizeof(PIXEL_TYPE); const unsigned int glyphScale = hudElement->glyphMultiplier;
for (unsigned int srcIdx=0, dstIdx=0; srcIdx<count; srcIdx++, dstIdx+=sizeof(PIXEL_TYPE)) { const unsigned int dstPointStride = pixelSize * glyphScale;
uint8_t index = *(fb + srcIdx); const unsigned int dstRowStride = fb_w * dstPointStride;
PIXEL_TYPE rgb = (((PIXEL_TYPE)(colormap[index].red) << SHIFT_R) | const unsigned int texSubRowStride = dstRowStride * (glyphScale-1);
((PIXEL_TYPE)(colormap[index].green) << SHIFT_G) |
((PIXEL_TYPE)(colormap[index].blue) << SHIFT_B)); LOG("fb_h:%u, fb_w:%u -- texH:%u texW:%u", fb_h, fb_w, parent->texHeight, parent->texWidth);
if (rgb == 0 && hudElement->blackIsTransparent) {
// make black transparent do {
} else { unsigned int srcIdx = 0;
rgb |= ((PIXEL_TYPE)MAX_SATURATION << SHIFT_A); unsigned int texIdx = 0;
for (unsigned int i=0; i<fb_h; i++, texIdx+=texSubRowStride) {
for (unsigned int j=0; j<fb_w; j++, srcIdx++, texIdx+=dstPointStride) {
uint8_t value = *(fb + srcIdx);
PIXEL_TYPE rgba = (((PIXEL_TYPE)(colormap[value].red) << SHIFT_R) |
((PIXEL_TYPE)(colormap[value].green) << SHIFT_G) |
((PIXEL_TYPE)(colormap[value].blue) << SHIFT_B));
if (rgba == 0 && hudElement->blackIsTransparent) {
// black remains transparent
} else {
rgba |= ((PIXEL_TYPE)MAX_SATURATION << SHIFT_A);
}
// scale glyph data 1x, 2x, ...
unsigned int dstIdx = texIdx;
for (unsigned int k=0; k<glyphScale; k++, dstIdx+=dstRowStride) {
for (unsigned int l=0; l<glyphScale; l++, dstIdx+=pixelSize) {
*( (PIXEL_TYPE *)(parent->texPixels + dstIdx) ) = rgba;
}
dstIdx -= dstPointStride;
}
}
} }
*( (PIXEL_TYPE*)(parent->texPixels + dstIdx) ) = rgb; } while (0);
}
// Second pass to generate a semi-opaque halo effect around each glyph
if (hudElement->opaquePixelHalo) { if (hudElement->opaquePixelHalo) {
for (int // -negative index values allowed here ... unsigned int srcIdx = 0;
srcIdx=0, dstPre=-((fb_w+1)*sizeof(PIXEL_TYPE)), dstIdx=0, dstPost=((fb_w-1)*sizeof(PIXEL_TYPE)); unsigned int texIdx = 0;
srcIdx<count; for (unsigned int i=0; i<fb_h; i++, texIdx+=texSubRowStride) {
srcIdx++, dstPre+=sizeof(PIXEL_TYPE), dstIdx+=sizeof(PIXEL_TYPE), dstPost+=sizeof(PIXEL_TYPE)) for (unsigned int j=0; j<fb_w; j++, srcIdx++, texIdx+=dstPointStride) {
{ uint8_t value = *(fb + srcIdx);
uint8_t index = *(fb + srcIdx); PIXEL_TYPE rgb = (((PIXEL_TYPE)(colormap[value].red) << SHIFT_R) |
PIXEL_TYPE rgb = (((PIXEL_TYPE)(colormap[index].red) << SHIFT_R) | ((PIXEL_TYPE)(colormap[value].green) << SHIFT_G) |
((PIXEL_TYPE)(colormap[index].green) << SHIFT_G) | ((PIXEL_TYPE)(colormap[value].blue) << SHIFT_B));
((PIXEL_TYPE)(colormap[index].blue) << SHIFT_B));
if (!rgb) {
continue;
}
#if USE_RGBA4444 unsigned int dstIdx = texIdx;
#define SEMI_OPAQUE (0x0C << SHIFT_A)
#else
#define SEMI_OPAQUE (0xC0 << SHIFT_A)
#endif
const unsigned int col = (srcIdx % fb_w);
if (dstPre >= 0) { // north pixels // perform "eight patch" on adjacent pixels
if (col != 0) { if (rgb) {
*((PIXEL_TYPE*)(parent->texPixels + dstPre)) |= SEMI_OPAQUE; EightPatchArgs_s args = {
} .parent = parent,
*((PIXEL_TYPE*)(parent->texPixels + dstPre + sizeof(PIXEL_TYPE) )) |= SEMI_OPAQUE; .pixelSize = pixelSize,
if (col < fb_w-1) { .glyphScale = glyphScale,
*((PIXEL_TYPE*)(parent->texPixels + dstPre + (2*sizeof(PIXEL_TYPE)) )) |= SEMI_OPAQUE; .fb_h = fb_h,
} .fb_w = fb_w,
} .srcIdx = srcIdx,
.dstIdx = dstIdx,
if (col != 0) { // west pixel };
*((PIXEL_TYPE*)(parent->texPixels + dstIdx - sizeof(PIXEL_TYPE) )) |= SEMI_OPAQUE; _eightpatch_opaquePixelHaloFilter(args);
}
if (col < fb_w-1) { // east pixel
*((PIXEL_TYPE*)(parent->texPixels + dstIdx + sizeof(PIXEL_TYPE) )) |= SEMI_OPAQUE;
}
if (dstPost < countOut) { // south pixels
if (col != 0) {
*((PIXEL_TYPE*)(parent->texPixels + dstPost)) |= SEMI_OPAQUE;
}
*((PIXEL_TYPE*)(parent->texPixels + dstPost + sizeof(PIXEL_TYPE) )) |= SEMI_OPAQUE;
if (col < fb_w-1) {
*((PIXEL_TYPE*)(parent->texPixels + dstPost + (2*sizeof(PIXEL_TYPE)) )) |= SEMI_OPAQUE;
} }
} }
} }

View File

@ -24,8 +24,9 @@
unsigned int tplHeight; /* template height */ \ unsigned int tplHeight; /* template height */ \
\ \
uint8_t *pixels; /* raw indexed data */ \ uint8_t *pixels; /* raw indexed data */ \
unsigned int pixWidth; /* FB width -- FIXME TODO : this is really the same as GLModel.texWidth */ \ unsigned int pixWidth; /* FB width -- this is the same as GLModel.texWidth if glyphMultiplier is 1 */ \
unsigned int pixHeight; /* FB height -- FIXME TODO : this is really the same as GLModel.texHeight */ \ unsigned int pixHeight; /* FB height -- this is the same as GLModel.texHeight if glyphMultiplier is 1 */ \
unsigned int glyphMultiplier; \
\ \
interface_colorscheme_t colorScheme; \ interface_colorscheme_t colorScheme; \
bool blackIsTransparent; \ bool blackIsTransparent; \

View File

@ -37,8 +37,8 @@
#define MAINROW 4 // main keyboard row offset #define MAINROW 4 // main keyboard row offset
#define SWITCHCOL 0 #define SWITCHCOL 0
#define KBD_FB_WIDTH (KBD_TEMPLATE_COLS * FONT80_WIDTH_PIXELS) #define KBD_FB_WIDTH (KBD_TEMPLATE_COLS * FONT80_WIDTH_PIXELS) // 10 * 7 == 70
#define KBD_FB_HEIGHT (KBD_TEMPLATE_ROWS * FONT_HEIGHT_PIXELS) #define KBD_FB_HEIGHT (KBD_TEMPLATE_ROWS * FONT_HEIGHT_PIXELS) // 8 * 16 == 128
#define KBD_OBJ_W 2.0 #define KBD_OBJ_W 2.0
#define KBD_OBJ_H 2.0 #define KBD_OBJ_H 2.0
@ -129,27 +129,34 @@ static struct {
bool ctrlPressed; bool ctrlPressed;
unsigned int glyphMultiplier;
struct timespec timingBegin; struct timespec timingBegin;
} kbd = { 0 }; } kbd = { 0 };
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Misc internal methods // Misc internal methods
#warning FIXME TODO ... make this a generic GLModelHUDElement function
static void _rerender_character(int col, int row) { static void _rerender_character(int col, int row) {
GLModelHUDKeyboard *hudKeyboard = (GLModelHUDKeyboard *)(kbd.model->custom); GLModelHUDKeyboard *hudKeyboard = (GLModelHUDKeyboard *)(kbd.model->custom);
// re-generate texture from indexed color // In English, this changes one glyph within the keyboard texture data to be the (un)selected color. It handles
const unsigned int colCount = 1; // scaling from indexed color to RGBA8888 (4x) and then possibly scaling to 2x or greater.
const unsigned int pixCharsWidth = FONT80_WIDTH_PIXELS*colCount;
const unsigned int rowStride = hudKeyboard->pixWidth - pixCharsWidth;
unsigned int srcIdx = (row * hudKeyboard->pixWidth * FONT_HEIGHT_PIXELS) + (col * FONT80_WIDTH_PIXELS);
unsigned int dstIdx = srcIdx * sizeof(PIXEL_TYPE);
for (unsigned int i=0; i<FONT_HEIGHT_PIXELS; i++) { const unsigned int fb_w = hudKeyboard->pixWidth;
for (unsigned int j=0; j<pixCharsWidth; j++) { const unsigned int pixelSize = sizeof(PIXEL_TYPE);
const unsigned int glyphScale = hudKeyboard->glyphMultiplier;
const unsigned int dstPointStride = pixelSize * glyphScale;
const unsigned int dstRowStride = fb_w * dstPointStride;
const unsigned int texSubRowStride = dstRowStride + (dstRowStride * (glyphScale-1));
const unsigned int indexedIdx = (row * fb_w * FONT_HEIGHT_PIXELS) + (col * FONT80_WIDTH_PIXELS);
unsigned int texIdx = ((row * fb_w * FONT_HEIGHT_PIXELS * /*1 row:*/glyphScale) + (col * FONT80_WIDTH_PIXELS)) * dstPointStride;
// HACK : red <-> green swap for (unsigned int i=0; i<FONT_HEIGHT_PIXELS; i++, texIdx+=texSubRowStride) {
PIXEL_TYPE rgba = *((PIXEL_TYPE *)(kbd.model->texPixels + dstIdx)); for (unsigned int j=0; j<FONT80_WIDTH_PIXELS; j++, texIdx+=dstPointStride) {
// HACK : red <-> green swap of texture data
PIXEL_TYPE rgba = *((PIXEL_TYPE *)(kbd.model->texPixels + texIdx));
PIXEL_TYPE r = (rgba >> SHIFT_R) & MAX_SATURATION; PIXEL_TYPE r = (rgba >> SHIFT_R) & MAX_SATURATION;
PIXEL_TYPE g = (rgba >> SHIFT_G) & MAX_SATURATION; PIXEL_TYPE g = (rgba >> SHIFT_G) & MAX_SATURATION;
#if USE_RGBA4444 #if USE_RGBA4444
@ -157,13 +164,16 @@ static void _rerender_character(int col, int row) {
#else #else
rgba = ( ((rgba>>SHIFT_B)<<SHIFT_B) | (r << SHIFT_G) | (g << SHIFT_R) ); rgba = ( ((rgba>>SHIFT_B)<<SHIFT_B) | (r << SHIFT_G) | (g << SHIFT_R) );
#endif #endif
*( (PIXEL_TYPE *)(kbd.model->texPixels + dstIdx) ) = rgba; // scale texture data 1x, 2x, ...
unsigned int dstIdx = texIdx;
srcIdx += 1; for (unsigned int k=0; k<glyphScale; k++, dstIdx+=dstRowStride) {
dstIdx += sizeof(PIXEL_TYPE); for (unsigned int l=0; l<glyphScale; l++, dstIdx+=pixelSize) {
*( (PIXEL_TYPE *)(kbd.model->texPixels + dstIdx) ) = rgba;
}
dstIdx -= dstPointStride;
}
} }
srcIdx += rowStride; texIdx -= (FONT80_WIDTH_PIXELS * dstPointStride);
dstIdx = srcIdx * sizeof(PIXEL_TYPE);
} }
kbd.model->texDirty = true; kbd.model->texDirty = true;
@ -464,6 +474,7 @@ static void *_create_touchkbd_hud(void) {
if (hudKeyboard) { if (hudKeyboard) {
hudKeyboard->blackIsTransparent = true; hudKeyboard->blackIsTransparent = true;
hudKeyboard->opaquePixelHalo = true; hudKeyboard->opaquePixelHalo = true;
hudKeyboard->glyphMultiplier = kbd.glyphMultiplier;
} }
return hudKeyboard; return hudKeyboard;
} }
@ -498,7 +509,9 @@ static void gltouchkbd_setup(void) {
gltouchkbd_shutdown(); gltouchkbd_shutdown();
kbd.model = mdlCreateQuad(-1.0, -1.0, KBD_OBJ_W, KBD_OBJ_H, MODEL_DEPTH, KBD_FB_WIDTH, KBD_FB_HEIGHT, (GLCustom){ GLsizei texW = KBD_FB_WIDTH * kbd.glyphMultiplier;
GLsizei texH = KBD_FB_HEIGHT * kbd.glyphMultiplier;
kbd.model = mdlCreateQuad(-1.0, -1.0, KBD_OBJ_W, KBD_OBJ_H, MODEL_DEPTH, texW, texH, (GLCustom){
.create = &_create_touchkbd_hud, .create = &_create_touchkbd_hud,
.setup = &_setup_touchkbd_hud, .setup = &_setup_touchkbd_hud,
.destroy = &_destroy_touchkbd_hud, .destroy = &_destroy_touchkbd_hud,
@ -974,6 +987,8 @@ static void _init_gltouchkbd(void) {
kbd.ctrlCol = DEFAULT_CTRL_COL; kbd.ctrlCol = DEFAULT_CTRL_COL;
kbd.ctrlRow = CTRLROW; kbd.ctrlRow = CTRLROW;
kbd.glyphMultiplier = 1;
glnode_registerNode(RENDER_LOW, (GLNode){ glnode_registerNode(RENDER_LOW, (GLNode){
.setup = &gltouchkbd_setup, .setup = &gltouchkbd_setup,
.shutdown = &gltouchkbd_shutdown, .shutdown = &gltouchkbd_shutdown,

View File

@ -71,6 +71,7 @@ static struct {
static struct { static struct {
GLModel *model; GLModel *model;
unsigned int glyphMultiplier;
bool topLeftShowing; bool topLeftShowing;
bool topRightShowing; bool topRightShowing;
} menu = { 0 }; } menu = { 0 };
@ -356,6 +357,7 @@ static void *_create_touchmenu(void) {
if (hudMenu) { if (hudMenu) {
hudMenu->blackIsTransparent = true; hudMenu->blackIsTransparent = true;
hudMenu->opaquePixelHalo = true; hudMenu->opaquePixelHalo = true;
hudMenu->glyphMultiplier = menu.glyphMultiplier;
} }
return hudMenu; return hudMenu;
} }
@ -376,7 +378,10 @@ static void gltouchmenu_setup(void) {
LOG("gltouchmenu_setup ..."); LOG("gltouchmenu_setup ...");
mdlDestroyModel(&menu.model); mdlDestroyModel(&menu.model);
menu.model = mdlCreateQuad(-1.0, 1.0-MENU_OBJ_H, MENU_OBJ_W, MENU_OBJ_H, MODEL_DEPTH, MENU_FB_WIDTH, MENU_FB_HEIGHT, (GLCustom){
GLsizei texW = MENU_FB_WIDTH * menu.glyphMultiplier;
GLsizei texH = MENU_FB_HEIGHT * menu.glyphMultiplier;
menu.model = mdlCreateQuad(-1.0, 1.0-MENU_OBJ_H, MENU_OBJ_W, MENU_OBJ_H, MODEL_DEPTH, texW, texH, (GLCustom){
.create = &_create_touchmenu, .create = &_create_touchmenu,
.setup = &_setup_touchmenu, .setup = &_setup_touchmenu,
.destroy = &_destroy_touchmenu, .destroy = &_destroy_touchmenu,
@ -552,6 +557,8 @@ static void _init_gltouchmenu(void) {
interface_setTouchMenuEnabled = &gltouchmenu_setTouchMenuEnabled; interface_setTouchMenuEnabled = &gltouchmenu_setTouchMenuEnabled;
interface_setTouchMenuVisibility = &gltouchmenu_setTouchMenuVisibility; interface_setTouchMenuVisibility = &gltouchmenu_setTouchMenuVisibility;
menu.glyphMultiplier = 1;
glnode_registerNode(RENDER_TOP, (GLNode){ glnode_registerNode(RENDER_TOP, (GLNode){
.setup = &gltouchmenu_setup, .setup = &gltouchmenu_setup,
.shutdown = &gltouchmenu_shutdown, .shutdown = &gltouchmenu_shutdown,