|
| 1 | +/* |
| 2 | + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: CC0-1.0 |
| 5 | + */ |
| 6 | + |
| 7 | +// This demo UI is adapted from LVGL official example: https://docs.lvgl.io/master/widgets/extra/meter.html#simple-meter |
| 8 | +// This demo writes text onto canvas that can later be rotated to appear on a portrait or landscape oriened display. |
| 9 | + |
| 10 | +#define LV_FONT_MONTSERRAT_28 1 // 28 pt font. Must precede "lvgl.h". |
| 11 | + |
| 12 | +#include "lvgl.h" |
| 13 | +#include "esp_heap_caps.h" |
| 14 | + |
| 15 | +#define CANVAS_COLOR_FORMAT LV_IMG_CF_ALPHA_1BIT // desginates 1 bit per pixel display. |
| 16 | + |
| 17 | +extern void rotate_buffer(uint32_t rotate, int xlen, int ylen, uint8_t *in_buf, uint8_t *out_buf); |
| 18 | + |
| 19 | +static lv_style_t style_28; |
| 20 | +static uint8_t *canvas_buffer = NULL; |
| 21 | +/** |
| 22 | + * |
| 23 | + * @brief Use LVGL canvas to generate display and demonstrate rotation |
| 24 | + * @param width pixels of non-rotated display |
| 25 | + * @param height pixels of non-rotated display |
| 26 | + * @param rotate LVGL rotation value, [0, 1, 2, 3] for LV_DISP_ROT_<NONE|90|180|270]> |
| 27 | + * @note A canvas is created with no parent. Text is written to the canvas. |
| 28 | + * The canvas is then assigned the active screen as parent. |
| 29 | + * This apparently causes lvgl_flush_callback to be invoked. |
| 30 | + */ |
| 31 | +void lvgl_canvas_ui(int width, int height, uint32_t rotate) |
| 32 | + |
| 33 | +{ |
| 34 | + int disp_x, disp_y; |
| 35 | + if (rotate == LV_DISP_ROT_90 || rotate == LV_DISP_ROT_270) { |
| 36 | + disp_x = height; |
| 37 | + disp_y = width; |
| 38 | + } else { |
| 39 | + disp_y = height; |
| 40 | + disp_x = width; |
| 41 | + } |
| 42 | + if (!canvas_buffer) { |
| 43 | + // Needs 8 extra bytes for monochrome displays? |
| 44 | + canvas_buffer = heap_caps_malloc(disp_x * disp_y / 8 + 8, MALLOC_CAP_DMA); |
| 45 | + } |
| 46 | + memset(canvas_buffer, 0x00, disp_x * disp_y / 8 + 8); |
| 47 | + |
| 48 | + // Create a screen (a necessary, non-visible step in LVGL) |
| 49 | + lv_obj_t *scr = lv_obj_create(NULL); // canvas must not be attached to display until later |
| 50 | + // Create the canvas on the screen |
| 51 | + lv_obj_t *canvas = lv_canvas_create(scr); |
| 52 | + // Assign canvas_buffer to the canvas. The canvas is configured to be either portrait or landscape orientation. |
| 53 | + lv_canvas_set_buffer(canvas, canvas_buffer, disp_x, disp_y, CANVAS_COLOR_FORMAT); |
| 54 | + lv_canvas_fill_bg(canvas, lv_color_hex(0x000000), LV_OPA_0); // 0x000000 is a white background on eInk displays |
| 55 | + |
| 56 | +#if 0 |
| 57 | + // NOTE: palette is only used with indexed color formats, not monochrome. |
| 58 | + // Might be used for multi-color eInk displays? |
| 59 | + lv_canvas_set_palette(canvas, 0, LV_COLOR_WHITE); |
| 60 | + lv_canvas_set_palette(canvas, 1, LV_COLOR_BLACK); |
| 61 | +#endif |
| 62 | + |
| 63 | + lv_style_init(&style_28); |
| 64 | + lv_style_set_text_font(&style_28, &lv_font_montserrat_28); |
| 65 | + lv_draw_label_dsc_t label_dsc; // label properties descriptor |
| 66 | + lv_draw_label_dsc_init(&label_dsc); |
| 67 | + label_dsc.color = lv_color_make(0xFF, 0xFF, 0xFF); // Black color (or use lv_color_hex(0xFFFFFF)) |
| 68 | + label_dsc.font = &lv_font_montserrat_28; // Use a built-in font |
| 69 | + label_dsc.align = LV_TEXT_ALIGN_LEFT; |
| 70 | + |
| 71 | + int xoff = 10, yoff = 10; // offset for start of text string |
| 72 | + // canvas_draw_text automatically wraps text when right margin is reached |
| 73 | + lv_canvas_draw_text(canvas, xoff, yoff, disp_x - xoff, &label_dsc, "This text goes to end of line and then wraps. Isn't that cool?"); |
| 74 | + |
| 75 | + // Now we need a second canvas if we want to rotate |
| 76 | + lv_obj_t *rot_canvas = lv_canvas_create(lv_obj_create(NULL)); |
| 77 | + uint8_t *rot_buf = heap_caps_malloc(disp_x * disp_y / 8 + 8, MALLOC_CAP_DMA); |
| 78 | + memset(rot_buf, 0x00, disp_x * disp_y / 8 + 8); |
| 79 | + |
| 80 | + if (rotate == LV_DISP_ROT_90 || rotate == LV_DISP_ROT_270) { |
| 81 | +#if 1 // use my rotate function |
| 82 | + // ROT_270 is performed by ROT90 and then mirror both X & Y (in main.c) |
| 83 | + rotate_buffer(rotate, disp_x, disp_y, canvas_buffer, rot_buf); |
| 84 | + lv_canvas_set_buffer(canvas, rot_buf, width, height, LV_IMG_CF_ALPHA_1BIT); |
| 85 | + |
| 86 | +#else // use lvgl_canvas_transform |
| 87 | + // Display of 90 appears as 0 degrees, and clipped on right side. |
| 88 | + // Display of 270 appears as 180 degrees, and clipped on left side of display. |
| 89 | + // The text is first written to a landscape canvas. Thus it seems that no rotation occurs, |
| 90 | + // thereby resulting in the text clipping. |
| 91 | + int angle = 900 * rotate; |
| 92 | + |
| 93 | + lv_canvas_set_buffer(rot_canvas, rot_buf, height, width, LV_IMG_CF_ALPHA_1BIT); |
| 94 | + lv_img_dsc_t img_desc = *lv_canvas_get_img(canvas); |
| 95 | + lv_canvas_transform(rot_canvas, &img_desc, angle, 256, 0, 264, width / 2, height / 2, true); |
| 96 | + lv_canvas_copy_buf(canvas, rot_buf, 0, 0, lv_obj_get_height(rot_canvas), lv_obj_get_width(rot_canvas)); |
| 97 | +#endif |
| 98 | + } |
| 99 | + // Must clear LVGL display, else we get "double-exposure" image on screen |
| 100 | + lv_obj_clean(lv_scr_act()); |
| 101 | + // Attach the canvas to the active screen (the display device). |
| 102 | + // This presumably triggers LVGL flush callback (see flush_cb in main.c) |
| 103 | + lv_obj_set_parent(canvas, lv_scr_act()); |
| 104 | +} |
0 commit comments