Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for text and PNG 90-degree rotations #811

Merged
merged 2 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 57 additions & 17 deletions libraries/bitmap_fonts/bitmap_fonts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ namespace bitmap {
return text_width;
}

void character(const font_t *font, rect_func rectangle, const char c, const int32_t x, const int32_t y, const uint8_t scale, unicode_sorta::codepage_t codepage) {
void character(const font_t *font, rect_func rectangle, const char c, const int32_t x, const int32_t y, const uint8_t scale, int32_t rotation, unicode_sorta::codepage_t codepage) {
if(c < 32 || c > 127 + 64) { // + 64 char remappings defined in unicode_sorta.hpp
return;
}
Expand Down Expand Up @@ -89,7 +89,8 @@ namespace bitmap {
uint8_t accent_offset = char_index < 65 ? offset_upper : offset_lower;

// Offset our y position to account for our column canvas being 32 pixels
int y_offset = y - (8 * scale);
// this gives us 8 "pixels" of headroom above the letters for diacritic marks
int font_offset = (8 * scale);

// Iterate through each horizontal column of font (and accent) data
for(uint8_t cx = 0; cx < font->widths[char_index]; cx++) {
Expand All @@ -98,6 +99,8 @@ namespace bitmap {
// We shift the char down 8 pixels to make room for an accent above.
uint32_t data = *d << 8;

int32_t o_x = cx * scale;

// For fonts that are taller than 8 pixels (up to 16) they need two bytes
if(two_bytes_per_column) {
d++;
Expand All @@ -113,7 +116,28 @@ namespace bitmap {
// Draw the 32 pixel column
for(uint8_t cy = 0; cy < 32; cy++) {
if((1U << cy) & data) {
rectangle(x + (cx * scale), y_offset + (cy * scale), scale, scale);
int32_t o_y = cy * scale;
int32_t px = 0;
int32_t py = 0;
switch (rotation) {
case 0:
px = x + o_x;
py = y - font_offset + o_y;
break;
case 90:
px = x + font_offset - o_y;
py = y + o_x;
break;
case 180:
px = x - o_x;
py = y + font_offset - o_y;
break;
case 270:
px = x - font_offset + o_y;
py = y - o_x;
break;
}
rectangle(px, py, scale, scale);
}
}

Expand All @@ -123,8 +147,9 @@ namespace bitmap {
}
}

void text(const font_t *font, rect_func rectangle, const std::string_view &t, const int32_t x, const int32_t y, const int32_t wrap, const uint8_t scale, const uint8_t letter_spacing, bool fixed_width) {
uint32_t co = 0, lo = 0; // character and line (if wrapping) offset
void text(const font_t *font, rect_func rectangle, const std::string_view &t, const int32_t x, const int32_t y, const int32_t wrap, const uint8_t scale, const uint8_t letter_spacing, bool fixed_width, int32_t rotation) {
uint32_t char_offset = 0;
uint32_t line_offset = 0; // line (if wrapping) offset
unicode_sorta::codepage_t codepage = unicode_sorta::PAGE_195;

size_t i = 0;
Expand Down Expand Up @@ -153,37 +178,52 @@ namespace bitmap {
continue;
}
word_width += measure_character(font, t[j], scale, codepage, fixed_width);
word_width += letter_spacing * scale;
codepage = unicode_sorta::PAGE_195;
}

// if this word would exceed the wrap limit then
// move to the next line
if(co != 0 && co + word_width > (uint32_t)wrap) {
co = 0;
lo += (font->height + 1) * scale;
if(char_offset != 0 && char_offset + word_width > (uint32_t)wrap) {
char_offset = 0;
line_offset += (font->height + 1) * scale;
}

// draw word
for(size_t j = i; j < next_break; j++) {
for(size_t j = i; j < std::min(next_break + 1, t.length()); j++) {
if (t[j] == unicode_sorta::PAGE_194_START) {
codepage = unicode_sorta::PAGE_194;
continue;
} else if (t[j] == unicode_sorta::PAGE_195_START) {
continue;
}
if (t[j] == '\n') {
lo += (font->height + 1) * scale;
co = 0;
if (t[j] == '\n') { // Linebreak
line_offset += (font->height + 1) * scale;
char_offset = 0;
} else if (t[j] == ' ') { // Space
char_offset += font->widths[0] * scale;
} else {
character(font, rectangle, t[j], x + co, y + lo, scale, codepage);
co += measure_character(font, t[j], scale, codepage, fixed_width);
co += letter_spacing * scale;
switch(rotation) {
case 0:
character(font, rectangle, t[j], x + char_offset, y + line_offset, scale, rotation, codepage);
break;
case 90:
character(font, rectangle, t[j], x - line_offset, y + char_offset, scale, rotation, codepage);
break;
case 180:
character(font, rectangle, t[j], x - char_offset, y - line_offset, scale, rotation, codepage);
break;
case 270:
character(font, rectangle, t[j], x + line_offset, y - char_offset, scale, rotation, codepage);
break;
}
char_offset += measure_character(font, t[j], scale, codepage, fixed_width);
char_offset += letter_spacing * scale;
}
codepage = unicode_sorta::PAGE_195;
}

// move character offset to end of word and add a space
co += font->widths[0] * scale;
// move character offset
i = next_break += 1;
}
}
Expand Down
4 changes: 2 additions & 2 deletions libraries/bitmap_fonts/bitmap_fonts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ namespace bitmap {
int32_t measure_character(const font_t *font, const char c, const uint8_t scale, unicode_sorta::codepage_t codepage = unicode_sorta::PAGE_195, bool fixed_width = false);
int32_t measure_text(const font_t *font, const std::string_view &t, const uint8_t scale = 2, const uint8_t letter_spacing = 1, bool fixed_width = false);

void character(const font_t *font, rect_func rectangle, const char c, const int32_t x, const int32_t y, const uint8_t scale = 2, unicode_sorta::codepage_t codepage = unicode_sorta::PAGE_195);
void text(const font_t *font, rect_func rectangle, const std::string_view &t, const int32_t x, const int32_t y, const int32_t wrap, const uint8_t scale = 2, const uint8_t letter_spacing = 1, bool fixed_width = false);
void character(const font_t *font, rect_func rectangle, const char c, const int32_t x, const int32_t y, const uint8_t scale = 2, int32_t rotation = 0, unicode_sorta::codepage_t codepage = unicode_sorta::PAGE_195);
void text(const font_t *font, rect_func rectangle, const std::string_view &t, const int32_t x, const int32_t y, const int32_t wrap, const uint8_t scale = 2, const uint8_t letter_spacing = 1, bool fixed_width = false, int32_t rotation = 0);
}
4 changes: 2 additions & 2 deletions libraries/pico_graphics/pico_graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ namespace pimoroni {
if (bitmap_font) {
bitmap::character(bitmap_font, [this](int32_t x, int32_t y, int32_t w, int32_t h) {
rectangle(Rect(x, y, w, h));
}, c, p.x, p.y, std::max(1.0f, s));
}, c, p.x, p.y, std::max(1.0f, s), int32_t(a) % 360);
return;
}

Expand All @@ -148,7 +148,7 @@ namespace pimoroni {
if (bitmap_font) {
bitmap::text(bitmap_font, [this](int32_t x, int32_t y, int32_t w, int32_t h) {
rectangle(Rect(x, y, w, h));
}, t, p.x, p.y, wrap, std::max(1.0f, s), letter_spacing, fixed_width);
}, t, p.x, p.y, wrap, std::max(1.0f, s), letter_spacing, fixed_width, int32_t(a) % 360);
return;
}

Expand Down
49 changes: 43 additions & 6 deletions micropython/modules/pngdec/pngdec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ typedef struct _PNG_decode_target {
Point position = {0, 0};
Rect source = {0, 0, 0, 0};
Point scale = {1, 1};
int rotation = 0;
} _PNG_decode_target;

typedef struct _PNG_obj_t {
Expand Down Expand Up @@ -125,14 +126,37 @@ MICROPY_EVENT_POLL_HOOK
Point current_position = target->position;
uint8_t current_mode = target->mode;
Point scale = target->scale;
int rotation = target->rotation;
Point step = {0, 0};

// "pixel" is slow and clipped,
// guaranteeing we wont draw png data out of the framebuffer..
// Can we clip beforehand and make this faster?

if(pDraw->y < target->source.y || pDraw->y >= target->source.y + target->source.h) return;

current_position.y += pDraw->y * scale.y;
current_position -= Point(0, target->source.y);
switch (rotation) {
case 0:
current_position.y += (pDraw->y - target->source.y) * scale.y;
step = {scale.x, 0};
break;
case 90:
current_position.y += target->source.w * scale.y;
current_position.x += target->source.h * scale.x;
current_position.x += (pDraw->y - target->source.y) * -scale.x;
step = {0, -scale.y};
break;
case 180:
current_position.x += target->source.w * scale.x;
current_position.y += target->source.h * scale.y;
current_position.y += (pDraw->y - target->source.y) * -scale.y;
step = {-scale.x, 0};
break;
case 270:
current_position.x += (pDraw->y - target->source.y) * scale.x;
step = {0, scale.y};
break;
}

//mp_printf(&mp_plat_print, "Drawing scanline at %d, %dbpp, type: %d, width: %d pitch: %d alpha: %d\n", y, pDraw->iBpp, pDraw->iPixelType, pDraw->iWidth, pDraw->iPitch, pDraw->iHasAlpha);
uint8_t *pixel = (uint8_t *)pDraw->pPixels;
Expand All @@ -144,7 +168,7 @@ MICROPY_EVENT_POLL_HOOK
if(x < target->source.x || x >= target->source.x + target->source.w) continue;
current_graphics->set_pen(r, g, b);
current_graphics->rectangle({current_position.x, current_position.y, scale.x, scale.y});
current_position.x += scale.x;
current_position += step;
}
} else if (pDraw->iPixelType == PNG_PIXEL_TRUECOLOR_ALPHA) {
for(int x = 0; x < pDraw->iWidth; x++) {
Expand All @@ -157,7 +181,7 @@ MICROPY_EVENT_POLL_HOOK
current_graphics->set_pen(r, g, b);
current_graphics->rectangle({current_position.x, current_position.y, scale.x, scale.y});
}
current_position.x += scale.x;
current_position += step;
}
} else if (pDraw->iPixelType == PNG_PIXEL_INDEXED) {
for(int x = 0; x < pDraw->iWidth; x++) {
Expand Down Expand Up @@ -219,7 +243,7 @@ MICROPY_EVENT_POLL_HOOK
current_graphics->rectangle({current_position.x, current_position.y, scale.x, scale.y});
}
}
current_position.x += scale.x;
current_position += step;
}
}
}
Expand Down Expand Up @@ -292,14 +316,15 @@ mp_obj_t _PNG_openRAM(mp_obj_t self_in, mp_obj_t buffer) {

// decode
mp_obj_t _PNG_decode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_x, ARG_y, ARG_scale, ARG_mode, ARG_source };
enum { ARG_self, ARG_x, ARG_y, ARG_scale, ARG_mode, ARG_source, ARG_rotate };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_x, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_y, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_scale, MP_ARG_OBJ, {.u_obj = nullptr} },
{ MP_QSTR_mode, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_source, MP_ARG_OBJ, {.u_obj = nullptr} },
{ MP_QSTR_rotate, MP_ARG_INT, {.u_int = 0} },
};

mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
Expand All @@ -322,6 +347,18 @@ mp_obj_t _PNG_decode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
self->decode_target->source = {0, 0, self->width, self->height};
}

self->decode_target->rotation = args[ARG_rotate].u_int;
switch(self->decode_target->rotation) {
case 0:
case 90:
case 180:
case 270:
break;
default:
mp_raise_ValueError("decode(): rotation must be one of 0, 90, 180 or 270");
break;
}

// Scale is a single int, corresponds to both width/height
if (mp_obj_is_int(args[ARG_scale].u_obj)) {
self->decode_target->scale = {
Expand Down