diff --git a/blitbuffer.c b/blitbuffer.c index f59ed2063..85dec512f 100644 --- a/blitbuffer.c +++ b/blitbuffer.c @@ -72,6 +72,7 @@ static const char* 39185*ColorRGB16_GetG(v) + \ 15220*ColorRGB16_GetB(v)) >> 14U) #define RGB_To_RGB16(r, g, b) (((r & 0xF8) << 8U) + ((g & 0xFC) << 3U) + (b >> 3U)) +// NOTE: `A` was a *terrible* variable name to settle on. It's actually luminance, e.g., grayscale, a.k.a., Y8. #define RGB_To_A(r, g, b) ((4898U*r + 9618U*g + 1869U*b) >> 14U) // Helpers to pack pixels manually, without going through the Color structs. @@ -92,6 +93,9 @@ static const char* (((_v >> 8U) + _v) >> 8U); \ }) +#define ColorRGB32_To_PMUL(color) \ + (ColorRGB32){DIV_255(color->r * color->alpha), DIV_255(color->g * color->alpha), DIV_255(color->b * color->alpha), color->alpha} + // MIN/MAX with no side-effects, // c.f., https://gcc.gnu.org/onlinedocs/cpp/Duplication-of-Side-Effects.html#Duplication-of-Side-Effects // & https://dustri.org/b/min-and-max-macro-considered-harmful.html @@ -182,7 +186,7 @@ static const char* }) static inline void BB8_SET_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const Color8 * restrict color) { - if (likely(x >= 0U && x < width && y >= 0U && y < height)) { + if (likely(x < width && y < height)) { Color8 * restrict pixel; BB_GET_PIXEL(bb, rotation, Color8, x, y, &pixel); *pixel = *color; @@ -190,7 +194,7 @@ static inline void BB8_SET_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, } static inline void BB8A_SET_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const Color8A * restrict color) { - if (likely(x >= 0U && x < width && y >= 0U && y < height)) { + if (likely(x < width && y < height)) { Color8A * restrict pixel; BB_GET_PIXEL(bb, rotation, Color8A, x, y, &pixel); *pixel = *color; @@ -198,7 +202,7 @@ static inline void BB8A_SET_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation } static inline void BBRGB16_SET_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const ColorRGB16 * restrict color) { - if (likely(x >= 0U && x < width && y >= 0U && y < height)) { + if (likely(x < width && y < height)) { ColorRGB16 * restrict pixel; BB_GET_PIXEL(bb, rotation, ColorRGB16, x, y, &pixel); *pixel = *color; @@ -206,7 +210,7 @@ static inline void BBRGB16_SET_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotat } static inline void BBRGB24_SET_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const ColorRGB24 * restrict color) { - if (likely(x >= 0U && x < width && y >= 0U && y < height)) { + if (likely(x < width && y < height)) { ColorRGB24 * restrict pixel; BB_GET_PIXEL(bb, rotation, ColorRGB24, x, y, &pixel); *pixel = *color; @@ -214,7 +218,7 @@ static inline void BBRGB24_SET_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotat } static inline void BBRGB32_SET_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const ColorRGB32 * restrict color) { - if (likely(x >= 0U && x < width && y >= 0U && y < height)) { + if (likely(x < width && y < height)) { ColorRGB32 * restrict pixel; BB_GET_PIXEL(bb, rotation, ColorRGB32, x, y, &pixel); *pixel = *color; @@ -222,7 +226,7 @@ static inline void BBRGB32_SET_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotat } static inline void BB8_BLEND_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const Color8A * restrict color) { - if (likely(x >= 0U && x < width && y >= 0U && y < height)) { + if (likely(x < width && y < height)) { Color8 * restrict pixel; BB_GET_PIXEL(bb, rotation, Color8, x, y, &pixel); @@ -233,7 +237,7 @@ static inline void BB8_BLEND_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotatio } } static inline void BB8A_BLEND_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const Color8A * restrict color) { - if (likely(x >= 0U && x < width && y >= 0U && y < height)) { + if (likely(x < width && y < height)) { Color8A * restrict pixel; BB_GET_PIXEL(bb, rotation, Color8A, x, y, &pixel); @@ -245,7 +249,7 @@ static inline void BB8A_BLEND_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotati } } static inline void BBRGB16_BLEND_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const Color8A * restrict color) { - if (likely(x >= 0U && x < width && y >= 0U && y < height)) { + if (likely(x < width && y < height)) { ColorRGB16 * restrict pixel; BB_GET_PIXEL(bb, rotation, ColorRGB16, x, y, &pixel); @@ -260,7 +264,7 @@ static inline void BBRGB16_BLEND_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rot } static inline void BBRGB24_BLEND_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const ColorRGB32 * restrict color) { - if (likely(x >= 0U && x < width && y >= 0U && y < height)) { + if (likely(x < width && y < height)) { ColorRGB24 * restrict pixel; BB_GET_PIXEL(bb, rotation, ColorRGB24, x, y, &pixel); @@ -274,7 +278,7 @@ static inline void BBRGB24_BLEND_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rot } static inline void BBRGB32_BLEND_PIXEL_CLAMPED(BlitBuffer * restrict bb, int rotation, unsigned int x, unsigned int y, unsigned int width, unsigned int height, const ColorRGB32 * restrict color) { - if (likely(x >= 0U && x < width && y >= 0U && y < height)) { + if (likely(x < width && y < height)) { ColorRGB32 * restrict pixel; BB_GET_PIXEL(bb, rotation, ColorRGB32, x, y, &pixel); @@ -484,7 +488,143 @@ void BB_fill_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsi } } -void BB_blend_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, Color8A * restrict color) { +void BB_fill_rect_RGB32(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color) { + const int rotation = GET_BB_ROTATION(bb); + unsigned int rx, ry, rw, rh; + // Compute rotated rectangle coordinates & size + switch (rotation) { + case 0: + rx = x; + ry = y; + rw = w; + rh = h; + break; + case 1: + rx = bb->w - (y + h); + ry = x; + rw = h; + rh = w; + break; + case 2: + rx = bb->w - (x + w); + ry = bb->h - (y + h); + rw = w; + rh = h; + break; + case 3: + rx = y; + ry = bb->h - (x + w); + rw = h; + rh = w; + break; + } + + // Handle any target pitch properly + const int bb_type = GET_BB_TYPE(bb); + switch (bb_type) { + case TYPE_BB8: + if (rx == 0 && rw == bb->w) { + // Single step for contiguous scanlines (e.g., BB_fill()) + //fprintf(stdout, "%s: Full BB8 paintRect\n", __FUNCTION__); + uint8_t * restrict p = bb->data + bb->stride*ry; + memset(p, RGB_To_A(color->r, color->g, color->b), bb->stride*rh); + } else { + // Scanline per scanline + //fprintf(stdout, "%s: Scanline BB8 paintRect\n", __FUNCTION__); + const uint8_t source_y8 = RGB_To_A(color->r, color->g, color->b); + for (unsigned int j = ry; j < ry+rh; j++) { + uint8_t * restrict p = bb->data + bb->stride*j + rx; + memset(p, source_y8, rw); + } + } + break; + case TYPE_BB8A: + // We do NOT want to stomp on the alpha byte here... + if (rx == 0 && rw == bb->w) { + // Single step for contiguous scanlines + const uint16_t src = (uint16_t) Y8_To_Y8A(RGB_To_A(color->r, color->g, color->b)); + //fprintf(stdout, "%s: Full BB8A paintRect\n", __FUNCTION__); + uint16_t * restrict p = (uint16_t *) (bb->data + bb->stride*ry); + size_t px_count = bb->pixel_stride*rh; + while (px_count--) { + *p++ = src; + } + } else { + // Scanline per scanline + const uint16_t src = (uint16_t) Y8_To_Y8A(RGB_To_A(color->r, color->g, color->b)); + //fprintf(stdout, "%s: Scanline BB8A paintRect\n", __FUNCTION__); + for (unsigned int j = ry; j < ry+rh; j++) { + uint16_t * restrict p = (uint16_t *) (bb->data + bb->stride*j) + rx; + size_t px_count = rw; + while (px_count--) { + *p++ = src; + } + } + } + break; + case TYPE_BBRGB16: + // Again, RGB565 means we can't use a straight memset + if (rx == 0 && rw == bb->w) { + // Single step for contiguous scanlines + const ColorRGB16 src = ColorRGB32_To_Color16(color); + //fprintf(stdout, "%s: Full BBRGB16 paintRect\n", __FUNCTION__); + ColorRGB16 * restrict p = (ColorRGB16 *) (bb->data + bb->stride*ry); + size_t px_count = bb->pixel_stride*rh; + while (px_count--) { + *p++ = src; + } + } else { + // Scanline per scanline + const ColorRGB16 src = ColorRGB32_To_Color16(color); + //fprintf(stdout, "%s: Sanline BBRGB16 paintRect\n", __FUNCTION__); + for (unsigned int j = ry; j < ry+rh; j++) { + ColorRGB16 * restrict p = (ColorRGB16 *) (bb->data + bb->stride*j) + rx; + size_t px_count = rw; + while (px_count--) { + *p++ = src; + } + } + } + break; + case TYPE_BBRGB24: + { + // Pixel per pixel + const ColorRGB24 src = ColorRGB32_To_Color24(color); + //fprintf(stdout, "%s: Pixel BBRGB24 paintRect\n", __FUNCTION__); + for (unsigned int j = ry; j < ry+rh; j++) { + for (unsigned int k = rx; k < rx+rw; k++) { + uint8_t * restrict p = bb->data + bb->stride*j + (k * 3U); + memcpy(p, &src, 3); + } + } + } + break; + case TYPE_BBRGB32: + // And here either, as we want to preserve the alpha byte + if (rx == 0 && rw == bb->w) { + // Single step for contiguous scanlines + //fprintf(stdout, "%s: Full BBRGB32 paintRect\n", __FUNCTION__); + ColorRGB32 * restrict p = (ColorRGB32 *) (bb->data + bb->stride*ry); + size_t px_count = bb->pixel_stride*rh; + while (px_count--) { + *p++ = *color; + } + } else { + // Scanline per scanline + //fprintf(stdout, "%s: Pixel BBRGB32 paintRect\n", __FUNCTION__); + for (unsigned int j = ry; j < ry+rh; j++) { + ColorRGB32 * restrict p = (ColorRGB32 *) (bb->data + bb->stride*j) + rx; + size_t px_count = rw; + while (px_count--) { + *p++ = *color; + } + } + } + break; + } +} + +void BB_blend_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const Color8A * restrict color) { const int bb_type = GET_BB_TYPE(bb); const int bb_rotation = GET_BB_ROTATION(bb); const uint8_t alpha = color->alpha; @@ -545,6 +685,211 @@ void BB_blend_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, uns } } +void BB_blend_RGB32_over_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color) { + const int bb_type = GET_BB_TYPE(bb); + const int bb_rotation = GET_BB_ROTATION(bb); + const uint8_t alpha = color->alpha; + const uint8_t ainv = alpha ^ 0xFF; + switch (bb_type) { + case TYPE_BB8: + { + const uint8_t source_y8 = RGB_To_A(color->r, color->g, color->b); + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + Color8 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, Color8, i, j, &dstptr); + dstptr->a = (uint8_t) DIV_255(dstptr->a * ainv + source_y8 * alpha); + } + } + } + break; + case TYPE_BB8A: + { + const uint8_t source_y8 = RGB_To_A(color->r, color->g, color->b); + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + Color8A * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, Color8A, i, j, &dstptr); + dstptr->a = (uint8_t) DIV_255(dstptr->a * ainv + source_y8 * alpha); + } + } + } + break; + case TYPE_BBRGB16: + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + ColorRGB16 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, ColorRGB16, i, j, &dstptr); + const uint8_t r = (uint8_t) DIV_255(ColorRGB16_GetR(dstptr->v) * ainv + color->r * alpha); + const uint8_t g = (uint8_t) DIV_255(ColorRGB16_GetG(dstptr->v) * ainv + color->g * alpha); + const uint8_t b = (uint8_t) DIV_255(ColorRGB16_GetB(dstptr->v) * ainv + color->b * alpha); + dstptr->v = (uint16_t) RGB_To_RGB16(r, g, b); + } + } + break; + case TYPE_BBRGB24: + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + ColorRGB24 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, ColorRGB24, i, j, &dstptr); + dstptr->r = (uint8_t) DIV_255(dstptr->r * ainv + color->r * alpha); + dstptr->g = (uint8_t) DIV_255(dstptr->g * ainv + color->g * alpha); + dstptr->b = (uint8_t) DIV_255(dstptr->b * ainv + color->b * alpha); + } + } + break; + case TYPE_BBRGB32: + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + ColorRGB32 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, ColorRGB32, i, j, &dstptr); + dstptr->r = (uint8_t) DIV_255(dstptr->r * ainv + color->r * alpha); + dstptr->g = (uint8_t) DIV_255(dstptr->g * ainv + color->g * alpha); + dstptr->b = (uint8_t) DIV_255(dstptr->b * ainv + color->b * alpha); + } + } + break; + } +} + +// Dumb multiply blending mode (used for painting book highlights) +void BB_blend_RGB_multiply_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB24 * restrict color) { + const int bb_type = GET_BB_TYPE(bb); + const int bb_rotation = GET_BB_ROTATION(bb); + switch (bb_type) { + case TYPE_BB8: + { + const uint8_t source_y8 = RGB_To_A(color->r, color->g, color->b); + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + Color8 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, Color8, i, j, &dstptr); + dstptr->a = (uint8_t) DIV_255(dstptr->a * source_y8); + } + } + } + break; + case TYPE_BB8A: + { + const uint8_t source_y8 = RGB_To_A(color->r, color->g, color->b); + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + Color8A * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, Color8A, i, j, &dstptr); + dstptr->a = (uint8_t) DIV_255(dstptr->a * source_y8); + } + } + } + break; + case TYPE_BBRGB16: + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + ColorRGB16 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, ColorRGB16, i, j, &dstptr); + const uint8_t r = (uint8_t) DIV_255(ColorRGB16_GetR(dstptr->v) * color->r); + const uint8_t g = (uint8_t) DIV_255(ColorRGB16_GetG(dstptr->v) * color->g); + const uint8_t b = (uint8_t) DIV_255(ColorRGB16_GetB(dstptr->v) * color->b); + dstptr->v = (uint16_t) RGB_To_RGB16(r, g, b); + } + } + break; + case TYPE_BBRGB24: + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + ColorRGB24 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, ColorRGB24, i, j, &dstptr); + dstptr->r = (uint8_t) DIV_255(dstptr->r * color->r); + dstptr->g = (uint8_t) DIV_255(dstptr->g * color->g); + dstptr->b = (uint8_t) DIV_255(dstptr->b * color->b); + } + } + break; + case TYPE_BBRGB32: + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + ColorRGB32 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, ColorRGB32, i, j, &dstptr); + dstptr->r = (uint8_t) DIV_255(dstptr->r * color->r); + dstptr->g = (uint8_t) DIV_255(dstptr->g * color->g); + dstptr->b = (uint8_t) DIV_255(dstptr->b * color->b); + } + } + break; + } +} + +// Fancier variant if we ever want to honor color's alpha... +// Function name is a slight misnommer, as we're essentially doing (color MUL rect) OVER rect +void BB_blend_RGB32_multiply_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color) { + const int bb_type = GET_BB_TYPE(bb); + const int bb_rotation = GET_BB_ROTATION(bb); + const uint8_t alpha = color->alpha; + const uint8_t ainv = alpha ^ 0xFF; + switch (bb_type) { + case TYPE_BB8: + { + const uint8_t source_y8 = RGB_To_A(color->r, color->g, color->b); + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + Color8 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, Color8, i, j, &dstptr); + dstptr->a = (uint8_t) DIV_255(dstptr->a * ainv + DIV_255(dstptr->a * source_y8) * alpha); + } + } + } + break; + case TYPE_BB8A: + { + const uint8_t source_y8 = RGB_To_A(color->r, color->g, color->b); + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + Color8A * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, Color8A, i, j, &dstptr); + dstptr->a = (uint8_t) DIV_255(dstptr->a * ainv + DIV_255(dstptr->a * source_y8) * alpha); + } + } + } + break; + case TYPE_BBRGB16: + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + ColorRGB16 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, ColorRGB16, i, j, &dstptr); + const uint8_t dr = ColorRGB16_GetR(dstptr->v); + const uint8_t dg = ColorRGB16_GetR(dstptr->v); + const uint8_t db = ColorRGB16_GetR(dstptr->v); + const uint8_t r = (uint8_t) DIV_255(dr * ainv + DIV_255(dr * color->r) * alpha); + const uint8_t g = (uint8_t) DIV_255(dg * ainv + DIV_255(dg * color->g) * alpha); + const uint8_t b = (uint8_t) DIV_255(db * ainv + DIV_255(db * color->b) * alpha); + dstptr->v = (uint16_t) RGB_To_RGB16(r, g, b); + } + } + break; + case TYPE_BBRGB24: + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + ColorRGB24 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, ColorRGB24, i, j, &dstptr); + dstptr->r = (uint8_t) DIV_255(dstptr->r * ainv + DIV_255(dstptr->r * color->r) * alpha); + dstptr->g = (uint8_t) DIV_255(dstptr->g * ainv + DIV_255(dstptr->g * color->g) * alpha); + dstptr->b = (uint8_t) DIV_255(dstptr->b * ainv + DIV_255(dstptr->b * color->b) * alpha); + } + } + break; + case TYPE_BBRGB32: + for (unsigned int j = y; j < y + h; j++) { + for (unsigned int i = x; i < x + w; i++) { + ColorRGB32 * restrict dstptr; + BB_GET_PIXEL(bb, bb_rotation, ColorRGB32, i, j, &dstptr); + dstptr->r = (uint8_t) DIV_255(dstptr->r * ainv + DIV_255(dstptr->r * color->r) * alpha); + dstptr->g = (uint8_t) DIV_255(dstptr->g * ainv + DIV_255(dstptr->g * color->g) * alpha); + dstptr->b = (uint8_t) DIV_255(dstptr->b * ainv + DIV_255(dstptr->b * color->b) * alpha); + } + } + break; + } +} + void BB_invert_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h) { const int rotation = GET_BB_ROTATION(bb); unsigned int rx, ry, rw, rh; @@ -691,7 +1036,7 @@ void BB_invert_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, un } } -void BB_hatch_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, unsigned int stripe_width, Color8 * restrict color, uint8_t alpha) { +void BB_hatch_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, unsigned int stripe_width, const Color8 * restrict color, uint8_t alpha) { if (alpha == 0) { // NOP return; } @@ -2412,7 +2757,7 @@ void BB_invert_blit_from(BlitBuffer * restrict dst, const BlitBuffer * restrict } void BB_color_blit_from(BlitBuffer * restrict dst, const BlitBuffer * restrict src, - unsigned int dest_x, unsigned int dest_y, unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h, Color8A * restrict color) { + unsigned int dest_x, unsigned int dest_y, unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h, const Color8A * restrict color) { const int dbb_type = GET_BB_TYPE(dst); const int sbb_type = GET_BB_TYPE(src); const int sbb_rotation = GET_BB_ROTATION(src); @@ -2535,10 +2880,140 @@ void BB_color_blit_from(BlitBuffer * restrict dst, const BlitBuffer * restrict s } } +void BB_color_blit_from_RGB32(BlitBuffer * restrict dst, const BlitBuffer * restrict src, + unsigned int dest_x, unsigned int dest_y, unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color) { + const int dbb_type = GET_BB_TYPE(dst); + const int sbb_type = GET_BB_TYPE(src); + const int sbb_rotation = GET_BB_ROTATION(src); + const int dbb_rotation = GET_BB_ROTATION(dst); + switch (dbb_type) { + case TYPE_BB8: + { + const uint8_t source_y8 = RGB_To_A(color->r, color->g, color->b); + for (unsigned int d_y = dest_y, o_y = offs_y; d_y < dest_y + h; d_y++, o_y++) { + for (unsigned int d_x = dest_x, o_x = offs_x; d_x < dest_x + w; d_x++, o_x++) { + uint8_t alpha; + SET_ALPHA_FROM_A(src, sbb_type, sbb_rotation, o_x, o_y, &alpha); + if (alpha == 0) { + // NOP + } else if (alpha == 0xFF) { + Color8 * restrict dstptr; + BB_GET_PIXEL(dst, dbb_rotation, Color8, d_x, d_y, &dstptr); + dstptr->a = source_y8; + } else { + const uint8_t ainv = alpha ^ 0xFF; + Color8 * restrict dstptr; + BB_GET_PIXEL(dst, dbb_rotation, Color8, d_x, d_y, &dstptr); + dstptr->a = (uint8_t) DIV_255(dstptr->a * ainv + source_y8 * alpha); + } + } + } + } + break; + case TYPE_BB8A: + { + const uint8_t source_y8 = RGB_To_A(color->r, color->g, color->b); + for (unsigned int d_y = dest_y, o_y = offs_y; d_y < dest_y + h; d_y++, o_y++) { + for (unsigned int d_x = dest_x, o_x = offs_x; d_x < dest_x + w; d_x++, o_x++) { + uint8_t alpha; + SET_ALPHA_FROM_A(src, sbb_type, sbb_rotation, o_x, o_y, &alpha); + if (alpha == 0) { + // NOP + } else if (alpha == 0xFF) { + Color8A * restrict dstptr; + BB_GET_PIXEL(dst, dbb_rotation, Color8A, d_x, d_y, &dstptr); + dstptr->a = source_y8; + } else { + const uint8_t ainv = alpha ^ 0xFF; + Color8A * restrict dstptr; + BB_GET_PIXEL(dst, dbb_rotation, Color8A, d_x, d_y, &dstptr); + dstptr->a = (uint8_t) DIV_255(dstptr->a * ainv + source_y8 * alpha); + } + } + } + } + break; + case TYPE_BBRGB16: + for (unsigned int d_y = dest_y, o_y = offs_y; d_y < dest_y + h; d_y++, o_y++) { + for (unsigned int d_x = dest_x, o_x = offs_x; d_x < dest_x + w; d_x++, o_x++) { + uint8_t alpha; + SET_ALPHA_FROM_A(src, sbb_type, sbb_rotation, o_x, o_y, &alpha); + if (alpha == 0) { + // NOP + } else if (alpha == 0xFF) { + ColorRGB16 * restrict dstptr; + BB_GET_PIXEL(dst, dbb_rotation, ColorRGB16, d_x, d_y, &dstptr); + dstptr->v = (uint16_t) RGB_To_RGB16(color->r, color->g, color->b); + } else { + const uint8_t ainv = alpha ^ 0xFF; + ColorRGB16 * restrict dstptr; + BB_GET_PIXEL(dst, dbb_rotation, ColorRGB16, d_x, d_y, &dstptr); + const uint8_t r = (uint8_t) DIV_255(ColorRGB16_GetR(dstptr->v) * ainv + color->r * alpha); + const uint8_t g = (uint8_t) DIV_255(ColorRGB16_GetG(dstptr->v) * ainv + color->g * alpha); + const uint8_t b = (uint8_t) DIV_255(ColorRGB16_GetB(dstptr->v) * ainv + color->b * alpha); + dstptr->v = (uint16_t) RGB_To_RGB16(r, g, b); + } + } + } + break; + case TYPE_BBRGB24: + for (unsigned int d_y = dest_y, o_y = offs_y; d_y < dest_y + h; d_y++, o_y++) { + for (unsigned int d_x = dest_x, o_x = offs_x; d_x < dest_x + w; d_x++, o_x++) { + uint8_t alpha; + SET_ALPHA_FROM_A(src, sbb_type, sbb_rotation, o_x, o_y, &alpha); + if (alpha == 0) { + // NOP + } else if (alpha == 0xFF) { + ColorRGB24 * restrict dstptr; + BB_GET_PIXEL(dst, dbb_rotation, ColorRGB24, d_x, d_y, &dstptr); + dstptr->r = color->r; + dstptr->g = color->g; + dstptr->b = color->b; + } else { + const uint8_t ainv = alpha ^ 0xFF; + ColorRGB24 * restrict dstptr; + BB_GET_PIXEL(dst, dbb_rotation, ColorRGB24, d_x, d_y, &dstptr); + dstptr->r = (uint8_t) DIV_255(dstptr->r * ainv + color->r * alpha); + dstptr->g = (uint8_t) DIV_255(dstptr->g * ainv + color->g * alpha); + dstptr->b = (uint8_t) DIV_255(dstptr->b * ainv + color->b * alpha); + } + } + } + break; + case TYPE_BBRGB32: + for (unsigned int d_y = dest_y, o_y = offs_y; d_y < dest_y + h; d_y++, o_y++) { + for (unsigned int d_x = dest_x, o_x = offs_x; d_x < dest_x + w; d_x++, o_x++) { + // NOTE: GCC *may* throw a -Wmaybe-uninitialized about alpha here, + // because of the lack of default case in the SET_ALPHA_FROM_A switch. + // Not a cause for alarm here :). + uint8_t alpha; + SET_ALPHA_FROM_A(src, sbb_type, sbb_rotation, o_x, o_y, &alpha); + if (alpha == 0) { + // NOP + } else if (alpha == 0xFF) { + ColorRGB32 * restrict dstptr; + BB_GET_PIXEL(dst, dbb_rotation, ColorRGB32, d_x, d_y, &dstptr); + dstptr->r = color->r; + dstptr->g = color->g; + dstptr->b = color->b; + } else { + const uint8_t ainv = alpha ^ 0xFF; + ColorRGB32 * restrict dstptr; + BB_GET_PIXEL(dst, dbb_rotation, ColorRGB32, d_x, d_y, &dstptr); + dstptr->r = (uint8_t) DIV_255(dstptr->r * ainv + color->r * alpha); + dstptr->g = (uint8_t) DIV_255(dstptr->g * ainv + color->g * alpha); + dstptr->b = (uint8_t) DIV_255(dstptr->b * ainv + color->b * alpha); + } + } + } + break; + } +} + // Information about those three algorithms can be found on http://members.chello.at/~easyfilter/ (Zingl Alois) void BB_paint_rounded_corner_noAA(BlitBuffer * restrict bb, unsigned int off_x, unsigned int off_y, unsigned int w, unsigned int h, int bw, int r, uint8_t c); void BB_paint_rounded_corner_AA(BlitBuffer * restrict bb, unsigned int off_x, unsigned int off_y, unsigned int w, unsigned int h, int bw, int r, uint8_t c); -void BB_paint_rounded_corner_AA_1px(BlitBuffer * restrict bb, unsigned int off_x, unsigned int off_y, unsigned int w, unsigned int h, int bw, int r, uint8_t c); +void BB_paint_rounded_corner_AA_1px(BlitBuffer * restrict bb, unsigned int off_x, unsigned int off_y, unsigned int w, unsigned int h, int r, uint8_t c); void BB_paint_rounded_corner(BlitBuffer * restrict bb, unsigned int off_x, unsigned int off_y, unsigned int w, unsigned int h, unsigned int bw, unsigned int r, uint8_t c, int anti_aliasing) { /* @@ -2553,13 +3028,14 @@ void BB_paint_rounded_corner(BlitBuffer * restrict bb, unsigned int off_x, unsig bw = r; } - if (!anti_aliasing) + if (!anti_aliasing) { BB_paint_rounded_corner_noAA(bb, off_x, off_y, w, h, bw, r, c); - else { - if (bw == 1) - BB_paint_rounded_corner_AA_1px(bb, off_x, off_y, w, h, 1, r, c); - else + } else { + if (bw == 1) { + BB_paint_rounded_corner_AA_1px(bb, off_x, off_y, w, h, r, c); + } else { BB_paint_rounded_corner_AA(bb, off_x, off_y, w, h, bw, r, c); + } } } @@ -2615,7 +3091,7 @@ void BB_paint_rounded_corner_noAA(BlitBuffer * restrict bb, unsigned int off_x, } // Fill between inner and outer circle. - for (unsigned int tmp_y = y; tmp_y > y2; tmp_y--) { + for (int tmp_y = y; tmp_y > y2; tmp_y--) { if (bb_type == TYPE_BB8) { const Color8 color = { .a = c }; @@ -2727,7 +3203,7 @@ void BB_paint_rounded_corner_noAA(BlitBuffer * restrict bb, unsigned int off_x, setPixelAA_BBRGB32(off_x+r+x0, off_y+h-r-1+y0); \ setPixelAA_BBRGB32(off_x+w-r-1+x1,off_y+h-r-1+y0); -void BB_paint_rounded_corner_AA_1px(BlitBuffer * restrict bb, unsigned int off_x, unsigned int off_y, unsigned int w, unsigned int h, int bw, int r, uint8_t c) { // draw a black anti-aliased circle with thickness 1 +void BB_paint_rounded_corner_AA_1px(BlitBuffer * restrict bb, unsigned int off_x, unsigned int off_y, unsigned int w, unsigned int h, int r, uint8_t c) { // draw a black anti-aliased circle with thickness 1 const int bb_type = GET_BB_TYPE(bb); const int bb_rotation = GET_BB_ROTATION(bb); const unsigned int bb_width = BB_GET_WIDTH(bb); diff --git a/blitbuffer.h b/blitbuffer.h index 3c6f148a7..ce115f7ad 100644 --- a/blitbuffer.h +++ b/blitbuffer.h @@ -132,9 +132,13 @@ typedef struct BlitBufferRGB32 { DLL_PUBLIC void BB_fill(BlitBuffer * restrict bb, uint8_t v); DLL_PUBLIC void BB_fill_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint8_t v); -DLL_PUBLIC void BB_blend_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, Color8A * restrict color); +DLL_PUBLIC void BB_fill_rect_RGB32(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color); +DLL_PUBLIC void BB_blend_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const Color8A * restrict color); +DLL_PUBLIC void BB_blend_RGB32_over_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color); +DLL_PUBLIC void BB_blend_RGB_multiply_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB24 * restrict color); +DLL_PUBLIC void BB_blend_RGB32_multiply_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color); DLL_PUBLIC void BB_invert_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h); -DLL_PUBLIC void BB_hatch_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, unsigned int stripe_width, Color8 * restrict color, uint8_t alpha); +DLL_PUBLIC void BB_hatch_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, unsigned int stripe_width, const Color8 * restrict color, uint8_t alpha); DLL_PUBLIC void BB_blit_to_BB8(const BlitBuffer * restrict src, BlitBuffer * restrict dst, unsigned int dest_x, unsigned int dest_y, unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h); DLL_PUBLIC void BB_dither_blit_to_BB8(const BlitBuffer * restrict src, BlitBuffer * restrict dst, @@ -164,7 +168,9 @@ DLL_PUBLIC void BB_dither_pmulalpha_blit_from(BlitBuffer * restrict dest, const DLL_PUBLIC void BB_invert_blit_from(BlitBuffer * restrict dest, const BlitBuffer * restrict source, unsigned int dest_x, unsigned int dest_y, unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h); DLL_PUBLIC void BB_color_blit_from(BlitBuffer * restrict dest, const BlitBuffer * restrict source, unsigned int dest_x, unsigned int dest_y, - unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h, Color8A * restrict color); + unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h, const Color8A * restrict color); +DLL_PUBLIC void BB_color_blit_from_RGB32(BlitBuffer * restrict dest, const BlitBuffer * restrict source, unsigned int dest_x, unsigned int dest_y, + unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color); DLL_PUBLIC void BB_paint_rounded_corner(BlitBuffer * restrict bb, unsigned int off_x, unsigned int off_y, unsigned int w, unsigned int h, unsigned int bw, unsigned int r, uint8_t c, int anti_aliasing); #endif diff --git a/ffi/blitbuffer.lua b/ffi/blitbuffer.lua index a67af380a..d6c38ebbe 100644 --- a/ffi/blitbuffer.lua +++ b/ffi/blitbuffer.lua @@ -119,9 +119,13 @@ typedef struct BlitBufferRGB32 { void BB_fill(BlitBuffer * restrict bb, uint8_t v); void BB_fill_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint8_t v); -void BB_blend_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, Color8A * restrict color); +void BB_fill_rect_RGB32(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color); +void BB_blend_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const Color8A * restrict color); +void BB_blend_RGB32_over_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color); +void BB_blend_RGB_multiply_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB24 * restrict color); +void BB_blend_RGB32_multiply_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color); void BB_invert_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h); -void BB_hatch_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, unsigned int stripe_width, Color8 * restrict color, uint8_t alpha); +void BB_hatch_rect(BlitBuffer * restrict bb, unsigned int x, unsigned int y, unsigned int w, unsigned int h, unsigned int stripe_width, const Color8 * restrict color, uint8_t alpha); void BB_blit_to(const BlitBuffer * restrict source, BlitBuffer * restrict dest, unsigned int dest_x, unsigned int dest_y, unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h); void BB_dither_blit_to(const BlitBuffer * restrict source, BlitBuffer * restrict dest, unsigned int dest_x, unsigned int dest_y, @@ -139,7 +143,9 @@ void BB_dither_pmulalpha_blit_from(BlitBuffer * restrict dest, const BlitBuffer void BB_invert_blit_from(BlitBuffer * restrict dest, const BlitBuffer * restrict source, unsigned int dest_x, unsigned int dest_y, unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h); void BB_color_blit_from(BlitBuffer * restrict dest, const BlitBuffer * restrict source, unsigned int dest_x, unsigned int dest_y, - unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h, Color8A * restrict color); + unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h, const Color8A * restrict color); +void BB_color_blit_from_RGB32(BlitBuffer * restrict dest, const BlitBuffer * restrict source, unsigned int dest_x, unsigned int dest_y, + unsigned int offs_x, unsigned int offs_y, unsigned int w, unsigned int h, const ColorRGB32 * restrict color); void BB_paint_rounded_corner(BlitBuffer * restrict bb, unsigned int off_x, unsigned int off_y, unsigned int w, unsigned int h, unsigned int bw, unsigned int r, uint8_t c, int anti_alias); ]] @@ -365,6 +371,36 @@ function Color8_mt.__index:ditherpmulblend(x, y, color) value = dither_o8x8(x, y, value) self:set(Color8(value)) end +-- Dumb multiply blending with an RGB24 color (will be grayscaled when targeting grayscale BBs) +function Color4L_mt.__index:mul(color) + local value = div255(self:getColor8() * color:getColor8()) + self:set(Color4L(value)) +end +function Color4U_mt.__index:mul(color) +local value = div255(self:getColor8() * color:getColor8()) + self:set(Color4U(value)) +end +function Color8_mt.__index:mul(color) + local value = div255(self.a * color:getColor8()) + self:set(Color8(value)) +end +function Color8A_mt.__index:mul(color) + local value = div255(self.a * color:getColor8()) + self:set(Color8A(value, self:getAlpha())) +end +function ColorRGB16_mt.__index:mul(color) + local r = div255(self:getR() * color:getR()) + local g = div255(self:getG() * color:getG()) + local b = div255(self:getB() * color:getB()) + self:set(ColorRGB24(r, g, b)) +end +ColorRGB24_mt.__index.mul = ColorRGB16_mt.__index.mul +function ColorRGB32_mt.__index:mul(color) + local r = div255(self:getR() * color:getR()) + local g = div255(self:getG() * color:getG()) + local b = div255(self:getB() * color:getB()) + self:set(ColorRGB32(r, g, b, self:getAlpha())) +end -- color conversions: -- NOTE: These *always* return a new Color? object, even when no conversion is needed. @@ -506,7 +542,7 @@ end function ColorRGB24_mt.__index:getColorRGB32() return ColorRGB32(self.r, self.g, self.b, 0xFF) end function ColorRGB32_mt.__index:getColorRGB32() return ColorRGB32(self.r, self.g, self.b, self.alpha) end --- RGB getters (special case for 4bpp mode) +-- RGB getters (with the necessary trickery for non-RGB color types) function Color4L_mt.__index:getR() return self:getColor8().a end Color4L_mt.__index.getG = Color4L_mt.__index.getR Color4L_mt.__index.getB = Color4L_mt.__index.getR @@ -846,6 +882,14 @@ function BBRGB16_mt.__index:setPixelAdd(x, y, color, alpha) end BBRGB24_mt.__index.setPixelAdd = BBRGB16_mt.__index.setPixelAdd BBRGB32_mt.__index.setPixelAdd = BBRGB16_mt.__index.setPixelAdd +-- Multiply +function BB_mt.__index:setPixelMultiply(x, y, color) + -- this method uses an RGB24 color value + local px, py = self:getPhysicalCoordinates(x, y) + local c = color:getColorRGB24() + if self:getInverse() == 1 then c = c:invert() end + self:getPixelP(px, py)[0]:mul(c) +end -- Straight alpha blending function BB_mt.__index:setPixelBlend(x, y, color) -- fast path: @@ -960,6 +1004,41 @@ function BB_mt.__index:setPixelColorize(x, y, mask, color) self:getPixelP(px, py)[0]:blend(color, alpha) end end +-- Colorize in an RGB32 color (NOTE: colorblitFrom has already handled inversion for us) +function BB_mt.__index:setPixelColorizeRGB32(x, y, mask, color) + -- use 8bit grayscale pixel value as alpha for blitting + local alpha = mask:getColor8().a + -- fast path: + if alpha == 0 then + return + end + local px, py = self:getPhysicalCoordinates(x, y) + -- Grayscale the input color + local c = color:getColor8() + if alpha == 0xFF then + self:getPixelP(px, py)[0]:set(c) + else + -- NOTE: We're using an alpha mask, not color's actual alpha value, which we don't want to mess with, + -- as that's a pointer to our set_param... + -- Avoids screwing with alpha when blitting to 8A or RGB32 bbs (c.f., #3949). + self:getPixelP(px, py)[0]:blend(c, alpha) + end +end +function BBRGB16_mt.__index:setPixelColorizeRGB32(x, y, mask, color) + local alpha = mask:getColor8().a + if alpha == 0 then + return + end + local px, py = self:getPhysicalCoordinates(x, y) + -- We keep the input ColorRGB32 color as-is + if alpha == 0xFF then + self:getPixelP(px, py)[0]:set(color) + else + self:getPixelP(px, py)[0]:blend(color, alpha) + end +end +BBRGB24_mt.__index.setPixelColorizeRGB32 = BBRGB16_mt.__index.setPixelColorizeRGB32 +BBRGB32_mt.__index.setPixelColorizeRGB32 = BBRGB16_mt.__index.setPixelColorizeRGB32 -- Invert function BB_mt.__index:setPixelInverted(x, y, color) self:setPixel(x, y, color:invert()) @@ -1339,6 +1418,23 @@ function BB_mt.__index:colorblitFrom(source, dest_x, dest_y, offs_x, offs_y, wid end end +function BB_mt.__index:colorblitFromRGB32(source, dest_x, dest_y, offs_x, offs_y, width, height, color) + -- Enforce type coercion for safety (plus, we might need a copy for inversion anyway) + local c = color:getColorRGB32() + if self:canUseCbbTogether(source) then + width, height = width or source:getWidth(), height or source:getHeight() + width, dest_x, offs_x = BB.checkBounds(width, dest_x or 0, offs_x or 0, self:getWidth(), source:getWidth()) + height, dest_y, offs_y = BB.checkBounds(height, dest_y or 0, offs_y or 0, self:getHeight(), source:getHeight()) + if width <= 0 or height <= 0 then return end + cblitbuffer.BB_color_blit_from_RGB32(ffi.cast(P_BlitBuffer, self), + ffi.cast(P_BlitBuffer_ROData, source), + dest_x, dest_y, offs_x, offs_y, width, height, c) + else + if self:getInverse() == 1 then c = c:invert() end + self:blitFrom(source, dest_x, dest_y, offs_x, offs_y, width, height, self.setPixelColorizeRGB32, c) + end +end + -- scale method does not modify the original blitbuffer, instead, it allocates -- and returns a new scaled blitbuffer. function BB_mt.__index:scale(new_width, new_height) @@ -1404,7 +1500,7 @@ PAINTING --]] --[[ -fill the whole blitbuffer with a given (grayscale) color value +fill the whole blitbuffer with a given (grayscale) value --]] function BB_mt.__index:fill(value) if self:canUseCbb() then @@ -1691,6 +1787,121 @@ function BB4_mt.__index:paintRect(x, y, w, h, value, setter) end end +--[[ +paintRect variant that takes a ColorRGB32 instead of a luminance value +--]] +function BB_mt.__index:paintRectRGB32(x, y, w, h, color, setter) + setter = setter or self.setPixel + x, y, w, h = self:getBoundedRect(x, y, w, h) + if w <= 0 or h <= 0 then return end + -- Type coercion for safety + local c = color and color:getColorRGB32() or ColorRGB32(0x80, 0x80, 0x80, 0xFF) + if self:canUseCbb() and setter == self.setPixel then + cblitbuffer.BB_fill_rect_RGB32(ffi.cast(P_BlitBuffer, self), + x, y, w, h, c) + else + -- We can only do fast filling when there's no complex processing involved (i.e., simple setPixel only) + local bbtype = self:getType() + if setter == self.setPixel and bbtype ~= TYPE_BBRGB24 then + -- Handle rotation... + x, y, w, h = self:getPhysicalRect(x, y, w, h) + + if self:getInverse() == 1 then c = c:invert() end + + -- We check against the BB's unrotated coordinates (i.e., self.w and not self:getWidth()), + -- as our memory region has a fixed layout, too! + if x == 0 and w == self.w then + -- Single step for contiguous scanlines + --print("Single fill paintRect") + if bbtype == TYPE_BBRGB32 then + local src = c + local p = ffi.cast(P_ColorRGB32, ffi.cast(uint8pt, self.data) + self.stride*y) + for i = 1, self.pixel_stride*h do + p[0] = src + p = p+1 + end + elseif bbtype == TYPE_BBRGB16 then + local src = c:getColorRGB16() + local p = ffi.cast(P_ColorRGB16, ffi.cast(uint8pt, self.data) + self.stride*y) + for i = 1, self.pixel_stride*h do + p[0] = src + p = p+1 + end + elseif bbtype == TYPE_BB8A then + local src = c:getColor8A() + local p = ffi.cast(P_Color8A, ffi.cast(uint8pt, self.data) + self.stride*y) + for i = 1, self.pixel_stride*h do + p[0] = src + p = p+1 + end + else + -- BB8, where we can just use ffi.fill + local p = ffi.cast(uint8pt, self.data) + self.stride*y + ffi.fill(p, self.stride*h, c:getColor8().a) + end + else + -- Scanline per scanline fill + --print("Scanline fill paintRect") + if bbtype == TYPE_BBRGB32 then + local src = c + for j = y, y+h-1 do + local p = ffi.cast(P_ColorRGB32, ffi.cast(uint8pt, self.data) + self.stride*j) + x + for i = 0, w-1 do + p[0] = src + p = p+1 + end + end + elseif bbtype == TYPE_BBRGB16 then + local src = c:getColorRGB16() + for j = y, y+h-1 do + local p = ffi.cast(P_ColorRGB16, ffi.cast(uint8pt, self.data) + self.stride*j) + x + for i = 0, w-1 do + p[0] = src + p = p+1 + end + end + elseif bbtype == TYPE_BB8A then + local src = c:getColor8A() + for j = y, y+h-1 do + local p = ffi.cast(P_Color8A, ffi.cast(uint8pt, self.data) + self.stride*j) + x + for i = 0, w-1 do + p[0] = src + p = p+1 + end + end + else + -- BB8 + for j = y, y+h-1 do + local p = ffi.cast(uint8pt, self.data) + self.stride*j + x + ffi.fill(p, w, c:getColor8().a) + end + end + end + else + --print("Old-style paintRect pixel loop") + for tmp_y = y, y+h-1 do + for tmp_x = x, x+w-1 do + setter(self, tmp_x, tmp_y, color) + end + end + end + end +end + +-- BB4 version, identical if not for the lack of fast filling, because nibbles aren't addressable... +-- Also, no cbb branch, as cbb doesn't handle 4bpp targets at all. +function BB4_mt.__index:paintRectRGB32(x, y, w, h, color, setter) + setter = setter or self.setPixel + color = color and color:getColor8() or Color8(0x80) + x, y, w, h = self:getBoundedRect(x, y, w, h) + if w <= 0 or h <= 0 then return end + for tmp_y = y, y+h-1 do + for tmp_x = x, x+w-1 do + setter(self, tmp_x, tmp_y, color) + end + end +end + --[[ paint a circle onto this buffer @@ -1977,15 +2188,17 @@ function BB_mt.__index:progressBar(x, y, w, h, load_m_w, load_m_h, load_percent, end --[[ -dim color values in rectangular area +Ligthen color values in a rectangular area +(i.e., blend pure white at the requested opacity OVER rect). +NOTE: Used to be called dimRect because it effectively makes black text dimmer. @param x X coordinate @param y Y coordinate @param w width @param h height -@param by dim by this factor (default: 0.5) +@param by lighten by this factor (default: 0.5) --]] -function BB_mt.__index:dimRect(x, y, w, h, by) +function BB_mt.__index:lightenRect(x, y, w, h, by) local color = Color8A(0xFF, 0xFF*(by or 0.5)) if self:canUseCbb() then x, y, w, h = self:getBoundedRect(x, y, w, h) @@ -1998,15 +2211,16 @@ function BB_mt.__index:dimRect(x, y, w, h, by) end --[[ -lighten color values in rectangular area +Darken color values in a rectangular area +(i.e., blend pure black at the requested opacity OVER rect). @param x X coordinate @param y Y coordinate @param w width @param h height -@param by lighten by this factor (default: 0.5) +@param by darken by this factor (default: 0.5) --]] -function BB_mt.__index:lightenRect(x, y, w, h, by) +function BB_mt.__index:darkenRect(x, y, w, h, by) local color = Color8A(0, 0xFF*(by or 0.5)) if self:canUseCbb() then x, y, w, h = self:getBoundedRect(x, y, w, h) @@ -2018,6 +2232,53 @@ function BB_mt.__index:lightenRect(x, y, w, h, by) end end +--[[ +Multiply color values in a rectangular area by a given source color. +(i.e., blend color MUL rect). +Mainly used to emulate a highlighter pen over a text buffer. + +@param x X coordinate +@param y Y coordinate +@param w width +@param h height +@param color source color (alpha is ignored) +--]] +function BB_mt.__index:multiplyRectRGB(x, y, w, h, color) + -- Type coercion for safety + local c = color and color:getColorRGB24() or ColorRGB24(0x80, 0x80, 0x80) + if self:canUseCbb() then + x, y, w, h = self:getBoundedRect(x, y, w, h) + if w <= 0 or h <= 0 then return end + cblitbuffer.BB_blend_RGB_multiply_rect(ffi.cast(P_BlitBuffer, self), + x, y, w, h, c) + else + self:paintRect(x, y, w, h, c, self.setPixelMultiply) + end +end + +--[[ +Blend color values in a rectangular area +(i.e., blend color OVER rect). + +@param x X coordinate +@param y Y coordinate +@param w width +@param h height +@param color source color +--]] +function BB_mt.__index:blendRectRGB32(x, y, w, h, color) + -- Type coercion for safety + local c = color and color:getColorRGB32() or ColorRGB32(0x80, 0x80, 0x80, 0xFF) + if self:canUseCbb() then + x, y, w, h = self:getBoundedRect(x, y, w, h) + if w <= 0 or h <= 0 then return end + cblitbuffer.BB_blend_RGB32_over_rect(ffi.cast(P_BlitBuffer, self), + x, y, w, h, c) + else + self:paintRect(x, y, w, h, c, self.setPixelBlend) + end +end + --[[ make a full copy of the current buffer, with its own memory --]] @@ -2161,11 +2422,11 @@ local Jpeg -- lazy load ffi/jpeg function BB_mt.__index:getBufferData() local w, h = self:getWidth(), self:getHeight() local bbdump, source_ptr, stride - if self:getType() == BB.TYPE_BBRGB24 then + if self:getType() == TYPE_BBRGB24 then source_ptr = ffi.cast(uint8pt, self.data) stride = self.stride else - bbdump = BB.new(w, h, BB.TYPE_BBRGB24, nil) + bbdump = BB.new(w, h, TYPE_BBRGB24, nil) bbdump:blitFrom(self) source_ptr = ffi.cast(uint8pt, bbdump.data) stride = bbdump.stride @@ -2297,6 +2558,71 @@ function BB.gray(level) return Color8(bxor(floor(0xFF * level), 0xFF)) end +--[[ +return a Color value resembling a given hex string (nil on failure) +--]] +function BB.colorFromString(value) + value = value:gsub('#','') + -- #rrggbbaa + if(#value == 8) then + return ColorRGB32( + tonumber(value:sub(1, 2), 16), + tonumber(value:sub(3, 4), 16), + tonumber(value:sub(5, 6), 16), + tonumber(value:sub(7, 8), 16) + ) + -- #rrggbb + elseif(#value == 6) then + return ColorRGB32( + tonumber(value:sub(1, 2), 16), + tonumber(value:sub(3, 4), 16), + tonumber(value:sub(5, 6), 16), + 0xFF + ) + -- #vv + elseif(#value == 2) then + return ColorRGB32( + tonumber(value:sub(1, 2), 16), + tonumber(value:sub(1, 2), 16), + tonumber(value:sub(1, 2), 16), + 0xFF + ) + else + return nil + end +end + +-- Common color names used for highlights +BB.HIGHLIGHT_COLORS = { + ["red"] = "#FF3300", + ["orange"] = "#FF8800", + ["yellow"] = "#FFFF33", + ["green"] = "#00AA66", + ["olive"] = "#88FF77", + ["cyan"] = "#00FFEE", + ["blue"] = "#0066FF", + ["purple"] = "#EE00FF", +} +--[[ +return a Color value given a common color name (nil for unknown colors) +--]] +function BB.colorFromName(name) + local color_hash = BB.HIGHLIGHT_COLORS[name:lower()] + if not color_hash then return nil end + return BB.colorFromString(color_hash) +end + +--[[ +Test whether a Color is a Luminance-only type. + +Technically that would mean anything <= Color8A, +but, in practice, since we only care about the constants defined below, +we only check against Color8. +--]] +function BB.isColor8(color) + return ffi.istype(Color8, color) +end + -- the full eInk palette: BB.COLOR_WHITE = Color8(0xFF) BB.COLOR_GRAY_E = Color8(0xEE) diff --git a/ffi/framebuffer_mxcfb.lua b/ffi/framebuffer_mxcfb.lua index 347ffa1bc..fa9ed96e9 100644 --- a/ffi/framebuffer_mxcfb.lua +++ b/ffi/framebuffer_mxcfb.lua @@ -40,6 +40,7 @@ local framebuffer = { refresh_pixel_size = 1, -- Used to enforce an alignment constraint on devices with quirky drivers alignment_constraint = nil, + dither_alignment_constraint = 8, -- We recycle ffi cdata marker_data = nil, @@ -279,7 +280,7 @@ local function mxc_update(fb, ioc_cmd, ioc_data, is_flashing, waveform_mode, x, -- (Sidebar: this is probably a kernel issue, the EPDC driver is responsible for the alignment fixup, -- c.f., epdc_process_update @ drivers/video/fbdev/mxc/mxc_epdc_v2_fb.c on a Kobo Mk. 7 kernel...). -- And regardless of alignment constraints, make sure the rectangle is strictly bounded inside the screen. - x, y, w, h = bb:getBoundedRect(x, y, w, h, dither and 8 or fb.alignment_constraint) + x, y, w, h = bb:getBoundedRect(x, y, w, h, dither and fb.dither_alignment_constraint or fb.alignment_constraint) -- The ioctl operates in the native rotation, so, make sure we rotate the rectangle as needed x, y, w, h = bb:getPhysicalRect(x, y, w, h) @@ -1014,7 +1015,8 @@ function framebuffer:init() -- so partial refreshes at exact coordinates will sometimes be cut-off one pixel short... -- That can obviously lead to leftover stale content visible on screen, -- so, just enforce larger refresh regions on our side... - self.alignment_constraint = 8 + self.alignment_constraint = 16 + self.dither_alignment_constraint = 16 self.waveform_a2 = C.HWTCON_WAVEFORM_MODE_A2 self.waveform_fast = C.HWTCON_WAVEFORM_MODE_DU diff --git a/ffi/mupdf.lua b/ffi/mupdf.lua index 887fe7ab6..f7a5059d6 100644 --- a/ffi/mupdf.lua +++ b/ffi/mupdf.lua @@ -676,23 +676,44 @@ end mupdf.STRIKE_HEIGHT = 0.375 mupdf.UNDERLINE_HEIGHT = 0 mupdf.LINE_THICKNESS = 0.05 +mupdf.HIGHLIGHT_COLOR = {1.0, 1.0, 0.0} +mupdf.UNDERLINE_COLOR = {0.0, 0.0, 1.0} +mupdf.STRIKE_OUT_COLOR = {1.0, 0.0, 0.0} -function page_mt.__index:addMarkupAnnotation(points, n, type) +function page_mt.__index:addMarkupAnnotation(points, n, type, bb_color) local color = ffi.new("float[3]") local alpha = 1.0 if type == M.PDF_ANNOT_HIGHLIGHT then - color[0] = 1.0 - color[1] = 1.0 - color[2] = 0.0 + if bb_color then + color[0] = bb_color.r / 255 + color[1] = bb_color.g / 255 + color[2] = bb_color.b / 255 + else + color[0] = mupdf.HIGHLIGHT_COLOR[1] + color[1] = mupdf.HIGHLIGHT_COLOR[2] + color[2] = mupdf.HIGHLIGHT_COLOR[3] + end alpha = 0.5 elseif type == M.PDF_ANNOT_UNDERLINE then - color[0] = 0.0 - color[1] = 0.0 - color[2] = 1.0 + if bb_color then + color[0] = bb_color.r / 255 + color[1] = bb_color.g / 255 + color[2] = bb_color.b / 255 + else + color[0] = mupdf.UNDERLINE_COLOR[1] + color[1] = mupdf.UNDERLINE_COLOR[2] + color[2] = mupdf.UNDERLINE_COLOR[3] + end elseif type == M.PDF_ANNOT_STRIKE_OUT then - color[0] = 1.0 - color[1] = 0.0 - color[2] = 0.0 + if bb_color then + color[0] = bb_color.r / 255 + color[1] = bb_color.g / 255 + color[2] = bb_color.b / 255 + else + color[0] = mupdf.STRIKE_OUT_COLOR[1] + color[1] = mupdf.STRIKE_OUT_COLOR[2] + color[2] = mupdf.STRIKE_OUT_COLOR[3] + end else return end