Skip to content

Commit e1527c4

Browse files
authored
Merge pull request #811 from pimoroni/feature/rotation
Support for text and PNG 90-degree rotations
2 parents 3639b46 + 4ad6df5 commit e1527c4

File tree

4 files changed

+104
-27
lines changed

4 files changed

+104
-27
lines changed

libraries/bitmap_fonts/bitmap_fonts.cpp

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ namespace bitmap {
4242
return text_width;
4343
}
4444

45-
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) {
45+
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) {
4646
if(c < 32 || c > 127 + 64) { // + 64 char remappings defined in unicode_sorta.hpp
4747
return;
4848
}
@@ -89,7 +89,8 @@ namespace bitmap {
8989
uint8_t accent_offset = char_index < 65 ? offset_upper : offset_lower;
9090

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

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

102+
int32_t o_x = cx * scale;
103+
101104
// For fonts that are taller than 8 pixels (up to 16) they need two bytes
102105
if(two_bytes_per_column) {
103106
d++;
@@ -113,7 +116,28 @@ namespace bitmap {
113116
// Draw the 32 pixel column
114117
for(uint8_t cy = 0; cy < 32; cy++) {
115118
if((1U << cy) & data) {
116-
rectangle(x + (cx * scale), y_offset + (cy * scale), scale, scale);
119+
int32_t o_y = cy * scale;
120+
int32_t px = 0;
121+
int32_t py = 0;
122+
switch (rotation) {
123+
case 0:
124+
px = x + o_x;
125+
py = y - font_offset + o_y;
126+
break;
127+
case 90:
128+
px = x + font_offset - o_y;
129+
py = y + o_x;
130+
break;
131+
case 180:
132+
px = x - o_x;
133+
py = y + font_offset - o_y;
134+
break;
135+
case 270:
136+
px = x - font_offset + o_y;
137+
py = y - o_x;
138+
break;
139+
}
140+
rectangle(px, py, scale, scale);
117141
}
118142
}
119143

@@ -123,8 +147,9 @@ namespace bitmap {
123147
}
124148
}
125149

126-
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) {
127-
uint32_t co = 0, lo = 0; // character and line (if wrapping) offset
150+
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) {
151+
uint32_t char_offset = 0;
152+
uint32_t line_offset = 0; // line (if wrapping) offset
128153
unicode_sorta::codepage_t codepage = unicode_sorta::PAGE_195;
129154

130155
size_t i = 0;
@@ -153,37 +178,52 @@ namespace bitmap {
153178
continue;
154179
}
155180
word_width += measure_character(font, t[j], scale, codepage, fixed_width);
181+
word_width += letter_spacing * scale;
156182
codepage = unicode_sorta::PAGE_195;
157183
}
158184

159185
// if this word would exceed the wrap limit then
160186
// move to the next line
161-
if(co != 0 && co + word_width > (uint32_t)wrap) {
162-
co = 0;
163-
lo += (font->height + 1) * scale;
187+
if(char_offset != 0 && char_offset + word_width > (uint32_t)wrap) {
188+
char_offset = 0;
189+
line_offset += (font->height + 1) * scale;
164190
}
165191

166192
// draw word
167-
for(size_t j = i; j < next_break; j++) {
193+
for(size_t j = i; j < std::min(next_break + 1, t.length()); j++) {
168194
if (t[j] == unicode_sorta::PAGE_194_START) {
169195
codepage = unicode_sorta::PAGE_194;
170196
continue;
171197
} else if (t[j] == unicode_sorta::PAGE_195_START) {
172198
continue;
173199
}
174-
if (t[j] == '\n') {
175-
lo += (font->height + 1) * scale;
176-
co = 0;
200+
if (t[j] == '\n') { // Linebreak
201+
line_offset += (font->height + 1) * scale;
202+
char_offset = 0;
203+
} else if (t[j] == ' ') { // Space
204+
char_offset += font->widths[0] * scale;
177205
} else {
178-
character(font, rectangle, t[j], x + co, y + lo, scale, codepage);
179-
co += measure_character(font, t[j], scale, codepage, fixed_width);
180-
co += letter_spacing * scale;
206+
switch(rotation) {
207+
case 0:
208+
character(font, rectangle, t[j], x + char_offset, y + line_offset, scale, rotation, codepage);
209+
break;
210+
case 90:
211+
character(font, rectangle, t[j], x - line_offset, y + char_offset, scale, rotation, codepage);
212+
break;
213+
case 180:
214+
character(font, rectangle, t[j], x - char_offset, y - line_offset, scale, rotation, codepage);
215+
break;
216+
case 270:
217+
character(font, rectangle, t[j], x + line_offset, y - char_offset, scale, rotation, codepage);
218+
break;
219+
}
220+
char_offset += measure_character(font, t[j], scale, codepage, fixed_width);
221+
char_offset += letter_spacing * scale;
181222
}
182223
codepage = unicode_sorta::PAGE_195;
183224
}
184225

185-
// move character offset to end of word and add a space
186-
co += font->widths[0] * scale;
226+
// move character offset
187227
i = next_break += 1;
188228
}
189229
}

libraries/bitmap_fonts/bitmap_fonts.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ namespace bitmap {
2222
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);
2323
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);
2424

25-
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);
26-
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);
25+
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);
26+
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);
2727
}

libraries/pico_graphics/pico_graphics.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ namespace pimoroni {
132132
if (bitmap_font) {
133133
bitmap::character(bitmap_font, [this](int32_t x, int32_t y, int32_t w, int32_t h) {
134134
rectangle(Rect(x, y, w, h));
135-
}, c, p.x, p.y, std::max(1.0f, s));
135+
}, c, p.x, p.y, std::max(1.0f, s), int32_t(a) % 360);
136136
return;
137137
}
138138

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

micropython/modules/pngdec/pngdec.cpp

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ typedef struct _PNG_decode_target {
2424
Point position = {0, 0};
2525
Rect source = {0, 0, 0, 0};
2626
Point scale = {1, 1};
27+
int rotation = 0;
2728
} _PNG_decode_target;
2829

2930
typedef struct _PNG_obj_t {
@@ -125,14 +126,37 @@ MICROPY_EVENT_POLL_HOOK
125126
Point current_position = target->position;
126127
uint8_t current_mode = target->mode;
127128
Point scale = target->scale;
129+
int rotation = target->rotation;
130+
Point step = {0, 0};
131+
128132
// "pixel" is slow and clipped,
129133
// guaranteeing we wont draw png data out of the framebuffer..
130134
// Can we clip beforehand and make this faster?
131135

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

134-
current_position.y += pDraw->y * scale.y;
135-
current_position -= Point(0, target->source.y);
138+
switch (rotation) {
139+
case 0:
140+
current_position.y += (pDraw->y - target->source.y) * scale.y;
141+
step = {scale.x, 0};
142+
break;
143+
case 90:
144+
current_position.y += target->source.w * scale.y;
145+
current_position.x += target->source.h * scale.x;
146+
current_position.x += (pDraw->y - target->source.y) * -scale.x;
147+
step = {0, -scale.y};
148+
break;
149+
case 180:
150+
current_position.x += target->source.w * scale.x;
151+
current_position.y += target->source.h * scale.y;
152+
current_position.y += (pDraw->y - target->source.y) * -scale.y;
153+
step = {-scale.x, 0};
154+
break;
155+
case 270:
156+
current_position.x += (pDraw->y - target->source.y) * scale.x;
157+
step = {0, scale.y};
158+
break;
159+
}
136160

137161
//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);
138162
uint8_t *pixel = (uint8_t *)pDraw->pPixels;
@@ -144,7 +168,7 @@ MICROPY_EVENT_POLL_HOOK
144168
if(x < target->source.x || x >= target->source.x + target->source.w) continue;
145169
current_graphics->set_pen(r, g, b);
146170
current_graphics->rectangle({current_position.x, current_position.y, scale.x, scale.y});
147-
current_position.x += scale.x;
171+
current_position += step;
148172
}
149173
} else if (pDraw->iPixelType == PNG_PIXEL_TRUECOLOR_ALPHA) {
150174
for(int x = 0; x < pDraw->iWidth; x++) {
@@ -157,7 +181,7 @@ MICROPY_EVENT_POLL_HOOK
157181
current_graphics->set_pen(r, g, b);
158182
current_graphics->rectangle({current_position.x, current_position.y, scale.x, scale.y});
159183
}
160-
current_position.x += scale.x;
184+
current_position += step;
161185
}
162186
} else if (pDraw->iPixelType == PNG_PIXEL_INDEXED) {
163187
for(int x = 0; x < pDraw->iWidth; x++) {
@@ -219,7 +243,7 @@ MICROPY_EVENT_POLL_HOOK
219243
current_graphics->rectangle({current_position.x, current_position.y, scale.x, scale.y});
220244
}
221245
}
222-
current_position.x += scale.x;
246+
current_position += step;
223247
}
224248
}
225249
}
@@ -292,14 +316,15 @@ mp_obj_t _PNG_openRAM(mp_obj_t self_in, mp_obj_t buffer) {
292316

293317
// decode
294318
mp_obj_t _PNG_decode(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
295-
enum { ARG_self, ARG_x, ARG_y, ARG_scale, ARG_mode, ARG_source };
319+
enum { ARG_self, ARG_x, ARG_y, ARG_scale, ARG_mode, ARG_source, ARG_rotate };
296320
static const mp_arg_t allowed_args[] = {
297321
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
298322
{ MP_QSTR_x, MP_ARG_INT, {.u_int = 0} },
299323
{ MP_QSTR_y, MP_ARG_INT, {.u_int = 0} },
300324
{ MP_QSTR_scale, MP_ARG_OBJ, {.u_obj = nullptr} },
301325
{ MP_QSTR_mode, MP_ARG_INT, {.u_int = 0} },
302326
{ MP_QSTR_source, MP_ARG_OBJ, {.u_obj = nullptr} },
327+
{ MP_QSTR_rotate, MP_ARG_INT, {.u_int = 0} },
303328
};
304329

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

350+
self->decode_target->rotation = args[ARG_rotate].u_int;
351+
switch(self->decode_target->rotation) {
352+
case 0:
353+
case 90:
354+
case 180:
355+
case 270:
356+
break;
357+
default:
358+
mp_raise_ValueError("decode(): rotation must be one of 0, 90, 180 or 270");
359+
break;
360+
}
361+
325362
// Scale is a single int, corresponds to both width/height
326363
if (mp_obj_is_int(args[ARG_scale].u_obj)) {
327364
self->decode_target->scale = {

0 commit comments

Comments
 (0)