diff options
| author | jacqueline <me@jacqueline.id.au> | 2023-06-01 15:41:47 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2023-06-01 15:41:47 +1000 |
| commit | dd27c3530432ea0b09f01e604bf577f31d8ef841 (patch) | |
| tree | bbf86cf81a78f0ff0b07f31f1c390db473f26fd3 /lib/lvgl/src/extra | |
| parent | 6fd588e970470b15936187980829916d0dbe77bb (diff) | |
| download | tangara-fw-dd27c3530432ea0b09f01e604bf577f31d8ef841.tar.gz | |
convert lvgl from submodule to a plain old directory
Diffstat (limited to 'lib/lvgl/src/extra')
105 files changed, 36740 insertions, 0 deletions
diff --git a/lib/lvgl b/lib/lvgl deleted file mode 160000 -Subproject 0732400e7b564dd0e7dc4a924619d8e19c5b23a diff --git a/lib/lvgl/src/extra/README.md b/lib/lvgl/src/extra/README.md new file mode 100644 index 00000000..80bb49d4 --- /dev/null +++ b/lib/lvgl/src/extra/README.md @@ -0,0 +1,31 @@ +# Extra components + +This directory contains extra (optional) components to lvgl. +It's a good place for contributions as there are less strict expectations about the completeness and flexibility of the components here. + +In other words, if you have created a complex widget from other widgets, or modified an existing widget with special events, styles or animations, or have a new feature that could work as a plugin to lvgl feel free to the share it here. + +## How to contribute +- Create a [Pull request](https://docs.lvgl.io/8.0/CONTRIBUTING.html#pull-request) with your new content +- Please and follow the [Coding style](https://github.com/lvgl/lvgl/blob/master/docs/CODING_STYLE.md) of LVGL +- Add setter/getter functions in pair +- Update [lv_conf_template.h](https://github.com/lvgl/lvgl/blob/master/lv_conf_template.h) +- Add description in the [docs](https://github.com/lvgl/lvgl/tree/master/docs) +- Add [examples](https://github.com/lvgl/lvgl/tree/master/examples) +- Update the [changelog](https://github.com/lvgl/lvgl/tree/master/docs/CHANGELOG.md) +- Add yourself to the [Contributors](#contributors) section below. + +## Ideas +Here some ideas as inspiration feel free to contribute with ideas too. +- New [Calendar headers](https://github.com/lvgl/lvgl/tree/master/src/extra/widgets/calendar) +- Color picker with RGB and or HSV bars +- Ruler, horizontal or vertical with major and minor ticks and labels +- New [List items types](https://github.com/lvgl/lvgl/tree/master/src/extra/widgets/list) +- [Preloaders](https://www.google.com/search?q=preloader&sxsrf=ALeKk01ddA4YB0WEgLLN1bZNSm8YER7pkg:1623080551559&source=lnms&tbm=isch&sa=X&ved=2ahUKEwiwoN6d7oXxAhVuw4sKHVedBB4Q_AUoAXoECAEQAw&biw=952&bih=940) +- Drop-down list with a container to which content can be added +- 9 patch button: Similar to [lv_imgbtn](https://docs.lvgl.io/8.0/widgets/extra/imgbtn.html) but 9 images for 4 corner, 4 sides and the center + +## Contributors +- lv_animimg: @ZhaoQiang-b45475 +- lv_span: @guoweilkd +- lv_menu: @HX2003
\ No newline at end of file diff --git a/lib/lvgl/src/extra/layouts/flex/lv_flex.c b/lib/lvgl/src/extra/layouts/flex/lv_flex.c new file mode 100644 index 00000000..405a56b7 --- /dev/null +++ b/lib/lvgl/src/extra/layouts/flex/lv_flex.c @@ -0,0 +1,596 @@ +/** + * @file lv_flex.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../lv_layouts.h" + +#if LV_USE_FLEX + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_flex_align_t main_place; + lv_flex_align_t cross_place; + lv_flex_align_t track_place; + uint8_t row : 1; + uint8_t wrap : 1; + uint8_t rev : 1; +} flex_t; + +typedef struct { + lv_obj_t * item; + lv_coord_t min_size; + lv_coord_t max_size; + lv_coord_t final_size; + uint32_t grow_value; + uint32_t clamped : 1; +} grow_dsc_t; + +typedef struct { + lv_coord_t track_cross_size; + lv_coord_t track_main_size; /*For all items*/ + lv_coord_t track_fix_main_size; /*For non grow items*/ + uint32_t item_cnt; + grow_dsc_t * grow_dsc; + uint32_t grow_item_cnt; + uint32_t grow_dsc_calc : 1; +} track_t; + + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void flex_update(lv_obj_t * cont, void * user_data); +static int32_t find_track_end(lv_obj_t * cont, flex_t * f, int32_t item_start_id, lv_coord_t max_main_size, + lv_coord_t item_gap, track_t * t); +static void children_repos(lv_obj_t * cont, flex_t * f, int32_t item_first_id, int32_t item_last_id, lv_coord_t abs_x, + lv_coord_t abs_y, lv_coord_t max_main_size, lv_coord_t item_gap, track_t * t); +static void place_content(lv_flex_align_t place, lv_coord_t max_size, lv_coord_t content_size, lv_coord_t item_cnt, + lv_coord_t * start_pos, lv_coord_t * gap); +static lv_obj_t * get_next_item(lv_obj_t * cont, bool rev, int32_t * item_id); + +/********************** + * GLOBAL VARIABLES + **********************/ +uint16_t LV_LAYOUT_FLEX; +lv_style_prop_t LV_STYLE_FLEX_FLOW; +lv_style_prop_t LV_STYLE_FLEX_MAIN_PLACE; +lv_style_prop_t LV_STYLE_FLEX_CROSS_PLACE; +lv_style_prop_t LV_STYLE_FLEX_TRACK_PLACE; +lv_style_prop_t LV_STYLE_FLEX_GROW; + + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/*===================== + * Setter functions + *====================*/ + +void lv_flex_init(void) +{ + LV_LAYOUT_FLEX = lv_layout_register(flex_update, NULL); + + LV_STYLE_FLEX_FLOW = lv_style_register_prop(LV_STYLE_PROP_FLAG_NONE); + LV_STYLE_FLEX_MAIN_PLACE = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + LV_STYLE_FLEX_CROSS_PLACE = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + LV_STYLE_FLEX_TRACK_PLACE = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); +} + +void lv_obj_set_flex_flow(lv_obj_t * obj, lv_flex_flow_t flow) +{ + lv_obj_set_style_flex_flow(obj, flow, 0); + lv_obj_set_style_layout(obj, LV_LAYOUT_FLEX, 0); +} + +void lv_obj_set_flex_align(lv_obj_t * obj, lv_flex_align_t main_place, lv_flex_align_t cross_place, + lv_flex_align_t track_place) +{ + lv_obj_set_style_flex_main_place(obj, main_place, 0); + lv_obj_set_style_flex_cross_place(obj, cross_place, 0); + lv_obj_set_style_flex_track_place(obj, track_place, 0); + lv_obj_set_style_layout(obj, LV_LAYOUT_FLEX, 0); +} + +void lv_obj_set_flex_grow(lv_obj_t * obj, uint8_t grow) +{ + lv_obj_set_style_flex_grow(obj, grow, 0); + lv_obj_mark_layout_as_dirty(lv_obj_get_parent(obj)); +} + + +void lv_style_set_flex_flow(lv_style_t * style, lv_flex_flow_t value) +{ + lv_style_value_t v = { + .num = (int32_t)value + }; + lv_style_set_prop(style, LV_STYLE_FLEX_FLOW, v); +} + +void lv_style_set_flex_main_place(lv_style_t * style, lv_flex_align_t value) +{ + lv_style_value_t v = { + .num = (int32_t)value + }; + lv_style_set_prop(style, LV_STYLE_FLEX_MAIN_PLACE, v); +} + +void lv_style_set_flex_cross_place(lv_style_t * style, lv_flex_align_t value) +{ + lv_style_value_t v = { + .num = (int32_t)value + }; + lv_style_set_prop(style, LV_STYLE_FLEX_CROSS_PLACE, v); +} + +void lv_style_set_flex_track_place(lv_style_t * style, lv_flex_align_t value) +{ + lv_style_value_t v = { + .num = (int32_t)value + }; + lv_style_set_prop(style, LV_STYLE_FLEX_TRACK_PLACE, v); +} + +void lv_style_set_flex_grow(lv_style_t * style, uint8_t value) +{ + lv_style_value_t v = { + .num = (int32_t)value + }; + lv_style_set_prop(style, LV_STYLE_FLEX_GROW, v); +} + + +void lv_obj_set_style_flex_flow(lv_obj_t * obj, lv_flex_flow_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = (int32_t) value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_FLEX_FLOW, v, selector); +} + +void lv_obj_set_style_flex_main_place(lv_obj_t * obj, lv_flex_align_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = (int32_t) value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_FLEX_MAIN_PLACE, v, selector); +} + +void lv_obj_set_style_flex_cross_place(lv_obj_t * obj, lv_flex_align_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = (int32_t) value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_FLEX_CROSS_PLACE, v, selector); +} + +void lv_obj_set_style_flex_track_place(lv_obj_t * obj, lv_flex_align_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = (int32_t) value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_FLEX_TRACK_PLACE, v, selector); +} + +void lv_obj_set_style_flex_grow(lv_obj_t * obj, uint8_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = (int32_t) value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_FLEX_GROW, v, selector); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void flex_update(lv_obj_t * cont, void * user_data) +{ + LV_LOG_INFO("update %p container", (void *)cont); + LV_UNUSED(user_data); + + flex_t f; + lv_flex_flow_t flow = lv_obj_get_style_flex_flow(cont, LV_PART_MAIN); + f.row = flow & _LV_FLEX_COLUMN ? 0 : 1; + f.wrap = flow & _LV_FLEX_WRAP ? 1 : 0; + f.rev = flow & _LV_FLEX_REVERSE ? 1 : 0; + f.main_place = lv_obj_get_style_flex_main_place(cont, LV_PART_MAIN); + f.cross_place = lv_obj_get_style_flex_cross_place(cont, LV_PART_MAIN); + f.track_place = lv_obj_get_style_flex_track_place(cont, LV_PART_MAIN); + + bool rtl = lv_obj_get_style_base_dir(cont, LV_PART_MAIN) == LV_BASE_DIR_RTL ? true : false; + lv_coord_t track_gap = !f.row ? lv_obj_get_style_pad_column(cont, LV_PART_MAIN) : lv_obj_get_style_pad_row(cont, + LV_PART_MAIN); + lv_coord_t item_gap = f.row ? lv_obj_get_style_pad_column(cont, LV_PART_MAIN) : lv_obj_get_style_pad_row(cont, + LV_PART_MAIN); + lv_coord_t max_main_size = (f.row ? lv_obj_get_content_width(cont) : lv_obj_get_content_height(cont)); + lv_coord_t border_width = lv_obj_get_style_border_width(cont, LV_PART_MAIN); + lv_coord_t abs_y = cont->coords.y1 + lv_obj_get_style_pad_top(cont, + LV_PART_MAIN) + border_width - lv_obj_get_scroll_y(cont); + lv_coord_t abs_x = cont->coords.x1 + lv_obj_get_style_pad_left(cont, + LV_PART_MAIN) + border_width - lv_obj_get_scroll_x(cont); + + lv_flex_align_t track_cross_place = f.track_place; + lv_coord_t * cross_pos = (f.row ? &abs_y : &abs_x); + + lv_coord_t w_set = lv_obj_get_style_width(cont, LV_PART_MAIN); + lv_coord_t h_set = lv_obj_get_style_height(cont, LV_PART_MAIN); + + /*Content sized objects should squeezed the gap between the children, therefore any alignment will look like `START`*/ + if((f.row && h_set == LV_SIZE_CONTENT && cont->h_layout == 0) || + (!f.row && w_set == LV_SIZE_CONTENT && cont->w_layout == 0)) { + track_cross_place = LV_FLEX_ALIGN_START; + } + + if(rtl && !f.row) { + if(track_cross_place == LV_FLEX_ALIGN_START) track_cross_place = LV_FLEX_ALIGN_END; + else if(track_cross_place == LV_FLEX_ALIGN_END) track_cross_place = LV_FLEX_ALIGN_START; + } + + lv_coord_t total_track_cross_size = 0; + lv_coord_t gap = 0; + uint32_t track_cnt = 0; + int32_t track_first_item; + int32_t next_track_first_item; + + if(track_cross_place != LV_FLEX_ALIGN_START) { + track_first_item = f.rev ? cont->spec_attr->child_cnt - 1 : 0; + track_t t; + while(track_first_item < (int32_t)cont->spec_attr->child_cnt && track_first_item >= 0) { + /*Search the first item of the next row*/ + t.grow_dsc_calc = 0; + next_track_first_item = find_track_end(cont, &f, track_first_item, max_main_size, item_gap, &t); + total_track_cross_size += t.track_cross_size + track_gap; + track_cnt++; + track_first_item = next_track_first_item; + } + + if(track_cnt) total_track_cross_size -= track_gap; /*No gap after the last track*/ + + /*Place the tracks to get the start position*/ + lv_coord_t max_cross_size = (f.row ? lv_obj_get_content_height(cont) : lv_obj_get_content_width(cont)); + place_content(track_cross_place, max_cross_size, total_track_cross_size, track_cnt, cross_pos, &gap); + } + + track_first_item = f.rev ? cont->spec_attr->child_cnt - 1 : 0; + + if(rtl && !f.row) { + *cross_pos += total_track_cross_size; + } + + while(track_first_item < (int32_t)cont->spec_attr->child_cnt && track_first_item >= 0) { + track_t t; + t.grow_dsc_calc = 1; + /*Search the first item of the next row*/ + next_track_first_item = find_track_end(cont, &f, track_first_item, max_main_size, item_gap, &t); + + if(rtl && !f.row) { + *cross_pos -= t.track_cross_size; + } + children_repos(cont, &f, track_first_item, next_track_first_item, abs_x, abs_y, max_main_size, item_gap, &t); + track_first_item = next_track_first_item; + lv_mem_buf_release(t.grow_dsc); + t.grow_dsc = NULL; + if(rtl && !f.row) { + *cross_pos -= gap + track_gap; + } + else { + *cross_pos += t.track_cross_size + gap + track_gap; + } + } + LV_ASSERT_MEM_INTEGRITY(); + + if(w_set == LV_SIZE_CONTENT || h_set == LV_SIZE_CONTENT) { + lv_obj_refr_size(cont); + } + + lv_event_send(cont, LV_EVENT_LAYOUT_CHANGED, NULL); + + LV_TRACE_LAYOUT("finished"); +} + +/** + * Find the last item of a track + */ +static int32_t find_track_end(lv_obj_t * cont, flex_t * f, int32_t item_start_id, lv_coord_t max_main_size, + lv_coord_t item_gap, track_t * t) +{ + lv_coord_t w_set = lv_obj_get_style_width(cont, LV_PART_MAIN); + lv_coord_t h_set = lv_obj_get_style_height(cont, LV_PART_MAIN); + + /*Can't wrap if the size if auto (i.e. the size depends on the children)*/ + if(f->wrap && ((f->row && w_set == LV_SIZE_CONTENT) || (!f->row && h_set == LV_SIZE_CONTENT))) { + f->wrap = false; + } + lv_coord_t(*get_main_size)(const lv_obj_t *) = (f->row ? lv_obj_get_width : lv_obj_get_height); + lv_coord_t(*get_cross_size)(const lv_obj_t *) = (!f->row ? lv_obj_get_width : lv_obj_get_height); + + t->track_main_size = 0; + t->track_fix_main_size = 0; + t->grow_item_cnt = 0; + t->track_cross_size = 0; + t->item_cnt = 0; + t->grow_dsc = NULL; + + int32_t item_id = item_start_id; + + lv_obj_t * item = lv_obj_get_child(cont, item_id); + while(item) { + if(item_id != item_start_id && lv_obj_has_flag(item, LV_OBJ_FLAG_FLEX_IN_NEW_TRACK)) break; + + if(!lv_obj_has_flag_any(item, LV_OBJ_FLAG_IGNORE_LAYOUT | LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) { + uint8_t grow_value = lv_obj_get_style_flex_grow(item, LV_PART_MAIN); + if(grow_value) { + t->grow_item_cnt++; + t->track_fix_main_size += item_gap; + if(t->grow_dsc_calc) { + grow_dsc_t * new_dsc = lv_mem_buf_get(sizeof(grow_dsc_t) * (t->grow_item_cnt)); + LV_ASSERT_MALLOC(new_dsc); + if(new_dsc == NULL) return item_id; + + if(t->grow_dsc) { + lv_memcpy(new_dsc, t->grow_dsc, sizeof(grow_dsc_t) * (t->grow_item_cnt - 1)); + lv_mem_buf_release(t->grow_dsc); + } + new_dsc[t->grow_item_cnt - 1].item = item; + new_dsc[t->grow_item_cnt - 1].min_size = f->row ? lv_obj_get_style_min_width(item, + LV_PART_MAIN) : lv_obj_get_style_min_height(item, LV_PART_MAIN); + new_dsc[t->grow_item_cnt - 1].max_size = f->row ? lv_obj_get_style_max_width(item, + LV_PART_MAIN) : lv_obj_get_style_max_height(item, LV_PART_MAIN); + new_dsc[t->grow_item_cnt - 1].grow_value = grow_value; + new_dsc[t->grow_item_cnt - 1].clamped = 0; + t->grow_dsc = new_dsc; + } + } + else { + lv_coord_t item_size = get_main_size(item); + if(f->wrap && t->track_fix_main_size + item_size > max_main_size) break; + t->track_fix_main_size += item_size + item_gap; + } + + + t->track_cross_size = LV_MAX(get_cross_size(item), t->track_cross_size); + t->item_cnt++; + } + + item_id += f->rev ? -1 : +1; + if(item_id < 0) break; + item = lv_obj_get_child(cont, item_id); + } + + if(t->track_fix_main_size > 0) t->track_fix_main_size -= item_gap; /*There is no gap after the last item*/ + + /*If there is at least one "grow item" the track takes the full space*/ + t->track_main_size = t->grow_item_cnt ? max_main_size : t->track_fix_main_size; + + /*Have at least one item in a row*/ + if(item && item_id == item_start_id) { + item = cont->spec_attr->children[item_id]; + get_next_item(cont, f->rev, &item_id); + if(item) { + t->track_cross_size = get_cross_size(item); + t->track_main_size = get_main_size(item); + t->item_cnt = 1; + } + } + + return item_id; +} + +/** + * Position the children in the same track + */ +static void children_repos(lv_obj_t * cont, flex_t * f, int32_t item_first_id, int32_t item_last_id, lv_coord_t abs_x, + lv_coord_t abs_y, lv_coord_t max_main_size, lv_coord_t item_gap, track_t * t) +{ + void (*area_set_main_size)(lv_area_t *, lv_coord_t) = (f->row ? lv_area_set_width : lv_area_set_height); + lv_coord_t (*area_get_main_size)(const lv_area_t *) = (f->row ? lv_area_get_width : lv_area_get_height); + lv_coord_t (*area_get_cross_size)(const lv_area_t *) = (!f->row ? lv_area_get_width : lv_area_get_height); + + /*Calculate the size of grow items first*/ + uint32_t i; + bool grow_reiterate = true; + while(grow_reiterate) { + grow_reiterate = false; + lv_coord_t grow_value_sum = 0; + lv_coord_t grow_max_size = t->track_main_size - t->track_fix_main_size; + for(i = 0; i < t->grow_item_cnt; i++) { + if(t->grow_dsc[i].clamped == 0) { + grow_value_sum += t->grow_dsc[i].grow_value; + } + else { + grow_max_size -= t->grow_dsc[i].final_size; + } + } + lv_coord_t grow_unit; + + for(i = 0; i < t->grow_item_cnt; i++) { + if(t->grow_dsc[i].clamped == 0) { + LV_ASSERT(grow_value_sum != 0); + grow_unit = grow_max_size / grow_value_sum; + lv_coord_t size = grow_unit * t->grow_dsc[i].grow_value; + lv_coord_t size_clamp = LV_CLAMP(t->grow_dsc[i].min_size, size, t->grow_dsc[i].max_size); + + if(size_clamp != size) { + t->grow_dsc[i].clamped = 1; + grow_reiterate = true; + } + t->grow_dsc[i].final_size = size_clamp; + grow_value_sum -= t->grow_dsc[i].grow_value; + grow_max_size -= t->grow_dsc[i].final_size; + } + } + } + + + bool rtl = lv_obj_get_style_base_dir(cont, LV_PART_MAIN) == LV_BASE_DIR_RTL ? true : false; + + lv_coord_t main_pos = 0; + + lv_coord_t place_gap = 0; + place_content(f->main_place, max_main_size, t->track_main_size, t->item_cnt, &main_pos, &place_gap); + if(f->row && rtl) main_pos += lv_obj_get_content_width(cont); + + lv_obj_t * item = lv_obj_get_child(cont, item_first_id); + /*Reposition the children*/ + while(item && item_first_id != item_last_id) { + if(lv_obj_has_flag_any(item, LV_OBJ_FLAG_IGNORE_LAYOUT | LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) { + item = get_next_item(cont, f->rev, &item_first_id); + continue; + } + lv_coord_t grow_size = lv_obj_get_style_flex_grow(item, LV_PART_MAIN); + if(grow_size) { + lv_coord_t s = 0; + for(i = 0; i < t->grow_item_cnt; i++) { + if(t->grow_dsc[i].item == item) { + s = t->grow_dsc[i].final_size; + break; + } + } + + if(f->row) item->w_layout = 1; + else item->h_layout = 1; + + if(s != area_get_main_size(&item->coords)) { + lv_obj_invalidate(item); + + lv_area_t old_coords; + lv_area_copy(&old_coords, &item->coords); + area_set_main_size(&item->coords, s); + lv_event_send(item, LV_EVENT_SIZE_CHANGED, &old_coords); + lv_event_send(lv_obj_get_parent(item), LV_EVENT_CHILD_CHANGED, item); + lv_obj_invalidate(item); + } + } + else { + item->w_layout = 0; + item->h_layout = 0; + } + + lv_coord_t cross_pos = 0; + switch(f->cross_place) { + case LV_FLEX_ALIGN_CENTER: + /*Round up the cross size to avoid rounding error when dividing by 2 + *The issue comes up e,g, with column direction with center cross direction if an element's width changes*/ + cross_pos = (((t->track_cross_size + 1) & (~1)) - area_get_cross_size(&item->coords)) / 2; + break; + case LV_FLEX_ALIGN_END: + cross_pos = t->track_cross_size - area_get_cross_size(&item->coords); + break; + default: + break; + } + + if(f->row && rtl) main_pos -= area_get_main_size(&item->coords); + + + /*Handle percentage value of translate*/ + lv_coord_t tr_x = lv_obj_get_style_translate_x(item, LV_PART_MAIN); + lv_coord_t tr_y = lv_obj_get_style_translate_y(item, LV_PART_MAIN); + lv_coord_t w = lv_obj_get_width(item); + lv_coord_t h = lv_obj_get_height(item); + if(LV_COORD_IS_PCT(tr_x)) tr_x = (w * LV_COORD_GET_PCT(tr_x)) / 100; + if(LV_COORD_IS_PCT(tr_y)) tr_y = (h * LV_COORD_GET_PCT(tr_y)) / 100; + + lv_coord_t diff_x = abs_x - item->coords.x1 + tr_x; + lv_coord_t diff_y = abs_y - item->coords.y1 + tr_y; + diff_x += f->row ? main_pos : cross_pos; + diff_y += f->row ? cross_pos : main_pos; + + if(diff_x || diff_y) { + lv_obj_invalidate(item); + item->coords.x1 += diff_x; + item->coords.x2 += diff_x; + item->coords.y1 += diff_y; + item->coords.y2 += diff_y; + lv_obj_invalidate(item); + lv_obj_move_children_by(item, diff_x, diff_y, false); + } + + if(!(f->row && rtl)) main_pos += area_get_main_size(&item->coords) + item_gap + place_gap; + else main_pos -= item_gap + place_gap; + + item = get_next_item(cont, f->rev, &item_first_id); + } +} + +/** + * Tell a start coordinate and gap for a placement type. + */ +static void place_content(lv_flex_align_t place, lv_coord_t max_size, lv_coord_t content_size, lv_coord_t item_cnt, + lv_coord_t * start_pos, lv_coord_t * gap) +{ + if(item_cnt <= 1) { + switch(place) { + case LV_FLEX_ALIGN_SPACE_BETWEEN: + case LV_FLEX_ALIGN_SPACE_AROUND: + case LV_FLEX_ALIGN_SPACE_EVENLY: + place = LV_FLEX_ALIGN_CENTER; + break; + default: + break; + } + } + + switch(place) { + case LV_FLEX_ALIGN_CENTER: + *gap = 0; + *start_pos += (max_size - content_size) / 2; + break; + case LV_FLEX_ALIGN_END: + *gap = 0; + *start_pos += max_size - content_size; + break; + case LV_FLEX_ALIGN_SPACE_BETWEEN: + *gap = (lv_coord_t)(max_size - content_size) / (lv_coord_t)(item_cnt - 1); + break; + case LV_FLEX_ALIGN_SPACE_AROUND: + *gap += (lv_coord_t)(max_size - content_size) / (lv_coord_t)(item_cnt); + *start_pos += *gap / 2; + break; + case LV_FLEX_ALIGN_SPACE_EVENLY: + *gap = (lv_coord_t)(max_size - content_size) / (lv_coord_t)(item_cnt + 1); + *start_pos += *gap; + break; + default: + *gap = 0; + } +} + +static lv_obj_t * get_next_item(lv_obj_t * cont, bool rev, int32_t * item_id) +{ + if(rev) { + (*item_id)--; + if(*item_id >= 0) return cont->spec_attr->children[*item_id]; + else return NULL; + } + else { + (*item_id)++; + if((*item_id) < (int32_t)cont->spec_attr->child_cnt) return cont->spec_attr->children[*item_id]; + else return NULL; + } +} + +#endif /*LV_USE_FLEX*/ diff --git a/lib/lvgl/src/extra/layouts/flex/lv_flex.h b/lib/lvgl/src/extra/layouts/flex/lv_flex.h new file mode 100644 index 00000000..58c3221e --- /dev/null +++ b/lib/lvgl/src/extra/layouts/flex/lv_flex.h @@ -0,0 +1,152 @@ +/** + * @file lv_flex.h + * + */ + +#ifndef LV_FLEX_H +#define LV_FLEX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" +#if LV_USE_FLEX + +/********************* + * DEFINES + *********************/ + +#define LV_OBJ_FLAG_FLEX_IN_NEW_TRACK LV_OBJ_FLAG_LAYOUT_1 +LV_EXPORT_CONST_INT(LV_OBJ_FLAG_FLEX_IN_NEW_TRACK); + +#define _LV_FLEX_COLUMN (1 << 0) +#define _LV_FLEX_WRAP (1 << 2) +#define _LV_FLEX_REVERSE (1 << 3) + +/********************** + * TYPEDEFS + **********************/ + +/*Can't include lv_obj.h because it includes this header file*/ +struct _lv_obj_t; + +typedef enum { + LV_FLEX_ALIGN_START, + LV_FLEX_ALIGN_END, + LV_FLEX_ALIGN_CENTER, + LV_FLEX_ALIGN_SPACE_EVENLY, + LV_FLEX_ALIGN_SPACE_AROUND, + LV_FLEX_ALIGN_SPACE_BETWEEN, +} lv_flex_align_t; + +typedef enum { + LV_FLEX_FLOW_ROW = 0x00, + LV_FLEX_FLOW_COLUMN = _LV_FLEX_COLUMN, + LV_FLEX_FLOW_ROW_WRAP = LV_FLEX_FLOW_ROW | _LV_FLEX_WRAP, + LV_FLEX_FLOW_ROW_REVERSE = LV_FLEX_FLOW_ROW | _LV_FLEX_REVERSE, + LV_FLEX_FLOW_ROW_WRAP_REVERSE = LV_FLEX_FLOW_ROW | _LV_FLEX_WRAP | _LV_FLEX_REVERSE, + LV_FLEX_FLOW_COLUMN_WRAP = LV_FLEX_FLOW_COLUMN | _LV_FLEX_WRAP, + LV_FLEX_FLOW_COLUMN_REVERSE = LV_FLEX_FLOW_COLUMN | _LV_FLEX_REVERSE, + LV_FLEX_FLOW_COLUMN_WRAP_REVERSE = LV_FLEX_FLOW_COLUMN | _LV_FLEX_WRAP | _LV_FLEX_REVERSE, +} lv_flex_flow_t; + +/********************** + * GLOBAL VARIABLES + **********************/ +extern uint16_t LV_LAYOUT_FLEX; +extern lv_style_prop_t LV_STYLE_FLEX_FLOW; +extern lv_style_prop_t LV_STYLE_FLEX_MAIN_PLACE; +extern lv_style_prop_t LV_STYLE_FLEX_CROSS_PLACE; +extern lv_style_prop_t LV_STYLE_FLEX_TRACK_PLACE; +extern lv_style_prop_t LV_STYLE_FLEX_GROW; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize a flex layout the default values + * @param flex pointer to a flex layout descriptor + */ +void lv_flex_init(void); + +/** + * Set hot the item should flow + * @param flex pointer to a flex layout descriptor + * @param flow an element of `lv_flex_flow_t`. + */ +void lv_obj_set_flex_flow(lv_obj_t * obj, lv_flex_flow_t flow); + +/** + * Set how to place (where to align) the items and tracks + * @param flex pointer: to a flex layout descriptor + * @param main_place where to place the items on main axis (in their track). Any value of `lv_flex_align_t`. + * @param cross_place where to place the item in their track on the cross axis. `LV_FLEX_ALIGN_START/END/CENTER` + * @param track_place where to place the tracks in the cross direction. Any value of `lv_flex_align_t`. + */ +void lv_obj_set_flex_align(lv_obj_t * obj, lv_flex_align_t main_place, lv_flex_align_t cross_place, + lv_flex_align_t track_cross_place); + +/** + * Sets the width or height (on main axis) to grow the object in order fill the free space + * @param obj pointer to an object. The parent must have flex layout else nothing will happen. + * @param grow a value to set how much free space to take proportionally to other growing items. + */ +void lv_obj_set_flex_grow(lv_obj_t * obj, uint8_t grow); + +void lv_style_set_flex_flow(lv_style_t * style, lv_flex_flow_t value); +void lv_style_set_flex_main_place(lv_style_t * style, lv_flex_align_t value); +void lv_style_set_flex_cross_place(lv_style_t * style, lv_flex_align_t value); +void lv_style_set_flex_track_place(lv_style_t * style, lv_flex_align_t value); +void lv_style_set_flex_grow(lv_style_t * style, uint8_t value); +void lv_obj_set_style_flex_flow(lv_obj_t * obj, lv_flex_flow_t value, lv_style_selector_t selector); +void lv_obj_set_style_flex_main_place(lv_obj_t * obj, lv_flex_align_t value, lv_style_selector_t selector); +void lv_obj_set_style_flex_cross_place(lv_obj_t * obj, lv_flex_align_t value, lv_style_selector_t selector); +void lv_obj_set_style_flex_track_place(lv_obj_t * obj, lv_flex_align_t value, lv_style_selector_t selector); +void lv_obj_set_style_flex_grow(lv_obj_t * obj, uint8_t value, lv_style_selector_t selector); + +static inline lv_flex_flow_t lv_obj_get_style_flex_flow(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_FLEX_FLOW); + return (lv_flex_flow_t)v.num; +} + +static inline lv_flex_align_t lv_obj_get_style_flex_main_place(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_FLEX_MAIN_PLACE); + return (lv_flex_align_t)v.num; +} + +static inline lv_flex_align_t lv_obj_get_style_flex_cross_place(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_FLEX_CROSS_PLACE); + return (lv_flex_align_t)v.num; +} + +static inline lv_flex_align_t lv_obj_get_style_flex_track_place(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_FLEX_TRACK_PLACE); + return (lv_flex_align_t)v.num; +} + +static inline uint8_t lv_obj_get_style_flex_grow(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_FLEX_GROW); + return (uint8_t)v.num; +} + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_FLEX*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_FLEX_H*/ diff --git a/lib/lvgl/src/extra/layouts/grid/lv_grid.c b/lib/lvgl/src/extra/layouts/grid/lv_grid.c new file mode 100644 index 00000000..74f8e95f --- /dev/null +++ b/lib/lvgl/src/extra/layouts/grid/lv_grid.c @@ -0,0 +1,782 @@ +/** + * @file lv_grid.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../lv_layouts.h" + +#if LV_USE_GRID + +/********************* + * DEFINES + *********************/ +/** + * Some helper defines + */ +#define IS_FR(x) (x >= LV_COORD_MAX - 100) +#define IS_CONTENT(x) (x == LV_COORD_MAX - 101) +#define GET_FR(x) (x - (LV_COORD_MAX - 100)) + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + uint32_t col; + uint32_t row; + lv_point_t grid_abs; +} item_repos_hint_t; + +typedef struct { + lv_coord_t * x; + lv_coord_t * y; + lv_coord_t * w; + lv_coord_t * h; + uint32_t col_num; + uint32_t row_num; + lv_coord_t grid_w; + lv_coord_t grid_h; +} _lv_grid_calc_t; + + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void grid_update(lv_obj_t * cont, void * user_data); +static void calc(lv_obj_t * obj, _lv_grid_calc_t * calc); +static void calc_free(_lv_grid_calc_t * calc); +static void calc_cols(lv_obj_t * cont, _lv_grid_calc_t * c); +static void calc_rows(lv_obj_t * cont, _lv_grid_calc_t * c); +static void item_repos(lv_obj_t * item, _lv_grid_calc_t * c, item_repos_hint_t * hint); +static lv_coord_t grid_align(lv_coord_t cont_size, bool auto_size, uint8_t align, lv_coord_t gap, uint32_t track_num, + lv_coord_t * size_array, lv_coord_t * pos_array, bool reverse); +static uint32_t count_tracks(const lv_coord_t * templ); + +static inline const lv_coord_t * get_col_dsc(lv_obj_t * obj) +{ + return lv_obj_get_style_grid_column_dsc_array(obj, 0); +} +static inline const lv_coord_t * get_row_dsc(lv_obj_t * obj) +{ + return lv_obj_get_style_grid_row_dsc_array(obj, 0); +} +static inline uint8_t get_col_pos(lv_obj_t * obj) +{ + return lv_obj_get_style_grid_cell_column_pos(obj, 0); +} +static inline uint8_t get_row_pos(lv_obj_t * obj) +{ + return lv_obj_get_style_grid_cell_row_pos(obj, 0); +} +static inline uint8_t get_col_span(lv_obj_t * obj) +{ + return lv_obj_get_style_grid_cell_column_span(obj, 0); +} +static inline uint8_t get_row_span(lv_obj_t * obj) +{ + return lv_obj_get_style_grid_cell_row_span(obj, 0); +} +static inline uint8_t get_cell_col_align(lv_obj_t * obj) +{ + return lv_obj_get_style_grid_cell_x_align(obj, 0); +} +static inline uint8_t get_cell_row_align(lv_obj_t * obj) +{ + return lv_obj_get_style_grid_cell_y_align(obj, 0); +} +static inline uint8_t get_grid_col_align(lv_obj_t * obj) +{ + return lv_obj_get_style_grid_column_align(obj, 0); +} +static inline uint8_t get_grid_row_align(lv_obj_t * obj) +{ + return lv_obj_get_style_grid_row_align(obj, 0); +} + +/********************** + * GLOBAL VARIABLES + **********************/ +uint16_t LV_LAYOUT_GRID; +lv_style_prop_t LV_STYLE_GRID_COLUMN_DSC_ARRAY; +lv_style_prop_t LV_STYLE_GRID_COLUMN_ALIGN; +lv_style_prop_t LV_STYLE_GRID_ROW_DSC_ARRAY; +lv_style_prop_t LV_STYLE_GRID_ROW_ALIGN; +lv_style_prop_t LV_STYLE_GRID_CELL_COLUMN_POS; +lv_style_prop_t LV_STYLE_GRID_CELL_COLUMN_SPAN; +lv_style_prop_t LV_STYLE_GRID_CELL_X_ALIGN; +lv_style_prop_t LV_STYLE_GRID_CELL_ROW_POS; +lv_style_prop_t LV_STYLE_GRID_CELL_ROW_SPAN; +lv_style_prop_t LV_STYLE_GRID_CELL_Y_ALIGN; + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + + +void lv_grid_init(void) +{ + LV_LAYOUT_GRID = lv_layout_register(grid_update, NULL); + + LV_STYLE_GRID_COLUMN_DSC_ARRAY = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + LV_STYLE_GRID_ROW_DSC_ARRAY = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + LV_STYLE_GRID_COLUMN_ALIGN = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + LV_STYLE_GRID_ROW_ALIGN = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + + LV_STYLE_GRID_CELL_ROW_SPAN = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + LV_STYLE_GRID_CELL_ROW_POS = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + LV_STYLE_GRID_CELL_COLUMN_SPAN = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + LV_STYLE_GRID_CELL_COLUMN_POS = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + LV_STYLE_GRID_CELL_X_ALIGN = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); + LV_STYLE_GRID_CELL_Y_ALIGN = lv_style_register_prop(LV_STYLE_PROP_LAYOUT_REFR); +} + +void lv_obj_set_grid_dsc_array(lv_obj_t * obj, const lv_coord_t col_dsc[], const lv_coord_t row_dsc[]) +{ + lv_obj_set_style_grid_column_dsc_array(obj, col_dsc, 0); + lv_obj_set_style_grid_row_dsc_array(obj, row_dsc, 0); + lv_obj_set_style_layout(obj, LV_LAYOUT_GRID, 0); +} + +void lv_obj_set_grid_align(lv_obj_t * obj, lv_grid_align_t column_align, lv_grid_align_t row_align) +{ + lv_obj_set_style_grid_column_align(obj, column_align, 0); + lv_obj_set_style_grid_row_align(obj, row_align, 0); + +} + +void lv_obj_set_grid_cell(lv_obj_t * obj, lv_grid_align_t x_align, uint8_t col_pos, uint8_t col_span, + lv_grid_align_t y_align, uint8_t row_pos, uint8_t row_span) + +{ + lv_obj_set_style_grid_cell_column_pos(obj, col_pos, 0); + lv_obj_set_style_grid_cell_row_pos(obj, row_pos, 0); + lv_obj_set_style_grid_cell_x_align(obj, x_align, 0); + lv_obj_set_style_grid_cell_column_span(obj, col_span, 0); + lv_obj_set_style_grid_cell_row_span(obj, row_span, 0); + lv_obj_set_style_grid_cell_y_align(obj, y_align, 0); + + lv_obj_mark_layout_as_dirty(lv_obj_get_parent(obj)); +} + + +void lv_style_set_grid_row_dsc_array(lv_style_t * style, const lv_coord_t value[]) +{ + lv_style_value_t v = { + .ptr = (const void *)value + }; + lv_style_set_prop(style, LV_STYLE_GRID_ROW_DSC_ARRAY, v); +} + +void lv_style_set_grid_column_dsc_array(lv_style_t * style, const lv_coord_t value[]) +{ + lv_style_value_t v = { + .ptr = (const void *)value + }; + lv_style_set_prop(style, LV_STYLE_GRID_COLUMN_DSC_ARRAY, v); +} + +void lv_style_set_grid_row_align(lv_style_t * style, lv_grid_align_t value) +{ + lv_style_value_t v = { + .num = (lv_grid_align_t)value + }; + lv_style_set_prop(style, LV_STYLE_GRID_ROW_ALIGN, v); +} + +void lv_style_set_grid_column_align(lv_style_t * style, lv_grid_align_t value) +{ + lv_style_value_t v = { + .num = (lv_grid_align_t)value + }; + lv_style_set_prop(style, LV_STYLE_GRID_COLUMN_ALIGN, v); +} + + +void lv_style_set_grid_cell_column_pos(lv_style_t * style, lv_coord_t value) +{ + lv_style_value_t v = { + .num = value + }; + lv_style_set_prop(style, LV_STYLE_GRID_CELL_COLUMN_POS, v); +} + +void lv_style_set_grid_cell_column_span(lv_style_t * style, lv_coord_t value) +{ + lv_style_value_t v = { + .num = value + }; + lv_style_set_prop(style, LV_STYLE_GRID_CELL_COLUMN_SPAN, v); +} + +void lv_style_set_grid_cell_row_pos(lv_style_t * style, lv_coord_t value) +{ + lv_style_value_t v = { + .num = value + }; + lv_style_set_prop(style, LV_STYLE_GRID_CELL_ROW_POS, v); +} + +void lv_style_set_grid_cell_row_span(lv_style_t * style, lv_coord_t value) +{ + lv_style_value_t v = { + .num = value + }; + lv_style_set_prop(style, LV_STYLE_GRID_CELL_ROW_SPAN, v); +} + +void lv_style_set_grid_cell_x_align(lv_style_t * style, lv_coord_t value) +{ + lv_style_value_t v = { + .num = value + }; + lv_style_set_prop(style, LV_STYLE_GRID_CELL_X_ALIGN, v); +} + +void lv_style_set_grid_cell_y_align(lv_style_t * style, lv_coord_t value) +{ + lv_style_value_t v = { + .num = value + }; + lv_style_set_prop(style, LV_STYLE_GRID_CELL_Y_ALIGN, v); +} + +void lv_obj_set_style_grid_row_dsc_array(lv_obj_t * obj, const lv_coord_t value[], lv_style_selector_t selector) +{ + lv_style_value_t v = { + .ptr = (const void *)value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_GRID_ROW_DSC_ARRAY, v, selector); +} + +void lv_obj_set_style_grid_column_dsc_array(lv_obj_t * obj, const lv_coord_t value[], lv_style_selector_t selector) +{ + lv_style_value_t v = { + .ptr = (const void *)value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_GRID_COLUMN_DSC_ARRAY, v, selector); +} + + +void lv_obj_set_style_grid_row_align(lv_obj_t * obj, lv_grid_align_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = (int32_t) value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_GRID_ROW_ALIGN, v, selector); +} + +void lv_obj_set_style_grid_column_align(lv_obj_t * obj, lv_grid_align_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = (int32_t) value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_GRID_COLUMN_ALIGN, v, selector); +} + + +void lv_obj_set_style_grid_cell_column_pos(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_GRID_CELL_COLUMN_POS, v, selector); +} + +void lv_obj_set_style_grid_cell_column_span(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_GRID_CELL_COLUMN_SPAN, v, selector); +} + +void lv_obj_set_style_grid_cell_row_pos(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_GRID_CELL_ROW_POS, v, selector); +} + +void lv_obj_set_style_grid_cell_row_span(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_GRID_CELL_ROW_SPAN, v, selector); +} + +void lv_obj_set_style_grid_cell_x_align(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_GRID_CELL_X_ALIGN, v, selector); +} + +void lv_obj_set_style_grid_cell_y_align(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector) +{ + lv_style_value_t v = { + .num = value + }; + lv_obj_set_local_style_prop(obj, LV_STYLE_GRID_CELL_Y_ALIGN, v, selector); +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void grid_update(lv_obj_t * cont, void * user_data) +{ + LV_LOG_INFO("update %p container", (void *)cont); + LV_UNUSED(user_data); + + const lv_coord_t * col_templ = get_col_dsc(cont); + const lv_coord_t * row_templ = get_row_dsc(cont); + if(col_templ == NULL || row_templ == NULL) return; + + _lv_grid_calc_t c; + calc(cont, &c); + + item_repos_hint_t hint; + lv_memset_00(&hint, sizeof(hint)); + + /*Calculate the grids absolute x and y coordinates. + *It will be used as helper during item repositioning to avoid calculating this value for every children*/ + lv_coord_t border_widt = lv_obj_get_style_border_width(cont, LV_PART_MAIN); + lv_coord_t pad_left = lv_obj_get_style_pad_left(cont, LV_PART_MAIN) + border_widt; + lv_coord_t pad_top = lv_obj_get_style_pad_top(cont, LV_PART_MAIN) + border_widt; + hint.grid_abs.x = pad_left + cont->coords.x1 - lv_obj_get_scroll_x(cont); + hint.grid_abs.y = pad_top + cont->coords.y1 - lv_obj_get_scroll_y(cont); + + uint32_t i; + for(i = 0; i < cont->spec_attr->child_cnt; i++) { + lv_obj_t * item = cont->spec_attr->children[i]; + item_repos(item, &c, &hint); + } + calc_free(&c); + + lv_coord_t w_set = lv_obj_get_style_width(cont, LV_PART_MAIN); + lv_coord_t h_set = lv_obj_get_style_height(cont, LV_PART_MAIN); + if(w_set == LV_SIZE_CONTENT || h_set == LV_SIZE_CONTENT) { + lv_obj_refr_size(cont); + } + + lv_event_send(cont, LV_EVENT_LAYOUT_CHANGED, NULL); + + LV_TRACE_LAYOUT("finished"); +} + +/** + * Calculate the grid cells coordinates + * @param cont an object that has a grid + * @param calc store the calculated cells sizes here + * @note `_lv_grid_calc_free(calc_out)` needs to be called when `calc_out` is not needed anymore + */ +static void calc(lv_obj_t * cont, _lv_grid_calc_t * calc_out) +{ + if(lv_obj_get_child(cont, 0) == NULL) { + lv_memset_00(calc_out, sizeof(_lv_grid_calc_t)); + return; + } + + calc_rows(cont, calc_out); + calc_cols(cont, calc_out); + + lv_coord_t col_gap = lv_obj_get_style_pad_column(cont, LV_PART_MAIN); + lv_coord_t row_gap = lv_obj_get_style_pad_row(cont, LV_PART_MAIN); + + bool rev = lv_obj_get_style_base_dir(cont, LV_PART_MAIN) == LV_BASE_DIR_RTL ? true : false; + + lv_coord_t w_set = lv_obj_get_style_width(cont, LV_PART_MAIN); + lv_coord_t h_set = lv_obj_get_style_height(cont, LV_PART_MAIN); + bool auto_w = (w_set == LV_SIZE_CONTENT && !cont->w_layout) ? true : false; + lv_coord_t cont_w = lv_obj_get_content_width(cont); + calc_out->grid_w = grid_align(cont_w, auto_w, get_grid_col_align(cont), col_gap, calc_out->col_num, calc_out->w, + calc_out->x, rev); + + bool auto_h = (h_set == LV_SIZE_CONTENT && !cont->h_layout) ? true : false; + lv_coord_t cont_h = lv_obj_get_content_height(cont); + calc_out->grid_h = grid_align(cont_h, auto_h, get_grid_row_align(cont), row_gap, calc_out->row_num, calc_out->h, + calc_out->y, false); + + LV_ASSERT_MEM_INTEGRITY(); +} + +/** + * Free the a grid calculation's data + * @param calc pointer to the calculated grid cell coordinates + */ +static void calc_free(_lv_grid_calc_t * calc) +{ + lv_mem_buf_release(calc->x); + lv_mem_buf_release(calc->y); + lv_mem_buf_release(calc->w); + lv_mem_buf_release(calc->h); +} + +static void calc_cols(lv_obj_t * cont, _lv_grid_calc_t * c) +{ + const lv_coord_t * col_templ = get_col_dsc(cont); + lv_coord_t cont_w = lv_obj_get_content_width(cont); + + c->col_num = count_tracks(col_templ); + c->x = lv_mem_buf_get(sizeof(lv_coord_t) * c->col_num); + c->w = lv_mem_buf_get(sizeof(lv_coord_t) * c->col_num); + + /*Set sizes for CONTENT cells*/ + uint32_t i; + for(i = 0; i < c->col_num; i++) { + lv_coord_t size = LV_COORD_MIN; + if(IS_CONTENT(col_templ[i])) { + /*Check the size of children of this cell*/ + uint32_t ci; + for(ci = 0; ci < lv_obj_get_child_cnt(cont); ci++) { + lv_obj_t * item = lv_obj_get_child(cont, ci); + if(lv_obj_has_flag_any(item, LV_OBJ_FLAG_IGNORE_LAYOUT | LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) continue; + uint32_t col_span = get_col_span(item); + if(col_span != 1) continue; + + uint32_t col_pos = get_col_pos(item); + if(col_pos != i) continue; + + size = LV_MAX(size, lv_obj_get_width(item)); + } + if(size >= 0) c->w[i] = size; + else c->w[i] = 0; + } + } + + uint32_t col_fr_cnt = 0; + lv_coord_t grid_w = 0; + + for(i = 0; i < c->col_num; i++) { + lv_coord_t x = col_templ[i]; + if(IS_FR(x)) { + col_fr_cnt += GET_FR(x); + } + else if(IS_CONTENT(x)) { + grid_w += c->w[i]; + } + else { + c->w[i] = x; + grid_w += x; + } + } + + lv_coord_t col_gap = lv_obj_get_style_pad_column(cont, LV_PART_MAIN); + cont_w -= col_gap * (c->col_num - 1); + lv_coord_t free_w = cont_w - grid_w; + if(free_w < 0) free_w = 0; + + int32_t last_fr_i = -1; + int32_t last_fr_x = 0; + for(i = 0; i < c->col_num; i++) { + lv_coord_t x = col_templ[i]; + if(IS_FR(x)) { + lv_coord_t f = GET_FR(x); + c->w[i] = (free_w * f) / col_fr_cnt; + last_fr_i = i; + last_fr_x = f; + } + } + + /*To avoid rounding errors set the last FR track to the remaining size */ + if(last_fr_i >= 0) { + c->w[last_fr_i] = free_w - ((free_w * (col_fr_cnt - last_fr_x)) / col_fr_cnt); + } +} + +static void calc_rows(lv_obj_t * cont, _lv_grid_calc_t * c) +{ + uint32_t i; + const lv_coord_t * row_templ = get_row_dsc(cont); + c->row_num = count_tracks(row_templ); + c->y = lv_mem_buf_get(sizeof(lv_coord_t) * c->row_num); + c->h = lv_mem_buf_get(sizeof(lv_coord_t) * c->row_num); + /*Set sizes for CONTENT cells*/ + for(i = 0; i < c->row_num; i++) { + lv_coord_t size = LV_COORD_MIN; + if(IS_CONTENT(row_templ[i])) { + /*Check the size of children of this cell*/ + uint32_t ci; + for(ci = 0; ci < lv_obj_get_child_cnt(cont); ci++) { + lv_obj_t * item = lv_obj_get_child(cont, ci); + if(lv_obj_has_flag_any(item, LV_OBJ_FLAG_IGNORE_LAYOUT | LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) continue; + uint32_t row_span = get_row_span(item); + if(row_span != 1) continue; + + uint32_t row_pos = get_row_pos(item); + if(row_pos != i) continue; + + size = LV_MAX(size, lv_obj_get_height(item)); + } + if(size >= 0) c->h[i] = size; + else c->h[i] = 0; + } + } + + uint32_t row_fr_cnt = 0; + lv_coord_t grid_h = 0; + + for(i = 0; i < c->row_num; i++) { + lv_coord_t x = row_templ[i]; + if(IS_FR(x)) { + row_fr_cnt += GET_FR(x); + } + else if(IS_CONTENT(x)) { + grid_h += c->h[i]; + } + else { + c->h[i] = x; + grid_h += x; + } + } + + + lv_coord_t row_gap = lv_obj_get_style_pad_row(cont, LV_PART_MAIN); + lv_coord_t cont_h = lv_obj_get_content_height(cont) - row_gap * (c->row_num - 1); + lv_coord_t free_h = cont_h - grid_h; + if(free_h < 0) free_h = 0; + + int32_t last_fr_i = -1; + int32_t last_fr_x = 0; + for(i = 0; i < c->row_num; i++) { + lv_coord_t x = row_templ[i]; + if(IS_FR(x)) { + lv_coord_t f = GET_FR(x); + c->h[i] = (free_h * f) / row_fr_cnt; + last_fr_i = i; + last_fr_x = f; + } + } + + /*To avoid rounding errors set the last FR track to the remaining size */ + if(last_fr_i >= 0) { + c->h[last_fr_i] = free_h - ((free_h * (row_fr_cnt - last_fr_x)) / row_fr_cnt); + } +} + +/** + * Reposition a grid item in its cell + * @param item a grid item to reposition + * @param calc the calculated grid of `cont` + * @param child_id_ext helper value if the ID of the child is know (order from the oldest) else -1 + * @param grid_abs helper value, the absolute position of the grid, NULL if unknown + */ +static void item_repos(lv_obj_t * item, _lv_grid_calc_t * c, item_repos_hint_t * hint) +{ + if(lv_obj_has_flag_any(item, LV_OBJ_FLAG_IGNORE_LAYOUT | LV_OBJ_FLAG_HIDDEN | LV_OBJ_FLAG_FLOATING)) return; + uint32_t col_span = get_col_span(item); + uint32_t row_span = get_row_span(item); + if(row_span == 0 || col_span == 0) return; + + uint32_t col_pos = get_col_pos(item); + uint32_t row_pos = get_row_pos(item); + lv_grid_align_t col_align = get_cell_col_align(item); + lv_grid_align_t row_align = get_cell_row_align(item); + + + lv_coord_t col_x1 = c->x[col_pos]; + lv_coord_t col_x2 = c->x[col_pos + col_span - 1] + c->w[col_pos + col_span - 1]; + lv_coord_t col_w = col_x2 - col_x1; + + lv_coord_t row_y1 = c->y[row_pos]; + lv_coord_t row_y2 = c->y[row_pos + row_span - 1] + c->h[row_pos + row_span - 1]; + lv_coord_t row_h = row_y2 - row_y1; + + + /*If the item has RTL base dir switch start and end*/ + if(lv_obj_get_style_base_dir(item, LV_PART_MAIN) == LV_BASE_DIR_RTL) { + if(col_align == LV_GRID_ALIGN_START) col_align = LV_GRID_ALIGN_END; + else if(col_align == LV_GRID_ALIGN_END) col_align = LV_GRID_ALIGN_START; + } + + lv_coord_t x; + lv_coord_t y; + lv_coord_t item_w = lv_area_get_width(&item->coords); + lv_coord_t item_h = lv_area_get_height(&item->coords); + + switch(col_align) { + default: + case LV_GRID_ALIGN_START: + x = c->x[col_pos]; + item->w_layout = 0; + break; + case LV_GRID_ALIGN_STRETCH: + x = c->x[col_pos]; + item_w = col_w; + item->w_layout = 1; + break; + case LV_GRID_ALIGN_CENTER: + x = c->x[col_pos] + (col_w - item_w) / 2; + item->w_layout = 0; + break; + case LV_GRID_ALIGN_END: + x = c->x[col_pos] + col_w - lv_obj_get_width(item); + item->w_layout = 0; + break; + } + + switch(row_align) { + default: + case LV_GRID_ALIGN_START: + y = c->y[row_pos]; + item->h_layout = 0; + break; + case LV_GRID_ALIGN_STRETCH: + y = c->y[row_pos]; + item_h = row_h; + item->h_layout = 1; + break; + case LV_GRID_ALIGN_CENTER: + y = c->y[row_pos] + (row_h - item_h) / 2; + item->h_layout = 0; + break; + case LV_GRID_ALIGN_END: + y = c->y[row_pos] + row_h - lv_obj_get_height(item); + item->h_layout = 0; + break; + } + + /*Set a new size if required*/ + if(lv_obj_get_width(item) != item_w || lv_obj_get_height(item) != item_h) { + lv_area_t old_coords; + lv_area_copy(&old_coords, &item->coords); + lv_obj_invalidate(item); + lv_area_set_width(&item->coords, item_w); + lv_area_set_height(&item->coords, item_h); + lv_obj_invalidate(item); + lv_event_send(item, LV_EVENT_SIZE_CHANGED, &old_coords); + lv_event_send(lv_obj_get_parent(item), LV_EVENT_CHILD_CHANGED, item); + + } + + /*Handle percentage value of translate*/ + lv_coord_t tr_x = lv_obj_get_style_translate_x(item, LV_PART_MAIN); + lv_coord_t tr_y = lv_obj_get_style_translate_y(item, LV_PART_MAIN); + lv_coord_t w = lv_obj_get_width(item); + lv_coord_t h = lv_obj_get_height(item); + if(LV_COORD_IS_PCT(tr_x)) tr_x = (w * LV_COORD_GET_PCT(tr_x)) / 100; + if(LV_COORD_IS_PCT(tr_y)) tr_y = (h * LV_COORD_GET_PCT(tr_y)) / 100; + + x += tr_x; + y += tr_y; + + lv_coord_t diff_x = hint->grid_abs.x + x - item->coords.x1; + lv_coord_t diff_y = hint->grid_abs.y + y - item->coords.y1; + if(diff_x || diff_y) { + lv_obj_invalidate(item); + item->coords.x1 += diff_x; + item->coords.x2 += diff_x; + item->coords.y1 += diff_y; + item->coords.y2 += diff_y; + lv_obj_invalidate(item); + lv_obj_move_children_by(item, diff_x, diff_y, false); + } +} + +/** + * Place the grid track according to align methods. It keeps the track sizes but sets their position. + * It can process both columns or rows according to the passed parameters. + * @param cont_size size of the containers content area (width/height) + * @param auto_size true: the container has auto size in the current direction + * @param align align method + * @param gap grid gap + * @param track_num number of tracks + * @param size_array array with the track sizes + * @param pos_array write the positions of the tracks here + * @return the total size of the grid + */ +static lv_coord_t grid_align(lv_coord_t cont_size, bool auto_size, uint8_t align, lv_coord_t gap, uint32_t track_num, + lv_coord_t * size_array, lv_coord_t * pos_array, bool reverse) +{ + lv_coord_t grid_size = 0; + uint32_t i; + + if(auto_size) { + pos_array[0] = 0; + } + else { + /*With spaced alignment gap will be calculated from the remaining space*/ + if(align == LV_GRID_ALIGN_SPACE_AROUND || align == LV_GRID_ALIGN_SPACE_BETWEEN || align == LV_GRID_ALIGN_SPACE_EVENLY) { + gap = 0; + if(track_num == 1) align = LV_GRID_ALIGN_CENTER; + } + + /*Get the full grid size with gap*/ + for(i = 0; i < track_num; i++) { + grid_size += size_array[i] + gap; + } + grid_size -= gap; + + /*Calculate the position of the first item and set gap is necessary*/ + switch(align) { + case LV_GRID_ALIGN_START: + pos_array[0] = 0; + break; + case LV_GRID_ALIGN_CENTER: + pos_array[0] = (cont_size - grid_size) / 2; + break; + case LV_GRID_ALIGN_END: + pos_array[0] = cont_size - grid_size; + break; + case LV_GRID_ALIGN_SPACE_BETWEEN: + pos_array[0] = 0; + gap = (lv_coord_t)(cont_size - grid_size) / (lv_coord_t)(track_num - 1); + break; + case LV_GRID_ALIGN_SPACE_AROUND: + gap = (lv_coord_t)(cont_size - grid_size) / (lv_coord_t)(track_num); + pos_array[0] = gap / 2; + break; + case LV_GRID_ALIGN_SPACE_EVENLY: + gap = (lv_coord_t)(cont_size - grid_size) / (lv_coord_t)(track_num + 1); + pos_array[0] = gap; + break; + + } + } + + /*Set the position of all tracks from the start position, gaps and track sizes*/ + for(i = 0; i < track_num - 1; i++) { + pos_array[i + 1] = pos_array[i] + size_array[i] + gap; + } + + lv_coord_t total_gird_size = pos_array[track_num - 1] + size_array[track_num - 1] - pos_array[0]; + + if(reverse) { + for(i = 0; i < track_num; i++) { + pos_array[i] = cont_size - pos_array[i] - size_array[i]; + } + + } + + /*Return the full size of the grid*/ + return total_gird_size; +} + +static uint32_t count_tracks(const lv_coord_t * templ) +{ + uint32_t i; + for(i = 0; templ[i] != LV_GRID_TEMPLATE_LAST; i++); + + return i; +} + + +#endif /*LV_USE_GRID*/ diff --git a/lib/lvgl/src/extra/layouts/grid/lv_grid.h b/lib/lvgl/src/extra/layouts/grid/lv_grid.h new file mode 100644 index 00000000..5c4f7672 --- /dev/null +++ b/lib/lvgl/src/extra/layouts/grid/lv_grid.h @@ -0,0 +1,194 @@ +/** + * @file lv_grid.h + * + */ + +#ifndef LV_GRID_H +#define LV_GRID_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" +#if LV_USE_GRID + +/********************* + * DEFINES + *********************/ +/** + * Can be used track size to make the track fill the free space. + * @param x how much space to take proportionally to other FR tracks + * @return a special track size + */ +#define LV_GRID_FR(x) (LV_COORD_MAX - 100 + x) + +#define LV_GRID_CONTENT (LV_COORD_MAX - 101) +LV_EXPORT_CONST_INT(LV_GRID_CONTENT); + +#define LV_GRID_TEMPLATE_LAST (LV_COORD_MAX) +LV_EXPORT_CONST_INT(LV_GRID_TEMPLATE_LAST); + +/********************** + * TYPEDEFS + **********************/ + +/*Can't include lv_obj.h because it includes this header file*/ +struct _lv_obj_t; + +typedef enum { + LV_GRID_ALIGN_START, + LV_GRID_ALIGN_CENTER, + LV_GRID_ALIGN_END, + LV_GRID_ALIGN_STRETCH, + LV_GRID_ALIGN_SPACE_EVENLY, + LV_GRID_ALIGN_SPACE_AROUND, + LV_GRID_ALIGN_SPACE_BETWEEN, +} lv_grid_align_t; + +/********************** + * GLOBAL VARIABLES + **********************/ + +extern uint16_t LV_LAYOUT_GRID; +extern lv_style_prop_t LV_STYLE_GRID_COLUMN_DSC_ARRAY; +extern lv_style_prop_t LV_STYLE_GRID_COLUMN_ALIGN; +extern lv_style_prop_t LV_STYLE_GRID_ROW_DSC_ARRAY; +extern lv_style_prop_t LV_STYLE_GRID_ROW_ALIGN; +extern lv_style_prop_t LV_STYLE_GRID_CELL_COLUMN_POS; +extern lv_style_prop_t LV_STYLE_GRID_CELL_COLUMN_SPAN; +extern lv_style_prop_t LV_STYLE_GRID_CELL_X_ALIGN; +extern lv_style_prop_t LV_STYLE_GRID_CELL_ROW_POS; +extern lv_style_prop_t LV_STYLE_GRID_CELL_ROW_SPAN; +extern lv_style_prop_t LV_STYLE_GRID_CELL_Y_ALIGN; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +void lv_grid_init(void); + +void lv_obj_set_grid_dsc_array(lv_obj_t * obj, const lv_coord_t col_dsc[], const lv_coord_t row_dsc[]); + +void lv_obj_set_grid_align(lv_obj_t * obj, lv_grid_align_t column_align, lv_grid_align_t row_align); + +/** + * Set the cell of an object. The object's parent needs to have grid layout, else nothing will happen + * @param obj pointer to an object + * @param column_align the vertical alignment in the cell. `LV_GRID_START/END/CENTER/STRETCH` + * @param col_pos column ID + * @param col_span number of columns to take (>= 1) + * @param row_align the horizontal alignment in the cell. `LV_GRID_START/END/CENTER/STRETCH` + * @param row_pos row ID + * @param row_span number of rows to take (>= 1) + */ +void lv_obj_set_grid_cell(lv_obj_t * obj, lv_grid_align_t column_align, uint8_t col_pos, uint8_t col_span, + lv_grid_align_t row_align, uint8_t row_pos, uint8_t row_span); + +/** + * Just a wrapper to `LV_GRID_FR` for bindings. + */ +static inline lv_coord_t lv_grid_fr(uint8_t x) +{ + return LV_GRID_FR(x); +} + +void lv_style_set_grid_row_dsc_array(lv_style_t * style, const lv_coord_t value[]); +void lv_style_set_grid_column_dsc_array(lv_style_t * style, const lv_coord_t value[]); +void lv_style_set_grid_row_align(lv_style_t * style, lv_grid_align_t value); +void lv_style_set_grid_column_align(lv_style_t * style, lv_grid_align_t value); +void lv_style_set_grid_cell_column_pos(lv_style_t * style, lv_coord_t value); +void lv_style_set_grid_cell_column_span(lv_style_t * style, lv_coord_t value); +void lv_style_set_grid_cell_row_pos(lv_style_t * style, lv_coord_t value); +void lv_style_set_grid_cell_row_span(lv_style_t * style, lv_coord_t value); +void lv_style_set_grid_cell_x_align(lv_style_t * style, lv_coord_t value); +void lv_style_set_grid_cell_y_align(lv_style_t * style, lv_coord_t value); + +void lv_obj_set_style_grid_row_dsc_array(lv_obj_t * obj, const lv_coord_t value[], lv_style_selector_t selector); +void lv_obj_set_style_grid_column_dsc_array(lv_obj_t * obj, const lv_coord_t value[], lv_style_selector_t selector); +void lv_obj_set_style_grid_row_align(lv_obj_t * obj, lv_grid_align_t value, lv_style_selector_t selector); +void lv_obj_set_style_grid_column_align(lv_obj_t * obj, lv_grid_align_t value, lv_style_selector_t selector); +void lv_obj_set_style_grid_cell_column_pos(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector); +void lv_obj_set_style_grid_cell_column_span(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector); +void lv_obj_set_style_grid_cell_row_pos(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector); +void lv_obj_set_style_grid_cell_row_span(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector); +void lv_obj_set_style_grid_cell_x_align(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector); +void lv_obj_set_style_grid_cell_y_align(lv_obj_t * obj, lv_coord_t value, lv_style_selector_t selector); + +static inline const lv_coord_t * lv_obj_get_style_grid_row_dsc_array(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_GRID_ROW_DSC_ARRAY); + return (const lv_coord_t *)v.ptr; +} + +static inline const lv_coord_t * lv_obj_get_style_grid_column_dsc_array(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_GRID_COLUMN_DSC_ARRAY); + return (const lv_coord_t *)v.ptr; +} + +static inline lv_grid_align_t lv_obj_get_style_grid_row_align(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_GRID_ROW_ALIGN); + return (lv_grid_align_t)v.num; +} + +static inline lv_grid_align_t lv_obj_get_style_grid_column_align(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_GRID_COLUMN_ALIGN); + return (lv_grid_align_t)v.num; +} + +static inline lv_coord_t lv_obj_get_style_grid_cell_column_pos(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_GRID_CELL_COLUMN_POS); + return (lv_coord_t)v.num; +} + +static inline lv_coord_t lv_obj_get_style_grid_cell_column_span(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_GRID_CELL_COLUMN_SPAN); + return (lv_coord_t)v.num; +} + +static inline lv_coord_t lv_obj_get_style_grid_cell_row_pos(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_GRID_CELL_ROW_POS); + return (lv_coord_t)v.num; +} + +static inline lv_coord_t lv_obj_get_style_grid_cell_row_span(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_GRID_CELL_ROW_SPAN); + return (lv_coord_t)v.num; +} + +static inline lv_coord_t lv_obj_get_style_grid_cell_x_align(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_GRID_CELL_X_ALIGN); + return (lv_coord_t)v.num; +} + +static inline lv_coord_t lv_obj_get_style_grid_cell_y_align(const lv_obj_t * obj, uint32_t part) +{ + lv_style_value_t v = lv_obj_get_style_prop(obj, part, LV_STYLE_GRID_CELL_Y_ALIGN); + return (lv_coord_t)v.num; +} + +/********************** + * GLOBAL VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ +#endif /*LV_USE_GRID*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_GRID_H*/ diff --git a/lib/lvgl/src/extra/layouts/lv_layouts.h b/lib/lvgl/src/extra/layouts/lv_layouts.h new file mode 100644 index 00000000..9c1e958d --- /dev/null +++ b/lib/lvgl/src/extra/layouts/lv_layouts.h @@ -0,0 +1,44 @@ +/** + * @file lv_layouts.h + * + */ + +#ifndef LV_LAYOUTS_H +#define LV_LAYOUTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "flex/lv_flex.h" +#include "grid/lv_grid.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ +#if LV_USE_LOG && LV_LOG_TRACE_LAYOUT +# define LV_TRACE_LAYOUT(...) LV_LOG_TRACE(__VA_ARGS__) +#else +# define LV_TRACE_LAYOUT(...) +#endif + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_LAYOUTS_H*/ diff --git a/lib/lvgl/src/extra/libs/bmp/lv_bmp.c b/lib/lvgl/src/extra/libs/bmp/lv_bmp.c new file mode 100644 index 00000000..f89a0a8c --- /dev/null +++ b/lib/lvgl/src/extra/libs/bmp/lv_bmp.c @@ -0,0 +1,258 @@ +/** + * @file lv_bmp.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" +#if LV_USE_BMP + +#include <string.h> + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_fs_file_t f; + unsigned int px_offset; + int px_width; + int px_height; + unsigned int bpp; + int row_size_bytes; +} bmp_dsc_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static lv_res_t decoder_info(lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header); +static lv_res_t decoder_open(lv_img_decoder_t * dec, lv_img_decoder_dsc_t * dsc); + + +static lv_res_t decoder_read_line(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc, + lv_coord_t x, lv_coord_t y, lv_coord_t len, uint8_t * buf); + +static void decoder_close(lv_img_decoder_t * dec, lv_img_decoder_dsc_t * dsc); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +void lv_bmp_init(void) +{ + lv_img_decoder_t * dec = lv_img_decoder_create(); + lv_img_decoder_set_info_cb(dec, decoder_info); + lv_img_decoder_set_open_cb(dec, decoder_open); + lv_img_decoder_set_read_line_cb(dec, decoder_read_line); + lv_img_decoder_set_close_cb(dec, decoder_close); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/** + * Get info about a PNG image + * @param src can be file name or pointer to a C array + * @param header store the info here + * @return LV_RES_OK: no error; LV_RES_INV: can't get the info + */ +static lv_res_t decoder_info(lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header) +{ + LV_UNUSED(decoder); + + lv_img_src_t src_type = lv_img_src_get_type(src); /*Get the source type*/ + + /*If it's a BMP file...*/ + if(src_type == LV_IMG_SRC_FILE) { + const char * fn = src; + if(strcmp(lv_fs_get_ext(fn), "bmp") == 0) { /*Check the extension*/ + /*Save the data in the header*/ + lv_fs_file_t f; + lv_fs_res_t res = lv_fs_open(&f, src, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) return LV_RES_INV; + uint8_t headers[54]; + + lv_fs_read(&f, headers, 54, NULL); + uint32_t w; + uint32_t h; + memcpy(&w, headers + 18, 4); + memcpy(&h, headers + 22, 4); + header->w = w; + header->h = h; + header->always_zero = 0; + lv_fs_close(&f); +#if LV_COLOR_DEPTH == 32 + uint16_t bpp; + memcpy(&bpp, headers + 28, 2); + header->cf = bpp == 32 ? LV_IMG_CF_TRUE_COLOR_ALPHA : LV_IMG_CF_TRUE_COLOR; +#else + header->cf = LV_IMG_CF_TRUE_COLOR; +#endif + return LV_RES_OK; + } + } + /* BMP file as data not supported for simplicity. + * Convert them to LVGL compatible C arrays directly. */ + else if(src_type == LV_IMG_SRC_VARIABLE) { + return LV_RES_INV; + } + + return LV_RES_INV; /*If didn't succeeded earlier then it's an error*/ +} + + +/** + * Open a PNG image and return the decided image + * @param src can be file name or pointer to a C array + * @param style style of the image object (unused now but certain formats might use it) + * @return pointer to the decoded image or `LV_IMG_DECODER_OPEN_FAIL` if failed + */ +static lv_res_t decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc) +{ + LV_UNUSED(decoder); + + /*If it's a PNG file...*/ + if(dsc->src_type == LV_IMG_SRC_FILE) { + const char * fn = dsc->src; + + if(strcmp(lv_fs_get_ext(fn), "bmp") != 0) { + return LV_RES_INV; /*Check the extension*/ + } + + bmp_dsc_t b; + memset(&b, 0x00, sizeof(b)); + + lv_fs_res_t res = lv_fs_open(&b.f, dsc->src, LV_FS_MODE_RD); + if(res == LV_RES_OK) return LV_RES_INV; + + uint8_t header[54]; + lv_fs_read(&b.f, header, 54, NULL); + + if(0x42 != header[0] || 0x4d != header[1]) { + lv_fs_close(&b.f); + return LV_RES_INV; + } + + memcpy(&b.px_offset, header + 10, 4); + memcpy(&b.px_width, header + 18, 4); + memcpy(&b.px_height, header + 22, 4); + memcpy(&b.bpp, header + 28, 2); + b.row_size_bytes = ((b.bpp * b.px_width + 31) / 32) * 4; + + bool color_depth_error = false; + if(LV_COLOR_DEPTH == 32 && (b.bpp != 32 && b.bpp != 24)) { + LV_LOG_WARN("LV_COLOR_DEPTH == 32 but bpp is %d (should be 32 or 24)", b.bpp); + color_depth_error = true; + } + else if(LV_COLOR_DEPTH == 16 && b.bpp != 16) { + LV_LOG_WARN("LV_COLOR_DEPTH == 16 but bpp is %d (should be 16)", b.bpp); + color_depth_error = true; + } + else if(LV_COLOR_DEPTH == 8 && b.bpp != 8) { + LV_LOG_WARN("LV_COLOR_DEPTH == 8 but bpp is %d (should be 8)", b.bpp); + color_depth_error = true; + } + + if(color_depth_error) { + dsc->error_msg = "Color depth mismatch"; + lv_fs_close(&b.f); + return LV_RES_INV; + } + + dsc->user_data = lv_mem_alloc(sizeof(bmp_dsc_t)); + LV_ASSERT_MALLOC(dsc->user_data); + if(dsc->user_data == NULL) return LV_RES_INV; + memcpy(dsc->user_data, &b, sizeof(b)); + + dsc->img_data = NULL; + return LV_RES_OK; + } + /* BMP file as data not supported for simplicity. + * Convert them to LVGL compatible C arrays directly. */ + else if(dsc->src_type == LV_IMG_SRC_VARIABLE) { + return LV_RES_INV; + } + + return LV_RES_INV; /*If not returned earlier then it failed*/ +} + + +static lv_res_t decoder_read_line(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc, + lv_coord_t x, lv_coord_t y, lv_coord_t len, uint8_t * buf) +{ + LV_UNUSED(decoder); + + bmp_dsc_t * b = dsc->user_data; + y = (b->px_height - 1) - y; /*BMP images are stored upside down*/ + uint32_t p = b->px_offset + b->row_size_bytes * y; + p += x * (b->bpp / 8); + lv_fs_seek(&b->f, p, LV_FS_SEEK_SET); + lv_fs_read(&b->f, buf, len * (b->bpp / 8), NULL); + +#if LV_COLOR_DEPTH == 16 && LV_COLOR_16_SWAP == 1 + for(unsigned int i = 0; i < len * (b->bpp / 8); i += 2) { + buf[i] = buf[i] ^ buf[i + 1]; + buf[i + 1] = buf[i] ^ buf[i + 1]; + buf[i] = buf[i] ^ buf[i + 1]; + } + +#elif LV_COLOR_DEPTH == 32 + if(b->bpp == 32) { + lv_coord_t i; + for(i = 0; i < len; i++) { + uint8_t b0 = buf[i * 4]; + uint8_t b1 = buf[i * 4 + 1]; + uint8_t b2 = buf[i * 4 + 2]; + uint8_t b3 = buf[i * 4 + 3]; + lv_color32_t * c = (lv_color32_t *)&buf[i * 4]; + c->ch.red = b2; + c->ch.green = b1; + c->ch.blue = b0; + c->ch.alpha = b3; + } + } + if(b->bpp == 24) { + lv_coord_t i; + + for(i = len - 1; i >= 0; i--) { + uint8_t * t = &buf[i * 3]; + lv_color32_t * c = (lv_color32_t *)&buf[i * 4]; + c->ch.red = t[2]; + c->ch.green = t[1]; + c->ch.blue = t[0]; + c->ch.alpha = 0xff; + } + } +#endif + + return LV_RES_OK; +} + + +/** + * Free the allocated resources + */ +static void decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc) +{ + LV_UNUSED(decoder); + bmp_dsc_t * b = dsc->user_data; + lv_fs_close(&b->f); + lv_mem_free(dsc->user_data); + +} + +#endif /*LV_USE_BMP*/ diff --git a/lib/lvgl/src/extra/libs/bmp/lv_bmp.h b/lib/lvgl/src/extra/libs/bmp/lv_bmp.h new file mode 100644 index 00000000..db1e5409 --- /dev/null +++ b/lib/lvgl/src/extra/libs/bmp/lv_bmp.h @@ -0,0 +1,42 @@ +/** + * @file lv_bmp.h + * + */ + +#ifndef LV_BMP_H +#define LV_BMP_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lv_conf_internal.h" +#if LV_USE_BMP + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +void lv_bmp_init(void); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_BMP*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_BMP_H*/ diff --git a/lib/lvgl/src/extra/libs/ffmpeg/lv_ffmpeg.c b/lib/lvgl/src/extra/libs/ffmpeg/lv_ffmpeg.c new file mode 100644 index 00000000..efaa6925 --- /dev/null +++ b/lib/lvgl/src/extra/libs/ffmpeg/lv_ffmpeg.c @@ -0,0 +1,875 @@ +/** + * @file lv_ffmpeg.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_ffmpeg.h" +#if LV_USE_FFMPEG != 0 + +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libavutil/imgutils.h> +#include <libavutil/samplefmt.h> +#include <libavutil/timestamp.h> +#include <libswscale/swscale.h> + +/********************* + * DEFINES + *********************/ +#if LV_COLOR_DEPTH == 1 || LV_COLOR_DEPTH == 8 + #define AV_PIX_FMT_TRUE_COLOR AV_PIX_FMT_RGB8 +#elif LV_COLOR_DEPTH == 16 + #if LV_COLOR_16_SWAP == 0 + #define AV_PIX_FMT_TRUE_COLOR AV_PIX_FMT_RGB565LE + #else + #define AV_PIX_FMT_TRUE_COLOR AV_PIX_FMT_RGB565BE + #endif +#elif LV_COLOR_DEPTH == 32 + #define AV_PIX_FMT_TRUE_COLOR AV_PIX_FMT_BGR0 +#else + #error Unsupported LV_COLOR_DEPTH +#endif + +#define MY_CLASS &lv_ffmpeg_player_class + +#define FRAME_DEF_REFR_PERIOD 33 /*[ms]*/ + +/********************** + * TYPEDEFS + **********************/ +struct ffmpeg_context_s { + AVFormatContext * fmt_ctx; + AVCodecContext * video_dec_ctx; + AVStream * video_stream; + uint8_t * video_src_data[4]; + uint8_t * video_dst_data[4]; + struct SwsContext * sws_ctx; + AVFrame * frame; + AVPacket pkt; + int video_stream_idx; + int video_src_linesize[4]; + int video_dst_linesize[4]; + enum AVPixelFormat video_dst_pix_fmt; + bool has_alpha; +}; + +#pragma pack(1) + +struct lv_img_pixel_color_s { + lv_color_t c; + uint8_t alpha; +}; + +#pragma pack() + +/********************** + * STATIC PROTOTYPES + **********************/ + +static lv_res_t decoder_info(lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header); +static lv_res_t decoder_open(lv_img_decoder_t * dec, lv_img_decoder_dsc_t * dsc); +static void decoder_close(lv_img_decoder_t * dec, lv_img_decoder_dsc_t * dsc); + +static struct ffmpeg_context_s * ffmpeg_open_file(const char * path); +static void ffmpeg_close(struct ffmpeg_context_s * ffmpeg_ctx); +static void ffmpeg_close_src_ctx(struct ffmpeg_context_s * ffmpeg_ctx); +static void ffmpeg_close_dst_ctx(struct ffmpeg_context_s * ffmpeg_ctx); +static int ffmpeg_image_allocate(struct ffmpeg_context_s * ffmpeg_ctx); +static int ffmpeg_get_img_header(const char * path, lv_img_header_t * header); +static int ffmpeg_get_frame_refr_period(struct ffmpeg_context_s * ffmpeg_ctx); +static uint8_t * ffmpeg_get_img_data(struct ffmpeg_context_s * ffmpeg_ctx); +static int ffmpeg_update_next_frame(struct ffmpeg_context_s * ffmpeg_ctx); +static int ffmpeg_output_video_frame(struct ffmpeg_context_s * ffmpeg_ctx); +static bool ffmpeg_pix_fmt_has_alpha(enum AVPixelFormat pix_fmt); +static bool ffmpeg_pix_fmt_is_yuv(enum AVPixelFormat pix_fmt); + +static void lv_ffmpeg_player_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_ffmpeg_player_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); + +#if LV_COLOR_DEPTH != 32 + static void convert_color_depth(uint8_t * img, uint32_t px_cnt); +#endif + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_ffmpeg_player_class = { + .constructor_cb = lv_ffmpeg_player_constructor, + .destructor_cb = lv_ffmpeg_player_destructor, + .instance_size = sizeof(lv_ffmpeg_player_t), + .base_class = &lv_img_class +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_ffmpeg_init(void) +{ + lv_img_decoder_t * dec = lv_img_decoder_create(); + lv_img_decoder_set_info_cb(dec, decoder_info); + lv_img_decoder_set_open_cb(dec, decoder_open); + lv_img_decoder_set_close_cb(dec, decoder_close); + +#if LV_FFMPEG_AV_DUMP_FORMAT == 0 + av_log_set_level(AV_LOG_QUIET); +#endif +} + +int lv_ffmpeg_get_frame_num(const char * path) +{ + int ret = -1; + struct ffmpeg_context_s * ffmpeg_ctx = ffmpeg_open_file(path); + + if(ffmpeg_ctx) { + ret = ffmpeg_ctx->video_stream->nb_frames; + ffmpeg_close(ffmpeg_ctx); + } + + return ret; +} + +lv_obj_t * lv_ffmpeg_player_create(lv_obj_t * parent) +{ + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_res_t lv_ffmpeg_player_set_src(lv_obj_t * obj, const char * path) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_res_t res = LV_RES_INV; + + lv_ffmpeg_player_t * player = (lv_ffmpeg_player_t *)obj; + + if(player->ffmpeg_ctx) { + ffmpeg_close(player->ffmpeg_ctx); + player->ffmpeg_ctx = NULL; + } + + lv_timer_pause(player->timer); + + player->ffmpeg_ctx = ffmpeg_open_file(path); + + if(!player->ffmpeg_ctx) { + LV_LOG_ERROR("ffmpeg file open failed: %s", path); + goto failed; + } + + if(ffmpeg_image_allocate(player->ffmpeg_ctx) < 0) { + LV_LOG_ERROR("ffmpeg image allocate failed"); + ffmpeg_close(player->ffmpeg_ctx); + goto failed; + } + + bool has_alpha = player->ffmpeg_ctx->has_alpha; + int width = player->ffmpeg_ctx->video_dec_ctx->width; + int height = player->ffmpeg_ctx->video_dec_ctx->height; + uint32_t data_size = 0; + + if(has_alpha) { + data_size = width * height * LV_IMG_PX_SIZE_ALPHA_BYTE; + } + else { + data_size = width * height * LV_COLOR_SIZE / 8; + } + + player->imgdsc.header.always_zero = 0; + player->imgdsc.header.w = width; + player->imgdsc.header.h = height; + player->imgdsc.data_size = data_size; + player->imgdsc.header.cf = has_alpha ? LV_IMG_CF_TRUE_COLOR_ALPHA : LV_IMG_CF_TRUE_COLOR; + player->imgdsc.data = ffmpeg_get_img_data(player->ffmpeg_ctx); + + lv_img_set_src(&player->img.obj, &(player->imgdsc)); + + int period = ffmpeg_get_frame_refr_period(player->ffmpeg_ctx); + + if(period > 0) { + LV_LOG_INFO("frame refresh period = %d ms, rate = %d fps", + period, 1000 / period); + lv_timer_set_period(player->timer, period); + } + else { + LV_LOG_WARN("unable to get frame refresh period"); + } + + res = LV_RES_OK; + +failed: + return res; +} + +void lv_ffmpeg_player_set_cmd(lv_obj_t * obj, lv_ffmpeg_player_cmd_t cmd) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_ffmpeg_player_t * player = (lv_ffmpeg_player_t *)obj; + + if(!player->ffmpeg_ctx) { + LV_LOG_ERROR("ffmpeg_ctx is NULL"); + return; + } + + lv_timer_t * timer = player->timer; + + switch(cmd) { + case LV_FFMPEG_PLAYER_CMD_START: + av_seek_frame(player->ffmpeg_ctx->fmt_ctx, + 0, 0, AVSEEK_FLAG_BACKWARD); + lv_timer_resume(timer); + LV_LOG_INFO("ffmpeg player start"); + break; + case LV_FFMPEG_PLAYER_CMD_STOP: + av_seek_frame(player->ffmpeg_ctx->fmt_ctx, + 0, 0, AVSEEK_FLAG_BACKWARD); + lv_timer_pause(timer); + LV_LOG_INFO("ffmpeg player stop"); + break; + case LV_FFMPEG_PLAYER_CMD_PAUSE: + lv_timer_pause(timer); + LV_LOG_INFO("ffmpeg player pause"); + break; + case LV_FFMPEG_PLAYER_CMD_RESUME: + lv_timer_resume(timer); + LV_LOG_INFO("ffmpeg player resume"); + break; + default: + LV_LOG_ERROR("Error cmd: %d", cmd); + break; + } +} + +void lv_ffmpeg_player_set_auto_restart(lv_obj_t * obj, bool en) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_ffmpeg_player_t * player = (lv_ffmpeg_player_t *)obj; + player->auto_restart = en; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static lv_res_t decoder_info(lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header) +{ + /* Get the source type */ + lv_img_src_t src_type = lv_img_src_get_type(src); + + if(src_type == LV_IMG_SRC_FILE) { + const char * fn = src; + + if(ffmpeg_get_img_header(fn, header) < 0) { + LV_LOG_ERROR("ffmpeg can't get image header"); + return LV_RES_INV; + } + + return LV_RES_OK; + } + + /* If didn't succeeded earlier then it's an error */ + return LV_RES_INV; +} + +static lv_res_t decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc) +{ + if(dsc->src_type == LV_IMG_SRC_FILE) { + const char * path = dsc->src; + + struct ffmpeg_context_s * ffmpeg_ctx = ffmpeg_open_file(path); + + if(ffmpeg_ctx == NULL) { + return LV_RES_INV; + } + + if(ffmpeg_image_allocate(ffmpeg_ctx) < 0) { + LV_LOG_ERROR("ffmpeg image allocate failed"); + ffmpeg_close(ffmpeg_ctx); + return LV_RES_INV; + } + + if(ffmpeg_update_next_frame(ffmpeg_ctx) < 0) { + ffmpeg_close(ffmpeg_ctx); + LV_LOG_ERROR("ffmpeg update frame failed"); + return LV_RES_INV; + } + + ffmpeg_close_src_ctx(ffmpeg_ctx); + uint8_t * img_data = ffmpeg_get_img_data(ffmpeg_ctx); + +#if LV_COLOR_DEPTH != 32 + if(ffmpeg_ctx->has_alpha) { + convert_color_depth(img_data, dsc->header.w * dsc->header.h); + } +#endif + + dsc->user_data = ffmpeg_ctx; + dsc->img_data = img_data; + + /* The image is fully decoded. Return with its pointer */ + return LV_RES_OK; + } + + /* If not returned earlier then it failed */ + return LV_RES_INV; +} + +static void decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc) +{ + struct ffmpeg_context_s * ffmpeg_ctx = dsc->user_data; + ffmpeg_close(ffmpeg_ctx); +} + +#if LV_COLOR_DEPTH != 32 + +static void convert_color_depth(uint8_t * img, uint32_t px_cnt) +{ + lv_color32_t * img_src_p = (lv_color32_t *)img; + struct lv_img_pixel_color_s * img_dst_p = (struct lv_img_pixel_color_s *)img; + + for(uint32_t i = 0; i < px_cnt; i++) { + lv_color32_t temp = *img_src_p; + img_dst_p->c = lv_color_hex(temp.full); + img_dst_p->alpha = temp.ch.alpha; + + img_src_p++; + img_dst_p++; + } +} + +#endif + +static uint8_t * ffmpeg_get_img_data(struct ffmpeg_context_s * ffmpeg_ctx) +{ + uint8_t * img_data = ffmpeg_ctx->video_dst_data[0]; + + if(img_data == NULL) { + LV_LOG_ERROR("ffmpeg video dst data is NULL"); + } + + return img_data; +} + +static bool ffmpeg_pix_fmt_has_alpha(enum AVPixelFormat pix_fmt) +{ + const AVPixFmtDescriptor * desc = av_pix_fmt_desc_get(pix_fmt); + + if(desc == NULL) { + return false; + } + + if(pix_fmt == AV_PIX_FMT_PAL8) { + return true; + } + + return (desc->flags & AV_PIX_FMT_FLAG_ALPHA) ? true : false; +} + +static bool ffmpeg_pix_fmt_is_yuv(enum AVPixelFormat pix_fmt) +{ + const AVPixFmtDescriptor * desc = av_pix_fmt_desc_get(pix_fmt); + + if(desc == NULL) { + return false; + } + + return !(desc->flags & AV_PIX_FMT_FLAG_RGB) && desc->nb_components >= 2; +} + +static int ffmpeg_output_video_frame(struct ffmpeg_context_s * ffmpeg_ctx) +{ + int ret = -1; + + int width = ffmpeg_ctx->video_dec_ctx->width; + int height = ffmpeg_ctx->video_dec_ctx->height; + AVFrame * frame = ffmpeg_ctx->frame; + + if(frame->width != width + || frame->height != height + || frame->format != ffmpeg_ctx->video_dec_ctx->pix_fmt) { + + /* To handle this change, one could call av_image_alloc again and + * decode the following frames into another rawvideo file. + */ + LV_LOG_ERROR("Width, height and pixel format have to be " + "constant in a rawvideo file, but the width, height or " + "pixel format of the input video changed:\n" + "old: width = %d, height = %d, format = %s\n" + "new: width = %d, height = %d, format = %s\n", + width, + height, + av_get_pix_fmt_name(ffmpeg_ctx->video_dec_ctx->pix_fmt), + frame->width, frame->height, + av_get_pix_fmt_name(frame->format)); + goto failed; + } + + LV_LOG_TRACE("video_frame coded_n:%d", frame->coded_picture_number); + + /* copy decoded frame to destination buffer: + * this is required since rawvideo expects non aligned data + */ + av_image_copy(ffmpeg_ctx->video_src_data, ffmpeg_ctx->video_src_linesize, + (const uint8_t **)(frame->data), frame->linesize, + ffmpeg_ctx->video_dec_ctx->pix_fmt, width, height); + + if(ffmpeg_ctx->sws_ctx == NULL) { + int swsFlags = SWS_BILINEAR; + + if(ffmpeg_pix_fmt_is_yuv(ffmpeg_ctx->video_dec_ctx->pix_fmt)) { + + /* When the video width and height are not multiples of 8, + * and there is no size change in the conversion, + * a blurry screen will appear on the right side + * This problem was discovered in 2012 and + * continues to exist in version 4.1.3 in 2019 + * This problem can be avoided by increasing SWS_ACCURATE_RND + */ + if((width & 0x7) || (height & 0x7)) { + LV_LOG_WARN("The width(%d) and height(%d) the image " + "is not a multiple of 8, " + "the decoding speed may be reduced", + width, height); + swsFlags |= SWS_ACCURATE_RND; + } + } + + ffmpeg_ctx->sws_ctx = sws_getContext( + width, height, ffmpeg_ctx->video_dec_ctx->pix_fmt, + width, height, ffmpeg_ctx->video_dst_pix_fmt, + swsFlags, + NULL, NULL, NULL); + } + + if(!ffmpeg_ctx->has_alpha) { + int lv_linesize = sizeof(lv_color_t) * width; + int dst_linesize = ffmpeg_ctx->video_dst_linesize[0]; + if(dst_linesize != lv_linesize) { + LV_LOG_WARN("ffmpeg linesize = %d, but lvgl image require %d", + dst_linesize, + lv_linesize); + ffmpeg_ctx->video_dst_linesize[0] = lv_linesize; + } + } + + ret = sws_scale( + ffmpeg_ctx->sws_ctx, + (const uint8_t * const *)(ffmpeg_ctx->video_src_data), + ffmpeg_ctx->video_src_linesize, + 0, + height, + ffmpeg_ctx->video_dst_data, + ffmpeg_ctx->video_dst_linesize); + +failed: + return ret; +} + +static int ffmpeg_decode_packet(AVCodecContext * dec, const AVPacket * pkt, + struct ffmpeg_context_s * ffmpeg_ctx) +{ + int ret = 0; + + /* submit the packet to the decoder */ + ret = avcodec_send_packet(dec, pkt); + if(ret < 0) { + LV_LOG_ERROR("Error submitting a packet for decoding (%s)", + av_err2str(ret)); + return ret; + } + + /* get all the available frames from the decoder */ + while(ret >= 0) { + ret = avcodec_receive_frame(dec, ffmpeg_ctx->frame); + if(ret < 0) { + + /* those two return values are special and mean there is + * no output frame available, + * but there were no errors during decoding + */ + if(ret == AVERROR_EOF || ret == AVERROR(EAGAIN)) { + return 0; + } + + LV_LOG_ERROR("Error during decoding (%s)", av_err2str(ret)); + return ret; + } + + /* write the frame data to output file */ + if(dec->codec->type == AVMEDIA_TYPE_VIDEO) { + ret = ffmpeg_output_video_frame(ffmpeg_ctx); + } + + av_frame_unref(ffmpeg_ctx->frame); + if(ret < 0) { + LV_LOG_WARN("ffmpeg_decode_packet ended %d", ret); + return ret; + } + } + + return 0; +} + +static int ffmpeg_open_codec_context(int * stream_idx, + AVCodecContext ** dec_ctx, AVFormatContext * fmt_ctx, + enum AVMediaType type) +{ + int ret; + int stream_index; + AVStream * st; + AVCodec * dec = NULL; + AVDictionary * opts = NULL; + + ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0); + if(ret < 0) { + LV_LOG_ERROR("Could not find %s stream in input file", + av_get_media_type_string(type)); + return ret; + } + else { + stream_index = ret; + st = fmt_ctx->streams[stream_index]; + + /* find decoder for the stream */ + dec = avcodec_find_decoder(st->codecpar->codec_id); + if(dec == NULL) { + LV_LOG_ERROR("Failed to find %s codec", + av_get_media_type_string(type)); + return AVERROR(EINVAL); + } + + /* Allocate a codec context for the decoder */ + *dec_ctx = avcodec_alloc_context3(dec); + if(*dec_ctx == NULL) { + LV_LOG_ERROR("Failed to allocate the %s codec context", + av_get_media_type_string(type)); + return AVERROR(ENOMEM); + } + + /* Copy codec parameters from input stream to output codec context */ + if((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0) { + LV_LOG_ERROR( + "Failed to copy %s codec parameters to decoder context", + av_get_media_type_string(type)); + return ret; + } + + /* Init the decoders */ + if((ret = avcodec_open2(*dec_ctx, dec, &opts)) < 0) { + LV_LOG_ERROR("Failed to open %s codec", + av_get_media_type_string(type)); + return ret; + } + + *stream_idx = stream_index; + } + + return 0; +} + +static int ffmpeg_get_img_header(const char * filepath, + lv_img_header_t * header) +{ + int ret = -1; + + AVFormatContext * fmt_ctx = NULL; + AVCodecContext * video_dec_ctx = NULL; + int video_stream_idx; + + /* open input file, and allocate format context */ + if(avformat_open_input(&fmt_ctx, filepath, NULL, NULL) < 0) { + LV_LOG_ERROR("Could not open source file %s", filepath); + goto failed; + } + + /* retrieve stream information */ + if(avformat_find_stream_info(fmt_ctx, NULL) < 0) { + LV_LOG_ERROR("Could not find stream information"); + goto failed; + } + + if(ffmpeg_open_codec_context(&video_stream_idx, &video_dec_ctx, + fmt_ctx, AVMEDIA_TYPE_VIDEO) + >= 0) { + bool has_alpha = ffmpeg_pix_fmt_has_alpha(video_dec_ctx->pix_fmt); + + /* allocate image where the decoded image will be put */ + header->w = video_dec_ctx->width; + header->h = video_dec_ctx->height; + header->always_zero = 0; + header->cf = (has_alpha ? LV_IMG_CF_TRUE_COLOR_ALPHA : LV_IMG_CF_TRUE_COLOR); + + ret = 0; + } + +failed: + avcodec_free_context(&video_dec_ctx); + avformat_close_input(&fmt_ctx); + + return ret; +} + +static int ffmpeg_get_frame_refr_period(struct ffmpeg_context_s * ffmpeg_ctx) +{ + int avg_frame_rate_num = ffmpeg_ctx->video_stream->avg_frame_rate.num; + if(avg_frame_rate_num > 0) { + int period = 1000 * (int64_t)ffmpeg_ctx->video_stream->avg_frame_rate.den + / avg_frame_rate_num; + return period; + } + + return -1; +} + +static int ffmpeg_update_next_frame(struct ffmpeg_context_s * ffmpeg_ctx) +{ + int ret = 0; + + while(1) { + + /* read frames from the file */ + if(av_read_frame(ffmpeg_ctx->fmt_ctx, &(ffmpeg_ctx->pkt)) >= 0) { + bool is_image = false; + + /* check if the packet belongs to a stream we are interested in, + * otherwise skip it + */ + if(ffmpeg_ctx->pkt.stream_index == ffmpeg_ctx->video_stream_idx) { + ret = ffmpeg_decode_packet(ffmpeg_ctx->video_dec_ctx, + &(ffmpeg_ctx->pkt), ffmpeg_ctx); + is_image = true; + } + + av_packet_unref(&(ffmpeg_ctx->pkt)); + + if(ret < 0) { + LV_LOG_WARN("video frame is empty %d", ret); + break; + } + + /* Used to filter data that is not an image */ + if(is_image) { + break; + } + } + else { + ret = -1; + break; + } + } + + return ret; +} + +struct ffmpeg_context_s * ffmpeg_open_file(const char * path) +{ + if(path == NULL || strlen(path) == 0) { + LV_LOG_ERROR("file path is empty"); + return NULL; + } + + struct ffmpeg_context_s * ffmpeg_ctx = calloc(1, sizeof(struct ffmpeg_context_s)); + + if(ffmpeg_ctx == NULL) { + LV_LOG_ERROR("ffmpeg_ctx malloc failed"); + goto failed; + } + + /* open input file, and allocate format context */ + + if(avformat_open_input(&(ffmpeg_ctx->fmt_ctx), path, NULL, NULL) < 0) { + LV_LOG_ERROR("Could not open source file %s", path); + goto failed; + } + + /* retrieve stream information */ + + if(avformat_find_stream_info(ffmpeg_ctx->fmt_ctx, NULL) < 0) { + LV_LOG_ERROR("Could not find stream information"); + goto failed; + } + + if(ffmpeg_open_codec_context( + &(ffmpeg_ctx->video_stream_idx), + &(ffmpeg_ctx->video_dec_ctx), + ffmpeg_ctx->fmt_ctx, AVMEDIA_TYPE_VIDEO) + >= 0) { + ffmpeg_ctx->video_stream = ffmpeg_ctx->fmt_ctx->streams[ffmpeg_ctx->video_stream_idx]; + + ffmpeg_ctx->has_alpha = ffmpeg_pix_fmt_has_alpha(ffmpeg_ctx->video_dec_ctx->pix_fmt); + + ffmpeg_ctx->video_dst_pix_fmt = (ffmpeg_ctx->has_alpha ? AV_PIX_FMT_BGRA : AV_PIX_FMT_TRUE_COLOR); + } + +#if LV_FFMPEG_AV_DUMP_FORMAT != 0 + /* dump input information to stderr */ + av_dump_format(ffmpeg_ctx->fmt_ctx, 0, path, 0); +#endif + + if(ffmpeg_ctx->video_stream == NULL) { + LV_LOG_ERROR("Could not find video stream in the input, aborting"); + goto failed; + } + + return ffmpeg_ctx; + +failed: + ffmpeg_close(ffmpeg_ctx); + return NULL; +} + +static int ffmpeg_image_allocate(struct ffmpeg_context_s * ffmpeg_ctx) +{ + int ret; + + /* allocate image where the decoded image will be put */ + ret = av_image_alloc( + ffmpeg_ctx->video_src_data, + ffmpeg_ctx->video_src_linesize, + ffmpeg_ctx->video_dec_ctx->width, + ffmpeg_ctx->video_dec_ctx->height, + ffmpeg_ctx->video_dec_ctx->pix_fmt, + 4); + + if(ret < 0) { + LV_LOG_ERROR("Could not allocate src raw video buffer"); + return ret; + } + + LV_LOG_INFO("alloc video_src_bufsize = %d", ret); + + ret = av_image_alloc( + ffmpeg_ctx->video_dst_data, + ffmpeg_ctx->video_dst_linesize, + ffmpeg_ctx->video_dec_ctx->width, + ffmpeg_ctx->video_dec_ctx->height, + ffmpeg_ctx->video_dst_pix_fmt, + 4); + + if(ret < 0) { + LV_LOG_ERROR("Could not allocate dst raw video buffer"); + return ret; + } + + LV_LOG_INFO("allocate video_dst_bufsize = %d", ret); + + ffmpeg_ctx->frame = av_frame_alloc(); + + if(ffmpeg_ctx->frame == NULL) { + LV_LOG_ERROR("Could not allocate frame"); + return -1; + } + + /* initialize packet, set data to NULL, let the demuxer fill it */ + av_init_packet(&ffmpeg_ctx->pkt); + ffmpeg_ctx->pkt.data = NULL; + ffmpeg_ctx->pkt.size = 0; + + return 0; +} + +static void ffmpeg_close_src_ctx(struct ffmpeg_context_s * ffmpeg_ctx) +{ + avcodec_free_context(&(ffmpeg_ctx->video_dec_ctx)); + avformat_close_input(&(ffmpeg_ctx->fmt_ctx)); + av_frame_free(&(ffmpeg_ctx->frame)); + if(ffmpeg_ctx->video_src_data[0] != NULL) { + av_free(ffmpeg_ctx->video_src_data[0]); + ffmpeg_ctx->video_src_data[0] = NULL; + } +} + +static void ffmpeg_close_dst_ctx(struct ffmpeg_context_s * ffmpeg_ctx) +{ + if(ffmpeg_ctx->video_dst_data[0] != NULL) { + av_free(ffmpeg_ctx->video_dst_data[0]); + ffmpeg_ctx->video_dst_data[0] = NULL; + } +} + +static void ffmpeg_close(struct ffmpeg_context_s * ffmpeg_ctx) +{ + if(ffmpeg_ctx == NULL) { + LV_LOG_WARN("ffmpeg_ctx is NULL"); + return; + } + + sws_freeContext(ffmpeg_ctx->sws_ctx); + ffmpeg_close_src_ctx(ffmpeg_ctx); + ffmpeg_close_dst_ctx(ffmpeg_ctx); + free(ffmpeg_ctx); + + LV_LOG_INFO("ffmpeg_ctx closed"); +} + +static void lv_ffmpeg_player_frame_update_cb(lv_timer_t * timer) +{ + lv_obj_t * obj = (lv_obj_t *)timer->user_data; + lv_ffmpeg_player_t * player = (lv_ffmpeg_player_t *)obj; + + if(!player->ffmpeg_ctx) { + return; + } + + int has_next = ffmpeg_update_next_frame(player->ffmpeg_ctx); + + if(has_next < 0) { + lv_ffmpeg_player_set_cmd(obj, player->auto_restart ? LV_FFMPEG_PLAYER_CMD_START : LV_FFMPEG_PLAYER_CMD_STOP); + return; + } + +#if LV_COLOR_DEPTH != 32 + if(player->ffmpeg_ctx->has_alpha) { + convert_color_depth((uint8_t *)(player->imgdsc.data), + player->imgdsc.header.w * player->imgdsc.header.h); + } +#endif + + lv_img_cache_invalidate_src(lv_img_get_src(obj)); + lv_obj_invalidate(obj); +} + +static void lv_ffmpeg_player_constructor(const lv_obj_class_t * class_p, + lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + lv_ffmpeg_player_t * player = (lv_ffmpeg_player_t *)obj; + + player->auto_restart = false; + player->ffmpeg_ctx = NULL; + player->timer = lv_timer_create(lv_ffmpeg_player_frame_update_cb, + FRAME_DEF_REFR_PERIOD, obj); + lv_timer_pause(player->timer); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_ffmpeg_player_destructor(const lv_obj_class_t * class_p, + lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + lv_ffmpeg_player_t * player = (lv_ffmpeg_player_t *)obj; + + if(player->timer) { + lv_timer_del(player->timer); + player->timer = NULL; + } + + lv_img_cache_invalidate_src(lv_img_get_src(obj)); + + ffmpeg_close(player->ffmpeg_ctx); + player->ffmpeg_ctx = NULL; + + LV_TRACE_OBJ_CREATE("finished"); +} + +#endif /*LV_USE_FFMPEG*/ diff --git a/lib/lvgl/src/extra/libs/ffmpeg/lv_ffmpeg.h b/lib/lvgl/src/extra/libs/ffmpeg/lv_ffmpeg.h new file mode 100644 index 00000000..8c7fc26b --- /dev/null +++ b/lib/lvgl/src/extra/libs/ffmpeg/lv_ffmpeg.h @@ -0,0 +1,104 @@ +/** + * @file lv_ffmpeg.h + * + */ +#ifndef LV_FFMPEG_H +#define LV_FFMPEG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" +#if LV_USE_FFMPEG != 0 + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +struct ffmpeg_context_s; + +extern const lv_obj_class_t lv_ffmpeg_player_class; + +typedef struct { + lv_img_t img; + lv_timer_t * timer; + lv_img_dsc_t imgdsc; + bool auto_restart; + struct ffmpeg_context_s * ffmpeg_ctx; +} lv_ffmpeg_player_t; + +typedef enum { + LV_FFMPEG_PLAYER_CMD_START, + LV_FFMPEG_PLAYER_CMD_STOP, + LV_FFMPEG_PLAYER_CMD_PAUSE, + LV_FFMPEG_PLAYER_CMD_RESUME, + _LV_FFMPEG_PLAYER_CMD_LAST +} lv_ffmpeg_player_cmd_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Register FFMPEG image decoder + */ +void lv_ffmpeg_init(void); + +/** + * Get the number of frames contained in the file + * @param path image or video file name + * @return Number of frames, less than 0 means failed + */ +int lv_ffmpeg_get_frame_num(const char * path); + +/** + * Create ffmpeg_player object + * @param parent pointer to an object, it will be the parent of the new player + * @return pointer to the created ffmpeg_player + */ +lv_obj_t * lv_ffmpeg_player_create(lv_obj_t * parent); + +/** + * Set the path of the file to be played + * @param obj pointer to a ffmpeg_player object + * @param path video file path + * @return LV_RES_OK: no error; LV_RES_INV: can't get the info. + */ +lv_res_t lv_ffmpeg_player_set_src(lv_obj_t * obj, const char * path); + +/** + * Set command control video player + * @param obj pointer to a ffmpeg_player object + * @param cmd control commands + */ +void lv_ffmpeg_player_set_cmd(lv_obj_t * obj, lv_ffmpeg_player_cmd_t cmd); + +/** + * Set the video to automatically replay + * @param obj pointer to a ffmpeg_player object + * @param en true: enable the auto restart + */ +void lv_ffmpeg_player_set_auto_restart(lv_obj_t * obj, bool en); + +/*===================== + * Other functions + *====================*/ + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_FFMPEG*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_FFMPEG_H*/ diff --git a/lib/lvgl/src/extra/libs/freetype/arial.ttf b/lib/lvgl/src/extra/libs/freetype/arial.ttf Binary files differnew file mode 100644 index 00000000..886789b8 --- /dev/null +++ b/lib/lvgl/src/extra/libs/freetype/arial.ttf diff --git a/lib/lvgl/src/extra/libs/freetype/lv_freetype.c b/lib/lvgl/src/extra/libs/freetype/lv_freetype.c new file mode 100644 index 00000000..4bf66022 --- /dev/null +++ b/lib/lvgl/src/extra/libs/freetype/lv_freetype.c @@ -0,0 +1,687 @@ +/** + * @file lv_freetype.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_freetype.h" +#if LV_USE_FREETYPE + +#include "ft2build.h" +#include FT_FREETYPE_H +#include FT_GLYPH_H +#include FT_CACHE_H +#include FT_SIZES_H +#include FT_IMAGE_H +#include FT_OUTLINE_H + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_ll_t face_ll; +} lv_faces_control_t; + +typedef struct name_refer_t { + const char * name; /* point to font name string */ + int32_t cnt; /* reference count */ +} name_refer_t; + +typedef struct { + const void * mem; + const char * name; + size_t mem_size; +#if LV_FREETYPE_CACHE_SIZE < 0 + FT_Size size; +#endif + lv_font_t * font; + uint16_t style; + uint16_t height; +} lv_font_fmt_ft_dsc_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +#if LV_FREETYPE_CACHE_SIZE >= 0 +static FT_Error font_face_requester(FTC_FaceID face_id, + FT_Library library_is, FT_Pointer req_data, FT_Face * aface); +static bool lv_ft_font_init_cache(lv_ft_info_t * info); +static void lv_ft_font_destroy_cache(lv_font_t * font); +#else +static FT_Face face_find_in_list(lv_ft_info_t * info); +static void face_add_to_list(FT_Face face); +static void face_remove_from_list(FT_Face face); +static void face_generic_finalizer(void * object); +static bool lv_ft_font_init_nocache(lv_ft_info_t * info); +static void lv_ft_font_destroy_nocache(lv_font_t * font); +#endif + +static const char * name_refer_save(const char * name); +static void name_refer_del(const char * name); +static const char * name_refer_find(const char * name); + +/********************** +* STATIC VARIABLES +**********************/ +static FT_Library library; +static lv_ll_t names_ll; + +#if LV_FREETYPE_CACHE_SIZE >= 0 + static FTC_Manager cache_manager; + static FTC_CMapCache cmap_cache; + static FT_Face current_face = NULL; + + #if LV_FREETYPE_SBIT_CACHE + static FTC_SBitCache sbit_cache; + static FTC_SBit sbit; + #else + static FTC_ImageCache image_cache; + static FT_Glyph image_glyph; + #endif + +#else + static lv_faces_control_t face_control; +#endif + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +bool lv_freetype_init(uint16_t max_faces, uint16_t max_sizes, uint32_t max_bytes) +{ + FT_Error error = FT_Init_FreeType(&library); + if(error) { + LV_LOG_ERROR("init freeType error(%d)", error); + return false; + } + + _lv_ll_init(&names_ll, sizeof(name_refer_t)); + +#if LV_FREETYPE_CACHE_SIZE >= 0 + error = FTC_Manager_New(library, max_faces, max_sizes, + max_bytes, font_face_requester, NULL, &cache_manager); + if(error) { + FT_Done_FreeType(library); + LV_LOG_ERROR("Failed to open cache manager"); + return false; + } + + error = FTC_CMapCache_New(cache_manager, &cmap_cache); + if(error) { + LV_LOG_ERROR("Failed to open Cmap Cache"); + goto Fail; + } + +#if LV_FREETYPE_SBIT_CACHE + error = FTC_SBitCache_New(cache_manager, &sbit_cache); + if(error) { + LV_LOG_ERROR("Failed to open sbit cache"); + goto Fail; + } +#else + error = FTC_ImageCache_New(cache_manager, &image_cache); + if(error) { + LV_LOG_ERROR("Failed to open image cache"); + goto Fail; + } +#endif + + return true; +Fail: + FTC_Manager_Done(cache_manager); + FT_Done_FreeType(library); + return false; +#else + LV_UNUSED(max_faces); + LV_UNUSED(max_sizes); + LV_UNUSED(max_bytes); + _lv_ll_init(&face_control.face_ll, sizeof(FT_Face *)); + return true; +#endif/* LV_FREETYPE_CACHE_SIZE */ +} + +void lv_freetype_destroy(void) +{ +#if LV_FREETYPE_CACHE_SIZE >= 0 + FTC_Manager_Done(cache_manager); +#endif + FT_Done_FreeType(library); +} + +bool lv_ft_font_init(lv_ft_info_t * info) +{ +#if LV_FREETYPE_CACHE_SIZE >= 0 + return lv_ft_font_init_cache(info); +#else + return lv_ft_font_init_nocache(info); +#endif +} + +void lv_ft_font_destroy(lv_font_t * font) +{ +#if LV_FREETYPE_CACHE_SIZE >= 0 + lv_ft_font_destroy_cache(font); +#else + lv_ft_font_destroy_nocache(font); +#endif +} + +/********************** + * STATIC FUNCTIONS + **********************/ +#if LV_FREETYPE_CACHE_SIZE >= 0 + +static FT_Error font_face_requester(FTC_FaceID face_id, + FT_Library library_is, FT_Pointer req_data, FT_Face * aface) +{ + LV_UNUSED(library_is); + LV_UNUSED(req_data); + + lv_font_fmt_ft_dsc_t * dsc = (lv_font_fmt_ft_dsc_t *)face_id; + FT_Error error; + if(dsc->mem) { + error = FT_New_Memory_Face(library, dsc->mem, dsc->mem_size, 0, aface); + } + else { + error = FT_New_Face(library, dsc->name, 0, aface); + } + if(error) { + LV_LOG_ERROR("FT_New_Face error:%d\n", error); + return error; + } + return FT_Err_Ok; +} + +static bool get_bold_glyph(const lv_font_t * font, FT_Face face, + FT_UInt glyph_index, lv_font_glyph_dsc_t * dsc_out) +{ + if(FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT)) { + return false; + } + + lv_font_fmt_ft_dsc_t * dsc = (lv_font_fmt_ft_dsc_t *)(font->dsc); + if(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + if(dsc->style & FT_FONT_STYLE_BOLD) { + int strength = 1 << 6; + FT_Outline_Embolden(&face->glyph->outline, strength); + } + } + + if(FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL)) { + return false; + } + + dsc_out->adv_w = (face->glyph->metrics.horiAdvance >> 6); + dsc_out->box_h = face->glyph->bitmap.rows; /*Height of the bitmap in [px]*/ + dsc_out->box_w = face->glyph->bitmap.width; /*Width of the bitmap in [px]*/ + dsc_out->ofs_x = face->glyph->bitmap_left; /*X offset of the bitmap in [pf]*/ + dsc_out->ofs_y = face->glyph->bitmap_top - + face->glyph->bitmap.rows; /*Y offset of the bitmap measured from the as line*/ + dsc_out->bpp = 8; /*Bit per pixel: 1/2/4/8*/ + + return true; +} + +static bool get_glyph_dsc_cb_cache(const lv_font_t * font, + lv_font_glyph_dsc_t * dsc_out, uint32_t unicode_letter, uint32_t unicode_letter_next) +{ + LV_UNUSED(unicode_letter_next); + if(unicode_letter < 0x20) { + dsc_out->adv_w = 0; + dsc_out->box_h = 0; + dsc_out->box_w = 0; + dsc_out->ofs_x = 0; + dsc_out->ofs_y = 0; + dsc_out->bpp = 0; + return true; + } + + lv_font_fmt_ft_dsc_t * dsc = (lv_font_fmt_ft_dsc_t *)(font->dsc); + + FTC_FaceID face_id = (FTC_FaceID)dsc; + FT_Size face_size; + struct FTC_ScalerRec_ scaler; + scaler.face_id = face_id; + scaler.width = dsc->height; + scaler.height = dsc->height; + scaler.pixel = 1; + if(FTC_Manager_LookupSize(cache_manager, &scaler, &face_size) != 0) { + return false; + } + + FT_Face face = face_size->face; + FT_UInt charmap_index = FT_Get_Charmap_Index(face->charmap); + FT_UInt glyph_index = FTC_CMapCache_Lookup(cmap_cache, face_id, charmap_index, unicode_letter); + dsc_out->is_placeholder = glyph_index == 0; + + if(dsc->style & FT_FONT_STYLE_ITALIC) { + FT_Matrix italic_matrix; + italic_matrix.xx = 1 << 16; + italic_matrix.xy = 0x5800; + italic_matrix.yx = 0; + italic_matrix.yy = 1 << 16; + FT_Set_Transform(face, &italic_matrix, NULL); + } + + if(dsc->style & FT_FONT_STYLE_BOLD) { + current_face = face; + if(!get_bold_glyph(font, face, glyph_index, dsc_out)) { + current_face = NULL; + return false; + } + goto end; + } + + FTC_ImageTypeRec desc_type; + desc_type.face_id = face_id; + desc_type.flags = FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL; + desc_type.height = dsc->height; + desc_type.width = dsc->height; + +#if LV_FREETYPE_SBIT_CACHE + FT_Error error = FTC_SBitCache_Lookup(sbit_cache, &desc_type, glyph_index, &sbit, NULL); + if(error) { + LV_LOG_ERROR("SBitCache_Lookup error"); + return false; + } + + dsc_out->adv_w = sbit->xadvance; + dsc_out->box_h = sbit->height; /*Height of the bitmap in [px]*/ + dsc_out->box_w = sbit->width; /*Width of the bitmap in [px]*/ + dsc_out->ofs_x = sbit->left; /*X offset of the bitmap in [pf]*/ + dsc_out->ofs_y = sbit->top - sbit->height; /*Y offset of the bitmap measured from the as line*/ + dsc_out->bpp = 8; /*Bit per pixel: 1/2/4/8*/ +#else + FT_Error error = FTC_ImageCache_Lookup(image_cache, &desc_type, glyph_index, &image_glyph, NULL); + if(error) { + LV_LOG_ERROR("ImageCache_Lookup error"); + return false; + } + if(image_glyph->format != FT_GLYPH_FORMAT_BITMAP) { + LV_LOG_ERROR("Glyph_To_Bitmap error"); + return false; + } + + FT_BitmapGlyph glyph_bitmap = (FT_BitmapGlyph)image_glyph; + dsc_out->adv_w = (glyph_bitmap->root.advance.x >> 16); + dsc_out->box_h = glyph_bitmap->bitmap.rows; /*Height of the bitmap in [px]*/ + dsc_out->box_w = glyph_bitmap->bitmap.width; /*Width of the bitmap in [px]*/ + dsc_out->ofs_x = glyph_bitmap->left; /*X offset of the bitmap in [pf]*/ + dsc_out->ofs_y = glyph_bitmap->top - + glyph_bitmap->bitmap.rows; /*Y offset of the bitmap measured from the as line*/ + dsc_out->bpp = 8; /*Bit per pixel: 1/2/4/8*/ +#endif + +end: + if((dsc->style & FT_FONT_STYLE_ITALIC) && (unicode_letter_next == '\0')) { + dsc_out->adv_w = dsc_out->box_w + dsc_out->ofs_x; + } + + return true; +} + +static const uint8_t * get_glyph_bitmap_cb_cache(const lv_font_t * font, uint32_t unicode_letter) +{ + LV_UNUSED(unicode_letter); + + lv_font_fmt_ft_dsc_t * dsc = (lv_font_fmt_ft_dsc_t *)(font->dsc); + if(dsc->style & FT_FONT_STYLE_BOLD) { + if(current_face && current_face->glyph->format == FT_GLYPH_FORMAT_BITMAP) { + return (const uint8_t *)(current_face->glyph->bitmap.buffer); + } + return NULL; + } + +#if LV_FREETYPE_SBIT_CACHE + return (const uint8_t *)sbit->buffer; +#else + FT_BitmapGlyph glyph_bitmap = (FT_BitmapGlyph)image_glyph; + return (const uint8_t *)glyph_bitmap->bitmap.buffer; +#endif +} + +static bool lv_ft_font_init_cache(lv_ft_info_t * info) +{ + size_t need_size = sizeof(lv_font_fmt_ft_dsc_t) + sizeof(lv_font_t); + lv_font_fmt_ft_dsc_t * dsc = lv_mem_alloc(need_size); + if(dsc == NULL) return false; + lv_memset_00(dsc, need_size); + + dsc->font = (lv_font_t *)(((char *)dsc) + sizeof(lv_font_fmt_ft_dsc_t)); + dsc->mem = info->mem; + dsc->mem_size = info->mem_size; + dsc->name = name_refer_save(info->name); + dsc->height = info->weight; + dsc->style = info->style; + + /* use to get font info */ + FT_Size face_size; + struct FTC_ScalerRec_ scaler; + scaler.face_id = (FTC_FaceID)dsc; + scaler.width = info->weight; + scaler.height = info->weight; + scaler.pixel = 1; + FT_Error error = FTC_Manager_LookupSize(cache_manager, &scaler, &face_size); + if(error) { + LV_LOG_ERROR("Failed to LookupSize"); + goto Fail; + } + + lv_font_t * font = dsc->font; + font->dsc = dsc; + font->get_glyph_dsc = get_glyph_dsc_cb_cache; + font->get_glyph_bitmap = get_glyph_bitmap_cb_cache; + font->subpx = LV_FONT_SUBPX_NONE; + font->line_height = (face_size->face->size->metrics.height >> 6); + font->base_line = -(face_size->face->size->metrics.descender >> 6); + + FT_Fixed scale = face_size->face->size->metrics.y_scale; + int8_t thickness = FT_MulFix(scale, face_size->face->underline_thickness) >> 6; + font->underline_position = FT_MulFix(scale, face_size->face->underline_position) >> 6; + font->underline_thickness = thickness < 1 ? 1 : thickness; + + /* return to user */ + info->font = font; + + return true; + +Fail: + lv_mem_free(dsc); + return false; +} + +void lv_ft_font_destroy_cache(lv_font_t * font) +{ + if(font == NULL) { + return; + } + + lv_font_fmt_ft_dsc_t * dsc = (lv_font_fmt_ft_dsc_t *)(font->dsc); + if(dsc) { + FTC_Manager_RemoveFaceID(cache_manager, (FTC_FaceID)dsc); + name_refer_del(dsc->name); + lv_mem_free(dsc); + } +} +#else/* LV_FREETYPE_CACHE_SIZE */ + +static FT_Face face_find_in_list(lv_ft_info_t * info) +{ + lv_font_fmt_ft_dsc_t * dsc; + FT_Face * pface = _lv_ll_get_head(&face_control.face_ll); + while(pface) { + dsc = (lv_font_fmt_ft_dsc_t *)(*pface)->generic.data; + if(strcmp(dsc->name, info->name) == 0) { + return *pface; + } + pface = _lv_ll_get_next(&face_control.face_ll, pface); + } + + return NULL; +} + +static void face_add_to_list(FT_Face face) +{ + FT_Face * pface; + pface = (FT_Face *)_lv_ll_ins_tail(&face_control.face_ll); + *pface = face; +} + +static void face_remove_from_list(FT_Face face) +{ + FT_Face * pface = _lv_ll_get_head(&face_control.face_ll); + while(pface) { + if(*pface == face) { + _lv_ll_remove(&face_control.face_ll, pface); + lv_mem_free(pface); + break; + } + pface = _lv_ll_get_next(&face_control.face_ll, pface); + } +} + +static void face_generic_finalizer(void * object) +{ + FT_Face face = (FT_Face)object; + face_remove_from_list(face); + LV_LOG_INFO("face finalizer(%p)\n", face); +} + +static bool get_glyph_dsc_cb_nocache(const lv_font_t * font, + lv_font_glyph_dsc_t * dsc_out, uint32_t unicode_letter, uint32_t unicode_letter_next) +{ + LV_UNUSED(unicode_letter_next); + if(unicode_letter < 0x20) { + dsc_out->adv_w = 0; + dsc_out->box_h = 0; + dsc_out->box_w = 0; + dsc_out->ofs_x = 0; + dsc_out->ofs_y = 0; + dsc_out->bpp = 0; + return true; + } + + FT_Error error; + lv_font_fmt_ft_dsc_t * dsc = (lv_font_fmt_ft_dsc_t *)(font->dsc); + FT_Face face = dsc->size->face; + + FT_UInt glyph_index = FT_Get_Char_Index(face, unicode_letter); + + if(face->size != dsc->size) { + FT_Activate_Size(dsc->size); + } + dsc_out->is_placeholder = glyph_index == 0; + + error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT); + if(error) { + return false; + } + + if(face->glyph->format == FT_GLYPH_FORMAT_OUTLINE) { + if(dsc->style & FT_FONT_STYLE_BOLD) { + int strength = 1 << 6; + FT_Outline_Embolden(&face->glyph->outline, strength); + } + + if(dsc->style & FT_FONT_STYLE_ITALIC) { + FT_Matrix italic_matrix; + italic_matrix.xx = 1 << 16; + italic_matrix.xy = 0x5800; + italic_matrix.yx = 0; + italic_matrix.yy = 1 << 16; + FT_Outline_Transform(&face->glyph->outline, &italic_matrix); + } + } + + error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL); + if(error) { + return false; + } + + dsc_out->adv_w = (face->glyph->metrics.horiAdvance >> 6); + dsc_out->box_h = face->glyph->bitmap.rows; /*Height of the bitmap in [px]*/ + dsc_out->box_w = face->glyph->bitmap.width; /*Width of the bitmap in [px]*/ + dsc_out->ofs_x = face->glyph->bitmap_left; /*X offset of the bitmap in [pf]*/ + dsc_out->ofs_y = face->glyph->bitmap_top - + face->glyph->bitmap.rows; /*Y offset of the bitmap measured from the as line*/ + dsc_out->bpp = 8; /*Bit per pixel: 1/2/4/8*/ + + if((dsc->style & FT_FONT_STYLE_ITALIC) && (unicode_letter_next == '\0')) { + dsc_out->adv_w = dsc_out->box_w + dsc_out->ofs_x; + } + + return true; +} + +static const uint8_t * get_glyph_bitmap_cb_nocache(const lv_font_t * font, uint32_t unicode_letter) +{ + LV_UNUSED(unicode_letter); + lv_font_fmt_ft_dsc_t * dsc = (lv_font_fmt_ft_dsc_t *)(font->dsc); + FT_Face face = dsc->size->face; + return (const uint8_t *)(face->glyph->bitmap.buffer); +} + +static bool lv_ft_font_init_nocache(lv_ft_info_t * info) +{ + size_t need_size = sizeof(lv_font_fmt_ft_dsc_t) + sizeof(lv_font_t); + lv_font_fmt_ft_dsc_t * dsc = lv_mem_alloc(need_size); + if(dsc == NULL) return false; + lv_memset_00(dsc, need_size); + + dsc->font = (lv_font_t *)(((char *)dsc) + sizeof(lv_font_fmt_ft_dsc_t)); + dsc->mem = info->mem; + dsc->mem_size = info->mem_size; + dsc->name = name_refer_save(info->name); + dsc->height = info->weight; + dsc->style = info->style; + + FT_Face face = face_find_in_list(info); + if(face == NULL) { + FT_Error error; + if(dsc->mem) { + error = FT_New_Memory_Face(library, dsc->mem, (FT_Long) dsc->mem_size, 0, &face); + } + else { + error = FT_New_Face(library, dsc->name, 0, &face); + } + if(error) { + LV_LOG_WARN("create face error(%d)", error); + goto Fail; + } + + /* link face and face info */ + face->generic.data = dsc; + face->generic.finalizer = face_generic_finalizer; + face_add_to_list(face); + } + else { + FT_Size size; + FT_Error error = FT_New_Size(face, &size); + if(error) { + goto Fail; + } + FT_Activate_Size(size); + FT_Reference_Face(face); + } + + FT_Set_Pixel_Sizes(face, 0, info->weight); + dsc->size = face->size; + + lv_font_t * font = dsc->font; + font->dsc = dsc; + font->get_glyph_dsc = get_glyph_dsc_cb_nocache; + font->get_glyph_bitmap = get_glyph_bitmap_cb_nocache; + font->line_height = (face->size->metrics.height >> 6); + font->base_line = -(face->size->metrics.descender >> 6); + font->subpx = LV_FONT_SUBPX_NONE; + + FT_Fixed scale = face->size->metrics.y_scale; + int8_t thickness = FT_MulFix(scale, face->underline_thickness) >> 6; + font->underline_position = FT_MulFix(scale, face->underline_position) >> 6; + font->underline_thickness = thickness < 1 ? 1 : thickness; + + info->font = font; + return true; + +Fail: + lv_mem_free(dsc); + return false; +} + +static void lv_ft_font_destroy_nocache(lv_font_t * font) +{ + if(font == NULL) { + return; + } + + lv_font_fmt_ft_dsc_t * dsc = (lv_font_fmt_ft_dsc_t *)(font->dsc); + if(dsc) { + FT_Face face = dsc->size->face; + FT_Done_Size(dsc->size); + FT_Done_Face(face); + name_refer_del(dsc->name); + lv_mem_free(dsc); + } +} + +#endif/* LV_FREETYPE_CACHE_SIZE */ + +/** + * find name string in names list.name string cnt += 1 if find. + * @param name name string + * @return the string pointer of name. + */ +static const char * name_refer_find(const char * name) +{ + name_refer_t * refer = _lv_ll_get_head(&names_ll); + while(refer) { + if(strcmp(refer->name, name) == 0) { + refer->cnt += 1; + return refer->name; + } + refer = _lv_ll_get_next(&names_ll, refer); + } + return NULL; +} + +/** + * del name string from list. + */ +static void name_refer_del(const char * name) +{ + name_refer_t * refer = _lv_ll_get_head(&names_ll); + while(refer) { + if(strcmp(refer->name, name) == 0) { + refer->cnt -= 1; + if(refer->cnt <= 0) { + _lv_ll_remove(&names_ll, refer); + lv_mem_free((void *)refer->name); + lv_mem_free(refer); + } + return; + } + refer = _lv_ll_get_next(&names_ll, refer); + } + + LV_LOG_WARN("name_in_names_del error(not find:%p).", name); +} + +/** + * save name string to list. + * @param name name string + * @return Saved string pointer + */ +static const char * name_refer_save(const char * name) +{ + const char * pos = name_refer_find(name); + if(pos) { + return pos; + } + + name_refer_t * refer = _lv_ll_ins_tail(&names_ll); + if(refer) { + uint32_t len = strlen(name) + 1; + refer->name = lv_mem_alloc(len); + if(refer->name) { + lv_memcpy((void *)refer->name, name, len); + refer->cnt = 1; + return refer->name; + } + _lv_ll_remove(&names_ll, refer); + lv_mem_free(refer); + } + LV_LOG_WARN("save_name_to_names error(not memory)."); + return ""; +} + +#endif /*LV_USE_FREETYPE*/ diff --git a/lib/lvgl/src/extra/libs/freetype/lv_freetype.h b/lib/lvgl/src/extra/libs/freetype/lv_freetype.h new file mode 100644 index 00000000..247a7fb7 --- /dev/null +++ b/lib/lvgl/src/extra/libs/freetype/lv_freetype.h @@ -0,0 +1,83 @@ +/** + * @file lv_freetype.h + * + */ +#ifndef LV_FREETYPE_H +#define LV_FREETYPE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" +#if LV_USE_FREETYPE + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef enum { + FT_FONT_STYLE_NORMAL = 0, + FT_FONT_STYLE_ITALIC = 1 << 0, + FT_FONT_STYLE_BOLD = 1 << 1 +} LV_FT_FONT_STYLE; + +typedef struct { + const char * name; /* The name of the font file */ + const void * mem; /* The pointer of the font file */ + size_t mem_size; /* The size of the memory */ + lv_font_t * font; /* point to lvgl font */ + uint16_t weight; /* font size */ + uint16_t style; /* font style */ +} lv_ft_info_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * init freetype library + * @param max_faces Maximum number of opened FT_Face objects managed by this cache instance. Use 0 for defaults. + * @param max_sizes Maximum number of opened FT_Size objects managed by this cache instance. Use 0 for defaults. + * @param max_bytes Maximum number of bytes to use for cached data nodes. Use 0 for defaults. + * Note that this value does not account for managed FT_Face and FT_Size objects. + * @return true on success, otherwise false. + */ +bool lv_freetype_init(uint16_t max_faces, uint16_t max_sizes, uint32_t max_bytes); + +/** + * Destroy freetype library + */ +void lv_freetype_destroy(void); + +/** + * Creates a font with info parameter specified. + * @param info See lv_ft_info_t for details. + * when success, lv_ft_info_t->font point to the font you created. + * @return true on success, otherwise false. + */ +bool lv_ft_font_init(lv_ft_info_t * info); + +/** + * Destroy a font that has been created. + * @param font pointer to font. + */ +void lv_ft_font_destroy(lv_font_t * font); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_FREETYPE*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* LV_FREETYPE_H */ diff --git a/lib/lvgl/src/extra/libs/fsdrv/lv_fs_fatfs.c b/lib/lvgl/src/extra/libs/fsdrv/lv_fs_fatfs.c new file mode 100644 index 00000000..cc1d2e6d --- /dev/null +++ b/lib/lvgl/src/extra/libs/fsdrv/lv_fs_fatfs.c @@ -0,0 +1,290 @@ +/** + * @file lv_fs_fatfs.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_FS_FATFS +#include "ff.h" + +/********************* + * DEFINES + *********************/ + +#if LV_FS_FATFS_LETTER == '\0' + #error "LV_FS_FATFS_LETTER must be an upper case ASCII letter" +#endif + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void fs_init(void); + +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode); +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p); +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br); +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw); +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence); +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p); +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path); +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn); +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_fs_fatfs_init(void) +{ + /*---------------------------------------------------- + * Initialize your storage device and File System + * -------------------------------------------------*/ + fs_init(); + + /*--------------------------------------------------- + * Register the file system interface in LVGL + *--------------------------------------------------*/ + + /*Add a simple drive to open images*/ + static lv_fs_drv_t fs_drv; /*A driver descriptor*/ + lv_fs_drv_init(&fs_drv); + + /*Set up fields...*/ + fs_drv.letter = LV_FS_FATFS_LETTER; + fs_drv.cache_size = LV_FS_FATFS_CACHE_SIZE; + + fs_drv.open_cb = fs_open; + fs_drv.close_cb = fs_close; + fs_drv.read_cb = fs_read; + fs_drv.write_cb = fs_write; + fs_drv.seek_cb = fs_seek; + fs_drv.tell_cb = fs_tell; + + fs_drv.dir_close_cb = fs_dir_close; + fs_drv.dir_open_cb = fs_dir_open; + fs_drv.dir_read_cb = fs_dir_read; + + lv_fs_drv_register(&fs_drv); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/*Initialize your Storage device and File system.*/ +static void fs_init(void) +{ + /*Initialize the SD card and FatFS itself. + *Better to do it in your code to keep this library untouched for easy updating*/ +} + +/** + * Open a file + * @param drv pointer to a driver where this function belongs + * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt) + * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR + * @return pointer to FIL struct or NULL in case of fail + */ +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode) +{ + LV_UNUSED(drv); + uint8_t flags = 0; + + if(mode == LV_FS_MODE_WR) flags = FA_WRITE | FA_OPEN_ALWAYS; + else if(mode == LV_FS_MODE_RD) flags = FA_READ; + else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = FA_READ | FA_WRITE | FA_OPEN_ALWAYS; + + FIL * f = lv_mem_alloc(sizeof(FIL)); + if(f == NULL) return NULL; + + FRESULT res = f_open(f, path, flags); + if(res == FR_OK) { + return f; + } + else { + lv_mem_free(f); + return NULL; + } +} + +/** + * Close an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FIL variable. (opened with fs_open) + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p) +{ + LV_UNUSED(drv); + f_close(file_p); + lv_mem_free(file_p); + return LV_FS_RES_OK; +} + +/** + * Read data from an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FIL variable. + * @param buf pointer to a memory block where to store the read data + * @param btr number of Bytes To Read + * @param br the real number of read bytes (Byte Read) + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br) +{ + LV_UNUSED(drv); + FRESULT res = f_read(file_p, buf, btr, (UINT *)br); + if(res == FR_OK) return LV_FS_RES_OK; + else return LV_FS_RES_UNKNOWN; +} + +/** + * Write into a file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FIL variable + * @param buf pointer to a buffer with the bytes to write + * @param btw Bytes To Write + * @param bw the number of real written bytes (Bytes Written). NULL if unused. + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw) +{ + LV_UNUSED(drv); + FRESULT res = f_write(file_p, buf, btw, (UINT *)bw); + if(res == FR_OK) return LV_FS_RES_OK; + else return LV_FS_RES_UNKNOWN; +} + +/** + * Set the read write pointer. Also expand the file size if necessary. + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FIL variable. (opened with fs_open ) + * @param pos the new position of read write pointer + * @param whence only LV_SEEK_SET is supported + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence) +{ + LV_UNUSED(drv); + switch(whence) { + case LV_FS_SEEK_SET: + f_lseek(file_p, pos); + break; + case LV_FS_SEEK_CUR: + f_lseek(file_p, f_tell((FIL *)file_p) + pos); + break; + case LV_FS_SEEK_END: + f_lseek(file_p, f_size((FIL *)file_p) + pos); + break; + default: + break; + } + return LV_FS_RES_OK; +} + +/** + * Give the position of the read write pointer + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FIL variable. + * @param pos_p pointer to to store the result + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p) +{ + LV_UNUSED(drv); + *pos_p = f_tell((FIL *)file_p); + return LV_FS_RES_OK; +} + +/** + * Initialize a 'DIR' variable for directory reading + * @param drv pointer to a driver where this function belongs + * @param path path to a directory + * @return pointer to an initialized 'DIR' variable + */ +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path) +{ + LV_UNUSED(drv); + DIR * d = lv_mem_alloc(sizeof(DIR)); + if(d == NULL) return NULL; + + FRESULT res = f_opendir(d, path); + if(res != FR_OK) { + lv_mem_free(d); + d = NULL; + } + return d; +} + +/** + * Read the next filename from a directory. + * The name of the directories will begin with '/' + * @param drv pointer to a driver where this function belongs + * @param dir_p pointer to an initialized 'DIR' variable + * @param fn pointer to a buffer to store the filename + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn) +{ + LV_UNUSED(drv); + FRESULT res; + FILINFO fno; + fn[0] = '\0'; + + do { + res = f_readdir(dir_p, &fno); + if(res != FR_OK) return LV_FS_RES_UNKNOWN; + + if(fno.fattrib & AM_DIR) { + fn[0] = '/'; + strcpy(&fn[1], fno.fname); + } + else strcpy(fn, fno.fname); + + } while(strcmp(fn, "/.") == 0 || strcmp(fn, "/..") == 0); + + return LV_FS_RES_OK; +} + +/** + * Close the directory reading + * @param drv pointer to a driver where this function belongs + * @param dir_p pointer to an initialized 'DIR' variable + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p) +{ + LV_UNUSED(drv); + f_closedir(dir_p); + lv_mem_free(dir_p); + return LV_FS_RES_OK; +} + +#else /*LV_USE_FS_FATFS == 0*/ + +#if defined(LV_FS_FATFS_LETTER) && LV_FS_FATFS_LETTER != '\0' + #warning "LV_USE_FS_FATFS is not enabled but LV_FS_FATFS_LETTER is set" +#endif + +#endif /*LV_USE_FS_POSIX*/ + diff --git a/lib/lvgl/src/extra/libs/fsdrv/lv_fs_posix.c b/lib/lvgl/src/extra/libs/fsdrv/lv_fs_posix.c new file mode 100644 index 00000000..f988daee --- /dev/null +++ b/lib/lvgl/src/extra/libs/fsdrv/lv_fs_posix.c @@ -0,0 +1,319 @@ +/** + * @file lv_fs_posix.c + * + */ + + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_FS_POSIX + +#include <fcntl.h> +#include <stdio.h> +#ifndef WIN32 + #include <dirent.h> + #include <unistd.h> +#else + #include <windows.h> +#endif + +/********************* + * DEFINES + *********************/ + +#if LV_FS_POSIX_LETTER == '\0' + #error "LV_FS_POSIX_LETTER must be an upper case ASCII letter" +#endif + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode); +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p); +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br); +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw); +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence); +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p); +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path); +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn); +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Register a driver for the File system interface + */ +void lv_fs_posix_init(void) +{ + /*--------------------------------------------------- + * Register the file system interface in LVGL + *--------------------------------------------------*/ + + /*Add a simple drive to open images*/ + static lv_fs_drv_t fs_drv; /*A driver descriptor*/ + lv_fs_drv_init(&fs_drv); + + /*Set up fields...*/ + fs_drv.letter = LV_FS_POSIX_LETTER; + fs_drv.cache_size = LV_FS_POSIX_CACHE_SIZE; + + fs_drv.open_cb = fs_open; + fs_drv.close_cb = fs_close; + fs_drv.read_cb = fs_read; + fs_drv.write_cb = fs_write; + fs_drv.seek_cb = fs_seek; + fs_drv.tell_cb = fs_tell; + + fs_drv.dir_close_cb = fs_dir_close; + fs_drv.dir_open_cb = fs_dir_open; + fs_drv.dir_read_cb = fs_dir_read; + + lv_fs_drv_register(&fs_drv); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/** + * Open a file + * @param drv pointer to a driver where this function belongs + * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt) + * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR + * @return a file handle or -1 in case of fail + */ +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode) +{ + LV_UNUSED(drv); + + uint32_t flags = 0; + if(mode == LV_FS_MODE_WR) flags = O_WRONLY; + else if(mode == LV_FS_MODE_RD) flags = O_RDONLY; + else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = O_RDWR; + + /*Make the path relative to the current directory (the projects root folder)*/ + char buf[256]; + lv_snprintf(buf, sizeof(buf), LV_FS_POSIX_PATH "%s", path); + + int f = open(buf, flags); + if(f < 0) return NULL; + + return (void *)(lv_uintptr_t)f; +} + +/** + * Close an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p a file handle. (opened with fs_open) + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p) +{ + LV_UNUSED(drv); + close((lv_uintptr_t)file_p); + return LV_FS_RES_OK; +} + +/** + * Read data from an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p a file handle variable. + * @param buf pointer to a memory block where to store the read data + * @param btr number of Bytes To Read + * @param br the real number of read bytes (Byte Read) + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br) +{ + LV_UNUSED(drv); + *br = read((lv_uintptr_t)file_p, buf, btr); + return (int32_t)(*br) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; +} + +/** + * Write into a file + * @param drv pointer to a driver where this function belongs + * @param file_p a file handle variable + * @param buf pointer to a buffer with the bytes to write + * @param btw Bytes To Write + * @param bw the number of real written bytes (Bytes Written). NULL if unused. + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw) +{ + LV_UNUSED(drv); + *bw = write((lv_uintptr_t)file_p, buf, btw); + return (int32_t)(*bw) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; +} + +/** + * Set the read write pointer. Also expand the file size if necessary. + * @param drv pointer to a driver where this function belongs + * @param file_p a file handle variable. (opened with fs_open ) + * @param pos the new position of read write pointer + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence) +{ + LV_UNUSED(drv); + off_t offset = lseek((lv_uintptr_t)file_p, pos, whence); + return offset < 0 ? LV_FS_RES_FS_ERR : LV_FS_RES_OK; +} + +/** + * Give the position of the read write pointer + * @param drv pointer to a driver where this function belongs + * @param file_p a file handle variable. + * @param pos_p pointer to to store the result + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p) +{ + LV_UNUSED(drv); + off_t offset = lseek((lv_uintptr_t)file_p, 0, SEEK_CUR); + *pos_p = offset; + return offset < 0 ? LV_FS_RES_FS_ERR : LV_FS_RES_OK; +} + +#ifdef WIN32 + static char next_fn[256]; +#endif + +/** + * Initialize a 'fs_read_dir_t' variable for directory reading + * @param drv pointer to a driver where this function belongs + * @param path path to a directory + * @return pointer to an initialized 'DIR' or 'HANDLE' variable + */ +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path) +{ + LV_UNUSED(drv); + +#ifndef WIN32 + /*Make the path relative to the current directory (the projects root folder)*/ + char buf[256]; + lv_snprintf(buf, sizeof(buf), LV_FS_POSIX_PATH "%s", path); + return opendir(buf); +#else + HANDLE d = INVALID_HANDLE_VALUE; + WIN32_FIND_DATA fdata; + + /*Make the path relative to the current directory (the projects root folder)*/ + char buf[256]; + lv_snprintf(buf, sizeof(buf), LV_FS_POSIX_PATH "%s\\*", path); + + strcpy(next_fn, ""); + d = FindFirstFile(buf, &fdata); + do { + if(strcmp(fdata.cFileName, ".") == 0 || strcmp(fdata.cFileName, "..") == 0) { + continue; + } + else { + if(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + sprintf(next_fn, "/%s", fdata.cFileName); + } + else { + sprintf(next_fn, "%s", fdata.cFileName); + } + break; + } + } while(FindNextFileA(d, &fdata)); + + return d; +#endif +} + +/** + * Read the next filename from a directory. + * The name of the directories will begin with '/' + * @param drv pointer to a driver where this function belongs + * @param dir_p pointer to an initialized 'DIR' or 'HANDLE' variable + * @param fn pointer to a buffer to store the filename + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn) +{ + LV_UNUSED(drv); + +#ifndef WIN32 + struct dirent * entry; + do { + entry = readdir(dir_p); + if(entry) { + if(entry->d_type == DT_DIR) sprintf(fn, "/%s", entry->d_name); + else strcpy(fn, entry->d_name); + } + else { + strcpy(fn, ""); + } + } while(strcmp(fn, "/.") == 0 || strcmp(fn, "/..") == 0); +#else + strcpy(fn, next_fn); + + strcpy(next_fn, ""); + WIN32_FIND_DATA fdata; + + if(FindNextFile(dir_p, &fdata) == false) return LV_FS_RES_OK; + do { + if(strcmp(fdata.cFileName, ".") == 0 || strcmp(fdata.cFileName, "..") == 0) { + continue; + } + else { + if(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + sprintf(next_fn, "/%s", fdata.cFileName); + } + else { + sprintf(next_fn, "%s", fdata.cFileName); + } + break; + } + } while(FindNextFile(dir_p, &fdata)); + +#endif + return LV_FS_RES_OK; +} + +/** + * Close the directory reading + * @param drv pointer to a driver where this function belongs + * @param dir_p pointer to an initialized 'DIR' or 'HANDLE' variable + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p) +{ + LV_UNUSED(drv); +#ifndef WIN32 + closedir(dir_p); +#else + FindClose(dir_p); +#endif + return LV_FS_RES_OK; +} +#else /*LV_USE_FS_POSIX == 0*/ + +#if defined(LV_FS_POSIX_LETTER) && LV_FS_POSIX_LETTER != '\0' + #warning "LV_USE_FS_POSIX is not enabled but LV_FS_POSIX_LETTER is set" +#endif + +#endif /*LV_USE_FS_POSIX*/ diff --git a/lib/lvgl/src/extra/libs/fsdrv/lv_fs_stdio.c b/lib/lvgl/src/extra/libs/fsdrv/lv_fs_stdio.c new file mode 100644 index 00000000..c2de6880 --- /dev/null +++ b/lib/lvgl/src/extra/libs/fsdrv/lv_fs_stdio.c @@ -0,0 +1,329 @@ +/** + * @file lv_fs_stdio.c + * + */ + + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" +#if LV_USE_FS_STDIO != '\0' + +#include <stdio.h> +#ifndef WIN32 + #include <dirent.h> + #include <unistd.h> +#else + #include <windows.h> +#endif + +/********************* + * DEFINES + *********************/ +#define MAX_PATH_LEN 256 + +/********************** + * TYPEDEFS + **********************/ +typedef struct { +#ifdef WIN32 + HANDLE dir_p; + char next_fn[MAX_PATH_LEN]; +#else + DIR * dir_p; +#endif +} dir_handle_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode); +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p); +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br); +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw); +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence); +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p); +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path); +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn); +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Register a driver for the File system interface + */ +void lv_fs_stdio_init(void) +{ + /*--------------------------------------------------- + * Register the file system interface in LVGL + *--------------------------------------------------*/ + + /*Add a simple drive to open images*/ + static lv_fs_drv_t fs_drv; /*A driver descriptor*/ + lv_fs_drv_init(&fs_drv); + + /*Set up fields...*/ + fs_drv.letter = LV_FS_STDIO_LETTER; + fs_drv.cache_size = LV_FS_STDIO_CACHE_SIZE; + + fs_drv.open_cb = fs_open; + fs_drv.close_cb = fs_close; + fs_drv.read_cb = fs_read; + fs_drv.write_cb = fs_write; + fs_drv.seek_cb = fs_seek; + fs_drv.tell_cb = fs_tell; + + fs_drv.dir_close_cb = fs_dir_close; + fs_drv.dir_open_cb = fs_dir_open; + fs_drv.dir_read_cb = fs_dir_read; + + lv_fs_drv_register(&fs_drv); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/** + * Open a file + * @param drv pointer to a driver where this function belongs + * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt) + * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR + * @return pointer to FIL struct or NULL in case of fail + */ +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode) +{ + LV_UNUSED(drv); + + const char * flags = ""; + + if(mode == LV_FS_MODE_WR) flags = "wb"; + else if(mode == LV_FS_MODE_RD) flags = "rb"; + else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = "rb+"; + + /*Make the path relative to the current directory (the projects root folder)*/ + + char buf[MAX_PATH_LEN]; + lv_snprintf(buf, sizeof(buf), LV_FS_STDIO_PATH "%s", path); + + return fopen(buf, flags); +} + +/** + * Close an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FILE variable. (opened with fs_open) + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p) +{ + LV_UNUSED(drv); + fclose(file_p); + return LV_FS_RES_OK; +} + +/** + * Read data from an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FILE variable. + * @param buf pointer to a memory block where to store the read data + * @param btr number of Bytes To Read + * @param br the real number of read bytes (Byte Read) + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br) +{ + LV_UNUSED(drv); + *br = fread(buf, 1, btr, file_p); + return (int32_t)(*br) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; +} + +/** + * Write into a file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FILE variable + * @param buf pointer to a buffer with the bytes to write + * @param btw Bytes To Write + * @param bw the number of real written bytes (Bytes Written). NULL if unused. + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw) +{ + LV_UNUSED(drv); + *bw = fwrite(buf, 1, btw, file_p); + return (int32_t)(*bw) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK; +} + +/** + * Set the read write pointer. Also expand the file size if necessary. + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FILE variable. (opened with fs_open ) + * @param pos the new position of read write pointer + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence) +{ + LV_UNUSED(drv); + fseek(file_p, pos, whence); + return LV_FS_RES_OK; +} + +/** + * Give the position of the read write pointer + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FILE variable. + * @param pos_p pointer to to store the result + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p) +{ + LV_UNUSED(drv); + *pos_p = ftell(file_p); + return LV_FS_RES_OK; +} + +/** + * Initialize a 'DIR' or 'HANDLE' variable for directory reading + * @param drv pointer to a driver where this function belongs + * @param path path to a directory + * @return pointer to an initialized 'DIR' or 'HANDLE' variable + */ +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path) +{ + LV_UNUSED(drv); + dir_handle_t * handle = (dir_handle_t *)lv_mem_alloc(sizeof(dir_handle_t)); +#ifndef WIN32 + /*Make the path relative to the current directory (the projects root folder)*/ + char buf[MAX_PATH_LEN]; + lv_snprintf(buf, sizeof(buf), LV_FS_STDIO_PATH "%s", path); + handle->dir_p = opendir(buf); + if(handle->dir_p == NULL) { + lv_mem_free(handle); + return NULL; + } + return handle; +#else + handle->dir_p = INVALID_HANDLE_VALUE; + WIN32_FIND_DATAA fdata; + + /*Make the path relative to the current directory (the projects root folder)*/ + char buf[MAX_PATH_LEN]; + lv_snprintf(buf, sizeof(buf), LV_FS_STDIO_PATH "%s\\*", path); + + strcpy(handle->next_fn, ""); + handle->dir_p = FindFirstFileA(buf, &fdata); + do { + if(strcmp(fdata.cFileName, ".") == 0 || strcmp(fdata.cFileName, "..") == 0) { + continue; + } + else { + if(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + lv_snprintf(handle->next_fn, sizeof(handle->next_fn), "/%s", fdata.cFileName); + } + else { + lv_snprintf(handle->next_fn, sizeof(handle->next_fn), "%s", fdata.cFileName); + } + break; + } + } while(FindNextFileA(handle->dir_p, &fdata)); + + if(handle->dir_p == INVALID_HANDLE_VALUE) { + lv_mem_free(handle); + return INVALID_HANDLE_VALUE; + } + return handle; +#endif +} + +/** + * Read the next filename form a directory. + * The name of the directories will begin with '/' + * @param drv pointer to a driver where this function belongs + * @param dir_p pointer to an initialized 'DIR' or 'HANDLE' variable + * @param fn pointer to a buffer to store the filename + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn) +{ + LV_UNUSED(drv); + dir_handle_t * handle = (dir_handle_t *)dir_p; +#ifndef WIN32 + struct dirent * entry; + do { + entry = readdir(handle->dir_p); + if(entry) { + if(entry->d_type == DT_DIR) lv_snprintf(fn, MAX_PATH_LEN, "/%s", entry->d_name); + else strcpy(fn, entry->d_name); + } + else { + strcpy(fn, ""); + } + } while(strcmp(fn, "/.") == 0 || strcmp(fn, "/..") == 0); +#else + strcpy(fn, handle->next_fn); + + strcpy(handle->next_fn, ""); + WIN32_FIND_DATAA fdata; + + if(FindNextFileA(handle->dir_p, &fdata) == false) return LV_FS_RES_OK; + do { + if(strcmp(fdata.cFileName, ".") == 0 || strcmp(fdata.cFileName, "..") == 0) { + continue; + } + else { + if(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + lv_snprintf(handle->next_fn, sizeof(handle->next_fn), "/%s", fdata.cFileName); + } + else { + lv_snprintf(handle->next_fn, sizeof(handle->next_fn), "%s", fdata.cFileName); + } + break; + } + } while(FindNextFileA(handle->dir_p, &fdata)); + +#endif + return LV_FS_RES_OK; +} + +/** + * Close the directory reading + * @param drv pointer to a driver where this function belongs + * @param dir_p pointer to an initialized 'DIR' or 'HANDLE' variable + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p) +{ + LV_UNUSED(drv); + dir_handle_t * handle = (dir_handle_t *)dir_p; +#ifndef WIN32 + closedir(handle->dir_p); +#else + FindClose(handle->dir_p); +#endif + lv_mem_free(handle); + return LV_FS_RES_OK; +} + +#else /*LV_USE_FS_STDIO == 0*/ + +#if defined(LV_FS_STDIO_LETTER) && LV_FS_STDIO_LETTER != '\0' + #warning "LV_USE_FS_STDIO is not enabled but LV_FS_STDIO_LETTER is set" +#endif + +#endif /*LV_USE_FS_POSIX*/ + diff --git a/lib/lvgl/src/extra/libs/fsdrv/lv_fs_win32.c b/lib/lvgl/src/extra/libs/fsdrv/lv_fs_win32.c new file mode 100644 index 00000000..1a59aa49 --- /dev/null +++ b/lib/lvgl/src/extra/libs/fsdrv/lv_fs_win32.c @@ -0,0 +1,466 @@ +/** + * @file lv_fs_win32.c + * + */ + + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" +#if LV_USE_FS_WIN32 != '\0' + +#include <windows.h> +#include <stdio.h> + +/********************* + * DEFINES + *********************/ +#define MAX_PATH_LEN 256 + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + HANDLE dir_p; + char next_fn[MAX_PATH_LEN]; + lv_fs_res_t next_error; +} dir_handle_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static bool is_dots_name(const char * name); +static lv_fs_res_t fs_error_from_win32(DWORD error); +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode); +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p); +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br); +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw); +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence); +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p); +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path); +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn); +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Register a driver for the File system interface + */ +void lv_fs_win32_init(void) +{ + /*--------------------------------------------------- + * Register the file system interface in LVGL + *--------------------------------------------------*/ + + /*Add a simple driver to open images*/ + static lv_fs_drv_t fs_drv; /*A driver descriptor*/ + lv_fs_drv_init(&fs_drv); + + /*Set up fields...*/ + fs_drv.letter = LV_FS_WIN32_LETTER; + fs_drv.cache_size = LV_FS_WIN32_CACHE_SIZE; + + fs_drv.open_cb = fs_open; + fs_drv.close_cb = fs_close; + fs_drv.read_cb = fs_read; + fs_drv.write_cb = fs_write; + fs_drv.seek_cb = fs_seek; + fs_drv.tell_cb = fs_tell; + + fs_drv.dir_close_cb = fs_dir_close; + fs_drv.dir_open_cb = fs_dir_open; + fs_drv.dir_read_cb = fs_dir_read; + + lv_fs_drv_register(&fs_drv); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/** + * Check the dots name + * @param name file or dir name + * @return true if the name is dots name + */ +static bool is_dots_name(const char * name) +{ + return name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2])); +} + +/** + * Convert Win32 error code to error from lv_fs_res_t enum + * @param error Win32 error code + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_error_from_win32(DWORD error) +{ + lv_fs_res_t res; + + switch(error) { + case ERROR_SUCCESS: + res = LV_FS_RES_OK; + break; + case ERROR_BAD_UNIT: + case ERROR_NOT_READY: + case ERROR_CRC: + case ERROR_SEEK: + case ERROR_NOT_DOS_DISK: + case ERROR_WRITE_FAULT: + case ERROR_READ_FAULT: + case ERROR_GEN_FAILURE: + case ERROR_WRONG_DISK: + res = LV_FS_RES_HW_ERR; + break; + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_TARGET_HANDLE: + res = LV_FS_RES_FS_ERR; + break; + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + case ERROR_NO_MORE_FILES: + case ERROR_SECTOR_NOT_FOUND: + case ERROR_BAD_NETPATH: + case ERROR_BAD_NET_NAME: + case ERROR_BAD_PATHNAME: + case ERROR_FILENAME_EXCED_RANGE: + res = LV_FS_RES_NOT_EX; + break; + case ERROR_DISK_FULL: + res = LV_FS_RES_FULL; + break; + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + case ERROR_DRIVE_LOCKED: + res = LV_FS_RES_LOCKED; + break; + case ERROR_ACCESS_DENIED: + case ERROR_CURRENT_DIRECTORY: + case ERROR_WRITE_PROTECT: + case ERROR_NETWORK_ACCESS_DENIED: + case ERROR_CANNOT_MAKE: + case ERROR_FAIL_I24: + case ERROR_SEEK_ON_DEVICE: + case ERROR_NOT_LOCKED: + case ERROR_LOCK_FAILED: + res = LV_FS_RES_DENIED; + break; + case ERROR_BUSY: + res = LV_FS_RES_BUSY; + break; + case ERROR_TIMEOUT: + res = LV_FS_RES_TOUT; + break; + case ERROR_NOT_SAME_DEVICE: + case ERROR_DIRECT_ACCESS_HANDLE: + res = LV_FS_RES_NOT_IMP; + break; + case ERROR_TOO_MANY_OPEN_FILES: + case ERROR_ARENA_TRASHED: + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_INVALID_BLOCK: + case ERROR_OUT_OF_PAPER: + case ERROR_SHARING_BUFFER_EXCEEDED: + case ERROR_NOT_ENOUGH_QUOTA: + res = LV_FS_RES_OUT_OF_MEM; + break; + case ERROR_INVALID_FUNCTION: + case ERROR_INVALID_ACCESS: + case ERROR_INVALID_DATA: + case ERROR_BAD_COMMAND: + case ERROR_BAD_LENGTH: + case ERROR_INVALID_PARAMETER: + case ERROR_NEGATIVE_SEEK: + res = LV_FS_RES_INV_PARAM; + break; + default: + res = LV_FS_RES_UNKNOWN; + break; + } + + return res; +} + +/** + * Open a file + * @param drv pointer to a driver where this function belongs + * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt) + * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR + * @return pointer to FIL struct or NULL in case of fail + */ +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode) +{ + LV_UNUSED(drv); + + DWORD desired_access = 0; + + if(mode & LV_FS_MODE_RD) { + desired_access |= GENERIC_READ; + } + + if(mode & LV_FS_MODE_WR) { + desired_access |= GENERIC_WRITE; + } + + /*Make the path relative to the current directory (the projects root folder)*/ + + char buf[MAX_PATH]; + lv_snprintf(buf, sizeof(buf), LV_FS_WIN32_PATH "%s", path); + + return (void *)CreateFileA( + buf, + desired_access, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); +} + +/** + * Close an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FILE variable. (opened with fs_open) + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p) +{ + LV_UNUSED(drv); + return CloseHandle((HANDLE)file_p) + ? LV_FS_RES_OK + : fs_error_from_win32(GetLastError()); +} + +/** + * Read data from an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FILE variable. + * @param buf pointer to a memory block where to store the read data + * @param btr number of Bytes To Read + * @param br the real number of read bytes (Byte Read) + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br) +{ + LV_UNUSED(drv); + return ReadFile((HANDLE)file_p, buf, btr, (LPDWORD)br, NULL) + ? LV_FS_RES_OK + : fs_error_from_win32(GetLastError()); +} + +/** + * Write into a file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FILE variable + * @param buf pointer to a buffer with the bytes to write + * @param btw Bytes To Write + * @param bw the number of real written bytes (Bytes Written). NULL if unused. + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw) +{ + LV_UNUSED(drv); + return WriteFile((HANDLE)file_p, buf, btw, (LPDWORD)bw, NULL) + ? LV_FS_RES_OK + : fs_error_from_win32(GetLastError()); +} + +/** + * Set the read write pointer. Also expand the file size if necessary. + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FILE variable. (opened with fs_open ) + * @param pos the new position of read write pointer + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence) +{ + LV_UNUSED(drv); + + DWORD move_method = (DWORD) -1; + if(whence == LV_FS_SEEK_SET) { + move_method = FILE_BEGIN; + } + else if(whence == LV_FS_SEEK_CUR) { + move_method = FILE_CURRENT; + } + else if(whence == LV_FS_SEEK_END) { + move_method = FILE_END; + } + + LARGE_INTEGER distance_to_move; + distance_to_move.QuadPart = pos; + return SetFilePointerEx((HANDLE)file_p, distance_to_move, NULL, move_method) + ? LV_FS_RES_OK + : fs_error_from_win32(GetLastError()); +} + +/** + * Give the position of the read write pointer + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a FILE variable. + * @param pos_p pointer to to store the result + * @return LV_FS_RES_OK: no error, the file is read + * any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p) +{ + LV_UNUSED(drv); + + if(!pos_p) { + return LV_FS_RES_INV_PARAM; + } + + LARGE_INTEGER file_pointer; + file_pointer.QuadPart = 0; + + LARGE_INTEGER distance_to_move; + distance_to_move.QuadPart = 0; + if(SetFilePointerEx( + (HANDLE)file_p, + distance_to_move, + &file_pointer, + FILE_CURRENT)) { + if(file_pointer.QuadPart > LONG_MAX) { + return LV_FS_RES_INV_PARAM; + } + else { + *pos_p = file_pointer.LowPart; + return LV_FS_RES_OK; + } + } + else { + return fs_error_from_win32(GetLastError()); + } +} + +/** + * Initialize a 'DIR' or 'HANDLE' variable for directory reading + * @param drv pointer to a driver where this function belongs + * @param path path to a directory + * @return pointer to an initialized 'DIR' or 'HANDLE' variable + */ +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path) +{ + LV_UNUSED(drv); + dir_handle_t * handle = (dir_handle_t *)lv_mem_alloc(sizeof(dir_handle_t)); + handle->dir_p = INVALID_HANDLE_VALUE; + handle->next_error = LV_FS_RES_OK; + WIN32_FIND_DATAA fdata; + + /*Make the path relative to the current directory (the projects root folder)*/ + char buf[MAX_PATH_LEN]; +#ifdef LV_FS_WIN32_PATH + lv_snprintf(buf, sizeof(buf), LV_FS_WIN32_PATH "%s\\*", path); +#else + lv_snprintf(buf, sizeof(buf), "%s\\*", path); +#endif + + strcpy(handle->next_fn, ""); + handle->dir_p = FindFirstFileA(buf, &fdata); + do { + if(is_dots_name(fdata.cFileName)) { + continue; + } + else { + if(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + lv_snprintf(handle->next_fn, sizeof(handle->next_fn), "/%s", fdata.cFileName); + } + else { + lv_snprintf(handle->next_fn, sizeof(handle->next_fn), "%s", fdata.cFileName); + } + break; + } + } while(FindNextFileA(handle->dir_p, &fdata)); + + if(handle->dir_p == INVALID_HANDLE_VALUE) { + lv_mem_free(handle); + handle->next_error = fs_error_from_win32(GetLastError()); + return INVALID_HANDLE_VALUE; + } + else { + handle->next_error = LV_FS_RES_OK; + return handle; + } +} + +/** + * Read the next filename from a directory. + * The name of the directories will begin with '/' + * @param drv pointer to a driver where this function belongs + * @param dir_p pointer to an initialized 'DIR' or 'HANDLE' variable + * @param fn pointer to a buffer to store the filename + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn) +{ + LV_UNUSED(drv); + dir_handle_t * handle = (dir_handle_t *)dir_p; + strcpy(fn, handle->next_fn); + lv_fs_res_t current_error = handle->next_error; + strcpy(handle->next_fn, ""); + + WIN32_FIND_DATAA fdata; + + while(FindNextFileA(handle->dir_p, &fdata)) { + if(is_dots_name(fdata.cFileName)) { + continue; + } + else { + if(fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + lv_snprintf(handle->next_fn, sizeof(handle->next_fn), "/%s", fdata.cFileName); + } + else { + lv_snprintf(handle->next_fn, sizeof(handle->next_fn), "%s", fdata.cFileName); + } + break; + } + } + + if(handle->next_fn[0] == '\0') { + handle->next_error = fs_error_from_win32(GetLastError()); + } + + return current_error; +} + +/** + * Close the directory reading + * @param drv pointer to a driver where this function belongs + * @param dir_p pointer to an initialized 'DIR' or 'HANDLE' variable + * @return LV_FS_RES_OK or any error from lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p) +{ + LV_UNUSED(drv); + dir_handle_t * handle = (dir_handle_t *)dir_p; + lv_fs_res_t res = FindClose(handle->dir_p) + ? LV_FS_RES_OK + : fs_error_from_win32(GetLastError()); + lv_mem_free(handle); + return res; +} + +#else /*LV_USE_FS_WIN32 == 0*/ + +#if defined(LV_FS_WIN32_LETTER) && LV_FS_WIN32_LETTER != '\0' + #warning "LV_USE_FS_WIN32 is not enabled but LV_FS_WIN32_LETTER is set" +#endif + +#endif diff --git a/lib/lvgl/src/extra/libs/fsdrv/lv_fsdrv.h b/lib/lvgl/src/extra/libs/fsdrv/lv_fsdrv.h new file mode 100644 index 00000000..285d598f --- /dev/null +++ b/lib/lvgl/src/extra/libs/fsdrv/lv_fsdrv.h @@ -0,0 +1,55 @@ +/** + * @file lv_fsdrv.h + * + */ + +#ifndef LV_FSDRV_H +#define LV_FSDRV_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lv_conf_internal.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +#if LV_USE_FS_FATFS != '\0' +void lv_fs_fatfs_init(void); +#endif + +#if LV_USE_FS_STDIO != '\0' +void lv_fs_stdio_init(void); +#endif + +#if LV_USE_FS_POSIX != '\0' +void lv_fs_posix_init(void); +#endif + +#if LV_USE_FS_WIN32 != '\0' +void lv_fs_win32_init(void); +#endif + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_FSDRV_H*/ + diff --git a/lib/lvgl/src/extra/libs/gif/gifdec.c b/lib/lvgl/src/extra/libs/gif/gifdec.c new file mode 100644 index 00000000..68f50057 --- /dev/null +++ b/lib/lvgl/src/extra/libs/gif/gifdec.c @@ -0,0 +1,659 @@ +#include "gifdec.h" +#include "../../../misc/lv_log.h" +#include "../../../misc/lv_mem.h" +#include "../../../misc/lv_color.h" +#if LV_USE_GIF + +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#define MIN(A, B) ((A) < (B) ? (A) : (B)) +#define MAX(A, B) ((A) > (B) ? (A) : (B)) + +typedef struct Entry { + uint16_t length; + uint16_t prefix; + uint8_t suffix; +} Entry; + +typedef struct Table { + int bulk; + int nentries; + Entry *entries; +} Table; + +static gd_GIF * gif_open(gd_GIF * gif); +static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file); +static void f_gif_read(gd_GIF * gif, void * buf, size_t len); +static int f_gif_seek(gd_GIF * gif, size_t pos, int k); +static void f_gif_close(gd_GIF * gif); + +static uint16_t +read_num(gd_GIF * gif) +{ + uint8_t bytes[2]; + + f_gif_read(gif, bytes, 2); + return bytes[0] + (((uint16_t) bytes[1]) << 8); +} + + + +gd_GIF * +gd_open_gif_file(const char *fname) +{ + gd_GIF gif_base; + memset(&gif_base, 0, sizeof(gif_base)); + + bool res = f_gif_open(&gif_base, fname, true); + if(!res) return NULL; + + return gif_open(&gif_base); +} + + +gd_GIF * +gd_open_gif_data(const void *data) +{ + gd_GIF gif_base; + memset(&gif_base, 0, sizeof(gif_base)); + + bool res = f_gif_open(&gif_base, data, false); + if(!res) return NULL; + + return gif_open(&gif_base); +} + +static gd_GIF * gif_open(gd_GIF * gif_base) +{ + uint8_t sigver[3]; + uint16_t width, height, depth; + uint8_t fdsz, bgidx, aspect; + int i; + uint8_t *bgcolor; + int gct_sz; + gd_GIF *gif = NULL; + + /* Header */ + f_gif_read(gif_base, sigver, 3); + if (memcmp(sigver, "GIF", 3) != 0) { + LV_LOG_WARN("invalid signature\n"); + goto fail; + } + /* Version */ + f_gif_read(gif_base, sigver, 3); + if (memcmp(sigver, "89a", 3) != 0) { + LV_LOG_WARN("invalid version\n"); + goto fail; + } + /* Width x Height */ + width = read_num(gif_base); + height = read_num(gif_base); + /* FDSZ */ + f_gif_read(gif_base, &fdsz, 1); + /* Presence of GCT */ + if (!(fdsz & 0x80)) { + LV_LOG_WARN("no global color table\n"); + goto fail; + } + /* Color Space's Depth */ + depth = ((fdsz >> 4) & 7) + 1; + /* Ignore Sort Flag. */ + /* GCT Size */ + gct_sz = 1 << ((fdsz & 0x07) + 1); + /* Background Color Index */ + f_gif_read(gif_base, &bgidx, 1); + /* Aspect Ratio */ + f_gif_read(gif_base, &aspect, 1); + /* Create gd_GIF Structure. */ +#if LV_COLOR_DEPTH == 32 + gif = lv_mem_alloc(sizeof(gd_GIF) + 5 * width * height); +#elif LV_COLOR_DEPTH == 16 + gif = lv_mem_alloc(sizeof(gd_GIF) + 4 * width * height); +#elif LV_COLOR_DEPTH == 8 || LV_COLOR_DEPTH == 1 + gif = lv_mem_alloc(sizeof(gd_GIF) + 3 * width * height); +#endif + + if (!gif) goto fail; + memcpy(gif, gif_base, sizeof(gd_GIF)); + gif->width = width; + gif->height = height; + gif->depth = depth; + /* Read GCT */ + gif->gct.size = gct_sz; + f_gif_read(gif, gif->gct.colors, 3 * gif->gct.size); + gif->palette = &gif->gct; + gif->bgindex = bgidx; + gif->canvas = (uint8_t *) &gif[1]; +#if LV_COLOR_DEPTH == 32 + gif->frame = &gif->canvas[4 * width * height]; +#elif LV_COLOR_DEPTH == 16 + gif->frame = &gif->canvas[3 * width * height]; +#elif LV_COLOR_DEPTH == 8 || LV_COLOR_DEPTH == 1 + gif->frame = &gif->canvas[2 * width * height]; +#endif + if (gif->bgindex) { + memset(gif->frame, gif->bgindex, gif->width * gif->height); + } + bgcolor = &gif->palette->colors[gif->bgindex*3]; + + for (i = 0; i < gif->width * gif->height; i++) { +#if LV_COLOR_DEPTH == 32 + gif->canvas[i*4 + 0] = *(bgcolor + 2); + gif->canvas[i*4 + 1] = *(bgcolor + 1); + gif->canvas[i*4 + 2] = *(bgcolor + 0); + gif->canvas[i*4 + 3] = 0xff; +#elif LV_COLOR_DEPTH == 16 + lv_color_t c = lv_color_make(*(bgcolor + 0), *(bgcolor + 1), *(bgcolor + 2)); + gif->canvas[i*3 + 0] = c.full & 0xff; + gif->canvas[i*3 + 1] = (c.full >> 8) & 0xff; + gif->canvas[i*3 + 2] = 0xff; +#elif LV_COLOR_DEPTH == 8 + lv_color_t c = lv_color_make(*(bgcolor + 0), *(bgcolor + 1), *(bgcolor + 2)); + gif->canvas[i*2 + 0] = c.full; + gif->canvas[i*2 + 1] = 0xff; +#elif LV_COLOR_DEPTH == 1 + lv_color_t c = lv_color_make(*(bgcolor + 0), *(bgcolor + 1), *(bgcolor + 2)); + gif->canvas[i*2 + 0] = c.ch.red > 128 ? 1 : 0; + gif->canvas[i*2 + 1] = 0xff; +#endif + } + gif->anim_start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + goto ok; +fail: + f_gif_close(gif_base); +ok: + return gif; +} + +static void +discard_sub_blocks(gd_GIF *gif) +{ + uint8_t size; + + do { + f_gif_read(gif, &size, 1); + f_gif_seek(gif, size, LV_FS_SEEK_CUR); + } while (size); +} + +static void +read_plain_text_ext(gd_GIF *gif) +{ + if (gif->plain_text) { + uint16_t tx, ty, tw, th; + uint8_t cw, ch, fg, bg; + size_t sub_block; + f_gif_seek(gif, 1, LV_FS_SEEK_CUR); /* block size = 12 */ + tx = read_num(gif); + ty = read_num(gif); + tw = read_num(gif); + th = read_num(gif); + f_gif_read(gif, &cw, 1); + f_gif_read(gif, &ch, 1); + f_gif_read(gif, &fg, 1); + f_gif_read(gif, &bg, 1); + sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg); + f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); + } else { + /* Discard plain text metadata. */ + f_gif_seek(gif, 13, LV_FS_SEEK_CUR); + } + /* Discard plain text sub-blocks. */ + discard_sub_blocks(gif); +} + +static void +read_graphic_control_ext(gd_GIF *gif) +{ + uint8_t rdit; + + /* Discard block size (always 0x04). */ + f_gif_seek(gif, 1, LV_FS_SEEK_CUR); + f_gif_read(gif, &rdit, 1); + gif->gce.disposal = (rdit >> 2) & 3; + gif->gce.input = rdit & 2; + gif->gce.transparency = rdit & 1; + gif->gce.delay = read_num(gif); + f_gif_read(gif, &gif->gce.tindex, 1); + /* Skip block terminator. */ + f_gif_seek(gif, 1, LV_FS_SEEK_CUR); +} + +static void +read_comment_ext(gd_GIF *gif) +{ + if (gif->comment) { + size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + gif->comment(gif); + f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); + } + /* Discard comment sub-blocks. */ + discard_sub_blocks(gif); +} + +static void +read_application_ext(gd_GIF *gif) +{ + char app_id[8]; + char app_auth_code[3]; + + /* Discard block size (always 0x0B). */ + f_gif_seek(gif, 1, LV_FS_SEEK_CUR); + /* Application Identifier. */ + f_gif_read(gif, app_id, 8); + /* Application Authentication Code. */ + f_gif_read(gif, app_auth_code, 3); + if (!strncmp(app_id, "NETSCAPE", sizeof(app_id))) { + /* Discard block size (0x03) and constant byte (0x01). */ + f_gif_seek(gif, 2, LV_FS_SEEK_CUR); + gif->loop_count = read_num(gif); + /* Skip block terminator. */ + f_gif_seek(gif, 1, LV_FS_SEEK_CUR); + } else if (gif->application) { + size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + gif->application(gif, app_id, app_auth_code); + f_gif_seek(gif, sub_block, LV_FS_SEEK_SET); + discard_sub_blocks(gif); + } else { + discard_sub_blocks(gif); + } +} + +static void +read_ext(gd_GIF *gif) +{ + uint8_t label; + + f_gif_read(gif, &label, 1); + switch (label) { + case 0x01: + read_plain_text_ext(gif); + break; + case 0xF9: + read_graphic_control_ext(gif); + break; + case 0xFE: + read_comment_ext(gif); + break; + case 0xFF: + read_application_ext(gif); + break; + default: + LV_LOG_WARN("unknown extension: %02X\n", label); + } +} + +static Table * +new_table(int key_size) +{ + int key; + int init_bulk = MAX(1 << (key_size + 1), 0x100); + Table *table = lv_mem_alloc(sizeof(*table) + sizeof(Entry) * init_bulk); + if (table) { + table->bulk = init_bulk; + table->nentries = (1 << key_size) + 2; + table->entries = (Entry *) &table[1]; + for (key = 0; key < (1 << key_size); key++) + table->entries[key] = (Entry) {1, 0xFFF, key}; + } + return table; +} + +/* Add table entry. Return value: + * 0 on success + * +1 if key size must be incremented after this addition + * -1 if could not realloc table */ +static int +add_entry(Table **tablep, uint16_t length, uint16_t prefix, uint8_t suffix) +{ + Table *table = *tablep; + if (table->nentries == table->bulk) { + table->bulk *= 2; + table = lv_mem_realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk); + if (!table) return -1; + table->entries = (Entry *) &table[1]; + *tablep = table; + } + table->entries[table->nentries] = (Entry) {length, prefix, suffix}; + table->nentries++; + if ((table->nentries & (table->nentries - 1)) == 0) + return 1; + return 0; +} + +static uint16_t +get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte) +{ + int bits_read; + int rpad; + int frag_size; + uint16_t key; + + key = 0; + for (bits_read = 0; bits_read < key_size; bits_read += frag_size) { + rpad = (*shift + bits_read) % 8; + if (rpad == 0) { + /* Update byte. */ + if (*sub_len == 0) { + f_gif_read(gif, sub_len, 1); /* Must be nonzero! */ + if (*sub_len == 0) return 0x1000; + } + f_gif_read(gif, byte, 1); + (*sub_len)--; + } + frag_size = MIN(key_size - bits_read, 8 - rpad); + key |= ((uint16_t) ((*byte) >> rpad)) << bits_read; + } + /* Clear extra bits to the left. */ + key &= (1 << key_size) - 1; + *shift = (*shift + key_size) % 8; + return key; +} + +/* Compute output index of y-th input line, in frame of height h. */ +static int +interlaced_line_index(int h, int y) +{ + int p; /* number of lines in current pass */ + + p = (h - 1) / 8 + 1; + if (y < p) /* pass 1 */ + return y * 8; + y -= p; + p = (h - 5) / 8 + 1; + if (y < p) /* pass 2 */ + return y * 8 + 4; + y -= p; + p = (h - 3) / 4 + 1; + if (y < p) /* pass 3 */ + return y * 4 + 2; + y -= p; + /* pass 4 */ + return y * 2 + 1; +} + +/* Decompress image pixels. + * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */ +static int +read_image_data(gd_GIF *gif, int interlace) +{ + uint8_t sub_len, shift, byte; + int init_key_size, key_size, table_is_full=0; + int frm_off, frm_size, str_len=0, i, p, x, y; + uint16_t key, clear, stop; + int ret; + Table *table; + Entry entry = {0}; + size_t start, end; + + f_gif_read(gif, &byte, 1); + key_size = (int) byte; + start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + discard_sub_blocks(gif); + end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR); + f_gif_seek(gif, start, LV_FS_SEEK_SET); + clear = 1 << key_size; + stop = clear + 1; + table = new_table(key_size); + key_size++; + init_key_size = key_size; + sub_len = shift = 0; + key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */ + frm_off = 0; + ret = 0; + frm_size = gif->fw*gif->fh; + while (frm_off < frm_size) { + if (key == clear) { + key_size = init_key_size; + table->nentries = (1 << (key_size - 1)) + 2; + table_is_full = 0; + } else if (!table_is_full) { + ret = add_entry(&table, str_len + 1, key, entry.suffix); + if (ret == -1) { + lv_mem_free(table); + return -1; + } + if (table->nentries == 0x1000) { + ret = 0; + table_is_full = 1; + } + } + key = get_key(gif, key_size, &sub_len, &shift, &byte); + if (key == clear) continue; + if (key == stop || key == 0x1000) break; + if (ret == 1) key_size++; + entry = table->entries[key]; + str_len = entry.length; + for (i = 0; i < str_len; i++) { + p = frm_off + entry.length - 1; + x = p % gif->fw; + y = p / gif->fw; + if (interlace) + y = interlaced_line_index((int) gif->fh, y); + gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix; + if (entry.prefix == 0xFFF) + break; + else + entry = table->entries[entry.prefix]; + } + frm_off += str_len; + if (key < table->nentries - 1 && !table_is_full) + table->entries[table->nentries - 1].suffix = entry.suffix; + } + lv_mem_free(table); + if (key == stop) f_gif_read(gif, &sub_len, 1); /* Must be zero! */ + f_gif_seek(gif, end, LV_FS_SEEK_SET); + return 0; +} + +/* Read image. + * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table). */ +static int +read_image(gd_GIF *gif) +{ + uint8_t fisrz; + int interlace; + + /* Image Descriptor. */ + gif->fx = read_num(gif); + gif->fy = read_num(gif); + gif->fw = read_num(gif); + gif->fh = read_num(gif); + f_gif_read(gif, &fisrz, 1); + interlace = fisrz & 0x40; + /* Ignore Sort Flag. */ + /* Local Color Table? */ + if (fisrz & 0x80) { + /* Read LCT */ + gif->lct.size = 1 << ((fisrz & 0x07) + 1); + f_gif_read(gif, gif->lct.colors, 3 * gif->lct.size); + gif->palette = &gif->lct; + } else + gif->palette = &gif->gct; + /* Image Data. */ + return read_image_data(gif, interlace); +} + +static void +render_frame_rect(gd_GIF *gif, uint8_t *buffer) +{ + int i, j, k; + uint8_t index, *color; + i = gif->fy * gif->width + gif->fx; + for (j = 0; j < gif->fh; j++) { + for (k = 0; k < gif->fw; k++) { + index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k]; + color = &gif->palette->colors[index*3]; + if (!gif->gce.transparency || index != gif->gce.tindex) { +#if LV_COLOR_DEPTH == 32 + buffer[(i+k)*4 + 0] = *(color + 2); + buffer[(i+k)*4 + 1] = *(color + 1); + buffer[(i+k)*4 + 2] = *(color + 0); + buffer[(i+k)*4 + 3] = 0xFF; +#elif LV_COLOR_DEPTH == 16 + lv_color_t c = lv_color_make(*(color + 0), *(color + 1), *(color + 2)); + buffer[(i+k)*3 + 0] = c.full & 0xff; + buffer[(i+k)*3 + 1] = (c.full >> 8) & 0xff; + buffer[(i+k)*3 + 2] = 0xff; +#elif LV_COLOR_DEPTH == 8 + lv_color_t c = lv_color_make(*(color + 0), *(color + 1), *(color + 2)); + buffer[(i+k)*2 + 0] = c.full; + buffer[(i+k)*2 + 1] = 0xff; +#elif LV_COLOR_DEPTH == 1 + uint8_t b = (*(color + 0)) | (*(color + 1)) | (*(color + 2)); + buffer[(i+k)*2 + 0] = b > 128 ? 1 : 0; + buffer[(i+k)*2 + 1] = 0xff; +#endif + } + } + i += gif->width; + } +} + +static void +dispose(gd_GIF *gif) +{ + int i, j, k; + uint8_t *bgcolor; + switch (gif->gce.disposal) { + case 2: /* Restore to background color. */ + bgcolor = &gif->palette->colors[gif->bgindex*3]; + + uint8_t opa = 0xff; + if(gif->gce.transparency) opa = 0x00; + + i = gif->fy * gif->width + gif->fx; + for (j = 0; j < gif->fh; j++) { + for (k = 0; k < gif->fw; k++) { +#if LV_COLOR_DEPTH == 32 + gif->canvas[(i+k)*4 + 0] = *(bgcolor + 2); + gif->canvas[(i+k)*4 + 1] = *(bgcolor + 1); + gif->canvas[(i+k)*4 + 2] = *(bgcolor + 0); + gif->canvas[(i+k)*4 + 3] = opa; +#elif LV_COLOR_DEPTH == 16 + lv_color_t c = lv_color_make(*(bgcolor + 0), *(bgcolor + 1), *(bgcolor + 2)); + gif->canvas[(i+k)*3 + 0] = c.full & 0xff; + gif->canvas[(i+k)*3 + 1] = (c.full >> 8) & 0xff; + gif->canvas[(i+k)*3 + 2] = opa; +#elif LV_COLOR_DEPTH == 8 + lv_color_t c = lv_color_make(*(bgcolor + 0), *(bgcolor + 1), *(bgcolor + 2)); + gif->canvas[(i+k)*2 + 0] = c.full; + gif->canvas[(i+k)*2 + 1] = opa; +#elif LV_COLOR_DEPTH == 1 + uint8_t b = (*(bgcolor + 0)) | (*(bgcolor + 1)) | (*(bgcolor + 2)); + gif->canvas[(i+k)*2 + 0] = b > 128 ? 1 : 0; + gif->canvas[(i+k)*2 + 1] = opa; +#endif + } + i += gif->width; + } + break; + case 3: /* Restore to previous, i.e., don't update canvas.*/ + break; + default: + /* Add frame non-transparent pixels to canvas. */ + render_frame_rect(gif, gif->canvas); + } +} + +/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */ +int +gd_get_frame(gd_GIF *gif) +{ + char sep; + + dispose(gif); + f_gif_read(gif, &sep, 1); + while (sep != ',') { + if (sep == ';') + return 0; + if (sep == '!') + read_ext(gif); + else return -1; + f_gif_read(gif, &sep, 1); + } + if (read_image(gif) == -1) + return -1; + return 1; +} + +void +gd_render_frame(gd_GIF *gif, uint8_t *buffer) +{ +// uint32_t i; +// uint32_t j; +// for(i = 0, j = 0; i < gif->width * gif->height * 3; i+= 3, j+=4) { +// buffer[j + 0] = gif->canvas[i + 2]; +// buffer[j + 1] = gif->canvas[i + 1]; +// buffer[j + 2] = gif->canvas[i + 0]; +// buffer[j + 3] = 0xFF; +// } +// memcpy(buffer, gif->canvas, gif->width * gif->height * 3); + render_frame_rect(gif, buffer); +} + +void +gd_rewind(gd_GIF *gif) +{ + f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET); +} + +void +gd_close_gif(gd_GIF *gif) +{ + f_gif_close(gif); + lv_mem_free(gif); +} + +static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file) +{ + gif->f_rw_p = 0; + gif->data = NULL; + gif->is_file = is_file; + + if(is_file) { + lv_fs_res_t res = lv_fs_open(&gif->fd, path, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) return false; + else return true; + } else { + gif->data = path; + return true; + } +} + +static void f_gif_read(gd_GIF * gif, void * buf, size_t len) +{ + if(gif->is_file) { + lv_fs_read(&gif->fd, buf, len, NULL); + } else + { + memcpy(buf, &gif->data[gif->f_rw_p], len); + gif->f_rw_p += len; + } +} + +static int f_gif_seek(gd_GIF * gif, size_t pos, int k) +{ + if(gif->is_file) { + lv_fs_seek(&gif->fd, pos, k); + uint32_t x; + lv_fs_tell(&gif->fd, &x); + return x; + } else { + if(k == LV_FS_SEEK_CUR) gif->f_rw_p += pos; + else if(k == LV_FS_SEEK_SET) gif->f_rw_p = pos; + return gif->f_rw_p; + } +} + +static void f_gif_close(gd_GIF * gif) +{ + if(gif->is_file) { + lv_fs_close(&gif->fd); + } +} + +#endif /*LV_USE_GIF*/ diff --git a/lib/lvgl/src/extra/libs/gif/gifdec.h b/lib/lvgl/src/extra/libs/gif/gifdec.h new file mode 100644 index 00000000..00f17c1d --- /dev/null +++ b/lib/lvgl/src/extra/libs/gif/gifdec.h @@ -0,0 +1,60 @@ +#ifndef GIFDEC_H +#define GIFDEC_H + +#include <stdint.h> +#include "../../../misc/lv_fs.h" + +#if LV_USE_GIF + +typedef struct gd_Palette { + int size; + uint8_t colors[0x100 * 3]; +} gd_Palette; + +typedef struct gd_GCE { + uint16_t delay; + uint8_t tindex; + uint8_t disposal; + int input; + int transparency; +} gd_GCE; + + + +typedef struct gd_GIF { + lv_fs_file_t fd; + const char * data; + uint8_t is_file; + uint32_t f_rw_p; + int32_t anim_start; + uint16_t width, height; + uint16_t depth; + uint16_t loop_count; + gd_GCE gce; + gd_Palette *palette; + gd_Palette lct, gct; + void (*plain_text)( + struct gd_GIF *gif, uint16_t tx, uint16_t ty, + uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch, + uint8_t fg, uint8_t bg + ); + void (*comment)(struct gd_GIF *gif); + void (*application)(struct gd_GIF *gif, char id[8], char auth[3]); + uint16_t fx, fy, fw, fh; + uint8_t bgindex; + uint8_t *canvas, *frame; +} gd_GIF; + +gd_GIF * gd_open_gif_file(const char *fname); + +gd_GIF * gd_open_gif_data(const void *data); + +void gd_render_frame(gd_GIF *gif, uint8_t *buffer); + +int gd_get_frame(gd_GIF *gif); +void gd_rewind(gd_GIF *gif); +void gd_close_gif(gd_GIF *gif); + +#endif /*LV_USE_GIF*/ + +#endif /* GIFDEC_H */ diff --git a/lib/lvgl/src/extra/libs/gif/lv_gif.c b/lib/lvgl/src/extra/libs/gif/lv_gif.c new file mode 100644 index 00000000..4cb2955e --- /dev/null +++ b/lib/lvgl/src/extra/libs/gif/lv_gif.c @@ -0,0 +1,155 @@ +/** + * @file lv_gifenc.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_gif.h" +#if LV_USE_GIF + +#include "gifdec.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_gif_class + +/********************** + * TYPEDEFS + **********************/ + + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_gif_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_gif_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void next_frame_task_cb(lv_timer_t * t); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_gif_class = { + .constructor_cb = lv_gif_constructor, + .destructor_cb = lv_gif_destructor, + .instance_size = sizeof(lv_gif_t), + .base_class = &lv_img_class +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_gif_create(lv_obj_t * parent) +{ + + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +void lv_gif_set_src(lv_obj_t * obj, const void * src) +{ + lv_gif_t * gifobj = (lv_gif_t *) obj; + + /*Close previous gif if any*/ + if(gifobj->gif) { + lv_img_cache_invalidate_src(&gifobj->imgdsc); + gd_close_gif(gifobj->gif); + gifobj->gif = NULL; + gifobj->imgdsc.data = NULL; + } + + if(lv_img_src_get_type(src) == LV_IMG_SRC_VARIABLE) { + const lv_img_dsc_t * img_dsc = src; + gifobj->gif = gd_open_gif_data(img_dsc->data); + } + else if(lv_img_src_get_type(src) == LV_IMG_SRC_FILE) { + gifobj->gif = gd_open_gif_file(src); + } + if(gifobj->gif == NULL) { + LV_LOG_WARN("Could't load the source"); + return; + } + + gifobj->imgdsc.data = gifobj->gif->canvas; + gifobj->imgdsc.header.always_zero = 0; + gifobj->imgdsc.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA; + gifobj->imgdsc.header.h = gifobj->gif->height; + gifobj->imgdsc.header.w = gifobj->gif->width; + gifobj->last_call = lv_tick_get(); + + lv_img_set_src(obj, &gifobj->imgdsc); + + lv_timer_resume(gifobj->timer); + lv_timer_reset(gifobj->timer); + + next_frame_task_cb(gifobj->timer); + +} + +void lv_gif_restart(lv_obj_t * obj) +{ + lv_gif_t * gifobj = (lv_gif_t *) obj; + gd_rewind(gifobj->gif); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_gif_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + + lv_gif_t * gifobj = (lv_gif_t *) obj; + + gifobj->timer = lv_timer_create(next_frame_task_cb, 10, obj); + lv_timer_pause(gifobj->timer); +} + +static void lv_gif_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_gif_t * gifobj = (lv_gif_t *) obj; + lv_img_cache_invalidate_src(&gifobj->imgdsc); + gd_close_gif(gifobj->gif); + lv_timer_del(gifobj->timer); +} + +static void next_frame_task_cb(lv_timer_t * t) +{ + lv_obj_t * obj = t->user_data; + lv_gif_t * gifobj = (lv_gif_t *) obj; + uint32_t elaps = lv_tick_elaps(gifobj->last_call); + if(elaps < gifobj->gif->gce.delay * 10) return; + + gifobj->last_call = lv_tick_get(); + + int has_next = gd_get_frame(gifobj->gif); + if(has_next == 0) { + /*It was the last repeat*/ + if(gifobj->gif->loop_count == 1) { + lv_res_t res = lv_event_send(obj, LV_EVENT_READY, NULL); + if(res != LV_FS_RES_OK) return; + } + else { + if(gifobj->gif->loop_count > 1) gifobj->gif->loop_count--; + gd_rewind(gifobj->gif); + } + } + + gd_render_frame(gifobj->gif, (uint8_t *)gifobj->imgdsc.data); + + lv_img_cache_invalidate_src(lv_img_get_src(obj)); + lv_obj_invalidate(obj); +} + +#endif /*LV_USE_GIF*/ diff --git a/lib/lvgl/src/extra/libs/gif/lv_gif.h b/lib/lvgl/src/extra/libs/gif/lv_gif.h new file mode 100644 index 00000000..d8c93dbc --- /dev/null +++ b/lib/lvgl/src/extra/libs/gif/lv_gif.h @@ -0,0 +1,58 @@ +/** + * @file lv_gif.h + * + */ + +#ifndef LV_GIF_H +#define LV_GIF_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "../../../lvgl.h" +#if LV_USE_GIF + +#include "gifdec.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_img_t img; + gd_GIF * gif; + lv_timer_t * timer; + lv_img_dsc_t imgdsc; + uint32_t last_call; +} lv_gif_t; + +extern const lv_obj_class_t lv_gif_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t * lv_gif_create(lv_obj_t * parent); +void lv_gif_set_src(lv_obj_t * obj, const void * src); +void lv_gif_restart(lv_obj_t * gif); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_GIF*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_GIF_H*/ diff --git a/lib/lvgl/src/extra/libs/lv_libs.h b/lib/lvgl/src/extra/libs/lv_libs.h new file mode 100644 index 00000000..6782b1d0 --- /dev/null +++ b/lib/lvgl/src/extra/libs/lv_libs.h @@ -0,0 +1,46 @@ +/** + * @file lv_libs.h + * + */ + +#ifndef LV_LIBS_H +#define LV_LIBS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "bmp/lv_bmp.h" +#include "fsdrv/lv_fsdrv.h" +#include "png/lv_png.h" +#include "gif/lv_gif.h" +#include "qrcode/lv_qrcode.h" +#include "sjpg/lv_sjpg.h" +#include "freetype/lv_freetype.h" +#include "rlottie/lv_rlottie.h" +#include "ffmpeg/lv_ffmpeg.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_LIBS_H*/ diff --git a/lib/lvgl/src/extra/libs/png/lodepng.c b/lib/lvgl/src/extra/libs/png/lodepng.c new file mode 100644 index 00000000..82e18e1a --- /dev/null +++ b/lib/lvgl/src/extra/libs/png/lodepng.c @@ -0,0 +1,6469 @@ +/* +LodePNG version 20201017 + +Copyright (c) 2005-2020 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +/* +The manual and changelog are in the header file "lodepng.h" +Rename this file to lodepng.cpp to use it for C++, or to lodepng.c to use it for C. +*/ + +#include "lodepng.h" +#if LV_USE_PNG + +#ifdef LODEPNG_COMPILE_DISK +#include <limits.h> /* LONG_MAX */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +#include <stdlib.h> /* allocations */ +#endif /* LODEPNG_COMPILE_ALLOCATORS */ + +#if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/ +#pragma warning( disable : 4244 ) /*implicit conversions: not warned by gcc -Wall -Wextra and requires too much casts*/ +#pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/ +#endif /*_MSC_VER */ + +const char* LODEPNG_VERSION_STRING = "20201017"; + +/* +This source file is built up in the following large parts. The code sections +with the "LODEPNG_COMPILE_" #defines divide this up further in an intermixed way. +-Tools for C and common code for PNG and Zlib +-C Code for Zlib (huffman, deflate, ...) +-C Code for PNG (file format chunks, adam7, PNG filters, color conversions, ...) +-The C++ wrapper around all of the above +*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // Tools for C, and common code for PNG and Zlib. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*The malloc, realloc and free functions defined here with "lodepng_" in front +of the name, so that you can easily change them to others related to your +platform if needed. Everything else in the code calls these. Pass +-DLODEPNG_NO_COMPILE_ALLOCATORS to the compiler, or comment out +#define LODEPNG_COMPILE_ALLOCATORS in the header, to disable the ones here and +define them in your own project's source files without needing to change +lodepng source code. Don't forget to remove "static" if you copypaste them +from here.*/ + +#ifdef LODEPNG_COMPILE_ALLOCATORS +static void* lodepng_malloc(size_t size) { +#ifdef LODEPNG_MAX_ALLOC + if(size > LODEPNG_MAX_ALLOC) return 0; +#endif + return lv_mem_alloc(size); +} + +/* NOTE: when realloc returns NULL, it leaves the original memory untouched */ +static void* lodepng_realloc(void* ptr, size_t new_size) { +#ifdef LODEPNG_MAX_ALLOC + if(new_size > LODEPNG_MAX_ALLOC) return 0; +#endif + return lv_mem_realloc(ptr, new_size); +} + +static void lodepng_free(void* ptr) { + lv_mem_free(ptr); +} +#else /*LODEPNG_COMPILE_ALLOCATORS*/ +/* TODO: support giving additional void* payload to the custom allocators */ +void* lodepng_malloc(size_t size); +void* lodepng_realloc(void* ptr, size_t new_size); +void lodepng_free(void* ptr); +#endif /*LODEPNG_COMPILE_ALLOCATORS*/ + +/* convince the compiler to inline a function, for use when this measurably improves performance */ +/* inline is not available in C90, but use it when supported by the compiler */ +#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || (defined(__cplusplus) && (__cplusplus >= 199711L)) +#define LODEPNG_INLINE inline +#else +#define LODEPNG_INLINE /* not available */ +#endif + +/* restrict is not available in C90, but use it when supported by the compiler */ +#if (defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1))) ||\ + (defined(_MSC_VER) && (_MSC_VER >= 1400)) || \ + (defined(__WATCOMC__) && (__WATCOMC__ >= 1250) && !defined(__cplusplus)) +#define LODEPNG_RESTRICT __restrict +#else +#define LODEPNG_RESTRICT /* not available */ +#endif + +/* Replacements for C library functions such as memcpy and strlen, to support platforms +where a full C library is not available. The compiler can recognize them and compile +to something as fast. */ + +static void lodepng_memcpy(void* LODEPNG_RESTRICT dst, + const void* LODEPNG_RESTRICT src, size_t size) { + lv_memcpy(dst, src, size); +} + +static void lodepng_memset(void* LODEPNG_RESTRICT dst, + int value, size_t num) { + lv_memset(dst, value, num); +} + +/* does not check memory out of bounds, do not use on untrusted data */ +static size_t lodepng_strlen(const char* a) { + const char* orig = a; + /* avoid warning about unused function in case of disabled COMPILE... macros */ + (void)(&lodepng_strlen); + while(*a) a++; + return (size_t)(a - orig); +} + +#define LODEPNG_MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define LODEPNG_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define LODEPNG_ABS(x) ((x) < 0 ? -(x) : (x)) + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER) +/* Safely check if adding two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_addofl(size_t a, size_t b, size_t* result) { + *result = a + b; /* Unsigned addition is well defined and safe in C90 */ + return *result < a; +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_DECODER)*/ + +#ifdef LODEPNG_COMPILE_DECODER +/* Safely check if multiplying two integers will overflow (no undefined +behavior, compiler removing the code, etc...) and output result. */ +static int lodepng_mulofl(size_t a, size_t b, size_t* result) { + *result = a * b; /* Unsigned multiplication is well defined and safe in C90 */ + return (a != 0 && *result / a != b); +} + +#ifdef LODEPNG_COMPILE_ZLIB +/* Safely check if a + b > c, even if overflow could happen. */ +static int lodepng_gtofl(size_t a, size_t b, size_t c) { + size_t d; + if(lodepng_addofl(a, b, &d)) return 1; + return d > c; +} +#endif /*LODEPNG_COMPILE_ZLIB*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +/* +Often in case of an error a value is assigned to a variable and then it breaks +out of a loop (to go to the cleanup phase of a function). This macro does that. +It makes the error handling code shorter and more readable. + +Example: if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83); +*/ +#define CERROR_BREAK(errorvar, code){\ + errorvar = code;\ + break;\ +} + +/*version of CERROR_BREAK that assumes the common case where the error variable is named "error"*/ +#define ERROR_BREAK(code) CERROR_BREAK(error, code) + +/*Set error var to the error code, and return it.*/ +#define CERROR_RETURN_ERROR(errorvar, code){\ + errorvar = code;\ + return code;\ +} + +/*Try the code, if it returns error, also return the error.*/ +#define CERROR_TRY_RETURN(call){\ + unsigned error = call;\ + if(error) return error;\ +} + +/*Set error var to the error code, and return from the void function.*/ +#define CERROR_RETURN(errorvar, code){\ + errorvar = code;\ + return;\ +} + +/* +About uivector, ucvector and string: +-All of them wrap dynamic arrays or text strings in a similar way. +-LodePNG was originally written in C++. The vectors replace the std::vectors that were used in the C++ version. +-The string tools are made to avoid problems with compilers that declare things like strncat as deprecated. +-They're not used in the interface, only internally in this file as static functions. +-As with many other structs in this file, the init and cleanup functions serve as ctor and dtor. +*/ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER +/*dynamic vector of unsigned ints*/ +typedef struct uivector { + unsigned* data; + size_t size; /*size in number of unsigned longs*/ + size_t allocsize; /*allocated size in bytes*/ +} uivector; + +static void uivector_cleanup(void* p) { + ((uivector*)p)->size = ((uivector*)p)->allocsize = 0; + lodepng_free(((uivector*)p)->data); + ((uivector*)p)->data = NULL; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_resize(uivector* p, size_t size) { + size_t allocsize = size * sizeof(unsigned); + if(allocsize > p->allocsize) { + size_t newsize = allocsize + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static void uivector_init(uivector* p) { + p->data = NULL; + p->size = p->allocsize = 0; +} + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned uivector_push_back(uivector* p, unsigned c) { + if(!uivector_resize(p, p->size + 1)) return 0; + p->data[p->size - 1] = c; + return 1; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* /////////////////////////////////////////////////////////////////////////// */ + +/*dynamic vector of unsigned chars*/ +typedef struct ucvector { + unsigned char* data; + size_t size; /*used size*/ + size_t allocsize; /*allocated size*/ +} ucvector; + +/*returns 1 if success, 0 if failure ==> nothing done*/ +static unsigned ucvector_resize(ucvector* p, size_t size) { + if(size > p->allocsize) { + size_t newsize = size + (p->allocsize >> 1u); + void* data = lodepng_realloc(p->data, newsize); + if(data) { + p->allocsize = newsize; + p->data = (unsigned char*)data; + } + else return 0; /*error: not enough memory*/ + } + p->size = size; + return 1; /*success*/ +} + +static ucvector ucvector_init(unsigned char* buffer, size_t size) { + ucvector v; + v.data = buffer; + v.allocsize = v.size = size; + return v; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +/*free string pointer and set it to NULL*/ +static void string_cleanup(char** out) { + lodepng_free(*out); + *out = NULL; +} + +/*also appends null termination character*/ +static char* alloc_string_sized(const char* in, size_t insize) { + char* out = (char*)lodepng_malloc(insize + 1); + if(out) { + lodepng_memcpy(out, in, insize); + out[insize] = 0; + } + return out; +} + +/* dynamically allocates a new string with a copy of the null terminated input text */ +static char* alloc_string(const char* in) { + return alloc_string_sized(in, lodepng_strlen(in)); +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG) +static unsigned lodepng_read32bitInt(const unsigned char* buffer) { + return (((unsigned)buffer[0] << 24u) | ((unsigned)buffer[1] << 16u) | + ((unsigned)buffer[2] << 8u) | (unsigned)buffer[3]); +} +#endif /*defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_PNG)*/ + +#if defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER) +/*buffer must have at least 4 allocated bytes available*/ +static void lodepng_set32bitInt(unsigned char* buffer, unsigned value) { + buffer[0] = (unsigned char)((value >> 24) & 0xff); + buffer[1] = (unsigned char)((value >> 16) & 0xff); + buffer[2] = (unsigned char)((value >> 8) & 0xff); + buffer[3] = (unsigned char)((value ) & 0xff); +} +#endif /*defined(LODEPNG_COMPILE_PNG) || defined(LODEPNG_COMPILE_ENCODER)*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / File IO / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DISK + +/* returns negative value on error. This should be pure C compatible, so no fstat. */ +static long lodepng_filesize(const char* filename) { + lv_fs_file_t f; + lv_fs_res_t res = lv_fs_open(&f, filename, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) return -1; + uint32_t size = 0; + if(lv_fs_seek(&f, 0, LV_FS_SEEK_END) != 0) { + lv_fs_close(&f); + return -1; + } + + lv_fs_tell(&f, &size); + lv_fs_close(&f); + return size; +} + +/* load file into buffer that already has the correct allocated size. Returns error code.*/ +static unsigned lodepng_buffer_file(unsigned char* out, size_t size, const char* filename) { + lv_fs_file_t f; + lv_fs_res_t res = lv_fs_open(&f, filename, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) return 78; + + uint32_t br; + res = lv_fs_read(&f, out, size, &br); + if(res != LV_FS_RES_OK) return 78; + if (br != size) return 78; + lv_fs_close(&f); + return 0; +} + +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename) { + long size = lodepng_filesize(filename); + if(size < 0) return 78; + *outsize = (size_t)size; + + *out = (unsigned char*)lodepng_malloc((size_t)size); + if(!(*out) && size > 0) return 83; /*the above malloc failed*/ + + return lodepng_buffer_file(*out, (size_t)size, filename); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename) { + lv_fs_file_t f; + lv_fs_res_t res = lv_fs_open(&f, filename, LV_FS_MODE_WR); + if(res != LV_FS_RES_OK) return 79; + + uint32_t bw; + res = lv_fs_write(&f, buffer, buffersize, &bw); + lv_fs_close(&f); + return 0; +} + +#endif /*LODEPNG_COMPILE_DISK*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of common code and tools. Begin of Zlib related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_ENCODER + +typedef struct { + ucvector* data; + unsigned char bp; /*ok to overflow, indicates bit pos inside byte*/ +} LodePNGBitWriter; + +static void LodePNGBitWriter_init(LodePNGBitWriter* writer, ucvector* data) { + writer->data = data; + writer->bp = 0; +} + +/*TODO: this ignores potential out of memory errors*/ +#define WRITEBIT(writer, bit){\ + /* append new byte */\ + if(((writer->bp) & 7u) == 0) {\ + if(!ucvector_resize(writer->data, writer->data->size + 1)) return;\ + writer->data->data[writer->data->size - 1] = 0;\ + }\ + (writer->data->data[writer->data->size - 1]) |= (bit << ((writer->bp) & 7u));\ + ++writer->bp;\ +} + +/* LSB of value is written first, and LSB of bytes is used first */ +static void writeBits(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + if(nbits == 1) { /* compiler should statically compile this case if nbits == 1 */ + WRITEBIT(writer, value); + } else { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + size_t i; + for(i = 0; i != nbits; ++i) { + WRITEBIT(writer, (unsigned char)((value >> i) & 1)); + } + } +} + +/* This one is to use for adding huffman symbol, the value bits are written MSB first */ +static void writeBitsReversed(LodePNGBitWriter* writer, unsigned value, size_t nbits) { + size_t i; + for(i = 0; i != nbits; ++i) { + /* TODO: increase output size only once here rather than in each WRITEBIT */ + WRITEBIT(writer, (unsigned char)((value >> (nbits - 1u - i)) & 1u)); + } +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +typedef struct { + const unsigned char* data; + size_t size; /*size of data in bytes*/ + size_t bitsize; /*size of data in bits, end of valid bp values, should be 8*size*/ + size_t bp; + unsigned buffer; /*buffer for reading bits. NOTE: 'unsigned' must support at least 32 bits*/ +} LodePNGBitReader; + +/* data size argument is in bytes. Returns error if size too large causing overflow */ +static unsigned LodePNGBitReader_init(LodePNGBitReader* reader, const unsigned char* data, size_t size) { + size_t temp; + reader->data = data; + reader->size = size; + /* size in bits, return error if overflow (if size_t is 32 bit this supports up to 500MB) */ + if(lodepng_mulofl(size, 8u, &reader->bitsize)) return 105; + /*ensure incremented bp can be compared to bitsize without overflow even when it would be incremented 32 too much and + trying to ensure 32 more bits*/ + if(lodepng_addofl(reader->bitsize, 64u, &temp)) return 105; + reader->bp = 0; + reader->buffer = 0; + return 0; /*ok*/ +} + +/* +ensureBits functions: +Ensures the reader can at least read nbits bits in one or more readBits calls, +safely even if not enough bits are available. +Returns 1 if there are enough bits available, 0 if not. +*/ + +/*See ensureBits documentation above. This one ensures exactly 1 bit */ +/*static unsigned ensureBits1(LodePNGBitReader* reader) { + if(reader->bp >= reader->bitsize) return 0; + reader->buffer = (unsigned)reader->data[reader->bp >> 3u] >> (reader->bp & 7u); + return 1; +}*/ + +/*See ensureBits documentation above. This one ensures up to 9 bits */ +static unsigned ensureBits9(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 1u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 17 bits */ +static unsigned ensureBits17(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 2u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 25 bits */ +static LODEPNG_INLINE unsigned ensureBits25(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 3u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/*See ensureBits documentation above. This one ensures up to 32 bits */ +static LODEPNG_INLINE unsigned ensureBits32(LodePNGBitReader* reader, size_t nbits) { + size_t start = reader->bp >> 3u; + size_t size = reader->size; + if(start + 4u < size) { + reader->buffer = (unsigned)reader->data[start + 0] | ((unsigned)reader->data[start + 1] << 8u) | + ((unsigned)reader->data[start + 2] << 16u) | ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + reader->buffer |= (((unsigned)reader->data[start + 4] << 24u) << (8u - (reader->bp & 7u))); + return 1; + } else { + reader->buffer = 0; + if(start + 0u < size) reader->buffer |= reader->data[start + 0]; + if(start + 1u < size) reader->buffer |= ((unsigned)reader->data[start + 1] << 8u); + if(start + 2u < size) reader->buffer |= ((unsigned)reader->data[start + 2] << 16u); + if(start + 3u < size) reader->buffer |= ((unsigned)reader->data[start + 3] << 24u); + reader->buffer >>= (reader->bp & 7u); + return reader->bp + nbits <= reader->bitsize; + } +} + +/* Get bits without advancing the bit pointer. Must have enough bits available with ensureBits. Max nbits is 31. */ +static unsigned peekBits(LodePNGBitReader* reader, size_t nbits) { + /* The shift allows nbits to be only up to 31. */ + return reader->buffer & ((1u << nbits) - 1u); +} + +/* Must have enough bits available with ensureBits */ +static void advanceBits(LodePNGBitReader* reader, size_t nbits) { + reader->buffer >>= nbits; + reader->bp += nbits; +} + +/* Must have enough bits available with ensureBits */ +static unsigned readBits(LodePNGBitReader* reader, size_t nbits) { + unsigned result = peekBits(reader, nbits); + advanceBits(reader, nbits); + return result; +} + +#if 0 /*Disable because tests fail due to unused declaration*/ +/* Public for testing only. steps and result must have numsteps values. */ +static unsigned lode_png_test_bitreader(const unsigned char* data, size_t size, + size_t numsteps, const size_t* steps, unsigned* result) { + size_t i; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, data, size); + if(error) return 0; + for(i = 0; i < numsteps; i++) { + size_t step = steps[i]; + unsigned ok; + if(step > 25) ok = ensureBits32(&reader, step); + else if(step > 17) ok = ensureBits25(&reader, step); + else if(step > 9) ok = ensureBits17(&reader, step); + else ok = ensureBits9(&reader, step); + if(!ok) return 0; + result[i] = readBits(&reader, step); + } + return 1; +} +#endif + +#endif /*LODEPNG_COMPILE_DECODER*/ + +static unsigned reverseBits(unsigned bits, unsigned num) { + /*TODO: implement faster lookup table based version when needed*/ + unsigned i, result = 0; + for(i = 0; i < num; i++) result |= ((bits >> (num - i - 1u)) & 1u) << i; + return result; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflate - Huffman / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#define FIRST_LENGTH_CODE_INDEX 257 +#define LAST_LENGTH_CODE_INDEX 285 +/*256 literals, the end code, some length codes, and 2 unused codes*/ +#define NUM_DEFLATE_CODE_SYMBOLS 288 +/*the distance codes have their own symbols, 30 used, 2 unused*/ +#define NUM_DISTANCE_SYMBOLS 32 +/*the code length codes. 0-15: code lengths, 16: copy previous 3-6 times, 17: 3-10 zeros, 18: 11-138 zeros*/ +#define NUM_CODE_LENGTH_CODES 19 + +/*the base lengths represented by codes 257-285*/ +static const unsigned LENGTHBASE[29] + = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, + 67, 83, 99, 115, 131, 163, 195, 227, 258}; + +/*the extra bits used by codes 257-285 (added to base length)*/ +static const unsigned LENGTHEXTRA[29] + = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, 0}; + +/*the base backwards distances (the bits of distance codes appear after length codes and use their own huffman tree)*/ +static const unsigned DISTANCEBASE[30] + = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, + 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577}; + +/*the extra bits of backwards distances (added to base)*/ +static const unsigned DISTANCEEXTRA[30] + = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, + 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13}; + +/*the order in which "code length alphabet code lengths" are stored as specified by deflate, out of this the huffman +tree of the dynamic huffman tree lengths is generated*/ +static const unsigned CLCL_ORDER[NUM_CODE_LENGTH_CODES] + = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + +/* ////////////////////////////////////////////////////////////////////////// */ + +/* +Huffman tree struct, containing multiple representations of the tree +*/ +typedef struct HuffmanTree { + unsigned* codes; /*the huffman codes (bit patterns representing the symbols)*/ + unsigned* lengths; /*the lengths of the huffman codes*/ + unsigned maxbitlen; /*maximum number of bits a single code can get*/ + unsigned numcodes; /*number of symbols in the alphabet = number of codes*/ + /* for reading only */ + unsigned char* table_len; /*length of symbol from lookup table, or max length if secondary lookup needed*/ + unsigned short* table_value; /*value of symbol from lookup table, or pointer to secondary table if needed*/ +} HuffmanTree; + +static void HuffmanTree_init(HuffmanTree* tree) { + tree->codes = 0; + tree->lengths = 0; + tree->table_len = 0; + tree->table_value = 0; +} + +static void HuffmanTree_cleanup(HuffmanTree* tree) { + lodepng_free(tree->codes); + lodepng_free(tree->lengths); + lodepng_free(tree->table_len); + lodepng_free(tree->table_value); +} + +/* amount of bits for first huffman table lookup (aka root bits), see HuffmanTree_makeTable and huffmanDecodeSymbol.*/ +/* values 8u and 9u work the fastest */ +#define FIRSTBITS 9u + +/* a symbol value too big to represent any valid symbol, to indicate reading disallowed huffman bits combination, +which is possible in case of only 0 or 1 present symbols. */ +#define INVALIDSYMBOL 65535u + +/* make table for huffman decoding */ +static unsigned HuffmanTree_makeTable(HuffmanTree* tree) { + static const unsigned headsize = 1u << FIRSTBITS; /*size of the first table*/ + static const unsigned mask = (1u << FIRSTBITS) /*headsize*/ - 1u; + size_t i, numpresent, pointer, size; /*total table size*/ + unsigned* maxlens = (unsigned*)lodepng_malloc(headsize * sizeof(unsigned)); + if(!maxlens) return 83; /*alloc fail*/ + + /* compute maxlens: max total bit length of symbols sharing prefix in the first table*/ + lodepng_memset(maxlens, 0, headsize * sizeof(*maxlens)); + for(i = 0; i < tree->numcodes; i++) { + unsigned symbol = tree->codes[i]; + unsigned l = tree->lengths[i]; + unsigned index; + if(l <= FIRSTBITS) continue; /*symbols that fit in first table don't increase secondary table size*/ + /*get the FIRSTBITS MSBs, the MSBs of the symbol are encoded first. See later comment about the reversing*/ + index = reverseBits(symbol >> (l - FIRSTBITS), FIRSTBITS); + maxlens[index] = LODEPNG_MAX(maxlens[index], l); + } + /* compute total table size: size of first table plus all secondary tables for symbols longer than FIRSTBITS */ + size = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l > FIRSTBITS) size += (1u << (l - FIRSTBITS)); + } + tree->table_len = (unsigned char*)lodepng_malloc(size * sizeof(*tree->table_len)); + tree->table_value = (unsigned short*)lodepng_malloc(size * sizeof(*tree->table_value)); + if(!tree->table_len || !tree->table_value) { + lodepng_free(maxlens); + /* freeing tree->table values is done at a higher scope */ + return 83; /*alloc fail*/ + } + /*initialize with an invalid length to indicate unused entries*/ + for(i = 0; i < size; ++i) tree->table_len[i] = 16; + + /*fill in the first table for long symbols: max prefix size and pointer to secondary tables*/ + pointer = headsize; + for(i = 0; i < headsize; ++i) { + unsigned l = maxlens[i]; + if(l <= FIRSTBITS) continue; + tree->table_len[i] = l; + tree->table_value[i] = pointer; + pointer += (1u << (l - FIRSTBITS)); + } + lodepng_free(maxlens); + + /*fill in the first table for short symbols, or secondary table for long symbols*/ + numpresent = 0; + for(i = 0; i < tree->numcodes; ++i) { + unsigned l = tree->lengths[i]; + unsigned symbol = tree->codes[i]; /*the huffman bit pattern. i itself is the value.*/ + /*reverse bits, because the huffman bits are given in MSB first order but the bit reader reads LSB first*/ + unsigned reverse = reverseBits(symbol, l); + if(l == 0) continue; + numpresent++; + + if(l <= FIRSTBITS) { + /*short symbol, fully in first table, replicated num times if l < FIRSTBITS*/ + unsigned num = 1u << (FIRSTBITS - l); + unsigned j; + for(j = 0; j < num; ++j) { + /*bit reader will read the l bits of symbol first, the remaining FIRSTBITS - l bits go to the MSB's*/ + unsigned index = reverse | (j << l); + if(tree->table_len[index] != 16) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + tree->table_len[index] = l; + tree->table_value[index] = i; + } + } else { + /*long symbol, shares prefix with other long symbols in first lookup table, needs second lookup*/ + /*the FIRSTBITS MSBs of the symbol are the first table index*/ + unsigned index = reverse & mask; + unsigned maxlen = tree->table_len[index]; + /*log2 of secondary table length, should be >= l - FIRSTBITS*/ + unsigned tablelen = maxlen - FIRSTBITS; + unsigned start = tree->table_value[index]; /*starting index in secondary table*/ + unsigned num = 1u << (tablelen - (l - FIRSTBITS)); /*amount of entries of this symbol in secondary table*/ + unsigned j; + if(maxlen < l) return 55; /*invalid tree: long symbol shares prefix with short symbol*/ + for(j = 0; j < num; ++j) { + unsigned reverse2 = reverse >> FIRSTBITS; /* l - FIRSTBITS bits */ + unsigned index2 = start + (reverse2 | (j << (l - FIRSTBITS))); + tree->table_len[index2] = l; + tree->table_value[index2] = i; + } + } + } + + if(numpresent < 2) { + /* In case of exactly 1 symbol, in theory the huffman symbol needs 0 bits, + but deflate uses 1 bit instead. In case of 0 symbols, no symbols can + appear at all, but such huffman tree could still exist (e.g. if distance + codes are never used). In both cases, not all symbols of the table will be + filled in. Fill them in with an invalid symbol value so returning them from + huffmanDecodeSymbol will cause error. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) { + /* As length, use a value smaller than FIRSTBITS for the head table, + and a value larger than FIRSTBITS for the secondary table, to ensure + valid behavior for advanceBits when reading this symbol. */ + tree->table_len[i] = (i < headsize) ? 1 : (FIRSTBITS + 1); + tree->table_value[i] = INVALIDSYMBOL; + } + } + } else { + /* A good huffman tree has N * 2 - 1 nodes, of which N - 1 are internal nodes. + If that is not the case (due to too long length codes), the table will not + have been fully used, and this is an error (not all bit combinations can be + decoded): an oversubscribed huffman tree, indicated by error 55. */ + for(i = 0; i < size; ++i) { + if(tree->table_len[i] == 16) return 55; + } + } + + return 0; +} + +/* +Second step for the ...makeFromLengths and ...makeFromFrequencies functions. +numcodes, lengths and maxbitlen must already be filled in correctly. return +value is error. +*/ +static unsigned HuffmanTree_makeFromLengths2(HuffmanTree* tree) { + unsigned* blcount; + unsigned* nextcode; + unsigned error = 0; + unsigned bits, n; + + tree->codes = (unsigned*)lodepng_malloc(tree->numcodes * sizeof(unsigned)); + blcount = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + nextcode = (unsigned*)lodepng_malloc((tree->maxbitlen + 1) * sizeof(unsigned)); + if(!tree->codes || !blcount || !nextcode) error = 83; /*alloc fail*/ + + if(!error) { + for(n = 0; n != tree->maxbitlen + 1; n++) blcount[n] = nextcode[n] = 0; + /*step 1: count number of instances of each code length*/ + for(bits = 0; bits != tree->numcodes; ++bits) ++blcount[tree->lengths[bits]]; + /*step 2: generate the nextcode values*/ + for(bits = 1; bits <= tree->maxbitlen; ++bits) { + nextcode[bits] = (nextcode[bits - 1] + blcount[bits - 1]) << 1u; + } + /*step 3: generate all the codes*/ + for(n = 0; n != tree->numcodes; ++n) { + if(tree->lengths[n] != 0) { + tree->codes[n] = nextcode[tree->lengths[n]]++; + /*remove superfluous bits from the code*/ + tree->codes[n] &= ((1u << tree->lengths[n]) - 1u); + } + } + } + + lodepng_free(blcount); + lodepng_free(nextcode); + + if(!error) error = HuffmanTree_makeTable(tree); + return error; +} + +/* +given the code lengths (as stored in the PNG file), generate the tree as defined +by Deflate. maxbitlen is the maximum bits that a code in the tree can have. +return value is error. +*/ +static unsigned HuffmanTree_makeFromLengths(HuffmanTree* tree, const unsigned* bitlen, + size_t numcodes, unsigned maxbitlen) { + unsigned i; + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + for(i = 0; i != numcodes; ++i) tree->lengths[i] = bitlen[i]; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + tree->maxbitlen = maxbitlen; + return HuffmanTree_makeFromLengths2(tree); +} + +#ifdef LODEPNG_COMPILE_ENCODER + +/*BPM: Boundary Package Merge, see "A Fast and Space-Economical Algorithm for Length-Limited Coding", +Jyrki Katajainen, Alistair Moffat, Andrew Turpin, 1995.*/ + +/*chain node for boundary package merge*/ +typedef struct BPMNode { + int weight; /*the sum of all weights in this chain*/ + unsigned index; /*index of this leaf node (called "count" in the paper)*/ + struct BPMNode* tail; /*the next nodes in this chain (null if last)*/ + int in_use; +} BPMNode; + +/*lists of chains*/ +typedef struct BPMLists { + /*memory pool*/ + unsigned memsize; + BPMNode* memory; + unsigned numfree; + unsigned nextfree; + BPMNode** freelist; + /*two heads of lookahead chains per list*/ + unsigned listsize; + BPMNode** chains0; + BPMNode** chains1; +} BPMLists; + +/*creates a new chain node with the given parameters, from the memory in the lists */ +static BPMNode* bpmnode_create(BPMLists* lists, int weight, unsigned index, BPMNode* tail) { + unsigned i; + BPMNode* result; + + /*memory full, so garbage collect*/ + if(lists->nextfree >= lists->numfree) { + /*mark only those that are in use*/ + for(i = 0; i != lists->memsize; ++i) lists->memory[i].in_use = 0; + for(i = 0; i != lists->listsize; ++i) { + BPMNode* node; + for(node = lists->chains0[i]; node != 0; node = node->tail) node->in_use = 1; + for(node = lists->chains1[i]; node != 0; node = node->tail) node->in_use = 1; + } + /*collect those that are free*/ + lists->numfree = 0; + for(i = 0; i != lists->memsize; ++i) { + if(!lists->memory[i].in_use) lists->freelist[lists->numfree++] = &lists->memory[i]; + } + lists->nextfree = 0; + } + + result = lists->freelist[lists->nextfree++]; + result->weight = weight; + result->index = index; + result->tail = tail; + return result; +} + +/*sort the leaves with stable mergesort*/ +static void bpmnode_sort(BPMNode* leaves, size_t num) { + BPMNode* mem = (BPMNode*)lodepng_malloc(sizeof(*leaves) * num); + size_t width, counter = 0; + for(width = 1; width < num; width *= 2) { + BPMNode* a = (counter & 1) ? mem : leaves; + BPMNode* b = (counter & 1) ? leaves : mem; + size_t p; + for(p = 0; p < num; p += 2 * width) { + size_t q = (p + width > num) ? num : (p + width); + size_t r = (p + 2 * width > num) ? num : (p + 2 * width); + size_t i = p, j = q, k; + for(k = p; k < r; k++) { + if(i < q && (j >= r || a[i].weight <= a[j].weight)) b[k] = a[i++]; + else b[k] = a[j++]; + } + } + counter++; + } + if(counter & 1) lodepng_memcpy(leaves, mem, sizeof(*leaves) * num); + lodepng_free(mem); +} + +/*Boundary Package Merge step, numpresent is the amount of leaves, and c is the current chain.*/ +static void boundaryPM(BPMLists* lists, BPMNode* leaves, size_t numpresent, int c, int num) { + unsigned lastindex = lists->chains1[c]->index; + + if(c == 0) { + if(lastindex >= numpresent) return; + lists->chains0[c] = lists->chains1[c]; + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, 0); + } else { + /*sum of the weights of the head nodes of the previous lookahead chains.*/ + int sum = lists->chains0[c - 1]->weight + lists->chains1[c - 1]->weight; + lists->chains0[c] = lists->chains1[c]; + if(lastindex < numpresent && sum > leaves[lastindex].weight) { + lists->chains1[c] = bpmnode_create(lists, leaves[lastindex].weight, lastindex + 1, lists->chains1[c]->tail); + return; + } + lists->chains1[c] = bpmnode_create(lists, sum, lastindex, lists->chains1[c - 1]); + /*in the end we are only interested in the chain of the last list, so no + need to recurse if we're at the last one (this gives measurable speedup)*/ + if(num + 1 < (int)(2 * numpresent - 2)) { + boundaryPM(lists, leaves, numpresent, c - 1, num); + boundaryPM(lists, leaves, numpresent, c - 1, num); + } + } +} + +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + unsigned i; + size_t numpresent = 0; /*number of symbols with non-zero frequency*/ + BPMNode* leaves; /*the symbols, only those with > 0 frequency*/ + + if(numcodes == 0) return 80; /*error: a tree of 0 symbols is not supposed to be made*/ + if((1u << maxbitlen) < (unsigned)numcodes) return 80; /*error: represent all symbols*/ + + leaves = (BPMNode*)lodepng_malloc(numcodes * sizeof(*leaves)); + if(!leaves) return 83; /*alloc fail*/ + + for(i = 0; i != numcodes; ++i) { + if(frequencies[i] > 0) { + leaves[numpresent].weight = (int)frequencies[i]; + leaves[numpresent].index = i; + ++numpresent; + } + } + + lodepng_memset(lengths, 0, numcodes * sizeof(*lengths)); + + /*ensure at least two present symbols. There should be at least one symbol + according to RFC 1951 section 3.2.7. Some decoders incorrectly require two. To + make these work as well ensure there are at least two symbols. The + Package-Merge code below also doesn't work correctly if there's only one + symbol, it'd give it the theoretical 0 bits but in practice zlib wants 1 bit*/ + if(numpresent == 0) { + lengths[0] = lengths[1] = 1; /*note that for RFC 1951 section 3.2.7, only lengths[0] = 1 is needed*/ + } else if(numpresent == 1) { + lengths[leaves[0].index] = 1; + lengths[leaves[0].index == 0 ? 1 : 0] = 1; + } else { + BPMLists lists; + BPMNode* node; + + bpmnode_sort(leaves, numpresent); + + lists.listsize = maxbitlen; + lists.memsize = 2 * maxbitlen * (maxbitlen + 1); + lists.nextfree = 0; + lists.numfree = lists.memsize; + lists.memory = (BPMNode*)lodepng_malloc(lists.memsize * sizeof(*lists.memory)); + lists.freelist = (BPMNode**)lodepng_malloc(lists.memsize * sizeof(BPMNode*)); + lists.chains0 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + lists.chains1 = (BPMNode**)lodepng_malloc(lists.listsize * sizeof(BPMNode*)); + if(!lists.memory || !lists.freelist || !lists.chains0 || !lists.chains1) error = 83; /*alloc fail*/ + + if(!error) { + for(i = 0; i != lists.memsize; ++i) lists.freelist[i] = &lists.memory[i]; + + bpmnode_create(&lists, leaves[0].weight, 1, 0); + bpmnode_create(&lists, leaves[1].weight, 2, 0); + + for(i = 0; i != lists.listsize; ++i) { + lists.chains0[i] = &lists.memory[0]; + lists.chains1[i] = &lists.memory[1]; + } + + /*each boundaryPM call adds one chain to the last list, and we need 2 * numpresent - 2 chains.*/ + for(i = 2; i != 2 * numpresent - 2; ++i) boundaryPM(&lists, leaves, numpresent, (int)maxbitlen - 1, (int)i); + + for(node = lists.chains1[maxbitlen - 1]; node; node = node->tail) { + for(i = 0; i != node->index; ++i) ++lengths[leaves[i].index]; + } + } + + lodepng_free(lists.memory); + lodepng_free(lists.freelist); + lodepng_free(lists.chains0); + lodepng_free(lists.chains1); + } + + lodepng_free(leaves); + return error; +} + +/*Create the Huffman tree given the symbol frequencies*/ +static unsigned HuffmanTree_makeFromFrequencies(HuffmanTree* tree, const unsigned* frequencies, + size_t mincodes, size_t numcodes, unsigned maxbitlen) { + unsigned error = 0; + while(!frequencies[numcodes - 1] && numcodes > mincodes) --numcodes; /*trim zeroes*/ + tree->lengths = (unsigned*)lodepng_malloc(numcodes * sizeof(unsigned)); + if(!tree->lengths) return 83; /*alloc fail*/ + tree->maxbitlen = maxbitlen; + tree->numcodes = (unsigned)numcodes; /*number of symbols*/ + + error = lodepng_huffman_code_lengths(tree->lengths, frequencies, numcodes, maxbitlen); + if(!error) error = HuffmanTree_makeFromLengths2(tree); + return error; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*get the literal and length code tree of a deflated block with fixed tree, as per the deflate specification*/ +static unsigned generateFixedLitLenTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*288 possible codes: 0-255=literals, 256=endcode, 257-285=lengthcodes, 286-287=unused*/ + for(i = 0; i <= 143; ++i) bitlen[i] = 8; + for(i = 144; i <= 255; ++i) bitlen[i] = 9; + for(i = 256; i <= 279; ++i) bitlen[i] = 7; + for(i = 280; i <= 287; ++i) bitlen[i] = 8; + + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DEFLATE_CODE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +/*get the distance code tree of a deflated block with fixed tree, as specified in the deflate specification*/ +static unsigned generateFixedDistanceTree(HuffmanTree* tree) { + unsigned i, error = 0; + unsigned* bitlen = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen) return 83; /*alloc fail*/ + + /*there are 32 distance codes, but 30-31 are unused*/ + for(i = 0; i != NUM_DISTANCE_SYMBOLS; ++i) bitlen[i] = 5; + error = HuffmanTree_makeFromLengths(tree, bitlen, NUM_DISTANCE_SYMBOLS, 15); + + lodepng_free(bitlen); + return error; +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* +returns the code. The bit reader must already have been ensured at least 15 bits +*/ +static unsigned huffmanDecodeSymbol(LodePNGBitReader* reader, const HuffmanTree* codetree) { + unsigned short code = peekBits(reader, FIRSTBITS); + unsigned short l = codetree->table_len[code]; + unsigned short value = codetree->table_value[code]; + if(l <= FIRSTBITS) { + advanceBits(reader, l); + return value; + } else { + unsigned index2; + advanceBits(reader, FIRSTBITS); + index2 = value + peekBits(reader, l - FIRSTBITS); + advanceBits(reader, codetree->table_len[index2] - FIRSTBITS); + return codetree->table_value[index2]; + } +} +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Inflator (Decompressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*get the tree of a deflated block with fixed tree, as specified in the deflate specification +Returns error code.*/ +static unsigned getTreeInflateFixed(HuffmanTree* tree_ll, HuffmanTree* tree_d) { + unsigned error = generateFixedLitLenTree(tree_ll); + if(error) return error; + return generateFixedDistanceTree(tree_d); +} + +/*get the tree of a deflated block with dynamic tree, the tree itself is also Huffman compressed with a known tree*/ +static unsigned getTreeInflateDynamic(HuffmanTree* tree_ll, HuffmanTree* tree_d, + LodePNGBitReader* reader) { + /*make sure that length values that aren't filled in will be 0, or a wrong tree will be generated*/ + unsigned error = 0; + unsigned n, HLIT, HDIST, HCLEN, i; + + /*see comments in deflateDynamic for explanation of the context and these variables, it is analogous*/ + unsigned* bitlen_ll = 0; /*lit,len code lengths*/ + unsigned* bitlen_d = 0; /*dist code lengths*/ + /*code length code lengths ("clcl"), the bit lengths of the huffman tree used to compress bitlen_ll and bitlen_d*/ + unsigned* bitlen_cl = 0; + HuffmanTree tree_cl; /*the code tree for code length codes (the huffman tree for compressed huffman trees)*/ + + if(!ensureBits17(reader, 14)) return 49; /*error: the bit pointer is or will go past the memory*/ + + /*number of literal/length codes + 257. Unlike the spec, the value 257 is added to it here already*/ + HLIT = readBits(reader, 5) + 257; + /*number of distance codes. Unlike the spec, the value 1 is added to it here already*/ + HDIST = readBits(reader, 5) + 1; + /*number of code length codes. Unlike the spec, the value 4 is added to it here already*/ + HCLEN = readBits(reader, 4) + 4; + + bitlen_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(unsigned)); + if(!bitlen_cl) return 83 /*alloc fail*/; + + HuffmanTree_init(&tree_cl); + + while(!error) { + /*read the code length codes out of 3 * (amount of code length codes) bits*/ + if(lodepng_gtofl(reader->bp, HCLEN * 3, reader->bitsize)) { + ERROR_BREAK(50); /*error: the bit pointer is or will go past the memory*/ + } + for(i = 0; i != HCLEN; ++i) { + ensureBits9(reader, 3); /*out of bounds already checked above */ + bitlen_cl[CLCL_ORDER[i]] = readBits(reader, 3); + } + for(i = HCLEN; i != NUM_CODE_LENGTH_CODES; ++i) { + bitlen_cl[CLCL_ORDER[i]] = 0; + } + + error = HuffmanTree_makeFromLengths(&tree_cl, bitlen_cl, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*now we can use this tree to read the lengths for the tree that this function will return*/ + bitlen_ll = (unsigned*)lodepng_malloc(NUM_DEFLATE_CODE_SYMBOLS * sizeof(unsigned)); + bitlen_d = (unsigned*)lodepng_malloc(NUM_DISTANCE_SYMBOLS * sizeof(unsigned)); + if(!bitlen_ll || !bitlen_d) ERROR_BREAK(83 /*alloc fail*/); + lodepng_memset(bitlen_ll, 0, NUM_DEFLATE_CODE_SYMBOLS * sizeof(*bitlen_ll)); + lodepng_memset(bitlen_d, 0, NUM_DISTANCE_SYMBOLS * sizeof(*bitlen_d)); + + /*i is the current symbol we're reading in the part that contains the code lengths of lit/len and dist codes*/ + i = 0; + while(i < HLIT + HDIST) { + unsigned code; + ensureBits25(reader, 22); /* up to 15 bits for huffman code, up to 7 extra bits below*/ + code = huffmanDecodeSymbol(reader, &tree_cl); + if(code <= 15) /*a length code*/ { + if(i < HLIT) bitlen_ll[i] = code; + else bitlen_d[i - HLIT] = code; + ++i; + } else if(code == 16) /*repeat previous*/ { + unsigned replength = 3; /*read in the 2 bits that indicate repeat length (3-6)*/ + unsigned value; /*set value to the previous code*/ + + if(i == 0) ERROR_BREAK(54); /*can't repeat previous if i is 0*/ + + replength += readBits(reader, 2); + + if(i < HLIT + 1) value = bitlen_ll[i - 1]; + else value = bitlen_d[i - HLIT - 1]; + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(13); /*error: i is larger than the amount of codes*/ + if(i < HLIT) bitlen_ll[i] = value; + else bitlen_d[i - HLIT] = value; + ++i; + } + } else if(code == 17) /*repeat "0" 3-10 times*/ { + unsigned replength = 3; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 3); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(14); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else if(code == 18) /*repeat "0" 11-138 times*/ { + unsigned replength = 11; /*read in the bits that indicate repeat length*/ + replength += readBits(reader, 7); + + /*repeat this value in the next lengths*/ + for(n = 0; n < replength; ++n) { + if(i >= HLIT + HDIST) ERROR_BREAK(15); /*error: i is larger than the amount of codes*/ + + if(i < HLIT) bitlen_ll[i] = 0; + else bitlen_d[i - HLIT] = 0; + ++i; + } + } else /*if(code == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(50); /*error, bit pointer jumps past memory*/ + } + } + if(error) break; + + if(bitlen_ll[256] == 0) ERROR_BREAK(64); /*the length of the end code 256 must be larger than 0*/ + + /*now we've finally got HLIT and HDIST, so generate the code trees, and the function is done*/ + error = HuffmanTree_makeFromLengths(tree_ll, bitlen_ll, NUM_DEFLATE_CODE_SYMBOLS, 15); + if(error) break; + error = HuffmanTree_makeFromLengths(tree_d, bitlen_d, NUM_DISTANCE_SYMBOLS, 15); + + break; /*end of error-while*/ + } + + lodepng_free(bitlen_cl); + lodepng_free(bitlen_ll); + lodepng_free(bitlen_d); + HuffmanTree_cleanup(&tree_cl); + + return error; +} + +/*inflate a block with dynamic of fixed Huffman tree. btype must be 1 or 2.*/ +static unsigned inflateHuffmanBlock(ucvector* out, LodePNGBitReader* reader, + unsigned btype, size_t max_output_size) { + unsigned error = 0; + HuffmanTree tree_ll; /*the huffman tree for literal and length codes*/ + HuffmanTree tree_d; /*the huffman tree for distance codes*/ + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + if(btype == 1) error = getTreeInflateFixed(&tree_ll, &tree_d); + else /*if(btype == 2)*/ error = getTreeInflateDynamic(&tree_ll, &tree_d, reader); + + while(!error) /*decode all symbols until end reached, breaks at end code*/ { + /*code_ll is literal, length or end code*/ + unsigned code_ll; + ensureBits25(reader, 20); /* up to 15 for the huffman symbol, up to 5 for the length extra bits */ + code_ll = huffmanDecodeSymbol(reader, &tree_ll); + if(code_ll <= 255) /*literal symbol*/ { + if(!ucvector_resize(out, out->size + 1)) ERROR_BREAK(83 /*alloc fail*/); + out->data[out->size - 1] = (unsigned char)code_ll; + } else if(code_ll >= FIRST_LENGTH_CODE_INDEX && code_ll <= LAST_LENGTH_CODE_INDEX) /*length code*/ { + unsigned code_d, distance; + unsigned numextrabits_l, numextrabits_d; /*extra bits for length and distance*/ + size_t start, backward, length; + + /*part 1: get length base*/ + length = LENGTHBASE[code_ll - FIRST_LENGTH_CODE_INDEX]; + + /*part 2: get extra bits and add the value of that to length*/ + numextrabits_l = LENGTHEXTRA[code_ll - FIRST_LENGTH_CODE_INDEX]; + if(numextrabits_l != 0) { + /* bits already ensured above */ + length += readBits(reader, numextrabits_l); + } + + /*part 3: get distance code*/ + ensureBits32(reader, 28); /* up to 15 for the huffman symbol, up to 13 for the extra bits */ + code_d = huffmanDecodeSymbol(reader, &tree_d); + if(code_d > 29) { + if(code_d <= 31) { + ERROR_BREAK(18); /*error: invalid distance code (30-31 are never used)*/ + } else /* if(code_d == INVALIDSYMBOL) */{ + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + } + distance = DISTANCEBASE[code_d]; + + /*part 4: get extra bits from distance*/ + numextrabits_d = DISTANCEEXTRA[code_d]; + if(numextrabits_d != 0) { + /* bits already ensured above */ + distance += readBits(reader, numextrabits_d); + } + + /*part 5: fill in all the out[n] values based on the length and dist*/ + start = out->size; + if(distance > start) ERROR_BREAK(52); /*too long backward distance*/ + backward = start - distance; + + if(!ucvector_resize(out, out->size + length)) ERROR_BREAK(83 /*alloc fail*/); + if(distance < length) { + size_t forward; + lodepng_memcpy(out->data + start, out->data + backward, distance); + start += distance; + for(forward = distance; forward < length; ++forward) { + out->data[start++] = out->data[backward++]; + } + } else { + lodepng_memcpy(out->data + start, out->data + backward, length); + } + } else if(code_ll == 256) { + break; /*end code, break the loop*/ + } else /*if(code_ll == INVALIDSYMBOL)*/ { + ERROR_BREAK(16); /*error: tried to read disallowed huffman symbol*/ + } + /*check if any of the ensureBits above went out of bounds*/ + if(reader->bp > reader->bitsize) { + /*return error code 10 or 11 depending on the situation that happened in huffmanDecodeSymbol + (10=no endcode, 11=wrong jump outside of tree)*/ + /* TODO: revise error codes 10,11,50: the above comment is no longer valid */ + ERROR_BREAK(51); /*error, bit pointer jumps past memory*/ + } + if(max_output_size && out->size > max_output_size) { + ERROR_BREAK(109); /*error, larger than max size*/ + } + } + + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned inflateNoCompression(ucvector* out, LodePNGBitReader* reader, + const LodePNGDecompressSettings* settings) { + size_t bytepos; + size_t size = reader->size; + unsigned LEN, NLEN, error = 0; + + /*go to first boundary of byte*/ + bytepos = (reader->bp + 7u) >> 3u; + + /*read LEN (2 bytes) and NLEN (2 bytes)*/ + if(bytepos + 4 >= size) return 52; /*error, bit pointer will jump past memory*/ + LEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + NLEN = (unsigned)reader->data[bytepos] + ((unsigned)reader->data[bytepos + 1] << 8u); bytepos += 2; + + /*check if 16-bit NLEN is really the one's complement of LEN*/ + if(!settings->ignore_nlen && LEN + NLEN != 65535) { + return 21; /*error: NLEN is not one's complement of LEN*/ + } + + if(!ucvector_resize(out, out->size + LEN)) return 83; /*alloc fail*/ + + /*read the literal data: LEN bytes are now stored in the out buffer*/ + if(bytepos + LEN > size) return 23; /*error: reading outside of in buffer*/ + + lodepng_memcpy(out->data + out->size - LEN, reader->data + bytepos, LEN); + bytepos += LEN; + + reader->bp = bytepos << 3u; + + return error; +} + +static unsigned lodepng_inflatev(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned BFINAL = 0; + LodePNGBitReader reader; + unsigned error = LodePNGBitReader_init(&reader, in, insize); + + if(error) return error; + + while(!BFINAL) { + unsigned BTYPE; + if(!ensureBits9(&reader, 3)) return 52; /*error, bit pointer will jump past memory*/ + BFINAL = readBits(&reader, 1); + BTYPE = readBits(&reader, 2); + + if(BTYPE == 3) return 20; /*error: invalid BTYPE*/ + else if(BTYPE == 0) error = inflateNoCompression(out, &reader, settings); /*no compression*/ + else error = inflateHuffmanBlock(out, &reader, BTYPE, settings->max_output_size); /*compression, BTYPE 01 or 10*/ + if(!error && settings->max_output_size && out->size > settings->max_output_size) error = 109; + if(error) break; + } + + return error; +} + +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_inflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned inflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + if(settings->custom_inflate) { + unsigned error = settings->custom_inflate(&out->data, &out->size, in, insize, settings); + out->allocsize = out->size; + if(error) { + /*the custom inflate is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && out->size > settings->max_output_size) error = 109; + } + return error; + } else { + return lodepng_inflatev(out, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Deflator (Compressor) / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static const size_t MAX_SUPPORTED_DEFLATE_LENGTH = 258; + +/*search the index in the array, that has the largest value smaller than or equal to the given value, +given array must be sorted (if no value is smaller, it returns the size of the given array)*/ +static size_t searchCodeIndex(const unsigned* array, size_t array_size, size_t value) { + /*binary search (only small gain over linear). TODO: use CPU log2 instruction for getting symbols instead*/ + size_t left = 1; + size_t right = array_size - 1; + + while(left <= right) { + size_t mid = (left + right) >> 1; + if(array[mid] >= value) right = mid - 1; + else left = mid + 1; + } + if(left >= array_size || array[left] > value) left--; + return left; +} + +static void addLengthDistance(uivector* values, size_t length, size_t distance) { + /*values in encoded vector are those used by deflate: + 0-255: literal bytes + 256: end + 257-285: length/distance pair (length code, followed by extra length bits, distance code, extra distance bits) + 286-287: invalid*/ + + unsigned length_code = (unsigned)searchCodeIndex(LENGTHBASE, 29, length); + unsigned extra_length = (unsigned)(length - LENGTHBASE[length_code]); + unsigned dist_code = (unsigned)searchCodeIndex(DISTANCEBASE, 30, distance); + unsigned extra_distance = (unsigned)(distance - DISTANCEBASE[dist_code]); + + size_t pos = values->size; + /*TODO: return error when this fails (out of memory)*/ + unsigned ok = uivector_resize(values, values->size + 4); + if(ok) { + values->data[pos + 0] = length_code + FIRST_LENGTH_CODE_INDEX; + values->data[pos + 1] = extra_length; + values->data[pos + 2] = dist_code; + values->data[pos + 3] = extra_distance; + } +} + +/*3 bytes of data get encoded into two bytes. The hash cannot use more than 3 +bytes as input because 3 is the minimum match length for deflate*/ +static const unsigned HASH_NUM_VALUES = 65536; +static const unsigned HASH_BIT_MASK = 65535; /*HASH_NUM_VALUES - 1, but C90 does not like that as initializer*/ + +typedef struct Hash { + int* head; /*hash value to head circular pos - can be outdated if went around window*/ + /*circular pos to prev circular pos*/ + unsigned short* chain; + int* val; /*circular pos to hash value*/ + + /*TODO: do this not only for zeros but for any repeated byte. However for PNG + it's always going to be the zeros that dominate, so not important for PNG*/ + int* headz; /*similar to head, but for chainz*/ + unsigned short* chainz; /*those with same amount of zeros*/ + unsigned short* zeros; /*length of zeros streak, used as a second hash chain*/ +} Hash; + +static unsigned hash_init(Hash* hash, unsigned windowsize) { + unsigned i; + hash->head = (int*)lodepng_malloc(sizeof(int) * HASH_NUM_VALUES); + hash->val = (int*)lodepng_malloc(sizeof(int) * windowsize); + hash->chain = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + hash->zeros = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + hash->headz = (int*)lodepng_malloc(sizeof(int) * (MAX_SUPPORTED_DEFLATE_LENGTH + 1)); + hash->chainz = (unsigned short*)lodepng_malloc(sizeof(unsigned short) * windowsize); + + if(!hash->head || !hash->chain || !hash->val || !hash->headz|| !hash->chainz || !hash->zeros) { + return 83; /*alloc fail*/ + } + + /*initialize hash table*/ + for(i = 0; i != HASH_NUM_VALUES; ++i) hash->head[i] = -1; + for(i = 0; i != windowsize; ++i) hash->val[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chain[i] = i; /*same value as index indicates uninitialized*/ + + for(i = 0; i <= MAX_SUPPORTED_DEFLATE_LENGTH; ++i) hash->headz[i] = -1; + for(i = 0; i != windowsize; ++i) hash->chainz[i] = i; /*same value as index indicates uninitialized*/ + + return 0; +} + +static void hash_cleanup(Hash* hash) { + lodepng_free(hash->head); + lodepng_free(hash->val); + lodepng_free(hash->chain); + + lodepng_free(hash->zeros); + lodepng_free(hash->headz); + lodepng_free(hash->chainz); +} + + + +static unsigned getHash(const unsigned char* data, size_t size, size_t pos) { + unsigned result = 0; + if(pos + 2 < size) { + /*A simple shift and xor hash is used. Since the data of PNGs is dominated + by zeroes due to the filters, a better hash does not have a significant + effect on speed in traversing the chain, and causes more time spend on + calculating the hash.*/ + result ^= ((unsigned)data[pos + 0] << 0u); + result ^= ((unsigned)data[pos + 1] << 4u); + result ^= ((unsigned)data[pos + 2] << 8u); + } else { + size_t amount, i; + if(pos >= size) return 0; + amount = size - pos; + for(i = 0; i != amount; ++i) result ^= ((unsigned)data[pos + i] << (i * 8u)); + } + return result & HASH_BIT_MASK; +} + +static unsigned countZeros(const unsigned char* data, size_t size, size_t pos) { + const unsigned char* start = data + pos; + const unsigned char* end = start + MAX_SUPPORTED_DEFLATE_LENGTH; + if(end > data + size) end = data + size; + data = start; + while(data != end && *data == 0) ++data; + /*subtracting two addresses returned as 32-bit number (max value is MAX_SUPPORTED_DEFLATE_LENGTH)*/ + return (unsigned)(data - start); +} + +/*wpos = pos & (windowsize - 1)*/ +static void updateHashChain(Hash* hash, size_t wpos, unsigned hashval, unsigned short numzeros) { + hash->val[wpos] = (int)hashval; + if(hash->head[hashval] != -1) hash->chain[wpos] = hash->head[hashval]; + hash->head[hashval] = (int)wpos; + + hash->zeros[wpos] = numzeros; + if(hash->headz[numzeros] != -1) hash->chainz[wpos] = hash->headz[numzeros]; + hash->headz[numzeros] = (int)wpos; +} + +/* +LZ77-encode the data. Return value is error code. The input are raw bytes, the output +is in the form of unsigned integers with codes representing for example literal bytes, or +length/distance pairs. +It uses a hash table technique to let it encode faster. When doing LZ77 encoding, a +sliding window (of windowsize) is used, and all past bytes in that window can be used as +the "dictionary". A brute force search through all possible distances would be slow, and +this hash technique is one out of several ways to speed this up. +*/ +static unsigned encodeLZ77(uivector* out, Hash* hash, + const unsigned char* in, size_t inpos, size_t insize, unsigned windowsize, + unsigned minmatch, unsigned nicematch, unsigned lazymatching) { + size_t pos; + unsigned i, error = 0; + /*for large window lengths, assume the user wants no compression loss. Otherwise, max hash chain length speedup.*/ + unsigned maxchainlength = windowsize >= 8192 ? windowsize : windowsize / 8u; + unsigned maxlazymatch = windowsize >= 8192 ? MAX_SUPPORTED_DEFLATE_LENGTH : 64; + + unsigned usezeros = 1; /*not sure if setting it to false for windowsize < 8192 is better or worse*/ + unsigned numzeros = 0; + + unsigned offset; /*the offset represents the distance in LZ77 terminology*/ + unsigned length; + unsigned lazy = 0; + unsigned lazylength = 0, lazyoffset = 0; + unsigned hashval; + unsigned current_offset, current_length; + unsigned prev_offset; + const unsigned char *lastptr, *foreptr, *backptr; + unsigned hashpos; + + if(windowsize == 0 || windowsize > 32768) return 60; /*error: windowsize smaller/larger than allowed*/ + if((windowsize & (windowsize - 1)) != 0) return 90; /*error: must be power of two*/ + + if(nicematch > MAX_SUPPORTED_DEFLATE_LENGTH) nicematch = MAX_SUPPORTED_DEFLATE_LENGTH; + + for(pos = inpos; pos < insize; ++pos) { + size_t wpos = pos & (windowsize - 1); /*position for in 'circular' hash buffers*/ + unsigned chainlength = 0; + + hashval = getHash(in, insize, pos); + + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + + updateHashChain(hash, wpos, hashval, numzeros); + + /*the length and offset found for the current position*/ + length = 0; + offset = 0; + + hashpos = hash->chain[wpos]; + + lastptr = &in[insize < pos + MAX_SUPPORTED_DEFLATE_LENGTH ? insize : pos + MAX_SUPPORTED_DEFLATE_LENGTH]; + + /*search for the longest string*/ + prev_offset = 0; + for(;;) { + if(chainlength++ >= maxchainlength) break; + current_offset = (unsigned)(hashpos <= wpos ? wpos - hashpos : wpos - hashpos + windowsize); + + if(current_offset < prev_offset) break; /*stop when went completely around the circular buffer*/ + prev_offset = current_offset; + if(current_offset > 0) { + /*test the next characters*/ + foreptr = &in[pos]; + backptr = &in[pos - current_offset]; + + /*common case in PNGs is lots of zeros. Quickly skip over them as a speedup*/ + if(numzeros >= 3) { + unsigned skip = hash->zeros[hashpos]; + if(skip > numzeros) skip = numzeros; + backptr += skip; + foreptr += skip; + } + + while(foreptr != lastptr && *backptr == *foreptr) /*maximum supported length by deflate is max length*/ { + ++backptr; + ++foreptr; + } + current_length = (unsigned)(foreptr - &in[pos]); + + if(current_length > length) { + length = current_length; /*the longest length*/ + offset = current_offset; /*the offset that is related to this longest length*/ + /*jump out once a length of max length is found (speed gain). This also jumps + out if length is MAX_SUPPORTED_DEFLATE_LENGTH*/ + if(current_length >= nicematch) break; + } + } + + if(hashpos == hash->chain[hashpos]) break; + + if(numzeros >= 3 && length > numzeros) { + hashpos = hash->chainz[hashpos]; + if(hash->zeros[hashpos] != numzeros) break; + } else { + hashpos = hash->chain[hashpos]; + /*outdated hash value, happens if particular value was not encountered in whole last window*/ + if(hash->val[hashpos] != (int)hashval) break; + } + } + + if(lazymatching) { + if(!lazy && length >= 3 && length <= maxlazymatch && length < MAX_SUPPORTED_DEFLATE_LENGTH) { + lazy = 1; + lazylength = length; + lazyoffset = offset; + continue; /*try the next byte*/ + } + if(lazy) { + lazy = 0; + if(pos == 0) ERROR_BREAK(81); + if(length > lazylength + 1) { + /*push the previous character as literal*/ + if(!uivector_push_back(out, in[pos - 1])) ERROR_BREAK(83 /*alloc fail*/); + } else { + length = lazylength; + offset = lazyoffset; + hash->head[hashval] = -1; /*the same hashchain update will be done, this ensures no wrong alteration*/ + hash->headz[numzeros] = -1; /*idem*/ + --pos; + } + } + } + if(length >= 3 && offset > windowsize) ERROR_BREAK(86 /*too big (or overflown negative) offset*/); + + /*encode it as length/distance pair or literal value*/ + if(length < 3) /*only lengths of 3 or higher are supported as length/distance pair*/ { + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else if(length < minmatch || (length == 3 && offset > 4096)) { + /*compensate for the fact that longer offsets have more extra bits, a + length of only 3 may be not worth it then*/ + if(!uivector_push_back(out, in[pos])) ERROR_BREAK(83 /*alloc fail*/); + } else { + addLengthDistance(out, length, offset); + for(i = 1; i < length; ++i) { + ++pos; + wpos = pos & (windowsize - 1); + hashval = getHash(in, insize, pos); + if(usezeros && hashval == 0) { + if(numzeros == 0) numzeros = countZeros(in, insize, pos); + else if(pos + numzeros > insize || in[pos + numzeros - 1] != 0) --numzeros; + } else { + numzeros = 0; + } + updateHashChain(hash, wpos, hashval, numzeros); + } + } + } /*end of the loop through each character of input*/ + + return error; +} + +/* /////////////////////////////////////////////////////////////////////////// */ + +static unsigned deflateNoCompression(ucvector* out, const unsigned char* data, size_t datasize) { + /*non compressed deflate block data: 1 bit BFINAL,2 bits BTYPE,(5 bits): it jumps to start of next byte, + 2 bytes LEN, 2 bytes NLEN, LEN bytes literal DATA*/ + + size_t i, numdeflateblocks = (datasize + 65534u) / 65535u; + unsigned datapos = 0; + for(i = 0; i != numdeflateblocks; ++i) { + unsigned BFINAL, BTYPE, LEN, NLEN; + unsigned char firstbyte; + size_t pos = out->size; + + BFINAL = (i == numdeflateblocks - 1); + BTYPE = 0; + + LEN = 65535; + if(datasize - datapos < 65535u) LEN = (unsigned)datasize - datapos; + NLEN = 65535 - LEN; + + if(!ucvector_resize(out, out->size + LEN + 5)) return 83; /*alloc fail*/ + + firstbyte = (unsigned char)(BFINAL + ((BTYPE & 1u) << 1u) + ((BTYPE & 2u) << 1u)); + out->data[pos + 0] = firstbyte; + out->data[pos + 1] = (unsigned char)(LEN & 255); + out->data[pos + 2] = (unsigned char)(LEN >> 8u); + out->data[pos + 3] = (unsigned char)(NLEN & 255); + out->data[pos + 4] = (unsigned char)(NLEN >> 8u); + lodepng_memcpy(out->data + pos + 5, data + datapos, LEN); + datapos += LEN; + } + + return 0; +} + +/* +write the lz77-encoded data, which has lit, len and dist codes, to compressed stream using huffman trees. +tree_ll: the tree for lit and len codes. +tree_d: the tree for distance codes. +*/ +static void writeLZ77data(LodePNGBitWriter* writer, const uivector* lz77_encoded, + const HuffmanTree* tree_ll, const HuffmanTree* tree_d) { + size_t i = 0; + for(i = 0; i != lz77_encoded->size; ++i) { + unsigned val = lz77_encoded->data[i]; + writeBitsReversed(writer, tree_ll->codes[val], tree_ll->lengths[val]); + if(val > 256) /*for a length code, 3 more things have to be added*/ { + unsigned length_index = val - FIRST_LENGTH_CODE_INDEX; + unsigned n_length_extra_bits = LENGTHEXTRA[length_index]; + unsigned length_extra_bits = lz77_encoded->data[++i]; + + unsigned distance_code = lz77_encoded->data[++i]; + + unsigned distance_index = distance_code; + unsigned n_distance_extra_bits = DISTANCEEXTRA[distance_index]; + unsigned distance_extra_bits = lz77_encoded->data[++i]; + + writeBits(writer, length_extra_bits, n_length_extra_bits); + writeBitsReversed(writer, tree_d->codes[distance_code], tree_d->lengths[distance_code]); + writeBits(writer, distance_extra_bits, n_distance_extra_bits); + } + } +} + +/*Deflate for a block of type "dynamic", that is, with freely, optimally, created huffman trees*/ +static unsigned deflateDynamic(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + unsigned error = 0; + + /* + A block is compressed as follows: The PNG data is lz77 encoded, resulting in + literal bytes and length/distance pairs. This is then huffman compressed with + two huffman trees. One huffman tree is used for the lit and len values ("ll"), + another huffman tree is used for the dist values ("d"). These two trees are + stored using their code lengths, and to compress even more these code lengths + are also run-length encoded and huffman compressed. This gives a huffman tree + of code lengths "cl". The code lengths used to describe this third tree are + the code length code lengths ("clcl"). + */ + + /*The lz77 encoded data, represented with integers since there will also be length and distance codes in it*/ + uivector lz77_encoded; + HuffmanTree tree_ll; /*tree for lit,len values*/ + HuffmanTree tree_d; /*tree for distance codes*/ + HuffmanTree tree_cl; /*tree for encoding the code lengths representing tree_ll and tree_d*/ + unsigned* frequencies_ll = 0; /*frequency of lit,len codes*/ + unsigned* frequencies_d = 0; /*frequency of dist codes*/ + unsigned* frequencies_cl = 0; /*frequency of code length codes*/ + unsigned* bitlen_lld = 0; /*lit,len,dist code lengths (int bits), literally (without repeat codes).*/ + unsigned* bitlen_lld_e = 0; /*bitlen_lld encoded with repeat codes (this is a rudimentary run length compression)*/ + size_t datasize = dataend - datapos; + + /* + If we could call "bitlen_cl" the the code length code lengths ("clcl"), that is the bit lengths of codes to represent + tree_cl in CLCL_ORDER, then due to the huffman compression of huffman tree representations ("two levels"), there are + some analogies: + bitlen_lld is to tree_cl what data is to tree_ll and tree_d. + bitlen_lld_e is to bitlen_lld what lz77_encoded is to data. + bitlen_cl is to bitlen_lld_e what bitlen_lld is to lz77_encoded. + */ + + unsigned BFINAL = final; + size_t i; + size_t numcodes_ll, numcodes_d, numcodes_lld, numcodes_lld_e, numcodes_cl; + unsigned HLIT, HDIST, HCLEN; + + uivector_init(&lz77_encoded); + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + HuffmanTree_init(&tree_cl); + /* could fit on stack, but >1KB is on the larger side so allocate instead */ + frequencies_ll = (unsigned*)lodepng_malloc(286 * sizeof(*frequencies_ll)); + frequencies_d = (unsigned*)lodepng_malloc(30 * sizeof(*frequencies_d)); + frequencies_cl = (unsigned*)lodepng_malloc(NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(!frequencies_ll || !frequencies_d || !frequencies_cl) error = 83; /*alloc fail*/ + + /*This while loop never loops due to a break at the end, it is here to + allow breaking out of it to the cleanup phase on error conditions.*/ + while(!error) { + lodepng_memset(frequencies_ll, 0, 286 * sizeof(*frequencies_ll)); + lodepng_memset(frequencies_d, 0, 30 * sizeof(*frequencies_d)); + lodepng_memset(frequencies_cl, 0, NUM_CODE_LENGTH_CODES * sizeof(*frequencies_cl)); + + if(settings->use_lz77) { + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(error) break; + } else { + if(!uivector_resize(&lz77_encoded, datasize)) ERROR_BREAK(83 /*alloc fail*/); + for(i = datapos; i < dataend; ++i) lz77_encoded.data[i - datapos] = data[i]; /*no LZ77, but still will be Huffman compressed*/ + } + + /*Count the frequencies of lit, len and dist codes*/ + for(i = 0; i != lz77_encoded.size; ++i) { + unsigned symbol = lz77_encoded.data[i]; + ++frequencies_ll[symbol]; + if(symbol > 256) { + unsigned dist = lz77_encoded.data[i + 2]; + ++frequencies_d[dist]; + i += 3; + } + } + frequencies_ll[256] = 1; /*there will be exactly 1 end code, at the end of the block*/ + + /*Make both huffman trees, one for the lit and len codes, one for the dist codes*/ + error = HuffmanTree_makeFromFrequencies(&tree_ll, frequencies_ll, 257, 286, 15); + if(error) break; + /*2, not 1, is chosen for mincodes: some buggy PNG decoders require at least 2 symbols in the dist tree*/ + error = HuffmanTree_makeFromFrequencies(&tree_d, frequencies_d, 2, 30, 15); + if(error) break; + + numcodes_ll = LODEPNG_MIN(tree_ll.numcodes, 286); + numcodes_d = LODEPNG_MIN(tree_d.numcodes, 30); + /*store the code lengths of both generated trees in bitlen_lld*/ + numcodes_lld = numcodes_ll + numcodes_d; + bitlen_lld = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld)); + /*numcodes_lld_e never needs more size than bitlen_lld*/ + bitlen_lld_e = (unsigned*)lodepng_malloc(numcodes_lld * sizeof(*bitlen_lld_e)); + if(!bitlen_lld || !bitlen_lld_e) ERROR_BREAK(83); /*alloc fail*/ + numcodes_lld_e = 0; + + for(i = 0; i != numcodes_ll; ++i) bitlen_lld[i] = tree_ll.lengths[i]; + for(i = 0; i != numcodes_d; ++i) bitlen_lld[numcodes_ll + i] = tree_d.lengths[i]; + + /*run-length compress bitlen_ldd into bitlen_lld_e by using repeat codes 16 (copy length 3-6 times), + 17 (3-10 zeroes), 18 (11-138 zeroes)*/ + for(i = 0; i != numcodes_lld; ++i) { + unsigned j = 0; /*amount of repetitions*/ + while(i + j + 1 < numcodes_lld && bitlen_lld[i + j + 1] == bitlen_lld[i]) ++j; + + if(bitlen_lld[i] == 0 && j >= 2) /*repeat code for zeroes*/ { + ++j; /*include the first zero*/ + if(j <= 10) /*repeat code 17 supports max 10 zeroes*/ { + bitlen_lld_e[numcodes_lld_e++] = 17; + bitlen_lld_e[numcodes_lld_e++] = j - 3; + } else /*repeat code 18 supports max 138 zeroes*/ { + if(j > 138) j = 138; + bitlen_lld_e[numcodes_lld_e++] = 18; + bitlen_lld_e[numcodes_lld_e++] = j - 11; + } + i += (j - 1); + } else if(j >= 3) /*repeat code for value other than zero*/ { + size_t k; + unsigned num = j / 6u, rest = j % 6u; + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + for(k = 0; k < num; ++k) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = 6 - 3; + } + if(rest >= 3) { + bitlen_lld_e[numcodes_lld_e++] = 16; + bitlen_lld_e[numcodes_lld_e++] = rest - 3; + } + else j -= rest; + i += j; + } else /*too short to benefit from repeat code*/ { + bitlen_lld_e[numcodes_lld_e++] = bitlen_lld[i]; + } + } + + /*generate tree_cl, the huffmantree of huffmantrees*/ + for(i = 0; i != numcodes_lld_e; ++i) { + ++frequencies_cl[bitlen_lld_e[i]]; + /*after a repeat code come the bits that specify the number of repetitions, + those don't need to be in the frequencies_cl calculation*/ + if(bitlen_lld_e[i] >= 16) ++i; + } + + error = HuffmanTree_makeFromFrequencies(&tree_cl, frequencies_cl, + NUM_CODE_LENGTH_CODES, NUM_CODE_LENGTH_CODES, 7); + if(error) break; + + /*compute amount of code-length-code-lengths to output*/ + numcodes_cl = NUM_CODE_LENGTH_CODES; + /*trim zeros at the end (using CLCL_ORDER), but minimum size must be 4 (see HCLEN below)*/ + while(numcodes_cl > 4u && tree_cl.lengths[CLCL_ORDER[numcodes_cl - 1u]] == 0) { + numcodes_cl--; + } + + /* + Write everything into the output + + After the BFINAL and BTYPE, the dynamic block consists out of the following: + - 5 bits HLIT, 5 bits HDIST, 4 bits HCLEN + - (HCLEN+4)*3 bits code lengths of code length alphabet + - HLIT + 257 code lengths of lit/length alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - HDIST + 1 code lengths of distance alphabet (encoded using the code length + alphabet, + possible repetition codes 16, 17, 18) + - compressed data + - 256 (end code) + */ + + /*Write block type*/ + writeBits(writer, BFINAL, 1); + writeBits(writer, 0, 1); /*first bit of BTYPE "dynamic"*/ + writeBits(writer, 1, 1); /*second bit of BTYPE "dynamic"*/ + + /*write the HLIT, HDIST and HCLEN values*/ + /*all three sizes take trimmed ending zeroes into account, done either by HuffmanTree_makeFromFrequencies + or in the loop for numcodes_cl above, which saves space. */ + HLIT = (unsigned)(numcodes_ll - 257); + HDIST = (unsigned)(numcodes_d - 1); + HCLEN = (unsigned)(numcodes_cl - 4); + writeBits(writer, HLIT, 5); + writeBits(writer, HDIST, 5); + writeBits(writer, HCLEN, 4); + + /*write the code lengths of the code length alphabet ("bitlen_cl")*/ + for(i = 0; i != numcodes_cl; ++i) writeBits(writer, tree_cl.lengths[CLCL_ORDER[i]], 3); + + /*write the lengths of the lit/len AND the dist alphabet*/ + for(i = 0; i != numcodes_lld_e; ++i) { + writeBitsReversed(writer, tree_cl.codes[bitlen_lld_e[i]], tree_cl.lengths[bitlen_lld_e[i]]); + /*extra bits of repeat codes*/ + if(bitlen_lld_e[i] == 16) writeBits(writer, bitlen_lld_e[++i], 2); + else if(bitlen_lld_e[i] == 17) writeBits(writer, bitlen_lld_e[++i], 3); + else if(bitlen_lld_e[i] == 18) writeBits(writer, bitlen_lld_e[++i], 7); + } + + /*write the compressed data symbols*/ + writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + /*error: the length of the end code 256 must be larger than 0*/ + if(tree_ll.lengths[256] == 0) ERROR_BREAK(64); + + /*write the end code*/ + writeBitsReversed(writer, tree_ll.codes[256], tree_ll.lengths[256]); + + break; /*end of error-while*/ + } + + /*cleanup*/ + uivector_cleanup(&lz77_encoded); + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + HuffmanTree_cleanup(&tree_cl); + lodepng_free(frequencies_ll); + lodepng_free(frequencies_d); + lodepng_free(frequencies_cl); + lodepng_free(bitlen_lld); + lodepng_free(bitlen_lld_e); + + return error; +} + +static unsigned deflateFixed(LodePNGBitWriter* writer, Hash* hash, + const unsigned char* data, + size_t datapos, size_t dataend, + const LodePNGCompressSettings* settings, unsigned final) { + HuffmanTree tree_ll; /*tree for literal values and length codes*/ + HuffmanTree tree_d; /*tree for distance codes*/ + + unsigned BFINAL = final; + unsigned error = 0; + size_t i; + + HuffmanTree_init(&tree_ll); + HuffmanTree_init(&tree_d); + + error = generateFixedLitLenTree(&tree_ll); + if(!error) error = generateFixedDistanceTree(&tree_d); + + if(!error) { + writeBits(writer, BFINAL, 1); + writeBits(writer, 1, 1); /*first bit of BTYPE*/ + writeBits(writer, 0, 1); /*second bit of BTYPE*/ + + if(settings->use_lz77) /*LZ77 encoded*/ { + uivector lz77_encoded; + uivector_init(&lz77_encoded); + error = encodeLZ77(&lz77_encoded, hash, data, datapos, dataend, settings->windowsize, + settings->minmatch, settings->nicematch, settings->lazymatching); + if(!error) writeLZ77data(writer, &lz77_encoded, &tree_ll, &tree_d); + uivector_cleanup(&lz77_encoded); + } else /*no LZ77, but still will be Huffman compressed*/ { + for(i = datapos; i < dataend; ++i) { + writeBitsReversed(writer, tree_ll.codes[data[i]], tree_ll.lengths[data[i]]); + } + } + /*add END code*/ + if(!error) writeBitsReversed(writer,tree_ll.codes[256], tree_ll.lengths[256]); + } + + /*cleanup*/ + HuffmanTree_cleanup(&tree_ll); + HuffmanTree_cleanup(&tree_d); + + return error; +} + +static unsigned lodepng_deflatev(ucvector* out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + unsigned error = 0; + size_t i, blocksize, numdeflateblocks; + Hash hash; + LodePNGBitWriter writer; + + LodePNGBitWriter_init(&writer, out); + + if(settings->btype > 2) return 61; + else if(settings->btype == 0) return deflateNoCompression(out, in, insize); + else if(settings->btype == 1) blocksize = insize; + else /*if(settings->btype == 2)*/ { + /*on PNGs, deflate blocks of 65-262k seem to give most dense encoding*/ + blocksize = insize / 8u + 8; + if(blocksize < 65536) blocksize = 65536; + if(blocksize > 262144) blocksize = 262144; + } + + numdeflateblocks = (insize + blocksize - 1) / blocksize; + if(numdeflateblocks == 0) numdeflateblocks = 1; + + error = hash_init(&hash, settings->windowsize); + + if(!error) { + for(i = 0; i != numdeflateblocks && !error; ++i) { + unsigned final = (i == numdeflateblocks - 1); + size_t start = i * blocksize; + size_t end = start + blocksize; + if(end > insize) end = insize; + + if(settings->btype == 1) error = deflateFixed(&writer, &hash, in, start, end, settings, final); + else if(settings->btype == 2) error = deflateDynamic(&writer, &hash, in, start, end, settings, final); + } + } + + hash_cleanup(&hash); + + return error; +} + +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_deflatev(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +static unsigned deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings) { + if(settings->custom_deflate) { + unsigned error = settings->custom_deflate(out, outsize, in, insize, settings); + /*the custom deflate is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_deflate(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Adler32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +static unsigned update_adler32(unsigned adler, const unsigned char* data, unsigned len) { + unsigned s1 = adler & 0xffffu; + unsigned s2 = (adler >> 16u) & 0xffffu; + + while(len != 0u) { + unsigned i; + /*at least 5552 sums can be done before the sums overflow, saving a lot of module divisions*/ + unsigned amount = len > 5552u ? 5552u : len; + len -= amount; + for(i = 0; i != amount; ++i) { + s1 += (*data++); + s2 += s1; + } + s1 %= 65521u; + s2 %= 65521u; + } + + return (s2 << 16u) | s1; +} + +/*Return the adler32 of the bytes data[0..len-1]*/ +static unsigned adler32(const unsigned char* data, unsigned len) { + return update_adler32(1u, data, len); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Zlib / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_DECODER + +static unsigned lodepng_zlib_decompressv(ucvector* out, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings) { + unsigned error = 0; + unsigned CM, CINFO, FDICT; + + if(insize < 2) return 53; /*error, size of zlib data too small*/ + /*read information from zlib header*/ + if((in[0] * 256 + in[1]) % 31 != 0) { + /*error: 256 * in[0] + in[1] must be a multiple of 31, the FCHECK value is supposed to be made that way*/ + return 24; + } + + CM = in[0] & 15; + CINFO = (in[0] >> 4) & 15; + /*FCHECK = in[1] & 31;*/ /*FCHECK is already tested above*/ + FDICT = (in[1] >> 5) & 1; + /*FLEVEL = (in[1] >> 6) & 3;*/ /*FLEVEL is not used here*/ + + if(CM != 8 || CINFO > 7) { + /*error: only compression method 8: inflate with sliding window of 32k is supported by the PNG spec*/ + return 25; + } + if(FDICT != 0) { + /*error: the specification of PNG says about the zlib stream: + "The additional flags shall not specify a preset dictionary."*/ + return 26; + } + + error = inflatev(out, in + 2, insize - 2, settings); + if(error) return error; + + if(!settings->ignore_adler32) { + unsigned ADLER32 = lodepng_read32bitInt(&in[insize - 4]); + unsigned checksum = adler32(out->data, (unsigned)(out->size)); + if(checksum != ADLER32) return 58; /*error, adler checksum not correct, data must be corrupted*/ + } + + return 0; /*no error*/ +} + + +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGDecompressSettings* settings) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + return error; +} + +/*expected_size is expected output size, to avoid intermediate allocations. Set to 0 if not known. */ +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + unsigned error; + if(settings->custom_zlib) { + error = settings->custom_zlib(out, outsize, in, insize, settings); + if(error) { + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 110*/ + error = 110; + /*if there's a max output size, and the custom zlib returned error, then indicate that error instead*/ + if(settings->max_output_size && *outsize > settings->max_output_size) error = 109; + } + } else { + ucvector v = ucvector_init(*out, *outsize); + if(expected_size) { + /*reserve the memory to avoid intermediate reallocations*/ + ucvector_resize(&v, *outsize + expected_size); + v.size = *outsize; + } + error = lodepng_zlib_decompressv(&v, in, insize, settings); + *out = v.data; + *outsize = v.size; + } + return error; +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER + +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + size_t i; + unsigned error; + unsigned char* deflatedata = 0; + size_t deflatesize = 0; + + error = deflate(&deflatedata, &deflatesize, in, insize, settings); + + *out = NULL; + *outsize = 0; + if(!error) { + *outsize = deflatesize + 6; + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!*out) error = 83; /*alloc fail*/ + } + + if(!error) { + unsigned ADLER32 = adler32(in, (unsigned)insize); + /*zlib data: 1 byte CMF (CM+CINFO), 1 byte FLG, deflate data, 4 byte ADLER32 checksum of the Decompressed data*/ + unsigned CMF = 120; /*0b01111000: CM 8, CINFO 7. With CINFO 7, any window size up to 32768 can be used.*/ + unsigned FLEVEL = 0; + unsigned FDICT = 0; + unsigned CMFFLG = 256 * CMF + FDICT * 32 + FLEVEL * 64; + unsigned FCHECK = 31 - CMFFLG % 31; + CMFFLG += FCHECK; + + (*out)[0] = (unsigned char)(CMFFLG >> 8); + (*out)[1] = (unsigned char)(CMFFLG & 255); + for(i = 0; i != deflatesize; ++i) (*out)[i + 2] = deflatedata[i]; + lodepng_set32bitInt(&(*out)[*outsize - 4], ADLER32); + } + + lodepng_free(deflatedata); + return error; +} + +/* compress using the default or custom zlib function */ +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(settings->custom_zlib) { + unsigned error = settings->custom_zlib(out, outsize, in, insize, settings); + /*the custom zlib is allowed to have its own error codes, however, we translate it to code 111*/ + return error ? 111 : 0; + } else { + return lodepng_zlib_compress(out, outsize, in, insize, settings); + } +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#else /*no LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DECODER +static unsigned zlib_decompress(unsigned char** out, size_t* outsize, size_t expected_size, + const unsigned char* in, size_t insize, const LodePNGDecompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + LV_UNUSED(expected_size); + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER +static unsigned zlib_compress(unsigned char** out, size_t* outsize, const unsigned char* in, + size_t insize, const LodePNGCompressSettings* settings) { + if(!settings->custom_zlib) return 87; /*no custom zlib function provided */ + return settings->custom_zlib(out, outsize, in, insize, settings); +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#endif /*LODEPNG_COMPILE_ZLIB*/ + +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/*this is a good tradeoff between speed and compression ratio*/ +#define DEFAULT_WINDOWSIZE 2048 + +void lodepng_compress_settings_init(LodePNGCompressSettings* settings) { + /*compress with dynamic huffman tree (not in the mathematical sense, just not the predefined one)*/ + settings->btype = 2; + settings->use_lz77 = 1; + settings->windowsize = DEFAULT_WINDOWSIZE; + settings->minmatch = 3; + settings->nicematch = 128; + settings->lazymatching = 1; + + settings->custom_zlib = 0; + settings->custom_deflate = 0; + settings->custom_context = 0; +} + +const LodePNGCompressSettings lodepng_default_compress_settings = {2, 1, DEFAULT_WINDOWSIZE, 3, 128, 1, 0, 0, 0}; + + +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DECODER + +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings) { + settings->ignore_adler32 = 0; + settings->ignore_nlen = 0; + settings->max_output_size = 0; + + settings->custom_zlib = 0; + settings->custom_inflate = 0; + settings->custom_context = 0; +} + +const LodePNGDecompressSettings lodepng_default_decompress_settings = {0, 0, 0, 0, 0, 0}; + +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // End of Zlib related code. Begin of PNG related code. // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_PNG + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / CRC32 / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +#ifndef LODEPNG_NO_COMPILE_CRC +/* CRC polynomial: 0xedb88320 */ +static unsigned lodepng_crc32_table[256] = { + 0u, 1996959894u, 3993919788u, 2567524794u, 124634137u, 1886057615u, 3915621685u, 2657392035u, + 249268274u, 2044508324u, 3772115230u, 2547177864u, 162941995u, 2125561021u, 3887607047u, 2428444049u, + 498536548u, 1789927666u, 4089016648u, 2227061214u, 450548861u, 1843258603u, 4107580753u, 2211677639u, + 325883990u, 1684777152u, 4251122042u, 2321926636u, 335633487u, 1661365465u, 4195302755u, 2366115317u, + 997073096u, 1281953886u, 3579855332u, 2724688242u, 1006888145u, 1258607687u, 3524101629u, 2768942443u, + 901097722u, 1119000684u, 3686517206u, 2898065728u, 853044451u, 1172266101u, 3705015759u, 2882616665u, + 651767980u, 1373503546u, 3369554304u, 3218104598u, 565507253u, 1454621731u, 3485111705u, 3099436303u, + 671266974u, 1594198024u, 3322730930u, 2970347812u, 795835527u, 1483230225u, 3244367275u, 3060149565u, + 1994146192u, 31158534u, 2563907772u, 4023717930u, 1907459465u, 112637215u, 2680153253u, 3904427059u, + 2013776290u, 251722036u, 2517215374u, 3775830040u, 2137656763u, 141376813u, 2439277719u, 3865271297u, + 1802195444u, 476864866u, 2238001368u, 4066508878u, 1812370925u, 453092731u, 2181625025u, 4111451223u, + 1706088902u, 314042704u, 2344532202u, 4240017532u, 1658658271u, 366619977u, 2362670323u, 4224994405u, + 1303535960u, 984961486u, 2747007092u, 3569037538u, 1256170817u, 1037604311u, 2765210733u, 3554079995u, + 1131014506u, 879679996u, 2909243462u, 3663771856u, 1141124467u, 855842277u, 2852801631u, 3708648649u, + 1342533948u, 654459306u, 3188396048u, 3373015174u, 1466479909u, 544179635u, 3110523913u, 3462522015u, + 1591671054u, 702138776u, 2966460450u, 3352799412u, 1504918807u, 783551873u, 3082640443u, 3233442989u, + 3988292384u, 2596254646u, 62317068u, 1957810842u, 3939845945u, 2647816111u, 81470997u, 1943803523u, + 3814918930u, 2489596804u, 225274430u, 2053790376u, 3826175755u, 2466906013u, 167816743u, 2097651377u, + 4027552580u, 2265490386u, 503444072u, 1762050814u, 4150417245u, 2154129355u, 426522225u, 1852507879u, + 4275313526u, 2312317920u, 282753626u, 1742555852u, 4189708143u, 2394877945u, 397917763u, 1622183637u, + 3604390888u, 2714866558u, 953729732u, 1340076626u, 3518719985u, 2797360999u, 1068828381u, 1219638859u, + 3624741850u, 2936675148u, 906185462u, 1090812512u, 3747672003u, 2825379669u, 829329135u, 1181335161u, + 3412177804u, 3160834842u, 628085408u, 1382605366u, 3423369109u, 3138078467u, 570562233u, 1426400815u, + 3317316542u, 2998733608u, 733239954u, 1555261956u, 3268935591u, 3050360625u, 752459403u, 1541320221u, + 2607071920u, 3965973030u, 1969922972u, 40735498u, 2617837225u, 3943577151u, 1913087877u, 83908371u, + 2512341634u, 3803740692u, 2075208622u, 213261112u, 2463272603u, 3855990285u, 2094854071u, 198958881u, + 2262029012u, 4057260610u, 1759359992u, 534414190u, 2176718541u, 4139329115u, 1873836001u, 414664567u, + 2282248934u, 4279200368u, 1711684554u, 285281116u, 2405801727u, 4167216745u, 1634467795u, 376229701u, + 2685067896u, 3608007406u, 1308918612u, 956543938u, 2808555105u, 3495958263u, 1231636301u, 1047427035u, + 2932959818u, 3654703836u, 1088359270u, 936918000u, 2847714899u, 3736837829u, 1202900863u, 817233897u, + 3183342108u, 3401237130u, 1404277552u, 615818150u, 3134207493u, 3453421203u, 1423857449u, 601450431u, + 3009837614u, 3294710456u, 1567103746u, 711928724u, 3020668471u, 3272380065u, 1510334235u, 755167117u +}; + +/*Return the CRC of the bytes buf[0..len-1].*/ +unsigned lodepng_crc32(const unsigned char* data, size_t length) { + unsigned r = 0xffffffffu; + size_t i; + for(i = 0; i < length; ++i) { + r = lodepng_crc32_table[(r ^ data[i]) & 0xffu] ^ (r >> 8u); + } + return r ^ 0xffffffffu; +} +#else /* !LODEPNG_NO_COMPILE_CRC */ +unsigned lodepng_crc32(const unsigned char* data, size_t length); +#endif /* !LODEPNG_NO_COMPILE_CRC */ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Reading and writing PNG color channel bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/* The color channel bits of less-than-8-bit pixels are read with the MSB of bytes first, +so LodePNGBitWriter and LodePNGBitReader can't be used for those. */ + +static unsigned char readBitFromReversedStream(size_t* bitpointer, const unsigned char* bitstream) { + unsigned char result = (unsigned char)((bitstream[(*bitpointer) >> 3] >> (7 - ((*bitpointer) & 0x7))) & 1); + ++(*bitpointer); + return result; +} + +/* TODO: make this faster */ +static unsigned readBitsFromReversedStream(size_t* bitpointer, const unsigned char* bitstream, size_t nbits) { + unsigned result = 0; + size_t i; + for(i = 0 ; i < nbits; ++i) { + result <<= 1u; + result |= (unsigned)readBitFromReversedStream(bitpointer, bitstream); + } + return result; +} + +static void setBitOfReversedStream(size_t* bitpointer, unsigned char* bitstream, unsigned char bit) { + /*the current bit in bitstream may be 0 or 1 for this to work*/ + if(bit == 0) bitstream[(*bitpointer) >> 3u] &= (unsigned char)(~(1u << (7u - ((*bitpointer) & 7u)))); + else bitstream[(*bitpointer) >> 3u] |= (1u << (7u - ((*bitpointer) & 7u))); + ++(*bitpointer); +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG chunks / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +unsigned lodepng_chunk_length(const unsigned char* chunk) { + return lodepng_read32bitInt(&chunk[0]); +} + +void lodepng_chunk_type(char type[5], const unsigned char* chunk) { + unsigned i; + for(i = 0; i != 4; ++i) type[i] = (char)chunk[4 + i]; + type[4] = 0; /*null termination char*/ +} + +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type) { + if(lodepng_strlen(type) != 4) return 0; + return (chunk[4] == type[0] && chunk[5] == type[1] && chunk[6] == type[2] && chunk[7] == type[3]); +} + +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk) { + return((chunk[4] & 32) != 0); +} + +unsigned char lodepng_chunk_private(const unsigned char* chunk) { + return((chunk[6] & 32) != 0); +} + +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk) { + return((chunk[7] & 32) != 0); +} + +unsigned char* lodepng_chunk_data(unsigned char* chunk) { + return &chunk[8]; +} + +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk) { + return &chunk[8]; +} + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_read32bitInt(&chunk[length + 8]); + /*the CRC is taken of the data and the 4 chunk type letters, not the length*/ + unsigned checksum = lodepng_crc32(&chunk[4], length + 4); + if(CRC != checksum) return 1; + else return 0; +} + +void lodepng_chunk_generate_crc(unsigned char* chunk) { + unsigned length = lodepng_chunk_length(chunk); + unsigned CRC = lodepng_crc32(&chunk[4], length + 4); + lodepng_set32bitInt(chunk + 8 + length, CRC); +} + +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end) { + if(chunk >= end || end - chunk < 12) return end; /*too small to contain a chunk*/ + if(chunk[0] == 0x89 && chunk[1] == 0x50 && chunk[2] == 0x4e && chunk[3] == 0x47 + && chunk[4] == 0x0d && chunk[5] == 0x0a && chunk[6] == 0x1a && chunk[7] == 0x0a) { + /* Is PNG magic header at start of PNG file. Jump to first actual chunk. */ + return chunk + 8; + } else { + size_t total_chunk_length; + const unsigned char* result; + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return end; + result = chunk + total_chunk_length; + if(result < chunk) return end; /*pointer overflow*/ + return result; + } +} + +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next(chunk, end); + } + + return 0; /*Shouldn't reach this*/ +} + +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]) { + for(;;) { + if(chunk >= end || end - chunk < 12) return 0; /* past file end: chunk + 12 > end */ + if(lodepng_chunk_type_equals(chunk, type)) return chunk; + chunk = lodepng_chunk_next_const(chunk, end); + } + + return 0; /*Shouldn't reach this*/ +} + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk) { + unsigned i; + size_t total_chunk_length, new_length; + unsigned char *chunk_start, *new_buffer; + + if(lodepng_addofl(lodepng_chunk_length(chunk), 12, &total_chunk_length)) return 77; + if(lodepng_addofl(*outsize, total_chunk_length, &new_length)) return 77; + + new_buffer = (unsigned char*)lodepng_realloc(*out, new_length); + if(!new_buffer) return 83; /*alloc fail*/ + (*out) = new_buffer; + (*outsize) = new_length; + chunk_start = &(*out)[new_length - total_chunk_length]; + + for(i = 0; i != total_chunk_length; ++i) chunk_start[i] = chunk[i]; + + return 0; +} + +/*Sets length and name and allocates the space for data and crc but does not +set data or crc yet. Returns the start of the chunk in chunk. The start of +the data is at chunk + 8. To finalize chunk, add the data, then use +lodepng_chunk_generate_crc */ +static unsigned lodepng_chunk_init(unsigned char** chunk, + ucvector* out, + unsigned length, const char* type) { + size_t new_length = out->size; + if(lodepng_addofl(new_length, length, &new_length)) return 77; + if(lodepng_addofl(new_length, 12, &new_length)) return 77; + if(!ucvector_resize(out, new_length)) return 83; /*alloc fail*/ + *chunk = out->data + new_length - length - 12u; + + /*1: length*/ + lodepng_set32bitInt(*chunk, length); + + /*2: chunk name (4 letters)*/ + lodepng_memcpy(*chunk + 4, type, 4); + + return 0; +} + +/* like lodepng_chunk_create but with custom allocsize */ +static unsigned lodepng_chunk_createv(ucvector* out, + unsigned length, const char* type, const unsigned char* data) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, length, type)); + + /*3: the data*/ + lodepng_memcpy(chunk + 8, data, length); + + /*4: CRC (of the chunkname characters and the data)*/ + lodepng_chunk_generate_crc(chunk); + + return 0; +} + +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, + unsigned length, const char* type, const unsigned char* data) { + ucvector v = ucvector_init(*out, *outsize); + unsigned error = lodepng_chunk_createv(&v, length, type, data); + *out = v.data; + *outsize = v.size; + return error; +} + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / Color types, channels, bits / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*checks if the colortype is valid and the bitdepth bd is allowed for this colortype. +Return value is a LodePNG error code.*/ +static unsigned checkColorValidity(LodePNGColorType colortype, unsigned bd) { + switch(colortype) { + case LCT_GREY: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 || bd == 16)) return 37; break; + case LCT_RGB: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_PALETTE: if(!(bd == 1 || bd == 2 || bd == 4 || bd == 8 )) return 37; break; + case LCT_GREY_ALPHA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_RGBA: if(!( bd == 8 || bd == 16)) return 37; break; + case LCT_MAX_OCTET_VALUE: return 31; /* invalid color type */ + default: return 31; /* invalid color type */ + } + return 0; /*allowed color type / bits combination*/ +} + +static unsigned getNumColorChannels(LodePNGColorType colortype) { + switch(colortype) { + case LCT_GREY: return 1; + case LCT_RGB: return 3; + case LCT_PALETTE: return 1; + case LCT_GREY_ALPHA: return 2; + case LCT_RGBA: return 4; + case LCT_MAX_OCTET_VALUE: return 0; /* invalid color type */ + default: return 0; /*invalid color type*/ + } +} + +static unsigned lodepng_get_bpp_lct(LodePNGColorType colortype, unsigned bitdepth) { + /*bits per pixel is amount of channels * bits per channel*/ + return getNumColorChannels(colortype) * bitdepth; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +void lodepng_color_mode_init(LodePNGColorMode* info) { + info->key_defined = 0; + info->key_r = info->key_g = info->key_b = 0; + info->colortype = LCT_RGBA; + info->bitdepth = 8; + info->palette = 0; + info->palettesize = 0; +} + +/*allocates palette memory if needed, and initializes all colors to black*/ +static void lodepng_color_mode_alloc_palette(LodePNGColorMode* info) { + size_t i; + /*if the palette is already allocated, it will have size 1024 so no reallocation needed in that case*/ + /*the palette must have room for up to 256 colors with 4 bytes each.*/ + if(!info->palette) info->palette = (unsigned char*)lodepng_malloc(1024); + if(!info->palette) return; /*alloc fail*/ + for(i = 0; i != 256; ++i) { + /*Initialize all unused colors with black, the value used for invalid palette indices. + This is an error according to the PNG spec, but common PNG decoders make it black instead. + That makes color conversion slightly faster due to no error handling needed.*/ + info->palette[i * 4 + 0] = 0; + info->palette[i * 4 + 1] = 0; + info->palette[i * 4 + 2] = 0; + info->palette[i * 4 + 3] = 255; + } +} + +void lodepng_color_mode_cleanup(LodePNGColorMode* info) { + lodepng_palette_clear(info); +} + +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source) { + lodepng_color_mode_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGColorMode)); + if(source->palette) { + dest->palette = (unsigned char*)lodepng_malloc(1024); + if(!dest->palette && source->palettesize) return 83; /*alloc fail*/ + lodepng_memcpy(dest->palette, source->palette, source->palettesize * 4); + } + return 0; +} + +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth) { + LodePNGColorMode result; + lodepng_color_mode_init(&result); + result.colortype = colortype; + result.bitdepth = bitdepth; + return result; +} + +static int lodepng_color_mode_equal(const LodePNGColorMode* a, const LodePNGColorMode* b) { + size_t i; + if(a->colortype != b->colortype) return 0; + if(a->bitdepth != b->bitdepth) return 0; + if(a->key_defined != b->key_defined) return 0; + if(a->key_defined) { + if(a->key_r != b->key_r) return 0; + if(a->key_g != b->key_g) return 0; + if(a->key_b != b->key_b) return 0; + } + if(a->palettesize != b->palettesize) return 0; + for(i = 0; i != a->palettesize * 4; ++i) { + if(a->palette[i] != b->palette[i]) return 0; + } + return 1; +} + +void lodepng_palette_clear(LodePNGColorMode* info) { + if(info->palette) lodepng_free(info->palette); + info->palette = 0; + info->palettesize = 0; +} + +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(!info->palette) /*allocate palette if empty*/ { + lodepng_color_mode_alloc_palette(info); + if(!info->palette) return 83; /*alloc fail*/ + } + if(info->palettesize >= 256) { + return 108; /*too many palette values*/ + } + info->palette[4 * info->palettesize + 0] = r; + info->palette[4 * info->palettesize + 1] = g; + info->palette[4 * info->palettesize + 2] = b; + info->palette[4 * info->palettesize + 3] = a; + ++info->palettesize; + return 0; +} + +/*calculate bits per pixel out of colortype and bitdepth*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info) { + return lodepng_get_bpp_lct(info->colortype, info->bitdepth); +} + +unsigned lodepng_get_channels(const LodePNGColorMode* info) { + return getNumColorChannels(info->colortype); +} + +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info) { + return info->colortype == LCT_GREY || info->colortype == LCT_GREY_ALPHA; +} + +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info) { + return (info->colortype & 4) != 0; /*4 or 6*/ +} + +unsigned lodepng_is_palette_type(const LodePNGColorMode* info) { + return info->colortype == LCT_PALETTE; +} + +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info) { + size_t i; + for(i = 0; i != info->palettesize; ++i) { + if(info->palette[i * 4 + 3] < 255) return 1; + } + return 0; +} + +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info) { + return info->key_defined + || lodepng_is_alpha_type(info) + || lodepng_has_palette_alpha(info); +} + +static size_t lodepng_get_raw_size_lct(unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + size_t bpp = lodepng_get_bpp_lct(colortype, bitdepth); + size_t n = (size_t)w * (size_t)h; + return ((n / 8u) * bpp) + ((n & 7u) * bpp + 7u) / 8u; +} + +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color) { + return lodepng_get_raw_size_lct(w, h, color->colortype, color->bitdepth); +} + + +#ifdef LODEPNG_COMPILE_PNG + +/*in an idat chunk, each scanline is a multiple of 8 bits, unlike the lodepng output buffer, +and in addition has one extra byte per line: the filter byte. So this gives a larger +result than lodepng_get_raw_size. Set h to 1 to get the size of 1 row including filter byte. */ +static size_t lodepng_get_raw_size_idat(unsigned w, unsigned h, unsigned bpp) { + /* + 1 for the filter byte, and possibly plus padding bits per line. */ + /* Ignoring casts, the expression is equal to (w * bpp + 7) / 8 + 1, but avoids overflow of w * bpp */ + size_t line = ((size_t)(w / 8u) * bpp) + 1u + ((w & 7u) * bpp + 7u) / 8u; + return (size_t)h * line; +} + +#ifdef LODEPNG_COMPILE_DECODER +/*Safely checks whether size_t overflow can be caused due to amount of pixels. +This check is overcautious rather than precise. If this check indicates no overflow, +you can safely compute in a size_t (but not an unsigned): +-(size_t)w * (size_t)h * 8 +-amount of bytes in IDAT (including filter, padding and Adam7 bytes) +-amount of bytes in raw color model +Returns 1 if overflow possible, 0 if not. +*/ +static int lodepng_pixel_overflow(unsigned w, unsigned h, + const LodePNGColorMode* pngcolor, const LodePNGColorMode* rawcolor) { + size_t bpp = LODEPNG_MAX(lodepng_get_bpp(pngcolor), lodepng_get_bpp(rawcolor)); + size_t numpixels, total; + size_t line; /* bytes per line in worst case */ + + if(lodepng_mulofl((size_t)w, (size_t)h, &numpixels)) return 1; + if(lodepng_mulofl(numpixels, 8, &total)) return 1; /* bit pointer with 8-bit color, or 8 bytes per channel color */ + + /* Bytes per scanline with the expression "(w / 8u) * bpp) + ((w & 7u) * bpp + 7u) / 8u" */ + if(lodepng_mulofl((size_t)(w / 8u), bpp, &line)) return 1; + if(lodepng_addofl(line, ((w & 7u) * bpp + 7u) / 8u, &line)) return 1; + + if(lodepng_addofl(line, 5, &line)) return 1; /* 5 bytes overhead per line: 1 filterbyte, 4 for Adam7 worst case */ + if(lodepng_mulofl(line, h, &total)) return 1; /* Total bytes in worst case */ + + return 0; /* no overflow */ +} +#endif /*LODEPNG_COMPILE_DECODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static void LodePNGUnknownChunks_init(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) info->unknown_chunks_data[i] = 0; + for(i = 0; i != 3; ++i) info->unknown_chunks_size[i] = 0; +} + +static void LodePNGUnknownChunks_cleanup(LodePNGInfo* info) { + unsigned i; + for(i = 0; i != 3; ++i) lodepng_free(info->unknown_chunks_data[i]); +} + +static unsigned LodePNGUnknownChunks_copy(LodePNGInfo* dest, const LodePNGInfo* src) { + unsigned i; + + LodePNGUnknownChunks_cleanup(dest); + + for(i = 0; i != 3; ++i) { + size_t j; + dest->unknown_chunks_size[i] = src->unknown_chunks_size[i]; + dest->unknown_chunks_data[i] = (unsigned char*)lodepng_malloc(src->unknown_chunks_size[i]); + if(!dest->unknown_chunks_data[i] && dest->unknown_chunks_size[i]) return 83; /*alloc fail*/ + for(j = 0; j < src->unknown_chunks_size[i]; ++j) { + dest->unknown_chunks_data[i][j] = src->unknown_chunks_data[i][j]; + } + } + + return 0; +} + +/******************************************************************************/ + +static void LodePNGText_init(LodePNGInfo* info) { + info->text_num = 0; + info->text_keys = NULL; + info->text_strings = NULL; +} + +static void LodePNGText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->text_num; ++i) { + string_cleanup(&info->text_keys[i]); + string_cleanup(&info->text_strings[i]); + } + lodepng_free(info->text_keys); + lodepng_free(info->text_strings); +} + +static unsigned LodePNGText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->text_keys = NULL; + dest->text_strings = NULL; + dest->text_num = 0; + for(i = 0; i != source->text_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_text(dest, source->text_keys[i], source->text_strings[i])); + } + return 0; +} + +static unsigned lodepng_add_text_sized(LodePNGInfo* info, const char* key, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->text_keys, sizeof(char*) * (info->text_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->text_strings, sizeof(char*) * (info->text_num + 1))); + + if(new_keys) info->text_keys = new_keys; + if(new_strings) info->text_strings = new_strings; + + if(!new_keys || !new_strings) return 83; /*alloc fail*/ + + ++info->text_num; + info->text_keys[info->text_num - 1] = alloc_string(key); + info->text_strings[info->text_num - 1] = alloc_string_sized(str, size); + if(!info->text_keys[info->text_num - 1] || !info->text_strings[info->text_num - 1]) return 83; /*alloc fail*/ + + return 0; +} + +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str) { + return lodepng_add_text_sized(info, key, str, lodepng_strlen(str)); +} + +void lodepng_clear_text(LodePNGInfo* info) { + LodePNGText_cleanup(info); +} + +/******************************************************************************/ + +static void LodePNGIText_init(LodePNGInfo* info) { + info->itext_num = 0; + info->itext_keys = NULL; + info->itext_langtags = NULL; + info->itext_transkeys = NULL; + info->itext_strings = NULL; +} + +static void LodePNGIText_cleanup(LodePNGInfo* info) { + size_t i; + for(i = 0; i != info->itext_num; ++i) { + string_cleanup(&info->itext_keys[i]); + string_cleanup(&info->itext_langtags[i]); + string_cleanup(&info->itext_transkeys[i]); + string_cleanup(&info->itext_strings[i]); + } + lodepng_free(info->itext_keys); + lodepng_free(info->itext_langtags); + lodepng_free(info->itext_transkeys); + lodepng_free(info->itext_strings); +} + +static unsigned LodePNGIText_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + size_t i = 0; + dest->itext_keys = NULL; + dest->itext_langtags = NULL; + dest->itext_transkeys = NULL; + dest->itext_strings = NULL; + dest->itext_num = 0; + for(i = 0; i != source->itext_num; ++i) { + CERROR_TRY_RETURN(lodepng_add_itext(dest, source->itext_keys[i], source->itext_langtags[i], + source->itext_transkeys[i], source->itext_strings[i])); + } + return 0; +} + +void lodepng_clear_itext(LodePNGInfo* info) { + LodePNGIText_cleanup(info); +} + +static unsigned lodepng_add_itext_sized(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str, size_t size) { + char** new_keys = (char**)(lodepng_realloc(info->itext_keys, sizeof(char*) * (info->itext_num + 1))); + char** new_langtags = (char**)(lodepng_realloc(info->itext_langtags, sizeof(char*) * (info->itext_num + 1))); + char** new_transkeys = (char**)(lodepng_realloc(info->itext_transkeys, sizeof(char*) * (info->itext_num + 1))); + char** new_strings = (char**)(lodepng_realloc(info->itext_strings, sizeof(char*) * (info->itext_num + 1))); + + if(new_keys) info->itext_keys = new_keys; + if(new_langtags) info->itext_langtags = new_langtags; + if(new_transkeys) info->itext_transkeys = new_transkeys; + if(new_strings) info->itext_strings = new_strings; + + if(!new_keys || !new_langtags || !new_transkeys || !new_strings) return 83; /*alloc fail*/ + + ++info->itext_num; + + info->itext_keys[info->itext_num - 1] = alloc_string(key); + info->itext_langtags[info->itext_num - 1] = alloc_string(langtag); + info->itext_transkeys[info->itext_num - 1] = alloc_string(transkey); + info->itext_strings[info->itext_num - 1] = alloc_string_sized(str, size); + + return 0; +} + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str) { + return lodepng_add_itext_sized(info, key, langtag, transkey, str, lodepng_strlen(str)); +} + +/* same as set but does not delete */ +static unsigned lodepng_assign_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(profile_size == 0) return 100; /*invalid ICC profile size*/ + + info->iccp_name = alloc_string(name); + info->iccp_profile = (unsigned char*)lodepng_malloc(profile_size); + + if(!info->iccp_name || !info->iccp_profile) return 83; /*alloc fail*/ + + lodepng_memcpy(info->iccp_profile, profile, profile_size); + info->iccp_profile_size = profile_size; + + return 0; /*ok*/ +} + +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size) { + if(info->iccp_name) lodepng_clear_icc(info); + info->iccp_defined = 1; + + return lodepng_assign_icc(info, name, profile, profile_size); +} + +void lodepng_clear_icc(LodePNGInfo* info) { + string_cleanup(&info->iccp_name); + lodepng_free(info->iccp_profile); + info->iccp_profile = NULL; + info->iccp_profile_size = 0; + info->iccp_defined = 0; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +void lodepng_info_init(LodePNGInfo* info) { + lodepng_color_mode_init(&info->color); + info->interlace_method = 0; + info->compression_method = 0; + info->filter_method = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + info->background_defined = 0; + info->background_r = info->background_g = info->background_b = 0; + + LodePNGText_init(info); + LodePNGIText_init(info); + + info->time_defined = 0; + info->phys_defined = 0; + + info->gama_defined = 0; + info->chrm_defined = 0; + info->srgb_defined = 0; + info->iccp_defined = 0; + info->iccp_name = NULL; + info->iccp_profile = NULL; + + LodePNGUnknownChunks_init(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +void lodepng_info_cleanup(LodePNGInfo* info) { + lodepng_color_mode_cleanup(&info->color); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + LodePNGText_cleanup(info); + LodePNGIText_cleanup(info); + + lodepng_clear_icc(info); + + LodePNGUnknownChunks_cleanup(info); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source) { + lodepng_info_cleanup(dest); + lodepng_memcpy(dest, source, sizeof(LodePNGInfo)); + lodepng_color_mode_init(&dest->color); + CERROR_TRY_RETURN(lodepng_color_mode_copy(&dest->color, &source->color)); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + CERROR_TRY_RETURN(LodePNGText_copy(dest, source)); + CERROR_TRY_RETURN(LodePNGIText_copy(dest, source)); + if(source->iccp_defined) { + CERROR_TRY_RETURN(lodepng_assign_icc(dest, source->iccp_name, source->iccp_profile, source->iccp_profile_size)); + } + + LodePNGUnknownChunks_init(dest); + CERROR_TRY_RETURN(LodePNGUnknownChunks_copy(dest, source)); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + return 0; +} + +/* ////////////////////////////////////////////////////////////////////////// */ + +/*index: bitgroup index, bits: bitgroup size(1, 2 or 4), in: bitgroup value, out: octet array to add bits to*/ +static void addColorBits(unsigned char* out, size_t index, unsigned bits, unsigned in) { + unsigned m = bits == 1 ? 7 : bits == 2 ? 3 : 1; /*8 / bits - 1*/ + /*p = the partial index in the byte, e.g. with 4 palettebits it is 0 for first half or 1 for second half*/ + unsigned p = index & m; + in &= (1u << bits) - 1u; /*filter out any other bits of the input value*/ + in = in << (bits * (m - p)); + if(p == 0) out[index * bits / 8u] = in; + else out[index * bits / 8u] |= in; +} + +typedef struct ColorTree ColorTree; + +/* +One node of a color tree +This is the data structure used to count the number of unique colors and to get a palette +index for a color. It's like an octree, but because the alpha channel is used too, each +node has 16 instead of 8 children. +*/ +struct ColorTree { + ColorTree* children[16]; /*up to 16 pointers to ColorTree of next level*/ + int index; /*the payload. Only has a meaningful value if this is in the last level*/ +}; + +static void color_tree_init(ColorTree* tree) { + lodepng_memset(tree->children, 0, 16 * sizeof(*tree->children)); + tree->index = -1; +} + +static void color_tree_cleanup(ColorTree* tree) { + int i; + for(i = 0; i != 16; ++i) { + if(tree->children[i]) { + color_tree_cleanup(tree->children[i]); + lodepng_free(tree->children[i]); + } + } +} + +/*returns -1 if color not present, its index otherwise*/ +static int color_tree_get(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + int bit = 0; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) return -1; + else tree = tree->children[i]; + } + return tree ? tree->index : -1; +} + +#ifdef LODEPNG_COMPILE_ENCODER +static int color_tree_has(ColorTree* tree, unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + return color_tree_get(tree, r, g, b, a) >= 0; +} +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/*color is not allowed to already exist. +Index should be >= 0 (it's signed to be compatible with using -1 for "doesn't exist") +Returns error code, or 0 if ok*/ +static unsigned color_tree_add(ColorTree* tree, + unsigned char r, unsigned char g, unsigned char b, unsigned char a, unsigned index) { + int bit; + for(bit = 0; bit < 8; ++bit) { + int i = 8 * ((r >> bit) & 1) + 4 * ((g >> bit) & 1) + 2 * ((b >> bit) & 1) + 1 * ((a >> bit) & 1); + if(!tree->children[i]) { + tree->children[i] = (ColorTree*)lodepng_malloc(sizeof(ColorTree)); + if(!tree->children[i]) return 83; /*alloc fail*/ + color_tree_init(tree->children[i]); + } + tree = tree->children[i]; + } + tree->index = (int)index; + return 0; +} + +/*put a pixel, given its RGBA color, into image of any color type*/ +static unsigned rgba8ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, ColorTree* tree /*for palette*/, + unsigned char r, unsigned char g, unsigned char b, unsigned char a) { + if(mode->colortype == LCT_GREY) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) out[i] = gray; + else if(mode->bitdepth == 16) out[i * 2 + 0] = out[i * 2 + 1] = gray; + else { + /*take the most significant bits of gray*/ + gray = ((unsigned)gray >> (8u - mode->bitdepth)) & ((1u << mode->bitdepth) - 1u); + addColorBits(out, i, mode->bitdepth, gray); + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + out[i * 3 + 0] = r; + out[i * 3 + 1] = g; + out[i * 3 + 2] = b; + } else { + out[i * 6 + 0] = out[i * 6 + 1] = r; + out[i * 6 + 2] = out[i * 6 + 3] = g; + out[i * 6 + 4] = out[i * 6 + 5] = b; + } + } else if(mode->colortype == LCT_PALETTE) { + int index = color_tree_get(tree, r, g, b, a); + if(index < 0) return 82; /*color not in palette*/ + if(mode->bitdepth == 8) out[i] = index; + else addColorBits(out, i, mode->bitdepth, (unsigned)index); + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned char gray = r; /*((unsigned short)r + g + b) / 3u;*/ + if(mode->bitdepth == 8) { + out[i * 2 + 0] = gray; + out[i * 2 + 1] = a; + } else if(mode->bitdepth == 16) { + out[i * 4 + 0] = out[i * 4 + 1] = gray; + out[i * 4 + 2] = out[i * 4 + 3] = a; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + out[i * 4 + 0] = r; + out[i * 4 + 1] = g; + out[i * 4 + 2] = b; + out[i * 4 + 3] = a; + } else { + out[i * 8 + 0] = out[i * 8 + 1] = r; + out[i * 8 + 2] = out[i * 8 + 3] = g; + out[i * 8 + 4] = out[i * 8 + 5] = b; + out[i * 8 + 6] = out[i * 8 + 7] = a; + } + } + + return 0; /*no error*/ +} + +/*put a pixel, given its RGBA16 color, into image of any color 16-bitdepth type*/ +static void rgba16ToPixel(unsigned char* out, size_t i, + const LodePNGColorMode* mode, + unsigned short r, unsigned short g, unsigned short b, unsigned short a) { + if(mode->colortype == LCT_GREY) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 2 + 0] = (gray >> 8) & 255; + out[i * 2 + 1] = gray & 255; + } else if(mode->colortype == LCT_RGB) { + out[i * 6 + 0] = (r >> 8) & 255; + out[i * 6 + 1] = r & 255; + out[i * 6 + 2] = (g >> 8) & 255; + out[i * 6 + 3] = g & 255; + out[i * 6 + 4] = (b >> 8) & 255; + out[i * 6 + 5] = b & 255; + } else if(mode->colortype == LCT_GREY_ALPHA) { + unsigned short gray = r; /*((unsigned)r + g + b) / 3u;*/ + out[i * 4 + 0] = (gray >> 8) & 255; + out[i * 4 + 1] = gray & 255; + out[i * 4 + 2] = (a >> 8) & 255; + out[i * 4 + 3] = a & 255; + } else if(mode->colortype == LCT_RGBA) { + out[i * 8 + 0] = (r >> 8) & 255; + out[i * 8 + 1] = r & 255; + out[i * 8 + 2] = (g >> 8) & 255; + out[i * 8 + 3] = g & 255; + out[i * 8 + 4] = (b >> 8) & 255; + out[i * 8 + 5] = b & 255; + out[i * 8 + 6] = (a >> 8) & 255; + out[i * 8 + 7] = a & 255; + } +} + +/*Get RGBA8 color of pixel with index i (y * width + x) from the raw image with given color type.*/ +static void getPixelColorRGBA8(unsigned char* r, unsigned char* g, + unsigned char* b, unsigned char* a, + const unsigned char* in, size_t i, + const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i]; + if(mode->key_defined && *r == mode->key_r) *a = 0; + else *a = 255; + } else if(mode->bitdepth == 16) { + *r = *g = *b = in[i * 2 + 0]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 255; + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = i * mode->bitdepth; + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + *r = *g = *b = (value * 255) / highest; + if(mode->key_defined && value == mode->key_r) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + *r = in[i * 3 + 0]; *g = in[i * 3 + 1]; *b = in[i * 3 + 2]; + if(mode->key_defined && *r == mode->key_r && *g == mode->key_g && *b == mode->key_b) *a = 0; + else *a = 255; + } else { + *r = in[i * 6 + 0]; + *g = in[i * 6 + 2]; + *b = in[i * 6 + 4]; + if(mode->key_defined && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 255; + } + } else if(mode->colortype == LCT_PALETTE) { + unsigned index; + if(mode->bitdepth == 8) index = in[i]; + else { + size_t j = i * mode->bitdepth; + index = readBitsFromReversedStream(&j, in, mode->bitdepth); + } + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + *r = mode->palette[index * 4 + 0]; + *g = mode->palette[index * 4 + 1]; + *b = mode->palette[index * 4 + 2]; + *a = mode->palette[index * 4 + 3]; + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + *r = *g = *b = in[i * 2 + 0]; + *a = in[i * 2 + 1]; + } else { + *r = *g = *b = in[i * 4 + 0]; + *a = in[i * 4 + 2]; + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + *r = in[i * 4 + 0]; + *g = in[i * 4 + 1]; + *b = in[i * 4 + 2]; + *a = in[i * 4 + 3]; + } else { + *r = in[i * 8 + 0]; + *g = in[i * 8 + 2]; + *b = in[i * 8 + 4]; + *a = in[i * 8 + 6]; + } + } +} + +/*Similar to getPixelColorRGBA8, but with all the for loops inside of the color +mode test cases, optimized to convert the colors much faster, when converting +to the common case of RGBA with 8 bit per channel. buffer must be RGBA with +enough memory.*/ +static void getPixelColorsRGBA8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + unsigned num_channels = 4; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r) buffer[3] = 0; + } + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + buffer[3] = mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r ? 0 : 255; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + buffer[3] = mode->key_defined && value == mode->key_r ? 0 : 255; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 3], 3); + buffer[3] = 255; + } + if(mode->key_defined) { + buffer -= numpixels * num_channels; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + if(buffer[0] == mode->key_r && buffer[1]== mode->key_g && buffer[2] == mode->key_b) buffer[3] = 0; + } + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + buffer[3] = mode->key_defined + && 256U * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256U * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256U * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b ? 0 : 255; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 4); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + buffer[3] = in[i * 2 + 1]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + buffer[3] = in[i * 4 + 2]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 4); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + buffer[3] = in[i * 8 + 6]; + } + } + } +} + +/*Similar to getPixelColorsRGBA8, but with 3-channel RGB output.*/ +static void getPixelColorsRGB8(unsigned char* LODEPNG_RESTRICT buffer, size_t numpixels, + const unsigned char* LODEPNG_RESTRICT in, + const LodePNGColorMode* mode) { + const unsigned num_channels = 3; + size_t i; + if(mode->colortype == LCT_GREY) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i]; + } + } else if(mode->bitdepth == 16) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2]; + } + } else { + unsigned highest = ((1U << mode->bitdepth) - 1U); /*highest possible value for this bit depth*/ + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned value = readBitsFromReversedStream(&j, in, mode->bitdepth); + buffer[0] = buffer[1] = buffer[2] = (value * 255) / highest; + } + } + } else if(mode->colortype == LCT_RGB) { + if(mode->bitdepth == 8) { + lodepng_memcpy(buffer, in, numpixels * 3); + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 6 + 0]; + buffer[1] = in[i * 6 + 2]; + buffer[2] = in[i * 6 + 4]; + } + } + } else if(mode->colortype == LCT_PALETTE) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = in[i]; + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } else { + size_t j = 0; + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + unsigned index = readBitsFromReversedStream(&j, in, mode->bitdepth); + /*out of bounds of palette not checked: see lodepng_color_mode_alloc_palette.*/ + lodepng_memcpy(buffer, &mode->palette[index * 4], 3); + } + } + } else if(mode->colortype == LCT_GREY_ALPHA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 2 + 0]; + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = buffer[1] = buffer[2] = in[i * 4 + 0]; + } + } + } else if(mode->colortype == LCT_RGBA) { + if(mode->bitdepth == 8) { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + lodepng_memcpy(buffer, &in[i * 4], 3); + } + } else { + for(i = 0; i != numpixels; ++i, buffer += num_channels) { + buffer[0] = in[i * 8 + 0]; + buffer[1] = in[i * 8 + 2]; + buffer[2] = in[i * 8 + 4]; + } + } + } +} + +/*Get RGBA16 color of pixel with index i (y * width + x) from the raw image with +given color type, but the given color type must be 16-bit itself.*/ +static void getPixelColorRGBA16(unsigned short* r, unsigned short* g, unsigned short* b, unsigned short* a, + const unsigned char* in, size_t i, const LodePNGColorMode* mode) { + if(mode->colortype == LCT_GREY) { + *r = *g = *b = 256 * in[i * 2 + 0] + in[i * 2 + 1]; + if(mode->key_defined && 256U * in[i * 2 + 0] + in[i * 2 + 1] == mode->key_r) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_RGB) { + *r = 256u * in[i * 6 + 0] + in[i * 6 + 1]; + *g = 256u * in[i * 6 + 2] + in[i * 6 + 3]; + *b = 256u * in[i * 6 + 4] + in[i * 6 + 5]; + if(mode->key_defined + && 256u * in[i * 6 + 0] + in[i * 6 + 1] == mode->key_r + && 256u * in[i * 6 + 2] + in[i * 6 + 3] == mode->key_g + && 256u * in[i * 6 + 4] + in[i * 6 + 5] == mode->key_b) *a = 0; + else *a = 65535; + } else if(mode->colortype == LCT_GREY_ALPHA) { + *r = *g = *b = 256u * in[i * 4 + 0] + in[i * 4 + 1]; + *a = 256u * in[i * 4 + 2] + in[i * 4 + 3]; + } else if(mode->colortype == LCT_RGBA) { + *r = 256u * in[i * 8 + 0] + in[i * 8 + 1]; + *g = 256u * in[i * 8 + 2] + in[i * 8 + 3]; + *b = 256u * in[i * 8 + 4] + in[i * 8 + 5]; + *a = 256u * in[i * 8 + 6] + in[i * 8 + 7]; + } +} + +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + if(mode_in->colortype == LCT_PALETTE && !mode_in->palette) { + return 107; /* error: must provide palette if input mode is palette */ + } + + if(lodepng_color_mode_equal(mode_out, mode_in)) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + + if(mode_out->colortype == LCT_PALETTE) { + size_t palettesize = mode_out->palettesize; + const unsigned char* palette = mode_out->palette; + size_t palsize = (size_t)1u << mode_out->bitdepth; + /*if the user specified output palette but did not give the values, assume + they want the values of the input color type (assuming that one is palette). + Note that we never create a new palette ourselves.*/ + if(palettesize == 0) { + palettesize = mode_in->palettesize; + palette = mode_in->palette; + /*if the input was also palette with same bitdepth, then the color types are also + equal, so copy literally. This to preserve the exact indices that were in the PNG + even in case there are duplicate colors in the palette.*/ + if(mode_in->colortype == LCT_PALETTE && mode_in->bitdepth == mode_out->bitdepth) { + size_t numbytes = lodepng_get_raw_size(w, h, mode_in); + lodepng_memcpy(out, in, numbytes); + return 0; + } + } + if(palettesize < palsize) palsize = palettesize; + color_tree_init(&tree); + for(i = 0; i != palsize; ++i) { + const unsigned char* p = &palette[i * 4]; + error = color_tree_add(&tree, p[0], p[1], p[2], p[3], (unsigned)i); + if(error) break; + } + } + + if(!error) { + if(mode_in->bitdepth == 16 && mode_out->bitdepth == 16) { + for(i = 0; i != numpixels; ++i) { + unsigned short r = 0, g = 0, b = 0, a = 0; + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + rgba16ToPixel(out, i, mode_out, r, g, b, a); + } + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGBA) { + getPixelColorsRGBA8(out, numpixels, in, mode_in); + } else if(mode_out->bitdepth == 8 && mode_out->colortype == LCT_RGB) { + getPixelColorsRGB8(out, numpixels, in, mode_in); + } else { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + error = rgba8ToPixel(out, i, mode_out, &tree, r, g, b, a); + if(error) break; + } + } + } + + if(mode_out->colortype == LCT_PALETTE) { + color_tree_cleanup(&tree); + } + + return error; +} + + +/* Converts a single rgb color without alpha from one type to another, color bits truncated to +their bitdepth. In case of single channel (gray or palette), only the r channel is used. Slow +function, do not use to process all pixels of an image. Alpha channel not supported on purpose: +this is for bKGD, supporting alpha may prevent it from finding a color in the palette, from the +specification it looks like bKGD should ignore the alpha values of the palette since it can use +any palette index but doesn't have an alpha channel. Idem with ignoring color key. */ +static unsigned lodepng_convert_rgb( + unsigned* r_out, unsigned* g_out, unsigned* b_out, + unsigned r_in, unsigned g_in, unsigned b_in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in) { + unsigned r = 0, g = 0, b = 0; + unsigned mul = 65535 / ((1u << mode_in->bitdepth) - 1u); /*65535, 21845, 4369, 257, 1*/ + unsigned shift = 16 - mode_out->bitdepth; + + if(mode_in->colortype == LCT_GREY || mode_in->colortype == LCT_GREY_ALPHA) { + r = g = b = r_in * mul; + } else if(mode_in->colortype == LCT_RGB || mode_in->colortype == LCT_RGBA) { + r = r_in * mul; + g = g_in * mul; + b = b_in * mul; + } else if(mode_in->colortype == LCT_PALETTE) { + if(r_in >= mode_in->palettesize) return 82; + r = mode_in->palette[r_in * 4 + 0] * 257u; + g = mode_in->palette[r_in * 4 + 1] * 257u; + b = mode_in->palette[r_in * 4 + 2] * 257u; + } else { + return 31; + } + + /* now convert to output format */ + if(mode_out->colortype == LCT_GREY || mode_out->colortype == LCT_GREY_ALPHA) { + *r_out = r >> shift ; + } else if(mode_out->colortype == LCT_RGB || mode_out->colortype == LCT_RGBA) { + *r_out = r >> shift ; + *g_out = g >> shift ; + *b_out = b >> shift ; + } else if(mode_out->colortype == LCT_PALETTE) { + unsigned i; + /* a 16-bit color cannot be in the palette */ + if((r >> 8) != (r & 255) || (g >> 8) != (g & 255) || (b >> 8) != (b & 255)) return 82; + for(i = 0; i < mode_out->palettesize; i++) { + unsigned j = i * 4; + if((r >> 8) == mode_out->palette[j + 0] && (g >> 8) == mode_out->palette[j + 1] && + (b >> 8) == mode_out->palette[j + 2]) { + *r_out = i; + return 0; + } + } + return 82; + } else { + return 31; + } + + return 0; +} + +#ifdef LODEPNG_COMPILE_ENCODER + +void lodepng_color_stats_init(LodePNGColorStats* stats) { + /*stats*/ + stats->colored = 0; + stats->key = 0; + stats->key_r = stats->key_g = stats->key_b = 0; + stats->alpha = 0; + stats->numcolors = 0; + stats->bits = 1; + stats->numpixels = 0; + /*settings*/ + stats->allow_palette = 1; + stats->allow_greyscale = 1; +} + +/*function used for debug purposes with C++*/ +/*void printColorStats(LodePNGColorStats* p) { + std::cout << "colored: " << (int)p->colored << ", "; + std::cout << "key: " << (int)p->key << ", "; + std::cout << "key_r: " << (int)p->key_r << ", "; + std::cout << "key_g: " << (int)p->key_g << ", "; + std::cout << "key_b: " << (int)p->key_b << ", "; + std::cout << "alpha: " << (int)p->alpha << ", "; + std::cout << "numcolors: " << (int)p->numcolors << ", "; + std::cout << "bits: " << (int)p->bits << std::endl; +}*/ + +/*Returns how many bits needed to represent given value (max 8 bit)*/ +static unsigned getValueRequiredBits(unsigned char value) { + if(value == 0 || value == 255) return 1; + /*The scaling of 2-bit and 4-bit values uses multiples of 85 and 17*/ + if(value % 17 == 0) return value % 85 == 0 ? 2 : 4; + return 8; +} + +/*stats must already have been inited. */ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* mode_in) { + size_t i; + ColorTree tree; + size_t numpixels = (size_t)w * (size_t)h; + unsigned error = 0; + + /* mark things as done already if it would be impossible to have a more expensive case */ + unsigned colored_done = lodepng_is_greyscale_type(mode_in) ? 1 : 0; + unsigned alpha_done = lodepng_can_have_alpha(mode_in) ? 0 : 1; + unsigned numcolors_done = 0; + unsigned bpp = lodepng_get_bpp(mode_in); + unsigned bits_done = (stats->bits == 1 && bpp == 1) ? 1 : 0; + unsigned sixteen = 0; /* whether the input image is 16 bit */ + unsigned maxnumcolors = 257; + if(bpp <= 8) maxnumcolors = LODEPNG_MIN(257, stats->numcolors + (1u << bpp)); + + stats->numpixels += numpixels; + + /*if palette not allowed, no need to compute numcolors*/ + if(!stats->allow_palette) numcolors_done = 1; + + color_tree_init(&tree); + + /*If the stats was already filled in from previous data, fill its palette in tree + and mark things as done already if we know they are the most expensive case already*/ + if(stats->alpha) alpha_done = 1; + if(stats->colored) colored_done = 1; + if(stats->bits == 16) numcolors_done = 1; + if(stats->bits >= bpp) bits_done = 1; + if(stats->numcolors >= maxnumcolors) numcolors_done = 1; + + if(!numcolors_done) { + for(i = 0; i < stats->numcolors; i++) { + const unsigned char* color = &stats->palette[i * 4]; + error = color_tree_add(&tree, color[0], color[1], color[2], color[3], i); + if(error) goto cleanup; + } + } + + /*Check if the 16-bit input is truly 16-bit*/ + if(mode_in->bitdepth == 16 && !sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if((r & 255) != ((r >> 8) & 255) || (g & 255) != ((g >> 8) & 255) || + (b & 255) != ((b >> 8) & 255) || (a & 255) != ((a >> 8) & 255)) /*first and second byte differ*/ { + stats->bits = 16; + sixteen = 1; + bits_done = 1; + numcolors_done = 1; /*counting colors no longer useful, palette doesn't support 16-bit*/ + break; + } + } + } + + if(sixteen) { + unsigned short r = 0, g = 0, b = 0, a = 0; + + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 65535 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 65535 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA16(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + } + } + } + } else /* < 16-bit */ { + unsigned char r = 0, g = 0, b = 0, a = 0; + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + + if(!bits_done && stats->bits < 8) { + /*only r is checked, < 8 bits is only relevant for grayscale*/ + unsigned bits = getValueRequiredBits(r); + if(bits > stats->bits) stats->bits = bits; + } + bits_done = (stats->bits >= bpp); + + if(!colored_done && (r != g || r != b)) { + stats->colored = 1; + colored_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no colored modes with less than 8-bit per channel*/ + } + + if(!alpha_done) { + unsigned matchkey = (r == stats->key_r && g == stats->key_g && b == stats->key_b); + if(a != 255 && (a != 0 || (stats->key && !matchkey))) { + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } else if(a == 0 && !stats->alpha && !stats->key) { + stats->key = 1; + stats->key_r = r; + stats->key_g = g; + stats->key_b = b; + } else if(a == 255 && stats->key && matchkey) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + + if(!numcolors_done) { + if(!color_tree_has(&tree, r, g, b, a)) { + error = color_tree_add(&tree, r, g, b, a, stats->numcolors); + if(error) goto cleanup; + if(stats->numcolors < 256) { + unsigned char* p = stats->palette; + unsigned n = stats->numcolors; + p[n * 4 + 0] = r; + p[n * 4 + 1] = g; + p[n * 4 + 2] = b; + p[n * 4 + 3] = a; + } + ++stats->numcolors; + numcolors_done = stats->numcolors >= maxnumcolors; + } + } + + if(alpha_done && numcolors_done && colored_done && bits_done) break; + } + + if(stats->key && !stats->alpha) { + for(i = 0; i != numpixels; ++i) { + getPixelColorRGBA8(&r, &g, &b, &a, in, i, mode_in); + if(a != 0 && r == stats->key_r && g == stats->key_g && b == stats->key_b) { + /* Color key cannot be used if an opaque pixel also has that RGB color. */ + stats->alpha = 1; + stats->key = 0; + alpha_done = 1; + if(stats->bits < 8) stats->bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + } + } + + /*make the stats's key always 16-bit for consistency - repeat each byte twice*/ + stats->key_r += (stats->key_r << 8); + stats->key_g += (stats->key_g << 8); + stats->key_b += (stats->key_b << 8); + } + +cleanup: + color_tree_cleanup(&tree); + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*Adds a single color to the color stats. The stats must already have been inited. The color must be given as 16-bit +(with 2 bytes repeating for 8-bit and 65535 for opaque alpha channel). This function is expensive, do not call it for +all pixels of an image but only for a few additional values. */ +static unsigned lodepng_color_stats_add(LodePNGColorStats* stats, + unsigned r, unsigned g, unsigned b, unsigned a) { + unsigned error = 0; + unsigned char image[8]; + LodePNGColorMode mode; + lodepng_color_mode_init(&mode); + image[0] = r >> 8; image[1] = r; image[2] = g >> 8; image[3] = g; + image[4] = b >> 8; image[5] = b; image[6] = a >> 8; image[7] = a; + mode.bitdepth = 16; + mode.colortype = LCT_RGBA; + error = lodepng_compute_color_stats(stats, image, 1, 1, &mode); + lodepng_color_mode_cleanup(&mode); + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Computes a minimal PNG color model that can contain all colors as indicated by the stats. +The stats should be computed with lodepng_compute_color_stats. +mode_in is raw color profile of the image the stats were computed on, to copy palette order from when relevant. +Minimal PNG color model means the color type and bit depth that gives smallest amount of bits in the output image, +e.g. gray if only grayscale pixels, palette if less than 256 colors, color key if only single transparent color, ... +This is used if auto_convert is enabled (it is by default). +*/ +static unsigned auto_choose_color(LodePNGColorMode* mode_out, + const LodePNGColorMode* mode_in, + const LodePNGColorStats* stats) { + unsigned error = 0; + unsigned palettebits; + size_t i, n; + size_t numpixels = stats->numpixels; + unsigned palette_ok, gray_ok; + + unsigned alpha = stats->alpha; + unsigned key = stats->key; + unsigned bits = stats->bits; + + mode_out->key_defined = 0; + + if(key && numpixels <= 16) { + alpha = 1; /*too few pixels to justify tRNS chunk overhead*/ + key = 0; + if(bits < 8) bits = 8; /*PNG has no alphachannel modes with less than 8-bit per channel*/ + } + + gray_ok = !stats->colored; + if(!stats->allow_greyscale) gray_ok = 0; + if(!gray_ok && bits < 8) bits = 8; + + n = stats->numcolors; + palettebits = n <= 2 ? 1 : (n <= 4 ? 2 : (n <= 16 ? 4 : 8)); + palette_ok = n <= 256 && bits <= 8 && n != 0; /*n==0 means likely numcolors wasn't computed*/ + if(numpixels < n * 2) palette_ok = 0; /*don't add palette overhead if image has only a few pixels*/ + if(gray_ok && !alpha && bits <= palettebits) palette_ok = 0; /*gray is less overhead*/ + if(!stats->allow_palette) palette_ok = 0; + + if(palette_ok) { + const unsigned char* p = stats->palette; + lodepng_palette_clear(mode_out); /*remove potential earlier palette*/ + for(i = 0; i != stats->numcolors; ++i) { + error = lodepng_palette_add(mode_out, p[i * 4 + 0], p[i * 4 + 1], p[i * 4 + 2], p[i * 4 + 3]); + if(error) break; + } + + mode_out->colortype = LCT_PALETTE; + mode_out->bitdepth = palettebits; + + if(mode_in->colortype == LCT_PALETTE && mode_in->palettesize >= mode_out->palettesize + && mode_in->bitdepth == mode_out->bitdepth) { + /*If input should have same palette colors, keep original to preserve its order and prevent conversion*/ + lodepng_color_mode_cleanup(mode_out); + lodepng_color_mode_copy(mode_out, mode_in); + } + } else /*8-bit or 16-bit per channel*/ { + mode_out->bitdepth = bits; + mode_out->colortype = alpha ? (gray_ok ? LCT_GREY_ALPHA : LCT_RGBA) + : (gray_ok ? LCT_GREY : LCT_RGB); + if(key) { + unsigned mask = (1u << mode_out->bitdepth) - 1u; /*stats always uses 16-bit, mask converts it*/ + mode_out->key_r = stats->key_r & mask; + mode_out->key_g = stats->key_g & mask; + mode_out->key_b = stats->key_b & mask; + mode_out->key_defined = 1; + } + } + + return error; +} + +#endif /* #ifdef LODEPNG_COMPILE_ENCODER */ + +/* +Paeth predictor, used by PNG filter type 4 +The parameters are of type short, but should come from unsigned chars, the shorts +are only needed to make the paeth calculation correct. +*/ +static unsigned char paethPredictor(short a, short b, short c) { + short pa = LODEPNG_ABS(b - c); + short pb = LODEPNG_ABS(a - c); + short pc = LODEPNG_ABS(a + b - c - c); + /* return input value associated with smallest of pa, pb, pc (with certain priority if equal) */ + if(pb < pa) { a = b; pa = pb; } + return (pc < pa) ? c : a; +} + +/*shared values used by multiple Adam7 related functions*/ + +static const unsigned ADAM7_IX[7] = { 0, 4, 0, 2, 0, 1, 0 }; /*x start values*/ +static const unsigned ADAM7_IY[7] = { 0, 0, 4, 0, 2, 0, 1 }; /*y start values*/ +static const unsigned ADAM7_DX[7] = { 8, 8, 4, 4, 2, 2, 1 }; /*x delta values*/ +static const unsigned ADAM7_DY[7] = { 8, 8, 8, 4, 4, 2, 2 }; /*y delta values*/ + +/* +Outputs various dimensions and positions in the image related to the Adam7 reduced images. +passw: output containing the width of the 7 passes +passh: output containing the height of the 7 passes +filter_passstart: output containing the index of the start and end of each + reduced image with filter bytes +padded_passstart output containing the index of the start and end of each + reduced image when without filter bytes but with padded scanlines +passstart: output containing the index of the start and end of each reduced + image without padding between scanlines, but still padding between the images +w, h: width and height of non-interlaced image +bpp: bits per pixel +"padded" is only relevant if bpp is less than 8 and a scanline or image does not + end at a full byte +*/ +static void Adam7_getpassvalues(unsigned passw[7], unsigned passh[7], size_t filter_passstart[8], + size_t padded_passstart[8], size_t passstart[8], unsigned w, unsigned h, unsigned bpp) { + /*the passstart values have 8 values: the 8th one indicates the byte after the end of the 7th (= last) pass*/ + unsigned i; + + /*calculate width and height in pixels of each pass*/ + for(i = 0; i != 7; ++i) { + passw[i] = (w + ADAM7_DX[i] - ADAM7_IX[i] - 1) / ADAM7_DX[i]; + passh[i] = (h + ADAM7_DY[i] - ADAM7_IY[i] - 1) / ADAM7_DY[i]; + if(passw[i] == 0) passh[i] = 0; + if(passh[i] == 0) passw[i] = 0; + } + + filter_passstart[0] = padded_passstart[0] = passstart[0] = 0; + for(i = 0; i != 7; ++i) { + /*if passw[i] is 0, it's 0 bytes, not 1 (no filtertype-byte)*/ + filter_passstart[i + 1] = filter_passstart[i] + + ((passw[i] && passh[i]) ? passh[i] * (1u + (passw[i] * bpp + 7u) / 8u) : 0); + /*bits padded if needed to fill full byte at end of each scanline*/ + padded_passstart[i + 1] = padded_passstart[i] + passh[i] * ((passw[i] * bpp + 7u) / 8u); + /*only padded at end of reduced image*/ + passstart[i + 1] = passstart[i] + (passh[i] * passw[i] * bpp + 7u) / 8u; + } +} + +#ifdef LODEPNG_COMPILE_DECODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Decoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + +/*read the information from the header and store it in the LodePNGInfo. return value is error*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned width, height; + LodePNGInfo* info = &state->info_png; + if(insize == 0 || in == 0) { + CERROR_RETURN_ERROR(state->error, 48); /*error: the given data is empty*/ + } + if(insize < 33) { + CERROR_RETURN_ERROR(state->error, 27); /*error: the data length is smaller than the length of a PNG header*/ + } + + /*when decoding a new PNG image, make sure all parameters created after previous decoding are reset*/ + /* TODO: remove this. One should use a new LodePNGState for new sessions */ + lodepng_info_cleanup(info); + lodepng_info_init(info); + + if(in[0] != 137 || in[1] != 80 || in[2] != 78 || in[3] != 71 + || in[4] != 13 || in[5] != 10 || in[6] != 26 || in[7] != 10) { + CERROR_RETURN_ERROR(state->error, 28); /*error: the first 8 bytes are not the correct PNG signature*/ + } + if(lodepng_chunk_length(in + 8) != 13) { + CERROR_RETURN_ERROR(state->error, 94); /*error: header size must be 13 bytes*/ + } + if(!lodepng_chunk_type_equals(in + 8, "IHDR")) { + CERROR_RETURN_ERROR(state->error, 29); /*error: it doesn't start with a IHDR chunk!*/ + } + + /*read the values given in the header*/ + width = lodepng_read32bitInt(&in[16]); + height = lodepng_read32bitInt(&in[20]); + /*TODO: remove the undocumented feature that allows to give null pointers to width or height*/ + if(w) *w = width; + if(h) *h = height; + info->color.bitdepth = in[24]; + info->color.colortype = (LodePNGColorType)in[25]; + info->compression_method = in[26]; + info->filter_method = in[27]; + info->interlace_method = in[28]; + + /*errors returned only after the parsing so other values are still output*/ + + /*error: invalid image size*/ + if(width == 0 || height == 0) CERROR_RETURN_ERROR(state->error, 93); + /*error: invalid colortype or bitdepth combination*/ + state->error = checkColorValidity(info->color.colortype, info->color.bitdepth); + if(state->error) return state->error; + /*error: only compression method 0 is allowed in the specification*/ + if(info->compression_method != 0) CERROR_RETURN_ERROR(state->error, 32); + /*error: only filter method 0 is allowed in the specification*/ + if(info->filter_method != 0) CERROR_RETURN_ERROR(state->error, 33); + /*error: only interlace methods 0 and 1 exist in the specification*/ + if(info->interlace_method > 1) CERROR_RETURN_ERROR(state->error, 34); + + if(!state->decoder.ignore_crc) { + unsigned CRC = lodepng_read32bitInt(&in[29]); + unsigned checksum = lodepng_crc32(&in[12], 17); + if(CRC != checksum) { + CERROR_RETURN_ERROR(state->error, 57); /*invalid CRC*/ + } + } + + return state->error; +} + +static unsigned unfilterScanline(unsigned char* recon, const unsigned char* scanline, const unsigned char* precon, + size_t bytewidth, unsigned char filterType, size_t length) { + /* + For PNG filter method 0 + unfilter a PNG image scanline by scanline. when the pixels are smaller than 1 byte, + the filter works byte per byte (bytewidth = 1) + precon is the previous unfiltered scanline, recon the result, scanline the current one + the incoming scanlines do NOT include the filtertype byte, that one is given in the parameter filterType instead + recon and scanline MAY be the same memory address! precon must be disjoint. + */ + + size_t i; + switch(filterType) { + case 0: + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + break; + case 1: + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + recon[i - bytewidth]; + break; + case 2: + if(precon) { + for(i = 0; i != length; ++i) recon[i] = scanline[i] + precon[i]; + } else { + for(i = 0; i != length; ++i) recon[i] = scanline[i]; + } + break; + case 3: + if(precon) { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i] + (precon[i] >> 1u); + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + ((recon[i - bytewidth] + precon[i]) >> 1u); + } else { + for(i = 0; i != bytewidth; ++i) recon[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) recon[i] = scanline[i] + (recon[i - bytewidth] >> 1u); + } + break; + case 4: + if(precon) { + for(i = 0; i != bytewidth; ++i) { + recon[i] = (scanline[i] + precon[i]); /*paethPredictor(0, precon[i], 0) is always precon[i]*/ + } + + /* Unroll independent paths of the paeth predictor. A 6x and 8x version would also be possible but that + adds too much code. Whether this actually speeds anything up at all depends on compiler and settings. */ + if(bytewidth >= 4) { + for(; i + 3 < length; i += 4) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2], s3 = scanline[i + 3]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2], r3 = recon[j + 3]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2], p3 = precon[i + 3]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2], q3 = precon[j + 3]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + recon[i + 3] = s3 + paethPredictor(r3, p3, q3); + } + } else if(bytewidth >= 3) { + for(; i + 2 < length; i += 3) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1], s2 = scanline[i + 2]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1], r2 = recon[j + 2]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1], p2 = precon[i + 2]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1], q2 = precon[j + 2]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + recon[i + 2] = s2 + paethPredictor(r2, p2, q2); + } + } else if(bytewidth >= 2) { + for(; i + 1 < length; i += 2) { + size_t j = i - bytewidth; + unsigned char s0 = scanline[i + 0], s1 = scanline[i + 1]; + unsigned char r0 = recon[j + 0], r1 = recon[j + 1]; + unsigned char p0 = precon[i + 0], p1 = precon[i + 1]; + unsigned char q0 = precon[j + 0], q1 = precon[j + 1]; + recon[i + 0] = s0 + paethPredictor(r0, p0, q0); + recon[i + 1] = s1 + paethPredictor(r1, p1, q1); + } + } + + for(; i != length; ++i) { + recon[i] = (scanline[i] + paethPredictor(recon[i - bytewidth], precon[i], precon[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) { + recon[i] = scanline[i]; + } + for(i = bytewidth; i < length; ++i) { + /*paethPredictor(recon[i - bytewidth], 0, 0) is always recon[i - bytewidth]*/ + recon[i] = (scanline[i] + recon[i - bytewidth]); + } + } + break; + default: return 36; /*error: invalid filter type given*/ + } + return 0; +} + +static unsigned unfilter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + /* + For PNG filter method 0 + this function unfilters a single image (e.g. without interlacing this is called once, with Adam7 seven times) + out must have enough bytes allocated already, in must have the scanlines + 1 filtertype byte per scanline + w and h are image dimensions or dimensions of reduced image, bpp is bits per pixel + in and out are allowed to be the same memory address (but aren't the same size since in has the extra filter bytes) + */ + + unsigned y; + unsigned char* prevline = 0; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + for(y = 0; y < h; ++y) { + size_t outindex = linebytes * y; + size_t inindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + unsigned char filterType = in[inindex]; + + CERROR_TRY_RETURN(unfilterScanline(&out[outindex], &in[inindex + 1], prevline, bytewidth, filterType, linebytes)); + + prevline = &out[outindex]; + } + + return 0; +} + +/* +in: Adam7 interlaced image, with no padding bits between scanlines, but between + reduced images so that each reduced image starts at a byte. +out: the same pixels, but re-ordered so that they're now a non-interlaced image with size w*h +bpp: bits per pixel +out has the following size in bits: w * h * bpp. +in is possibly bigger due to padding bits between reduced images. +out must be big enough AND must be 0 everywhere if bpp < 8 in the current implementation +(because that's likely a little bit faster) +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_deinterlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = passstart[i] + (y * passw[i] + x) * bytewidth; + size_t pixeloutstart = ((ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * (size_t)w + + ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + obp = (ADAM7_IY[i] + (size_t)y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + (size_t)x * ADAM7_DX[i]) * bpp; + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +static void removePaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /* + After filtering there are still padding bits if scanlines have non multiple of 8 bit amounts. They need + to be removed (except at last scanline of (Adam7-reduced) image) before working with pure image buffers + for the Adam7 code, the color convert code and the output to the user. + in and out are allowed to be the same buffer, in may also be higher but still overlapping; in must + have >= ilinebits*h bits, out must have >= olinebits*h bits, olinebits must be <= ilinebits + also used to move bits after earlier such operations happened, e.g. in a sequence of reduced images from Adam7 + only useful if (ilinebits - olinebits) is a value in the range 1..7 + */ + unsigned y; + size_t diff = ilinebits - olinebits; + size_t ibp = 0, obp = 0; /*input and output bit pointers*/ + for(y = 0; y < h; ++y) { + size_t x; + for(x = 0; x < olinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + ibp += diff; + } +} + +/*out must be buffer big enough to contain full image, and in must contain the full decompressed data from +the IDAT chunks (with filter index bytes and possible padding bits) +return value is error*/ +static unsigned postProcessScanlines(unsigned char* out, unsigned char* in, + unsigned w, unsigned h, const LodePNGInfo* info_png) { + /* + This function converts the filtered-padded-interlaced data into pure 2D image buffer with the PNG's colortype. + Steps: + *) if no Adam7: 1) unfilter 2) remove padding bits (= possible extra bits per scanline if bpp < 8) + *) if adam7: 1) 7x unfilter 2) 7x remove padding bits 3) Adam7_deinterlace + NOTE: the in buffer will be overwritten with intermediate data! + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + if(bpp == 0) return 31; /*error: invalid colortype*/ + + if(info_png->interlace_method == 0) { + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + CERROR_TRY_RETURN(unfilter(in, in, w, h, bpp)); + removePaddingBits(out, in, w * bpp, ((w * bpp + 7u) / 8u) * 8u, h); + } + /*we can immediately filter into the out buffer, no other steps needed*/ + else CERROR_TRY_RETURN(unfilter(out, in, w, h, bpp)); + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + for(i = 0; i != 7; ++i) { + CERROR_TRY_RETURN(unfilter(&in[padded_passstart[i]], &in[filter_passstart[i]], passw[i], passh[i], bpp)); + /*TODO: possible efficiency improvement: if in this reduced image the bits fit nicely in 1 scanline, + move bytes instead of bits or move not at all*/ + if(bpp < 8) { + /*remove padding bits in scanlines; after this there still may be padding + bits between the different reduced images: each reduced image still starts nicely at a byte*/ + removePaddingBits(&in[passstart[i]], &in[padded_passstart[i]], passw[i] * bpp, + ((passw[i] * bpp + 7u) / 8u) * 8u, passh[i]); + } + } + + Adam7_deinterlace(out, in, w, h, bpp); + } + + return 0; +} + +static unsigned readChunk_PLTE(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned pos = 0, i; + color->palettesize = chunkLength / 3u; + if(color->palettesize == 0 || color->palettesize > 256) return 38; /*error: palette too small or big*/ + lodepng_color_mode_alloc_palette(color); + if(!color->palette && color->palettesize) { + color->palettesize = 0; + return 83; /*alloc fail*/ + } + + for(i = 0; i != color->palettesize; ++i) { + color->palette[4 * i + 0] = data[pos++]; /*R*/ + color->palette[4 * i + 1] = data[pos++]; /*G*/ + color->palette[4 * i + 2] = data[pos++]; /*B*/ + color->palette[4 * i + 3] = 255; /*alpha*/ + } + + return 0; /* OK */ +} + +static unsigned readChunk_tRNS(LodePNGColorMode* color, const unsigned char* data, size_t chunkLength) { + unsigned i; + if(color->colortype == LCT_PALETTE) { + /*error: more alpha values given than there are palette entries*/ + if(chunkLength > color->palettesize) return 39; + + for(i = 0; i != chunkLength; ++i) color->palette[4 * i + 3] = data[i]; + } else if(color->colortype == LCT_GREY) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 30; + + color->key_defined = 1; + color->key_r = color->key_g = color->key_b = 256u * data[0] + data[1]; + } else if(color->colortype == LCT_RGB) { + /*error: this chunk must be 6 bytes for RGB image*/ + if(chunkLength != 6) return 41; + + color->key_defined = 1; + color->key_r = 256u * data[0] + data[1]; + color->key_g = 256u * data[2] + data[3]; + color->key_b = 256u * data[4] + data[5]; + } + else return 42; /*error: tRNS chunk not allowed for other color models*/ + + return 0; /* OK */ +} + + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*background color chunk (bKGD)*/ +static unsigned readChunk_bKGD(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(info->color.colortype == LCT_PALETTE) { + /*error: this chunk must be 1 byte for indexed color image*/ + if(chunkLength != 1) return 43; + + /*error: invalid palette index, or maybe this chunk appeared before PLTE*/ + if(data[0] >= info->color.palettesize) return 103; + + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = data[0]; + } else if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + /*error: this chunk must be 2 bytes for grayscale image*/ + if(chunkLength != 2) return 44; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = info->background_g = info->background_b = 256u * data[0] + data[1]; + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + /*error: this chunk must be 6 bytes for grayscale image*/ + if(chunkLength != 6) return 45; + + /*the values are truncated to bitdepth in the PNG file*/ + info->background_defined = 1; + info->background_r = 256u * data[0] + data[1]; + info->background_g = 256u * data[2] + data[3]; + info->background_b = 256u * data[4] + data[5]; + } + + return 0; /* OK */ +} + +/*text chunk (tEXt)*/ +static unsigned readChunk_tEXt(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + char *key = 0, *str = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + unsigned length, string2_begin; + + length = 0; + while(length < chunkLength && data[length] != 0) ++length; + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + string2_begin = length + 1; /*skip keyword null terminator*/ + + length = (unsigned)(chunkLength < string2_begin ? 0 : chunkLength - string2_begin); + str = (char*)lodepng_malloc(length + 1); + if(!str) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(str, data + string2_begin, length); + str[length] = 0; + + error = lodepng_add_text(info, key, str); + + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*compressed text chunk (zTXt)*/ +static unsigned readChunk_zTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + char *key = 0; + unsigned char* str = 0; + size_t size = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + if(data[length + 1] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) CERROR_BREAK(error, 75); /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[string2_begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(error) break; + error = lodepng_add_text_sized(info, key, (char*)str, size); + break; + } + + lodepng_free(key); + lodepng_free(str); + + return error; +} + +/*international text chunk (iTXt)*/ +static unsigned readChunk_iTXt(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, begin, compressed; + char *key = 0, *langtag = 0, *transkey = 0; + + while(!error) /*not really a while loop, only used to break on error*/ { + /*Quick check if the chunk length isn't too small. Even without check + it'd still fail with other error checks below if it's too short. This just gives a different error code.*/ + if(chunkLength < 5) CERROR_BREAK(error, 30); /*iTXt chunk too short*/ + + /*read the key*/ + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 3 >= chunkLength) CERROR_BREAK(error, 75); /*no null termination char, corrupt?*/ + if(length < 1 || length > 79) CERROR_BREAK(error, 89); /*keyword too short or long*/ + + key = (char*)lodepng_malloc(length + 1); + if(!key) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(key, data, length); + key[length] = 0; + + /*read the compression method*/ + compressed = data[length + 1]; + if(data[length + 2] != 0) CERROR_BREAK(error, 72); /*the 0 byte indicating compression must be 0*/ + + /*even though it's not allowed by the standard, no error is thrown if + there's no null termination char, if the text is empty for the next 3 texts*/ + + /*read the langtag*/ + begin = length + 3; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + langtag = (char*)lodepng_malloc(length + 1); + if(!langtag) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(langtag, data + begin, length); + langtag[length] = 0; + + /*read the transkey*/ + begin += length + 1; + length = 0; + for(i = begin; i < chunkLength && data[i] != 0; ++i) ++length; + + transkey = (char*)lodepng_malloc(length + 1); + if(!transkey) CERROR_BREAK(error, 83); /*alloc fail*/ + + lodepng_memcpy(transkey, data + begin, length); + transkey[length] = 0; + + /*read the actual text*/ + begin += length + 1; + + length = (unsigned)chunkLength < begin ? 0 : (unsigned)chunkLength - begin; + + if(compressed) { + unsigned char* str = 0; + size_t size = 0; + zlibsettings.max_output_size = decoder->max_text_size; + /*will fail if zlib error, e.g. if length is too small*/ + error = zlib_decompress(&str, &size, 0, &data[begin], + length, &zlibsettings); + /*error: compressed text larger than decoder->max_text_size*/ + if(error && size > zlibsettings.max_output_size) error = 112; + if(!error) error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)str, size); + lodepng_free(str); + } else { + error = lodepng_add_itext_sized(info, key, langtag, transkey, (char*)(data + begin), length); + } + + break; + } + + lodepng_free(key); + lodepng_free(langtag); + lodepng_free(transkey); + + return error; +} + +static unsigned readChunk_tIME(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 7) return 73; /*invalid tIME chunk size*/ + + info->time_defined = 1; + info->time.year = 256u * data[0] + data[1]; + info->time.month = data[2]; + info->time.day = data[3]; + info->time.hour = data[4]; + info->time.minute = data[5]; + info->time.second = data[6]; + + return 0; /* OK */ +} + +static unsigned readChunk_pHYs(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 9) return 74; /*invalid pHYs chunk size*/ + + info->phys_defined = 1; + info->phys_x = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + info->phys_y = 16777216u * data[4] + 65536u * data[5] + 256u * data[6] + data[7]; + info->phys_unit = data[8]; + + return 0; /* OK */ +} + +static unsigned readChunk_gAMA(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 4) return 96; /*invalid gAMA chunk size*/ + + info->gama_defined = 1; + info->gama_gamma = 16777216u * data[0] + 65536u * data[1] + 256u * data[2] + data[3]; + + return 0; /* OK */ +} + +static unsigned readChunk_cHRM(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 32) return 97; /*invalid cHRM chunk size*/ + + info->chrm_defined = 1; + info->chrm_white_x = 16777216u * data[ 0] + 65536u * data[ 1] + 256u * data[ 2] + data[ 3]; + info->chrm_white_y = 16777216u * data[ 4] + 65536u * data[ 5] + 256u * data[ 6] + data[ 7]; + info->chrm_red_x = 16777216u * data[ 8] + 65536u * data[ 9] + 256u * data[10] + data[11]; + info->chrm_red_y = 16777216u * data[12] + 65536u * data[13] + 256u * data[14] + data[15]; + info->chrm_green_x = 16777216u * data[16] + 65536u * data[17] + 256u * data[18] + data[19]; + info->chrm_green_y = 16777216u * data[20] + 65536u * data[21] + 256u * data[22] + data[23]; + info->chrm_blue_x = 16777216u * data[24] + 65536u * data[25] + 256u * data[26] + data[27]; + info->chrm_blue_y = 16777216u * data[28] + 65536u * data[29] + 256u * data[30] + data[31]; + + return 0; /* OK */ +} + +static unsigned readChunk_sRGB(LodePNGInfo* info, const unsigned char* data, size_t chunkLength) { + if(chunkLength != 1) return 98; /*invalid sRGB chunk size (this one is never ignored)*/ + + info->srgb_defined = 1; + info->srgb_intent = data[0]; + + return 0; /* OK */ +} + +static unsigned readChunk_iCCP(LodePNGInfo* info, const LodePNGDecoderSettings* decoder, + const unsigned char* data, size_t chunkLength) { + unsigned error = 0; + unsigned i; + size_t size = 0; + /*copy the object to change parameters in it*/ + LodePNGDecompressSettings zlibsettings = decoder->zlibsettings; + + unsigned length, string2_begin; + + info->iccp_defined = 1; + if(info->iccp_name) lodepng_clear_icc(info); + + for(length = 0; length < chunkLength && data[length] != 0; ++length) ; + if(length + 2 >= chunkLength) return 75; /*no null termination, corrupt?*/ + if(length < 1 || length > 79) return 89; /*keyword too short or long*/ + + info->iccp_name = (char*)lodepng_malloc(length + 1); + if(!info->iccp_name) return 83; /*alloc fail*/ + + info->iccp_name[length] = 0; + for(i = 0; i != length; ++i) info->iccp_name[i] = (char)data[i]; + + if(data[length + 1] != 0) return 72; /*the 0 byte indicating compression must be 0*/ + + string2_begin = length + 2; + if(string2_begin > chunkLength) return 75; /*no null termination, corrupt?*/ + + length = (unsigned)chunkLength - string2_begin; + zlibsettings.max_output_size = decoder->max_icc_size; + error = zlib_decompress(&info->iccp_profile, &size, 0, + &data[string2_begin], + length, &zlibsettings); + /*error: ICC profile larger than decoder->max_icc_size*/ + if(error && size > zlibsettings.max_output_size) error = 113; + info->iccp_profile_size = size; + if(!error && !info->iccp_profile_size) error = 100; /*invalid ICC profile size*/ + return error; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize) { + const unsigned char* chunk = in + pos; + unsigned chunkLength; + const unsigned char* data; + unsigned unhandled = 0; + unsigned error = 0; + + if(pos + 4 > insize) return 30; + chunkLength = lodepng_chunk_length(chunk); + if(chunkLength > 2147483647) return 63; + data = lodepng_chunk_data_const(chunk); + if(data + chunkLength + 4 > in + insize) return 30; + + if(lodepng_chunk_type_equals(chunk, "PLTE")) { + error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + error = readChunk_tRNS(&state->info_png.color, data, chunkLength); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + error = readChunk_bKGD(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + error = readChunk_tEXt(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + error = readChunk_tIME(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + error = readChunk_pHYs(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + error = readChunk_gAMA(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + error = readChunk_cHRM(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + error = readChunk_sRGB(&state->info_png, data, chunkLength); + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else { + /* unhandled chunk is ok (is not an error) */ + unhandled = 1; + } + + if(!error && !unhandled && !state->decoder.ignore_crc) { + if(lodepng_chunk_check_crc(chunk)) return 57; /*invalid CRC*/ + } + + return error; +} + +/*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/ +static void decodeGeneric(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + unsigned char IEND = 0; + const unsigned char* chunk; + unsigned char* idat; /*the data from idat chunks, zlib compressed*/ + size_t idatsize = 0; + unsigned char* scanlines = 0; + size_t scanlines_size = 0, expected_size = 0; + size_t outsize = 0; + + /*for unknown chunk order*/ + unsigned unknown = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned critical_pos = 1; /*1 = after IHDR, 2 = after PLTE, 3 = after IDAT*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + + + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + + state->error = lodepng_inspect(w, h, state, in, insize); /*reads header and resets other parameters in state->info_png*/ + if(state->error) return; + + if(lodepng_pixel_overflow(*w, *h, &state->info_png.color, &state->info_raw)) { + CERROR_RETURN(state->error, 92); /*overflow possible due to amount of pixels*/ + } + + /*the input filesize is a safe upper bound for the sum of idat chunks size*/ + idat = (unsigned char*)lodepng_malloc(insize); + if(!idat) CERROR_RETURN(state->error, 83); /*alloc fail*/ + + chunk = &in[33]; /*first byte of the first chunk after the header*/ + + /*loop through the chunks, ignoring unknown chunks and stopping at IEND chunk. + IDAT data is put at the start of the in buffer*/ + while(!IEND && !state->error) { + unsigned chunkLength; + const unsigned char* data; /*the data in the chunk*/ + + /*error: size of the in buffer too small to contain next chunk*/ + if((size_t)((chunk - in) + 12) > insize || chunk < in) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 30); + } + + /*length of the data of the chunk, excluding the length bytes, chunk type and CRC bytes*/ + chunkLength = lodepng_chunk_length(chunk); + /*error: chunk length larger than the max PNG chunk size*/ + if(chunkLength > 2147483647) { + if(state->decoder.ignore_end) break; /*other errors may still happen though*/ + CERROR_BREAK(state->error, 63); + } + + if((size_t)((chunk - in) + chunkLength + 12) > insize || (chunk + chunkLength + 12) < in) { + CERROR_BREAK(state->error, 64); /*error: size of the in buffer too small to contain next chunk*/ + } + + data = lodepng_chunk_data_const(chunk); + + unknown = 0; + + /*IDAT chunk, containing compressed image data*/ + if(lodepng_chunk_type_equals(chunk, "IDAT")) { + size_t newsize; + if(lodepng_addofl(idatsize, chunkLength, &newsize)) CERROR_BREAK(state->error, 95); + if(newsize > insize) CERROR_BREAK(state->error, 95); + lodepng_memcpy(idat + idatsize, data, chunkLength); + idatsize += chunkLength; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 3; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "IEND")) { + /*IEND chunk*/ + IEND = 1; + } else if(lodepng_chunk_type_equals(chunk, "PLTE")) { + /*palette chunk (PLTE)*/ + state->error = readChunk_PLTE(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + critical_pos = 2; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else if(lodepng_chunk_type_equals(chunk, "tRNS")) { + /*palette transparency chunk (tRNS). Even though this one is an ancillary chunk , it is still compiled + in without 'LODEPNG_COMPILE_ANCILLARY_CHUNKS' because it contains essential color information that + affects the alpha channel of pixels. */ + state->error = readChunk_tRNS(&state->info_png.color, data, chunkLength); + if(state->error) break; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*background color chunk (bKGD)*/ + } else if(lodepng_chunk_type_equals(chunk, "bKGD")) { + state->error = readChunk_bKGD(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "tEXt")) { + /*text chunk (tEXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_tEXt(&state->info_png, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "zTXt")) { + /*compressed text chunk (zTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_zTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "iTXt")) { + /*international text chunk (iTXt)*/ + if(state->decoder.read_text_chunks) { + state->error = readChunk_iTXt(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; + } + } else if(lodepng_chunk_type_equals(chunk, "tIME")) { + state->error = readChunk_tIME(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "pHYs")) { + state->error = readChunk_pHYs(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "gAMA")) { + state->error = readChunk_gAMA(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "cHRM")) { + state->error = readChunk_cHRM(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "sRGB")) { + state->error = readChunk_sRGB(&state->info_png, data, chunkLength); + if(state->error) break; + } else if(lodepng_chunk_type_equals(chunk, "iCCP")) { + state->error = readChunk_iCCP(&state->info_png, &state->decoder, data, chunkLength); + if(state->error) break; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } else /*it's not an implemented chunk type, so ignore it: skip over the data*/ { + /*error: unknown critical chunk (5th bit of first byte of chunk type is 0)*/ + if(!state->decoder.ignore_critical && !lodepng_chunk_ancillary(chunk)) { + CERROR_BREAK(state->error, 69); + } + + unknown = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(state->decoder.remember_unknown_chunks) { + state->error = lodepng_chunk_append(&state->info_png.unknown_chunks_data[critical_pos - 1], + &state->info_png.unknown_chunks_size[critical_pos - 1], chunk); + if(state->error) break; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + } + + if(!state->decoder.ignore_crc && !unknown) /*check CRC if wanted, only on known chunk types*/ { + if(lodepng_chunk_check_crc(chunk)) CERROR_BREAK(state->error, 57); /*invalid CRC*/ + } + + if(!IEND) chunk = lodepng_chunk_next_const(chunk, in + insize); + } + + if(!state->error && state->info_png.color.colortype == LCT_PALETTE && !state->info_png.color.palette) { + state->error = 106; /* error: PNG file must have PLTE chunk if color type is palette */ + } + + if(!state->error) { + /*predict output size, to allocate exact size for output buffer to avoid more dynamic allocation. + If the decompressed size does not match the prediction, the image must be corrupt.*/ + if(state->info_png.interlace_method == 0) { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + expected_size = lodepng_get_raw_size_idat(*w, *h, bpp); + } else { + size_t bpp = lodepng_get_bpp(&state->info_png.color); + /*Adam-7 interlaced: expected size is the sum of the 7 sub-images sizes*/ + expected_size = 0; + expected_size += lodepng_get_raw_size_idat((*w + 7) >> 3, (*h + 7) >> 3, bpp); + if(*w > 4) expected_size += lodepng_get_raw_size_idat((*w + 3) >> 3, (*h + 7) >> 3, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 3) >> 2, (*h + 3) >> 3, bpp); + if(*w > 2) expected_size += lodepng_get_raw_size_idat((*w + 1) >> 2, (*h + 3) >> 2, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 1) >> 1, (*h + 1) >> 2, bpp); + if(*w > 1) expected_size += lodepng_get_raw_size_idat((*w + 0) >> 1, (*h + 1) >> 1, bpp); + expected_size += lodepng_get_raw_size_idat((*w + 0), (*h + 0) >> 1, bpp); + } + + state->error = zlib_decompress(&scanlines, &scanlines_size, expected_size, idat, idatsize, &state->decoder.zlibsettings); + } + if(!state->error && scanlines_size != expected_size) state->error = 91; /*decompressed size doesn't match prediction*/ + lodepng_free(idat); + + if(!state->error) { + outsize = lodepng_get_raw_size(*w, *h, &state->info_png.color); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!*out) state->error = 83; /*alloc fail*/ + } + if(!state->error) { + lodepng_memset(*out, 0, outsize); + state->error = postProcessScanlines(*out, scanlines, *w, *h, &state->info_png); + } + lodepng_free(scanlines); +} + +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize) { + *out = 0; + decodeGeneric(out, w, h, state, in, insize); + if(state->error) return state->error; + if(!state->decoder.color_convert || lodepng_color_mode_equal(&state->info_raw, &state->info_png.color)) { + /*same color type, no copying or converting of data needed*/ + /*store the info_png color settings on the info_raw so that the info_raw still reflects what colortype + the raw image has to the end user*/ + if(!state->decoder.color_convert) { + state->error = lodepng_color_mode_copy(&state->info_raw, &state->info_png.color); + if(state->error) return state->error; + } + } else { /*color conversion needed*/ + unsigned char* data = *out; + size_t outsize; + + /*TODO: check if this works according to the statement in the documentation: "The converter can convert + from grayscale input color type, to 8-bit grayscale or grayscale with alpha"*/ + if(!(state->info_raw.colortype == LCT_RGB || state->info_raw.colortype == LCT_RGBA) + && !(state->info_raw.bitdepth == 8)) { + return 56; /*unsupported color mode conversion*/ + } + + outsize = lodepng_get_raw_size(*w, *h, &state->info_raw); + *out = (unsigned char*)lodepng_malloc(outsize); + if(!(*out)) { + state->error = 83; /*alloc fail*/ + } + else state->error = lodepng_convert(*out, data, &state->info_raw, + &state->info_png.color, *w, *h); + lodepng_free(data); + } + return state->error; +} + +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*disable reading things that this function doesn't output*/ + state.decoder.read_text_chunks = 0; + state.decoder.remember_unknown_chunks = 0; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + error = lodepng_decode(out, w, h, &state, in, insize); + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGBA, 8); +} + +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, const unsigned char* in, size_t insize) { + return lodepng_decode_memory(out, w, h, in, insize, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + size_t buffersize; + unsigned error; + /* safe output values in case error happens */ + *out = 0; + *w = *h = 0; + error = lodepng_load_file(&buffer, &buffersize, filename); + if(!error) error = lodepng_decode_memory(out, w, h, buffer, buffersize, colortype, bitdepth); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGBA, 8); +} + +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, const char* filename) { + return lodepng_decode_file(out, w, h, filename, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings) { + settings->color_convert = 1; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->read_text_chunks = 1; + settings->remember_unknown_chunks = 0; + settings->max_text_size = 16777216; + settings->max_icc_size = 16777216; /* 16MB is much more than enough for any reasonable ICC profile */ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + settings->ignore_crc = 0; + settings->ignore_critical = 0; + settings->ignore_end = 0; + lodepng_decompress_settings_init(&settings->zlibsettings); +} + +#endif /*LODEPNG_COMPILE_DECODER*/ + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) + +void lodepng_state_init(LodePNGState* state) { +#ifdef LODEPNG_COMPILE_DECODER + lodepng_decoder_settings_init(&state->decoder); +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + lodepng_encoder_settings_init(&state->encoder); +#endif /*LODEPNG_COMPILE_ENCODER*/ + lodepng_color_mode_init(&state->info_raw); + lodepng_info_init(&state->info_png); + state->error = 1; +} + +void lodepng_state_cleanup(LodePNGState* state) { + lodepng_color_mode_cleanup(&state->info_raw); + lodepng_info_cleanup(&state->info_png); +} + +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source) { + lodepng_state_cleanup(dest); + *dest = *source; + lodepng_color_mode_init(&dest->info_raw); + lodepng_info_init(&dest->info_png); + dest->error = lodepng_color_mode_copy(&dest->info_raw, &source->info_raw); if(dest->error) return; + dest->error = lodepng_info_copy(&dest->info_png, &source->info_png); if(dest->error) return; +} + +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_ENCODER + +/* ////////////////////////////////////////////////////////////////////////// */ +/* / PNG Encoder / */ +/* ////////////////////////////////////////////////////////////////////////// */ + + +static unsigned writeSignature(ucvector* out) { + size_t pos = out->size; + const unsigned char signature[] = {137, 80, 78, 71, 13, 10, 26, 10}; + /*8 bytes PNG signature, aka the magic bytes*/ + if(!ucvector_resize(out, out->size + 8)) return 83; /*alloc fail*/ + lodepng_memcpy(out->data + pos, signature, 8); + return 0; +} + +static unsigned addChunk_IHDR(ucvector* out, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth, unsigned interlace_method) { + unsigned char *chunk, *data; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 13, "IHDR")); + data = chunk + 8; + + lodepng_set32bitInt(data + 0, w); /*width*/ + lodepng_set32bitInt(data + 4, h); /*height*/ + data[8] = (unsigned char)bitdepth; /*bit depth*/ + data[9] = (unsigned char)colortype; /*color type*/ + data[10] = 0; /*compression method*/ + data[11] = 0; /*filter method*/ + data[12] = interlace_method; /*interlace method*/ + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +/* only adds the chunk if needed (there is a key or palette with alpha) */ +static unsigned addChunk_PLTE(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk; + size_t i, j = 8; + + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, info->palettesize * 3, "PLTE")); + + for(i = 0; i != info->palettesize; ++i) { + /*add all channels except alpha channel*/ + chunk[j++] = info->palette[i * 4 + 0]; + chunk[j++] = info->palette[i * 4 + 1]; + chunk[j++] = info->palette[i * 4 + 2]; + } + + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tRNS(ucvector* out, const LodePNGColorMode* info) { + unsigned char* chunk = 0; + + if(info->colortype == LCT_PALETTE) { + size_t i, amount = info->palettesize; + /*the tail of palette values that all have 255 as alpha, does not have to be encoded*/ + for(i = info->palettesize; i != 0; --i) { + if(info->palette[4 * (i - 1) + 3] != 255) break; + --amount; + } + if(amount) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, amount, "tRNS")); + /*add the alpha channel values from the palette*/ + for(i = 0; i != amount; ++i) chunk[8 + i] = info->palette[4 * i + 3]; + } + } else if(info->colortype == LCT_GREY) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + } + } else if(info->colortype == LCT_RGB) { + if(info->key_defined) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "tRNS")); + chunk[8] = (unsigned char)(info->key_r >> 8); + chunk[9] = (unsigned char)(info->key_r & 255); + chunk[10] = (unsigned char)(info->key_g >> 8); + chunk[11] = (unsigned char)(info->key_g & 255); + chunk[12] = (unsigned char)(info->key_b >> 8); + chunk[13] = (unsigned char)(info->key_b & 255); + } + } + + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_IDAT(ucvector* out, const unsigned char* data, size_t datasize, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* zlib = 0; + size_t zlibsize = 0; + + error = zlib_compress(&zlib, &zlibsize, data, datasize, zlibsettings); + if(!error) { + error = lodepng_chunk_createv(out, zlibsize, "IDAT", zlib); + } + lodepng_free(zlib); + return error; +} + +static unsigned addChunk_IEND(ucvector* out) { + return lodepng_chunk_createv(out, 0, "IEND", 0); +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + +static unsigned addChunk_tEXt(ucvector* out, const char* keyword, const char* textstring) { + unsigned char* chunk = 0; + size_t keysize = lodepng_strlen(keyword), textsize = lodepng_strlen(textstring); + size_t size = keysize + 1 + textsize; + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, size, "tEXt")); + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + lodepng_memcpy(chunk + 9 + keysize, textstring, textsize); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_zTXt(ucvector* out, const char* keyword, const char* textstring, + LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword); + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "zTXt"); + } + if(!error) { + lodepng_memcpy(chunk + 8, keyword, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_iTXt(ucvector* out, unsigned compress, const char* keyword, const char* langtag, + const char* transkey, const char* textstring, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t textsize = lodepng_strlen(textstring); + size_t keysize = lodepng_strlen(keyword), langsize = lodepng_strlen(langtag), transsize = lodepng_strlen(transkey); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + + if(compress) { + error = zlib_compress(&compressed, &compressedsize, + (const unsigned char*)textstring, textsize, zlibsettings); + } + if(!error) { + size_t size = keysize + 3 + langsize + 1 + transsize + 1 + (compress ? compressedsize : textsize); + error = lodepng_chunk_init(&chunk, out, size, "iTXt"); + } + if(!error) { + size_t pos = 8; + lodepng_memcpy(chunk + pos, keyword, keysize); + pos += keysize; + chunk[pos++] = 0; /*null termination char*/ + chunk[pos++] = (compress ? 1 : 0); /*compression flag*/ + chunk[pos++] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + pos, langtag, langsize); + pos += langsize; + chunk[pos++] = 0; /*null termination char*/ + lodepng_memcpy(chunk + pos, transkey, transsize); + pos += transsize; + chunk[pos++] = 0; /*null termination char*/ + if(compress) { + lodepng_memcpy(chunk + pos, compressed, compressedsize); + } else { + lodepng_memcpy(chunk + pos, textstring, textsize); + } + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +static unsigned addChunk_bKGD(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk = 0; + if(info->color.colortype == LCT_GREY || info->color.colortype == LCT_GREY_ALPHA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 2, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + } else if(info->color.colortype == LCT_RGB || info->color.colortype == LCT_RGBA) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 6, "bKGD")); + chunk[8] = (unsigned char)(info->background_r >> 8); + chunk[9] = (unsigned char)(info->background_r & 255); + chunk[10] = (unsigned char)(info->background_g >> 8); + chunk[11] = (unsigned char)(info->background_g & 255); + chunk[12] = (unsigned char)(info->background_b >> 8); + chunk[13] = (unsigned char)(info->background_b & 255); + } else if(info->color.colortype == LCT_PALETTE) { + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 1, "bKGD")); + chunk[8] = (unsigned char)(info->background_r & 255); /*palette index*/ + } + if(chunk) lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_tIME(ucvector* out, const LodePNGTime* time) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 7, "tIME")); + chunk[8] = (unsigned char)(time->year >> 8); + chunk[9] = (unsigned char)(time->year & 255); + chunk[10] = (unsigned char)time->month; + chunk[11] = (unsigned char)time->day; + chunk[12] = (unsigned char)time->hour; + chunk[13] = (unsigned char)time->minute; + chunk[14] = (unsigned char)time->second; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_pHYs(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 9, "pHYs")); + lodepng_set32bitInt(chunk + 8, info->phys_x); + lodepng_set32bitInt(chunk + 12, info->phys_y); + chunk[16] = info->phys_unit; + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_gAMA(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 4, "gAMA")); + lodepng_set32bitInt(chunk + 8, info->gama_gamma); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_cHRM(ucvector* out, const LodePNGInfo* info) { + unsigned char* chunk; + CERROR_TRY_RETURN(lodepng_chunk_init(&chunk, out, 32, "cHRM")); + lodepng_set32bitInt(chunk + 8, info->chrm_white_x); + lodepng_set32bitInt(chunk + 12, info->chrm_white_y); + lodepng_set32bitInt(chunk + 16, info->chrm_red_x); + lodepng_set32bitInt(chunk + 20, info->chrm_red_y); + lodepng_set32bitInt(chunk + 24, info->chrm_green_x); + lodepng_set32bitInt(chunk + 28, info->chrm_green_y); + lodepng_set32bitInt(chunk + 32, info->chrm_blue_x); + lodepng_set32bitInt(chunk + 36, info->chrm_blue_y); + lodepng_chunk_generate_crc(chunk); + return 0; +} + +static unsigned addChunk_sRGB(ucvector* out, const LodePNGInfo* info) { + unsigned char data = info->srgb_intent; + return lodepng_chunk_createv(out, 1, "sRGB", &data); +} + +static unsigned addChunk_iCCP(ucvector* out, const LodePNGInfo* info, LodePNGCompressSettings* zlibsettings) { + unsigned error = 0; + unsigned char* chunk = 0; + unsigned char* compressed = 0; + size_t compressedsize = 0; + size_t keysize = lodepng_strlen(info->iccp_name); + + if(keysize < 1 || keysize > 79) return 89; /*error: invalid keyword size*/ + error = zlib_compress(&compressed, &compressedsize, + info->iccp_profile, info->iccp_profile_size, zlibsettings); + if(!error) { + size_t size = keysize + 2 + compressedsize; + error = lodepng_chunk_init(&chunk, out, size, "iCCP"); + } + if(!error) { + lodepng_memcpy(chunk + 8, info->iccp_name, keysize); + chunk[8 + keysize] = 0; /*null termination char*/ + chunk[9 + keysize] = 0; /*compression method: 0*/ + lodepng_memcpy(chunk + 10 + keysize, compressed, compressedsize); + lodepng_chunk_generate_crc(chunk); + } + + lodepng_free(compressed); + return error; +} + +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +static void filterScanline(unsigned char* out, const unsigned char* scanline, const unsigned char* prevline, + size_t length, size_t bytewidth, unsigned char filterType) { + size_t i; + switch(filterType) { + case 0: /*None*/ + for(i = 0; i != length; ++i) out[i] = scanline[i]; + break; + case 1: /*Sub*/ + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - scanline[i - bytewidth]; + break; + case 2: /*Up*/ + if(prevline) { + for(i = 0; i != length; ++i) out[i] = scanline[i] - prevline[i]; + } else { + for(i = 0; i != length; ++i) out[i] = scanline[i]; + } + break; + case 3: /*Average*/ + if(prevline) { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i] - (prevline[i] >> 1); + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - ((scanline[i - bytewidth] + prevline[i]) >> 1); + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + for(i = bytewidth; i < length; ++i) out[i] = scanline[i] - (scanline[i - bytewidth] >> 1); + } + break; + case 4: /*Paeth*/ + if(prevline) { + /*paethPredictor(0, prevline[i], 0) is always prevline[i]*/ + for(i = 0; i != bytewidth; ++i) out[i] = (scanline[i] - prevline[i]); + for(i = bytewidth; i < length; ++i) { + out[i] = (scanline[i] - paethPredictor(scanline[i - bytewidth], prevline[i], prevline[i - bytewidth])); + } + } else { + for(i = 0; i != bytewidth; ++i) out[i] = scanline[i]; + /*paethPredictor(scanline[i - bytewidth], 0, 0) is always scanline[i - bytewidth]*/ + for(i = bytewidth; i < length; ++i) out[i] = (scanline[i] - scanline[i - bytewidth]); + } + break; + default: return; /*invalid filter type given*/ + } +} + +/* integer binary logarithm, max return value is 31 */ +static size_t ilog2(size_t i) { + size_t result = 0; + if(i >= 65536) { result += 16; i >>= 16; } + if(i >= 256) { result += 8; i >>= 8; } + if(i >= 16) { result += 4; i >>= 4; } + if(i >= 4) { result += 2; i >>= 2; } + if(i >= 2) { result += 1; /*i >>= 1;*/ } + return result; +} + +/* integer approximation for i * log2(i), helper function for LFS_ENTROPY */ +static size_t ilog2i(size_t i) { + size_t l; + if(i == 0) return 0; + l = ilog2(i); + /* approximate i*log2(i): l is integer logarithm, ((i - (1u << l)) << 1u) + linearly approximates the missing fractional part multiplied by i */ + return i * l + ((i - (1u << l)) << 1u); +} + +static unsigned filter(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, + const LodePNGColorMode* color, const LodePNGEncoderSettings* settings) { + /* + For PNG filter method 0 + out must be a buffer with as size: h + (w * h * bpp + 7u) / 8u, because there are + the scanlines with 1 extra byte per scanline + */ + + unsigned bpp = lodepng_get_bpp(color); + /*the width of a scanline in bytes, not including the filter type*/ + size_t linebytes = lodepng_get_raw_size_idat(w, 1, bpp) - 1u; + + /*bytewidth is used for filtering, is 1 when bpp < 8, number of bytes per pixel otherwise*/ + size_t bytewidth = (bpp + 7u) / 8u; + const unsigned char* prevline = 0; + unsigned x, y; + unsigned error = 0; + LodePNGFilterStrategy strategy = settings->filter_strategy; + + /* + There is a heuristic called the minimum sum of absolute differences heuristic, suggested by the PNG standard: + * If the image type is Palette, or the bit depth is smaller than 8, then do not filter the image (i.e. + use fixed filtering, with the filter None). + * (The other case) If the image type is Grayscale or RGB (with or without Alpha), and the bit depth is + not smaller than 8, then use adaptive filtering heuristic as follows: independently for each row, apply + all five filters and select the filter that produces the smallest sum of absolute values per row. + This heuristic is used if filter strategy is LFS_MINSUM and filter_palette_zero is true. + + If filter_palette_zero is true and filter_strategy is not LFS_MINSUM, the above heuristic is followed, + but for "the other case", whatever strategy filter_strategy is set to instead of the minimum sum + heuristic is used. + */ + if(settings->filter_palette_zero && + (color->colortype == LCT_PALETTE || color->bitdepth < 8)) strategy = LFS_ZERO; + + if(bpp == 0) return 31; /*error: invalid color type*/ + + if(strategy >= LFS_ZERO && strategy <= LFS_FOUR) { + unsigned char type = (unsigned char)strategy; + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_MINSUM) { + /*adaptive filtering*/ + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned char type, bestType = 0; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + + /*calculate the sum of the result*/ + if(type == 0) { + for(x = 0; x != linebytes; ++x) sum += (unsigned char)(attempt[type][x]); + } else { + for(x = 0; x != linebytes; ++x) { + /*For differences, each byte should be treated as signed, values above 127 are negative + (converted to signed char). Filtertype 0 isn't a difference though, so use unsigned there. + This means filtertype 0 is almost never chosen, but that is justified.*/ + unsigned char s = attempt[type][x]; + sum += s < 128 ? s : (255U - s); + } + } + + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum < smallest) { + bestType = type; + smallest = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_ENTROPY) { + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t bestSum = 0; + unsigned type, bestType = 0; + unsigned count[256]; + + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + + if(!error) { + for(y = 0; y != h; ++y) { + /*try the 5 filter types*/ + for(type = 0; type != 5; ++type) { + size_t sum = 0; + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + lodepng_memset(count, 0, 256 * sizeof(*count)); + for(x = 0; x != linebytes; ++x) ++count[attempt[type][x]]; + ++count[type]; /*the filter type itself is part of the scanline*/ + for(x = 0; x != 256; ++x) { + sum += ilog2i(count[x]); + } + /*check if this is smallest sum (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || sum > bestSum) { + bestType = type; + bestSum = sum; + } + } + + prevline = &in[y * linebytes]; + + /*now fill the out values*/ + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } else if(strategy == LFS_PREDEFINED) { + for(y = 0; y != h; ++y) { + size_t outindex = (1 + linebytes) * y; /*the extra filterbyte added to each row*/ + size_t inindex = linebytes * y; + unsigned char type = settings->predefined_filters[y]; + out[outindex] = type; /*filter type byte*/ + filterScanline(&out[outindex + 1], &in[inindex], prevline, linebytes, bytewidth, type); + prevline = &in[inindex]; + } + } else if(strategy == LFS_BRUTE_FORCE) { + /*brute force filter chooser. + deflate the scanline after every filter attempt to see which one deflates best. + This is very slow and gives only slightly smaller, sometimes even larger, result*/ + size_t size[5]; + unsigned char* attempt[5]; /*five filtering attempts, one for each filter type*/ + size_t smallest = 0; + unsigned type = 0, bestType = 0; + unsigned char* dummy; + LodePNGCompressSettings zlibsettings; + lodepng_memcpy(&zlibsettings, &settings->zlibsettings, sizeof(LodePNGCompressSettings)); + /*use fixed tree on the attempts so that the tree is not adapted to the filtertype on purpose, + to simulate the true case where the tree is the same for the whole image. Sometimes it gives + better result with dynamic tree anyway. Using the fixed tree sometimes gives worse, but in rare + cases better compression. It does make this a bit less slow, so it's worth doing this.*/ + zlibsettings.btype = 1; + /*a custom encoder likely doesn't read the btype setting and is optimized for complete PNG + images only, so disable it*/ + zlibsettings.custom_zlib = 0; + zlibsettings.custom_deflate = 0; + for(type = 0; type != 5; ++type) { + attempt[type] = (unsigned char*)lodepng_malloc(linebytes); + if(!attempt[type]) error = 83; /*alloc fail*/ + } + if(!error) { + for(y = 0; y != h; ++y) /*try the 5 filter types*/ { + for(type = 0; type != 5; ++type) { + unsigned testsize = (unsigned)linebytes; + /*if(testsize > 8) testsize /= 8;*/ /*it already works good enough by testing a part of the row*/ + + filterScanline(attempt[type], &in[y * linebytes], prevline, linebytes, bytewidth, type); + size[type] = 0; + dummy = 0; + zlib_compress(&dummy, &size[type], attempt[type], testsize, &zlibsettings); + lodepng_free(dummy); + /*check if this is smallest size (or if type == 0 it's the first case so always store the values)*/ + if(type == 0 || size[type] < smallest) { + bestType = type; + smallest = size[type]; + } + } + prevline = &in[y * linebytes]; + out[y * (linebytes + 1)] = bestType; /*the first byte of a scanline will be the filter type*/ + for(x = 0; x != linebytes; ++x) out[y * (linebytes + 1) + 1 + x] = attempt[bestType][x]; + } + } + for(type = 0; type != 5; ++type) lodepng_free(attempt[type]); + } + else return 88; /* unknown filter strategy */ + + return error; +} + +static void addPaddingBits(unsigned char* out, const unsigned char* in, + size_t olinebits, size_t ilinebits, unsigned h) { + /*The opposite of the removePaddingBits function + olinebits must be >= ilinebits*/ + unsigned y; + size_t diff = olinebits - ilinebits; + size_t obp = 0, ibp = 0; /*bit pointers*/ + for(y = 0; y != h; ++y) { + size_t x; + for(x = 0; x < ilinebits; ++x) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + /*obp += diff; --> no, fill in some value in the padding bits too, to avoid + "Use of uninitialised value of size ###" warning from valgrind*/ + for(x = 0; x != diff; ++x) setBitOfReversedStream(&obp, out, 0); + } +} + +/* +in: non-interlaced image with size w*h +out: the same pixels, but re-ordered according to PNG's Adam7 interlacing, with + no padding bits between scanlines, but between reduced images so that each + reduced image starts at a byte. +bpp: bits per pixel +there are no padding bits, not between scanlines, not between reduced images +in has the following size in bits: w * h * bpp. +out is possibly bigger due to padding bits between reduced images +NOTE: comments about padding bits are only relevant if bpp < 8 +*/ +static void Adam7_interlace(unsigned char* out, const unsigned char* in, unsigned w, unsigned h, unsigned bpp) { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned i; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + if(bpp >= 8) { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + size_t bytewidth = bpp / 8u; + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + size_t pixelinstart = ((ADAM7_IY[i] + y * ADAM7_DY[i]) * w + ADAM7_IX[i] + x * ADAM7_DX[i]) * bytewidth; + size_t pixeloutstart = passstart[i] + (y * passw[i] + x) * bytewidth; + for(b = 0; b < bytewidth; ++b) { + out[pixeloutstart + b] = in[pixelinstart + b]; + } + } + } + } else /*bpp < 8: Adam7 with pixels < 8 bit is a bit trickier: with bit pointers*/ { + for(i = 0; i != 7; ++i) { + unsigned x, y, b; + unsigned ilinebits = bpp * passw[i]; + unsigned olinebits = bpp * w; + size_t obp, ibp; /*bit pointers (for out and in buffer)*/ + for(y = 0; y < passh[i]; ++y) + for(x = 0; x < passw[i]; ++x) { + ibp = (ADAM7_IY[i] + y * ADAM7_DY[i]) * olinebits + (ADAM7_IX[i] + x * ADAM7_DX[i]) * bpp; + obp = (8 * passstart[i]) + (y * ilinebits + x * bpp); + for(b = 0; b < bpp; ++b) { + unsigned char bit = readBitFromReversedStream(&ibp, in); + setBitOfReversedStream(&obp, out, bit); + } + } + } + } +} + +/*out must be buffer big enough to contain uncompressed IDAT chunk data, and in must contain the full image. +return value is error**/ +static unsigned preProcessScanlines(unsigned char** out, size_t* outsize, const unsigned char* in, + unsigned w, unsigned h, + const LodePNGInfo* info_png, const LodePNGEncoderSettings* settings) { + /* + This function converts the pure 2D image with the PNG's colortype, into filtered-padded-interlaced data. Steps: + *) if no Adam7: 1) add padding bits (= possible extra bits per scanline if bpp < 8) 2) filter + *) if adam7: 1) Adam7_interlace 2) 7x add padding bits 3) 7x filter + */ + unsigned bpp = lodepng_get_bpp(&info_png->color); + unsigned error = 0; + + if(info_png->interlace_method == 0) { + *outsize = h + (h * ((w * bpp + 7u) / 8u)); /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out) && (*outsize)) error = 83; /*alloc fail*/ + + if(!error) { + /*non multiple of 8 bits per scanline, padding bits needed per scanline*/ + if(bpp < 8 && w * bpp != ((w * bpp + 7u) / 8u) * 8u) { + unsigned char* padded = (unsigned char*)lodepng_malloc(h * ((w * bpp + 7u) / 8u)); + if(!padded) error = 83; /*alloc fail*/ + if(!error) { + addPaddingBits(padded, in, ((w * bpp + 7u) / 8u) * 8u, w * bpp, h); + error = filter(*out, padded, w, h, &info_png->color, settings); + } + lodepng_free(padded); + } else { + /*we can immediately filter into the out buffer, no other steps needed*/ + error = filter(*out, in, w, h, &info_png->color, settings); + } + } + } else /*interlace_method is 1 (Adam7)*/ { + unsigned passw[7], passh[7]; + size_t filter_passstart[8], padded_passstart[8], passstart[8]; + unsigned char* adam7; + + Adam7_getpassvalues(passw, passh, filter_passstart, padded_passstart, passstart, w, h, bpp); + + *outsize = filter_passstart[7]; /*image size plus an extra byte per scanline + possible padding bits*/ + *out = (unsigned char*)lodepng_malloc(*outsize); + if(!(*out)) error = 83; /*alloc fail*/ + + adam7 = (unsigned char*)lodepng_malloc(passstart[7]); + if(!adam7 && passstart[7]) error = 83; /*alloc fail*/ + + if(!error && adam7) { + unsigned i; + + Adam7_interlace(adam7, in, w, h, bpp); + for(i = 0; i != 7; ++i) { + if(bpp < 8) { + unsigned char* padded = (unsigned char*)lodepng_malloc(padded_passstart[i + 1] - padded_passstart[i]); + if(!padded) ERROR_BREAK(83); /*alloc fail*/ + addPaddingBits(padded, &adam7[passstart[i]], + ((passw[i] * bpp + 7u) / 8u) * 8u, passw[i] * bpp, passh[i]); + error = filter(&(*out)[filter_passstart[i]], padded, + passw[i], passh[i], &info_png->color, settings); + lodepng_free(padded); + } else { + error = filter(&(*out)[filter_passstart[i]], &adam7[padded_passstart[i]], + passw[i], passh[i], &info_png->color, settings); + } + + if(error) break; + } + } + + lodepng_free(adam7); + } + + return error; +} + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +static unsigned addUnknownChunks(ucvector* out, unsigned char* data, size_t datasize) { + unsigned char* inchunk = data; + while((size_t)(inchunk - data) < datasize) { + CERROR_TRY_RETURN(lodepng_chunk_append(&out->data, &out->size, inchunk)); + out->allocsize = out->size; /*fix the allocsize again*/ + inchunk = lodepng_chunk_next(inchunk, data + datasize); + } + return 0; +} + +static unsigned isGrayICCProfile(const unsigned char* profile, unsigned size) { + /* + It is a gray profile if bytes 16-19 are "GRAY", rgb profile if bytes 16-19 + are "RGB ". We do not perform any full parsing of the ICC profile here, other + than check those 4 bytes to grayscale profile. Other than that, validity of + the profile is not checked. This is needed only because the PNG specification + requires using a non-gray color model if there is an ICC profile with "RGB " + (sadly limiting compression opportunities if the input data is grayscale RGB + data), and requires using a gray color model if it is "GRAY". + */ + if(size < 20) return 0; + return profile[16] == 'G' && profile[17] == 'R' && profile[18] == 'A' && profile[19] == 'Y'; +} + +static unsigned isRGBICCProfile(const unsigned char* profile, unsigned size) { + /* See comment in isGrayICCProfile*/ + if(size < 20) return 0; + return profile[16] == 'R' && profile[17] == 'G' && profile[18] == 'B' && profile[19] == ' '; +} +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state) { + unsigned char* data = 0; /*uncompressed version of the IDAT chunk data*/ + size_t datasize = 0; + ucvector outv = ucvector_init(NULL, 0); + LodePNGInfo info; + const LodePNGInfo* info_png = &state->info_png; + + lodepng_info_init(&info); + + /*provide some proper output values if error will happen*/ + *out = 0; + *outsize = 0; + state->error = 0; + + /*check input values validity*/ + if((info_png->color.colortype == LCT_PALETTE || state->encoder.force_palette) + && (info_png->color.palettesize == 0 || info_png->color.palettesize > 256)) { + state->error = 68; /*invalid palette size, it is only allowed to be 1-256*/ + goto cleanup; + } + if(state->encoder.zlibsettings.btype > 2) { + state->error = 61; /*error: invalid btype*/ + goto cleanup; + } + if(info_png->interlace_method > 1) { + state->error = 71; /*error: invalid interlace mode*/ + goto cleanup; + } + state->error = checkColorValidity(info_png->color.colortype, info_png->color.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + state->error = checkColorValidity(state->info_raw.colortype, state->info_raw.bitdepth); + if(state->error) goto cleanup; /*error: invalid color type given*/ + + /* color convert and compute scanline filter types */ + lodepng_info_copy(&info, &state->info_png); + if(state->encoder.auto_convert) { + LodePNGColorStats stats; + lodepng_color_stats_init(&stats); +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined && + isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use palette with a GRAY ICC profile, even + if the palette has only gray colors, so disallow it.*/ + stats.allow_palette = 0; + } + if(info_png->iccp_defined && + isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size)) { + /*the PNG specification does not allow to use grayscale color with RGB ICC profile, so disallow gray.*/ + stats.allow_greyscale = 0; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = lodepng_compute_color_stats(&stats, image, w, h, &state->info_raw); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->background_defined) { + /*the background chunk's color must be taken into account as well*/ + unsigned r = 0, g = 0, b = 0; + LodePNGColorMode mode16 = lodepng_color_mode_make(LCT_RGB, 16); + lodepng_convert_rgb(&r, &g, &b, info_png->background_r, info_png->background_g, info_png->background_b, &mode16, &info_png->color); + state->error = lodepng_color_stats_add(&stats, r, g, b, 65535); + if(state->error) goto cleanup; + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + state->error = auto_choose_color(&info.color, &state->info_raw, &stats); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*also convert the background chunk*/ + if(info_png->background_defined) { + if(lodepng_convert_rgb(&info.background_r, &info.background_g, &info.background_b, + info_png->background_r, info_png->background_g, info_png->background_b, &info.color, &info_png->color)) { + state->error = 104; + goto cleanup; + } + } +#endif /* LODEPNG_COMPILE_ANCILLARY_CHUNKS */ + } +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + if(info_png->iccp_defined) { + unsigned gray_icc = isGrayICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned rgb_icc = isRGBICCProfile(info_png->iccp_profile, info_png->iccp_profile_size); + unsigned gray_png = info.color.colortype == LCT_GREY || info.color.colortype == LCT_GREY_ALPHA; + if(!gray_icc && !rgb_icc) { + state->error = 100; /* Disallowed profile color type for PNG */ + goto cleanup; + } + if(gray_icc != gray_png) { + /*Not allowed to use RGB/RGBA/palette with GRAY ICC profile or vice versa, + or in case of auto_convert, it wasn't possible to find appropriate model*/ + state->error = state->encoder.auto_convert ? 102 : 101; + goto cleanup; + } + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + if(!lodepng_color_mode_equal(&state->info_raw, &info.color)) { + unsigned char* converted; + size_t size = ((size_t)w * (size_t)h * (size_t)lodepng_get_bpp(&info.color) + 7u) / 8u; + + converted = (unsigned char*)lodepng_malloc(size); + if(!converted && size) state->error = 83; /*alloc fail*/ + if(!state->error) { + state->error = lodepng_convert(converted, image, &info.color, &state->info_raw, w, h); + } + if(!state->error) { + state->error = preProcessScanlines(&data, &datasize, converted, w, h, &info, &state->encoder); + } + lodepng_free(converted); + if(state->error) goto cleanup; + } else { + state->error = preProcessScanlines(&data, &datasize, image, w, h, &info, &state->encoder); + if(state->error) goto cleanup; + } + + /* output all PNG chunks */ { +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + size_t i; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*write signature and chunks*/ + state->error = writeSignature(&outv); + if(state->error) goto cleanup; + /*IHDR*/ + state->error = addChunk_IHDR(&outv, w, h, info.color.colortype, info.color.bitdepth, info.interlace_method); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*unknown chunks between IHDR and PLTE*/ + if(info.unknown_chunks_data[0]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[0], info.unknown_chunks_size[0]); + if(state->error) goto cleanup; + } + /*color profile chunks must come before PLTE */ + if(info.iccp_defined) { + state->error = addChunk_iCCP(&outv, &info, &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + if(info.srgb_defined) { + state->error = addChunk_sRGB(&outv, &info); + if(state->error) goto cleanup; + } + if(info.gama_defined) { + state->error = addChunk_gAMA(&outv, &info); + if(state->error) goto cleanup; + } + if(info.chrm_defined) { + state->error = addChunk_cHRM(&outv, &info); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*PLTE*/ + if(info.color.colortype == LCT_PALETTE) { + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + if(state->encoder.force_palette && (info.color.colortype == LCT_RGB || info.color.colortype == LCT_RGBA)) { + /*force_palette means: write suggested palette for truecolor in PLTE chunk*/ + state->error = addChunk_PLTE(&outv, &info.color); + if(state->error) goto cleanup; + } + /*tRNS (this will only add if when necessary) */ + state->error = addChunk_tRNS(&outv, &info.color); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*bKGD (must come between PLTE and the IDAt chunks*/ + if(info.background_defined) { + state->error = addChunk_bKGD(&outv, &info); + if(state->error) goto cleanup; + } + /*pHYs (must come before the IDAT chunks)*/ + if(info.phys_defined) { + state->error = addChunk_pHYs(&outv, &info); + if(state->error) goto cleanup; + } + + /*unknown chunks between PLTE and IDAT*/ + if(info.unknown_chunks_data[1]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[1], info.unknown_chunks_size[1]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + /*IDAT (multiple IDAT chunks must be consecutive)*/ + state->error = addChunk_IDAT(&outv, data, datasize, &state->encoder.zlibsettings); + if(state->error) goto cleanup; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*tIME*/ + if(info.time_defined) { + state->error = addChunk_tIME(&outv, &info.time); + if(state->error) goto cleanup; + } + /*tEXt and/or zTXt*/ + for(i = 0; i != info.text_num; ++i) { + if(lodepng_strlen(info.text_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.text_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + if(state->encoder.text_compression) { + state->error = addChunk_zTXt(&outv, info.text_keys[i], info.text_strings[i], &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } else { + state->error = addChunk_tEXt(&outv, info.text_keys[i], info.text_strings[i]); + if(state->error) goto cleanup; + } + } + /*LodePNG version id in text chunk*/ + if(state->encoder.add_id) { + unsigned already_added_id_text = 0; + for(i = 0; i != info.text_num; ++i) { + const char* k = info.text_keys[i]; + /* Could use strcmp, but we're not calling or reimplementing this C library function for this use only */ + if(k[0] == 'L' && k[1] == 'o' && k[2] == 'd' && k[3] == 'e' && + k[4] == 'P' && k[5] == 'N' && k[6] == 'G' && k[7] == '\0') { + already_added_id_text = 1; + break; + } + } + if(already_added_id_text == 0) { + state->error = addChunk_tEXt(&outv, "LodePNG", LODEPNG_VERSION_STRING); /*it's shorter as tEXt than as zTXt chunk*/ + if(state->error) goto cleanup; + } + } + /*iTXt*/ + for(i = 0; i != info.itext_num; ++i) { + if(lodepng_strlen(info.itext_keys[i]) > 79) { + state->error = 66; /*text chunk too large*/ + goto cleanup; + } + if(lodepng_strlen(info.itext_keys[i]) < 1) { + state->error = 67; /*text chunk too small*/ + goto cleanup; + } + state->error = addChunk_iTXt( + &outv, state->encoder.text_compression, + info.itext_keys[i], info.itext_langtags[i], info.itext_transkeys[i], info.itext_strings[i], + &state->encoder.zlibsettings); + if(state->error) goto cleanup; + } + + /*unknown chunks between IDAT and IEND*/ + if(info.unknown_chunks_data[2]) { + state->error = addUnknownChunks(&outv, info.unknown_chunks_data[2], info.unknown_chunks_size[2]); + if(state->error) goto cleanup; + } +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + state->error = addChunk_IEND(&outv); + if(state->error) goto cleanup; + } + +cleanup: + lodepng_info_cleanup(&info); + lodepng_free(data); + + /*instead of cleaning the vector up, give it to the output*/ + *out = outv.data; + *outsize = outv.size; + + return state->error; +} + +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, const unsigned char* image, + unsigned w, unsigned h, LodePNGColorType colortype, unsigned bitdepth) { + unsigned error; + LodePNGState state; + lodepng_state_init(&state); + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + state.info_png.color.colortype = colortype; + state.info_png.color.bitdepth = bitdepth; + lodepng_encode(out, outsize, image, w, h, &state); + error = state.error; + lodepng_state_cleanup(&state); + return error; +} + +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_memory(out, outsize, image, w, h, LCT_RGB, 8); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned lodepng_encode_file(const char* filename, const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, image, w, h, colortype, bitdepth); + if(!error) error = lodepng_save_file(buffer, buffersize, filename); + lodepng_free(buffer); + return error; +} + +unsigned lodepng_encode32_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGBA, 8); +} + +unsigned lodepng_encode24_file(const char* filename, const unsigned char* image, unsigned w, unsigned h) { + return lodepng_encode_file(filename, image, w, h, LCT_RGB, 8); +} +#endif /*LODEPNG_COMPILE_DISK*/ + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings) { + lodepng_compress_settings_init(&settings->zlibsettings); + settings->filter_palette_zero = 1; + settings->filter_strategy = LFS_MINSUM; + settings->auto_convert = 1; + settings->force_palette = 0; + settings->predefined_filters = 0; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + settings->add_id = 0; + settings->text_compression = 1; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/* +This returns the description of a numerical error code in English. This is also +the documentation of all the error codes. +*/ +const char* lodepng_error_text(unsigned code) { + switch(code) { + case 0: return "no error, everything went ok"; + case 1: return "nothing done yet"; /*the Encoder/Decoder has done nothing yet, error checking makes no sense yet*/ + case 10: return "end of input memory reached without huffman end code"; /*while huffman decoding*/ + case 11: return "error in code tree made it jump outside of huffman tree"; /*while huffman decoding*/ + case 13: return "problem while processing dynamic deflate block"; + case 14: return "problem while processing dynamic deflate block"; + case 15: return "problem while processing dynamic deflate block"; + /*this error could happen if there are only 0 or 1 symbols present in the huffman code:*/ + case 16: return "invalid code while processing dynamic deflate block"; + case 17: return "end of out buffer memory reached while inflating"; + case 18: return "invalid distance code while inflating"; + case 19: return "end of out buffer memory reached while inflating"; + case 20: return "invalid deflate block BTYPE encountered while decoding"; + case 21: return "NLEN is not ones complement of LEN in a deflate block"; + + /*end of out buffer memory reached while inflating: + This can happen if the inflated deflate data is longer than the amount of bytes required to fill up + all the pixels of the image, given the color depth and image dimensions. Something that doesn't + happen in a normal, well encoded, PNG image.*/ + case 22: return "end of out buffer memory reached while inflating"; + case 23: return "end of in buffer memory reached while inflating"; + case 24: return "invalid FCHECK in zlib header"; + case 25: return "invalid compression method in zlib header"; + case 26: return "FDICT encountered in zlib header while it's not used for PNG"; + case 27: return "PNG file is smaller than a PNG header"; + /*Checks the magic file header, the first 8 bytes of the PNG file*/ + case 28: return "incorrect PNG signature, it's no PNG or corrupted"; + case 29: return "first chunk is not the header chunk"; + case 30: return "chunk length too large, chunk broken off at end of file"; + case 31: return "illegal PNG color type or bpp"; + case 32: return "illegal PNG compression method"; + case 33: return "illegal PNG filter method"; + case 34: return "illegal PNG interlace method"; + case 35: return "chunk length of a chunk is too large or the chunk too small"; + case 36: return "illegal PNG filter type encountered"; + case 37: return "illegal bit depth for this color type given"; + case 38: return "the palette is too small or too big"; /*0, or more than 256 colors*/ + case 39: return "tRNS chunk before PLTE or has more entries than palette size"; + case 40: return "tRNS chunk has wrong size for grayscale image"; + case 41: return "tRNS chunk has wrong size for RGB image"; + case 42: return "tRNS chunk appeared while it was not allowed for this color type"; + case 43: return "bKGD chunk has wrong size for palette image"; + case 44: return "bKGD chunk has wrong size for grayscale image"; + case 45: return "bKGD chunk has wrong size for RGB image"; + case 48: return "empty input buffer given to decoder. Maybe caused by non-existing file?"; + case 49: return "jumped past memory while generating dynamic huffman tree"; + case 50: return "jumped past memory while generating dynamic huffman tree"; + case 51: return "jumped past memory while inflating huffman block"; + case 52: return "jumped past memory while inflating"; + case 53: return "size of zlib data too small"; + case 54: return "repeat symbol in tree while there was no value symbol yet"; + /*jumped past tree while generating huffman tree, this could be when the + tree will have more leaves than symbols after generating it out of the + given lengths. They call this an oversubscribed dynamic bit lengths tree in zlib.*/ + case 55: return "jumped past tree while generating huffman tree"; + case 56: return "given output image colortype or bitdepth not supported for color conversion"; + case 57: return "invalid CRC encountered (checking CRC can be disabled)"; + case 58: return "invalid ADLER32 encountered (checking ADLER32 can be disabled)"; + case 59: return "requested color conversion not supported"; + case 60: return "invalid window size given in the settings of the encoder (must be 0-32768)"; + case 61: return "invalid BTYPE given in the settings of the encoder (only 0, 1 and 2 are allowed)"; + /*LodePNG leaves the choice of RGB to grayscale conversion formula to the user.*/ + case 62: return "conversion from color to grayscale not supported"; + /*(2^31-1)*/ + case 63: return "length of a chunk too long, max allowed for PNG is 2147483647 bytes per chunk"; + /*this would result in the inability of a deflated block to ever contain an end code. It must be at least 1.*/ + case 64: return "the length of the END symbol 256 in the Huffman tree is 0"; + case 66: return "the length of a text chunk keyword given to the encoder is longer than the maximum of 79 bytes"; + case 67: return "the length of a text chunk keyword given to the encoder is smaller than the minimum of 1 byte"; + case 68: return "tried to encode a PLTE chunk with a palette that has less than 1 or more than 256 colors"; + case 69: return "unknown chunk type with 'critical' flag encountered by the decoder"; + case 71: return "invalid interlace mode given to encoder (must be 0 or 1)"; + case 72: return "while decoding, invalid compression method encountering in zTXt or iTXt chunk (it must be 0)"; + case 73: return "invalid tIME chunk size"; + case 74: return "invalid pHYs chunk size"; + /*length could be wrong, or data chopped off*/ + case 75: return "no null termination char found while decoding text chunk"; + case 76: return "iTXt chunk too short to contain required bytes"; + case 77: return "integer overflow in buffer size"; + case 78: return "failed to open file for reading"; /*file doesn't exist or couldn't be opened for reading*/ + case 79: return "failed to open file for writing"; + case 80: return "tried creating a tree of 0 symbols"; + case 81: return "lazy matching at pos 0 is impossible"; + case 82: return "color conversion to palette requested while a color isn't in palette, or index out of bounds"; + case 83: return "memory allocation failed"; + case 84: return "given image too small to contain all pixels to be encoded"; + case 86: return "impossible offset in lz77 encoding (internal bug)"; + case 87: return "must provide custom zlib function pointer if LODEPNG_COMPILE_ZLIB is not defined"; + case 88: return "invalid filter strategy given for LodePNGEncoderSettings.filter_strategy"; + case 89: return "text chunk keyword too short or long: must have size 1-79"; + /*the windowsize in the LodePNGCompressSettings. Requiring POT(==> & instead of %) makes encoding 12% faster.*/ + case 90: return "windowsize must be a power of two"; + case 91: return "invalid decompressed idat size"; + case 92: return "integer overflow due to too many pixels"; + case 93: return "zero width or height is invalid"; + case 94: return "header chunk must have a size of 13 bytes"; + case 95: return "integer overflow with combined idat chunk size"; + case 96: return "invalid gAMA chunk size"; + case 97: return "invalid cHRM chunk size"; + case 98: return "invalid sRGB chunk size"; + case 99: return "invalid sRGB rendering intent"; + case 100: return "invalid ICC profile color type, the PNG specification only allows RGB or GRAY"; + case 101: return "PNG specification does not allow RGB ICC profile on gray color types and vice versa"; + case 102: return "not allowed to set grayscale ICC profile with colored pixels by PNG specification"; + case 103: return "invalid palette index in bKGD chunk. Maybe it came before PLTE chunk?"; + case 104: return "invalid bKGD color while encoding (e.g. palette index out of range)"; + case 105: return "integer overflow of bitsize"; + case 106: return "PNG file must have PLTE chunk if color type is palette"; + case 107: return "color convert from palette mode requested without setting the palette data in it"; + case 108: return "tried to add more than 256 values to a palette"; + /*this limit can be configured in LodePNGDecompressSettings*/ + case 109: return "tried to decompress zlib or deflate data larger than desired max_output_size"; + case 110: return "custom zlib or inflate decompression failed"; + case 111: return "custom zlib or deflate compression failed"; + /*max text size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large text sizes.*/ + case 112: return "compressed text unreasonably large"; + /*max ICC size limit can be configured in LodePNGDecoderSettings. This error prevents + unreasonable memory consumption when decoding due to impossibly large ICC profile*/ + case 113: return "ICC profile unreasonably large"; + } + return "unknown error code"; +} +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* // C++ Wrapper // */ +/* ////////////////////////////////////////////////////////////////////////// */ +/* ////////////////////////////////////////////////////////////////////////// */ + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { + +#ifdef LODEPNG_COMPILE_DISK +unsigned load_file(std::vector<unsigned char>& buffer, const std::string& filename) { + long size = lodepng_filesize(filename.c_str()); + if(size < 0) return 78; + buffer.resize((size_t)size); + return size == 0 ? 0 : lodepng_buffer_file(&buffer[0], (size_t)size, filename.c_str()); +} + +/*write given buffer to the file, overwriting the file, it doesn't append to it.*/ +unsigned save_file(const std::vector<unsigned char>& buffer, const std::string& filename) { + return lodepng_save_file(buffer.empty() ? 0 : &buffer[0], buffer.size(), filename.c_str()); +} +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +unsigned decompress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_decompress(&buffer, &buffersize, 0, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned decompress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in, + const LodePNGDecompressSettings& settings) { + return decompress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned compress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings) { + unsigned char* buffer = 0; + size_t buffersize = 0; + unsigned error = zlib_compress(&buffer, &buffersize, in, insize, &settings); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned compress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in, + const LodePNGCompressSettings& settings) { + return compress(out, in.empty() ? 0 : &in[0], in.size(), settings); +} +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ + + +#ifdef LODEPNG_COMPILE_PNG + +State::State() { + lodepng_state_init(this); +} + +State::State(const State& other) { + lodepng_state_init(this); + lodepng_state_copy(this, &other); +} + +State::~State() { + lodepng_state_cleanup(this); +} + +State& State::operator=(const State& other) { + lodepng_state_copy(this, &other); + return *this; +} + +#ifdef LODEPNG_COMPILE_DECODER + +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, const unsigned char* in, + size_t insize, LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer = 0; + unsigned error = lodepng_decode_memory(&buffer, &w, &h, in, insize, colortype, bitdepth); + if(buffer && !error) { + State state; + state.info_raw.colortype = colortype; + state.info_raw.bitdepth = bitdepth; + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + const std::vector<unsigned char>& in, LodePNGColorType colortype, unsigned bitdepth) { + return decode(out, w, h, in.empty() ? 0 : &in[0], (unsigned)in.size(), colortype, bitdepth); +} + +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize) { + unsigned char* buffer = NULL; + unsigned error = lodepng_decode(&buffer, &w, &h, &state, in, insize); + if(buffer && !error) { + size_t buffersize = lodepng_get_raw_size(w, h, &state.info_raw); + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + } + lodepng_free(buffer); + return error; +} + +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + State& state, + const std::vector<unsigned char>& in) { + return decode(out, w, h, state, in.empty() ? 0 : &in[0], in.size()); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, const std::string& filename, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector<unsigned char> buffer; + /* safe output values in case error happens */ + w = h = 0; + unsigned error = load_file(buffer, filename); + if(error) return error; + return decode(out, w, h, buffer, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DECODER */ +#endif /* LODEPNG_COMPILE_DISK */ + +#ifdef LODEPNG_COMPILE_ENCODER +unsigned encode(std::vector<unsigned char>& out, const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode_memory(&buffer, &buffersize, in, w, h, colortype, bitdepth); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector<unsigned char>& out, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} + +unsigned encode(std::vector<unsigned char>& out, + const unsigned char* in, unsigned w, unsigned h, + State& state) { + unsigned char* buffer; + size_t buffersize; + unsigned error = lodepng_encode(&buffer, &buffersize, in, w, h, &state); + if(buffer) { + out.insert(out.end(), &buffer[0], &buffer[buffersize]); + lodepng_free(buffer); + } + return error; +} + +unsigned encode(std::vector<unsigned char>& out, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + State& state) { + if(lodepng_get_raw_size(w, h, &state.info_raw) > in.size()) return 84; + return encode(out, in.empty() ? 0 : &in[0], w, h, state); +} + +#ifdef LODEPNG_COMPILE_DISK +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + std::vector<unsigned char> buffer; + unsigned error = encode(buffer, in, w, h, colortype, bitdepth); + if(!error) error = save_file(buffer, filename); + return error; +} + +unsigned encode(const std::string& filename, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth) { + if(lodepng_get_raw_size_lct(w, h, colortype, bitdepth) > in.size()) return 84; + return encode(filename, in.empty() ? 0 : &in[0], w, h, colortype, bitdepth); +} +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_PNG */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +#endif /*LV_USE_PNG*/ diff --git a/lib/lvgl/src/extra/libs/png/lodepng.h b/lib/lvgl/src/extra/libs/png/lodepng.h new file mode 100644 index 00000000..dbfed72e --- /dev/null +++ b/lib/lvgl/src/extra/libs/png/lodepng.h @@ -0,0 +1,1981 @@ +/* +LodePNG version 20201017 + +Copyright (c) 2005-2020 Lode Vandevenne + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef LODEPNG_H +#define LODEPNG_H + +#include <string.h> /*for size_t*/ + +#include "../../../lvgl.h" +#if LV_USE_PNG +extern const char* LODEPNG_VERSION_STRING; + +/* +The following #defines are used to create code sections. They can be disabled +to disable code sections, which can give faster compile time and smaller binary. +The "NO_COMPILE" defines are designed to be used to pass as defines to the +compiler command to disable them without modifying this header, e.g. +-DLODEPNG_NO_COMPILE_ZLIB for gcc. +In addition to those below, you can also define LODEPNG_NO_COMPILE_CRC to +allow implementing a custom lodepng_crc32. +*/ +/*deflate & zlib. If disabled, you must specify alternative zlib functions in +the custom_zlib field of the compress and decompress settings*/ +#ifndef LODEPNG_NO_COMPILE_ZLIB +#define LODEPNG_COMPILE_ZLIB +#endif + +/*png encoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_PNG +#define LODEPNG_COMPILE_PNG +#endif + +/*deflate&zlib decoder and png decoder*/ +#ifndef LODEPNG_NO_COMPILE_DECODER +#define LODEPNG_COMPILE_DECODER +#endif + +/*deflate&zlib encoder and png encoder*/ +#ifndef LODEPNG_NO_COMPILE_ENCODER +#define LODEPNG_COMPILE_ENCODER +#endif + +/*the optional built in harddisk file loading and saving functions*/ +#ifndef LODEPNG_NO_COMPILE_DISK +#define LODEPNG_COMPILE_DISK +#endif + +/*support for chunks other than IHDR, IDAT, PLTE, tRNS, IEND: ancillary and unknown chunks*/ +#ifndef LODEPNG_NO_COMPILE_ANCILLARY_CHUNKS +#define LODEPNG_COMPILE_ANCILLARY_CHUNKS +#endif + +/*ability to convert error numerical codes to English text string*/ +#ifndef LODEPNG_NO_COMPILE_ERROR_TEXT +#define LODEPNG_COMPILE_ERROR_TEXT +#endif + +/*Compile the default allocators (C's free, malloc and realloc). If you disable this, +you can define the functions lodepng_free, lodepng_malloc and lodepng_realloc in your +source files with custom allocators.*/ +#ifndef LODEPNG_NO_COMPILE_ALLOCATORS +#define LODEPNG_COMPILE_ALLOCATORS +#endif + +/*compile the C++ version (you can disable the C++ wrapper here even when compiling for C++)*/ +#ifdef __cplusplus +#ifndef LODEPNG_NO_COMPILE_CPP +#define LODEPNG_COMPILE_CPP +#endif +#endif + +#ifdef LODEPNG_COMPILE_CPP +#include <vector> +#include <string> +#endif /*LODEPNG_COMPILE_CPP*/ + +#ifdef LODEPNG_COMPILE_PNG +/*The PNG color types (also used for raw image).*/ +typedef enum LodePNGColorType { + LCT_GREY = 0, /*grayscale: 1,2,4,8,16 bit*/ + LCT_RGB = 2, /*RGB: 8,16 bit*/ + LCT_PALETTE = 3, /*palette: 1,2,4,8 bit*/ + LCT_GREY_ALPHA = 4, /*grayscale with alpha: 8,16 bit*/ + LCT_RGBA = 6, /*RGB with alpha: 8,16 bit*/ + /*LCT_MAX_OCTET_VALUE lets the compiler allow this enum to represent any invalid + byte value from 0 to 255 that could be present in an invalid PNG file header. Do + not use, compare with or set the name LCT_MAX_OCTET_VALUE, instead either use + the valid color type names above, or numeric values like 1 or 7 when checking for + particular disallowed color type byte values, or cast to integer to print it.*/ + LCT_MAX_OCTET_VALUE = 255 +} LodePNGColorType; + +#ifdef LODEPNG_COMPILE_DECODER +/* +Converts PNG data in memory to raw pixel data. +out: Output parameter. Pointer to buffer that will contain the raw pixel data. + After decoding, its size is w * h * (bytes per pixel) bytes larger than + initially. Bytes per pixel depends on colortype and bitdepth. + Must be freed after usage with free(*out). + Note: for 16-bit per channel colors, uses big endian format like PNG does. +w: Output parameter. Pointer to width of pixel data. +h: Output parameter. Pointer to height of pixel data. +in: Memory buffer with the PNG file. +insize: size of the in buffer. +colortype: the desired color type for the raw output image. See explanation on PNG color types. +bitdepth: the desired bit depth for the raw output image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_decode_memory(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_memory, but always decodes to 32-bit RGBA raw image*/ +unsigned lodepng_decode32(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +/*Same as lodepng_decode_memory, but always decodes to 24-bit RGB raw image*/ +unsigned lodepng_decode24(unsigned char** out, unsigned* w, unsigned* h, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_DISK +/* +Load PNG from disk, from file with given name. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned lodepng_decode_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_decode_file, but always decodes to 32-bit RGBA raw image.*/ +unsigned lodepng_decode32_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); + +/*Same as lodepng_decode_file, but always decodes to 24-bit RGB raw image.*/ +unsigned lodepng_decode24_file(unsigned char** out, unsigned* w, unsigned* h, + const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_DECODER*/ + + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Converts raw pixel data into a PNG image in memory. The colortype and bitdepth + of the output PNG image cannot be chosen, they are automatically determined + by the colortype, bitdepth and content of the input pixel data. + Note: for 16-bit per channel colors, needs big endian format like PNG does. +out: Output parameter. Pointer to buffer that will contain the PNG image data. + Must be freed after usage with free(*out). +outsize: Output parameter. Pointer to the size in bytes of the out buffer. +image: The raw pixel data to encode. The size of this buffer should be + w * h * (bytes per pixel), bytes per pixel depends on colortype and bitdepth. +w: width of the raw pixel data in pixels. +h: height of the raw pixel data in pixels. +colortype: the color type of the raw input image. See explanation on PNG color types. +bitdepth: the bit depth of the raw input image. See explanation on PNG color types. +Return value: LodePNG error code (0 means no error). +*/ +unsigned lodepng_encode_memory(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_memory, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_memory, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DISK +/* +Converts raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned lodepng_encode_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h, + LodePNGColorType colortype, unsigned bitdepth); + +/*Same as lodepng_encode_file, but always encodes from 32-bit RGBA raw image.*/ +unsigned lodepng_encode32_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); + +/*Same as lodepng_encode_file, but always encodes from 24-bit RGB raw image.*/ +unsigned lodepng_encode24_file(const char* filename, + const unsigned char* image, unsigned w, unsigned h); +#endif /*LODEPNG_COMPILE_DISK*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#ifdef LODEPNG_COMPILE_CPP +namespace lodepng { +#ifdef LODEPNG_COMPILE_DECODER +/*Same as lodepng_decode_memory, but decodes to an std::vector. The colortype +is the format to output the pixels to. Default is RGBA 8-bit per channel.*/ +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + const unsigned char* in, size_t insize, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + const std::vector<unsigned char>& in, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts PNG file from disk to raw pixel data in memory. +Same as the other decode functions, but instead takes a filename as input. +*/ +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + const std::string& filename, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/*Same as lodepng_encode_memory, but encodes to an std::vector. colortype +is that of the raw input data. The output PNG color type will be auto chosen.*/ +unsigned encode(std::vector<unsigned char>& out, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(std::vector<unsigned char>& out, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#ifdef LODEPNG_COMPILE_DISK +/* +Converts 32-bit RGBA raw pixel data into a PNG file on disk. +Same as the other encode functions, but instead takes a filename as output. +NOTE: This overwrites existing files without warning! +*/ +unsigned encode(const std::string& filename, + const unsigned char* in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +unsigned encode(const std::string& filename, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + LodePNGColorType colortype = LCT_RGBA, unsigned bitdepth = 8); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_ENCODER */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ +#endif /*LODEPNG_COMPILE_PNG*/ + +#ifdef LODEPNG_COMPILE_ERROR_TEXT +/*Returns an English description of the numerical error code.*/ +const char* lodepng_error_text(unsigned code); +#endif /*LODEPNG_COMPILE_ERROR_TEXT*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Settings for zlib decompression*/ +typedef struct LodePNGDecompressSettings LodePNGDecompressSettings; +struct LodePNGDecompressSettings { + /* Check LodePNGDecoderSettings for more ignorable errors such as ignore_crc */ + unsigned ignore_adler32; /*if 1, continue and don't give an error message if the Adler32 checksum is corrupted*/ + unsigned ignore_nlen; /*ignore complement of len checksum in uncompressed blocks*/ + + /*Maximum decompressed size, beyond this the decoder may (and is encouraged to) stop decoding, + return an error, output a data size > max_output_size and all the data up to that point. This is + not hard limit nor a guarantee, but can prevent excessive memory usage. This setting is + ignored by the PNG decoder, but is used by the deflate/zlib decoder and can be used by custom ones. + Set to 0 to impose no limit (the default).*/ + size_t max_output_size; + + /*use custom zlib decoder instead of built in one (default: null). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + /*use custom deflate decoder instead of built in one (default: null) + if custom_zlib is not null, custom_inflate is ignored (the zlib format uses deflate). + Should return 0 if success, any non-0 if error (numeric value not exposed).*/ + unsigned (*custom_inflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGDecompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGDecompressSettings lodepng_default_decompress_settings; +void lodepng_decompress_settings_init(LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Settings for zlib compression. Tweaking these settings tweaks the balance +between speed and compression ratio. +*/ +typedef struct LodePNGCompressSettings LodePNGCompressSettings; +struct LodePNGCompressSettings /*deflate = compress*/ { + /*LZ77 related settings*/ + unsigned btype; /*the block type for LZ (0, 1, 2 or 3, see zlib standard). Should be 2 for proper compression.*/ + unsigned use_lz77; /*whether or not to use LZ77. Should be 1 for proper compression.*/ + unsigned windowsize; /*must be a power of two <= 32768. higher compresses more but is slower. Default value: 2048.*/ + unsigned minmatch; /*minimum lz77 length. 3 is normally best, 6 can be better for some PNGs. Default: 0*/ + unsigned nicematch; /*stop searching if >= this length found. Set to 258 for best compression. Default: 128*/ + unsigned lazymatching; /*use lazy matching: better compression but a bit slower. Default: true*/ + + /*use custom zlib encoder instead of built in one (default: null)*/ + unsigned (*custom_zlib)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + /*use custom deflate encoder instead of built in one (default: null) + if custom_zlib is used, custom_deflate is ignored since only the built in + zlib function will call custom_deflate*/ + unsigned (*custom_deflate)(unsigned char**, size_t*, + const unsigned char*, size_t, + const LodePNGCompressSettings*); + + const void* custom_context; /*optional custom settings for custom functions*/ +}; + +extern const LodePNGCompressSettings lodepng_default_compress_settings; +void lodepng_compress_settings_init(LodePNGCompressSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_PNG +/* +Color mode of an image. Contains all information required to decode the pixel +bits to RGBA colors. This information is the same as used in the PNG file +format, and is used both for PNG and raw image data in LodePNG. +*/ +typedef struct LodePNGColorMode { + /*header (IHDR)*/ + LodePNGColorType colortype; /*color type, see PNG standard or documentation further in this header file*/ + unsigned bitdepth; /*bits per sample, see PNG standard or documentation further in this header file*/ + + /* + palette (PLTE and tRNS) + + Dynamically allocated with the colors of the palette, including alpha. + This field may not be allocated directly, use lodepng_color_mode_init first, + then lodepng_palette_add per color to correctly initialize it (to ensure size + of exactly 1024 bytes). + + The alpha channels must be set as well, set them to 255 for opaque images. + + When decoding, by default you can ignore this palette, since LodePNG already + fills the palette colors in the pixels of the raw RGBA output. + + The palette is only supported for color type 3. + */ + unsigned char* palette; /*palette in RGBARGBA... order. Must be either 0, or when allocated must have 1024 bytes*/ + size_t palettesize; /*palette size in number of colors (amount of used bytes is 4 * palettesize)*/ + + /* + transparent color key (tRNS) + + This color uses the same bit depth as the bitdepth value in this struct, which can be 1-bit to 16-bit. + For grayscale PNGs, r, g and b will all 3 be set to the same. + + When decoding, by default you can ignore this information, since LodePNG sets + pixels with this key to transparent already in the raw RGBA output. + + The color key is only supported for color types 0 and 2. + */ + unsigned key_defined; /*is a transparent color key given? 0 = false, 1 = true*/ + unsigned key_r; /*red/grayscale component of color key*/ + unsigned key_g; /*green component of color key*/ + unsigned key_b; /*blue component of color key*/ +} LodePNGColorMode; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_color_mode_init(LodePNGColorMode* info); +void lodepng_color_mode_cleanup(LodePNGColorMode* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_color_mode_copy(LodePNGColorMode* dest, const LodePNGColorMode* source); +/* Makes a temporary LodePNGColorMode that does not need cleanup (no palette) */ +LodePNGColorMode lodepng_color_mode_make(LodePNGColorType colortype, unsigned bitdepth); + +void lodepng_palette_clear(LodePNGColorMode* info); +/*add 1 color to the palette*/ +unsigned lodepng_palette_add(LodePNGColorMode* info, + unsigned char r, unsigned char g, unsigned char b, unsigned char a); + +/*get the total amount of bits per pixel, based on colortype and bitdepth in the struct*/ +unsigned lodepng_get_bpp(const LodePNGColorMode* info); +/*get the amount of color channels used, based on colortype in the struct. +If a palette is used, it counts as 1 channel.*/ +unsigned lodepng_get_channels(const LodePNGColorMode* info); +/*is it a grayscale type? (only colortype 0 or 4)*/ +unsigned lodepng_is_greyscale_type(const LodePNGColorMode* info); +/*has it got an alpha channel? (only colortype 2 or 6)*/ +unsigned lodepng_is_alpha_type(const LodePNGColorMode* info); +/*has it got a palette? (only colortype 3)*/ +unsigned lodepng_is_palette_type(const LodePNGColorMode* info); +/*only returns true if there is a palette and there is a value in the palette with alpha < 255. +Loops through the palette to check this.*/ +unsigned lodepng_has_palette_alpha(const LodePNGColorMode* info); +/* +Check if the given color info indicates the possibility of having non-opaque pixels in the PNG image. +Returns true if the image can have translucent or invisible pixels (it still be opaque if it doesn't use such pixels). +Returns false if the image can only have opaque pixels. +In detail, it returns true only if it's a color type with alpha, or has a palette with non-opaque values, +or if "key_defined" is true. +*/ +unsigned lodepng_can_have_alpha(const LodePNGColorMode* info); +/*Returns the byte size of a raw image buffer with given width, height and color mode*/ +size_t lodepng_get_raw_size(unsigned w, unsigned h, const LodePNGColorMode* color); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +/*The information of a Time chunk in PNG.*/ +typedef struct LodePNGTime { + unsigned year; /*2 bytes used (0-65535)*/ + unsigned month; /*1-12*/ + unsigned day; /*1-31*/ + unsigned hour; /*0-23*/ + unsigned minute; /*0-59*/ + unsigned second; /*0-60 (to allow for leap seconds)*/ +} LodePNGTime; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/*Information about the PNG image, except pixels, width and height.*/ +typedef struct LodePNGInfo { + /*header (IHDR), palette (PLTE) and transparency (tRNS) chunks*/ + unsigned compression_method;/*compression method of the original file. Always 0.*/ + unsigned filter_method; /*filter method of the original file*/ + unsigned interlace_method; /*interlace method of the original file: 0=none, 1=Adam7*/ + LodePNGColorMode color; /*color type and bits, palette and transparency of the PNG file*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /* + Suggested background color chunk (bKGD) + + This uses the same color mode and bit depth as the PNG (except no alpha channel), + with values truncated to the bit depth in the unsigned integer. + + For grayscale and palette PNGs, the value is stored in background_r. The values + in background_g and background_b are then unused. + + So when decoding, you may get these in a different color mode than the one you requested + for the raw pixels. + + When encoding with auto_convert, you must use the color model defined in info_png.color for + these values. The encoder normally ignores info_png.color when auto_convert is on, but will + use it to interpret these values (and convert copies of them to its chosen color model). + + When encoding, avoid setting this to an expensive color, such as a non-gray value + when the image is gray, or the compression will be worse since it will be forced to + write the PNG with a more expensive color mode (when auto_convert is on). + + The decoder does not use this background color to edit the color of pixels. This is a + completely optional metadata feature. + */ + unsigned background_defined; /*is a suggested background color given?*/ + unsigned background_r; /*red/gray/palette component of suggested background color*/ + unsigned background_g; /*green component of suggested background color*/ + unsigned background_b; /*blue component of suggested background color*/ + + /* + Non-international text chunks (tEXt and zTXt) + + The char** arrays each contain num strings. The actual messages are in + text_strings, while text_keys are keywords that give a short description what + the actual text represents, e.g. Title, Author, Description, or anything else. + + All the string fields below including strings, keys, names and language tags are null terminated. + The PNG specification uses null characters for the keys, names and tags, and forbids null + characters to appear in the main text which is why we can use null termination everywhere here. + + A keyword is minimum 1 character and maximum 79 characters long (plus the + additional null terminator). It's discouraged to use a single line length + longer than 79 characters for texts. + + Don't allocate these text buffers yourself. Use the init/cleanup functions + correctly and use lodepng_add_text and lodepng_clear_text. + + Standard text chunk keywords and strings are encoded using Latin-1. + */ + size_t text_num; /*the amount of texts in these char** buffers (there may be more texts in itext)*/ + char** text_keys; /*the keyword of a text chunk (e.g. "Comment")*/ + char** text_strings; /*the actual text*/ + + /* + International text chunks (iTXt) + Similar to the non-international text chunks, but with additional strings + "langtags" and "transkeys", and the following text encodings are used: + keys: Latin-1, langtags: ASCII, transkeys and strings: UTF-8. + keys must be 1-79 characters (plus the additional null terminator), the other + strings are any length. + */ + size_t itext_num; /*the amount of international texts in this PNG*/ + char** itext_keys; /*the English keyword of the text chunk (e.g. "Comment")*/ + char** itext_langtags; /*language tag for this text's language, ISO/IEC 646 string, e.g. ISO 639 language tag*/ + char** itext_transkeys; /*keyword translated to the international language - UTF-8 string*/ + char** itext_strings; /*the actual international text - UTF-8 string*/ + + /*time chunk (tIME)*/ + unsigned time_defined; /*set to 1 to make the encoder generate a tIME chunk*/ + LodePNGTime time; + + /*phys chunk (pHYs)*/ + unsigned phys_defined; /*if 0, there is no pHYs chunk and the values below are undefined, if 1 else there is one*/ + unsigned phys_x; /*pixels per unit in x direction*/ + unsigned phys_y; /*pixels per unit in y direction*/ + unsigned phys_unit; /*may be 0 (unknown unit) or 1 (metre)*/ + + /* + Color profile related chunks: gAMA, cHRM, sRGB, iCPP + + LodePNG does not apply any color conversions on pixels in the encoder or decoder and does not interpret these color + profile values. It merely passes on the information. If you wish to use color profiles and convert colors, please + use these values with a color management library. + + See the PNG, ICC and sRGB specifications for more information about the meaning of these values. + */ + + /* gAMA chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned gama_defined; /* Whether a gAMA chunk is present (0 = not present, 1 = present). */ + unsigned gama_gamma; /* Gamma exponent times 100000 */ + + /* cHRM chunk: optional, overridden by sRGB or iCCP if those are present. */ + unsigned chrm_defined; /* Whether a cHRM chunk is present (0 = not present, 1 = present). */ + unsigned chrm_white_x; /* White Point x times 100000 */ + unsigned chrm_white_y; /* White Point y times 100000 */ + unsigned chrm_red_x; /* Red x times 100000 */ + unsigned chrm_red_y; /* Red y times 100000 */ + unsigned chrm_green_x; /* Green x times 100000 */ + unsigned chrm_green_y; /* Green y times 100000 */ + unsigned chrm_blue_x; /* Blue x times 100000 */ + unsigned chrm_blue_y; /* Blue y times 100000 */ + + /* + sRGB chunk: optional. May not appear at the same time as iCCP. + If gAMA is also present gAMA must contain value 45455. + If cHRM is also present cHRM must contain respectively 31270,32900,64000,33000,30000,60000,15000,6000. + */ + unsigned srgb_defined; /* Whether an sRGB chunk is present (0 = not present, 1 = present). */ + unsigned srgb_intent; /* Rendering intent: 0=perceptual, 1=rel. colorimetric, 2=saturation, 3=abs. colorimetric */ + + /* + iCCP chunk: optional. May not appear at the same time as sRGB. + + LodePNG does not parse or use the ICC profile (except its color space header field for an edge case), a + separate library to handle the ICC data (not included in LodePNG) format is needed to use it for color + management and conversions. + + For encoding, if iCCP is present, gAMA and cHRM are recommended to be added as well with values that match the ICC + profile as closely as possible, if you wish to do this you should provide the correct values for gAMA and cHRM and + enable their '_defined' flags since LodePNG will not automatically compute them from the ICC profile. + + For encoding, the ICC profile is required by the PNG specification to be an "RGB" profile for non-gray + PNG color types and a "GRAY" profile for gray PNG color types. If you disable auto_convert, you must ensure + the ICC profile type matches your requested color type, else the encoder gives an error. If auto_convert is + enabled (the default), and the ICC profile is not a good match for the pixel data, this will result in an encoder + error if the pixel data has non-gray pixels for a GRAY profile, or a silent less-optimal compression of the pixel + data if the pixels could be encoded as grayscale but the ICC profile is RGB. + + To avoid this do not set an ICC profile in the image unless there is a good reason for it, and when doing so + make sure you compute it carefully to avoid the above problems. + */ + unsigned iccp_defined; /* Whether an iCCP chunk is present (0 = not present, 1 = present). */ + char* iccp_name; /* Null terminated string with profile name, 1-79 bytes */ + /* + The ICC profile in iccp_profile_size bytes. + Don't allocate this buffer yourself. Use the init/cleanup functions + correctly and use lodepng_set_icc and lodepng_clear_icc. + */ + unsigned char* iccp_profile; + unsigned iccp_profile_size; /* The size of iccp_profile in bytes */ + + /* End of color profile related chunks */ + + + /* + unknown chunks: chunks not known by LodePNG, passed on byte for byte. + + There are 3 buffers, one for each position in the PNG where unknown chunks can appear. + Each buffer contains all unknown chunks for that position consecutively. + The 3 positions are: + 0: between IHDR and PLTE, 1: between PLTE and IDAT, 2: between IDAT and IEND. + + For encoding, do not store critical chunks or known chunks that are enabled with a "_defined" flag + above in here, since the encoder will blindly follow this and could then encode an invalid PNG file + (such as one with two IHDR chunks or the disallowed combination of sRGB with iCCP). But do use + this if you wish to store an ancillary chunk that is not supported by LodePNG (such as sPLT or hIST), + or any non-standard PNG chunk. + + Do not allocate or traverse this data yourself. Use the chunk traversing functions declared + later, such as lodepng_chunk_next and lodepng_chunk_append, to read/write this struct. + */ + unsigned char* unknown_chunks_data[3]; + size_t unknown_chunks_size[3]; /*size in bytes of the unknown chunks, given for protection*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGInfo; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_info_init(LodePNGInfo* info); +void lodepng_info_cleanup(LodePNGInfo* info); +/*return value is error code (0 means no error)*/ +unsigned lodepng_info_copy(LodePNGInfo* dest, const LodePNGInfo* source); + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS +unsigned lodepng_add_text(LodePNGInfo* info, const char* key, const char* str); /*push back both texts at once*/ +void lodepng_clear_text(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ + +unsigned lodepng_add_itext(LodePNGInfo* info, const char* key, const char* langtag, + const char* transkey, const char* str); /*push back the 4 texts of 1 chunk at once*/ +void lodepng_clear_itext(LodePNGInfo* info); /*use this to clear the itexts again after you filled them in*/ + +/*replaces if exists*/ +unsigned lodepng_set_icc(LodePNGInfo* info, const char* name, const unsigned char* profile, unsigned profile_size); +void lodepng_clear_icc(LodePNGInfo* info); /*use this to clear the texts again after you filled them in*/ +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ + +/* +Converts raw buffer from one color type to another color type, based on +LodePNGColorMode structs to describe the input and output color type. +See the reference manual at the end of this header file to see which color conversions are supported. +return value = LodePNG error code (0 if all went ok, an error if the conversion isn't supported) +The out buffer must have size (w * h * bpp + 7) / 8, where bpp is the bits per pixel +of the output color type (lodepng_get_bpp). +For < 8 bpp images, there should not be padding bits at the end of scanlines. +For 16-bit per channel colors, uses big endian format like PNG does. +Return value is LodePNG error code +*/ +unsigned lodepng_convert(unsigned char* out, const unsigned char* in, + const LodePNGColorMode* mode_out, const LodePNGColorMode* mode_in, + unsigned w, unsigned h); + +#ifdef LODEPNG_COMPILE_DECODER +/* +Settings for the decoder. This contains settings for the PNG and the Zlib +decoder, but not the Info settings from the Info structs. +*/ +typedef struct LodePNGDecoderSettings { + LodePNGDecompressSettings zlibsettings; /*in here is the setting to ignore Adler32 checksums*/ + + /* Check LodePNGDecompressSettings for more ignorable errors such as ignore_adler32 */ + unsigned ignore_crc; /*ignore CRC checksums*/ + unsigned ignore_critical; /*ignore unknown critical chunks*/ + unsigned ignore_end; /*ignore issues at end of file if possible (missing IEND chunk, too large chunk, ...)*/ + /* TODO: make a system involving warnings with levels and a strict mode instead. Other potentially recoverable + errors: srgb rendering intent value, size of content of ancillary chunks, more than 79 characters for some + strings, placement/combination rules for ancillary chunks, crc of unknown chunks, allowed characters + in string keys, etc... */ + + unsigned color_convert; /*whether to convert the PNG to the color type you want. Default: yes*/ + +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + unsigned read_text_chunks; /*if false but remember_unknown_chunks is true, they're stored in the unknown chunks*/ + + /*store all bytes from unknown chunks in the LodePNGInfo (off by default, useful for a png editor)*/ + unsigned remember_unknown_chunks; + + /* maximum size for decompressed text chunks. If a text chunk's text is larger than this, an error is returned, + unless reading text chunks is disabled or this limit is set higher or disabled. Set to 0 to allow any size. + By default it is a value that prevents unreasonably large strings from hogging memory. */ + size_t max_text_size; + + /* maximum size for compressed ICC chunks. If the ICC profile is larger than this, an error will be returned. Set to + 0 to allow any size. By default this is a value that prevents ICC profiles that would be much larger than any + legitimate profile could be to hog memory. */ + size_t max_icc_size; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGDecoderSettings; + +void lodepng_decoder_settings_init(LodePNGDecoderSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/*automatically use color type with less bits per pixel if losslessly possible. Default: AUTO*/ +typedef enum LodePNGFilterStrategy { + /*every filter at zero*/ + LFS_ZERO = 0, + /*every filter at 1, 2, 3 or 4 (paeth), unlike LFS_ZERO not a good choice, but for testing*/ + LFS_ONE = 1, + LFS_TWO = 2, + LFS_THREE = 3, + LFS_FOUR = 4, + /*Use filter that gives minimum sum, as described in the official PNG filter heuristic.*/ + LFS_MINSUM, + /*Use the filter type that gives smallest Shannon entropy for this scanline. Depending + on the image, this is better or worse than minsum.*/ + LFS_ENTROPY, + /* + Brute-force-search PNG filters by compressing each filter for each scanline. + Experimental, very slow, and only rarely gives better compression than MINSUM. + */ + LFS_BRUTE_FORCE, + /*use predefined_filters buffer: you specify the filter type for each scanline*/ + LFS_PREDEFINED +} LodePNGFilterStrategy; + +/*Gives characteristics about the integer RGBA colors of the image (count, alpha channel usage, bit depth, ...), +which helps decide which color model to use for encoding. +Used internally by default if "auto_convert" is enabled. Public because it's useful for custom algorithms.*/ +typedef struct LodePNGColorStats { + unsigned colored; /*not grayscale*/ + unsigned key; /*image is not opaque and color key is possible instead of full alpha*/ + unsigned short key_r; /*key values, always as 16-bit, in 8-bit case the byte is duplicated, e.g. 65535 means 255*/ + unsigned short key_g; + unsigned short key_b; + unsigned alpha; /*image is not opaque and alpha channel or alpha palette required*/ + unsigned numcolors; /*amount of colors, up to 257. Not valid if bits == 16 or allow_palette is disabled.*/ + unsigned char palette[1024]; /*Remembers up to the first 256 RGBA colors, in no particular order, only valid when numcolors is valid*/ + unsigned bits; /*bits per channel (not for palette). 1,2 or 4 for grayscale only. 16 if 16-bit per channel required.*/ + size_t numpixels; + + /*user settings for computing/using the stats*/ + unsigned allow_palette; /*default 1. if 0, disallow choosing palette colortype in auto_choose_color, and don't count numcolors*/ + unsigned allow_greyscale; /*default 1. if 0, choose RGB or RGBA even if the image only has gray colors*/ +} LodePNGColorStats; + +void lodepng_color_stats_init(LodePNGColorStats* stats); + +/*Get a LodePNGColorStats of the image. The stats must already have been inited. +Returns error code (e.g. alloc fail) or 0 if ok.*/ +unsigned lodepng_compute_color_stats(LodePNGColorStats* stats, + const unsigned char* image, unsigned w, unsigned h, + const LodePNGColorMode* mode_in); + +/*Settings for the encoder.*/ +typedef struct LodePNGEncoderSettings { + LodePNGCompressSettings zlibsettings; /*settings for the zlib encoder, such as window size, ...*/ + + unsigned auto_convert; /*automatically choose output PNG color type. Default: true*/ + + /*If true, follows the official PNG heuristic: if the PNG uses a palette or lower than + 8 bit depth, set all filters to zero. Otherwise use the filter_strategy. Note that to + completely follow the official PNG heuristic, filter_palette_zero must be true and + filter_strategy must be LFS_MINSUM*/ + unsigned filter_palette_zero; + /*Which filter strategy to use when not using zeroes due to filter_palette_zero. + Set filter_palette_zero to 0 to ensure always using your chosen strategy. Default: LFS_MINSUM*/ + LodePNGFilterStrategy filter_strategy; + /*used if filter_strategy is LFS_PREDEFINED. In that case, this must point to a buffer with + the same length as the amount of scanlines in the image, and each value must <= 5. You + have to cleanup this buffer, LodePNG will never free it. Don't forget that filter_palette_zero + must be set to 0 to ensure this is also used on palette or low bitdepth images.*/ + const unsigned char* predefined_filters; + + /*force creating a PLTE chunk if colortype is 2 or 6 (= a suggested palette). + If colortype is 3, PLTE is _always_ created.*/ + unsigned force_palette; +#ifdef LODEPNG_COMPILE_ANCILLARY_CHUNKS + /*add LodePNG identifier and version as a text chunk, for debugging*/ + unsigned add_id; + /*encode text chunks as zTXt chunks instead of tEXt chunks, and use compression in iTXt chunks*/ + unsigned text_compression; +#endif /*LODEPNG_COMPILE_ANCILLARY_CHUNKS*/ +} LodePNGEncoderSettings; + +void lodepng_encoder_settings_init(LodePNGEncoderSettings* settings); +#endif /*LODEPNG_COMPILE_ENCODER*/ + + +#if defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) +/*The settings, state and information for extended encoding and decoding.*/ +typedef struct LodePNGState { +#ifdef LODEPNG_COMPILE_DECODER + LodePNGDecoderSettings decoder; /*the decoding settings*/ +#endif /*LODEPNG_COMPILE_DECODER*/ +#ifdef LODEPNG_COMPILE_ENCODER + LodePNGEncoderSettings encoder; /*the encoding settings*/ +#endif /*LODEPNG_COMPILE_ENCODER*/ + LodePNGColorMode info_raw; /*specifies the format in which you would like to get the raw pixel buffer*/ + LodePNGInfo info_png; /*info of the PNG image obtained after decoding*/ + unsigned error; +} LodePNGState; + +/*init, cleanup and copy functions to use with this struct*/ +void lodepng_state_init(LodePNGState* state); +void lodepng_state_cleanup(LodePNGState* state); +void lodepng_state_copy(LodePNGState* dest, const LodePNGState* source); +#endif /* defined(LODEPNG_COMPILE_DECODER) || defined(LODEPNG_COMPILE_ENCODER) */ + +#ifdef LODEPNG_COMPILE_DECODER +/* +Same as lodepng_decode_memory, but uses a LodePNGState to allow custom settings and +getting much more information about the PNG image and color mode. +*/ +unsigned lodepng_decode(unsigned char** out, unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); + +/* +Read the PNG header, but not the actual data. This returns only the information +that is in the IHDR chunk of the PNG, such as width, height and color type. The +information is placed in the info_png field of the LodePNGState. +*/ +unsigned lodepng_inspect(unsigned* w, unsigned* h, + LodePNGState* state, + const unsigned char* in, size_t insize); +#endif /*LODEPNG_COMPILE_DECODER*/ + +/* +Reads one metadata chunk (other than IHDR) of the PNG file and outputs what it +read in the state. Returns error code on failure. +Use lodepng_inspect first with a new state, then e.g. lodepng_chunk_find_const +to find the desired chunk type, and if non null use lodepng_inspect_chunk (with +chunk_pointer - start_of_file as pos). +Supports most metadata chunks from the PNG standard (gAMA, bKGD, tEXt, ...). +Ignores unsupported, unknown, non-metadata or IHDR chunks (without error). +Requirements: &in[pos] must point to start of a chunk, must use regular +lodepng_inspect first since format of most other chunks depends on IHDR, and if +there is a PLTE chunk, that one must be inspected before tRNS or bKGD. +*/ +unsigned lodepng_inspect_chunk(LodePNGState* state, size_t pos, + const unsigned char* in, size_t insize); + +#ifdef LODEPNG_COMPILE_ENCODER +/*This function allocates the out buffer with standard malloc and stores the size in *outsize.*/ +unsigned lodepng_encode(unsigned char** out, size_t* outsize, + const unsigned char* image, unsigned w, unsigned h, + LodePNGState* state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +/* +The lodepng_chunk functions are normally not needed, except to traverse the +unknown chunks stored in the LodePNGInfo struct, or add new ones to it. +It also allows traversing the chunks of an encoded PNG file yourself. + +The chunk pointer always points to the beginning of the chunk itself, that is +the first byte of the 4 length bytes. + +In the PNG file format, chunks have the following format: +-4 bytes length: length of the data of the chunk in bytes (chunk itself is 12 bytes longer) +-4 bytes chunk type (ASCII a-z,A-Z only, see below) +-length bytes of data (may be 0 bytes if length was 0) +-4 bytes of CRC, computed on chunk name + data + +The first chunk starts at the 8th byte of the PNG file, the entire rest of the file +exists out of concatenated chunks with the above format. + +PNG standard chunk ASCII naming conventions: +-First byte: uppercase = critical, lowercase = ancillary +-Second byte: uppercase = public, lowercase = private +-Third byte: must be uppercase +-Fourth byte: uppercase = unsafe to copy, lowercase = safe to copy +*/ + +/* +Gets the length of the data of the chunk. Total chunk length has 12 bytes more. +There must be at least 4 bytes to read from. If the result value is too large, +it may be corrupt data. +*/ +unsigned lodepng_chunk_length(const unsigned char* chunk); + +/*puts the 4-byte type in null terminated string*/ +void lodepng_chunk_type(char type[5], const unsigned char* chunk); + +/*check if the type is the given type*/ +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type); + +/*0: it's one of the critical chunk types, 1: it's an ancillary chunk (see PNG standard)*/ +unsigned char lodepng_chunk_ancillary(const unsigned char* chunk); + +/*0: public, 1: private (see PNG standard)*/ +unsigned char lodepng_chunk_private(const unsigned char* chunk); + +/*0: the chunk is unsafe to copy, 1: the chunk is safe to copy (see PNG standard)*/ +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk); + +/*get pointer to the data of the chunk, where the input points to the header of the chunk*/ +unsigned char* lodepng_chunk_data(unsigned char* chunk); +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk); + +/*returns 0 if the crc is correct, 1 if it's incorrect (0 for OK as usual!)*/ +unsigned lodepng_chunk_check_crc(const unsigned char* chunk); + +/*generates the correct CRC from the data and puts it in the last 4 bytes of the chunk*/ +void lodepng_chunk_generate_crc(unsigned char* chunk); + +/* +Iterate to next chunks, allows iterating through all chunks of the PNG file. +Input must be at the beginning of a chunk (result of a previous lodepng_chunk_next call, +or the 8th byte of a PNG file which always has the first chunk), or alternatively may +point to the first byte of the PNG file (which is not a chunk but the magic header, the +function will then skip over it and return the first real chunk). +Will output pointer to the start of the next chunk, or at or beyond end of the file if there +is no more chunk after this or possibly if the chunk is corrupt. +Start this process at the 8th byte of the PNG file. +In a non-corrupt PNG file, the last chunk should have name "IEND". +*/ +unsigned char* lodepng_chunk_next(unsigned char* chunk, unsigned char* end); +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk, const unsigned char* end); + +/*Finds the first chunk with the given type in the range [chunk, end), or returns NULL if not found.*/ +unsigned char* lodepng_chunk_find(unsigned char* chunk, unsigned char* end, const char type[5]); +const unsigned char* lodepng_chunk_find_const(const unsigned char* chunk, const unsigned char* end, const char type[5]); + +/* +Appends chunk to the data in out. The given chunk should already have its chunk header. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returns error code (0 if it went ok) +*/ +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk); + +/* +Appends new chunk to out. The chunk to append is given by giving its length, type +and data separately. The type is a 4-letter string. +The out variable and outsize are updated to reflect the new reallocated buffer. +Returne error code (0 if it went ok) +*/ +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data); + + +/*Calculate CRC32 of buffer*/ +unsigned lodepng_crc32(const unsigned char* buf, size_t len); +#endif /*LODEPNG_COMPILE_PNG*/ + + +#ifdef LODEPNG_COMPILE_ZLIB +/* +This zlib part can be used independently to zlib compress and decompress a +buffer. It cannot be used to create gzip files however, and it only supports the +part of zlib that is required for PNG, it does not support dictionaries. +*/ + +#ifdef LODEPNG_COMPILE_DECODER +/*Inflate a buffer. Inflate is the decompression step of deflate. Out buffer must be freed after use.*/ +unsigned lodepng_inflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); + +/* +Decompresses Zlib data. Reallocates the out buffer and appends the data. The +data must be according to the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_decompress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGDecompressSettings* settings); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* +Compresses data with Zlib. Reallocates the out buffer and appends the data. +Zlib adds a small header and trailer around the deflate data. +The data is output in the format of the zlib specification. +Either, *out must be NULL and *outsize must be 0, or, *out must be a valid +buffer and *outsize its size in bytes. out must be freed by user after usage. +*/ +unsigned lodepng_zlib_compress(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +/* +Find length-limited Huffman code for given frequencies. This function is in the +public interface only for tests, it's used internally by lodepng_deflate. +*/ +unsigned lodepng_huffman_code_lengths(unsigned* lengths, const unsigned* frequencies, + size_t numcodes, unsigned maxbitlen); + +/*Compress a buffer with deflate. See RFC 1951. Out buffer must be freed after use.*/ +unsigned lodepng_deflate(unsigned char** out, size_t* outsize, + const unsigned char* in, size_t insize, + const LodePNGCompressSettings* settings); + +#endif /*LODEPNG_COMPILE_ENCODER*/ +#endif /*LODEPNG_COMPILE_ZLIB*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into buffer. The function allocates the out buffer, and +after usage you should free it. +out: output parameter, contains pointer to loaded buffer. +outsize: output parameter, size of the allocated out buffer +filename: the path to the file to load +return value: error code (0 means ok) +*/ +unsigned lodepng_load_file(unsigned char** out, size_t* outsize, const char* filename); + +/* +Save a file from buffer to disk. Warning, if it exists, this function overwrites +the file without warning! +buffer: the buffer to write +buffersize: size of the buffer to write +filename: the path to the file to save to +return value: error code (0 means ok) +*/ +unsigned lodepng_save_file(const unsigned char* buffer, size_t buffersize, const char* filename); +#endif /*LODEPNG_COMPILE_DISK*/ + +#ifdef LODEPNG_COMPILE_CPP +/* The LodePNG C++ wrapper uses std::vectors instead of manually allocated memory buffers. */ +namespace lodepng { +#ifdef LODEPNG_COMPILE_PNG +class State : public LodePNGState { + public: + State(); + State(const State& other); + ~State(); + State& operator=(const State& other); +}; + +#ifdef LODEPNG_COMPILE_DECODER +/* Same as other lodepng::decode, but using a State for more settings and information. */ +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + State& state, + const unsigned char* in, size_t insize); +unsigned decode(std::vector<unsigned char>& out, unsigned& w, unsigned& h, + State& state, + const std::vector<unsigned char>& in); +#endif /*LODEPNG_COMPILE_DECODER*/ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Same as other lodepng::encode, but using a State for more settings and information. */ +unsigned encode(std::vector<unsigned char>& out, + const unsigned char* in, unsigned w, unsigned h, + State& state); +unsigned encode(std::vector<unsigned char>& out, + const std::vector<unsigned char>& in, unsigned w, unsigned h, + State& state); +#endif /*LODEPNG_COMPILE_ENCODER*/ + +#ifdef LODEPNG_COMPILE_DISK +/* +Load a file from disk into an std::vector. +return value: error code (0 means ok) +*/ +unsigned load_file(std::vector<unsigned char>& buffer, const std::string& filename); + +/* +Save the binary data in an std::vector to a file on disk. The file is overwritten +without warning. +*/ +unsigned save_file(const std::vector<unsigned char>& buffer, const std::string& filename); +#endif /* LODEPNG_COMPILE_DISK */ +#endif /* LODEPNG_COMPILE_PNG */ + +#ifdef LODEPNG_COMPILE_ZLIB +#ifdef LODEPNG_COMPILE_DECODER +/* Zlib-decompress an unsigned char buffer */ +unsigned decompress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); + +/* Zlib-decompress an std::vector */ +unsigned decompress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in, + const LodePNGDecompressSettings& settings = lodepng_default_decompress_settings); +#endif /* LODEPNG_COMPILE_DECODER */ + +#ifdef LODEPNG_COMPILE_ENCODER +/* Zlib-compress an unsigned char buffer */ +unsigned compress(std::vector<unsigned char>& out, const unsigned char* in, size_t insize, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); + +/* Zlib-compress an std::vector */ +unsigned compress(std::vector<unsigned char>& out, const std::vector<unsigned char>& in, + const LodePNGCompressSettings& settings = lodepng_default_compress_settings); +#endif /* LODEPNG_COMPILE_ENCODER */ +#endif /* LODEPNG_COMPILE_ZLIB */ +} /* namespace lodepng */ +#endif /*LODEPNG_COMPILE_CPP*/ + +/* +TODO: +[.] test if there are no memory leaks or security exploits - done a lot but needs to be checked often +[.] check compatibility with various compilers - done but needs to be redone for every newer version +[X] converting color to 16-bit per channel types +[X] support color profile chunk types (but never let them touch RGB values by default) +[ ] support all public PNG chunk types (almost done except sBIT, sPLT and hIST) +[ ] make sure encoder generates no chunks with size > (2^31)-1 +[ ] partial decoding (stream processing) +[X] let the "isFullyOpaque" function check color keys and transparent palettes too +[X] better name for the variables "codes", "codesD", "codelengthcodes", "clcl" and "lldl" +[ ] allow treating some errors like warnings, when image is recoverable (e.g. 69, 57, 58) +[ ] make warnings like: oob palette, checksum fail, data after iend, wrong/unknown crit chunk, no null terminator in text, ... +[ ] error messages with line numbers (and version) +[ ] errors in state instead of as return code? +[ ] new errors/warnings like suspiciously big decompressed ztxt or iccp chunk +[ ] let the C++ wrapper catch exceptions coming from the standard library and return LodePNG error codes +[ ] allow user to provide custom color conversion functions, e.g. for premultiplied alpha, padding bits or not, ... +[ ] allow user to give data (void*) to custom allocator +[X] provide alternatives for C library functions not present on some platforms (memcpy, ...) +*/ + +#endif /*LV_USE_PNG*/ + +#endif /*LODEPNG_H inclusion guard*/ + +/* +LodePNG Documentation +--------------------- + +0. table of contents +-------------------- + + 1. about + 1.1. supported features + 1.2. features not supported + 2. C and C++ version + 3. security + 4. decoding + 5. encoding + 6. color conversions + 6.1. PNG color types + 6.2. color conversions + 6.3. padding bits + 6.4. A note about 16-bits per channel and endianness + 7. error values + 8. chunks and PNG editing + 9. compiler support + 10. examples + 10.1. decoder C++ example + 10.2. decoder C example + 11. state settings reference + 12. changes + 13. contact information + + +1. about +-------- + +PNG is a file format to store raster images losslessly with good compression, +supporting different color types and alpha channel. + +LodePNG is a PNG codec according to the Portable Network Graphics (PNG) +Specification (Second Edition) - W3C Recommendation 10 November 2003. + +The specifications used are: + +*) Portable Network Graphics (PNG) Specification (Second Edition): + http://www.w3.org/TR/2003/REC-PNG-20031110 +*) RFC 1950 ZLIB Compressed Data Format version 3.3: + http://www.gzip.org/zlib/rfc-zlib.html +*) RFC 1951 DEFLATE Compressed Data Format Specification ver 1.3: + http://www.gzip.org/zlib/rfc-deflate.html + +The most recent version of LodePNG can currently be found at +http://lodev.org/lodepng/ + +LodePNG works both in C (ISO C90) and C++, with a C++ wrapper that adds +extra functionality. + +LodePNG exists out of two files: +-lodepng.h: the header file for both C and C++ +-lodepng.c(pp): give it the name lodepng.c or lodepng.cpp (or .cc) depending on your usage + +If you want to start using LodePNG right away without reading this doc, get the +examples from the LodePNG website to see how to use it in code, or check the +smaller examples in chapter 13 here. + +LodePNG is simple but only supports the basic requirements. To achieve +simplicity, the following design choices were made: There are no dependencies +on any external library. There are functions to decode and encode a PNG with +a single function call, and extended versions of these functions taking a +LodePNGState struct allowing to specify or get more information. By default +the colors of the raw image are always RGB or RGBA, no matter what color type +the PNG file uses. To read and write files, there are simple functions to +convert the files to/from buffers in memory. + +This all makes LodePNG suitable for loading textures in games, demos and small +programs, ... It's less suitable for full fledged image editors, loading PNGs +over network (it requires all the image data to be available before decoding can +begin), life-critical systems, ... + +1.1. supported features +----------------------- + +The following features are supported by the decoder: + +*) decoding of PNGs with any color type, bit depth and interlace mode, to a 24- or 32-bit color raw image, + or the same color type as the PNG +*) encoding of PNGs, from any raw image to 24- or 32-bit color, or the same color type as the raw image +*) Adam7 interlace and deinterlace for any color type +*) loading the image from harddisk or decoding it from a buffer from other sources than harddisk +*) support for alpha channels, including RGBA color model, translucent palettes and color keying +*) zlib decompression (inflate) +*) zlib compression (deflate) +*) CRC32 and ADLER32 checksums +*) colorimetric color profile conversions: currently experimentally available in lodepng_util.cpp only, + plus alternatively ability to pass on chroma/gamma/ICC profile information to other color management system. +*) handling of unknown chunks, allowing making a PNG editor that stores custom and unknown chunks. +*) the following chunks are supported by both encoder and decoder: + IHDR: header information + PLTE: color palette + IDAT: pixel data + IEND: the final chunk + tRNS: transparency for palettized images + tEXt: textual information + zTXt: compressed textual information + iTXt: international textual information + bKGD: suggested background color + pHYs: physical dimensions + tIME: modification time + cHRM: RGB chromaticities + gAMA: RGB gamma correction + iCCP: ICC color profile + sRGB: rendering intent + +1.2. features not supported +--------------------------- + +The following features are _not_ supported: + +*) some features needed to make a conformant PNG-Editor might be still missing. +*) partial loading/stream processing. All data must be available and is processed in one call. +*) The following public chunks are not (yet) supported but treated as unknown chunks by LodePNG: + sBIT + hIST + sPLT + + +2. C and C++ version +-------------------- + +The C version uses buffers allocated with alloc that you need to free() +yourself. You need to use init and cleanup functions for each struct whenever +using a struct from the C version to avoid exploits and memory leaks. + +The C++ version has extra functions with std::vectors in the interface and the +lodepng::State class which is a LodePNGState with constructor and destructor. + +These files work without modification for both C and C++ compilers because all +the additional C++ code is in "#ifdef __cplusplus" blocks that make C-compilers +ignore it, and the C code is made to compile both with strict ISO C90 and C++. + +To use the C++ version, you need to rename the source file to lodepng.cpp +(instead of lodepng.c), and compile it with a C++ compiler. + +To use the C version, you need to rename the source file to lodepng.c (instead +of lodepng.cpp), and compile it with a C compiler. + + +3. Security +----------- + +Even if carefully designed, it's always possible that LodePNG contains possible +exploits. If you discover one, please let me know, and it will be fixed. + +When using LodePNG, care has to be taken with the C version of LodePNG, as well +as the C-style structs when working with C++. The following conventions are used +for all C-style structs: + +-if a struct has a corresponding init function, always call the init function when making a new one +-if a struct has a corresponding cleanup function, call it before the struct disappears to avoid memory leaks +-if a struct has a corresponding copy function, use the copy function instead of "=". + The destination must also be inited already. + + +4. Decoding +----------- + +Decoding converts a PNG compressed image to a raw pixel buffer. + +Most documentation on using the decoder is at its declarations in the header +above. For C, simple decoding can be done with functions such as +lodepng_decode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_decode. For C++, all decoding can be done with the +various lodepng::decode functions, and lodepng::State can be used for advanced +features. + +When using the LodePNGState, it uses the following fields for decoding: +*) LodePNGInfo info_png: it stores extra information about the PNG (the input) in here +*) LodePNGColorMode info_raw: here you can say what color mode of the raw image (the output) you want to get +*) LodePNGDecoderSettings decoder: you can specify a few extra settings for the decoder to use + +LodePNGInfo info_png +-------------------- + +After decoding, this contains extra information of the PNG image, except the actual +pixels, width and height because these are already gotten directly from the decoder +functions. + +It contains for example the original color type of the PNG image, text comments, +suggested background color, etc... More details about the LodePNGInfo struct are +at its declaration documentation. + +LodePNGColorMode info_raw +------------------------- + +When decoding, here you can specify which color type you want +the resulting raw image to be. If this is different from the colortype of the +PNG, then the decoder will automatically convert the result. This conversion +always works, except if you want it to convert a color PNG to grayscale or to +a palette with missing colors. + +By default, 32-bit color is used for the result. + +LodePNGDecoderSettings decoder +------------------------------ + +The settings can be used to ignore the errors created by invalid CRC and Adler32 +chunks, and to disable the decoding of tEXt chunks. + +There's also a setting color_convert, true by default. If false, no conversion +is done, the resulting data will be as it was in the PNG (after decompression) +and you'll have to puzzle the colors of the pixels together yourself using the +color type information in the LodePNGInfo. + + +5. Encoding +----------- + +Encoding converts a raw pixel buffer to a PNG compressed image. + +Most documentation on using the encoder is at its declarations in the header +above. For C, simple encoding can be done with functions such as +lodepng_encode32, and more advanced decoding can be done with the struct +LodePNGState and lodepng_encode. For C++, all encoding can be done with the +various lodepng::encode functions, and lodepng::State can be used for advanced +features. + +Like the decoder, the encoder can also give errors. However it gives less errors +since the encoder input is trusted, the decoder input (a PNG image that could +be forged by anyone) is not trusted. + +When using the LodePNGState, it uses the following fields for encoding: +*) LodePNGInfo info_png: here you specify how you want the PNG (the output) to be. +*) LodePNGColorMode info_raw: here you say what color type of the raw image (the input) has +*) LodePNGEncoderSettings encoder: you can specify a few settings for the encoder to use + +LodePNGInfo info_png +-------------------- + +When encoding, you use this the opposite way as when decoding: for encoding, +you fill in the values you want the PNG to have before encoding. By default it's +not needed to specify a color type for the PNG since it's automatically chosen, +but it's possible to choose it yourself given the right settings. + +The encoder will not always exactly match the LodePNGInfo struct you give, +it tries as close as possible. Some things are ignored by the encoder. The +encoder uses, for example, the following settings from it when applicable: +colortype and bitdepth, text chunks, time chunk, the color key, the palette, the +background color, the interlace method, unknown chunks, ... + +When encoding to a PNG with colortype 3, the encoder will generate a PLTE chunk. +If the palette contains any colors for which the alpha channel is not 255 (so +there are translucent colors in the palette), it'll add a tRNS chunk. + +LodePNGColorMode info_raw +------------------------- + +You specify the color type of the raw image that you give to the input here, +including a possible transparent color key and palette you happen to be using in +your raw image data. + +By default, 32-bit color is assumed, meaning your input has to be in RGBA +format with 4 bytes (unsigned chars) per pixel. + +LodePNGEncoderSettings encoder +------------------------------ + +The following settings are supported (some are in sub-structs): +*) auto_convert: when this option is enabled, the encoder will +automatically choose the smallest possible color mode (including color key) that +can encode the colors of all pixels without information loss. +*) btype: the block type for LZ77. 0 = uncompressed, 1 = fixed huffman tree, + 2 = dynamic huffman tree (best compression). Should be 2 for proper + compression. +*) use_lz77: whether or not to use LZ77 for compressed block types. Should be + true for proper compression. +*) windowsize: the window size used by the LZ77 encoder (1 - 32768). Has value + 2048 by default, but can be set to 32768 for better, but slow, compression. +*) force_palette: if colortype is 2 or 6, you can make the encoder write a PLTE + chunk if force_palette is true. This can used as suggested palette to convert + to by viewers that don't support more than 256 colors (if those still exist) +*) add_id: add text chunk "Encoder: LodePNG <version>" to the image. +*) text_compression: default 1. If 1, it'll store texts as zTXt instead of tEXt chunks. + zTXt chunks use zlib compression on the text. This gives a smaller result on + large texts but a larger result on small texts (such as a single program name). + It's all tEXt or all zTXt though, there's no separate setting per text yet. + + +6. color conversions +-------------------- + +An important thing to note about LodePNG, is that the color type of the PNG, and +the color type of the raw image, are completely independent. By default, when +you decode a PNG, you get the result as a raw image in the color type you want, +no matter whether the PNG was encoded with a palette, grayscale or RGBA color. +And if you encode an image, by default LodePNG will automatically choose the PNG +color type that gives good compression based on the values of colors and amount +of colors in the image. It can be configured to let you control it instead as +well, though. + +To be able to do this, LodePNG does conversions from one color mode to another. +It can convert from almost any color type to any other color type, except the +following conversions: RGB to grayscale is not supported, and converting to a +palette when the palette doesn't have a required color is not supported. This is +not supported on purpose: this is information loss which requires a color +reduction algorithm that is beyond the scope of a PNG encoder (yes, RGB to gray +is easy, but there are multiple ways if you want to give some channels more +weight). + +By default, when decoding, you get the raw image in 32-bit RGBA or 24-bit RGB +color, no matter what color type the PNG has. And by default when encoding, +LodePNG automatically picks the best color model for the output PNG, and expects +the input image to be 32-bit RGBA or 24-bit RGB. So, unless you want to control +the color format of the images yourself, you can skip this chapter. + +6.1. PNG color types +-------------------- + +A PNG image can have many color types, ranging from 1-bit color to 64-bit color, +as well as palettized color modes. After the zlib decompression and unfiltering +in the PNG image is done, the raw pixel data will have that color type and thus +a certain amount of bits per pixel. If you want the output raw image after +decoding to have another color type, a conversion is done by LodePNG. + +The PNG specification gives the following color types: + +0: grayscale, bit depths 1, 2, 4, 8, 16 +2: RGB, bit depths 8 and 16 +3: palette, bit depths 1, 2, 4 and 8 +4: grayscale with alpha, bit depths 8 and 16 +6: RGBA, bit depths 8 and 16 + +Bit depth is the amount of bits per pixel per color channel. So the total amount +of bits per pixel is: amount of channels * bitdepth. + +6.2. color conversions +---------------------- + +As explained in the sections about the encoder and decoder, you can specify +color types and bit depths in info_png and info_raw to change the default +behaviour. + +If, when decoding, you want the raw image to be something else than the default, +you need to set the color type and bit depth you want in the LodePNGColorMode, +or the parameters colortype and bitdepth of the simple decoding function. + +If, when encoding, you use another color type than the default in the raw input +image, you need to specify its color type and bit depth in the LodePNGColorMode +of the raw image, or use the parameters colortype and bitdepth of the simple +encoding function. + +If, when encoding, you don't want LodePNG to choose the output PNG color type +but control it yourself, you need to set auto_convert in the encoder settings +to false, and specify the color type you want in the LodePNGInfo of the +encoder (including palette: it can generate a palette if auto_convert is true, +otherwise not). + +If the input and output color type differ (whether user chosen or auto chosen), +LodePNG will do a color conversion, which follows the rules below, and may +sometimes result in an error. + +To avoid some confusion: +-the decoder converts from PNG to raw image +-the encoder converts from raw image to PNG +-the colortype and bitdepth in LodePNGColorMode info_raw, are those of the raw image +-the colortype and bitdepth in the color field of LodePNGInfo info_png, are those of the PNG +-when encoding, the color type in LodePNGInfo is ignored if auto_convert + is enabled, it is automatically generated instead +-when decoding, the color type in LodePNGInfo is set by the decoder to that of the original + PNG image, but it can be ignored since the raw image has the color type you requested instead +-if the color type of the LodePNGColorMode and PNG image aren't the same, a conversion + between the color types is done if the color types are supported. If it is not + supported, an error is returned. If the types are the same, no conversion is done. +-even though some conversions aren't supported, LodePNG supports loading PNGs from any + colortype and saving PNGs to any colortype, sometimes it just requires preparing + the raw image correctly before encoding. +-both encoder and decoder use the same color converter. + +The function lodepng_convert does the color conversion. It is available in the +interface but normally isn't needed since the encoder and decoder already call +it. + +Non supported color conversions: +-color to grayscale when non-gray pixels are present: no error is thrown, but +the result will look ugly because only the red channel is taken (it assumes all +three channels are the same in this case so ignores green and blue). The reason +no error is given is to allow converting from three-channel grayscale images to +one-channel even if there are numerical imprecisions. +-anything to palette when the palette does not have an exact match for a from-color +in it: in this case an error is thrown + +Supported color conversions: +-anything to 8-bit RGB, 8-bit RGBA, 16-bit RGB, 16-bit RGBA +-any gray or gray+alpha, to gray or gray+alpha +-anything to a palette, as long as the palette has the requested colors in it +-removing alpha channel +-higher to smaller bitdepth, and vice versa + +If you want no color conversion to be done (e.g. for speed or control): +-In the encoder, you can make it save a PNG with any color type by giving the +raw color mode and LodePNGInfo the same color mode, and setting auto_convert to +false. +-In the decoder, you can make it store the pixel data in the same color type +as the PNG has, by setting the color_convert setting to false. Settings in +info_raw are then ignored. + +6.3. padding bits +----------------- + +In the PNG file format, if a less than 8-bit per pixel color type is used and the scanlines +have a bit amount that isn't a multiple of 8, then padding bits are used so that each +scanline starts at a fresh byte. But that is NOT true for the LodePNG raw input and output. +The raw input image you give to the encoder, and the raw output image you get from the decoder +will NOT have these padding bits, e.g. in the case of a 1-bit image with a width +of 7 pixels, the first pixel of the second scanline will the 8th bit of the first byte, +not the first bit of a new byte. + +6.4. A note about 16-bits per channel and endianness +---------------------------------------------------- + +LodePNG uses unsigned char arrays for 16-bit per channel colors too, just like +for any other color format. The 16-bit values are stored in big endian (most +significant byte first) in these arrays. This is the opposite order of the +little endian used by x86 CPU's. + +LodePNG always uses big endian because the PNG file format does so internally. +Conversions to other formats than PNG uses internally are not supported by +LodePNG on purpose, there are myriads of formats, including endianness of 16-bit +colors, the order in which you store R, G, B and A, and so on. Supporting and +converting to/from all that is outside the scope of LodePNG. + +This may mean that, depending on your use case, you may want to convert the big +endian output of LodePNG to little endian with a for loop. This is certainly not +always needed, many applications and libraries support big endian 16-bit colors +anyway, but it means you cannot simply cast the unsigned char* buffer to an +unsigned short* buffer on x86 CPUs. + + +7. error values +--------------- + +All functions in LodePNG that return an error code, return 0 if everything went +OK, or a non-zero code if there was an error. + +The meaning of the LodePNG error values can be retrieved with the function +lodepng_error_text: given the numerical error code, it returns a description +of the error in English as a string. + +Check the implementation of lodepng_error_text to see the meaning of each code. + +It is not recommended to use the numerical values to programmatically make +different decisions based on error types as the numbers are not guaranteed to +stay backwards compatible. They are for human consumption only. Programmatically +only 0 or non-0 matter. + + +8. chunks and PNG editing +------------------------- + +If you want to add extra chunks to a PNG you encode, or use LodePNG for a PNG +editor that should follow the rules about handling of unknown chunks, or if your +program is able to read other types of chunks than the ones handled by LodePNG, +then that's possible with the chunk functions of LodePNG. + +A PNG chunk has the following layout: + +4 bytes length +4 bytes type name +length bytes data +4 bytes CRC + +8.1. iterating through chunks +----------------------------- + +If you have a buffer containing the PNG image data, then the first chunk (the +IHDR chunk) starts at byte number 8 of that buffer. The first 8 bytes are the +signature of the PNG and are not part of a chunk. But if you start at byte 8 +then you have a chunk, and can check the following things of it. + +NOTE: none of these functions check for memory buffer boundaries. To avoid +exploits, always make sure the buffer contains all the data of the chunks. +When using lodepng_chunk_next, make sure the returned value is within the +allocated memory. + +unsigned lodepng_chunk_length(const unsigned char* chunk): + +Get the length of the chunk's data. The total chunk length is this length + 12. + +void lodepng_chunk_type(char type[5], const unsigned char* chunk): +unsigned char lodepng_chunk_type_equals(const unsigned char* chunk, const char* type): + +Get the type of the chunk or compare if it's a certain type + +unsigned char lodepng_chunk_critical(const unsigned char* chunk): +unsigned char lodepng_chunk_private(const unsigned char* chunk): +unsigned char lodepng_chunk_safetocopy(const unsigned char* chunk): + +Check if the chunk is critical in the PNG standard (only IHDR, PLTE, IDAT and IEND are). +Check if the chunk is private (public chunks are part of the standard, private ones not). +Check if the chunk is safe to copy. If it's not, then, when modifying data in a critical +chunk, unsafe to copy chunks of the old image may NOT be saved in the new one if your +program doesn't handle that type of unknown chunk. + +unsigned char* lodepng_chunk_data(unsigned char* chunk): +const unsigned char* lodepng_chunk_data_const(const unsigned char* chunk): + +Get a pointer to the start of the data of the chunk. + +unsigned lodepng_chunk_check_crc(const unsigned char* chunk): +void lodepng_chunk_generate_crc(unsigned char* chunk): + +Check if the crc is correct or generate a correct one. + +unsigned char* lodepng_chunk_next(unsigned char* chunk): +const unsigned char* lodepng_chunk_next_const(const unsigned char* chunk): + +Iterate to the next chunk. This works if you have a buffer with consecutive chunks. Note that these +functions do no boundary checking of the allocated data whatsoever, so make sure there is enough +data available in the buffer to be able to go to the next chunk. + +unsigned lodepng_chunk_append(unsigned char** out, size_t* outsize, const unsigned char* chunk): +unsigned lodepng_chunk_create(unsigned char** out, size_t* outsize, unsigned length, + const char* type, const unsigned char* data): + +These functions are used to create new chunks that are appended to the data in *out that has +length *outsize. The append function appends an existing chunk to the new data. The create +function creates a new chunk with the given parameters and appends it. Type is the 4-letter +name of the chunk. + +8.2. chunks in info_png +----------------------- + +The LodePNGInfo struct contains fields with the unknown chunk in it. It has 3 +buffers (each with size) to contain 3 types of unknown chunks: +the ones that come before the PLTE chunk, the ones that come between the PLTE +and the IDAT chunks, and the ones that come after the IDAT chunks. +It's necessary to make the distinction between these 3 cases because the PNG +standard forces to keep the ordering of unknown chunks compared to the critical +chunks, but does not force any other ordering rules. + +info_png.unknown_chunks_data[0] is the chunks before PLTE +info_png.unknown_chunks_data[1] is the chunks after PLTE, before IDAT +info_png.unknown_chunks_data[2] is the chunks after IDAT + +The chunks in these 3 buffers can be iterated through and read by using the same +way described in the previous subchapter. + +When using the decoder to decode a PNG, you can make it store all unknown chunks +if you set the option settings.remember_unknown_chunks to 1. By default, this +option is off (0). + +The encoder will always encode unknown chunks that are stored in the info_png. +If you need it to add a particular chunk that isn't known by LodePNG, you can +use lodepng_chunk_append or lodepng_chunk_create to the chunk data in +info_png.unknown_chunks_data[x]. + +Chunks that are known by LodePNG should not be added in that way. E.g. to make +LodePNG add a bKGD chunk, set background_defined to true and add the correct +parameters there instead. + + +9. compiler support +------------------- + +No libraries other than the current standard C library are needed to compile +LodePNG. For the C++ version, only the standard C++ library is needed on top. +Add the files lodepng.c(pp) and lodepng.h to your project, include +lodepng.h where needed, and your program can read/write PNG files. + +It is compatible with C90 and up, and C++03 and up. + +If performance is important, use optimization when compiling! For both the +encoder and decoder, this makes a large difference. + +Make sure that LodePNG is compiled with the same compiler of the same version +and with the same settings as the rest of the program, or the interfaces with +std::vectors and std::strings in C++ can be incompatible. + +CHAR_BITS must be 8 or higher, because LodePNG uses unsigned chars for octets. + +*) gcc and g++ + +LodePNG is developed in gcc so this compiler is natively supported. It gives no +warnings with compiler options "-Wall -Wextra -pedantic -ansi", with gcc and g++ +version 4.7.1 on Linux, 32-bit and 64-bit. + +*) Clang + +Fully supported and warning-free. + +*) Mingw + +The Mingw compiler (a port of gcc for Windows) should be fully supported by +LodePNG. + +*) Visual Studio and Visual C++ Express Edition + +LodePNG should be warning-free with warning level W4. Two warnings were disabled +with pragmas though: warning 4244 about implicit conversions, and warning 4996 +where it wants to use a non-standard function fopen_s instead of the standard C +fopen. + +Visual Studio may want "stdafx.h" files to be included in each source file and +give an error "unexpected end of file while looking for precompiled header". +This is not standard C++ and will not be added to the stock LodePNG. You can +disable it for lodepng.cpp only by right clicking it, Properties, C/C++, +Precompiled Headers, and set it to Not Using Precompiled Headers there. + +NOTE: Modern versions of VS should be fully supported, but old versions, e.g. +VS6, are not guaranteed to work. + +*) Compilers on Macintosh + +LodePNG has been reported to work both with gcc and LLVM for Macintosh, both for +C and C++. + +*) Other Compilers + +If you encounter problems on any compilers, feel free to let me know and I may +try to fix it if the compiler is modern and standards compliant. + + +10. examples +------------ + +This decoder example shows the most basic usage of LodePNG. More complex +examples can be found on the LodePNG website. + +10.1. decoder C++ example +------------------------- + +#include "lodepng.h" +#include <iostream> + +int main(int argc, char *argv[]) { + const char* filename = argc > 1 ? argv[1] : "test.png"; + + //load and decode + std::vector<unsigned char> image; + unsigned width, height; + unsigned error = lodepng::decode(image, width, height, filename); + + //if there's an error, display it + if(error) std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl; + + //the pixels are now in the vector "image", 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ... +} + +10.2. decoder C example +----------------------- + +#include "lodepng.h" + +int main(int argc, char *argv[]) { + unsigned error; + unsigned char* image; + size_t width, height; + const char* filename = argc > 1 ? argv[1] : "test.png"; + + error = lodepng_decode32_file(&image, &width, &height, filename); + + if(error) printf("decoder error %u: %s\n", error, lodepng_error_text(error)); + + / * use image here * / + + free(image); + return 0; +} + +11. state settings reference +---------------------------- + +A quick reference of some settings to set on the LodePNGState + +For decoding: + +state.decoder.zlibsettings.ignore_adler32: ignore ADLER32 checksums +state.decoder.zlibsettings.custom_...: use custom inflate function +state.decoder.ignore_crc: ignore CRC checksums +state.decoder.ignore_critical: ignore unknown critical chunks +state.decoder.ignore_end: ignore missing IEND chunk. May fail if this corruption causes other errors +state.decoder.color_convert: convert internal PNG color to chosen one +state.decoder.read_text_chunks: whether to read in text metadata chunks +state.decoder.remember_unknown_chunks: whether to read in unknown chunks +state.info_raw.colortype: desired color type for decoded image +state.info_raw.bitdepth: desired bit depth for decoded image +state.info_raw....: more color settings, see struct LodePNGColorMode +state.info_png....: no settings for decoder but ouput, see struct LodePNGInfo + +For encoding: + +state.encoder.zlibsettings.btype: disable compression by setting it to 0 +state.encoder.zlibsettings.use_lz77: use LZ77 in compression +state.encoder.zlibsettings.windowsize: tweak LZ77 windowsize +state.encoder.zlibsettings.minmatch: tweak min LZ77 length to match +state.encoder.zlibsettings.nicematch: tweak LZ77 match where to stop searching +state.encoder.zlibsettings.lazymatching: try one more LZ77 matching +state.encoder.zlibsettings.custom_...: use custom deflate function +state.encoder.auto_convert: choose optimal PNG color type, if 0 uses info_png +state.encoder.filter_palette_zero: PNG filter strategy for palette +state.encoder.filter_strategy: PNG filter strategy to encode with +state.encoder.force_palette: add palette even if not encoding to one +state.encoder.add_id: add LodePNG identifier and version as a text chunk +state.encoder.text_compression: use compressed text chunks for metadata +state.info_raw.colortype: color type of raw input image you provide +state.info_raw.bitdepth: bit depth of raw input image you provide +state.info_raw: more color settings, see struct LodePNGColorMode +state.info_png.color.colortype: desired color type if auto_convert is false +state.info_png.color.bitdepth: desired bit depth if auto_convert is false +state.info_png.color....: more color settings, see struct LodePNGColorMode +state.info_png....: more PNG related settings, see struct LodePNGInfo + + +12. changes +----------- + +The version number of LodePNG is the date of the change given in the format +yyyymmdd. + +Some changes aren't backwards compatible. Those are indicated with a (!) +symbol. + +Not all changes are listed here, the commit history in github lists more: +https://github.com/lvandeve/lodepng + +*) 17 okt 2020: prevent decoding too large text/icc chunks by default. +*) 06 mar 2020: simplified some of the dynamic memory allocations. +*) 12 jan 2020: (!) added 'end' argument to lodepng_chunk_next to allow correct + overflow checks. +*) 14 aug 2019: around 25% faster decoding thanks to huffman lookup tables. +*) 15 jun 2019: (!) auto_choose_color API changed (for bugfix: don't use palette + if gray ICC profile) and non-ICC LodePNGColorProfile renamed to + LodePNGColorStats. +*) 30 dec 2018: code style changes only: removed newlines before opening braces. +*) 10 sep 2018: added way to inspect metadata chunks without full decoding. +*) 19 aug 2018: (!) fixed color mode bKGD is encoded with and made it use + palette index in case of palette. +*) 10 aug 2018: (!) added support for gAMA, cHRM, sRGB and iCCP chunks. This + change is backwards compatible unless you relied on unknown_chunks for those. +*) 11 jun 2018: less restrictive check for pixel size integer overflow +*) 14 jan 2018: allow optionally ignoring a few more recoverable errors +*) 17 sep 2017: fix memory leak for some encoder input error cases +*) 27 nov 2016: grey+alpha auto color model detection bugfix +*) 18 apr 2016: Changed qsort to custom stable sort (for platforms w/o qsort). +*) 09 apr 2016: Fixed colorkey usage detection, and better file loading (within + the limits of pure C90). +*) 08 dec 2015: Made load_file function return error if file can't be opened. +*) 24 okt 2015: Bugfix with decoding to palette output. +*) 18 apr 2015: Boundary PM instead of just package-merge for faster encoding. +*) 24 aug 2014: Moved to github +*) 23 aug 2014: Reduced needless memory usage of decoder. +*) 28 jun 2014: Removed fix_png setting, always support palette OOB for + simplicity. Made ColorProfile public. +*) 09 jun 2014: Faster encoder by fixing hash bug and more zeros optimization. +*) 22 dec 2013: Power of two windowsize required for optimization. +*) 15 apr 2013: Fixed bug with LAC_ALPHA and color key. +*) 25 mar 2013: Added an optional feature to ignore some PNG errors (fix_png). +*) 11 mar 2013: (!) Bugfix with custom free. Changed from "my" to "lodepng_" + prefix for the custom allocators and made it possible with a new #define to + use custom ones in your project without needing to change lodepng's code. +*) 28 jan 2013: Bugfix with color key. +*) 27 okt 2012: Tweaks in text chunk keyword length error handling. +*) 8 okt 2012: (!) Added new filter strategy (entropy) and new auto color mode. + (no palette). Better deflate tree encoding. New compression tweak settings. + Faster color conversions while decoding. Some internal cleanups. +*) 23 sep 2012: Reduced warnings in Visual Studio a little bit. +*) 1 sep 2012: (!) Removed #define's for giving custom (de)compression functions + and made it work with function pointers instead. +*) 23 jun 2012: Added more filter strategies. Made it easier to use custom alloc + and free functions and toggle #defines from compiler flags. Small fixes. +*) 6 may 2012: (!) Made plugging in custom zlib/deflate functions more flexible. +*) 22 apr 2012: (!) Made interface more consistent, renaming a lot. Removed + redundant C++ codec classes. Reduced amount of structs. Everything changed, + but it is cleaner now imho and functionality remains the same. Also fixed + several bugs and shrunk the implementation code. Made new samples. +*) 6 nov 2011: (!) By default, the encoder now automatically chooses the best + PNG color model and bit depth, based on the amount and type of colors of the + raw image. For this, autoLeaveOutAlphaChannel replaced by auto_choose_color. +*) 9 okt 2011: simpler hash chain implementation for the encoder. +*) 8 sep 2011: lz77 encoder lazy matching instead of greedy matching. +*) 23 aug 2011: tweaked the zlib compression parameters after benchmarking. + A bug with the PNG filtertype heuristic was fixed, so that it chooses much + better ones (it's quite significant). A setting to do an experimental, slow, + brute force search for PNG filter types is added. +*) 17 aug 2011: (!) changed some C zlib related function names. +*) 16 aug 2011: made the code less wide (max 120 characters per line). +*) 17 apr 2011: code cleanup. Bugfixes. Convert low to 16-bit per sample colors. +*) 21 feb 2011: fixed compiling for C90. Fixed compiling with sections disabled. +*) 11 dec 2010: encoding is made faster, based on suggestion by Peter Eastman + to optimize long sequences of zeros. +*) 13 nov 2010: added LodePNG_InfoColor_hasPaletteAlpha and + LodePNG_InfoColor_canHaveAlpha functions for convenience. +*) 7 nov 2010: added LodePNG_error_text function to get error code description. +*) 30 okt 2010: made decoding slightly faster +*) 26 okt 2010: (!) changed some C function and struct names (more consistent). + Reorganized the documentation and the declaration order in the header. +*) 08 aug 2010: only changed some comments and external samples. +*) 05 jul 2010: fixed bug thanks to warnings in the new gcc version. +*) 14 mar 2010: fixed bug where too much memory was allocated for char buffers. +*) 02 sep 2008: fixed bug where it could create empty tree that linux apps could + read by ignoring the problem but windows apps couldn't. +*) 06 jun 2008: added more error checks for out of memory cases. +*) 26 apr 2008: added a few more checks here and there to ensure more safety. +*) 06 mar 2008: crash with encoding of strings fixed +*) 02 feb 2008: support for international text chunks added (iTXt) +*) 23 jan 2008: small cleanups, and #defines to divide code in sections +*) 20 jan 2008: support for unknown chunks allowing using LodePNG for an editor. +*) 18 jan 2008: support for tIME and pHYs chunks added to encoder and decoder. +*) 17 jan 2008: ability to encode and decode compressed zTXt chunks added + Also various fixes, such as in the deflate and the padding bits code. +*) 13 jan 2008: Added ability to encode Adam7-interlaced images. Improved + filtering code of encoder. +*) 07 jan 2008: (!) changed LodePNG to use ISO C90 instead of C++. A + C++ wrapper around this provides an interface almost identical to before. + Having LodePNG be pure ISO C90 makes it more portable. The C and C++ code + are together in these files but it works both for C and C++ compilers. +*) 29 dec 2007: (!) changed most integer types to unsigned int + other tweaks +*) 30 aug 2007: bug fixed which makes this Borland C++ compatible +*) 09 aug 2007: some VS2005 warnings removed again +*) 21 jul 2007: deflate code placed in new namespace separate from zlib code +*) 08 jun 2007: fixed bug with 2- and 4-bit color, and small interlaced images +*) 04 jun 2007: improved support for Visual Studio 2005: crash with accessing + invalid std::vector element [0] fixed, and level 3 and 4 warnings removed +*) 02 jun 2007: made the encoder add a tag with version by default +*) 27 may 2007: zlib and png code separated (but still in the same file), + simple encoder/decoder functions added for more simple usage cases +*) 19 may 2007: minor fixes, some code cleaning, new error added (error 69), + moved some examples from here to lodepng_examples.cpp +*) 12 may 2007: palette decoding bug fixed +*) 24 apr 2007: changed the license from BSD to the zlib license +*) 11 mar 2007: very simple addition: ability to encode bKGD chunks. +*) 04 mar 2007: (!) tEXt chunk related fixes, and support for encoding + palettized PNG images. Plus little interface change with palette and texts. +*) 03 mar 2007: Made it encode dynamic Huffman shorter with repeat codes. + Fixed a bug where the end code of a block had length 0 in the Huffman tree. +*) 26 feb 2007: Huffman compression with dynamic trees (BTYPE 2) now implemented + and supported by the encoder, resulting in smaller PNGs at the output. +*) 27 jan 2007: Made the Adler-32 test faster so that a timewaste is gone. +*) 24 jan 2007: gave encoder an error interface. Added color conversion from any + greyscale type to 8-bit greyscale with or without alpha. +*) 21 jan 2007: (!) Totally changed the interface. It allows more color types + to convert to and is more uniform. See the manual for how it works now. +*) 07 jan 2007: Some cleanup & fixes, and a few changes over the last days: + encode/decode custom tEXt chunks, separate classes for zlib & deflate, and + at last made the decoder give errors for incorrect Adler32 or Crc. +*) 01 jan 2007: Fixed bug with encoding PNGs with less than 8 bits per channel. +*) 29 dec 2006: Added support for encoding images without alpha channel, and + cleaned out code as well as making certain parts faster. +*) 28 dec 2006: Added "Settings" to the encoder. +*) 26 dec 2006: The encoder now does LZ77 encoding and produces much smaller files now. + Removed some code duplication in the decoder. Fixed little bug in an example. +*) 09 dec 2006: (!) Placed output parameters of public functions as first parameter. + Fixed a bug of the decoder with 16-bit per color. +*) 15 okt 2006: Changed documentation structure +*) 09 okt 2006: Encoder class added. It encodes a valid PNG image from the + given image buffer, however for now it's not compressed. +*) 08 sep 2006: (!) Changed to interface with a Decoder class +*) 30 jul 2006: (!) LodePNG_InfoPng , width and height are now retrieved in different + way. Renamed decodePNG to decodePNGGeneric. +*) 29 jul 2006: (!) Changed the interface: image info is now returned as a + struct of type LodePNG::LodePNG_Info, instead of a vector, which was a bit clumsy. +*) 28 jul 2006: Cleaned the code and added new error checks. + Corrected terminology "deflate" into "inflate". +*) 23 jun 2006: Added SDL example in the documentation in the header, this + example allows easy debugging by displaying the PNG and its transparency. +*) 22 jun 2006: (!) Changed way to obtain error value. Added + loadFile function for convenience. Made decodePNG32 faster. +*) 21 jun 2006: (!) Changed type of info vector to unsigned. + Changed position of palette in info vector. Fixed an important bug that + happened on PNGs with an uncompressed block. +*) 16 jun 2006: Internally changed unsigned into unsigned where + needed, and performed some optimizations. +*) 07 jun 2006: (!) Renamed functions to decodePNG and placed them + in LodePNG namespace. Changed the order of the parameters. Rewrote the + documentation in the header. Renamed files to lodepng.cpp and lodepng.h +*) 22 apr 2006: Optimized and improved some code +*) 07 sep 2005: (!) Changed to std::vector interface +*) 12 aug 2005: Initial release (C++, decoder only) + + +13. contact information +----------------------- + +Feel free to contact me with suggestions, problems, comments, ... concerning +LodePNG. If you encounter a PNG image that doesn't work properly with this +decoder, feel free to send it and I'll use it to find and fix the problem. + +My email address is (puzzle the account and domain together with an @ symbol): +Domain: gmail dot com. +Account: lode dot vandevenne. + + +Copyright (c) 2005-2020 Lode Vandevenne +*/ diff --git a/lib/lvgl/src/extra/libs/png/lv_png.c b/lib/lvgl/src/extra/libs/png/lv_png.c new file mode 100644 index 00000000..d067ef50 --- /dev/null +++ b/lib/lvgl/src/extra/libs/png/lv_png.c @@ -0,0 +1,258 @@ +/** + * @file lv_png.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" +#if LV_USE_PNG + +#include "lv_png.h" +#include "lodepng.h" +#include <stdlib.h> + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static lv_res_t decoder_info(struct _lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header); +static lv_res_t decoder_open(lv_img_decoder_t * dec, lv_img_decoder_dsc_t * dsc); +static void decoder_close(lv_img_decoder_t * dec, lv_img_decoder_dsc_t * dsc); +static void convert_color_depth(uint8_t * img, uint32_t px_cnt); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Register the PNG decoder functions in LVGL + */ +void lv_png_init(void) +{ + lv_img_decoder_t * dec = lv_img_decoder_create(); + lv_img_decoder_set_info_cb(dec, decoder_info); + lv_img_decoder_set_open_cb(dec, decoder_open); + lv_img_decoder_set_close_cb(dec, decoder_close); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/** + * Get info about a PNG image + * @param src can be file name or pointer to a C array + * @param header store the info here + * @return LV_RES_OK: no error; LV_RES_INV: can't get the info + */ +static lv_res_t decoder_info(struct _lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header) +{ + (void) decoder; /*Unused*/ + lv_img_src_t src_type = lv_img_src_get_type(src); /*Get the source type*/ + + /*If it's a PNG file...*/ + if(src_type == LV_IMG_SRC_FILE) { + const char * fn = src; + if(strcmp(lv_fs_get_ext(fn), "png") == 0) { /*Check the extension*/ + + /* Read the width and height from the file. They have a constant location: + * [16..23]: width + * [24..27]: height + */ + uint32_t size[2]; + lv_fs_file_t f; + lv_fs_res_t res = lv_fs_open(&f, fn, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) return LV_RES_INV; + + lv_fs_seek(&f, 16, LV_FS_SEEK_SET); + + uint32_t rn; + lv_fs_read(&f, &size, 8, &rn); + lv_fs_close(&f); + + if(rn != 8) return LV_RES_INV; + + /*Save the data in the header*/ + header->always_zero = 0; + header->cf = LV_IMG_CF_TRUE_COLOR_ALPHA; + /*The width and height are stored in Big endian format so convert them to little endian*/ + header->w = (lv_coord_t)((size[0] & 0xff000000) >> 24) + ((size[0] & 0x00ff0000) >> 8); + header->h = (lv_coord_t)((size[1] & 0xff000000) >> 24) + ((size[1] & 0x00ff0000) >> 8); + + return LV_RES_OK; + } + } + /*If it's a PNG file in a C array...*/ + else if(src_type == LV_IMG_SRC_VARIABLE) { + const lv_img_dsc_t * img_dsc = src; + const uint32_t data_size = img_dsc->data_size; + const uint8_t magic[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}; + if(data_size < sizeof(magic)) return LV_RES_INV; + if(memcmp(magic, img_dsc->data, sizeof(magic))) return LV_RES_INV; + header->always_zero = 0; + header->cf = img_dsc->header.cf; /*Save the color format*/ + header->w = img_dsc->header.w; /*Save the color width*/ + header->h = img_dsc->header.h; /*Save the color height*/ + return LV_RES_OK; + } + + return LV_RES_INV; /*If didn't succeeded earlier then it's an error*/ +} + + +/** + * Open a PNG image and return the decided image + * @param src can be file name or pointer to a C array + * @param style style of the image object (unused now but certain formats might use it) + * @return pointer to the decoded image or `LV_IMG_DECODER_OPEN_FAIL` if failed + */ +static lv_res_t decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc) +{ + + (void) decoder; /*Unused*/ + uint32_t error; /*For the return values of PNG decoder functions*/ + + uint8_t * img_data = NULL; + + /*If it's a PNG file...*/ + if(dsc->src_type == LV_IMG_SRC_FILE) { + const char * fn = dsc->src; + if(strcmp(lv_fs_get_ext(fn), "png") == 0) { /*Check the extension*/ + + /*Load the PNG file into buffer. It's still compressed (not decoded)*/ + unsigned char * png_data; /*Pointer to the loaded data. Same as the original file just loaded into the RAM*/ + size_t png_data_size; /*Size of `png_data` in bytes*/ + + error = lodepng_load_file(&png_data, &png_data_size, fn); /*Load the file*/ + if(error) { + LV_LOG_WARN("error %u: %s\n", error, lodepng_error_text(error)); + return LV_RES_INV; + } + + /*Decode the PNG image*/ + uint32_t png_width; /*Will be the width of the decoded image*/ + uint32_t png_height; /*Will be the width of the decoded image*/ + + /*Decode the loaded image in ARGB8888 */ + error = lodepng_decode32(&img_data, &png_width, &png_height, png_data, png_data_size); + lv_mem_free(png_data); /*Free the loaded file*/ + if(error) { + if(img_data != NULL) { + lv_mem_free(img_data); + } + LV_LOG_WARN("error %u: %s\n", error, lodepng_error_text(error)); + return LV_RES_INV; + } + + /*Convert the image to the system's color depth*/ + convert_color_depth(img_data, png_width * png_height); + dsc->img_data = img_data; + return LV_RES_OK; /*The image is fully decoded. Return with its pointer*/ + } + } + /*If it's a PNG file in a C array...*/ + else if(dsc->src_type == LV_IMG_SRC_VARIABLE) { + const lv_img_dsc_t * img_dsc = dsc->src; + uint32_t png_width; /*No used, just required by he decoder*/ + uint32_t png_height; /*No used, just required by he decoder*/ + + /*Decode the image in ARGB8888 */ + error = lodepng_decode32(&img_data, &png_width, &png_height, img_dsc->data, img_dsc->data_size); + + if(error) { + if(img_data != NULL) { + lv_mem_free(img_data); + } + return LV_RES_INV; + } + + /*Convert the image to the system's color depth*/ + convert_color_depth(img_data, png_width * png_height); + + dsc->img_data = img_data; + return LV_RES_OK; /*Return with its pointer*/ + } + + return LV_RES_INV; /*If not returned earlier then it failed*/ +} + +/** + * Free the allocated resources + */ +static void decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc) +{ + LV_UNUSED(decoder); /*Unused*/ + if(dsc->img_data) { + lv_mem_free((uint8_t *)dsc->img_data); + dsc->img_data = NULL; + } +} + +/** + * If the display is not in 32 bit format (ARGB888) then covert the image to the current color depth + * @param img the ARGB888 image + * @param px_cnt number of pixels in `img` + */ +static void convert_color_depth(uint8_t * img, uint32_t px_cnt) +{ +#if LV_COLOR_DEPTH == 32 + lv_color32_t * img_argb = (lv_color32_t *)img; + lv_color_t c; + lv_color_t * img_c = (lv_color_t *) img; + uint32_t i; + for(i = 0; i < px_cnt; i++) { + c = lv_color_make(img_argb[i].ch.red, img_argb[i].ch.green, img_argb[i].ch.blue); + img_c[i].ch.red = c.ch.blue; + img_c[i].ch.blue = c.ch.red; + } +#elif LV_COLOR_DEPTH == 16 + lv_color32_t * img_argb = (lv_color32_t *)img; + lv_color_t c; + uint32_t i; + for(i = 0; i < px_cnt; i++) { + c = lv_color_make(img_argb[i].ch.blue, img_argb[i].ch.green, img_argb[i].ch.red); + img[i * 3 + 2] = img_argb[i].ch.alpha; + img[i * 3 + 1] = c.full >> 8; + img[i * 3 + 0] = c.full & 0xFF; + } +#elif LV_COLOR_DEPTH == 8 + lv_color32_t * img_argb = (lv_color32_t *)img; + lv_color_t c; + uint32_t i; + for(i = 0; i < px_cnt; i++) { + c = lv_color_make(img_argb[i].ch.red, img_argb[i].ch.green, img_argb[i].ch.blue); + img[i * 2 + 1] = img_argb[i].ch.alpha; + img[i * 2 + 0] = c.full; + } +#elif LV_COLOR_DEPTH == 1 + lv_color32_t * img_argb = (lv_color32_t *)img; + uint8_t b; + uint32_t i; + for(i = 0; i < px_cnt; i++) { + b = img_argb[i].ch.red | img_argb[i].ch.green | img_argb[i].ch.blue; + img[i * 2 + 1] = img_argb[i].ch.alpha; + img[i * 2 + 0] = b > 128 ? 1 : 0; + } +#endif +} + +#endif /*LV_USE_PNG*/ + + diff --git a/lib/lvgl/src/extra/libs/png/lv_png.h b/lib/lvgl/src/extra/libs/png/lv_png.h new file mode 100644 index 00000000..43804723 --- /dev/null +++ b/lib/lvgl/src/extra/libs/png/lv_png.h @@ -0,0 +1,46 @@ +/** + * @file lv_png.h + * + */ + +#ifndef LV_PNG_H +#define LV_PNG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lv_conf_internal.h" +#if LV_USE_PNG + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Register the PNG decoder functions in LVGL + */ +void lv_png_init(void); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_PNG*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_PNG_H*/ diff --git a/lib/lvgl/src/extra/libs/qrcode/lv_qrcode.c b/lib/lvgl/src/extra/libs/qrcode/lv_qrcode.c new file mode 100644 index 00000000..079873e1 --- /dev/null +++ b/lib/lvgl/src/extra/libs/qrcode/lv_qrcode.c @@ -0,0 +1,215 @@ +/** + * @file lv_qrcode.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_qrcode.h" +#if LV_USE_QRCODE + +#include "qrcodegen.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_qrcode_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_qrcode_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_qrcode_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ + +const lv_obj_class_t lv_qrcode_class = { + .constructor_cb = lv_qrcode_constructor, + .destructor_cb = lv_qrcode_destructor, + .base_class = &lv_canvas_class +}; + +static lv_coord_t size_param; +static lv_color_t dark_color_param; +static lv_color_t light_color_param; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create an empty QR code (an `lv_canvas`) object. + * @param parent point to an object where to create the QR code + * @param size width and height of the QR code + * @param dark_color dark color of the QR code + * @param light_color light color of the QR code + * @return pointer to the created QR code object + */ +lv_obj_t * lv_qrcode_create(lv_obj_t * parent, lv_coord_t size, lv_color_t dark_color, lv_color_t light_color) +{ + LV_LOG_INFO("begin"); + size_param = size; + light_color_param = light_color; + dark_color_param = dark_color; + + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; + +} + +/** + * Set the data of a QR code object + * @param qrcode pointer to aQ code object + * @param data data to display + * @param data_len length of data in bytes + * @return LV_RES_OK: if no error; LV_RES_INV: on error + */ +lv_res_t lv_qrcode_update(lv_obj_t * qrcode, const void * data, uint32_t data_len) +{ + lv_color_t c; + c.full = 1; + lv_canvas_fill_bg(qrcode, c, LV_OPA_COVER); + + if(data_len > qrcodegen_BUFFER_LEN_MAX) return LV_RES_INV; + + lv_img_dsc_t * imgdsc = lv_canvas_get_img(qrcode); + + int32_t qr_version = qrcodegen_getMinFitVersion(qrcodegen_Ecc_MEDIUM, data_len); + if(qr_version <= 0) return LV_RES_INV; + int32_t qr_size = qrcodegen_version2size(qr_version); + if(qr_size <= 0) return LV_RES_INV; + int32_t scale = imgdsc->header.w / qr_size; + if(scale <= 0) return LV_RES_INV; + int32_t remain = imgdsc->header.w % qr_size; + + /* The qr version is incremented by four point */ + uint32_t version_extend = remain / (scale << 2); + if(version_extend && qr_version < qrcodegen_VERSION_MAX) { + qr_version = qr_version + version_extend > qrcodegen_VERSION_MAX ? + qrcodegen_VERSION_MAX : qr_version + version_extend; + } + + uint8_t * qr0 = lv_mem_alloc(qrcodegen_BUFFER_LEN_FOR_VERSION(qr_version)); + LV_ASSERT_MALLOC(qr0); + uint8_t * data_tmp = lv_mem_alloc(qrcodegen_BUFFER_LEN_FOR_VERSION(qr_version)); + LV_ASSERT_MALLOC(data_tmp); + lv_memcpy(data_tmp, data, data_len); + + bool ok = qrcodegen_encodeBinary(data_tmp, data_len, + qr0, qrcodegen_Ecc_MEDIUM, + qr_version, qr_version, + qrcodegen_Mask_AUTO, true); + + if(!ok) { + lv_mem_free(qr0); + lv_mem_free(data_tmp); + return LV_RES_INV; + } + + lv_coord_t obj_w = imgdsc->header.w; + qr_size = qrcodegen_getSize(qr0); + scale = obj_w / qr_size; + int scaled = qr_size * scale; + int margin = (obj_w - scaled) / 2; + uint8_t * buf_u8 = (uint8_t *)imgdsc->data + 8; /*+8 skip the palette*/ + + /* Copy the qr code canvas: + * A simple `lv_canvas_set_px` would work but it's slow for so many pixels. + * So buffer 1 byte (8 px) from the qr code and set it in the canvas image */ + uint32_t row_byte_cnt = (imgdsc->header.w + 7) >> 3; + int y; + for(y = margin; y < scaled + margin; y += scale) { + uint8_t b = 0; + uint8_t p = 0; + bool aligned = false; + int x; + for(x = margin; x < scaled + margin; x++) { + bool a = qrcodegen_getModule(qr0, (x - margin) / scale, (y - margin) / scale); + + if(aligned == false && (x & 0x7) == 0) aligned = true; + + if(aligned == false) { + c.full = a ? 0 : 1; + lv_canvas_set_px_color(qrcode, x, y, c); + } + else { + if(!a) b |= (1 << (7 - p)); + p++; + if(p == 8) { + uint32_t px = row_byte_cnt * y + (x >> 3); + buf_u8[px] = b; + b = 0; + p = 0; + } + } + } + + /*Process the last byte of the row*/ + if(p) { + /*Make the rest of the bits white*/ + b |= (1 << (8 - p)) - 1; + + uint32_t px = row_byte_cnt * y + (x >> 3); + buf_u8[px] = b; + } + + /*The Qr is probably scaled so simply to the repeated rows*/ + int s; + const uint8_t * row_ori = buf_u8 + row_byte_cnt * y; + for(s = 1; s < scale; s++) { + lv_memcpy((uint8_t *)buf_u8 + row_byte_cnt * (y + s), row_ori, row_byte_cnt); + } + } + + lv_mem_free(qr0); + lv_mem_free(data_tmp); + return LV_RES_OK; +} + + +void lv_qrcode_delete(lv_obj_t * qrcode) +{ + lv_obj_del(qrcode); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_qrcode_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + + uint32_t buf_size = LV_CANVAS_BUF_SIZE_INDEXED_1BIT(size_param, size_param); + uint8_t * buf = lv_mem_alloc(buf_size); + LV_ASSERT_MALLOC(buf); + if(buf == NULL) return; + + lv_canvas_set_buffer(obj, buf, size_param, size_param, LV_IMG_CF_INDEXED_1BIT); + lv_canvas_set_palette(obj, 0, dark_color_param); + lv_canvas_set_palette(obj, 1, light_color_param); +} + +static void lv_qrcode_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + + lv_img_dsc_t * img = lv_canvas_get_img(obj); + lv_img_cache_invalidate_src(img); + lv_mem_free((void *)img->data); + img->data = NULL; +} + +#endif /*LV_USE_QRCODE*/ diff --git a/lib/lvgl/src/extra/libs/qrcode/lv_qrcode.h b/lib/lvgl/src/extra/libs/qrcode/lv_qrcode.h new file mode 100644 index 00000000..b0752ace --- /dev/null +++ b/lib/lvgl/src/extra/libs/qrcode/lv_qrcode.h @@ -0,0 +1,69 @@ +/** + * @file lv_qrcode + * + */ + +#ifndef LV_QRCODE_H +#define LV_QRCODE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" +#if LV_USE_QRCODE + +/********************* + * DEFINES + *********************/ + +extern const lv_obj_class_t lv_qrcode_class; + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create an empty QR code (an `lv_canvas`) object. + * @param parent point to an object where to create the QR code + * @param size width and height of the QR code + * @param dark_color dark color of the QR code + * @param light_color light color of the QR code + * @return pointer to the created QR code object + */ +lv_obj_t * lv_qrcode_create(lv_obj_t * parent, lv_coord_t size, lv_color_t dark_color, lv_color_t light_color); + +/** + * Set the data of a QR code object + * @param qrcode pointer to aQ code object + * @param data data to display + * @param data_len length of data in bytes + * @return LV_RES_OK: if no error; LV_RES_INV: on error + */ +lv_res_t lv_qrcode_update(lv_obj_t * qrcode, const void * data, uint32_t data_len); + +/** + * DEPRECATED: Use normal lv_obj_del instead + * Delete a QR code object + * @param qrcode pointer to a QR code object + */ +void lv_qrcode_delete(lv_obj_t * qrcode); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_QRCODE*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_QRCODE_H*/ diff --git a/lib/lvgl/src/extra/libs/qrcode/qrcodegen.c b/lib/lvgl/src/extra/libs/qrcode/qrcodegen.c new file mode 100644 index 00000000..37ee7423 --- /dev/null +++ b/lib/lvgl/src/extra/libs/qrcode/qrcodegen.c @@ -0,0 +1,1035 @@ +/* + * QR Code generator library (C) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#include <assert.h> +#include <limits.h> +#include <stdlib.h> +#include <string.h> +#include "qrcodegen.h" + +#ifndef QRCODEGEN_TEST + #define testable static // Keep functions private +#else + #define testable // Expose private functions +#endif + + +/*---- Forward declarations for private functions ----*/ + +// Regarding all public and private functions defined in this source file: +// - They require all pointer/array arguments to be not null unless the array length is zero. +// - They only read input scalar/array arguments, write to output pointer/array +// arguments, and return scalar values; they are "pure" functions. +// - They don't read mutable global variables or write to any global variables. +// - They don't perform I/O, read the clock, print to console, etc. +// - They allocate a small and constant amount of stack memory. +// - They don't allocate or free any memory on the heap. +// - They don't recurse or mutually recurse. All the code +// could be inlined into the top-level public functions. +// - They run in at most quadratic time with respect to input arguments. +// Most functions run in linear time, and some in constant time. +// There are no unbounded loops or non-obvious termination conditions. +// - They are completely thread-safe if the caller does not give the +// same writable buffer to concurrent calls to these functions. + +testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen); + +testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]); +testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl); +testable int getNumRawDataModules(int ver); + +testable void calcReedSolomonGenerator(int degree, uint8_t result[]); +testable void calcReedSolomonRemainder(const uint8_t data[], int dataLen, + const uint8_t generator[], int degree, uint8_t result[]); +testable uint8_t finiteFieldMultiply(uint8_t x, uint8_t y); + +testable void initializeFunctionModules(int version, uint8_t qrcode[]); +static void drawWhiteFunctionModules(uint8_t qrcode[], int version); +static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]); +testable int getAlignmentPatternPositions(int version, uint8_t result[7]); +static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]); + +static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]); +static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask); +static long getPenaltyScore(const uint8_t qrcode[]); +static void addRunToHistory(unsigned char run, unsigned char history[7]); +static bool hasFinderLikePattern(const unsigned char runHistory[7]); + +testable bool getModule(const uint8_t qrcode[], int x, int y); +testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack); +testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack); +static bool getBit(int x, int i); + +testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars); +testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version); +static int numCharCountBits(enum qrcodegen_Mode mode, int version); + + + +/*---- Private tables of constants ----*/ + +// The set of all legal characters in alphanumeric mode, where each character +// value maps to the index in the string. For checking text and encoding segments. +static const char *ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; + +// For generating error correction codes. +testable const int8_t ECC_CODEWORDS_PER_BLOCK[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low + {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium + {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile + {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High +}; + +#define qrcodegen_REED_SOLOMON_DEGREE_MAX 30 // Based on the table above + +// For generating error correction codes. +testable const int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41] = { + // Version: (note that index 0 is for padding, and is set to an illegal value) + //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level + {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low + {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium + {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile + {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High +}; + +// For automatic mask pattern selection. +static const int PENALTY_N1 = 3; +static const int PENALTY_N2 = 3; +static const int PENALTY_N3 = 40; +static const int PENALTY_N4 = 10; + + + +/*---- High-level QR Code encoding functions ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { + + size_t textLen = strlen(text); + if (textLen == 0) + return qrcodegen_encodeSegmentsAdvanced(NULL, 0, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); + size_t bufLen = qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion); + + struct qrcodegen_Segment seg; + if (qrcodegen_isNumeric(text)) { + if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_NUMERIC, textLen) > bufLen) + goto fail; + seg = qrcodegen_makeNumeric(text, tempBuffer); + } else if (qrcodegen_isAlphanumeric(text)) { + if (qrcodegen_calcSegmentBufferSize(qrcodegen_Mode_ALPHANUMERIC, textLen) > bufLen) + goto fail; + seg = qrcodegen_makeAlphanumeric(text, tempBuffer); + } else { + if (textLen > bufLen) + goto fail; + for (size_t i = 0; i < textLen; i++) + tempBuffer[i] = (uint8_t)text[i]; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, textLen); + if (seg.bitLength == -1) + goto fail; + seg.numChars = (int)textLen; + seg.data = tempBuffer; + } + return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, tempBuffer, qrcode); + +fail: + qrcode[0] = 0; // Set size to invalid value for safety + return false; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl) { + + struct qrcodegen_Segment seg; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, dataLen); + if (seg.bitLength == -1) { + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + seg.numChars = (int)dataLen; + seg.data = dataAndTemp; + return qrcodegen_encodeSegmentsAdvanced(&seg, 1, ecl, minVersion, maxVersion, mask, boostEcl, dataAndTemp, qrcode); +} + + +// Appends the given number of low-order bits of the given value to the given byte-based +// bit buffer, increasing the bit length. Requires 0 <= numBits <= 16 and val < 2^numBits. +testable void appendBitsToBuffer(unsigned int val, int numBits, uint8_t buffer[], int *bitLen) { + assert(0 <= numBits && numBits <= 16 && (unsigned long)val >> numBits == 0); + for (int i = numBits - 1; i >= 0; i--, (*bitLen)++) + buffer[*bitLen >> 3] |= ((val >> i) & 1) << (7 - (*bitLen & 7)); +} + + + +/*---- Low-level QR Code encoding functions ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]) { + return qrcodegen_encodeSegmentsAdvanced(segs, len, ecl, + qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, -1, true, tempBuffer, qrcode); +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, int mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]) { + assert(segs != NULL || len == 0); + assert(qrcodegen_VERSION_MIN <= minVersion && minVersion <= maxVersion && maxVersion <= qrcodegen_VERSION_MAX); + assert(0 <= (int)ecl && (int)ecl <= 3 && -1 <= (int)mask && (int)mask <= 7); + + // Find the minimal version number to use + int version, dataUsedBits; + for (version = minVersion; ; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + dataUsedBits = getTotalBits(segs, len, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + break; // This version number is found to be suitable + if (version >= maxVersion) { // All versions in the range could not fit the given data + qrcode[0] = 0; // Set size to invalid value for safety + return false; + } + } + assert(dataUsedBits != -1); + + // Increase the error correction level while the data still fits in the current version number + for (int i = (int)qrcodegen_Ecc_MEDIUM; i <= (int)qrcodegen_Ecc_HIGH; i++) { // From low to high + if (boostEcl && dataUsedBits <= getNumDataCodewords(version, (enum qrcodegen_Ecc)i) * 8) + ecl = (enum qrcodegen_Ecc)i; + } + + // Concatenate all segments to create the data bit string + memset(qrcode, 0, qrcodegen_BUFFER_LEN_FOR_VERSION(version) * sizeof(qrcode[0])); + int bitLen = 0; + for (size_t i = 0; i < len; i++) { + const struct qrcodegen_Segment *seg = &segs[i]; + appendBitsToBuffer((int)seg->mode, 4, qrcode, &bitLen); + appendBitsToBuffer(seg->numChars, numCharCountBits(seg->mode, version), qrcode, &bitLen); + for (int j = 0; j < seg->bitLength; j++) + appendBitsToBuffer((seg->data[j >> 3] >> (7 - (j & 7))) & 1, 1, qrcode, &bitLen); + } + assert(bitLen == dataUsedBits); + + // Add terminator and pad up to a byte if applicable + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; + assert(bitLen <= dataCapacityBits); + int terminatorBits = dataCapacityBits - bitLen; + if (terminatorBits > 4) + terminatorBits = 4; + appendBitsToBuffer(0, terminatorBits, qrcode, &bitLen); + appendBitsToBuffer(0, (8 - bitLen % 8) % 8, qrcode, &bitLen); + assert(bitLen % 8 == 0); + + // Pad with alternating bytes until data capacity is reached + for (uint8_t padByte = 0xEC; bitLen < dataCapacityBits; padByte ^= 0xEC ^ 0x11) + appendBitsToBuffer(padByte, 8, qrcode, &bitLen); + + // Draw function and data codeword modules + addEccAndInterleave(qrcode, version, ecl, tempBuffer); + initializeFunctionModules(version, qrcode); + drawCodewords(tempBuffer, getNumRawDataModules(version) / 8, qrcode); + drawWhiteFunctionModules(qrcode, version); + initializeFunctionModules(version, tempBuffer); + + // Handle masking + if (mask == qrcodegen_Mask_AUTO) { // Automatically choose best mask + long minPenalty = LONG_MAX; + for (int i = 0; i < 8; i++) { + enum qrcodegen_Mask msk = (enum qrcodegen_Mask)i; + applyMask(tempBuffer, qrcode, msk); + drawFormatBits(ecl, msk, qrcode); + long penalty = getPenaltyScore(qrcode); + if (penalty < minPenalty) { + mask = msk; + minPenalty = penalty; + } + applyMask(tempBuffer, qrcode, msk); // Undoes the mask due to XOR + } + } + assert(0 <= (int)mask && (int)mask <= 7); + applyMask(tempBuffer, qrcode, mask); + drawFormatBits(ecl, mask, qrcode); + return true; +} + + + +/*---- Error correction code generation functions ----*/ + +// Appends error correction bytes to each block of the given data array, then interleaves +// bytes from the blocks and stores them in the result array. data[0 : dataLen] contains +// the input data. data[dataLen : rawCodewords] is used as a temporary work area and will +// be clobbered by this function. The final answer is stored in result[0 : rawCodewords]. +testable void addEccAndInterleave(uint8_t data[], int version, enum qrcodegen_Ecc ecl, uint8_t result[]) { + // Calculate parameter numbers + assert(0 <= (int)ecl && (int)ecl < 4 && qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[(int)ecl][version]; + int blockEccLen = ECC_CODEWORDS_PER_BLOCK [(int)ecl][version]; + int rawCodewords = getNumRawDataModules(version) / 8; + int dataLen = getNumDataCodewords(version, ecl); + int numShortBlocks = numBlocks - rawCodewords % numBlocks; + int shortBlockDataLen = rawCodewords / numBlocks - blockEccLen; + + // Split data into blocks, calculate ECC, and interleave + // (not concatenate) the bytes into a single sequence + uint8_t generator[qrcodegen_REED_SOLOMON_DEGREE_MAX]; + calcReedSolomonGenerator(blockEccLen, generator); + const uint8_t *dat = data; + for (int i = 0; i < numBlocks; i++) { + int datLen = shortBlockDataLen + (i < numShortBlocks ? 0 : 1); + uint8_t *ecc = &data[dataLen]; // Temporary storage + calcReedSolomonRemainder(dat, datLen, generator, blockEccLen, ecc); + for (int j = 0, k = i; j < datLen; j++, k += numBlocks) { // Copy data + if (j == shortBlockDataLen) + k -= numShortBlocks; + result[k] = dat[j]; + } + for (int j = 0, k = dataLen + i; j < blockEccLen; j++, k += numBlocks) // Copy ECC + result[k] = ecc[j]; + dat += datLen; + } +} + + +// Returns the number of 8-bit codewords that can be used for storing data (not ECC), +// for the given version number and error correction level. The result is in the range [9, 2956]. +testable int getNumDataCodewords(int version, enum qrcodegen_Ecc ecl) { + int v = version, e = (int)ecl; + assert(0 <= e && e < 4); + return getNumRawDataModules(v) / 8 + - ECC_CODEWORDS_PER_BLOCK [e][v] + * NUM_ERROR_CORRECTION_BLOCKS[e][v]; +} + + +// Returns the number of data bits that can be stored in a QR Code of the given version number, after +// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. +// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. +testable int getNumRawDataModules(int ver) { + assert(qrcodegen_VERSION_MIN <= ver && ver <= qrcodegen_VERSION_MAX); + int result = (16 * ver + 128) * ver + 64; + if (ver >= 2) { + int numAlign = ver / 7 + 2; + result -= (25 * numAlign - 10) * numAlign - 55; + if (ver >= 7) + result -= 36; + } + return result; +} + + + +/*---- Reed-Solomon ECC generator functions ----*/ + +// Calculates the Reed-Solomon generator polynomial of the given degree, storing in result[0 : degree]. +testable void calcReedSolomonGenerator(int degree, uint8_t result[]) { + // Start with the monomial x^0 + assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + memset(result, 0, degree * sizeof(result[0])); + result[degree - 1] = 1; + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // drop the highest term, and store the rest of the coefficients in order of descending powers. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + uint8_t root = 1; + for (int i = 0; i < degree; i++) { + // Multiply the current product by (x - r^i) + for (int j = 0; j < degree; j++) { + result[j] = finiteFieldMultiply(result[j], root); + if (j + 1 < degree) + result[j] ^= result[j + 1]; + } + root = finiteFieldMultiply(root, 0x02); + } +} + + +// Calculates the remainder of the polynomial data[0 : dataLen] when divided by the generator[0 : degree], where all +// polynomials are in big endian and the generator has an implicit leading 1 term, storing the result in result[0 : degree]. +testable void calcReedSolomonRemainder(const uint8_t data[], int dataLen, + const uint8_t generator[], int degree, uint8_t result[]) { + + // Perform polynomial division + assert(1 <= degree && degree <= qrcodegen_REED_SOLOMON_DEGREE_MAX); + memset(result, 0, degree * sizeof(result[0])); + for (int i = 0; i < dataLen; i++) { + uint8_t factor = data[i] ^ result[0]; + memmove(&result[0], &result[1], (degree - 1) * sizeof(result[0])); + result[degree - 1] = 0; + for (int j = 0; j < degree; j++) + result[j] ^= finiteFieldMultiply(generator[j], factor); + } +} + +#undef qrcodegen_REED_SOLOMON_DEGREE_MAX + + +// Returns the product of the two given field elements modulo GF(2^8/0x11D). +// All inputs are valid. This could be implemented as a 256*256 lookup table. +testable uint8_t finiteFieldMultiply(uint8_t x, uint8_t y) { + // Russian peasant multiplication + uint8_t z = 0; + for (int i = 7; i >= 0; i--) { + z = (z << 1) ^ ((z >> 7) * 0x11D); + z ^= ((y >> i) & 1) * x; + } + return z; +} + + + +/*---- Drawing function modules ----*/ + +// Clears the given QR Code grid with white modules for the given +// version's size, then marks every function module as black. +testable void initializeFunctionModules(int version, uint8_t qrcode[]) { + // Initialize QR Code + int qrsize = version * 4 + 17; + memset(qrcode, 0, ((qrsize * qrsize + 7) / 8 + 1) * sizeof(qrcode[0])); + qrcode[0] = (uint8_t)qrsize; + + // Fill horizontal and vertical timing patterns + fillRectangle(6, 0, 1, qrsize, qrcode); + fillRectangle(0, 6, qrsize, 1, qrcode); + + // Fill 3 finder patterns (all corners except bottom right) and format bits + fillRectangle(0, 0, 9, 9, qrcode); + fillRectangle(qrsize - 8, 0, 8, 9, qrcode); + fillRectangle(0, qrsize - 8, 9, 8, qrcode); + + // Fill numerous alignment patterns + uint8_t alignPatPos[7]; + int numAlign = getAlignmentPatternPositions(version, alignPatPos); + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + // Don't draw on the three finder corners + if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) + fillRectangle(alignPatPos[i] - 2, alignPatPos[j] - 2, 5, 5, qrcode); + } + } + + // Fill version blocks + if (version >= 7) { + fillRectangle(qrsize - 11, 0, 3, 6, qrcode); + fillRectangle(0, qrsize - 11, 6, 3, qrcode); + } +} + + +// Draws white function modules and possibly some black modules onto the given QR Code, without changing +// non-function modules. This does not draw the format bits. This requires all function modules to be previously +// marked black (namely by initializeFunctionModules()), because this may skip redrawing black function modules. +static void drawWhiteFunctionModules(uint8_t qrcode[], int version) { + // Draw horizontal and vertical timing patterns + int qrsize = qrcodegen_getSize(qrcode); + for (int i = 7; i < qrsize - 7; i += 2) { + setModule(qrcode, 6, i, false); + setModule(qrcode, i, 6, false); + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + for (int dy = -4; dy <= 4; dy++) { + for (int dx = -4; dx <= 4; dx++) { + int dist = abs(dx); + if (abs(dy) > dist) + dist = abs(dy); + if (dist == 2 || dist == 4) { + setModuleBounded(qrcode, 3 + dx, 3 + dy, false); + setModuleBounded(qrcode, qrsize - 4 + dx, 3 + dy, false); + setModuleBounded(qrcode, 3 + dx, qrsize - 4 + dy, false); + } + } + } + + // Draw numerous alignment patterns + uint8_t alignPatPos[7]; + int numAlign = getAlignmentPatternPositions(version, alignPatPos); + for (int i = 0; i < numAlign; i++) { + for (int j = 0; j < numAlign; j++) { + if ((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0)) + continue; // Don't draw on the three finder corners + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) + setModule(qrcode, alignPatPos[i] + dx, alignPatPos[j] + dy, dx == 0 && dy == 0); + } + } + } + + // Draw version blocks + if (version >= 7) { + // Calculate error correction code and pack bits + int rem = version; // version is uint6, in the range [7, 40] + for (int i = 0; i < 12; i++) + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); + long bits = (long)version << 12 | rem; // uint18 + assert(bits >> 18 == 0); + + // Draw two copies + for (int i = 0; i < 6; i++) { + for (int j = 0; j < 3; j++) { + int k = qrsize - 11 + j; + setModule(qrcode, k, i, (bits & 1) != 0); + setModule(qrcode, i, k, (bits & 1) != 0); + bits >>= 1; + } + } + } +} + + +// Draws two copies of the format bits (with its own error correction code) based +// on the given mask and error correction level. This always draws all modules of +// the format bits, unlike drawWhiteFunctionModules() which might skip black modules. +static void drawFormatBits(enum qrcodegen_Ecc ecl, enum qrcodegen_Mask mask, uint8_t qrcode[]) { + // Calculate error correction code and pack bits + assert(0 <= (int)mask && (int)mask <= 7); + static const int table[] = {1, 0, 3, 2}; + int data = table[(int)ecl] << 3 | (int)mask; // errCorrLvl is uint2, mask is uint3 + int rem = data; + for (int i = 0; i < 10; i++) + rem = (rem << 1) ^ ((rem >> 9) * 0x537); + int bits = (data << 10 | rem) ^ 0x5412; // uint15 + assert(bits >> 15 == 0); + + // Draw first copy + for (int i = 0; i <= 5; i++) + setModule(qrcode, 8, i, getBit(bits, i)); + setModule(qrcode, 8, 7, getBit(bits, 6)); + setModule(qrcode, 8, 8, getBit(bits, 7)); + setModule(qrcode, 7, 8, getBit(bits, 8)); + for (int i = 9; i < 15; i++) + setModule(qrcode, 14 - i, 8, getBit(bits, i)); + + // Draw second copy + int qrsize = qrcodegen_getSize(qrcode); + for (int i = 0; i < 8; i++) + setModule(qrcode, qrsize - 1 - i, 8, getBit(bits, i)); + for (int i = 8; i < 15; i++) + setModule(qrcode, 8, qrsize - 15 + i, getBit(bits, i)); + setModule(qrcode, 8, qrsize - 8, true); // Always black +} + + +// Calculates and stores an ascending list of positions of alignment patterns +// for this version number, returning the length of the list (in the range [0,7]). +// Each position is in the range [0,177), and are used on both the x and y axes. +// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. +testable int getAlignmentPatternPositions(int version, uint8_t result[7]) { + if (version == 1) + return 0; + int numAlign = version / 7 + 2; + int step = (version == 32) ? 26 : + (version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2; + for (int i = numAlign - 1, pos = version * 4 + 10; i >= 1; i--, pos -= step) + result[i] = pos; + result[0] = 6; + return numAlign; +} + + +// Sets every pixel in the range [left : left + width] * [top : top + height] to black. +static void fillRectangle(int left, int top, int width, int height, uint8_t qrcode[]) { + for (int dy = 0; dy < height; dy++) { + for (int dx = 0; dx < width; dx++) + setModule(qrcode, left + dx, top + dy, true); + } +} + + + +/*---- Drawing data modules and masking ----*/ + +// Draws the raw codewords (including data and ECC) onto the given QR Code. This requires the initial state of +// the QR Code to be black at function modules and white at codeword modules (including unused remainder bits). +static void drawCodewords(const uint8_t data[], int dataLen, uint8_t qrcode[]) { + int qrsize = qrcodegen_getSize(qrcode); + int i = 0; // Bit index into the data + // Do the funny zigzag scan + for (int right = qrsize - 1; right >= 1; right -= 2) { // Index of right column in each column pair + if (right == 6) + right = 5; + for (int vert = 0; vert < qrsize; vert++) { // Vertical counter + for (int j = 0; j < 2; j++) { + int x = right - j; // Actual x coordinate + bool upward = ((right + 1) & 2) == 0; + int y = upward ? qrsize - 1 - vert : vert; // Actual y coordinate + if (!getModule(qrcode, x, y) && i < dataLen * 8) { + bool black = getBit(data[i >> 3], 7 - (i & 7)); + setModule(qrcode, x, y, black); + i++; + } + // If this QR Code has any remainder bits (0 to 7), they were assigned as + // 0/false/white by the constructor and are left unchanged by this method + } + } + } + assert(i == dataLen * 8); +} + + +// XORs the codeword modules in this QR Code with the given mask pattern. +// The function modules must be marked and the codeword bits must be drawn +// before masking. Due to the arithmetic of XOR, calling applyMask() with +// the same mask value a second time will undo the mask. A final well-formed +// QR Code needs exactly one (not zero, two, etc.) mask applied. +static void applyMask(const uint8_t functionModules[], uint8_t qrcode[], enum qrcodegen_Mask mask) { + assert(0 <= (int)mask && (int)mask <= 7); // Disallows qrcodegen_Mask_AUTO + int qrsize = qrcodegen_getSize(qrcode); + for (int y = 0; y < qrsize; y++) { + for (int x = 0; x < qrsize; x++) { + if (getModule(functionModules, x, y)) + continue; + bool invert; + switch ((int)mask) { + case 0: invert = (x + y) % 2 == 0; break; + case 1: invert = y % 2 == 0; break; + case 2: invert = x % 3 == 0; break; + case 3: invert = (x + y) % 3 == 0; break; + case 4: invert = (x / 3 + y / 2) % 2 == 0; break; + case 5: invert = x * y % 2 + x * y % 3 == 0; break; + case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; + case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; + default: assert(false); return; + } + bool val = getModule(qrcode, x, y); + setModule(qrcode, x, y, val ^ invert); + } + } +} + + +// Calculates and returns the penalty score based on state of the given QR Code's current modules. +// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. +static long getPenaltyScore(const uint8_t qrcode[]) { + int qrsize = qrcodegen_getSize(qrcode); + long result = 0; + + // Adjacent modules in row having same color, and finder-like patterns + for (int y = 0; y < qrsize; y++) { + unsigned char runHistory[7] = {0}; + bool color = false; + unsigned char runX = 0; + for (int x = 0; x < qrsize; x++) { + if (getModule(qrcode, x, y) == color) { + runX++; + if (runX == 5) + result += PENALTY_N1; + else if (runX > 5) + result++; + } else { + addRunToHistory(runX, runHistory); + if (!color && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; + color = getModule(qrcode, x, y); + runX = 1; + } + } + addRunToHistory(runX, runHistory); + if (color) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; + } + // Adjacent modules in column having same color, and finder-like patterns + for (int x = 0; x < qrsize; x++) { + unsigned char runHistory[7] = {0}; + bool color = false; + unsigned char runY = 0; + for (int y = 0; y < qrsize; y++) { + if (getModule(qrcode, x, y) == color) { + runY++; + if (runY == 5) + result += PENALTY_N1; + else if (runY > 5) + result++; + } else { + addRunToHistory(runY, runHistory); + if (!color && hasFinderLikePattern(runHistory)) + result += PENALTY_N3; + color = getModule(qrcode, x, y); + runY = 1; + } + } + addRunToHistory(runY, runHistory); + if (color) + addRunToHistory(0, runHistory); // Dummy run of white + if (hasFinderLikePattern(runHistory)) + result += PENALTY_N3; + } + + // 2*2 blocks of modules having same color + for (int y = 0; y < qrsize - 1; y++) { + for (int x = 0; x < qrsize - 1; x++) { + bool color = getModule(qrcode, x, y); + if ( color == getModule(qrcode, x + 1, y) && + color == getModule(qrcode, x, y + 1) && + color == getModule(qrcode, x + 1, y + 1)) + result += PENALTY_N2; + } + } + + // Balance of black and white modules + int black = 0; + for (int y = 0; y < qrsize; y++) { + for (int x = 0; x < qrsize; x++) { + if (getModule(qrcode, x, y)) + black++; + } + } + int total = qrsize * qrsize; // Note that size is odd, so black/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= black/total <= (55+5k)% + int k = (int)((labs(black * 20L - total * 10L) + total - 1) / total) - 1; + result += k * PENALTY_N4; + return result; +} + + +// Inserts the given value to the front of the given array, which shifts over the +// existing values and deletes the last value. A helper function for getPenaltyScore(). +static void addRunToHistory(unsigned char run, unsigned char history[7]) { + memmove(&history[1], &history[0], 6 * sizeof(history[0])); + history[0] = run; +} + + +// Tests whether the given run history has the pattern of ratio 1:1:3:1:1 in the middle, and +// surrounded by at least 4 on either or both ends. A helper function for getPenaltyScore(). +// Must only be called immediately after a run of white modules has ended. +static bool hasFinderLikePattern(const unsigned char runHistory[7]) { + unsigned char n = runHistory[1]; + // The maximum QR Code size is 177, hence the run length n <= 177. + // Arithmetic is promoted to int, so n*4 will not overflow. + return n > 0 && runHistory[2] == n && runHistory[4] == n && runHistory[5] == n + && runHistory[3] == n * 3 && (runHistory[0] >= n * 4 || runHistory[6] >= n * 4); +} + + + +/*---- Basic QR Code information ----*/ + +// Public function - see documentation comment in header file. +int qrcodegen_getSize(const uint8_t qrcode[]) { + assert(qrcode != NULL); + int result = qrcode[0]; + assert((qrcodegen_VERSION_MIN * 4 + 17) <= result + && result <= (qrcodegen_VERSION_MAX * 4 + 17)); + return result; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y) { + assert(qrcode != NULL); + int qrsize = qrcode[0]; + return (0 <= x && x < qrsize && 0 <= y && y < qrsize) && getModule(qrcode, x, y); +} + + +// Gets the module at the given coordinates, which must be in bounds. +testable bool getModule(const uint8_t qrcode[], int x, int y) { + int qrsize = qrcode[0]; + assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + int index = y * qrsize + x; + return getBit(qrcode[(index >> 3) + 1], index & 7); +} + + +// Sets the module at the given coordinates, which must be in bounds. +testable void setModule(uint8_t qrcode[], int x, int y, bool isBlack) { + int qrsize = qrcode[0]; + assert(21 <= qrsize && qrsize <= 177 && 0 <= x && x < qrsize && 0 <= y && y < qrsize); + int index = y * qrsize + x; + int bitIndex = index & 7; + int byteIndex = (index >> 3) + 1; + if (isBlack) + qrcode[byteIndex] |= 1 << bitIndex; + else + qrcode[byteIndex] &= (1 << bitIndex) ^ 0xFF; +} + + +// Sets the module at the given coordinates, doing nothing if out of bounds. +testable void setModuleBounded(uint8_t qrcode[], int x, int y, bool isBlack) { + int qrsize = qrcode[0]; + if (0 <= x && x < qrsize && 0 <= y && y < qrsize) + setModule(qrcode, x, y, isBlack); +} + + +// Returns true iff the i'th bit of x is set to 1. Requires x >= 0 and 0 <= i <= 14. +static bool getBit(int x, int i) { + return ((x >> i) & 1) != 0; +} + + + +/*---- Segment handling ----*/ + +// Public function - see documentation comment in header file. +bool qrcodegen_isAlphanumeric(const char *text) { + assert(text != NULL); + for (; *text != '\0'; text++) { + if (strchr(ALPHANUMERIC_CHARSET, *text) == NULL) + return false; + } + return true; +} + + +// Public function - see documentation comment in header file. +bool qrcodegen_isNumeric(const char *text) { + assert(text != NULL); + for (; *text != '\0'; text++) { + if (*text < '0' || *text > '9') + return false; + } + return true; +} + + +// Public function - see documentation comment in header file. +size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars) { + int temp = calcSegmentBitLength(mode, numChars); + if (temp == -1) + return SIZE_MAX; + assert(0 <= temp && temp <= INT16_MAX); + return ((size_t)temp + 7) / 8; +} + + +// Returns the number of data bits needed to represent a segment +// containing the given number of characters using the given mode. Notes: +// - Returns -1 on failure, i.e. numChars > INT16_MAX or +// the number of needed bits exceeds INT16_MAX (i.e. 32767). +// - Otherwise, all valid results are in the range [0, INT16_MAX]. +// - For byte mode, numChars measures the number of bytes, not Unicode code points. +// - For ECI mode, numChars must be 0, and the worst-case number of bits is returned. +// An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. +testable int calcSegmentBitLength(enum qrcodegen_Mode mode, size_t numChars) { + // All calculations are designed to avoid overflow on all platforms + if (numChars > (unsigned int)INT16_MAX) + return -1; + long result = (long)numChars; + if (mode == qrcodegen_Mode_NUMERIC) + result = (result * 10 + 2) / 3; // ceil(10/3 * n) + else if (mode == qrcodegen_Mode_ALPHANUMERIC) + result = (result * 11 + 1) / 2; // ceil(11/2 * n) + else if (mode == qrcodegen_Mode_BYTE) + result *= 8; + else if (mode == qrcodegen_Mode_KANJI) + result *= 13; + else if (mode == qrcodegen_Mode_ECI && numChars == 0) + result = 3 * 8; + else { // Invalid argument + assert(false); + return -1; + } + assert(result >= 0); + if ((unsigned int)result > (unsigned int)INT16_MAX) + return -1; + return (int)result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]) { + assert(data != NULL || len == 0); + struct qrcodegen_Segment result; + result.mode = qrcodegen_Mode_BYTE; + result.bitLength = calcSegmentBitLength(result.mode, len); + assert(result.bitLength != -1); + result.numChars = (int)len; + if (len > 0) + memcpy(buf, data, len * sizeof(buf[0])); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]) { + assert(digits != NULL); + struct qrcodegen_Segment result; + size_t len = strlen(digits); + result.mode = qrcodegen_Mode_NUMERIC; + int bitLen = calcSegmentBitLength(result.mode, len); + assert(bitLen != -1); + result.numChars = (int)len; + if (bitLen > 0) + memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); + result.bitLength = 0; + + unsigned int accumData = 0; + int accumCount = 0; + for (; *digits != '\0'; digits++) { + char c = *digits; + assert('0' <= c && c <= '9'); + accumData = accumData * 10 + (unsigned int)(c - '0'); + accumCount++; + if (accumCount == 3) { + appendBitsToBuffer(accumData, 10, buf, &result.bitLength); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 or 2 digits remaining + appendBitsToBuffer(accumData, accumCount * 3 + 1, buf, &result.bitLength); + assert(result.bitLength == bitLen); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]) { + assert(text != NULL); + struct qrcodegen_Segment result; + size_t len = strlen(text); + result.mode = qrcodegen_Mode_ALPHANUMERIC; + int bitLen = calcSegmentBitLength(result.mode, len); + assert(bitLen != -1); + result.numChars = (int)len; + if (bitLen > 0) + memset(buf, 0, ((size_t)bitLen + 7) / 8 * sizeof(buf[0])); + result.bitLength = 0; + + unsigned int accumData = 0; + int accumCount = 0; + for (; *text != '\0'; text++) { + const char *temp = strchr(ALPHANUMERIC_CHARSET, *text); + assert(temp != NULL); + accumData = accumData * 45 + (unsigned int)(temp - ALPHANUMERIC_CHARSET); + accumCount++; + if (accumCount == 2) { + appendBitsToBuffer(accumData, 11, buf, &result.bitLength); + accumData = 0; + accumCount = 0; + } + } + if (accumCount > 0) // 1 character remaining + appendBitsToBuffer(accumData, 6, buf, &result.bitLength); + assert(result.bitLength == bitLen); + result.data = buf; + return result; +} + + +// Public function - see documentation comment in header file. +struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]) { + struct qrcodegen_Segment result; + result.mode = qrcodegen_Mode_ECI; + result.numChars = 0; + result.bitLength = 0; + if (assignVal < 0) { + assert(false); + } else if (assignVal < (1 << 7)) { + memset(buf, 0, 1 * sizeof(buf[0])); + appendBitsToBuffer(assignVal, 8, buf, &result.bitLength); + } else if (assignVal < (1 << 14)) { + memset(buf, 0, 2 * sizeof(buf[0])); + appendBitsToBuffer(2, 2, buf, &result.bitLength); + appendBitsToBuffer(assignVal, 14, buf, &result.bitLength); + } else if (assignVal < 1000000L) { + memset(buf, 0, 3 * sizeof(buf[0])); + appendBitsToBuffer(6, 3, buf, &result.bitLength); + appendBitsToBuffer(assignVal >> 10, 11, buf, &result.bitLength); + appendBitsToBuffer(assignVal & 0x3FF, 10, buf, &result.bitLength); + } else { + assert(false); + } + result.data = buf; + return result; +} + + +// Calculates the number of bits needed to encode the given segments at the given version. +// Returns a non-negative number if successful. Otherwise returns -1 if a segment has too +// many characters to fit its length field, or the total bits exceeds INT16_MAX. +testable int getTotalBits(const struct qrcodegen_Segment segs[], size_t len, int version) { + assert(segs != NULL || len == 0); + long result = 0; + for (size_t i = 0; i < len; i++) { + int numChars = segs[i].numChars; + int bitLength = segs[i].bitLength; + assert(0 <= numChars && numChars <= INT16_MAX); + assert(0 <= bitLength && bitLength <= INT16_MAX); + int ccbits = numCharCountBits(segs[i].mode, version); + assert(0 <= ccbits && ccbits <= 16); + if (numChars >= (1L << ccbits)) + return -1; // The segment's length doesn't fit the field's bit width + result += 4L + ccbits + bitLength; + if (result > INT16_MAX) + return -1; // The sum might overflow an int type + } + assert(0 <= result && result <= INT16_MAX); + return (int)result; +} + + +// Returns the bit width of the character count field for a segment in the given mode +// in a QR Code at the given version number. The result is in the range [0, 16]. +static int numCharCountBits(enum qrcodegen_Mode mode, int version) { + assert(qrcodegen_VERSION_MIN <= version && version <= qrcodegen_VERSION_MAX); + int i = (version + 7) / 17; + switch (mode) { + case qrcodegen_Mode_NUMERIC : { static const int temp[] = {10, 12, 14}; return temp[i]; } + case qrcodegen_Mode_ALPHANUMERIC: { static const int temp[] = { 9, 11, 13}; return temp[i]; } + case qrcodegen_Mode_BYTE : { static const int temp[] = { 8, 16, 16}; return temp[i]; } + case qrcodegen_Mode_KANJI : { static const int temp[] = { 8, 10, 12}; return temp[i]; } + case qrcodegen_Mode_ECI : return 0; + default: assert(false); return -1; // Dummy value + } +} + +int qrcodegen_getMinFitVersion(enum qrcodegen_Ecc ecl, size_t dataLen) +{ + struct qrcodegen_Segment seg; + seg.mode = qrcodegen_Mode_BYTE; + seg.bitLength = calcSegmentBitLength(seg.mode, dataLen); + seg.numChars = (int)dataLen; + + for (int version = qrcodegen_VERSION_MIN; version <= qrcodegen_VERSION_MAX; version++) { + int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available + int dataUsedBits = getTotalBits(&seg, 1, version); + if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) + return version; + } + return -1; +} + +int qrcodegen_version2size(int version) +{ + if (version < qrcodegen_VERSION_MIN || version > qrcodegen_VERSION_MAX) { + return -1; + } + + return ((version - 1)*4 + 21); +} diff --git a/lib/lvgl/src/extra/libs/qrcode/qrcodegen.h b/lib/lvgl/src/extra/libs/qrcode/qrcodegen.h new file mode 100644 index 00000000..b484e917 --- /dev/null +++ b/lib/lvgl/src/extra/libs/qrcode/qrcodegen.h @@ -0,0 +1,319 @@ +/* + * QR Code generator library (C) + * + * Copyright (c) Project Nayuki. (MIT License) + * https://www.nayuki.io/page/qr-code-generator-library + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * - The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * - The Software is provided "as is", without warranty of any kind, express or + * implied, including but not limited to the warranties of merchantability, + * fitness for a particular purpose and noninfringement. In no event shall the + * authors or copyright holders be liable for any claim, damages or other + * liability, whether in an action of contract, tort or otherwise, arising from, + * out of or in connection with the Software or the use or other dealings in the + * Software. + */ + +#pragma once + +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * This library creates QR Code symbols, which is a type of two-dimension barcode. + * Invented by Denso Wave and described in the ISO/IEC 18004 standard. + * A QR Code structure is an immutable square grid of black and white cells. + * The library provides functions to create a QR Code from text or binary data. + * The library covers the QR Code Model 2 specification, supporting all versions (sizes) + * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. + * + * Ways to create a QR Code object: + * - High level: Take the payload data and call qrcodegen_encodeText() or qrcodegen_encodeBinary(). + * - Low level: Custom-make the list of segments and call + * qrcodegen_encodeSegments() or qrcodegen_encodeSegmentsAdvanced(). + * (Note that all ways require supplying the desired error correction level and various byte buffers.) + */ + + +/*---- Enum and struct types----*/ + +/* + * The error correction level in a QR Code symbol. + */ +enum qrcodegen_Ecc { + // Must be declared in ascending order of error protection + // so that an internal qrcodegen function works properly + qrcodegen_Ecc_LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords + qrcodegen_Ecc_MEDIUM , // The QR Code can tolerate about 15% erroneous codewords + qrcodegen_Ecc_QUARTILE, // The QR Code can tolerate about 25% erroneous codewords + qrcodegen_Ecc_HIGH , // The QR Code can tolerate about 30% erroneous codewords +}; + + +/* + * The mask pattern used in a QR Code symbol. + */ +enum qrcodegen_Mask { + // A special value to tell the QR Code encoder to + // automatically select an appropriate mask pattern + qrcodegen_Mask_AUTO = -1, + // The eight actual mask patterns + qrcodegen_Mask_0 = 0, + qrcodegen_Mask_1, + qrcodegen_Mask_2, + qrcodegen_Mask_3, + qrcodegen_Mask_4, + qrcodegen_Mask_5, + qrcodegen_Mask_6, + qrcodegen_Mask_7, +}; + + +/* + * Describes how a segment's data bits are interpreted. + */ +enum qrcodegen_Mode { + qrcodegen_Mode_NUMERIC = 0x1, + qrcodegen_Mode_ALPHANUMERIC = 0x2, + qrcodegen_Mode_BYTE = 0x4, + qrcodegen_Mode_KANJI = 0x8, + qrcodegen_Mode_ECI = 0x7, +}; + + +/* + * A segment of character/binary/control data in a QR Code symbol. + * The mid-level way to create a segment is to take the payload data + * and call a factory function such as qrcodegen_makeNumeric(). + * The low-level way to create a segment is to custom-make the bit buffer + * and initialize a qrcodegen_Segment struct with appropriate values. + * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. + * Any segment longer than this is meaningless for the purpose of generating QR Codes. + * Moreover, the maximum allowed bit length is 32767 because + * the largest QR Code (version 40) has 31329 modules. + */ +struct qrcodegen_Segment { + // The mode indicator of this segment. + enum qrcodegen_Mode mode; + + // The length of this segment's unencoded data. Measured in characters for + // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + // Always zero or positive. Not the same as the data's bit length. + int numChars; + + // The data bits of this segment, packed in bitwise big endian. + // Can be null if the bit length is zero. + uint8_t *data; + + // The number of valid data bits used in the buffer. Requires + // 0 <= bitLength <= 32767, and bitLength <= (capacity of data array) * 8. + // The character count (numChars) must agree with the mode and the bit buffer length. + int bitLength; +}; + + + +/*---- Macro constants and functions ----*/ + +#define qrcodegen_VERSION_MIN 1 // The minimum version number supported in the QR Code Model 2 standard +#define qrcodegen_VERSION_MAX 40 // The maximum version number supported in the QR Code Model 2 standard + +// Calculates the number of bytes needed to store any QR Code up to and including the given version number, +// as a compile-time constant. For example, 'uint8_t buffer[qrcodegen_BUFFER_LEN_FOR_VERSION(25)];' +// can store any single QR Code from version 1 to 25 (inclusive). The result fits in an int (or int16). +// Requires qrcodegen_VERSION_MIN <= n <= qrcodegen_VERSION_MAX. +#define qrcodegen_BUFFER_LEN_FOR_VERSION(n) ((((n) * 4 + 17) * ((n) * 4 + 17) + 7) / 8 + 1) + +// The worst-case number of bytes needed to store one QR Code, up to and including +// version 40. This value equals 3918, which is just under 4 kilobytes. +// Use this more convenient value to avoid calculating tighter memory bounds for buffers. +#define qrcodegen_BUFFER_LEN_MAX qrcodegen_BUFFER_LEN_FOR_VERSION(qrcodegen_VERSION_MAX) + + + +/*---- Functions (high level) to generate QR Codes ----*/ + +/* + * Encodes the given text string to a QR Code, returning true if encoding succeeded. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * - The input text must be encoded in UTF-8 and contain no NULs. + * - The variables ecl and mask must correspond to enum constant values. + * - Requires 1 <= minVersion <= maxVersion <= 40. + * - The arrays tempBuffer and qrcode must each have a length + * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). + * - After the function returns, tempBuffer contains no useful data. + * - If successful, the resulting QR Code may use numeric, + * alphanumeric, or byte mode to encode the text. + * - In the most optimistic case, a QR Code at version 40 with low ECC + * can hold any UTF-8 string up to 2953 bytes, or any alphanumeric string + * up to 4296 characters, or any digit string up to 7089 characters. + * These numbers represent the hard upper limit of the QR Code standard. + * - Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + */ +bool qrcodegen_encodeText(const char *text, uint8_t tempBuffer[], uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); + + +/* + * Encodes the given binary data to a QR Code, returning true if encoding succeeded. + * If the data is too long to fit in any version in the given range + * at the given ECC level, then false is returned. + * - The input array range dataAndTemp[0 : dataLen] should normally be + * valid UTF-8 text, but is not required by the QR Code standard. + * - The variables ecl and mask must correspond to enum constant values. + * - Requires 1 <= minVersion <= maxVersion <= 40. + * - The arrays dataAndTemp and qrcode must each have a length + * of at least qrcodegen_BUFFER_LEN_FOR_VERSION(maxVersion). + * - After the function returns, the contents of dataAndTemp may have changed, + * and does not represent useful data anymore. + * - If successful, the resulting QR Code will use byte mode to encode the data. + * - In the most optimistic case, a QR Code at version 40 with low ECC can hold any byte + * sequence up to length 2953. This is the hard upper limit of the QR Code standard. + * - Please consult the QR Code specification for information on + * data capacities per version, ECC level, and text encoding mode. + */ +bool qrcodegen_encodeBinary(uint8_t dataAndTemp[], size_t dataLen, uint8_t qrcode[], + enum qrcodegen_Ecc ecl, int minVersion, int maxVersion, enum qrcodegen_Mask mask, bool boostEcl); + + +/*---- Functions (low level) to generate QR Codes ----*/ + +/* + * Renders a QR Code representing the given segments at the given error correction level. + * The smallest possible QR Code version is automatically chosen for the output. Returns true if + * QR Code creation succeeded, or false if the data is too long to fit in any version. The ECC level + * of the result may be higher than the ecl argument if it can be done without increasing the version. + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). + * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will + * result in them being clobbered, but the QR Code output will still be correct. + * But the qrcode array must not overlap tempBuffer or any segment's data buffer. + */ +bool qrcodegen_encodeSegments(const struct qrcodegen_Segment segs[], size_t len, + enum qrcodegen_Ecc ecl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +/* + * Renders a QR Code representing the given segments with the given encoding parameters. + * Returns true if QR Code creation succeeded, or false if the data is too long to fit in the range of versions. + * The smallest possible QR Code version within the given range is automatically + * chosen for the output. Iff boostEcl is true, then the ECC level of the result + * may be higher than the ecl argument if it can be done without increasing the + * version. The mask number is either between 0 to 7 (inclusive) to force that + * mask, or -1 to automatically choose an appropriate mask (which may be slow). + * This function allows the user to create a custom sequence of segments that switches + * between modes (such as alphanumeric and byte) to encode text in less space. + * This is a low-level API; the high-level API is qrcodegen_encodeText() and qrcodegen_encodeBinary(). + * To save memory, the segments' data buffers can alias/overlap tempBuffer, and will + * result in them being clobbered, but the QR Code output will still be correct. + * But the qrcode array must not overlap tempBuffer or any segment's data buffer. + */ +bool qrcodegen_encodeSegmentsAdvanced(const struct qrcodegen_Segment segs[], size_t len, enum qrcodegen_Ecc ecl, + int minVersion, int maxVersion, int mask, bool boostEcl, uint8_t tempBuffer[], uint8_t qrcode[]); + + +/* + * Tests whether the given string can be encoded as a segment in alphanumeric mode. + * A string is encodable iff each character is in the following set: 0 to 9, A to Z + * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +bool qrcodegen_isAlphanumeric(const char *text); + + +/* + * Tests whether the given string can be encoded as a segment in numeric mode. + * A string is encodable iff each character is in the range 0 to 9. + */ +bool qrcodegen_isNumeric(const char *text); + + +/* + * Returns the number of bytes (uint8_t) needed for the data buffer of a segment + * containing the given number of characters using the given mode. Notes: + * - Returns SIZE_MAX on failure, i.e. numChars > INT16_MAX or + * the number of needed bits exceeds INT16_MAX (i.e. 32767). + * - Otherwise, all valid results are in the range [0, ceil(INT16_MAX / 8)], i.e. at most 4096. + * - It is okay for the user to allocate more bytes for the buffer than needed. + * - For byte mode, numChars measures the number of bytes, not Unicode code points. + * - For ECI mode, numChars must be 0, and the worst-case number of bytes is returned. + * An actual ECI segment can have shorter data. For non-ECI modes, the result is exact. + */ +size_t qrcodegen_calcSegmentBufferSize(enum qrcodegen_Mode mode, size_t numChars); + + +/* + * Returns a segment representing the given binary data encoded in + * byte mode. All input byte arrays are acceptable. Any text string + * can be converted to UTF-8 bytes and encoded as a byte mode segment. + */ +struct qrcodegen_Segment qrcodegen_makeBytes(const uint8_t data[], size_t len, uint8_t buf[]); + + +/* + * Returns a segment representing the given string of decimal digits encoded in numeric mode. + */ +struct qrcodegen_Segment qrcodegen_makeNumeric(const char *digits, uint8_t buf[]); + + +/* + * Returns a segment representing the given text string encoded in alphanumeric mode. + * The characters allowed are: 0 to 9, A to Z (uppercase only), space, + * dollar, percent, asterisk, plus, hyphen, period, slash, colon. + */ +struct qrcodegen_Segment qrcodegen_makeAlphanumeric(const char *text, uint8_t buf[]); + + +/* + * Returns a segment representing an Extended Channel Interpretation + * (ECI) designator with the given assignment value. + */ +struct qrcodegen_Segment qrcodegen_makeEci(long assignVal, uint8_t buf[]); + + +/*---- Functions to extract raw data from QR Codes ----*/ + +/* + * Returns the side length of the given QR Code, assuming that encoding succeeded. + * The result is in the range [21, 177]. Note that the length of the array buffer + * is related to the side length - every 'uint8_t qrcode[]' must have length at least + * qrcodegen_BUFFER_LEN_FOR_VERSION(version), which equals ceil(size^2 / 8 + 1). + */ +int qrcodegen_getSize(const uint8_t qrcode[]); + + +/* + * Returns the color of the module (pixel) at the given coordinates, which is false + * for white or true for black. The top left corner has the coordinates (x=0, y=0). + * If the given coordinates are out of bounds, then false (white) is returned. + */ +bool qrcodegen_getModule(const uint8_t qrcode[], int x, int y); + +/* + * Returns the qrcode size of the specified version. Returns -1 on failure + */ +int qrcodegen_version2size(int version); +/* + * Returns the min version of the data that can be stored. Returns -1 on failure + */ +int qrcodegen_getMinFitVersion(enum qrcodegen_Ecc ecl, size_t dataLen); + +#ifdef __cplusplus +} +#endif diff --git a/lib/lvgl/src/extra/libs/rlottie/lv_rlottie.c b/lib/lvgl/src/extra/libs/rlottie/lv_rlottie.c new file mode 100644 index 00000000..a264948c --- /dev/null +++ b/lib/lvgl/src/extra/libs/rlottie/lv_rlottie.c @@ -0,0 +1,284 @@ +/** + * @file lv_rlottie.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_rlottie.h" +#if LV_USE_RLOTTIE + +#include <rlottie_capi.h> + +/********************* +* DEFINES +*********************/ +#define MY_CLASS &lv_rlottie_class +#define LV_ARGB32 32 + +/********************** +* TYPEDEFS +**********************/ +#define LV_ARGB32 32 + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_rlottie_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_rlottie_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void next_frame_task_cb(lv_timer_t * t); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_rlottie_class = { + .constructor_cb = lv_rlottie_constructor, + .destructor_cb = lv_rlottie_destructor, + .instance_size = sizeof(lv_rlottie_t), + .base_class = &lv_img_class +}; + +static lv_coord_t create_width; +static lv_coord_t create_height; +static const char * rlottie_desc_create; +static const char * path_create; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_rlottie_create_from_file(lv_obj_t * parent, lv_coord_t width, lv_coord_t height, const char * path) +{ + + create_width = width; + create_height = height; + path_create = path; + rlottie_desc_create = NULL; + + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + + return obj; + +} + +lv_obj_t * lv_rlottie_create_from_raw(lv_obj_t * parent, lv_coord_t width, lv_coord_t height, const char * rlottie_desc) +{ + + create_width = width; + create_height = height; + rlottie_desc_create = rlottie_desc; + path_create = NULL; + + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + + return obj; +} + +void lv_rlottie_set_play_mode(lv_obj_t * obj, const lv_rlottie_ctrl_t ctrl) +{ + lv_rlottie_t * rlottie = (lv_rlottie_t *) obj; + rlottie->play_ctrl = ctrl; + + if(rlottie->task && (rlottie->dest_frame != rlottie->current_frame || + (rlottie->play_ctrl & LV_RLOTTIE_CTRL_PAUSE) == LV_RLOTTIE_CTRL_PLAY)) { + lv_timer_resume(rlottie->task); + } +} + +void lv_rlottie_set_current_frame(lv_obj_t * obj, const size_t goto_frame) +{ + lv_rlottie_t * rlottie = (lv_rlottie_t *) obj; + rlottie->current_frame = goto_frame < rlottie->total_frames ? goto_frame : rlottie->total_frames - 1; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_rlottie_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_rlottie_t * rlottie = (lv_rlottie_t *) obj; + + if(rlottie_desc_create) { + rlottie->animation = lottie_animation_from_data(rlottie_desc_create, rlottie_desc_create, ""); + } + else if(path_create) { + rlottie->animation = lottie_animation_from_file(path_create); + } + if(rlottie->animation == NULL) { + LV_LOG_WARN("The aniamtion can't be opened"); + return; + } + + rlottie->total_frames = lottie_animation_get_totalframe(rlottie->animation); + rlottie->framerate = (size_t)lottie_animation_get_framerate(rlottie->animation); + rlottie->current_frame = 0; + + rlottie->scanline_width = create_width * LV_ARGB32 / 8; + + size_t allocaled_buf_size = (create_width * create_height * LV_ARGB32 / 8); + rlottie->allocated_buf = lv_mem_alloc(allocaled_buf_size); + if(rlottie->allocated_buf != NULL) { + rlottie->allocated_buffer_size = allocaled_buf_size; + memset(rlottie->allocated_buf, 0, allocaled_buf_size); + } + + rlottie->imgdsc.header.always_zero = 0; + rlottie->imgdsc.header.cf = LV_IMG_CF_TRUE_COLOR_ALPHA; + rlottie->imgdsc.header.h = create_height; + rlottie->imgdsc.header.w = create_width; + rlottie->imgdsc.data = (void *)rlottie->allocated_buf; + rlottie->imgdsc.data_size = allocaled_buf_size; + + lv_img_set_src(obj, &rlottie->imgdsc); + + rlottie->play_ctrl = LV_RLOTTIE_CTRL_FORWARD | LV_RLOTTIE_CTRL_PLAY | LV_RLOTTIE_CTRL_LOOP; + rlottie->dest_frame = rlottie->total_frames; /* invalid destination frame so it's possible to pause on frame 0 */ + + rlottie->task = lv_timer_create(next_frame_task_cb, 1000 / rlottie->framerate, obj); + + lv_obj_update_layout(obj); +} + + +static void lv_rlottie_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_rlottie_t * rlottie = (lv_rlottie_t *) obj; + + if(rlottie->animation) { + lottie_animation_destroy(rlottie->animation); + rlottie->animation = 0; + rlottie->current_frame = 0; + rlottie->framerate = 0; + rlottie->scanline_width = 0; + rlottie->total_frames = 0; + } + + if(rlottie->task) { + lv_timer_del(rlottie->task); + rlottie->task = NULL; + rlottie->play_ctrl = LV_RLOTTIE_CTRL_FORWARD; + rlottie->dest_frame = 0; + } + + lv_img_cache_invalidate_src(&rlottie->imgdsc); + if(rlottie->allocated_buf) { + lv_mem_free(rlottie->allocated_buf); + rlottie->allocated_buf = NULL; + rlottie->allocated_buffer_size = 0; + } + +} + +#if LV_COLOR_DEPTH == 16 +static void convert_to_rgba5658(uint32_t * pix, const size_t width, const size_t height) +{ + /* rlottie draws in ARGB32 format, but LVGL only deal with RGB565 format with (optional 8 bit alpha channel) + so convert in place here the received buffer to LVGL format. */ + uint8_t * dest = (uint8_t *)pix; + uint32_t * src = pix; + for(size_t y = 0; y < height; y++) { + /* Convert a 4 bytes per pixel in format ARGB to R5G6B5A8 format + naive way: + r = ((c & 0xFF0000) >> 19) + g = ((c & 0xFF00) >> 10) + b = ((c & 0xFF) >> 3) + rgb565 = (r << 11) | (g << 5) | b + a = c >> 24; + That's 3 mask, 6 bitshift and 2 or operations + + A bit better: + r = ((c & 0xF80000) >> 8) + g = ((c & 0xFC00) >> 5) + b = ((c & 0xFF) >> 3) + rgb565 = r | g | b + a = c >> 24; + That's 3 mask, 3 bitshifts and 2 or operations */ + for(size_t x = 0; x < width; x++) { + uint32_t in = src[x]; +#if LV_COLOR_16_SWAP == 0 + uint16_t r = (uint16_t)(((in & 0xF80000) >> 8) | ((in & 0xFC00) >> 5) | ((in & 0xFF) >> 3)); +#else + /* We want: rrrr rrrr GGGg gggg bbbb bbbb => gggb bbbb rrrr rGGG */ + uint16_t r = (uint16_t)(((in & 0xF80000) >> 16) | ((in & 0xFC00) >> 13) | ((in & 0x1C00) << 3) | ((in & 0xF8) << 5)); +#endif + + lv_memcpy(dest, &r, sizeof(r)); + dest[sizeof(r)] = (uint8_t)(in >> 24); + dest += LV_IMG_PX_SIZE_ALPHA_BYTE; + } + src += width; + } +} +#endif + +static void next_frame_task_cb(lv_timer_t * t) +{ + lv_obj_t * obj = t->user_data; + lv_rlottie_t * rlottie = (lv_rlottie_t *) obj; + + if((rlottie->play_ctrl & LV_RLOTTIE_CTRL_PAUSE) == LV_RLOTTIE_CTRL_PAUSE) { + if(rlottie->current_frame == rlottie->dest_frame) { + /* Pause the timer too when it has run once to avoid CPU consumption */ + lv_timer_pause(t); + return; + } + rlottie->dest_frame = rlottie->current_frame; + } + else { + if((rlottie->play_ctrl & LV_RLOTTIE_CTRL_BACKWARD) == LV_RLOTTIE_CTRL_BACKWARD) { + if(rlottie->current_frame > 0) + --rlottie->current_frame; + else { /* Looping ? */ + if((rlottie->play_ctrl & LV_RLOTTIE_CTRL_LOOP) == LV_RLOTTIE_CTRL_LOOP) + rlottie->current_frame = rlottie->total_frames - 1; + else { + lv_event_send(obj, LV_EVENT_READY, NULL); + lv_timer_pause(t); + return; + } + } + } + else { + if(rlottie->current_frame < rlottie->total_frames) + ++rlottie->current_frame; + else { /* Looping ? */ + if((rlottie->play_ctrl & LV_RLOTTIE_CTRL_LOOP) == LV_RLOTTIE_CTRL_LOOP) + rlottie->current_frame = 0; + else { + lv_event_send(obj, LV_EVENT_READY, NULL); + lv_timer_pause(t); + return; + } + } + } + } + + lottie_animation_render( + rlottie->animation, + rlottie->current_frame, + rlottie->allocated_buf, + rlottie->imgdsc.header.w, + rlottie->imgdsc.header.h, + rlottie->scanline_width + ); + +#if LV_COLOR_DEPTH == 16 + convert_to_rgba5658(rlottie->allocated_buf, rlottie->imgdsc.header.w, rlottie->imgdsc.header.h); +#endif + + lv_obj_invalidate(obj); +} + +#endif /*LV_USE_RLOTTIE*/ diff --git a/lib/lvgl/src/extra/libs/rlottie/lv_rlottie.h b/lib/lvgl/src/extra/libs/rlottie/lv_rlottie.h new file mode 100644 index 00000000..d66dc22c --- /dev/null +++ b/lib/lvgl/src/extra/libs/rlottie/lv_rlottie.h @@ -0,0 +1,75 @@ +/** + * @file lv_rlottie.h + * + */ + +#ifndef LV_RLOTTIE_H +#define LV_RLOTTIE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" +#if LV_USE_RLOTTIE + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef enum { + LV_RLOTTIE_CTRL_FORWARD = 0, + LV_RLOTTIE_CTRL_BACKWARD = 1, + LV_RLOTTIE_CTRL_PAUSE = 2, + LV_RLOTTIE_CTRL_PLAY = 0, /* Yes, play = 0 is the default mode */ + LV_RLOTTIE_CTRL_LOOP = 8, +} lv_rlottie_ctrl_t; + +/** definition in lottieanimation_capi.c */ +struct Lottie_Animation_S; +typedef struct { + lv_img_t img_ext; + struct Lottie_Animation_S * animation; + lv_timer_t * task; + lv_img_dsc_t imgdsc; + size_t total_frames; + size_t current_frame; + size_t framerate; + uint32_t * allocated_buf; + size_t allocated_buffer_size; + size_t scanline_width; + lv_rlottie_ctrl_t play_ctrl; + size_t dest_frame; +} lv_rlottie_t; + +extern const lv_obj_class_t lv_rlottie_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t * lv_rlottie_create_from_file(lv_obj_t * parent, lv_coord_t width, lv_coord_t height, const char * path); + +lv_obj_t * lv_rlottie_create_from_raw(lv_obj_t * parent, lv_coord_t width, lv_coord_t height, + const char * rlottie_desc); + +void lv_rlottie_set_play_mode(lv_obj_t * rlottie, const lv_rlottie_ctrl_t ctrl); +void lv_rlottie_set_current_frame(lv_obj_t * rlottie, const size_t goto_frame); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_RLOTTIE*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_RLOTTIE_H*/ diff --git a/lib/lvgl/src/extra/libs/sjpg/lv_sjpg.c b/lib/lvgl/src/extra/libs/sjpg/lv_sjpg.c new file mode 100644 index 00000000..5a12ea25 --- /dev/null +++ b/lib/lvgl/src/extra/libs/sjpg/lv_sjpg.c @@ -0,0 +1,917 @@ +/** + * @file lv_sjpg.c + * + */ + +/*---------------------------------------------------------------------------------------------------------------------------------- +/ Added normal JPG support [7/10/2020] +/ ---------- +/ SJPEG is a custom created modified JPEG file format for small embedded platforms. +/ It will contain multiple JPEG fragments all embedded into a single file with a custom header. +/ This makes JPEG decoding easier using any JPEG library. Overall file size will be almost +/ similar to the parent jpeg file. We can generate sjpeg from any jpeg using a python script +/ provided along with this project. +/ (by vinodstanur | 2020 ) +/ SJPEG FILE STRUCTURE +/ -------------------------------------------------------------------------------------------------------------------------------- +/ Bytes | Value | +/ -------------------------------------------------------------------------------------------------------------------------------- +/ +/ 0 - 7 | "_SJPG__" followed by '\0' +/ +/ 8 - 13 | "V1.00" followed by '\0' [VERSION OF SJPG FILE for future compatibiliby] +/ +/ 14 - 15 | X_RESOLUTION (width) [little endian] +/ +/ 16 - 17 | Y_RESOLUTION (height) [little endian] +/ +/ 18 - 19 | TOTAL_FRAMES inside sjpeg [little endian] +/ +/ 20 - 21 | JPEG BLOCK WIDTH (16 normally) [little endian] +/ +/ 22 - [(TOTAL_FRAMES*2 )] | SIZE OF EACH JPEG SPLIT FRAGMENTS (FRAME_INFO_ARRAY) +/ +/ SJPEG data | Each JPEG frame can be extracted from SJPEG data by parsing the FRAME_INFO_ARRAY one time. +/ +/---------------------------------------------------------------------------------------------------------------------------------- +/ JPEG DECODER +/ ------------ +/ We are using TJpgDec - Tiny JPEG Decompressor library from ELM-CHAN for decoding each split-jpeg fragments. +/ The tjpgd.c and tjpgd.h is not modified and those are used as it is. So if any update comes for the tiny-jpeg, +/ just replace those files with updated files. +/---------------------------------------------------------------------------------------------------------------------------------*/ + +/********************* + * INCLUDES + *********************/ + +#include "../../../lvgl.h" +#if LV_USE_SJPG + +#include "tjpgd.h" +#include "lv_sjpg.h" +#include "../../../misc/lv_fs.h" + +/********************* + * DEFINES + *********************/ +#define TJPGD_WORKBUFF_SIZE 4096 //Recommended by TJPGD libray + +//NEVER EDIT THESE OFFSET VALUES +#define SJPEG_VERSION_OFFSET 8 +#define SJPEG_X_RES_OFFSET 14 +#define SJPEG_y_RES_OFFSET 16 +#define SJPEG_TOTAL_FRAMES_OFFSET 18 +#define SJPEG_BLOCK_WIDTH_OFFSET 20 +#define SJPEG_FRAME_INFO_ARRAY_OFFSET 22 + +/********************** + * TYPEDEFS + **********************/ + +enum io_source_type { + SJPEG_IO_SOURCE_C_ARRAY, + SJPEG_IO_SOURCE_DISK, +}; + +typedef struct { + enum io_source_type type; + lv_fs_file_t lv_file; + uint8_t * img_cache_buff; + int img_cache_x_res; + int img_cache_y_res; + uint8_t * raw_sjpg_data; //Used when type==SJPEG_IO_SOURCE_C_ARRAY. + uint32_t raw_sjpg_data_size; //Num bytes pointed to by raw_sjpg_data. + uint32_t raw_sjpg_data_next_read_pos; //Used for all types. +} io_source_t; + + +typedef struct { + uint8_t * sjpeg_data; + uint32_t sjpeg_data_size; + int sjpeg_x_res; + int sjpeg_y_res; + int sjpeg_total_frames; + int sjpeg_single_frame_height; + int sjpeg_cache_frame_index; + uint8_t ** frame_base_array; //to save base address of each split frames upto sjpeg_total_frames. + int * frame_base_offset; //to save base offset for fseek + uint8_t * frame_cache; + uint8_t * workb; //JPG work buffer for jpeg library + JDEC * tjpeg_jd; + io_source_t io; +} SJPEG; + +/********************** + * STATIC PROTOTYPES + **********************/ +static lv_res_t decoder_info(lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header); +static lv_res_t decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc); +static lv_res_t decoder_read_line(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc, lv_coord_t x, lv_coord_t y, + lv_coord_t len, uint8_t * buf); +static void decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc); +static size_t input_func(JDEC * jd, uint8_t * buff, size_t ndata); +static int is_jpg(const uint8_t * raw_data, size_t len); +static void lv_sjpg_cleanup(SJPEG * sjpeg); +static void lv_sjpg_free(SJPEG * sjpeg); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +void lv_split_jpeg_init(void) +{ + lv_img_decoder_t * dec = lv_img_decoder_create(); + lv_img_decoder_set_info_cb(dec, decoder_info); + lv_img_decoder_set_open_cb(dec, decoder_open); + lv_img_decoder_set_close_cb(dec, decoder_close); + lv_img_decoder_set_read_line_cb(dec, decoder_read_line); +} + +/********************** + * STATIC FUNCTIONS + **********************/ +/** + * Get info about an SJPG / JPG image + * @param decoder pointer to the decoder where this function belongs + * @param src can be file name or pointer to a C array + * @param header store the info here + * @return LV_RES_OK: no error; LV_RES_INV: can't get the info + */ +static lv_res_t decoder_info(lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header) +{ + LV_UNUSED(decoder); + + /*Check whether the type `src` is known by the decoder*/ + /* Read the SJPG/JPG header and find `width` and `height` */ + + lv_img_src_t src_type = lv_img_src_get_type(src); /*Get the source type*/ + + lv_res_t ret = LV_RES_OK; + + if(src_type == LV_IMG_SRC_VARIABLE) { + const lv_img_dsc_t * img_dsc = src; + uint8_t * raw_sjpeg_data = (uint8_t *)img_dsc->data; + const uint32_t raw_sjpeg_data_size = img_dsc->data_size; + + if(!strncmp((char *)raw_sjpeg_data, "_SJPG__", strlen("_SJPG__"))) { + + raw_sjpeg_data += 14; //seek to res info ... refer sjpeg format + header->always_zero = 0; + header->cf = LV_IMG_CF_RAW; + + header->w = *raw_sjpeg_data++; + header->w |= *raw_sjpeg_data++ << 8; + + header->h = *raw_sjpeg_data++; + header->h |= *raw_sjpeg_data++ << 8; + + return ret; + + } + else if(is_jpg(raw_sjpeg_data, raw_sjpeg_data_size) == true) { + header->always_zero = 0; + header->cf = LV_IMG_CF_RAW; + + uint8_t * workb_temp = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); + if(!workb_temp) return LV_RES_INV; + + io_source_t io_source_temp; + io_source_temp.type = SJPEG_IO_SOURCE_C_ARRAY; + io_source_temp.raw_sjpg_data = raw_sjpeg_data; + io_source_temp.raw_sjpg_data_size = raw_sjpeg_data_size; + io_source_temp.raw_sjpg_data_next_read_pos = 0; + + JDEC jd_tmp; + + JRESULT rc = jd_prepare(&jd_tmp, input_func, workb_temp, (size_t)TJPGD_WORKBUFF_SIZE, &io_source_temp); + if(rc == JDR_OK) { + header->w = jd_tmp.width; + header->h = jd_tmp.height; + + } + else { + ret = LV_RES_INV; + goto end; + } + +end: + lv_mem_free(workb_temp); + + return ret; + + } + } + else if(src_type == LV_IMG_SRC_FILE) { + const char * fn = src; + if(strcmp(lv_fs_get_ext(fn), "sjpg") == 0) { + + uint8_t buff[22]; + memset(buff, 0, sizeof(buff)); + + lv_fs_file_t file; + lv_fs_res_t res = lv_fs_open(&file, fn, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) return 78; + + uint32_t rn; + res = lv_fs_read(&file, buff, 8, &rn); + if(res != LV_FS_RES_OK || rn != 8) { + lv_fs_close(&file); + return LV_RES_INV; + } + + if(strcmp((char *)buff, "_SJPG__") == 0) { + lv_fs_seek(&file, 14, LV_FS_SEEK_SET); + res = lv_fs_read(&file, buff, 4, &rn); + if(res != LV_FS_RES_OK || rn != 4) { + lv_fs_close(&file); + return LV_RES_INV; + } + header->always_zero = 0; + header->cf = LV_IMG_CF_RAW; + uint8_t * raw_sjpeg_data = buff; + header->w = *raw_sjpeg_data++; + header->w |= *raw_sjpeg_data++ << 8; + header->h = *raw_sjpeg_data++; + header->h |= *raw_sjpeg_data++ << 8; + lv_fs_close(&file); + return LV_RES_OK; + + } + } + else if(strcmp(lv_fs_get_ext(fn), "jpg") == 0) { + lv_fs_file_t file; + lv_fs_res_t res = lv_fs_open(&file, fn, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) return 78; + + uint8_t * workb_temp = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); + if(!workb_temp) { + lv_fs_close(&file); + return LV_RES_INV; + } + + io_source_t io_source_temp; + io_source_temp.type = SJPEG_IO_SOURCE_DISK; + io_source_temp.raw_sjpg_data_next_read_pos = 0; + io_source_temp.img_cache_buff = NULL; + io_source_temp.lv_file = file; + JDEC jd_tmp; + + JRESULT rc = jd_prepare(&jd_tmp, input_func, workb_temp, (size_t)TJPGD_WORKBUFF_SIZE, &io_source_temp); + lv_mem_free(workb_temp); + lv_fs_close(&file); + + if(rc == JDR_OK) { + header->always_zero = 0; + header->cf = LV_IMG_CF_RAW; + header->w = jd_tmp.width; + header->h = jd_tmp.height; + return LV_RES_OK; + } + } + } + return LV_RES_INV; +} + +static int img_data_cb(JDEC * jd, void * data, JRECT * rect) +{ + io_source_t * io = jd->device; + uint8_t * cache = io->img_cache_buff; + const int xres = io->img_cache_x_res; + uint8_t * buf = data; + const int INPUT_PIXEL_SIZE = 3; + const int row_width = rect->right - rect->left + 1; // Row width in pixels. + const int row_size = row_width * INPUT_PIXEL_SIZE; // Row size (bytes). + + for(int y = rect->top; y <= rect->bottom; y++) { + int row_offset = y * xres * INPUT_PIXEL_SIZE + rect->left * INPUT_PIXEL_SIZE; + memcpy(cache + row_offset, buf, row_size); + buf += row_size; + } + + return 1; +} + +static size_t input_func(JDEC * jd, uint8_t * buff, size_t ndata) +{ + io_source_t * io = jd->device; + + if(!io) return 0; + + if(io->type == SJPEG_IO_SOURCE_C_ARRAY) { + const uint32_t bytes_left = io->raw_sjpg_data_size - io->raw_sjpg_data_next_read_pos; + const uint32_t to_read = ndata <= bytes_left ? (uint32_t)ndata : bytes_left; + if(to_read == 0) + return 0; + if(buff) { + memcpy(buff, io->raw_sjpg_data + io->raw_sjpg_data_next_read_pos, to_read); + } + io->raw_sjpg_data_next_read_pos += to_read; + return to_read; + } + else if(io->type == SJPEG_IO_SOURCE_DISK) { + + lv_fs_file_t * lv_file_p = &(io->lv_file); + + if(buff) { + uint32_t rn = 0; + lv_fs_read(lv_file_p, buff, (uint32_t)ndata, &rn); + return rn; + } + else { + uint32_t pos; + lv_fs_tell(lv_file_p, &pos); + lv_fs_seek(lv_file_p, (uint32_t)(ndata + pos), LV_FS_SEEK_SET); + return ndata; + } + } + return 0; +} + +/** + * Open SJPG image and return the decided image + * @param decoder pointer to the decoder where this function belongs + * @param dsc pointer to a descriptor which describes this decoding session + * @return LV_RES_OK: no error; LV_RES_INV: can't get the info + */ +static lv_res_t decoder_open(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc) +{ + LV_UNUSED(decoder); + lv_res_t lv_ret = LV_RES_OK; + + if(dsc->src_type == LV_IMG_SRC_VARIABLE) { + uint8_t * data; + SJPEG * sjpeg = (SJPEG *) dsc->user_data; + const uint32_t raw_sjpeg_data_size = ((lv_img_dsc_t *)dsc->src)->data_size; + if(sjpeg == NULL) { + sjpeg = lv_mem_alloc(sizeof(SJPEG)); + if(!sjpeg) return LV_RES_INV; + + memset(sjpeg, 0, sizeof(SJPEG)); + + dsc->user_data = sjpeg; + sjpeg->sjpeg_data = (uint8_t *)((lv_img_dsc_t *)(dsc->src))->data; + sjpeg->sjpeg_data_size = ((lv_img_dsc_t *)(dsc->src))->data_size; + } + + if(!strncmp((char *) sjpeg->sjpeg_data, "_SJPG__", strlen("_SJPG__"))) { + + data = sjpeg->sjpeg_data; + data += 14; + + sjpeg->sjpeg_x_res = *data++; + sjpeg->sjpeg_x_res |= *data++ << 8; + + sjpeg->sjpeg_y_res = *data++; + sjpeg->sjpeg_y_res |= *data++ << 8; + + sjpeg->sjpeg_total_frames = *data++; + sjpeg->sjpeg_total_frames |= *data++ << 8; + + sjpeg->sjpeg_single_frame_height = *data++; + sjpeg->sjpeg_single_frame_height |= *data++ << 8; + + sjpeg->frame_base_array = lv_mem_alloc(sizeof(uint8_t *) * sjpeg->sjpeg_total_frames); + if(! sjpeg->frame_base_array) { + lv_sjpg_cleanup(sjpeg); + sjpeg = NULL; + return LV_RES_INV; + } + + sjpeg->frame_base_offset = NULL; + + uint8_t * img_frame_base = data + sjpeg->sjpeg_total_frames * 2; + sjpeg->frame_base_array[0] = img_frame_base; + + for(int i = 1; i < sjpeg->sjpeg_total_frames; i++) { + int offset = *data++; + offset |= *data++ << 8; + sjpeg->frame_base_array[i] = sjpeg->frame_base_array[i - 1] + offset; + } + sjpeg->sjpeg_cache_frame_index = -1; + sjpeg->frame_cache = (void *)lv_mem_alloc(sjpeg->sjpeg_x_res * sjpeg->sjpeg_single_frame_height * 3/*2*/); + if(! sjpeg->frame_cache) { + lv_sjpg_cleanup(sjpeg); + sjpeg = NULL; + return LV_RES_INV; + } + sjpeg->io.img_cache_buff = sjpeg->frame_cache; + sjpeg->io.img_cache_x_res = sjpeg->sjpeg_x_res; + sjpeg->workb = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); + if(! sjpeg->workb) { + lv_sjpg_cleanup(sjpeg); + sjpeg = NULL; + return LV_RES_INV; + } + + sjpeg->tjpeg_jd = lv_mem_alloc(sizeof(JDEC)); + if(! sjpeg->tjpeg_jd) { + lv_sjpg_cleanup(sjpeg); + sjpeg = NULL; + return LV_RES_INV; + } + sjpeg->io.type = SJPEG_IO_SOURCE_C_ARRAY; + sjpeg->io.lv_file.file_d = NULL; + dsc->img_data = NULL; + return lv_ret; + } + else if(is_jpg(sjpeg->sjpeg_data, raw_sjpeg_data_size) == true) { + + uint8_t * workb_temp = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); + if(! workb_temp) { + lv_sjpg_cleanup(sjpeg); + sjpeg = NULL; + return LV_RES_INV; + } + io_source_t io_source_temp; + io_source_temp.type = SJPEG_IO_SOURCE_C_ARRAY; + io_source_temp.raw_sjpg_data = sjpeg->sjpeg_data; + io_source_temp.raw_sjpg_data_size = sjpeg->sjpeg_data_size; + io_source_temp.raw_sjpg_data_next_read_pos = 0; + + JDEC jd_tmp; + JRESULT rc = jd_prepare(&jd_tmp, input_func, workb_temp, (size_t)TJPGD_WORKBUFF_SIZE, &io_source_temp); + lv_mem_free(workb_temp); + + + if(rc == JDR_OK) { + sjpeg->sjpeg_x_res = jd_tmp.width; + sjpeg->sjpeg_y_res = jd_tmp.height; + sjpeg->sjpeg_total_frames = 1; + sjpeg->sjpeg_single_frame_height = jd_tmp.height; + + sjpeg->frame_base_array = lv_mem_alloc(sizeof(uint8_t *) * sjpeg->sjpeg_total_frames); + if(! sjpeg->frame_base_array) { + lv_sjpg_cleanup(sjpeg); + sjpeg = NULL; + return LV_RES_INV; + } + sjpeg->frame_base_offset = NULL; + + uint8_t * img_frame_base = sjpeg->sjpeg_data; + sjpeg->frame_base_array[0] = img_frame_base; + + sjpeg->sjpeg_cache_frame_index = -1; + sjpeg->frame_cache = (void *)lv_mem_alloc(sjpeg->sjpeg_x_res * sjpeg->sjpeg_single_frame_height * 3); + if(! sjpeg->frame_cache) { + lv_sjpg_cleanup(sjpeg); + sjpeg = NULL; + return LV_RES_INV; + } + + sjpeg->io.img_cache_buff = sjpeg->frame_cache; + sjpeg->io.img_cache_x_res = sjpeg->sjpeg_x_res; + sjpeg->workb = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); + if(! sjpeg->workb) { + lv_sjpg_cleanup(sjpeg); + sjpeg = NULL; + return LV_RES_INV; + } + + sjpeg->tjpeg_jd = lv_mem_alloc(sizeof(JDEC)); + if(! sjpeg->tjpeg_jd) { + lv_sjpg_cleanup(sjpeg); + sjpeg = NULL; + return LV_RES_INV; + } + + sjpeg->io.type = SJPEG_IO_SOURCE_C_ARRAY; + sjpeg->io.lv_file.file_d = NULL; + dsc->img_data = NULL; + return lv_ret; + } + else { + lv_ret = LV_RES_INV; + goto end; + } + +end: + lv_mem_free(workb_temp); + + return lv_ret; + } + } + else if(dsc->src_type == LV_IMG_SRC_FILE) { + /* If all fine, then the file will be kept open */ + const char * fn = dsc->src; + uint8_t * data; + + if(strcmp(lv_fs_get_ext(fn), "sjpg") == 0) { + + uint8_t buff[22]; + memset(buff, 0, sizeof(buff)); + + + lv_fs_file_t lv_file; + lv_fs_res_t res = lv_fs_open(&lv_file, fn, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) { + return 78; + } + + + uint32_t rn; + res = lv_fs_read(&lv_file, buff, 22, &rn); + if(res != LV_FS_RES_OK || rn != 22) { + lv_fs_close(&lv_file); + return LV_RES_INV; + } + + if(strcmp((char *)buff, "_SJPG__") == 0) { + + SJPEG * sjpeg = (SJPEG *) dsc->user_data; + if(sjpeg == NULL) { + sjpeg = lv_mem_alloc(sizeof(SJPEG)); + + if(! sjpeg) { + lv_fs_close(&lv_file); + return LV_RES_INV; + } + memset(sjpeg, 0, sizeof(SJPEG)); + + dsc->user_data = sjpeg; + sjpeg->sjpeg_data = (uint8_t *)((lv_img_dsc_t *)(dsc->src))->data; + sjpeg->sjpeg_data_size = ((lv_img_dsc_t *)(dsc->src))->data_size; + } + data = buff; + data += 14; + + sjpeg->sjpeg_x_res = *data++; + sjpeg->sjpeg_x_res |= *data++ << 8; + + sjpeg->sjpeg_y_res = *data++; + sjpeg->sjpeg_y_res |= *data++ << 8; + + sjpeg->sjpeg_total_frames = *data++; + sjpeg->sjpeg_total_frames |= *data++ << 8; + + sjpeg->sjpeg_single_frame_height = *data++; + sjpeg->sjpeg_single_frame_height |= *data++ << 8; + + sjpeg->frame_base_array = NULL;//lv_mem_alloc( sizeof(uint8_t *) * sjpeg->sjpeg_total_frames ); + sjpeg->frame_base_offset = lv_mem_alloc(sizeof(int) * sjpeg->sjpeg_total_frames); + if(! sjpeg->frame_base_offset) { + lv_fs_close(&lv_file); + lv_sjpg_cleanup(sjpeg); + return LV_RES_INV; + } + int img_frame_start_offset = (SJPEG_FRAME_INFO_ARRAY_OFFSET + sjpeg->sjpeg_total_frames * 2); + sjpeg->frame_base_offset[0] = img_frame_start_offset; //pointer used to save integer for now... + + for(int i = 1; i < sjpeg->sjpeg_total_frames; i++) { + res = lv_fs_read(&lv_file, buff, 2, &rn); + if(res != LV_FS_RES_OK || rn != 2) { + lv_fs_close(&lv_file); + return LV_RES_INV; + } + + data = buff; + int offset = *data++; + offset |= *data++ << 8; + sjpeg->frame_base_offset[i] = sjpeg->frame_base_offset[i - 1] + offset; + } + + sjpeg->sjpeg_cache_frame_index = -1; //INVALID AT BEGINNING for a forced compare mismatch at first time. + sjpeg->frame_cache = (void *)lv_mem_alloc(sjpeg->sjpeg_x_res * sjpeg->sjpeg_single_frame_height * 3); + if(! sjpeg->frame_cache) { + lv_fs_close(&lv_file); + lv_sjpg_cleanup(sjpeg); + return LV_RES_INV; + } + sjpeg->io.img_cache_buff = sjpeg->frame_cache; + sjpeg->io.img_cache_x_res = sjpeg->sjpeg_x_res; + sjpeg->workb = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); + if(! sjpeg->workb) { + lv_fs_close(&lv_file); + lv_sjpg_cleanup(sjpeg); + return LV_RES_INV; + } + + sjpeg->tjpeg_jd = lv_mem_alloc(sizeof(JDEC)); + if(! sjpeg->tjpeg_jd) { + lv_fs_close(&lv_file); + lv_sjpg_cleanup(sjpeg); + return LV_RES_INV; + } + + sjpeg->io.type = SJPEG_IO_SOURCE_DISK; + sjpeg->io.lv_file = lv_file; + dsc->img_data = NULL; + return LV_RES_OK; + } + } + else if(strcmp(lv_fs_get_ext(fn), "jpg") == 0) { + + lv_fs_file_t lv_file; + lv_fs_res_t res = lv_fs_open(&lv_file, fn, LV_FS_MODE_RD); + if(res != LV_FS_RES_OK) { + return LV_RES_INV; + } + + SJPEG * sjpeg = (SJPEG *) dsc->user_data; + if(sjpeg == NULL) { + sjpeg = lv_mem_alloc(sizeof(SJPEG)); + if(! sjpeg) { + lv_fs_close(&lv_file); + return LV_RES_INV; + } + + memset(sjpeg, 0, sizeof(SJPEG)); + dsc->user_data = sjpeg; + sjpeg->sjpeg_data = (uint8_t *)((lv_img_dsc_t *)(dsc->src))->data; + sjpeg->sjpeg_data_size = ((lv_img_dsc_t *)(dsc->src))->data_size; + } + + uint8_t * workb_temp = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); + if(! workb_temp) { + lv_fs_close(&lv_file); + lv_sjpg_cleanup(sjpeg); + return LV_RES_INV; + } + + io_source_t io_source_temp; + io_source_temp.type = SJPEG_IO_SOURCE_DISK; + io_source_temp.raw_sjpg_data_next_read_pos = 0; + io_source_temp.img_cache_buff = NULL; + io_source_temp.lv_file = lv_file; + + JDEC jd_tmp; + + JRESULT rc = jd_prepare(&jd_tmp, input_func, workb_temp, (size_t)TJPGD_WORKBUFF_SIZE, &io_source_temp); + + lv_mem_free(workb_temp); + + + if(rc == JDR_OK) { + sjpeg->sjpeg_x_res = jd_tmp.width; + sjpeg->sjpeg_y_res = jd_tmp.height; + sjpeg->sjpeg_total_frames = 1; + sjpeg->sjpeg_single_frame_height = jd_tmp.height; + + sjpeg->frame_base_array = NULL; + sjpeg->frame_base_offset = lv_mem_alloc(sizeof(uint8_t *) * sjpeg->sjpeg_total_frames); + if(! sjpeg->frame_base_offset) { + lv_fs_close(&lv_file); + lv_sjpg_cleanup(sjpeg); + return LV_RES_INV; + } + + int img_frame_start_offset = 0; + sjpeg->frame_base_offset[0] = img_frame_start_offset; + + sjpeg->sjpeg_cache_frame_index = -1; + sjpeg->frame_cache = (void *)lv_mem_alloc(sjpeg->sjpeg_x_res * sjpeg->sjpeg_single_frame_height * 3); + if(! sjpeg->frame_cache) { + lv_fs_close(&lv_file); + lv_sjpg_cleanup(sjpeg); + return LV_RES_INV; + } + + sjpeg->io.img_cache_buff = sjpeg->frame_cache; + sjpeg->io.img_cache_x_res = sjpeg->sjpeg_x_res; + sjpeg->workb = lv_mem_alloc(TJPGD_WORKBUFF_SIZE); + if(! sjpeg->workb) { + lv_fs_close(&lv_file); + lv_sjpg_cleanup(sjpeg); + return LV_RES_INV; + } + + sjpeg->tjpeg_jd = lv_mem_alloc(sizeof(JDEC)); + if(! sjpeg->tjpeg_jd) { + lv_fs_close(&lv_file); + lv_sjpg_cleanup(sjpeg); + return LV_RES_INV; + } + + sjpeg->io.type = SJPEG_IO_SOURCE_DISK; + sjpeg->io.lv_file = lv_file; + dsc->img_data = NULL; + return LV_RES_OK; + + } + else { + if(dsc->user_data) lv_mem_free(dsc->user_data); + lv_fs_close(&lv_file); + return LV_RES_INV; + } + } + } + + return LV_RES_INV; +} + +/** + * Decode `len` pixels starting from the given `x`, `y` coordinates and store them in `buf`. + * Required only if the "open" function can't open the whole decoded pixel array. (dsc->img_data == NULL) + * @param decoder pointer to the decoder the function associated with + * @param dsc pointer to decoder descriptor + * @param x start x coordinate + * @param y start y coordinate + * @param len number of pixels to decode + * @param buf a buffer to store the decoded pixels + * @return LV_RES_OK: ok; LV_RES_INV: failed + */ + +static lv_res_t decoder_read_line(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc, lv_coord_t x, lv_coord_t y, + lv_coord_t len, uint8_t * buf) +{ + LV_UNUSED(decoder); + if(dsc->src_type == LV_IMG_SRC_VARIABLE) { + SJPEG * sjpeg = (SJPEG *) dsc->user_data; + JRESULT rc; + + int sjpeg_req_frame_index = y / sjpeg->sjpeg_single_frame_height; + + /*If line not from cache, refresh cache */ + if(sjpeg_req_frame_index != sjpeg->sjpeg_cache_frame_index) { + sjpeg->io.raw_sjpg_data = sjpeg->frame_base_array[ sjpeg_req_frame_index ]; + if(sjpeg_req_frame_index == (sjpeg->sjpeg_total_frames - 1)) { + /*This is the last frame. */ + const uint32_t frame_offset = (uint32_t)(sjpeg->io.raw_sjpg_data - sjpeg->sjpeg_data); + sjpeg->io.raw_sjpg_data_size = sjpeg->sjpeg_data_size - frame_offset; + } + else { + sjpeg->io.raw_sjpg_data_size = + (uint32_t)(sjpeg->frame_base_array[sjpeg_req_frame_index + 1] - sjpeg->io.raw_sjpg_data); + } + sjpeg->io.raw_sjpg_data_next_read_pos = 0; + rc = jd_prepare(sjpeg->tjpeg_jd, input_func, sjpeg->workb, (size_t)TJPGD_WORKBUFF_SIZE, &(sjpeg->io)); + if(rc != JDR_OK) return LV_RES_INV; + rc = jd_decomp(sjpeg->tjpeg_jd, img_data_cb, 0); + if(rc != JDR_OK) return LV_RES_INV; + sjpeg->sjpeg_cache_frame_index = sjpeg_req_frame_index; + } + + int offset = 0; + uint8_t * cache = (uint8_t *)sjpeg->frame_cache + x * 3 + (y % sjpeg->sjpeg_single_frame_height) * sjpeg->sjpeg_x_res * + 3; + +#if LV_COLOR_DEPTH == 32 + for(int i = 0; i < len; i++) { + buf[offset + 3] = 0xff; + buf[offset + 2] = *cache++; + buf[offset + 1] = *cache++; + buf[offset + 0] = *cache++; + offset += 4; + } + +#elif LV_COLOR_DEPTH == 16 + + for(int i = 0; i < len; i++) { + uint16_t col_16bit = (*cache++ & 0xf8) << 8; + col_16bit |= (*cache++ & 0xFC) << 3; + col_16bit |= (*cache++ >> 3); +#if LV_BIG_ENDIAN_SYSTEM == 1 || LV_COLOR_16_SWAP == 1 + buf[offset++] = col_16bit >> 8; + buf[offset++] = col_16bit & 0xff; +#else + buf[offset++] = col_16bit & 0xff; + buf[offset++] = col_16bit >> 8; +#endif // LV_BIG_ENDIAN_SYSTEM + } + +#elif LV_COLOR_DEPTH == 8 + + for(int i = 0; i < len; i++) { + uint8_t col_8bit = (*cache++ & 0xC0); + col_8bit |= (*cache++ & 0xe0) >> 2; + col_8bit |= (*cache++ & 0xe0) >> 5; + buf[offset++] = col_8bit; + } +#else +#error Unsupported LV_COLOR_DEPTH + + +#endif // LV_COLOR_DEPTH + return LV_RES_OK; + } + else if(dsc->src_type == LV_IMG_SRC_FILE) { + SJPEG * sjpeg = (SJPEG *) dsc->user_data; + JRESULT rc; + int sjpeg_req_frame_index = y / sjpeg->sjpeg_single_frame_height; + + lv_fs_file_t * lv_file_p = &(sjpeg->io.lv_file); + if(!lv_file_p) goto end; + + /*If line not from cache, refresh cache */ + if(sjpeg_req_frame_index != sjpeg->sjpeg_cache_frame_index) { + sjpeg->io.raw_sjpg_data_next_read_pos = (int)(sjpeg->frame_base_offset [ sjpeg_req_frame_index ]); + lv_fs_seek(&(sjpeg->io.lv_file), sjpeg->io.raw_sjpg_data_next_read_pos, LV_FS_SEEK_SET); + + rc = jd_prepare(sjpeg->tjpeg_jd, input_func, sjpeg->workb, (size_t)TJPGD_WORKBUFF_SIZE, &(sjpeg->io)); + if(rc != JDR_OK) return LV_RES_INV; + + rc = jd_decomp(sjpeg->tjpeg_jd, img_data_cb, 0); + if(rc != JDR_OK) return LV_RES_INV; + + sjpeg->sjpeg_cache_frame_index = sjpeg_req_frame_index; + } + + int offset = 0; + uint8_t * cache = (uint8_t *)sjpeg->frame_cache + x * 3 + (y % sjpeg->sjpeg_single_frame_height) * sjpeg->sjpeg_x_res * + 3; + +#if LV_COLOR_DEPTH == 32 + for(int i = 0; i < len; i++) { + buf[offset + 3] = 0xff; + buf[offset + 2] = *cache++; + buf[offset + 1] = *cache++; + buf[offset + 0] = *cache++; + offset += 4; + } +#elif LV_COLOR_DEPTH == 16 + + for(int i = 0; i < len; i++) { + uint16_t col_8bit = (*cache++ & 0xf8) << 8; + col_8bit |= (*cache++ & 0xFC) << 3; + col_8bit |= (*cache++ >> 3); +#if LV_BIG_ENDIAN_SYSTEM == 1 || LV_COLOR_16_SWAP == 1 + buf[offset++] = col_8bit >> 8; + buf[offset++] = col_8bit & 0xff; +#else + buf[offset++] = col_8bit & 0xff; + buf[offset++] = col_8bit >> 8; +#endif // LV_BIG_ENDIAN_SYSTEM + } + +#elif LV_COLOR_DEPTH == 8 + + for(int i = 0; i < len; i++) { + uint8_t col_8bit = (*cache++ & 0xC0); + col_8bit |= (*cache++ & 0xe0) >> 2; + col_8bit |= (*cache++ & 0xe0) >> 5; + buf[offset++] = col_8bit; + } + +#else +#error Unsupported LV_COLOR_DEPTH + + +#endif // LV_COLOR_DEPTH + + return LV_RES_OK; + } +end: + return LV_RES_INV; +} + +/** + * Free the allocated resources + * @param decoder pointer to the decoder where this function belongs + * @param dsc pointer to a descriptor which describes this decoding session + */ +static void decoder_close(lv_img_decoder_t * decoder, lv_img_decoder_dsc_t * dsc) +{ + LV_UNUSED(decoder); + /*Free all allocated data*/ + SJPEG * sjpeg = (SJPEG *) dsc->user_data; + if(!sjpeg) return; + + switch(dsc->src_type) { + case LV_IMG_SRC_FILE: + if(sjpeg->io.lv_file.file_d) { + lv_fs_close(&(sjpeg->io.lv_file)); + } + lv_sjpg_cleanup(sjpeg); + break; + + case LV_IMG_SRC_VARIABLE: + lv_sjpg_cleanup(sjpeg); + break; + + default: + ; + } +} + +static int is_jpg(const uint8_t * raw_data, size_t len) +{ + const uint8_t jpg_signature[] = {0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46}; + if(len < sizeof(jpg_signature)) return false; + return memcmp(jpg_signature, raw_data, sizeof(jpg_signature)) == 0; +} + +static void lv_sjpg_free(SJPEG * sjpeg) +{ + if(sjpeg->frame_cache) lv_mem_free(sjpeg->frame_cache); + if(sjpeg->frame_base_array) lv_mem_free(sjpeg->frame_base_array); + if(sjpeg->frame_base_offset) lv_mem_free(sjpeg->frame_base_offset); + if(sjpeg->tjpeg_jd) lv_mem_free(sjpeg->tjpeg_jd); + if(sjpeg->workb) lv_mem_free(sjpeg->workb); +} + +static void lv_sjpg_cleanup(SJPEG * sjpeg) +{ + if(! sjpeg) return; + + lv_sjpg_free(sjpeg); + lv_mem_free(sjpeg); +} + +#endif /*LV_USE_SJPG*/ diff --git a/lib/lvgl/src/extra/libs/sjpg/lv_sjpg.h b/lib/lvgl/src/extra/libs/sjpg/lv_sjpg.h new file mode 100644 index 00000000..d06e80de --- /dev/null +++ b/lib/lvgl/src/extra/libs/sjpg/lv_sjpg.h @@ -0,0 +1,43 @@ +/** + * @file lv_sjpg.h + * + */ + +#ifndef LV_SJPEG_H +#define LV_SJPEG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#if LV_USE_SJPG + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +void lv_split_jpeg_init(void); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_SJPG*/ + +#ifdef __cplusplus +} +#endif + +#endif /* LV_SJPEG_H */ diff --git a/lib/lvgl/src/extra/libs/sjpg/tjpgd.c b/lib/lvgl/src/extra/libs/sjpg/tjpgd.c new file mode 100644 index 00000000..47ddefb6 --- /dev/null +++ b/lib/lvgl/src/extra/libs/sjpg/tjpgd.c @@ -0,0 +1,1155 @@ +/*----------------------------------------------------------------------------/ +/ TJpgDec - Tiny JPEG Decompressor R0.03 (C)ChaN, 2021 +/-----------------------------------------------------------------------------/ +/ The TJpgDec is a generic JPEG decompressor module for tiny embedded systems. +/ This is a free software that opened for education, research and commercial +/ developments under license policy of following terms. +/ +/ Copyright (C) 2021, ChaN, all right reserved. +/ +/ * The TJpgDec module is a free software and there is NO WARRANTY. +/ * No restriction on use. You can use, modify and redistribute it for +/ personal, non-profit or commercial products UNDER YOUR RESPONSIBILITY. +/ * Redistributions of source code must retain the above copyright notice. +/ +/-----------------------------------------------------------------------------/ +/ Oct 04, 2011 R0.01 First release. +/ Feb 19, 2012 R0.01a Fixed decompression fails when scan starts with an escape seq. +/ Sep 03, 2012 R0.01b Added JD_TBLCLIP option. +/ Mar 16, 2019 R0.01c Supprted stdint.h. +/ Jul 01, 2020 R0.01d Fixed wrong integer type usage. +/ May 08, 2021 R0.02 Supprted grayscale image. Separated configuration options. +/ Jun 11, 2021 R0.02a Some performance improvement. +/ Jul 01, 2021 R0.03 Added JD_FASTDECODE option. +/ Some performance improvement. +/----------------------------------------------------------------------------*/ + +#include "tjpgd.h" +#if LV_USE_SJPG + +#if JD_FASTDECODE == 2 +#define HUFF_BIT 10 /* Bit length to apply fast huffman decode */ +#define HUFF_LEN (1 << HUFF_BIT) +#define HUFF_MASK (HUFF_LEN - 1) +#endif + + +/*-----------------------------------------------*/ +/* Zigzag-order to raster-order conversion table */ +/*-----------------------------------------------*/ + +static const uint8_t Zig[64] = { /* Zigzag-order to raster-order conversion table */ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 +}; + + + +/*-------------------------------------------------*/ +/* Input scale factor of Arai algorithm */ +/* (scaled up 16 bits for fixed point operations) */ +/*-------------------------------------------------*/ + +static const uint16_t Ipsf[64] = { /* See also aa_idct.png */ + (uint16_t)(1.00000*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.17588*8192), (uint16_t)(1.00000*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.27590*8192), + (uint16_t)(1.38704*8192), (uint16_t)(1.92388*8192), (uint16_t)(1.81226*8192), (uint16_t)(1.63099*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.08979*8192), (uint16_t)(0.75066*8192), (uint16_t)(0.38268*8192), + (uint16_t)(1.30656*8192), (uint16_t)(1.81226*8192), (uint16_t)(1.70711*8192), (uint16_t)(1.53636*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.02656*8192), (uint16_t)(0.70711*8192), (uint16_t)(0.36048*8192), + (uint16_t)(1.17588*8192), (uint16_t)(1.63099*8192), (uint16_t)(1.53636*8192), (uint16_t)(1.38268*8192), (uint16_t)(1.17588*8192), (uint16_t)(0.92388*8192), (uint16_t)(0.63638*8192), (uint16_t)(0.32442*8192), + (uint16_t)(1.00000*8192), (uint16_t)(1.38704*8192), (uint16_t)(1.30656*8192), (uint16_t)(1.17588*8192), (uint16_t)(1.00000*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.27590*8192), + (uint16_t)(0.78570*8192), (uint16_t)(1.08979*8192), (uint16_t)(1.02656*8192), (uint16_t)(0.92388*8192), (uint16_t)(0.78570*8192), (uint16_t)(0.61732*8192), (uint16_t)(0.42522*8192), (uint16_t)(0.21677*8192), + (uint16_t)(0.54120*8192), (uint16_t)(0.75066*8192), (uint16_t)(0.70711*8192), (uint16_t)(0.63638*8192), (uint16_t)(0.54120*8192), (uint16_t)(0.42522*8192), (uint16_t)(0.29290*8192), (uint16_t)(0.14932*8192), + (uint16_t)(0.27590*8192), (uint16_t)(0.38268*8192), (uint16_t)(0.36048*8192), (uint16_t)(0.32442*8192), (uint16_t)(0.27590*8192), (uint16_t)(0.21678*8192), (uint16_t)(0.14932*8192), (uint16_t)(0.07612*8192) +}; + + + +/*---------------------------------------------*/ +/* Conversion table for fast clipping process */ +/*---------------------------------------------*/ + +#if JD_TBLCLIP + +#define BYTECLIP(v) Clip8[(unsigned int)(v) & 0x3FF] + +static const uint8_t Clip8[1024] = { + /* 0..255 */ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, + 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, + 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, + 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + /* 256..511 */ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + /* -512..-257 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + /* -256..-1 */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +#else /* JD_TBLCLIP */ + +static uint8_t BYTECLIP (int val) +{ + if (val < 0) return 0; + if (val > 255) return 255; + return (uint8_t)val; +} + +#endif + + + +/*-----------------------------------------------------------------------*/ +/* Allocate a memory block from memory pool */ +/*-----------------------------------------------------------------------*/ + +static void* alloc_pool ( /* Pointer to allocated memory block (NULL:no memory available) */ + JDEC* jd, /* Pointer to the decompressor object */ + size_t ndata /* Number of bytes to allocate */ +) +{ + char *rp = 0; + + + ndata = (ndata + 3) & ~3; /* Align block size to the word boundary */ + + if (jd->sz_pool >= ndata) { + jd->sz_pool -= ndata; + rp = (char*)jd->pool; /* Get start of available memory pool */ + jd->pool = (void*)(rp + ndata); /* Allocate requierd bytes */ + } + + return (void*)rp; /* Return allocated memory block (NULL:no memory to allocate) */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create de-quantization and prescaling tables with a DQT segment */ +/*-----------------------------------------------------------------------*/ + +static JRESULT create_qt_tbl ( /* 0:OK, !0:Failed */ + JDEC* jd, /* Pointer to the decompressor object */ + const uint8_t* data, /* Pointer to the quantizer tables */ + size_t ndata /* Size of input data */ +) +{ + unsigned int i, zi; + uint8_t d; + int32_t *pb; + + + while (ndata) { /* Process all tables in the segment */ + if (ndata < 65) return JDR_FMT1; /* Err: table size is unaligned */ + ndata -= 65; + d = *data++; /* Get table property */ + if (d & 0xF0) return JDR_FMT1; /* Err: not 8-bit resolution */ + i = d & 3; /* Get table ID */ + pb = alloc_pool(jd, 64 * sizeof (int32_t));/* Allocate a memory block for the table */ + if (!pb) return JDR_MEM1; /* Err: not enough memory */ + jd->qttbl[i] = pb; /* Register the table */ + for (i = 0; i < 64; i++) { /* Load the table */ + zi = Zig[i]; /* Zigzag-order to raster-order conversion */ + pb[zi] = (int32_t)((uint32_t)*data++ * Ipsf[zi]); /* Apply scale factor of Arai algorithm to the de-quantizers */ + } + } + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Create huffman code tables with a DHT segment */ +/*-----------------------------------------------------------------------*/ + +static JRESULT create_huffman_tbl ( /* 0:OK, !0:Failed */ + JDEC* jd, /* Pointer to the decompressor object */ + const uint8_t* data, /* Pointer to the packed huffman tables */ + size_t ndata /* Size of input data */ +) +{ + unsigned int i, j, b, cls, num; + size_t np; + uint8_t d, *pb, *pd; + uint16_t hc, *ph; + + + while (ndata) { /* Process all tables in the segment */ + if (ndata < 17) return JDR_FMT1; /* Err: wrong data size */ + ndata -= 17; + d = *data++; /* Get table number and class */ + if (d & 0xEE) return JDR_FMT1; /* Err: invalid class/number */ + cls = d >> 4; num = d & 0x0F; /* class = dc(0)/ac(1), table number = 0/1 */ + pb = alloc_pool(jd, 16); /* Allocate a memory block for the bit distribution table */ + if (!pb) return JDR_MEM1; /* Err: not enough memory */ + jd->huffbits[num][cls] = pb; + for (np = i = 0; i < 16; i++) { /* Load number of patterns for 1 to 16-bit code */ + np += (pb[i] = *data++); /* Get sum of code words for each code */ + } + ph = alloc_pool(jd, np * sizeof (uint16_t));/* Allocate a memory block for the code word table */ + if (!ph) return JDR_MEM1; /* Err: not enough memory */ + jd->huffcode[num][cls] = ph; + hc = 0; + for (j = i = 0; i < 16; i++) { /* Re-build huffman code word table */ + b = pb[i]; + while (b--) ph[j++] = hc++; + hc <<= 1; + } + + if (ndata < np) return JDR_FMT1; /* Err: wrong data size */ + ndata -= np; + pd = alloc_pool(jd, np); /* Allocate a memory block for the decoded data */ + if (!pd) return JDR_MEM1; /* Err: not enough memory */ + jd->huffdata[num][cls] = pd; + for (i = 0; i < np; i++) { /* Load decoded data corresponds to each code word */ + d = *data++; + if (!cls && d > 11) return JDR_FMT1; + pd[i] = d; + } +#if JD_FASTDECODE == 2 + { /* Create fast huffman decode table */ + unsigned int span, td, ti; + uint16_t *tbl_ac = 0; + uint8_t *tbl_dc = 0; + + if (cls) { + tbl_ac = alloc_pool(jd, HUFF_LEN * sizeof (uint16_t)); /* LUT for AC elements */ + if (!tbl_ac) return JDR_MEM1; /* Err: not enough memory */ + jd->hufflut_ac[num] = tbl_ac; + memset(tbl_ac, 0xFF, HUFF_LEN * sizeof (uint16_t)); /* Default value (0xFFFF: may be long code) */ + } else { + tbl_dc = alloc_pool(jd, HUFF_LEN * sizeof (uint8_t)); /* LUT for AC elements */ + if (!tbl_dc) return JDR_MEM1; /* Err: not enough memory */ + jd->hufflut_dc[num] = tbl_dc; + memset(tbl_dc, 0xFF, HUFF_LEN * sizeof (uint8_t)); /* Default value (0xFF: may be long code) */ + } + for (i = b = 0; b < HUFF_BIT; b++) { /* Create LUT */ + for (j = pb[b]; j; j--) { + ti = ph[i] << (HUFF_BIT - 1 - b) & HUFF_MASK; /* Index of input pattern for the code */ + if (cls) { + td = pd[i++] | ((b + 1) << 8); /* b15..b8: code length, b7..b0: zero run and data length */ + for (span = 1 << (HUFF_BIT - 1 - b); span; span--, tbl_ac[ti++] = (uint16_t)td) ; + } else { + td = pd[i++] | ((b + 1) << 4); /* b7..b4: code length, b3..b0: data length */ + for (span = 1 << (HUFF_BIT - 1 - b); span; span--, tbl_dc[ti++] = (uint8_t)td) ; + } + } + } + jd->longofs[num][cls] = i; /* Code table offset for long code */ + } +#endif + } + + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Extract a huffman decoded data from input stream */ +/*-----------------------------------------------------------------------*/ + +static int huffext ( /* >=0: decoded data, <0: error code */ + JDEC* jd, /* Pointer to the decompressor object */ + unsigned int id, /* Table ID (0:Y, 1:C) */ + unsigned int cls /* Table class (0:DC, 1:AC) */ +) +{ + size_t dc = jd->dctr; + uint8_t *dp = jd->dptr; + unsigned int d, flg = 0; + +#if JD_FASTDECODE == 0 + uint8_t bm, nd, bl; + const uint8_t *hb = jd->huffbits[id][cls]; /* Bit distribution table */ + const uint16_t *hc = jd->huffcode[id][cls]; /* Code word table */ + const uint8_t *hd = jd->huffdata[id][cls]; /* Data table */ + + + bm = jd->dbit; /* Bit mask to extract */ + d = 0; bl = 16; /* Max code length */ + do { + if (!bm) { /* Next byte? */ + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; /* Top of input buffer */ + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ + } else { + dp++; /* Next data ptr */ + } + dc--; /* Decrement number of available bytes */ + if (flg) { /* In flag sequence? */ + flg = 0; /* Exit flag sequence */ + if (*dp != 0) return 0 - (int)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */ + *dp = 0xFF; /* The flag is a data 0xFF */ + } else { + if (*dp == 0xFF) { /* Is start of flag sequence? */ + flg = 1; continue; /* Enter flag sequence, get trailing byte */ + } + } + bm = 0x80; /* Read from MSB */ + } + d <<= 1; /* Get a bit */ + if (*dp & bm) d++; + bm >>= 1; + + for (nd = *hb++; nd; nd--) { /* Search the code word in this bit length */ + if (d == *hc++) { /* Matched? */ + jd->dbit = bm; jd->dctr = dc; jd->dptr = dp; + return *hd; /* Return the decoded data */ + } + hd++; + } + bl--; + } while (bl); + +#else + const uint8_t *hb, *hd; + const uint16_t *hc; + unsigned int nc, bl, wbit = jd->dbit % 32; + uint32_t w = jd->wreg & ((1UL << wbit) - 1); + + + while (wbit < 16) { /* Prepare 16 bits into the working register */ + if (jd->marker) { + d = 0xFF; /* Input stream has stalled for a marker. Generate stuff bits */ + } else { + if (!dc) { /* Buffer empty, re-fill input buffer */ + dp = jd->inbuf; /* Top of input buffer */ + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ + } + d = *dp++; dc--; + if (flg) { /* In flag sequence? */ + flg = 0; /* Exit flag sequence */ + if (d != 0) jd->marker = d; /* Not an escape of 0xFF but a marker */ + d = 0xFF; + } else { + if (d == 0xFF) { /* Is start of flag sequence? */ + flg = 1; continue; /* Enter flag sequence, get trailing byte */ + } + } + } + w = w << 8 | d; /* Shift 8 bits in the working register */ + wbit += 8; + } + jd->dctr = dc; jd->dptr = dp; + jd->wreg = w; + +#if JD_FASTDECODE == 2 + /* Table serch for the short codes */ + d = (unsigned int)(w >> (wbit - HUFF_BIT)); /* Short code as table index */ + if (cls) { /* AC element */ + d = jd->hufflut_ac[id][d]; /* Table decode */ + if (d != 0xFFFF) { /* It is done if hit in short code */ + jd->dbit = wbit - (d >> 8); /* Snip the code length */ + return d & 0xFF; /* b7..0: zero run and following data bits */ + } + } else { /* DC element */ + d = jd->hufflut_dc[id][d]; /* Table decode */ + if (d != 0xFF) { /* It is done if hit in short code */ + jd->dbit = wbit - (d >> 4); /* Snip the code length */ + return d & 0xF; /* b3..0: following data bits */ + } + } + + /* Incremental serch for the codes longer than HUFF_BIT */ + hb = jd->huffbits[id][cls] + HUFF_BIT; /* Bit distribution table */ + hc = jd->huffcode[id][cls] + jd->longofs[id][cls]; /* Code word table */ + hd = jd->huffdata[id][cls] + jd->longofs[id][cls]; /* Data table */ + bl = HUFF_BIT + 1; +#else + /* Incremental serch for all codes */ + hb = jd->huffbits[id][cls]; /* Bit distribution table */ + hc = jd->huffcode[id][cls]; /* Code word table */ + hd = jd->huffdata[id][cls]; /* Data table */ + bl = 1; +#endif + for ( ; bl <= 16; bl++) { /* Incremental search */ + nc = *hb++; + if (nc) { + d = w >> (wbit - bl); + do { /* Search the code word in this bit length */ + if (d == *hc++) { /* Matched? */ + jd->dbit = wbit - bl; /* Snip the huffman code */ + return *hd; /* Return the decoded data */ + } + hd++; + } while (--nc); + } + } +#endif + + return 0 - (int)JDR_FMT1; /* Err: code not found (may be collapted data) */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Extract N bits from input stream */ +/*-----------------------------------------------------------------------*/ + +static int bitext ( /* >=0: extracted data, <0: error code */ + JDEC* jd, /* Pointer to the decompressor object */ + unsigned int nbit /* Number of bits to extract (1 to 16) */ +) +{ + size_t dc = jd->dctr; + uint8_t *dp = jd->dptr; + unsigned int d, flg = 0; + +#if JD_FASTDECODE == 0 + uint8_t mbit = jd->dbit; + + d = 0; + do { + if (!mbit) { /* Next byte? */ + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; /* Top of input buffer */ + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ + } else { + dp++; /* Next data ptr */ + } + dc--; /* Decrement number of available bytes */ + if (flg) { /* In flag sequence? */ + flg = 0; /* Exit flag sequence */ + if (*dp != 0) return 0 - (int)JDR_FMT1; /* Err: unexpected flag is detected (may be collapted data) */ + *dp = 0xFF; /* The flag is a data 0xFF */ + } else { + if (*dp == 0xFF) { /* Is start of flag sequence? */ + flg = 1; continue; /* Enter flag sequence */ + } + } + mbit = 0x80; /* Read from MSB */ + } + d <<= 1; /* Get a bit */ + if (*dp & mbit) d |= 1; + mbit >>= 1; + nbit--; + } while (nbit); + + jd->dbit = mbit; jd->dctr = dc; jd->dptr = dp; + return (int)d; + +#else + unsigned int wbit = jd->dbit % 32; + uint32_t w = jd->wreg & ((1UL << wbit) - 1); + + + while (wbit < nbit) { /* Prepare nbit bits into the working register */ + if (jd->marker) { + d = 0xFF; /* Input stream stalled, generate stuff bits */ + } else { + if (!dc) { /* Buffer empty, re-fill input buffer */ + dp = jd->inbuf; /* Top of input buffer */ + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return 0 - (int)JDR_INP; /* Err: read error or wrong stream termination */ + } + d = *dp++; dc--; + if (flg) { /* In flag sequence? */ + flg = 0; /* Exit flag sequence */ + if (d != 0) jd->marker = d; /* Not an escape of 0xFF but a marker */ + d = 0xFF; + } else { + if (d == 0xFF) { /* Is start of flag sequence? */ + flg = 1; continue; /* Enter flag sequence, get trailing byte */ + } + } + } + w = w << 8 | d; /* Get 8 bits into the working register */ + wbit += 8; + } + jd->wreg = w; jd->dbit = wbit - nbit; + jd->dctr = dc; jd->dptr = dp; + + return (int)(w >> ((wbit - nbit) % 32)); +#endif +} + + + + +/*-----------------------------------------------------------------------*/ +/* Process restart interval */ +/*-----------------------------------------------------------------------*/ + +static JRESULT restart ( + JDEC* jd, /* Pointer to the decompressor object */ + uint16_t rstn /* Expected restert sequense number */ +) +{ + unsigned int i; + uint8_t *dp = jd->dptr; + size_t dc = jd->dctr; + +#if JD_FASTDECODE == 0 + uint16_t d = 0; + + /* Get two bytes from the input stream */ + for (i = 0; i < 2; i++) { + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return JDR_INP; + } else { + dp++; + } + dc--; + d = d << 8 | *dp; /* Get a byte */ + } + jd->dptr = dp; jd->dctr = dc; jd->dbit = 0; + + /* Check the marker */ + if ((d & 0xFFD8) != 0xFFD0 || (d & 7) != (rstn & 7)) { + return JDR_FMT1; /* Err: expected RSTn marker is not detected (may be collapted data) */ + } + +#else + uint16_t marker; + + + if (jd->marker) { /* Generate a maker if it has been detected */ + marker = 0xFF00 | jd->marker; + jd->marker = 0; + } else { + marker = 0; + for (i = 0; i < 2; i++) { /* Get a restart marker */ + if (!dc) { /* No input data is available, re-fill input buffer */ + dp = jd->inbuf; + dc = jd->infunc(jd, dp, JD_SZBUF); + if (!dc) return JDR_INP; + } + marker = (marker << 8) | *dp++; /* Get a byte */ + dc--; + } + jd->dptr = dp; jd->dctr = dc; + } + + /* Check the marker */ + if ((marker & 0xFFD8) != 0xFFD0 || (marker & 7) != (rstn & 7)) { + return JDR_FMT1; /* Err: expected RSTn marker was not detected (may be collapted data) */ + } + + jd->dbit = 0; /* Discard stuff bits */ +#endif + + jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Reset DC offset */ + return JDR_OK; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Apply Inverse-DCT in Arai Algorithm (see also aa_idct.png) */ +/*-----------------------------------------------------------------------*/ + +static void block_idct ( + int32_t* src, /* Input block data (de-quantized and pre-scaled for Arai Algorithm) */ + jd_yuv_t* dst /* Pointer to the destination to store the block as byte array */ +) +{ + const int32_t M13 = (int32_t)(1.41421*4096), M2 = (int32_t)(1.08239*4096), M4 = (int32_t)(2.61313*4096), M5 = (int32_t)(1.84776*4096); + int32_t v0, v1, v2, v3, v4, v5, v6, v7; + int32_t t10, t11, t12, t13; + int i; + + /* Process columns */ + for (i = 0; i < 8; i++) { + v0 = src[8 * 0]; /* Get even elements */ + v1 = src[8 * 2]; + v2 = src[8 * 4]; + v3 = src[8 * 6]; + + t10 = v0 + v2; /* Process the even elements */ + t12 = v0 - v2; + t11 = (v1 - v3) * M13 >> 12; + v3 += v1; + t11 -= v3; + v0 = t10 + v3; + v3 = t10 - v3; + v1 = t11 + t12; + v2 = t12 - t11; + + v4 = src[8 * 7]; /* Get odd elements */ + v5 = src[8 * 1]; + v6 = src[8 * 5]; + v7 = src[8 * 3]; + + t10 = v5 - v4; /* Process the odd elements */ + t11 = v5 + v4; + t12 = v6 - v7; + v7 += v6; + v5 = (t11 - v7) * M13 >> 12; + v7 += t11; + t13 = (t10 + t12) * M5 >> 12; + v4 = t13 - (t10 * M2 >> 12); + v6 = t13 - (t12 * M4 >> 12) - v7; + v5 -= v6; + v4 -= v5; + + src[8 * 0] = v0 + v7; /* Write-back transformed values */ + src[8 * 7] = v0 - v7; + src[8 * 1] = v1 + v6; + src[8 * 6] = v1 - v6; + src[8 * 2] = v2 + v5; + src[8 * 5] = v2 - v5; + src[8 * 3] = v3 + v4; + src[8 * 4] = v3 - v4; + + src++; /* Next column */ + } + + /* Process rows */ + src -= 8; + for (i = 0; i < 8; i++) { + v0 = src[0] + (128L << 8); /* Get even elements (remove DC offset (-128) here) */ + v1 = src[2]; + v2 = src[4]; + v3 = src[6]; + + t10 = v0 + v2; /* Process the even elements */ + t12 = v0 - v2; + t11 = (v1 - v3) * M13 >> 12; + v3 += v1; + t11 -= v3; + v0 = t10 + v3; + v3 = t10 - v3; + v1 = t11 + t12; + v2 = t12 - t11; + + v4 = src[7]; /* Get odd elements */ + v5 = src[1]; + v6 = src[5]; + v7 = src[3]; + + t10 = v5 - v4; /* Process the odd elements */ + t11 = v5 + v4; + t12 = v6 - v7; + v7 += v6; + v5 = (t11 - v7) * M13 >> 12; + v7 += t11; + t13 = (t10 + t12) * M5 >> 12; + v4 = t13 - (t10 * M2 >> 12); + v6 = t13 - (t12 * M4 >> 12) - v7; + v5 -= v6; + v4 -= v5; + + /* Descale the transformed values 8 bits and output a row */ +#if JD_FASTDECODE >= 1 + dst[0] = (int16_t)((v0 + v7) >> 8); + dst[7] = (int16_t)((v0 - v7) >> 8); + dst[1] = (int16_t)((v1 + v6) >> 8); + dst[6] = (int16_t)((v1 - v6) >> 8); + dst[2] = (int16_t)((v2 + v5) >> 8); + dst[5] = (int16_t)((v2 - v5) >> 8); + dst[3] = (int16_t)((v3 + v4) >> 8); + dst[4] = (int16_t)((v3 - v4) >> 8); +#else + dst[0] = BYTECLIP((v0 + v7) >> 8); + dst[7] = BYTECLIP((v0 - v7) >> 8); + dst[1] = BYTECLIP((v1 + v6) >> 8); + dst[6] = BYTECLIP((v1 - v6) >> 8); + dst[2] = BYTECLIP((v2 + v5) >> 8); + dst[5] = BYTECLIP((v2 - v5) >> 8); + dst[3] = BYTECLIP((v3 + v4) >> 8); + dst[4] = BYTECLIP((v3 - v4) >> 8); +#endif + + dst += 8; src += 8; /* Next row */ + } +} + + + + +/*-----------------------------------------------------------------------*/ +/* Load all blocks in an MCU into working buffer */ +/*-----------------------------------------------------------------------*/ + +static JRESULT mcu_load ( + JDEC* jd /* Pointer to the decompressor object */ +) +{ + int32_t *tmp = (int32_t*)jd->workbuf; /* Block working buffer for de-quantize and IDCT */ + int d, e; + unsigned int blk, nby, i, bc, z, id, cmp; + jd_yuv_t *bp; + const int32_t *dqf; + + + nby = jd->msx * jd->msy; /* Number of Y blocks (1, 2 or 4) */ + bp = jd->mcubuf; /* Pointer to the first block of MCU */ + + for (blk = 0; blk < nby + 2; blk++) { /* Get nby Y blocks and two C blocks */ + cmp = (blk < nby) ? 0 : blk - nby + 1; /* Component number 0:Y, 1:Cb, 2:Cr */ + + if (cmp && jd->ncomp != 3) { /* Clear C blocks if not exist (monochrome image) */ + for (i = 0; i < 64; bp[i++] = 128) ; + + } else { /* Load Y/C blocks from input stream */ + id = cmp ? 1 : 0; /* Huffman table ID of this component */ + + /* Extract a DC element from input stream */ + d = huffext(jd, id, 0); /* Extract a huffman coded data (bit length) */ + if (d < 0) return (JRESULT)(0 - d); /* Err: invalid code or input */ + bc = (unsigned int)d; + d = jd->dcv[cmp]; /* DC value of previous block */ + if (bc) { /* If there is any difference from previous block */ + e = bitext(jd, bc); /* Extract data bits */ + if (e < 0) return (JRESULT)(0 - e); /* Err: input */ + bc = 1 << (bc - 1); /* MSB position */ + if (!(e & bc)) e -= (bc << 1) - 1; /* Restore negative value if needed */ + d += e; /* Get current value */ + jd->dcv[cmp] = (int16_t)d; /* Save current DC value for next block */ + } + dqf = jd->qttbl[jd->qtid[cmp]]; /* De-quantizer table ID for this component */ + tmp[0] = d * dqf[0] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ + + /* Extract following 63 AC elements from input stream */ + memset(&tmp[1], 0, 63 * sizeof (int32_t)); /* Initialize all AC elements */ + z = 1; /* Top of the AC elements (in zigzag-order) */ + do { + d = huffext(jd, id, 1); /* Extract a huffman coded value (zero runs and bit length) */ + if (d == 0) break; /* EOB? */ + if (d < 0) return (JRESULT)(0 - d); /* Err: invalid code or input error */ + bc = (unsigned int)d; + z += bc >> 4; /* Skip leading zero run */ + if (z >= 64) return JDR_FMT1; /* Too long zero run */ + if (bc &= 0x0F) { /* Bit length? */ + d = bitext(jd, bc); /* Extract data bits */ + if (d < 0) return (JRESULT)(0 - d); /* Err: input device */ + bc = 1 << (bc - 1); /* MSB position */ + if (!(d & bc)) d -= (bc << 1) - 1; /* Restore negative value if needed */ + i = Zig[z]; /* Get raster-order index */ + tmp[i] = d * dqf[i] >> 8; /* De-quantize, apply scale factor of Arai algorithm and descale 8 bits */ + } + } while (++z < 64); /* Next AC element */ + + if (JD_FORMAT != 2 || !cmp) { /* C components may not be processed if in grayscale output */ + if (z == 1 || (JD_USE_SCALE && jd->scale == 3)) { /* If no AC element or scale ratio is 1/8, IDCT can be ommited and the block is filled with DC value */ + d = (jd_yuv_t)((*tmp / 256) + 128); + if (JD_FASTDECODE >= 1) { + for (i = 0; i < 64; bp[i++] = d) ; + } else { + memset(bp, d, 64); + } + } else { + block_idct(tmp, bp); /* Apply IDCT and store the block to the MCU buffer */ + } + } + } + + bp += 64; /* Next block */ + } + + return JDR_OK; /* All blocks have been loaded successfully */ +} + + + + +/*-----------------------------------------------------------------------*/ +/* Output an MCU: Convert YCrCb to RGB and output it in RGB form */ +/*-----------------------------------------------------------------------*/ + +static JRESULT mcu_output ( + JDEC* jd, /* Pointer to the decompressor object */ + int (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ + unsigned int img_x, /* MCU location in the image */ + unsigned int img_y /* MCU location in the image */ +) +{ + const int CVACC = (sizeof (int) > 2) ? 1024 : 128; /* Adaptive accuracy for both 16-/32-bit systems */ + unsigned int ix, iy, mx, my, rx, ry; + int yy, cb, cr; + jd_yuv_t *py, *pc; + uint8_t *pix; + JRECT rect; + + + mx = jd->msx * 8; my = jd->msy * 8; /* MCU size (pixel) */ + rx = (img_x + mx <= jd->width) ? mx : jd->width - img_x; /* Output rectangular size (it may be clipped at right/bottom end of image) */ + ry = (img_y + my <= jd->height) ? my : jd->height - img_y; + if (JD_USE_SCALE) { + rx >>= jd->scale; ry >>= jd->scale; + if (!rx || !ry) return JDR_OK; /* Skip this MCU if all pixel is to be rounded off */ + img_x >>= jd->scale; img_y >>= jd->scale; + } + rect.left = img_x; rect.right = img_x + rx - 1; /* Rectangular area in the frame buffer */ + rect.top = img_y; rect.bottom = img_y + ry - 1; + + + if (!JD_USE_SCALE || jd->scale != 3) { /* Not for 1/8 scaling */ + pix = (uint8_t*)jd->workbuf; + + if (JD_FORMAT != 2) { /* RGB output (build an RGB MCU from Y/C component) */ + for (iy = 0; iy < my; iy++) { + pc = py = jd->mcubuf; + if (my == 16) { /* Double block height? */ + pc += 64 * 4 + (iy >> 1) * 8; + if (iy >= 8) py += 64; + } else { /* Single block height */ + pc += mx * 8 + iy * 8; + } + py += iy * 8; + for (ix = 0; ix < mx; ix++) { + cb = pc[0] - 128; /* Get Cb/Cr component and remove offset */ + cr = pc[64] - 128; + if (mx == 16) { /* Double block width? */ + if (ix == 8) py += 64 - 8; /* Jump to next block if double block heigt */ + pc += ix & 1; /* Step forward chroma pointer every two pixels */ + } else { /* Single block width */ + pc++; /* Step forward chroma pointer every pixel */ + } + yy = *py++; /* Get Y component */ + *pix++ = /*R*/ BYTECLIP(yy + ((int)(1.402 * CVACC) * cr) / CVACC); + *pix++ = /*G*/ BYTECLIP(yy - ((int)(0.344 * CVACC) * cb + (int)(0.714 * CVACC) * cr) / CVACC); + *pix++ = /*B*/ BYTECLIP(yy + ((int)(1.772 * CVACC) * cb) / CVACC); + } + } + } else { /* Monochrome output (build a grayscale MCU from Y comopnent) */ + for (iy = 0; iy < my; iy++) { + py = jd->mcubuf + iy * 8; + if (my == 16) { /* Double block height? */ + if (iy >= 8) py += 64; + } + for (ix = 0; ix < mx; ix++) { + if (mx == 16) { /* Double block width? */ + if (ix == 8) py += 64 - 8; /* Jump to next block if double block height */ + } + *pix++ = (uint8_t)*py++; /* Get and store a Y value as grayscale */ + } + } + } + + /* Descale the MCU rectangular if needed */ + if (JD_USE_SCALE && jd->scale) { + unsigned int x, y, r, g, b, s, w, a; + uint8_t *op; + + /* Get averaged RGB value of each square correcponds to a pixel */ + s = jd->scale * 2; /* Number of shifts for averaging */ + w = 1 << jd->scale; /* Width of square */ + a = (mx - w) * (JD_FORMAT != 2 ? 3 : 1); /* Bytes to skip for next line in the square */ + op = (uint8_t*)jd->workbuf; + for (iy = 0; iy < my; iy += w) { + for (ix = 0; ix < mx; ix += w) { + pix = (uint8_t*)jd->workbuf + (iy * mx + ix) * (JD_FORMAT != 2 ? 3 : 1); + r = g = b = 0; + for (y = 0; y < w; y++) { /* Accumulate RGB value in the square */ + for (x = 0; x < w; x++) { + r += *pix++; /* Accumulate R or Y (monochrome output) */ + if (JD_FORMAT != 2) { /* RGB output? */ + g += *pix++; /* Accumulate G */ + b += *pix++; /* Accumulate B */ + } + } + pix += a; + } /* Put the averaged pixel value */ + *op++ = (uint8_t)(r >> s); /* Put R or Y (monochrome output) */ + if (JD_FORMAT != 2) { /* RGB output? */ + *op++ = (uint8_t)(g >> s); /* Put G */ + *op++ = (uint8_t)(b >> s); /* Put B */ + } + } + } + } + + } else { /* For only 1/8 scaling (left-top pixel in each block are the DC value of the block) */ + + /* Build a 1/8 descaled RGB MCU from discrete comopnents */ + pix = (uint8_t*)jd->workbuf; + pc = jd->mcubuf + mx * my; + cb = pc[0] - 128; /* Get Cb/Cr component and restore right level */ + cr = pc[64] - 128; + for (iy = 0; iy < my; iy += 8) { + py = jd->mcubuf; + if (iy == 8) py += 64 * 2; + for (ix = 0; ix < mx; ix += 8) { + yy = *py; /* Get Y component */ + py += 64; + if (JD_FORMAT != 2) { + *pix++ = /*R*/ BYTECLIP(yy + ((int)(1.402 * CVACC) * cr / CVACC)); + *pix++ = /*G*/ BYTECLIP(yy - ((int)(0.344 * CVACC) * cb + (int)(0.714 * CVACC) * cr) / CVACC); + *pix++ = /*B*/ BYTECLIP(yy + ((int)(1.772 * CVACC) * cb / CVACC)); + } else { + *pix++ = yy; + } + } + } + } + + /* Squeeze up pixel table if a part of MCU is to be truncated */ + mx >>= jd->scale; + if (rx < mx) { /* Is the MCU spans rigit edge? */ + uint8_t *s, *d; + unsigned int x, y; + + s = d = (uint8_t*)jd->workbuf; + for (y = 0; y < ry; y++) { + for (x = 0; x < rx; x++) { /* Copy effective pixels */ + *d++ = *s++; + if (JD_FORMAT != 2) { + *d++ = *s++; + *d++ = *s++; + } + } + s += (mx - rx) * (JD_FORMAT != 2 ? 3 : 1); /* Skip truncated pixels */ + } + } + + /* Convert RGB888 to RGB565 if needed */ + if (JD_FORMAT == 1) { + uint8_t *s = (uint8_t*)jd->workbuf; + uint16_t w, *d = (uint16_t*)s; + unsigned int n = rx * ry; + + do { + w = (*s++ & 0xF8) << 8; /* RRRRR----------- */ + w |= (*s++ & 0xFC) << 3; /* -----GGGGGG----- */ + w |= *s++ >> 3; /* -----------BBBBB */ + *d++ = w; + } while (--n); + } + + /* Output the rectangular */ + return outfunc(jd, jd->workbuf, &rect) ? JDR_OK : JDR_INTR; +} + + + + +/*-----------------------------------------------------------------------*/ +/* Analyze the JPEG image and Initialize decompressor object */ +/*-----------------------------------------------------------------------*/ + +#define LDB_WORD(ptr) (uint16_t)(((uint16_t)*((uint8_t*)(ptr))<<8)|(uint16_t)*(uint8_t*)((ptr)+1)) + + +JRESULT jd_prepare ( + JDEC* jd, /* Blank decompressor object */ + size_t (*infunc)(JDEC*, uint8_t*, size_t), /* JPEG strem input function */ + void* pool, /* Working buffer for the decompression session */ + size_t sz_pool, /* Size of working buffer */ + void* dev /* I/O device identifier for the session */ +) +{ + uint8_t *seg, b; + uint16_t marker; + unsigned int n, i, ofs; + size_t len; + JRESULT rc; + + + memset(jd, 0, sizeof (JDEC)); /* Clear decompression object (this might be a problem if machine's null pointer is not all bits zero) */ + jd->pool = pool; /* Work memroy */ + jd->sz_pool = sz_pool; /* Size of given work memory */ + jd->infunc = infunc; /* Stream input function */ + jd->device = dev; /* I/O device identifier */ + + jd->inbuf = seg = alloc_pool(jd, JD_SZBUF); /* Allocate stream input buffer */ + if (!seg) return JDR_MEM1; + + ofs = marker = 0; /* Find SOI marker */ + do { + if (jd->infunc(jd, seg, 1) != 1) return JDR_INP; /* Err: SOI was not detected */ + ofs++; + marker = marker << 8 | seg[0]; + } while (marker != 0xFFD8); + + for (;;) { /* Parse JPEG segments */ + /* Get a JPEG marker */ + if (jd->infunc(jd, seg, 4) != 4) return JDR_INP; + marker = LDB_WORD(seg); /* Marker */ + len = LDB_WORD(seg + 2); /* Length field */ + if (len <= 2 || (marker >> 8) != 0xFF) return JDR_FMT1; + len -= 2; /* Segent content size */ + ofs += 4 + len; /* Number of bytes loaded */ + + switch (marker & 0xFF) { + case 0xC0: /* SOF0 (baseline JPEG) */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; /* Load segment data */ + + jd->width = LDB_WORD(&seg[3]); /* Image width in unit of pixel */ + jd->height = LDB_WORD(&seg[1]); /* Image height in unit of pixel */ + jd->ncomp = seg[5]; /* Number of color components */ + if (jd->ncomp != 3 && jd->ncomp != 1) return JDR_FMT3; /* Err: Supports only Grayscale and Y/Cb/Cr */ + + /* Check each image component */ + for (i = 0; i < jd->ncomp; i++) { + b = seg[7 + 3 * i]; /* Get sampling factor */ + if (i == 0) { /* Y component */ + if (b != 0x11 && b != 0x22 && b != 0x21) { /* Check sampling factor */ + return JDR_FMT3; /* Err: Supports only 4:4:4, 4:2:0 or 4:2:2 */ + } + jd->msx = b >> 4; jd->msy = b & 15; /* Size of MCU [blocks] */ + } else { /* Cb/Cr component */ + if (b != 0x11) return JDR_FMT3; /* Err: Sampling factor of Cb/Cr must be 1 */ + } + jd->qtid[i] = seg[8 + 3 * i]; /* Get dequantizer table ID for this component */ + if (jd->qtid[i] > 3) return JDR_FMT3; /* Err: Invalid ID */ + } + break; + + case 0xDD: /* DRI - Define Restart Interval */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; /* Load segment data */ + + jd->nrst = LDB_WORD(seg); /* Get restart interval (MCUs) */ + break; + + case 0xC4: /* DHT - Define Huffman Tables */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; /* Load segment data */ + + rc = create_huffman_tbl(jd, seg, len); /* Create huffman tables */ + if (rc) return rc; + break; + + case 0xDB: /* DQT - Define Quaitizer Tables */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; /* Load segment data */ + + rc = create_qt_tbl(jd, seg, len); /* Create de-quantizer tables */ + if (rc) return rc; + break; + + case 0xDA: /* SOS - Start of Scan */ + if (len > JD_SZBUF) return JDR_MEM2; + if (jd->infunc(jd, seg, len) != len) return JDR_INP; /* Load segment data */ + + if (!jd->width || !jd->height) return JDR_FMT1; /* Err: Invalid image size */ + if (seg[0] != jd->ncomp) return JDR_FMT3; /* Err: Wrong color components */ + + /* Check if all tables corresponding to each components have been loaded */ + for (i = 0; i < jd->ncomp; i++) { + b = seg[2 + 2 * i]; /* Get huffman table ID */ + if (b != 0x00 && b != 0x11) return JDR_FMT3; /* Err: Different table number for DC/AC element */ + n = i ? 1 : 0; /* Component class */ + if (!jd->huffbits[n][0] || !jd->huffbits[n][1]) { /* Check huffman table for this component */ + return JDR_FMT1; /* Err: Nnot loaded */ + } + if (!jd->qttbl[jd->qtid[i]]) { /* Check dequantizer table for this component */ + return JDR_FMT1; /* Err: Not loaded */ + } + } + + /* Allocate working buffer for MCU and pixel output */ + n = jd->msy * jd->msx; /* Number of Y blocks in the MCU */ + if (!n) return JDR_FMT1; /* Err: SOF0 has not been loaded */ + len = n * 64 * 2 + 64; /* Allocate buffer for IDCT and RGB output */ + if (len < 256) len = 256; /* but at least 256 byte is required for IDCT */ + jd->workbuf = alloc_pool(jd, len); /* and it may occupy a part of following MCU working buffer for RGB output */ + if (!jd->workbuf) return JDR_MEM1; /* Err: not enough memory */ + jd->mcubuf = alloc_pool(jd, (n + 2) * 64 * sizeof (jd_yuv_t)); /* Allocate MCU working buffer */ + if (!jd->mcubuf) return JDR_MEM1; /* Err: not enough memory */ + + /* Align stream read offset to JD_SZBUF */ + if (ofs %= JD_SZBUF) { + jd->dctr = jd->infunc(jd, seg + ofs, (size_t)(JD_SZBUF - ofs)); + } + jd->dptr = seg + ofs - (JD_FASTDECODE ? 0 : 1); + + return JDR_OK; /* Initialization succeeded. Ready to decompress the JPEG image. */ + + case 0xC1: /* SOF1 */ + case 0xC2: /* SOF2 */ + case 0xC3: /* SOF3 */ + case 0xC5: /* SOF5 */ + case 0xC6: /* SOF6 */ + case 0xC7: /* SOF7 */ + case 0xC9: /* SOF9 */ + case 0xCA: /* SOF10 */ + case 0xCB: /* SOF11 */ + case 0xCD: /* SOF13 */ + case 0xCE: /* SOF14 */ + case 0xCF: /* SOF15 */ + case 0xD9: /* EOI */ + return JDR_FMT3; /* Unsuppoted JPEG standard (may be progressive JPEG) */ + + default: /* Unknown segment (comment, exif or etc..) */ + /* Skip segment data (null pointer specifies to remove data from the stream) */ + if (jd->infunc(jd, 0, len) != len) return JDR_INP; + } + } +} + + + + +/*-----------------------------------------------------------------------*/ +/* Start to decompress the JPEG picture */ +/*-----------------------------------------------------------------------*/ + +JRESULT jd_decomp ( + JDEC* jd, /* Initialized decompression object */ + int (*outfunc)(JDEC*, void*, JRECT*), /* RGB output function */ + uint8_t scale /* Output de-scaling factor (0 to 3) */ +) +{ + unsigned int x, y, mx, my; + uint16_t rst, rsc; + JRESULT rc; + + + if (scale > (JD_USE_SCALE ? 3 : 0)) return JDR_PAR; + jd->scale = scale; + + mx = jd->msx * 8; my = jd->msy * 8; /* Size of the MCU (pixel) */ + + jd->dcv[2] = jd->dcv[1] = jd->dcv[0] = 0; /* Initialize DC values */ + rst = rsc = 0; + + rc = JDR_OK; + for (y = 0; y < jd->height; y += my) { /* Vertical loop of MCUs */ + for (x = 0; x < jd->width; x += mx) { /* Horizontal loop of MCUs */ + if (jd->nrst && rst++ == jd->nrst) { /* Process restart interval if enabled */ + rc = restart(jd, rsc++); + if (rc != JDR_OK) return rc; + rst = 1; + } + rc = mcu_load(jd); /* Load an MCU (decompress huffman coded stream, dequantize and apply IDCT) */ + if (rc != JDR_OK) return rc; + rc = mcu_output(jd, outfunc, x, y); /* Output the MCU (YCbCr to RGB, scaling and output) */ + if (rc != JDR_OK) return rc; + } + } + + return rc; +} + +#endif /*LV_USE_SJPG*/ diff --git a/lib/lvgl/src/extra/libs/sjpg/tjpgd.h b/lib/lvgl/src/extra/libs/sjpg/tjpgd.h new file mode 100644 index 00000000..b255ccfc --- /dev/null +++ b/lib/lvgl/src/extra/libs/sjpg/tjpgd.h @@ -0,0 +1,93 @@ +/*----------------------------------------------------------------------------/ +/ TJpgDec - Tiny JPEG Decompressor R0.03 include file (C)ChaN, 2021 +/----------------------------------------------------------------------------*/ +#ifndef DEF_TJPGDEC +#define DEF_TJPGDEC + +#ifdef __cplusplus +extern "C" { +#endif + +#include "../../../lv_conf_internal.h" +#if LV_USE_SJPG + +#include "tjpgdcnf.h" +#include <string.h> +#include <stdint.h> + +#if JD_FASTDECODE >= 1 +typedef int16_t jd_yuv_t; +#else +typedef uint8_t jd_yuv_t; +#endif + + +/* Error code */ +typedef enum { + JDR_OK = 0, /* 0: Succeeded */ + JDR_INTR, /* 1: Interrupted by output function */ + JDR_INP, /* 2: Device error or wrong termination of input stream */ + JDR_MEM1, /* 3: Insufficient memory pool for the image */ + JDR_MEM2, /* 4: Insufficient stream input buffer */ + JDR_PAR, /* 5: Parameter error */ + JDR_FMT1, /* 6: Data format error (may be broken data) */ + JDR_FMT2, /* 7: Right format but not supported */ + JDR_FMT3 /* 8: Not supported JPEG standard */ +} JRESULT; + +/* Rectangular region in the output image */ +typedef struct { + uint16_t left; /* Left end */ + uint16_t right; /* Right end */ + uint16_t top; /* Top end */ + uint16_t bottom; /* Bottom end */ +} JRECT; + +/* Decompressor object structure */ +typedef struct JDEC JDEC; +struct JDEC { + size_t dctr; /* Number of bytes available in the input buffer */ + uint8_t* dptr; /* Current data read ptr */ + uint8_t* inbuf; /* Bit stream input buffer */ + uint8_t dbit; /* Number of bits availavble in wreg or reading bit mask */ + uint8_t scale; /* Output scaling ratio */ + uint8_t msx, msy; /* MCU size in unit of block (width, height) */ + uint8_t qtid[3]; /* Quantization table ID of each component, Y, Cb, Cr */ + uint8_t ncomp; /* Number of color components 1:grayscale, 3:color */ + int16_t dcv[3]; /* Previous DC element of each component */ + uint16_t nrst; /* Restart inverval */ + uint16_t width, height; /* Size of the input image (pixel) */ + uint8_t* huffbits[2][2]; /* Huffman bit distribution tables [id][dcac] */ + uint16_t* huffcode[2][2]; /* Huffman code word tables [id][dcac] */ + uint8_t* huffdata[2][2]; /* Huffman decoded data tables [id][dcac] */ + int32_t* qttbl[4]; /* Dequantizer tables [id] */ +#if JD_FASTDECODE >= 1 + uint32_t wreg; /* Working shift register */ + uint8_t marker; /* Detected marker (0:None) */ +#if JD_FASTDECODE == 2 + uint8_t longofs[2][2]; /* Table offset of long code [id][dcac] */ + uint16_t* hufflut_ac[2]; /* Fast huffman decode tables for AC short code [id] */ + uint8_t* hufflut_dc[2]; /* Fast huffman decode tables for DC short code [id] */ +#endif +#endif + void* workbuf; /* Working buffer for IDCT and RGB output */ + jd_yuv_t* mcubuf; /* Working buffer for the MCU */ + void* pool; /* Pointer to available memory pool */ + size_t sz_pool; /* Size of momory pool (bytes available) */ + size_t (*infunc)(JDEC*, uint8_t*, size_t); /* Pointer to jpeg stream input function */ + void* device; /* Pointer to I/O device identifiler for the session */ +}; + + + +/* TJpgDec API functions */ +JRESULT jd_prepare (JDEC* jd, size_t (*infunc)(JDEC*,uint8_t*,size_t), void* pool, size_t sz_pool, void* dev); +JRESULT jd_decomp (JDEC* jd, int (*outfunc)(JDEC*,void*,JRECT*), uint8_t scale); + +#endif /*LV_USE_SJPG*/ + +#ifdef __cplusplus +} +#endif + +#endif /* _TJPGDEC */ diff --git a/lib/lvgl/src/extra/libs/sjpg/tjpgdcnf.h b/lib/lvgl/src/extra/libs/sjpg/tjpgdcnf.h new file mode 100644 index 00000000..6d425e6f --- /dev/null +++ b/lib/lvgl/src/extra/libs/sjpg/tjpgdcnf.h @@ -0,0 +1,33 @@ +/*----------------------------------------------*/ +/* TJpgDec System Configurations R0.03 */ +/*----------------------------------------------*/ + +#define JD_SZBUF 512 +/* Specifies size of stream input buffer */ + +#define JD_FORMAT 0 +/* Specifies output pixel format. +/ 0: RGB888 (24-bit/pix) +/ 1: RGB565 (16-bit/pix) +/ 2: Grayscale (8-bit/pix) +*/ + +#define JD_USE_SCALE 1 +/* Switches output descaling feature. +/ 0: Disable +/ 1: Enable +*/ + +#define JD_TBLCLIP 1 +/* Use table conversion for saturation arithmetic. A bit faster, but increases 1 KB of code size. +/ 0: Disable +/ 1: Enable +*/ + +#define JD_FASTDECODE 0 +/* Optimization level +/ 0: Basic optimization. Suitable for 8/16-bit MCUs. +/ 1: + 32-bit barrel shifter. Suitable for 32-bit MCUs. +/ 2: + Table conversion for huffman decoding (wants 6 << HUFF_BIT bytes of RAM) +*/ + diff --git a/lib/lvgl/src/extra/lv_extra.c b/lib/lvgl/src/extra/lv_extra.c new file mode 100644 index 00000000..0b500024 --- /dev/null +++ b/lib/lvgl/src/extra/lv_extra.c @@ -0,0 +1,93 @@ +/** + * @file lv_extra.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../lvgl.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_extra_init(void) +{ +#if LV_USE_FLEX + lv_flex_init(); +#endif + +#if LV_USE_GRID + lv_grid_init(); +#endif + +#if LV_USE_MSG + lv_msg_init(); +#endif + +#if LV_USE_FS_FATFS != '\0' + lv_fs_fatfs_init(); +#endif + +#if LV_USE_FS_STDIO != '\0' + lv_fs_stdio_init(); +#endif + +#if LV_USE_FS_POSIX != '\0' + lv_fs_posix_init(); +#endif + +#if LV_USE_FS_WIN32 != '\0' + lv_fs_win32_init(); +#endif + +#if LV_USE_FFMPEG + lv_ffmpeg_init(); +#endif + +#if LV_USE_PNG + lv_png_init(); +#endif + +#if LV_USE_SJPG + lv_split_jpeg_init(); +#endif + +#if LV_USE_BMP + lv_bmp_init(); +#endif + +#if LV_USE_FREETYPE + /*Init freetype library*/ +# if LV_FREETYPE_CACHE_SIZE >= 0 + lv_freetype_init(LV_FREETYPE_CACHE_FT_FACES, LV_FREETYPE_CACHE_FT_SIZES, LV_FREETYPE_CACHE_SIZE); +# else + lv_freetype_init(0, 0, 0); +# endif +#endif +} + +/********************** + * STATIC FUNCTIONS + **********************/ diff --git a/lib/lvgl/src/extra/lv_extra.h b/lib/lvgl/src/extra/lv_extra.h new file mode 100644 index 00000000..c0306a98 --- /dev/null +++ b/lib/lvgl/src/extra/lv_extra.h @@ -0,0 +1,48 @@ +/** + * @file lv_extra.h + * + */ + +#ifndef LV_EXTRA_H +#define LV_EXTRA_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ + +#include "layouts/lv_layouts.h" +#include "libs/lv_libs.h" +#include "others/lv_others.h" +#include "themes/lv_themes.h" +#include "widgets/lv_widgets.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize the extra components + */ +void lv_extra_init(void); + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_EXTRA_H*/ diff --git a/lib/lvgl/src/extra/lv_extra.mk b/lib/lvgl/src/extra/lv_extra.mk new file mode 100644 index 00000000..1afcc7b7 --- /dev/null +++ b/lib/lvgl/src/extra/lv_extra.mk @@ -0,0 +1 @@ +CSRCS += $(shell find -L $(LVGL_DIR)/$(LVGL_DIR_NAME)/src/extra -name \*.c) diff --git a/lib/lvgl/src/extra/others/fragment/README.md b/lib/lvgl/src/extra/others/fragment/README.md new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/lib/lvgl/src/extra/others/fragment/README.md diff --git a/lib/lvgl/src/extra/others/fragment/lv_fragment.c b/lib/lvgl/src/extra/others/fragment/lv_fragment.c new file mode 100644 index 00000000..a2cdfadc --- /dev/null +++ b/lib/lvgl/src/extra/others/fragment/lv_fragment.c @@ -0,0 +1,144 @@ +/** + * @file lv_fragment.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_fragment.h" + +#if LV_USE_FRAGMENT + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void cb_delete_assertion(lv_event_t * event); + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_fragment_t * lv_fragment_create(const lv_fragment_class_t * cls, void * args) +{ + LV_ASSERT_NULL(cls); + LV_ASSERT_NULL(cls->create_obj_cb); + LV_ASSERT(cls->instance_size > 0); + lv_fragment_t * instance = lv_mem_alloc(cls->instance_size); + lv_memset_00(instance, cls->instance_size); + instance->cls = cls; + instance->child_manager = lv_fragment_manager_create(instance); + if(cls->constructor_cb) { + cls->constructor_cb(instance, args); + } + return instance; +} + +void lv_fragment_del(lv_fragment_t * fragment) +{ + LV_ASSERT_NULL(fragment); + if(fragment->managed) { + lv_fragment_manager_remove(fragment->managed->manager, fragment); + return; + } + if(fragment->obj) { + lv_fragment_del_obj(fragment); + } + /* Objects will leak if this function called before objects deleted */ + const lv_fragment_class_t * cls = fragment->cls; + if(cls->destructor_cb) { + cls->destructor_cb(fragment); + } + lv_fragment_manager_del(fragment->child_manager); + lv_mem_free(fragment); +} + +lv_fragment_manager_t * lv_fragment_get_manager(lv_fragment_t * fragment) +{ + LV_ASSERT_NULL(fragment); + LV_ASSERT_NULL(fragment->managed); + return fragment->managed->manager; +} + +lv_obj_t * const * lv_fragment_get_container(lv_fragment_t * fragment) +{ + LV_ASSERT_NULL(fragment); + LV_ASSERT_NULL(fragment->managed); + return fragment->managed->container; +} + +lv_fragment_t * lv_fragment_get_parent(lv_fragment_t * fragment) +{ + LV_ASSERT_NULL(fragment); + LV_ASSERT_NULL(fragment->managed); + return lv_fragment_manager_get_parent_fragment(fragment->managed->manager); +} + +lv_obj_t * lv_fragment_create_obj(lv_fragment_t * fragment, lv_obj_t * container) +{ + lv_fragment_managed_states_t * states = fragment->managed; + if(states) { + states->destroying_obj = false; + } + const lv_fragment_class_t * cls = fragment->cls; + lv_obj_t * obj = cls->create_obj_cb(fragment, container); + LV_ASSERT_NULL(obj); + fragment->obj = obj; + lv_fragment_manager_create_obj(fragment->child_manager); + if(states) { + states->obj_created = true; + lv_obj_add_event_cb(obj, cb_delete_assertion, LV_EVENT_DELETE, NULL); + } + if(cls->obj_created_cb) { + cls->obj_created_cb(fragment, obj); + } + return obj; +} + +void lv_fragment_del_obj(lv_fragment_t * fragment) +{ + LV_ASSERT_NULL(fragment); + lv_fragment_manager_del_obj(fragment->child_manager); + lv_fragment_managed_states_t * states = fragment->managed; + if(states) { + if(!states->obj_created) return; + states->destroying_obj = true; + bool cb_removed = lv_obj_remove_event_cb(fragment->obj, cb_delete_assertion); + LV_ASSERT(cb_removed); + } + LV_ASSERT_NULL(fragment->obj); + const lv_fragment_class_t * cls = fragment->cls; + if(cls->obj_will_delete_cb) { + cls->obj_will_delete_cb(fragment, fragment->obj); + } + lv_obj_del(fragment->obj); + if(cls->obj_deleted_cb) { + cls->obj_deleted_cb(fragment, fragment->obj); + } + if(states) { + states->obj_created = false; + } + fragment->obj = NULL; +} + +void lv_fragment_recreate_obj(lv_fragment_t * fragment) +{ + LV_ASSERT_NULL(fragment); + LV_ASSERT_NULL(fragment->managed); + lv_fragment_del_obj(fragment); + lv_fragment_create_obj(fragment, *fragment->managed->container); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void cb_delete_assertion(lv_event_t * event) +{ + LV_UNUSED(event); + LV_ASSERT_MSG(0, "Please delete objects with lv_fragment_destroy_obj"); +} + +#endif /*LV_USE_FRAGMENT*/ diff --git a/lib/lvgl/src/extra/others/fragment/lv_fragment.h b/lib/lvgl/src/extra/others/fragment/lv_fragment.h new file mode 100644 index 00000000..da30b39a --- /dev/null +++ b/lib/lvgl/src/extra/others/fragment/lv_fragment.h @@ -0,0 +1,339 @@ +/** + * Public header for Fragment + * @file lv_fragment.h + */ + +#ifndef LV_FRAGMENT_H +#define LV_FRAGMENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lv_conf_internal.h" + +#if LV_USE_FRAGMENT + +#include "../../../core/lv_obj.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct _lv_fragment_manager_t lv_fragment_manager_t; + +typedef struct _lv_fragment_t lv_fragment_t; +typedef struct _lv_fragment_class_t lv_fragment_class_t; +typedef struct _lv_fragment_managed_states_t lv_fragment_managed_states_t; + +struct _lv_fragment_t { + /** + * Class of this fragment + */ + const lv_fragment_class_t * cls; + /** + * Managed fragment states. If not null, then this fragment is managed. + * + * @warning Don't modify values inside this struct! + */ + lv_fragment_managed_states_t * managed; + /** + * Child fragment manager + */ + lv_fragment_manager_t * child_manager; + /** + * lv_obj returned by create_obj_cb + */ + lv_obj_t * obj; + +}; + +struct _lv_fragment_class_t { + /** + * Constructor function for fragment class + * @param self Fragment instance + * @param args Arguments assigned by fragment manager + */ + void (*constructor_cb)(lv_fragment_t * self, void * args); + + /** + * Destructor function for fragment class + * @param self Fragment instance, will be freed after this call + */ + void (*destructor_cb)(lv_fragment_t * self); + + /** + * Fragment attached to manager + * @param self Fragment instance + */ + void (*attached_cb)(lv_fragment_t * self); + + /** + * Fragment detached from manager + * @param self Fragment instance + */ + void (*detached_cb)(lv_fragment_t * self); + + /** + * Create objects + * @param self Fragment instance + * @param container Container of the objects should be created upon + * @return Created object, NULL if multiple objects has been created + */ + lv_obj_t * (*create_obj_cb)(lv_fragment_t * self, lv_obj_t * container); + + /** + * + * @param self Fragment instance + * @param obj lv_obj returned by create_obj_cb + */ + void (*obj_created_cb)(lv_fragment_t * self, lv_obj_t * obj); + + /** + * Called before objects in the fragment will be deleted. + * + * @param self Fragment instance + * @param obj object with this fragment + */ + void (*obj_will_delete_cb)(lv_fragment_t * self, lv_obj_t * obj); + + /** + * Called when the object created by fragment received `LV_EVENT_DELETE` event + * @param self Fragment instance + * @param obj object with this fragment + */ + void (*obj_deleted_cb)(lv_fragment_t * self, lv_obj_t * obj); + + /** + * Handle event + * @param self Fragment instance + * @param which User-defined ID of event + * @param data1 User-defined data + * @param data2 User-defined data + */ + bool (*event_cb)(lv_fragment_t * self, int code, void * userdata); + + /** + * *REQUIRED*: Allocation size of fragment + */ + size_t instance_size; +}; + +/** + * Fragment states + */ +typedef struct _lv_fragment_managed_states_t { + /** + * Class of the fragment + */ + const lv_fragment_class_t * cls; + /** + * Manager the fragment attached to + */ + lv_fragment_manager_t * manager; + /** + * Container object the fragment adding view to + */ + lv_obj_t * const * container; + /** + * Fragment instance + */ + lv_fragment_t * instance; + /** + * true between `create_obj_cb` and `obj_deleted_cb` + */ + bool obj_created; + /** + * true before `lv_fragment_del_obj` is called. Don't touch any object if this is true + */ + bool destroying_obj; + /** + * true if this fragment is in navigation stack that can be popped + */ + bool in_stack; +} lv_fragment_managed_states_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create fragment manager instance + * @param parent Parent fragment if this manager is placed inside another fragment, can be null. + * @return Fragment manager instance + */ +lv_fragment_manager_t * lv_fragment_manager_create(lv_fragment_t * parent); + +/** + * Destroy fragment manager instance + * @param manager Fragment manager instance + */ +void lv_fragment_manager_del(lv_fragment_manager_t * manager); + +/** + * Create object of all fragments managed by this manager. + * @param manager Fragment manager instance + */ +void lv_fragment_manager_create_obj(lv_fragment_manager_t * manager); + +/** + * Delete object created by all fragments managed by this manager. Instance of fragments will not be deleted. + * @param manager Fragment manager instance + */ +void lv_fragment_manager_del_obj(lv_fragment_manager_t * manager); + +/** + * Attach fragment to manager, and add to container. + * @param manager Fragment manager instance + * @param fragment Fragment instance + * @param container Pointer to container object for manager to add objects to + */ +void lv_fragment_manager_add(lv_fragment_manager_t * manager, lv_fragment_t * fragment, lv_obj_t * const * container); + +/** + * Detach and destroy fragment. If fragment is in navigation stack, remove from it. + * @param manager Fragment manager instance + * @param fragment Fragment instance + */ +void lv_fragment_manager_remove(lv_fragment_manager_t * manager, lv_fragment_t * fragment); + +/** + * Attach fragment to manager and add to navigation stack. + * @param manager Fragment manager instance + * @param fragment Fragment instance + * @param container Pointer to container object for manager to add objects to + */ +void lv_fragment_manager_push(lv_fragment_manager_t * manager, lv_fragment_t * fragment, lv_obj_t * const * container); + +/** + * Remove the top-most fragment for stack + * @param manager Fragment manager instance + * @return true if there is fragment to pop + */ +bool lv_fragment_manager_pop(lv_fragment_manager_t * manager); + +/** + * Replace fragment. Old item in the stack will be removed. + * @param manager Fragment manager instance + * @param fragment Fragment instance + * @param container Pointer to container object for manager to add objects to + */ +void lv_fragment_manager_replace(lv_fragment_manager_t * manager, lv_fragment_t * fragment, + lv_obj_t * const * container); + +/** + * Send event to top-most fragment + * @param manager Fragment manager instance + * @param code User-defined ID of event + * @param userdata User-defined data + * @return true if fragment returned true + */ +bool lv_fragment_manager_send_event(lv_fragment_manager_t * manager, int code, void * userdata); + +/** + * Get stack size of this fragment manager + * @param manager Fragment manager instance + * @return Stack size of this fragment manager + */ +size_t lv_fragment_manager_get_stack_size(lv_fragment_manager_t * manager); + +/** + * Get top most fragment instance + * @param manager Fragment manager instance + * @return Top most fragment instance + */ +lv_fragment_t * lv_fragment_manager_get_top(lv_fragment_manager_t * manager); + +/** + * Find first fragment instance in the container + * @param manager Fragment manager instance + * @param container Container which target fragment added to + * @return First fragment instance in the container + */ +lv_fragment_t * lv_fragment_manager_find_by_container(lv_fragment_manager_t * manager, const lv_obj_t * container); + +/** + * Get parent fragment + * @param manager Fragment manager instance + * @return Parent fragment instance + */ +lv_fragment_t * lv_fragment_manager_get_parent_fragment(lv_fragment_manager_t * manager); + + +/** + * Create a fragment instance. + * + * @param cls Fragment class. This fragment must return non null object. + * @param args Arguments assigned by fragment manager + * @return Fragment instance + */ +lv_fragment_t * lv_fragment_create(const lv_fragment_class_t * cls, void * args); + +/** + * Destroy a fragment. + * @param fragment Fragment instance. + */ +void lv_fragment_del(lv_fragment_t * fragment); + +/** + * Get associated manager of this fragment + * @param fragment Fragment instance + * @return Fragment manager instance + */ +lv_fragment_manager_t * lv_fragment_get_manager(lv_fragment_t * fragment); + +/** + * Get container object of this fragment + * @param fragment Fragment instance + * @return Reference to container object + */ +lv_obj_t * const * lv_fragment_get_container(lv_fragment_t * fragment); + +/** + * Get parent fragment of this fragment + * @param fragment Fragment instance + * @return Parent fragment + */ +lv_fragment_t * lv_fragment_get_parent(lv_fragment_t * fragment); + +/** + * Create object by fragment. + * + * @param fragment Fragment instance. + * @param container Container of the objects should be created upon. + * @return Created object + */ +lv_obj_t * lv_fragment_create_obj(lv_fragment_t * fragment, lv_obj_t * container); + +/** + * Delete created object of a fragment + * + * @param fragment Fragment instance. + */ +void lv_fragment_del_obj(lv_fragment_t * fragment); + +/** + * Destroy obj in fragment, and recreate them. + * @param fragment Fragment instance + */ +void lv_fragment_recreate_obj(lv_fragment_t * fragment); + + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_FRAGMENT*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_FRAGMENT_H*/ diff --git a/lib/lvgl/src/extra/others/fragment/lv_fragment_manager.c b/lib/lvgl/src/extra/others/fragment/lv_fragment_manager.c new file mode 100644 index 00000000..ade72158 --- /dev/null +++ b/lib/lvgl/src/extra/others/fragment/lv_fragment_manager.c @@ -0,0 +1,281 @@ +/** + * @file lv_fragment_manager.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_fragment.h" + +#if LV_USE_FRAGMENT + +#include "../../../misc/lv_ll.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct _lv_fragment_stack_item_t { + lv_fragment_managed_states_t * states; +} lv_fragment_stack_item_t; + +struct _lv_fragment_manager_t { + lv_fragment_t * parent; + /** + * Linked list to store attached fragments + */ + lv_ll_t attached; + /** + * Linked list to store fragments in stack + */ + lv_ll_t stack; +}; + + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void item_create_obj(lv_fragment_managed_states_t * item); + +static void item_del_obj(lv_fragment_managed_states_t * item); + +static void item_del_fragment(lv_fragment_managed_states_t * item); + +static lv_fragment_managed_states_t * fragment_attach(lv_fragment_manager_t * manager, lv_fragment_t * fragment, + lv_obj_t * const * container); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_fragment_manager_t * lv_fragment_manager_create(lv_fragment_t * parent) +{ + lv_fragment_manager_t * instance = lv_mem_alloc(sizeof(lv_fragment_manager_t)); + lv_memset_00(instance, sizeof(lv_fragment_manager_t)); + instance->parent = parent; + _lv_ll_init(&instance->attached, sizeof(lv_fragment_managed_states_t)); + _lv_ll_init(&instance->stack, sizeof(lv_fragment_stack_item_t)); + return instance; +} + +void lv_fragment_manager_del(lv_fragment_manager_t * manager) +{ + LV_ASSERT_NULL(manager); + lv_fragment_managed_states_t * states; + _LV_LL_READ_BACK(&manager->attached, states) { + item_del_obj(states); + item_del_fragment(states); + } + _lv_ll_clear(&manager->attached); + _lv_ll_clear(&manager->stack); + lv_mem_free(manager); +} + +void lv_fragment_manager_create_obj(lv_fragment_manager_t * manager) +{ + LV_ASSERT_NULL(manager); + lv_fragment_stack_item_t * top = _lv_ll_get_tail(&manager->stack); + lv_fragment_managed_states_t * states = NULL; + _LV_LL_READ(&manager->attached, states) { + if(states->in_stack && top->states != states) { + /*Only create obj for top item in stack*/ + continue; + } + item_create_obj(states); + } +} + +void lv_fragment_manager_del_obj(lv_fragment_manager_t * manager) +{ + LV_ASSERT_NULL(manager); + lv_fragment_managed_states_t * states = NULL; + _LV_LL_READ_BACK(&manager->attached, states) { + item_del_obj(states); + } +} + +void lv_fragment_manager_add(lv_fragment_manager_t * manager, lv_fragment_t * fragment, lv_obj_t * const * container) +{ + lv_fragment_managed_states_t * states = fragment_attach(manager, fragment, container); + if(!manager->parent || manager->parent->managed->obj_created) { + item_create_obj(states); + } +} + +void lv_fragment_manager_remove(lv_fragment_manager_t * manager, lv_fragment_t * fragment) +{ + LV_ASSERT_NULL(manager); + LV_ASSERT_NULL(fragment); + LV_ASSERT_NULL(fragment->managed); + LV_ASSERT(fragment->managed->manager == manager); + lv_fragment_managed_states_t * states = fragment->managed; + lv_fragment_managed_states_t * prev = NULL; + bool was_top = false; + if(states->in_stack) { + void * stack_top = _lv_ll_get_tail(&manager->stack); + lv_fragment_stack_item_t * item = NULL; + _LV_LL_READ_BACK(&manager->stack, item) { + if(item->states == states) { + was_top = stack_top == item; + void * stack_prev = _lv_ll_get_prev(&manager->stack, item); + if(!stack_prev) break; + prev = ((lv_fragment_stack_item_t *) stack_prev)->states; + break; + } + } + if(item) { + _lv_ll_remove(&manager->stack, item); + lv_mem_free(item); + } + } + item_del_obj(states); + item_del_fragment(states); + _lv_ll_remove(&manager->attached, states); + lv_mem_free(states); + if(prev && was_top) { + item_create_obj(prev); + } +} + +void lv_fragment_manager_push(lv_fragment_manager_t * manager, lv_fragment_t * fragment, lv_obj_t * const * container) +{ + lv_fragment_stack_item_t * top = _lv_ll_get_tail(&manager->stack); + if(top != NULL) { + item_del_obj(top->states); + } + lv_fragment_managed_states_t * states = fragment_attach(manager, fragment, container); + states->in_stack = true; + /*Add fragment to the top of the stack*/ + lv_fragment_stack_item_t * item = _lv_ll_ins_tail(&manager->stack); + lv_memset_00(item, sizeof(lv_fragment_stack_item_t)); + item->states = states; + item_create_obj(states); +} + +bool lv_fragment_manager_pop(lv_fragment_manager_t * manager) +{ + lv_fragment_t * top = lv_fragment_manager_get_top(manager); + if(top == NULL) return false; + lv_fragment_manager_remove(manager, top); + return true; +} + +void lv_fragment_manager_replace(lv_fragment_manager_t * manager, lv_fragment_t * fragment, + lv_obj_t * const * container) +{ + lv_fragment_t * top = lv_fragment_manager_find_by_container(manager, *container); + if(top != NULL) { + lv_fragment_manager_remove(manager, top); + } + lv_fragment_manager_add(manager, fragment, container); +} + +bool lv_fragment_manager_send_event(lv_fragment_manager_t * manager, int code, void * userdata) +{ + LV_ASSERT_NULL(manager); + lv_fragment_managed_states_t * p = NULL; + _LV_LL_READ_BACK(&manager->attached, p) { + if(!p->obj_created || p->destroying_obj) continue; + lv_fragment_t * instance = p->instance; + if(!instance) continue; + if(lv_fragment_manager_send_event(instance->child_manager, code, userdata)) return true; + if(p->cls->event_cb && p->cls->event_cb(instance, code, userdata)) return true; + } + return false; +} + +size_t lv_fragment_manager_get_stack_size(lv_fragment_manager_t * manager) +{ + LV_ASSERT_NULL(manager); + return _lv_ll_get_len(&manager->stack); +} + +lv_fragment_t * lv_fragment_manager_get_top(lv_fragment_manager_t * manager) +{ + LV_ASSERT(manager); + lv_fragment_stack_item_t * top = _lv_ll_get_tail(&manager->stack); + if(!top)return NULL; + return top->states->instance; +} + +lv_fragment_t * lv_fragment_manager_find_by_container(lv_fragment_manager_t * manager, const lv_obj_t * container) +{ + LV_ASSERT(manager); + lv_fragment_managed_states_t * states; + _LV_LL_READ(&manager->attached, states) { + if(*states->container == container) return states->instance; + } + return NULL; +} + +lv_fragment_t * lv_fragment_manager_get_parent_fragment(lv_fragment_manager_t * manager) +{ + LV_ASSERT_NULL(manager); + return manager->parent; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void item_create_obj(lv_fragment_managed_states_t * item) +{ + LV_ASSERT(item->instance); + lv_fragment_create_obj(item->instance, item->container ? *item->container : NULL); +} + +static void item_del_obj(lv_fragment_managed_states_t * item) +{ + lv_fragment_del_obj(item->instance); +} + +/** + * Detach, then destroy fragment + * @param item fragment states + */ +static void item_del_fragment(lv_fragment_managed_states_t * item) +{ + lv_fragment_t * instance = item->instance; + if(instance->cls->detached_cb) { + instance->cls->detached_cb(instance); + } + instance->managed = NULL; + lv_fragment_del(instance); + item->instance = NULL; +} + + +static lv_fragment_managed_states_t * fragment_attach(lv_fragment_manager_t * manager, lv_fragment_t * fragment, + lv_obj_t * const * container) +{ + LV_ASSERT(manager); + LV_ASSERT(fragment); + LV_ASSERT(fragment->managed == NULL); + lv_fragment_managed_states_t * states = _lv_ll_ins_tail(&manager->attached); + lv_memset_00(states, sizeof(lv_fragment_managed_states_t)); + states->cls = fragment->cls; + states->manager = manager; + states->container = container; + states->instance = fragment; + fragment->managed = states; + if(fragment->cls->attached_cb) { + fragment->cls->attached_cb(fragment); + } + return states; +} + +#endif /*LV_USE_FRAGMENT*/ diff --git a/lib/lvgl/src/extra/others/gridnav/lv_gridnav.c b/lib/lvgl/src/extra/others/gridnav/lv_gridnav.c new file mode 100644 index 00000000..4eec637b --- /dev/null +++ b/lib/lvgl/src/extra/others/gridnav/lv_gridnav.c @@ -0,0 +1,375 @@ +/** + * @file lv_gridnav.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_gridnav.h" +#if LV_USE_GRIDNAV + +#include "../../../misc/lv_assert.h" +#include "../../../misc/lv_math.h" +#include "../../../core/lv_indev.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_gridnav_ctrl_t ctrl; + lv_obj_t * focused_obj; +} lv_gridnav_dsc_t; + +typedef enum { + FIND_LEFT, + FIND_RIGHT, + FIND_TOP, + FIND_BOTTOM, + FIND_NEXT_ROW_FIRST_ITEM, + FIND_PREV_ROW_LAST_ITEM, + FIND_FIRST_ROW, + FIND_LAST_ROW, +} find_mode_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void gridnav_event_cb(lv_event_t * e); +static lv_obj_t * find_chid(lv_obj_t * obj, lv_obj_t * start_child, find_mode_t mode); +static lv_obj_t * find_first_focusable(lv_obj_t * obj); +static lv_obj_t * find_last_focusable(lv_obj_t * obj); +static bool obj_is_focuable(lv_obj_t * obj); +static lv_coord_t get_x_center(lv_obj_t * obj); +static lv_coord_t get_y_center(lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_gridnav_add(lv_obj_t * obj, lv_gridnav_ctrl_t ctrl) +{ + lv_gridnav_remove(obj); /*Be sure to not add gridnav twice*/ + + lv_gridnav_dsc_t * dsc = lv_mem_alloc(sizeof(lv_gridnav_dsc_t)); + LV_ASSERT_MALLOC(dsc); + dsc->ctrl = ctrl; + dsc->focused_obj = NULL; + lv_obj_add_event_cb(obj, gridnav_event_cb, LV_EVENT_ALL, dsc); + + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_WITH_ARROW); +} + +void lv_gridnav_remove(lv_obj_t * obj) +{ + lv_gridnav_dsc_t * dsc = lv_obj_get_event_user_data(obj, gridnav_event_cb); + if(dsc == NULL) return; /* no gridnav on this object */ + + lv_mem_free(dsc); + lv_obj_remove_event_cb(obj, gridnav_event_cb); +} + +void lv_gridnav_set_focused(lv_obj_t * cont, lv_obj_t * to_focus, lv_anim_enable_t anim_en) +{ + LV_ASSERT_NULL(to_focus); + lv_gridnav_dsc_t * dsc = lv_obj_get_event_user_data(cont, gridnav_event_cb); + if(dsc == NULL) { + LV_LOG_WARN("`cont` is not a gridnav container"); + return; + } + + if(obj_is_focuable(to_focus) == false) { + LV_LOG_WARN("The object to focus is not focusable"); + return; + } + + lv_obj_clear_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + lv_obj_add_state(to_focus, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + lv_obj_scroll_to_view(to_focus, anim_en); + dsc->focused_obj = to_focus; + +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void gridnav_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_current_target(e); + lv_gridnav_dsc_t * dsc = lv_event_get_user_data(e); + lv_event_code_t code = lv_event_get_code(e); + + if(code == LV_EVENT_KEY) { + uint32_t child_cnt = lv_obj_get_child_cnt(obj); + if(child_cnt == 0) return; + + if(dsc->focused_obj == NULL) dsc->focused_obj = find_first_focusable(obj); + if(dsc->focused_obj == NULL) return; + + uint32_t key = lv_event_get_key(e); + lv_obj_t * guess = NULL; + + if(key == LV_KEY_RIGHT) { + if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) && + lv_obj_get_scroll_right(dsc->focused_obj) > 0) { + lv_coord_t d = lv_obj_get_width(dsc->focused_obj) / 4; + if(d <= 0) d = 1; + lv_obj_scroll_by_bounded(dsc->focused_obj, -d, 0, LV_ANIM_ON); + } + else { + guess = find_chid(obj, dsc->focused_obj, FIND_RIGHT); + if(guess == NULL) { + if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) { + guess = find_chid(obj, dsc->focused_obj, FIND_NEXT_ROW_FIRST_ITEM); + if(guess == NULL) guess = find_first_focusable(obj); + } + else { + lv_group_focus_next(lv_obj_get_group(obj)); + } + } + } + } + else if(key == LV_KEY_LEFT) { + if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) && + lv_obj_get_scroll_left(dsc->focused_obj) > 0) { + lv_coord_t d = lv_obj_get_width(dsc->focused_obj) / 4; + if(d <= 0) d = 1; + lv_obj_scroll_by_bounded(dsc->focused_obj, d, 0, LV_ANIM_ON); + } + else { + guess = find_chid(obj, dsc->focused_obj, FIND_LEFT); + if(guess == NULL) { + if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) { + guess = find_chid(obj, dsc->focused_obj, FIND_PREV_ROW_LAST_ITEM); + if(guess == NULL) guess = find_last_focusable(obj); + } + else { + lv_group_focus_prev(lv_obj_get_group(obj)); + } + } + } + } + else if(key == LV_KEY_DOWN) { + if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) && + lv_obj_get_scroll_bottom(dsc->focused_obj) > 0) { + lv_coord_t d = lv_obj_get_height(dsc->focused_obj) / 4; + if(d <= 0) d = 1; + lv_obj_scroll_by_bounded(dsc->focused_obj, 0, -d, LV_ANIM_ON); + } + else { + guess = find_chid(obj, dsc->focused_obj, FIND_BOTTOM); + if(guess == NULL) { + if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) { + guess = find_chid(obj, dsc->focused_obj, FIND_FIRST_ROW); + } + else { + lv_group_focus_next(lv_obj_get_group(obj)); + } + } + } + } + else if(key == LV_KEY_UP) { + if((dsc->ctrl & LV_GRIDNAV_CTRL_SCROLL_FIRST) && lv_obj_has_flag(dsc->focused_obj, LV_OBJ_FLAG_SCROLLABLE) && + lv_obj_get_scroll_top(dsc->focused_obj) > 0) { + lv_coord_t d = lv_obj_get_height(dsc->focused_obj) / 4; + if(d <= 0) d = 1; + lv_obj_scroll_by_bounded(dsc->focused_obj, 0, d, LV_ANIM_ON); + } + else { + guess = find_chid(obj, dsc->focused_obj, FIND_TOP); + if(guess == NULL) { + if(dsc->ctrl & LV_GRIDNAV_CTRL_ROLLOVER) { + guess = find_chid(obj, dsc->focused_obj, FIND_LAST_ROW); + } + else { + lv_group_focus_prev(lv_obj_get_group(obj)); + } + } + } + } + else { + if(lv_group_get_focused(lv_obj_get_group(obj)) == obj) { + lv_event_send(dsc->focused_obj, LV_EVENT_KEY, &key); + } + } + + if(guess && guess != dsc->focused_obj) { + lv_obj_clear_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + lv_obj_add_state(guess, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + lv_obj_scroll_to_view(guess, LV_ANIM_ON); + dsc->focused_obj = guess; + } + } + else if(code == LV_EVENT_FOCUSED) { + if(dsc->focused_obj == NULL) dsc->focused_obj = find_first_focusable(obj); + if(dsc->focused_obj) { + lv_obj_add_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + lv_obj_scroll_to_view(dsc->focused_obj, LV_ANIM_OFF); + } + } + else if(code == LV_EVENT_DEFOCUSED) { + if(dsc->focused_obj) { + lv_obj_clear_state(dsc->focused_obj, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + } + } + else if(code == LV_EVENT_CHILD_CREATED) { + lv_obj_t * child = lv_event_get_target(e); + if(lv_obj_get_parent(child) == obj) { + if(dsc->focused_obj == NULL) { + dsc->focused_obj = child; + if(lv_obj_has_state(obj, LV_STATE_FOCUSED)) { + lv_obj_add_state(child, LV_STATE_FOCUSED | LV_STATE_FOCUS_KEY); + lv_obj_scroll_to_view(child, LV_ANIM_OFF); + } + } + } + } + else if(code == LV_EVENT_CHILD_DELETED) { + /*This event bubble, so be sure this object's child was deleted. + *As we don't know which object was deleted we can't make the next focused. + *So make the first object focused*/ + lv_obj_t * target = lv_event_get_target(e); + if(target == obj) { + dsc->focused_obj = find_first_focusable(obj); + } + } + else if(code == LV_EVENT_DELETE) { + lv_gridnav_remove(obj); + } + else if(code == LV_EVENT_PRESSED || code == LV_EVENT_PRESSING || code == LV_EVENT_PRESS_LOST || + code == LV_EVENT_LONG_PRESSED || code == LV_EVENT_LONG_PRESSED_REPEAT || + code == LV_EVENT_CLICKED || code == LV_EVENT_RELEASED) { + if(lv_group_get_focused(lv_obj_get_group(obj)) == obj) { + /*Forward press/release related event too*/ + lv_indev_type_t t = lv_indev_get_type(lv_indev_get_act()); + if(t == LV_INDEV_TYPE_ENCODER || t == LV_INDEV_TYPE_KEYPAD) { + lv_event_send(dsc->focused_obj, code, lv_indev_get_act()); + } + } + } +} + +static lv_obj_t * find_chid(lv_obj_t * obj, lv_obj_t * start_child, find_mode_t mode) +{ + lv_coord_t x_start = get_x_center(start_child); + lv_coord_t y_start = get_y_center(start_child); + uint32_t child_cnt = lv_obj_get_child_cnt(obj); + lv_obj_t * guess = NULL; + lv_coord_t x_err_guess = LV_COORD_MAX; + lv_coord_t y_err_guess = LV_COORD_MAX; + lv_coord_t h_half = lv_obj_get_height(start_child) / 2; + lv_coord_t h_max = lv_obj_get_height(obj) + lv_obj_get_scroll_top(obj) + lv_obj_get_scroll_bottom(obj); + uint32_t i; + for(i = 0; i < child_cnt; i++) { + lv_obj_t * child = lv_obj_get_child(obj, i); + if(child == start_child) continue; + if(obj_is_focuable(child) == false) continue; + + lv_coord_t x_err = 0; + lv_coord_t y_err = 0; + switch(mode) { + case FIND_LEFT: + x_err = get_x_center(child) - x_start; + y_err = get_y_center(child) - y_start; + if(x_err >= 0) continue; /*It's on the right*/ + if(LV_ABS(y_err) > h_half) continue; /*Too far*/ + break; + case FIND_RIGHT: + x_err = get_x_center(child) - x_start; + y_err = get_y_center(child) - y_start; + if(x_err <= 0) continue; /*It's on the left*/ + if(LV_ABS(y_err) > h_half) continue; /*Too far*/ + break; + case FIND_TOP: + x_err = get_x_center(child) - x_start; + y_err = get_y_center(child) - y_start; + if(y_err >= 0) continue; /*It's on the bottom*/ + break; + case FIND_BOTTOM: + x_err = get_x_center(child) - x_start; + y_err = get_y_center(child) - y_start; + if(y_err <= 0) continue; /*It's on the top*/ + break; + case FIND_NEXT_ROW_FIRST_ITEM: + y_err = get_y_center(child) - y_start; + if(y_err <= 0) continue; /*It's on the top*/ + x_err = lv_obj_get_x(child); + break; + case FIND_PREV_ROW_LAST_ITEM: + y_err = get_y_center(child) - y_start; + if(y_err >= 0) continue; /*It's on the bottom*/ + x_err = obj->coords.x2 - child->coords.x2; + break; + case FIND_FIRST_ROW: + x_err = get_x_center(child) - x_start; + y_err = lv_obj_get_y(child); + break; + case FIND_LAST_ROW: + x_err = get_x_center(child) - x_start; + y_err = h_max - lv_obj_get_y(child); + } + + if(guess == NULL || + (y_err * y_err + x_err * x_err < y_err_guess * y_err_guess + x_err_guess * x_err_guess)) { + guess = child; + x_err_guess = x_err; + y_err_guess = y_err; + } + } + return guess; +} + +static lv_obj_t * find_first_focusable(lv_obj_t * obj) +{ + uint32_t child_cnt = lv_obj_get_child_cnt(obj); + uint32_t i; + for(i = 0; i < child_cnt; i++) { + lv_obj_t * child = lv_obj_get_child(obj, i); + if(obj_is_focuable(child)) return child; + + } + return NULL; +} + +static lv_obj_t * find_last_focusable(lv_obj_t * obj) +{ + uint32_t child_cnt = lv_obj_get_child_cnt(obj); + int32_t i; + for(i = child_cnt - 1; i >= 0; i--) { + lv_obj_t * child = lv_obj_get_child(obj, i); + if(obj_is_focuable(child)) return child; + } + return NULL; +} + +static bool obj_is_focuable(lv_obj_t * obj) +{ + if(lv_obj_has_flag(obj, LV_OBJ_FLAG_HIDDEN)) return false; + if(lv_obj_has_flag(obj, LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_CLICK_FOCUSABLE)) return true; + else return false; +} + +static lv_coord_t get_x_center(lv_obj_t * obj) +{ + return obj->coords.x1 + lv_area_get_width(&obj->coords) / 2; +} + +static lv_coord_t get_y_center(lv_obj_t * obj) +{ + return obj->coords.y1 + lv_area_get_height(&obj->coords) / 2; +} + +#endif /*LV_USE_GRIDNAV*/ diff --git a/lib/lvgl/src/extra/others/gridnav/lv_gridnav.h b/lib/lvgl/src/extra/others/gridnav/lv_gridnav.h new file mode 100644 index 00000000..f480ded4 --- /dev/null +++ b/lib/lvgl/src/extra/others/gridnav/lv_gridnav.h @@ -0,0 +1,123 @@ +/** + * @file lv_templ.c + * + */ + +/********************* + * INCLUDES + *********************/ + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/*This typedef exists purely to keep -Wpedantic happy when the file is empty.*/ +/*It can be removed.*/ +typedef int _keep_pedantic_happy; + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ +/** + * @file lv_gridnav.h + * + */ + +#ifndef LV_GRIDFOCUS_H +#define LV_GRIDFOCUS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" + +#if LV_USE_GRIDNAV + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef enum { + LV_GRIDNAV_CTRL_NONE = 0x0, + + /** + * If there is no next/previous object in a direction, + * the focus goes to the object in the next/previous row (on left/right keys) + * or first/last row (on up/down keys) + */ + LV_GRIDNAV_CTRL_ROLLOVER = 0x1, + + /** + * If an arrow is pressed and the focused object can be scrolled in that direction + * then it will be scrolled instead of going to the next/previous object. + * If there is no more room for scrolling the next/previous object will be focused normally */ + LV_GRIDNAV_CTRL_SCROLL_FIRST = 0x2, + +} lv_gridnav_ctrl_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Add grid navigation feature to an object. It expects the children to be arranged + * into a grid-like layout. Although it's not required to have pixel perfect alignment. + * This feature makes possible to use keys to navigate among the children and focus them. + * The keys other than arrows and press/release related events + * are forwarded to the focused child. + * @param obj pointer to an object on which navigation should be applied. + * @param ctrl control flags from `lv_gridnav_ctrl_t`. + */ +void lv_gridnav_add(lv_obj_t * obj, lv_gridnav_ctrl_t ctrl); + +/** + * Remove the grid navigation support from an object + * @param obj pointer to an object + */ +void lv_gridnav_remove(lv_obj_t * obj); + +/** + * Manually focus an object on gridnav container + * @param cont pointer to a gridnav container + * @param to_focus pointer to an object to focus + * @param anim_en LV_ANIM_ON/OFF + */ +void lv_gridnav_set_focused(lv_obj_t * cont, lv_obj_t * to_focus, lv_anim_enable_t anim_en); + +/********************** + * MACROS + **********************/ +#endif /*LV_USE_GRIDNAV*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_GRIDFOCUS_H*/ diff --git a/lib/lvgl/src/extra/others/ime/lv_ime_pinyin.c b/lib/lvgl/src/extra/others/ime/lv_ime_pinyin.c new file mode 100644 index 00000000..b1661e4f --- /dev/null +++ b/lib/lvgl/src/extra/others/ime/lv_ime_pinyin.c @@ -0,0 +1,1198 @@ +/** + * @file lv_ime_pinyin.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_ime_pinyin.h" +#if LV_USE_IME_PINYIN != 0 + +#include <stdio.h> + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_ime_pinyin_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_ime_pinyin_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_ime_pinyin_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_ime_pinyin_style_change_event(lv_event_t * e); +static void lv_ime_pinyin_kb_event(lv_event_t * e); +static void lv_ime_pinyin_cand_panel_event(lv_event_t * e); + +static void init_pinyin_dict(lv_obj_t * obj, lv_pinyin_dict_t * dict); +static void pinyin_input_proc(lv_obj_t * obj); +static void pinyin_page_proc(lv_obj_t * obj, uint16_t btn); +static char * pinyin_search_matching(lv_obj_t * obj, char * py_str, uint16_t * cand_num); +static void pinyin_ime_clear_data(lv_obj_t * obj); + +#if LV_IME_PINYIN_USE_K9_MODE + static void pinyin_k9_init_data(lv_obj_t * obj); + static void pinyin_k9_get_legal_py(lv_obj_t * obj, char * k9_input, const char * py9_map[]); + static bool pinyin_k9_is_valid_py(lv_obj_t * obj, char * py_str); + static void pinyin_k9_fill_cand(lv_obj_t * obj); + static void pinyin_k9_cand_page_proc(lv_obj_t * obj, uint16_t dir); +#endif + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_ime_pinyin_class = { + .constructor_cb = lv_ime_pinyin_constructor, + .destructor_cb = lv_ime_pinyin_destructor, + .width_def = LV_SIZE_CONTENT, + .height_def = LV_SIZE_CONTENT, + .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE, + .instance_size = sizeof(lv_ime_pinyin_t), + .base_class = &lv_obj_class +}; + +#if LV_IME_PINYIN_USE_K9_MODE +static char * lv_btnm_def_pinyin_k9_map[LV_IME_PINYIN_K9_CAND_TEXT_NUM + 20] = {\ + ",\0", "1#\0", "abc \0", "def\0", LV_SYMBOL_BACKSPACE"\0", "\n\0", + ".\0", "ghi\0", "jkl\0", "mno\0", LV_SYMBOL_KEYBOARD"\0", "\n\0", + "?\0", "pqrs\0", "tuv\0", "wxyz\0", LV_SYMBOL_NEW_LINE"\0", "\n\0", + LV_SYMBOL_LEFT"\0", "\0" + }; + +static lv_btnmatrix_ctrl_t default_kb_ctrl_k9_map[LV_IME_PINYIN_K9_CAND_TEXT_NUM + 16] = { 1 }; +static char lv_pinyin_k9_cand_str[LV_IME_PINYIN_K9_CAND_TEXT_NUM + 2][LV_IME_PINYIN_K9_MAX_INPUT] = {0}; +#endif + +static char lv_pinyin_cand_str[LV_IME_PINYIN_CAND_TEXT_NUM][4]; +static char * lv_btnm_def_pinyin_sel_map[LV_IME_PINYIN_CAND_TEXT_NUM + 3]; + +#if LV_IME_PINYIN_USE_DEFAULT_DICT +lv_pinyin_dict_t lv_ime_pinyin_def_dict[] = { + { "a", "啊" }, + { "ai", "愛" }, + { "an", "安暗案" }, + { "ba", "吧把爸八" }, + { "bai", "百白敗" }, + { "ban", "半般辦" }, + { "bang", "旁" }, + { "bao", "保薄包報" }, + { "bei", "被背悲北杯備" }, + { "ben", "本" }, + { "bi", "必比避鼻彼筆秘閉" }, + { "bian", "便邊變変辺" }, + { "biao", "表標" }, + { "bie", "別" }, + { "bing", "病並氷" }, + { "bo", "波薄泊" }, + { "bu", "不布步部捕補歩" }, + { "ca", "察" }, + { "cai", "才材菜財採" }, + { "can", "参残參" }, + { "ce", "策側" }, + { "ceng", "曾" }, + { "cha", "差查茶" }, + { "chai", "差" }, + { "chan", "產産單" }, + { "chang", "場廠" }, + { "chao", "超朝" }, + { "che", "車" }, + { "cheng", "成程乗" }, + { "chi", "尺吃持赤池遅歯" }, + { "chong", "充种重種" }, + { "chu", "出初楚触處処" }, + { "chuan", "川船傳" }, + { "chuang", "創窓" }, + { "chun", "春" }, + { "ci", "此次辞差" }, + { "cong", "從従" }, + { "cu", "卒" }, + { "cun", "存村" }, + { "cuo", "錯" }, + { "da", "大打答達" }, + { "dai", "代待帯帶貸" }, + { "dan", "但担擔誕單単" }, + { "dang", "当党當黨" }, + { "dao", "到道盗導島辺" }, + { "de", "的得" }, + { "dei", "" }, + { "deng", "等" }, + { "di", "地得低底弟第締" }, + { "dian", "点电店點電" }, + { "diao", "調" }, + { "ding", "定町" }, + { "dong", "冬東動働凍" }, + { "du", "独度都渡読" }, + { "duan", "段断短斷" }, + { "dui", "對対" }, + { "duo", "多駄" }, + { "e", "嗯悪" }, + { "en", "嗯" }, + { "er", "而耳二兒" }, + { "fa", "乏法發発髪" }, + { "fan", "反返犯番仮販飯範払" }, + { "fang", "方放房坊訪" }, + { "fei", "非飛費" }, + { "fen", "分份" }, + { "feng", "風豐" }, + { "fou", "否不" }, + { "fu", "父夫富服符付附府幅婦復複負払" }, + { "gai", "改概該" }, + { "gan", "甘感敢" }, + { "gang", "港剛" }, + { "gao", "告高" }, + { "ge", "各格歌革割個" }, + { "gei", "給" }, + { "gen", "跟根" }, + { "geng", "更" }, + { "gong", "工共供功公" }, + { "gou", "夠構溝" }, + { "gu", "古故鼓" }, + { "guai", "掛" }, + { "guan", "官管慣館觀関關" }, + { "guang", "光広" }, + { "gui", "規帰" }, + { "guo", "果国裏菓國過" }, + { "hai", "孩海害還" }, + { "han", "寒漢" }, + { "hang", "航行" }, + { "hao", "好号" }, + { "he", "合和喝何荷" }, + { "hei", "黒" }, + { "hen", "很" }, + { "heng", "行横" }, + { "hou", "厚喉候後" }, + { "hu", "乎呼湖護" }, + { "hua", "化画花話畫劃" }, + { "huai", "壊劃" }, + { "huan", "緩環歡還換" }, + { "huang", "黄" }, + { "hui", "回会慧絵揮會" }, + { "hun", "混婚" }, + { "huo", "活或火獲" }, + { "i", "" }, + { "ji", "己计及机既急季寄技即集基祭系奇紀積計記済幾際極繼績機濟" }, + { "jia", "家加價" }, + { "jian", "件建健肩見減間検簡漸" }, + { "jiang", "降強講將港" }, + { "jiao", "叫教交角覚覺較學" }, + { "jie", "介借接姐皆届界解結階節價" }, + { "jin", "今近禁金僅進" }, + { "jing", "京境景静精經経" }, + { "jiu", "就久九酒究" }, + { "ju", "句具局居決挙據舉" }, + { "jue", "角覚覺" }, + { "jun", "均" }, + { "kai", "開" }, + { "kan", "看刊" }, + { "kang", "康" }, + { "kao", "考" }, + { "ke", "可刻科克客渇課" }, + { "ken", "肯" }, + { "kong", "空控" }, + { "kou", "口" }, + { "ku", "苦庫" }, + { "kuai", "快塊会會" }, + { "kuang", "況" }, + { "kun", "困" }, + { "kuo", "括拡適" }, + { "la", "拉啦落" }, + { "lai", "来來頼" }, + { "lao", "老絡落" }, + { "le", "了楽樂" }, + { "lei", "類" }, + { "leng", "冷" }, + { "li", "力立利理例礼離麗裡勵歷" }, + { "lian", "連練臉聯" }, + { "liang", "良量涼兩両" }, + { "liao", "料" }, + { "lie", "列" }, + { "lin", "林隣賃" }, + { "ling", "另令領" }, + { "liu", "六留流" }, + { "lu", "律路録緑陸履慮" }, + { "lv", "旅" }, + { "lun", "輪論" }, + { "luo", "落絡" }, + { "ma", "媽嗎嘛" }, + { "mai", "買売" }, + { "man", "滿" }, + { "mang", "忙" }, + { "mao", "毛猫貿" }, + { "me", "麼" }, + { "mei", "美妹每沒毎媒" }, + { "men", "們" }, + { "mi", "米密秘" }, + { "mian", "免面勉眠" }, + { "miao", "描" }, + { "min", "民皿" }, + { "ming", "命明名" }, + { "mo", "末模麼" }, + { "mou", "某" }, + { "mu", "母木目模" }, + { "na", "那哪拿內南" }, + { "nan", "男南難" }, + { "nao", "腦" }, + { "ne", "那哪呢" }, + { "nei", "内那哪內" }, + { "neng", "能" }, + { "ni", "你妳呢" }, + { "nian", "年念" }, + { "niang", "娘" }, + { "nin", "您" }, + { "ning", "凝" }, + { "niu", "牛" }, + { "nong", "農濃" }, + { "nu", "女努" }, + { "nuan", "暖" }, + { "o", "" }, + { "ou", "歐" }, + { "pa", "怕" }, + { "pian", "片便" }, + { "pai", "迫派排" }, + { "pan", "判番" }, + { "pang", "旁" }, + { "pei", "配" }, + { "peng", "朋" }, + { "pi", "疲否" }, + { "pin", "品貧" }, + { "ping", "平評" }, + { "po", "迫破泊頗" }, + { "pu", "普僕" }, + { "qi", "起其奇七气期泣企妻契気" }, + { "qian", "嵌浅千前鉛錢針" }, + { "qiang", "強將" }, + { "qiao", "橋繰" }, + { "qie", "且切契" }, + { "qin", "寝勤親" }, + { "qing", "青清情晴輕頃請軽" }, + { "qiu", "求秋球" }, + { "qu", "去取趣曲區" }, + { "quan", "全犬券" }, + { "que", "缺確卻" }, + { "ran", "然" }, + { "rang", "讓" }, + { "re", "熱" }, + { "ren", "人任認" }, + { "reng", "仍" }, + { "ri", "日" }, + { "rong", "容" }, + { "rou", "弱若肉" }, + { "ru", "如入" }, + { "ruan", "軟" }, + { "sai", "賽" }, + { "san", "三" }, + { "sao", "騒繰" }, + { "se", "色" }, + { "sen", "森" }, + { "sha", "砂" }, + { "shan", "善山單" }, + { "shang", "上尚商" }, + { "shao", "少紹" }, + { "shaung", "雙" }, + { "she", "社射設捨渉" }, + { "shei", "誰" }, + { "shen", "什申深甚身伸沈神" }, + { "sheng", "生声昇勝乗聲" }, + { "shi", "是失示食时事式十石施使世实史室市始柿氏士仕拭時視師試適実實識" }, + { "shou", "手首守受授" }, + { "shu", "束数暑殊樹書屬輸術" }, + { "shui", "水説說誰" }, + { "shuo", "数説說" }, + { "si", "思寺司四私似死価" }, + { "song", "送" }, + { "su", "速宿素蘇訴" }, + { "suan", "算酸" }, + { "sui", "隨雖歲歳" }, + { "sun", "孫" }, + { "suo", "所" }, + { "ta", "她他它牠" }, + { "tai", "太台態臺" }, + { "tan", "探談曇" }, + { "tang", "糖" }, + { "tao", "桃逃套討" }, + { "te", "特" }, + { "ti", "体提替題體戻" }, + { "tian", "天田" }, + { "tiao", "条條調" }, + { "tie", "鉄" }, + { "ting", "停庭聽町" }, + { "tong", "同童通痛统統" }, + { "tou", "投透頭" }, + { "tu", "土徒茶図" }, + { "tuan", "團" }, + { "tui", "推退" }, + { "tuo", "脱駄" }, + { "u", "" }, + { "v", "" }, + { "wai", "外" }, + { "wan", "完万玩晩腕灣" }, + { "wang", "忘望亡往網" }, + { "wei", "危位未味委為謂維違圍" }, + { "wen", "文温問聞" }, + { "wo", "我" }, + { "wu", "午物五無屋亡鳥務汚" }, + { "xi", "夕息西洗喜系昔席希析嬉膝細習係" }, + { "xia", "下夏狭暇" }, + { "xian", "先限嫌洗現見線顯" }, + { "xiang", "向相香像想象降項詳響" }, + { "xiao", "小笑消效校削咲" }, + { "xie", "写携些解邪械協謝寫契" }, + { "xin", "心信新辛" }, + { "xing", "行形性幸型星興" }, + { "xiong", "兄胸" }, + { "xiu", "休秀修" }, + { "xu", "須需許續緒続" }, + { "xuan", "選懸" }, + { "xue", "学雪削靴學" }, + { "xun", "訓訊" }, + { "ya", "呀押壓" }, + { "yan", "言顔研煙嚴厳験驗塩" }, + { "yang", "央洋陽樣様" }, + { "yao", "要揺腰薬曜" }, + { "ye", "也野夜邪業葉" }, + { "yi", "一已亦依以移意医易伊役異億義議藝醫訳" }, + { "yin", "因引音飲銀" }, + { "ying", "英迎影映應營営" }, + { "yong", "永用泳擁" }, + { "you", "又有右友由尤油遊郵誘優" }, + { "yu", "予育余雨浴欲愈御宇域語於魚與込" }, + { "yuan", "元原源院員円園遠猿願" }, + { "yue", "月越約楽" }, + { "yun", "雲伝運" }, + { "za", "雑" }, + { "zai", "在再載災" }, + { "zang", "蔵" }, + { "zao", "早造" }, + { "ze", "則擇責" }, + { "zen", "怎" }, + { "zeng", "曾增増" }, + { "zha", "札" }, + { "zhai", "宅擇" }, + { "zhan", "站展戰戦" }, + { "zhang", "丈長障帳張" }, + { "zhao", "找着朝招" }, + { "zhe", "者這" }, + { "zhen", "真震針" }, + { "zheng", "正整争政爭" }, + { "zhi", "之只知支止制至治直指值置智値紙製質誌織隻識職執" }, + { "zhong", "中种終重種眾" }, + { "zhou", "周州昼宙洲週" }, + { "zhu", "助主住柱株祝逐注著諸屬術" }, + { "zhuan", "专專転" }, + { "zhuang", "状狀" }, + { "zhui", "追" }, + { "zhun", "準" }, + { "zhuo", "着" }, + { "zi", "子自字姉資" }, + { "zong", "總" }, + { "zuo", "左做昨坐座作" }, + { "zu", "足祖族卒組" }, + { "zui", "最酔" }, + { "zou", "走" }, + {NULL, NULL} +}; +#endif + + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +lv_obj_t * lv_ime_pinyin_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the keyboard of Pinyin input method. + * @param obj pointer to a Pinyin input method object + * @param dict pointer to a Pinyin input method keyboard + */ +void lv_ime_pinyin_set_keyboard(lv_obj_t * obj, lv_obj_t * kb) +{ + if(kb) { + LV_ASSERT_OBJ(kb, &lv_keyboard_class); + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + pinyin_ime->kb = kb; + lv_obj_add_event_cb(pinyin_ime->kb, lv_ime_pinyin_kb_event, LV_EVENT_VALUE_CHANGED, obj); + lv_obj_align_to(pinyin_ime->cand_panel, pinyin_ime->kb, LV_ALIGN_OUT_TOP_MID, 0, 0); +} + +/** + * Set the dictionary of Pinyin input method. + * @param obj pointer to a Pinyin input method object + * @param dict pointer to a Pinyin input method dictionary + */ +void lv_ime_pinyin_set_dict(lv_obj_t * obj, lv_pinyin_dict_t * dict) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + init_pinyin_dict(obj, dict); +} + +/** + * Set mode, 26-key input(k26) or 9-key input(k9). + * @param obj pointer to a Pinyin input method object + * @param mode the mode from 'lv_keyboard_mode_t' + */ +void lv_ime_pinyin_set_mode(lv_obj_t * obj, lv_ime_pinyin_mode_t mode) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + LV_ASSERT_OBJ(pinyin_ime->kb, &lv_keyboard_class); + + pinyin_ime->mode = mode; + +#if LV_IME_PINYIN_USE_K9_MODE + if(pinyin_ime->mode == LV_IME_PINYIN_MODE_K9) { + pinyin_k9_init_data(obj); + lv_keyboard_set_map(pinyin_ime->kb, LV_KEYBOARD_MODE_USER_1, (const char *)lv_btnm_def_pinyin_k9_map, + (const)default_kb_ctrl_k9_map); + lv_keyboard_set_mode(pinyin_ime->kb, LV_KEYBOARD_MODE_USER_1); + } +#endif +} + +/*===================== + * Getter functions + *====================*/ + +/** + * Set the dictionary of Pinyin input method. + * @param obj pointer to a Pinyin IME object + * @return pointer to the Pinyin IME keyboard + */ +lv_obj_t * lv_ime_pinyin_get_kb(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + return pinyin_ime->kb; +} + +/** + * Set the dictionary of Pinyin input method. + * @param obj pointer to a Pinyin input method object + * @return pointer to the Pinyin input method candidate panel + */ +lv_obj_t * lv_ime_pinyin_get_cand_panel(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + return pinyin_ime->cand_panel; +} + +/** + * Set the dictionary of Pinyin input method. + * @param obj pointer to a Pinyin input method object + * @return pointer to the Pinyin input method dictionary + */ +lv_pinyin_dict_t * lv_ime_pinyin_get_dict(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + return pinyin_ime->dict; +} + +/*===================== + * Other functions + *====================*/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_ime_pinyin_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + uint16_t py_str_i = 0; + uint16_t btnm_i = 0; + for(btnm_i = 0; btnm_i < (LV_IME_PINYIN_CAND_TEXT_NUM + 3); btnm_i++) { + if(btnm_i == 0) { + lv_btnm_def_pinyin_sel_map[btnm_i] = "<"; + } + else if(btnm_i == (LV_IME_PINYIN_CAND_TEXT_NUM + 1)) { + lv_btnm_def_pinyin_sel_map[btnm_i] = ">"; + } + else if(btnm_i == (LV_IME_PINYIN_CAND_TEXT_NUM + 2)) { + lv_btnm_def_pinyin_sel_map[btnm_i] = ""; + } + else { + lv_pinyin_cand_str[py_str_i][0] = ' '; + lv_btnm_def_pinyin_sel_map[btnm_i] = lv_pinyin_cand_str[py_str_i]; + py_str_i++; + } + } + + pinyin_ime->mode = LV_IME_PINYIN_MODE_K26; + pinyin_ime->py_page = 0; + pinyin_ime->ta_count = 0; + pinyin_ime->cand_num = 0; + lv_memset_00(pinyin_ime->input_char, sizeof(pinyin_ime->input_char)); + lv_memset_00(pinyin_ime->py_num, sizeof(pinyin_ime->py_num)); + lv_memset_00(pinyin_ime->py_pos, sizeof(pinyin_ime->py_pos)); + + lv_obj_set_size(obj, LV_PCT(100), LV_PCT(55)); + lv_obj_align(obj, LV_ALIGN_BOTTOM_MID, 0, 0); + +#if LV_IME_PINYIN_USE_DEFAULT_DICT + init_pinyin_dict(obj, lv_ime_pinyin_def_dict); +#endif + + /* Init pinyin_ime->cand_panel */ + pinyin_ime->cand_panel = lv_btnmatrix_create(lv_scr_act()); + lv_btnmatrix_set_map(pinyin_ime->cand_panel, (const char **)lv_btnm_def_pinyin_sel_map); + lv_obj_set_size(pinyin_ime->cand_panel, LV_PCT(100), LV_PCT(5)); + lv_obj_add_flag(pinyin_ime->cand_panel, LV_OBJ_FLAG_HIDDEN); + + lv_btnmatrix_set_one_checked(pinyin_ime->cand_panel, true); + + /* Set cand_panel style*/ + // Default style + lv_obj_set_style_bg_opa(pinyin_ime->cand_panel, LV_OPA_0, 0); + lv_obj_set_style_border_width(pinyin_ime->cand_panel, 0, 0); + lv_obj_set_style_pad_all(pinyin_ime->cand_panel, 8, 0); + lv_obj_set_style_pad_gap(pinyin_ime->cand_panel, 0, 0); + lv_obj_set_style_radius(pinyin_ime->cand_panel, 0, 0); + lv_obj_set_style_pad_gap(pinyin_ime->cand_panel, 0, 0); + lv_obj_set_style_base_dir(pinyin_ime->cand_panel, LV_BASE_DIR_LTR, 0); + + // LV_PART_ITEMS style + lv_obj_set_style_radius(pinyin_ime->cand_panel, 12, LV_PART_ITEMS); + lv_obj_set_style_bg_color(pinyin_ime->cand_panel, lv_color_white(), LV_PART_ITEMS); + lv_obj_set_style_bg_opa(pinyin_ime->cand_panel, LV_OPA_0, LV_PART_ITEMS); + lv_obj_set_style_shadow_opa(pinyin_ime->cand_panel, LV_OPA_0, LV_PART_ITEMS); + + // LV_PART_ITEMS | LV_STATE_PRESSED style + lv_obj_set_style_bg_opa(pinyin_ime->cand_panel, LV_OPA_COVER, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_set_style_bg_color(pinyin_ime->cand_panel, lv_color_white(), LV_PART_ITEMS | LV_STATE_PRESSED); + + /* event handler */ + lv_obj_add_event_cb(pinyin_ime->cand_panel, lv_ime_pinyin_cand_panel_event, LV_EVENT_VALUE_CHANGED, obj); + lv_obj_add_event_cb(obj, lv_ime_pinyin_style_change_event, LV_EVENT_STYLE_CHANGED, NULL); + +#if LV_IME_PINYIN_USE_K9_MODE + pinyin_ime->k9_input_str_len = 0; + pinyin_ime->k9_py_ll_pos = 0; + pinyin_ime->k9_legal_py_count = 0; + lv_memset_00(pinyin_ime->k9_input_str, LV_IME_PINYIN_K9_MAX_INPUT); + + pinyin_k9_init_data(obj); + + _lv_ll_init(&(pinyin_ime->k9_legal_py_ll), sizeof(ime_pinyin_k9_py_str_t)); +#endif +} + + +static void lv_ime_pinyin_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + if(lv_obj_is_valid(pinyin_ime->kb)) + lv_obj_del(pinyin_ime->kb); + + if(lv_obj_is_valid(pinyin_ime->cand_panel)) + lv_obj_del(pinyin_ime->cand_panel); +} + + +static void lv_ime_pinyin_kb_event(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * kb = lv_event_get_target(e); + lv_obj_t * obj = lv_event_get_user_data(e); + + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + +#if LV_IME_PINYIN_USE_K9_MODE + static const char * k9_py_map[8] = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}; +#endif + + if(code == LV_EVENT_VALUE_CHANGED) { + uint16_t btn_id = lv_btnmatrix_get_selected_btn(kb); + if(btn_id == LV_BTNMATRIX_BTN_NONE) return; + + const char * txt = lv_btnmatrix_get_btn_text(kb, lv_btnmatrix_get_selected_btn(kb)); + if(txt == NULL) return; + +#if LV_IME_PINYIN_USE_K9_MODE + if(pinyin_ime->mode == LV_IME_PINYIN_MODE_K9) { + lv_obj_t * ta = lv_keyboard_get_textarea(pinyin_ime->kb); + uint16_t tmp_btn_str_len = strlen(pinyin_ime->input_char); + if((btn_id >= 16) && (tmp_btn_str_len > 0) && (btn_id < (16 + LV_IME_PINYIN_K9_CAND_TEXT_NUM))) { + tmp_btn_str_len = strlen(pinyin_ime->input_char); + lv_memset_00(pinyin_ime->input_char, sizeof(pinyin_ime->input_char)); + strcat(pinyin_ime->input_char, txt); + pinyin_input_proc(obj); + + for(int index = 0; index < (pinyin_ime->ta_count + tmp_btn_str_len); index++) { + lv_textarea_del_char(ta); + } + + pinyin_ime->ta_count = tmp_btn_str_len; + pinyin_ime->k9_input_str_len = tmp_btn_str_len; + lv_textarea_add_text(ta, pinyin_ime->input_char); + + return; + } + } +#endif + + if(strcmp(txt, "Enter") == 0 || strcmp(txt, LV_SYMBOL_NEW_LINE) == 0) { + pinyin_ime_clear_data(obj); + lv_obj_add_flag(pinyin_ime->cand_panel, LV_OBJ_FLAG_HIDDEN); + } + else if(strcmp(txt, LV_SYMBOL_BACKSPACE) == 0) { + // del input char + if(pinyin_ime->ta_count > 0) { + if(pinyin_ime->mode == LV_IME_PINYIN_MODE_K26) + pinyin_ime->input_char[pinyin_ime->ta_count - 1] = '\0'; +#if LV_IME_PINYIN_USE_K9_MODE + else + pinyin_ime->k9_input_str[pinyin_ime->ta_count - 1] = '\0'; +#endif + + pinyin_ime->ta_count = pinyin_ime->ta_count - 1; + if(pinyin_ime->ta_count <= 0) { + lv_obj_add_flag(pinyin_ime->cand_panel, LV_OBJ_FLAG_HIDDEN); +#if LV_IME_PINYIN_USE_K9_MODE + lv_memset_00(lv_pinyin_k9_cand_str, sizeof(lv_pinyin_k9_cand_str)); + strcpy(lv_pinyin_k9_cand_str[LV_IME_PINYIN_K9_CAND_TEXT_NUM], LV_SYMBOL_RIGHT"\0"); + strcpy(lv_pinyin_k9_cand_str[LV_IME_PINYIN_K9_CAND_TEXT_NUM + 1], "\0"); +#endif + } + else if(pinyin_ime->mode == LV_IME_PINYIN_MODE_K26) { + pinyin_input_proc(obj); + } +#if LV_IME_PINYIN_USE_K9_MODE + else if(pinyin_ime->mode == LV_IME_PINYIN_MODE_K9) { + pinyin_ime->k9_input_str_len = strlen(pinyin_ime->input_char) - 1; + pinyin_k9_get_legal_py(obj, pinyin_ime->k9_input_str, k9_py_map); + pinyin_k9_fill_cand(obj); + pinyin_input_proc(obj); + } +#endif + } + } + else if((strcmp(txt, "ABC") == 0) || (strcmp(txt, "abc") == 0) || (strcmp(txt, "1#") == 0)) { + pinyin_ime->ta_count = 0; + lv_memset_00(pinyin_ime->input_char, sizeof(pinyin_ime->input_char)); + return; + } + else if(strcmp(txt, LV_SYMBOL_KEYBOARD) == 0) { + if(pinyin_ime->mode == LV_IME_PINYIN_MODE_K26) { + lv_ime_pinyin_set_mode(pinyin_ime, LV_IME_PINYIN_MODE_K9); + } + else { + lv_ime_pinyin_set_mode(pinyin_ime, LV_IME_PINYIN_MODE_K26); + lv_keyboard_set_mode(pinyin_ime->kb, LV_KEYBOARD_MODE_TEXT_LOWER); + } + pinyin_ime_clear_data(obj); + } + else if(strcmp(txt, LV_SYMBOL_OK) == 0) { + pinyin_ime_clear_data(obj); + } + else if((pinyin_ime->mode == LV_IME_PINYIN_MODE_K26) && ((txt[0] >= 'a' && txt[0] <= 'z') || (txt[0] >= 'A' && + txt[0] <= 'Z'))) { + strcat(pinyin_ime->input_char, txt); + pinyin_input_proc(obj); + pinyin_ime->ta_count++; + } +#if LV_IME_PINYIN_USE_K9_MODE + else if((pinyin_ime->mode == LV_IME_PINYIN_MODE_K9) && (txt[0] >= 'a' && txt[0] <= 'z')) { + for(uint16_t i = 0; i < 8; i++) { + if((strcmp(txt, k9_py_map[i]) == 0) || (strcmp(txt, "abc ") == 0)) { + if(strcmp(txt, "abc ") == 0) pinyin_ime->k9_input_str_len += strlen(k9_py_map[i]) + 1; + else pinyin_ime->k9_input_str_len += strlen(k9_py_map[i]); + pinyin_ime->k9_input_str[pinyin_ime->ta_count] = 50 + i; + + break; + } + } + pinyin_k9_get_legal_py(obj, pinyin_ime->k9_input_str, k9_py_map); + pinyin_k9_fill_cand(obj); + pinyin_input_proc(obj); + } + else if(strcmp(txt, LV_SYMBOL_LEFT) == 0) { + pinyin_k9_cand_page_proc(obj, 0); + } + else if(strcmp(txt, LV_SYMBOL_RIGHT) == 0) { + pinyin_k9_cand_page_proc(obj, 1); + } +#endif + } +} + + +static void lv_ime_pinyin_cand_panel_event(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * cand_panel = lv_event_get_target(e); + lv_obj_t * obj = (lv_obj_t *)lv_event_get_user_data(e); + + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + if(code == LV_EVENT_VALUE_CHANGED) { + uint32_t id = lv_btnmatrix_get_selected_btn(cand_panel); + if(id == 0) { + pinyin_page_proc(obj, 0); + return; + } + if(id == (LV_IME_PINYIN_CAND_TEXT_NUM + 1)) { + pinyin_page_proc(obj, 1); + return; + } + + const char * txt = lv_btnmatrix_get_btn_text(cand_panel, id); + lv_obj_t * ta = lv_keyboard_get_textarea(pinyin_ime->kb); + uint16_t index = 0; + for(index = 0; index < pinyin_ime->ta_count; index++) + lv_textarea_del_char(ta); + + lv_textarea_add_text(ta, txt); + + pinyin_ime_clear_data(obj); + } +} + + +static void pinyin_input_proc(lv_obj_t * obj) +{ + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + pinyin_ime->cand_str = pinyin_search_matching(obj, pinyin_ime->input_char, &pinyin_ime->cand_num); + if(pinyin_ime->cand_str == NULL) { + return; + } + + pinyin_ime->py_page = 0; + + for(uint8_t i = 0; i < LV_IME_PINYIN_CAND_TEXT_NUM; i++) { + memset(lv_pinyin_cand_str[i], 0x00, sizeof(lv_pinyin_cand_str[i])); + lv_pinyin_cand_str[i][0] = ' '; + } + + // fill buf + for(uint8_t i = 0; (i < pinyin_ime->cand_num && i < LV_IME_PINYIN_CAND_TEXT_NUM); i++) { + for(uint8_t j = 0; j < 3; j++) { + lv_pinyin_cand_str[i][j] = pinyin_ime->cand_str[i * 3 + j]; + } + } + + lv_obj_clear_flag(pinyin_ime->cand_panel, LV_OBJ_FLAG_HIDDEN); +} + +static void pinyin_page_proc(lv_obj_t * obj, uint16_t dir) +{ + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + uint16_t page_num = pinyin_ime->cand_num / LV_IME_PINYIN_CAND_TEXT_NUM; + uint16_t sur = pinyin_ime->cand_num % LV_IME_PINYIN_CAND_TEXT_NUM; + + if(dir == 0) { + if(pinyin_ime->py_page) { + pinyin_ime->py_page--; + } + } + else { + if(sur == 0) { + page_num -= 1; + } + if(pinyin_ime->py_page < page_num) { + pinyin_ime->py_page++; + } + else return; + } + + for(uint8_t i = 0; i < LV_IME_PINYIN_CAND_TEXT_NUM; i++) { + memset(lv_pinyin_cand_str[i], 0x00, sizeof(lv_pinyin_cand_str[i])); + lv_pinyin_cand_str[i][0] = ' '; + } + + // fill buf + uint16_t offset = pinyin_ime->py_page * (3 * LV_IME_PINYIN_CAND_TEXT_NUM); + for(uint8_t i = 0; (i < pinyin_ime->cand_num && i < LV_IME_PINYIN_CAND_TEXT_NUM); i++) { + if((sur > 0) && (pinyin_ime->py_page == page_num)) { + if(i > sur) + break; + } + for(uint8_t j = 0; j < 3; j++) { + lv_pinyin_cand_str[i][j] = pinyin_ime->cand_str[offset + (i * 3) + j]; + } + } +} + + +static void lv_ime_pinyin_style_change_event(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + if(code == LV_EVENT_STYLE_CHANGED) { + const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN); + lv_obj_set_style_text_font(pinyin_ime->cand_panel, font, 0); + } +} + + +static void init_pinyin_dict(lv_obj_t * obj, lv_pinyin_dict_t * dict) +{ + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + char headletter = 'a'; + uint16_t offset_sum = 0; + uint16_t offset_count = 0; + uint16_t letter_calc = 0; + + pinyin_ime->dict = dict; + + for(uint16_t i = 0; ; i++) { + if((NULL == (dict[i].py)) || (NULL == (dict[i].py_mb))) { + headletter = dict[i - 1].py[0]; + letter_calc = headletter - 'a'; + pinyin_ime->py_num[letter_calc] = offset_count; + break; + } + + if(headletter == (dict[i].py[0])) { + offset_count++; + } + else { + headletter = dict[i].py[0]; + letter_calc = headletter - 'a'; + pinyin_ime->py_num[letter_calc - 1] = offset_count; + offset_sum += offset_count; + pinyin_ime->py_pos[letter_calc] = offset_sum; + + offset_count = 1; + } + } +} + + +static char * pinyin_search_matching(lv_obj_t * obj, char * py_str, uint16_t * cand_num) +{ + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + lv_pinyin_dict_t * cpHZ; + uint8_t index, len = 0, offset; + volatile uint8_t count = 0; + + if(*py_str == '\0') return NULL; + if(*py_str == 'i') return NULL; + if(*py_str == 'u') return NULL; + if(*py_str == 'v') return NULL; + + offset = py_str[0] - 'a'; + len = strlen(py_str); + + cpHZ = &pinyin_ime->dict[pinyin_ime->py_pos[offset]]; + count = pinyin_ime->py_num[offset]; + + while(count--) { + for(index = 0; index < len; index++) { + if(*(py_str + index) != *((cpHZ->py) + index)) { + break; + } + } + + // perfect match + if(len == 1 || index == len) { + // The Chinese character in UTF-8 encoding format is 3 bytes + * cand_num = strlen((const char *)(cpHZ->py_mb)) / 3; + return (char *)(cpHZ->py_mb); + } + cpHZ++; + } + return NULL; +} + +static void pinyin_ime_clear_data(lv_obj_t * obj) +{ + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + +#if LV_IME_PINYIN_USE_K9_MODE + if(pinyin_ime->mode == LV_IME_PINYIN_MODE_K9) { + pinyin_ime->k9_input_str_len = 0; + pinyin_ime->k9_py_ll_pos = 0; + pinyin_ime->k9_legal_py_count = 0; + lv_memset_00(pinyin_ime->k9_input_str, LV_IME_PINYIN_K9_MAX_INPUT); + lv_memset_00(lv_pinyin_k9_cand_str, sizeof(lv_pinyin_k9_cand_str)); + strcpy(lv_pinyin_k9_cand_str[LV_IME_PINYIN_K9_CAND_TEXT_NUM], LV_SYMBOL_RIGHT"\0"); + strcpy(lv_pinyin_k9_cand_str[LV_IME_PINYIN_K9_CAND_TEXT_NUM + 1], "\0"); + } +#endif + + pinyin_ime->ta_count = 0; + lv_memset_00(lv_pinyin_cand_str, (sizeof(lv_pinyin_cand_str))); + lv_memset_00(pinyin_ime->input_char, sizeof(pinyin_ime->input_char)); + + lv_obj_add_flag(pinyin_ime->cand_panel, LV_OBJ_FLAG_HIDDEN); +} + + +#if LV_IME_PINYIN_USE_K9_MODE +static void pinyin_k9_init_data(lv_obj_t * obj) +{ + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + uint16_t py_str_i = 0; + uint16_t btnm_i = 0; + for(btnm_i = 19; btnm_i < (LV_IME_PINYIN_K9_CAND_TEXT_NUM + 21); btnm_i++) { + if(py_str_i == LV_IME_PINYIN_K9_CAND_TEXT_NUM) { + strcpy(lv_pinyin_k9_cand_str[py_str_i], LV_SYMBOL_RIGHT"\0"); + } + else if(py_str_i == LV_IME_PINYIN_K9_CAND_TEXT_NUM + 1) { + strcpy(lv_pinyin_k9_cand_str[py_str_i], "\0"); + } + else { + strcpy(lv_pinyin_k9_cand_str[py_str_i], " \0"); + } + + lv_btnm_def_pinyin_k9_map[btnm_i] = lv_pinyin_k9_cand_str[py_str_i]; + py_str_i++; + } + + default_kb_ctrl_k9_map[0] = LV_KEYBOARD_CTRL_BTN_FLAGS | 1; + default_kb_ctrl_k9_map[4] = LV_KEYBOARD_CTRL_BTN_FLAGS | 1; + default_kb_ctrl_k9_map[5] = LV_KEYBOARD_CTRL_BTN_FLAGS | 1; + default_kb_ctrl_k9_map[9] = LV_KEYBOARD_CTRL_BTN_FLAGS | 1; + default_kb_ctrl_k9_map[10] = LV_KEYBOARD_CTRL_BTN_FLAGS | 1; + default_kb_ctrl_k9_map[14] = LV_KEYBOARD_CTRL_BTN_FLAGS | 1; + default_kb_ctrl_k9_map[15] = LV_KEYBOARD_CTRL_BTN_FLAGS | 1; + default_kb_ctrl_k9_map[LV_IME_PINYIN_K9_CAND_TEXT_NUM + 16] = LV_KEYBOARD_CTRL_BTN_FLAGS | 1; +} + +static void pinyin_k9_get_legal_py(lv_obj_t * obj, char * k9_input, const char * py9_map[]) +{ + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + uint16_t len = strlen(k9_input); + + if((len == 0) || (len >= LV_IME_PINYIN_K9_MAX_INPUT)) { + return; + } + + char py_comp[LV_IME_PINYIN_K9_MAX_INPUT] = {0}; + int mark[LV_IME_PINYIN_K9_MAX_INPUT] = {0}; + int index = 0; + int flag = 0; + int count = 0; + + uint32_t ll_len = 0; + ime_pinyin_k9_py_str_t * ll_index = NULL; + + ll_len = _lv_ll_get_len(&pinyin_ime->k9_legal_py_ll); + ll_index = _lv_ll_get_head(&pinyin_ime->k9_legal_py_ll); + + while(index != -1) { + if(index == len) { + if(pinyin_k9_is_valid_py(obj, py_comp)) { + if((count >= ll_len) || (ll_len == 0)) { + ll_index = _lv_ll_ins_tail(&pinyin_ime->k9_legal_py_ll); + strcpy(ll_index->py_str, py_comp); + } + else if((count < ll_len)) { + strcpy(ll_index->py_str, py_comp); + ll_index = _lv_ll_get_next(&pinyin_ime->k9_legal_py_ll, ll_index); + } + count++; + } + index--; + } + else { + flag = mark[index]; + if(flag < strlen(py9_map[k9_input[index] - '2'])) { + py_comp[index] = py9_map[k9_input[index] - '2'][flag]; + mark[index] = mark[index] + 1; + index++; + } + else { + mark[index] = 0; + index--; + } + } + } + + if(count > 0) { + pinyin_ime->ta_count++; + pinyin_ime->k9_legal_py_count = count; + } +} + + +/*true: visible; false: not visible*/ +static bool pinyin_k9_is_valid_py(lv_obj_t * obj, char * py_str) +{ + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + lv_pinyin_dict_t * cpHZ = NULL; + uint8_t index = 0, len = 0, offset = 0; + uint16_t ret = 1; + volatile uint8_t count = 0; + + if(*py_str == '\0') return false; + if(*py_str == 'i') return false; + if(*py_str == 'u') return false; + if(*py_str == 'v') return false; + + offset = py_str[0] - 'a'; + len = strlen(py_str); + + cpHZ = &pinyin_ime->dict[pinyin_ime->py_pos[offset]]; + count = pinyin_ime->py_num[offset]; + + while(count--) { + for(index = 0; index < len; index++) { + if(*(py_str + index) != *((cpHZ->py) + index)) { + break; + } + } + + // perfect match + if(len == 1 || index == len) { + return true; + } + cpHZ++; + } + return false; +} + + +static void pinyin_k9_fill_cand(lv_obj_t * obj) +{ + static uint16_t len = 0; + uint16_t index = 0, tmp_len = 0; + ime_pinyin_k9_py_str_t * ll_index = NULL; + + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + tmp_len = pinyin_ime->k9_legal_py_count; + + if(tmp_len != len) { + lv_memset_00(lv_pinyin_k9_cand_str, sizeof(lv_pinyin_k9_cand_str)); + strcpy(lv_pinyin_k9_cand_str[LV_IME_PINYIN_K9_CAND_TEXT_NUM], LV_SYMBOL_RIGHT"\0"); + strcpy(lv_pinyin_k9_cand_str[LV_IME_PINYIN_K9_CAND_TEXT_NUM + 1], "\0"); + len = tmp_len; + } + + ll_index = _lv_ll_get_head(&pinyin_ime->k9_legal_py_ll); + strcpy(pinyin_ime->input_char, ll_index->py_str); + while(ll_index) { + if((index >= LV_IME_PINYIN_K9_CAND_TEXT_NUM) || \ + (index >= pinyin_ime->k9_legal_py_count)) + break; + + strcpy(lv_pinyin_k9_cand_str[index], ll_index->py_str); + ll_index = _lv_ll_get_next(&pinyin_ime->k9_legal_py_ll, ll_index); /*Find the next list*/ + index++; + } + pinyin_ime->k9_py_ll_pos = index; + + lv_obj_t * ta = lv_keyboard_get_textarea(pinyin_ime->kb); + for(index = 0; index < pinyin_ime->k9_input_str_len; index++) { + lv_textarea_del_char(ta); + } + pinyin_ime->k9_input_str_len = strlen(pinyin_ime->input_char); + lv_textarea_add_text(ta, pinyin_ime->input_char); +} + + +static void pinyin_k9_cand_page_proc(lv_obj_t * obj, uint16_t dir) +{ + lv_ime_pinyin_t * pinyin_ime = (lv_ime_pinyin_t *)obj; + + lv_obj_t * ta = lv_keyboard_get_textarea(pinyin_ime->kb); + uint16_t ll_len = _lv_ll_get_len(&pinyin_ime->k9_legal_py_ll); + + if((ll_len > LV_IME_PINYIN_K9_CAND_TEXT_NUM) && (pinyin_ime->k9_legal_py_count > LV_IME_PINYIN_K9_CAND_TEXT_NUM)) { + ime_pinyin_k9_py_str_t * ll_index = NULL; + uint16_t tmp_btn_str_len = 0; + int count = 0; + + ll_index = _lv_ll_get_head(&pinyin_ime->k9_legal_py_ll); + while(ll_index) { + if(count >= pinyin_ime->k9_py_ll_pos) break; + + ll_index = _lv_ll_get_next(&pinyin_ime->k9_legal_py_ll, ll_index); /*Find the next list*/ + count++; + } + + if((NULL == ll_index) && (dir == 1)) return; + + lv_memset_00(lv_pinyin_k9_cand_str, sizeof(lv_pinyin_k9_cand_str)); + strcpy(lv_pinyin_k9_cand_str[LV_IME_PINYIN_K9_CAND_TEXT_NUM], LV_SYMBOL_RIGHT"\0"); + strcpy(lv_pinyin_k9_cand_str[LV_IME_PINYIN_K9_CAND_TEXT_NUM + 1], "\0"); + + // next page + if(dir == 1) { + count = 0; + while(ll_index) { + if(count >= (LV_IME_PINYIN_K9_CAND_TEXT_NUM - 1)) + break; + + strcpy(lv_pinyin_k9_cand_str[count], ll_index->py_str); + ll_index = _lv_ll_get_next(&pinyin_ime->k9_legal_py_ll, ll_index); /*Find the next list*/ + count++; + } + pinyin_ime->k9_py_ll_pos += count - 1; + + } + // previous page + else { + count = LV_IME_PINYIN_K9_CAND_TEXT_NUM - 1; + ll_index = _lv_ll_get_prev(&pinyin_ime->k9_legal_py_ll, ll_index); + while(ll_index) { + if(count < 0) break; + + strcpy(lv_pinyin_k9_cand_str[count], ll_index->py_str); + ll_index = _lv_ll_get_prev(&pinyin_ime->k9_legal_py_ll, ll_index); /*Find the previous list*/ + count--; + } + + if(pinyin_ime->k9_py_ll_pos > LV_IME_PINYIN_K9_CAND_TEXT_NUM) + pinyin_ime->k9_py_ll_pos -= 1; + } + + lv_textarea_set_cursor_pos(ta, LV_TEXTAREA_CURSOR_LAST); + } +} + +#endif /*LV_IME_PINYIN_USE_K9_MODE*/ + +#endif /*LV_USE_IME_PINYIN*/ + diff --git a/lib/lvgl/src/extra/others/ime/lv_ime_pinyin.h b/lib/lvgl/src/extra/others/ime/lv_ime_pinyin.h new file mode 100644 index 00000000..3ff7bb98 --- /dev/null +++ b/lib/lvgl/src/extra/others/ime/lv_ime_pinyin.h @@ -0,0 +1,145 @@ +/** + * @file lv_ime_pinyin.h + * + */ +#ifndef LV_IME_PINYIN_H +#define LV_IME_PINYIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_IME_PINYIN != 0 + +/********************* + * DEFINES + *********************/ +#define LV_IME_PINYIN_K9_MAX_INPUT 7 + +/********************** + * TYPEDEFS + **********************/ + +typedef enum { + LV_IME_PINYIN_MODE_K26, + LV_IME_PINYIN_MODE_K9, +} lv_ime_pinyin_mode_t; + +/*Data of pinyin_dict*/ +typedef struct { + const char * const py; + const char * const py_mb; +} lv_pinyin_dict_t; + +/*Data of 9-key input(k9) mode*/ +typedef struct { + char py_str[7]; +} ime_pinyin_k9_py_str_t; + +/*Data of lv_ime_pinyin*/ +typedef struct { + lv_obj_t obj; + lv_obj_t * kb; + lv_obj_t * cand_panel; + lv_pinyin_dict_t * dict; + lv_ll_t k9_legal_py_ll; + char * cand_str; /* Candidate string */ + char input_char[16]; /* Input box character */ +#if LV_IME_PINYIN_USE_K9_MODE + char k9_input_str[LV_IME_PINYIN_K9_MAX_INPUT]; /* 9-key input(k9) mode input string */ + uint16_t k9_py_ll_pos; /* Current pinyin map pages(k9) */ + uint16_t k9_legal_py_count; /* Count of legal Pinyin numbers(k9) */ + uint16_t k9_input_str_len; /* 9-key input(k9) mode input string max len */ +#endif + uint16_t ta_count; /* The number of characters entered in the text box this time */ + uint16_t cand_num; /* Number of candidates */ + uint16_t py_page; /* Current pinyin map pages(k26) */ + uint16_t py_num[26]; /* Number and length of Pinyin */ + uint16_t py_pos[26]; /* Pinyin position */ + uint8_t mode : 1; /* Set mode, 1: 26-key input(k26), 0: 9-key input(k9). Default: 1. */ +} lv_ime_pinyin_t; + +/*********************** + * GLOBAL VARIABLES + ***********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ +lv_obj_t * lv_ime_pinyin_create(lv_obj_t * parent); + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the keyboard of Pinyin input method. + * @param obj pointer to a Pinyin input method object + * @param dict pointer to a Pinyin input method keyboard + */ +void lv_ime_pinyin_set_keyboard(lv_obj_t * obj, lv_obj_t * kb); + +/** + * Set the dictionary of Pinyin input method. + * @param obj pointer to a Pinyin input method object + * @param dict pointer to a Pinyin input method dictionary + */ +void lv_ime_pinyin_set_dict(lv_obj_t * obj, lv_pinyin_dict_t * dict); + +/** + * Set mode, 26-key input(k26) or 9-key input(k9). + * @param obj pointer to a Pinyin input method object + * @param mode the mode from 'lv_ime_pinyin_mode_t' + */ +void lv_ime_pinyin_set_mode(lv_obj_t * obj, lv_ime_pinyin_mode_t mode); + + +/*===================== + * Getter functions + *====================*/ + +/** + * Set the dictionary of Pinyin input method. + * @param obj pointer to a Pinyin IME object + * @return pointer to the Pinyin IME keyboard + */ +lv_obj_t * lv_ime_pinyin_get_kb(lv_obj_t * obj); + + +/** + * Set the dictionary of Pinyin input method. + * @param obj pointer to a Pinyin input method object + * @return pointer to the Pinyin input method candidate panel + */ +lv_obj_t * lv_ime_pinyin_get_cand_panel(lv_obj_t * obj); + + +/** + * Set the dictionary of Pinyin input method. + * @param obj pointer to a Pinyin input method object + * @return pointer to the Pinyin input method dictionary + */ +lv_pinyin_dict_t * lv_ime_pinyin_get_dict(lv_obj_t * obj); + +/*===================== + * Other functions + *====================*/ + +/********************** + * MACROS + **********************/ + +#endif /*LV_IME_PINYIN*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_USE_IME_PINYIN*/ + + diff --git a/lib/lvgl/src/extra/others/imgfont/lv_imgfont.c b/lib/lvgl/src/extra/others/imgfont/lv_imgfont.c new file mode 100644 index 00000000..ad4ab602 --- /dev/null +++ b/lib/lvgl/src/extra/others/imgfont/lv_imgfont.c @@ -0,0 +1,126 @@ +/** + * @file lv_imgfont.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_imgfont.h" + +#if LV_USE_IMGFONT + +/********************* + * DEFINES + *********************/ +#define LV_IMGFONT_PATH_MAX_LEN 64 + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_font_t * font; + lv_get_imgfont_path_cb_t path_cb; + char path[LV_IMGFONT_PATH_MAX_LEN]; +} imgfont_dsc_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static const uint8_t * imgfont_get_glyph_bitmap(const lv_font_t * font, uint32_t unicode); +static bool imgfont_get_glyph_dsc(const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, + uint32_t unicode, uint32_t unicode_next); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +lv_font_t * lv_imgfont_create(uint16_t height, lv_get_imgfont_path_cb_t path_cb) +{ + LV_ASSERT_MSG(LV_IMGFONT_PATH_MAX_LEN > sizeof(lv_img_dsc_t), + "LV_IMGFONT_PATH_MAX_LEN must be greater than sizeof(lv_img_dsc_t)"); + + size_t size = sizeof(imgfont_dsc_t) + sizeof(lv_font_t); + imgfont_dsc_t * dsc = (imgfont_dsc_t *)lv_mem_alloc(size); + if(dsc == NULL) return NULL; + lv_memset_00(dsc, size); + + dsc->font = (lv_font_t *)(((char *)dsc) + sizeof(imgfont_dsc_t)); + dsc->path_cb = path_cb; + + lv_font_t * font = dsc->font; + font->dsc = dsc; + font->get_glyph_dsc = imgfont_get_glyph_dsc; + font->get_glyph_bitmap = imgfont_get_glyph_bitmap; + font->subpx = LV_FONT_SUBPX_NONE; + font->line_height = height; + font->base_line = 0; + font->underline_position = 0; + font->underline_thickness = 0; + + return dsc->font; +} + +void lv_imgfont_destroy(lv_font_t * font) +{ + if(font == NULL) { + return; + } + + imgfont_dsc_t * dsc = (imgfont_dsc_t *)font->dsc; + lv_mem_free(dsc); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static const uint8_t * imgfont_get_glyph_bitmap(const lv_font_t * font, uint32_t unicode) +{ + LV_UNUSED(unicode); + LV_ASSERT_NULL(font); + imgfont_dsc_t * dsc = (imgfont_dsc_t *)font->dsc; + return (uint8_t *)dsc->path; +} + +static bool imgfont_get_glyph_dsc(const lv_font_t * font, lv_font_glyph_dsc_t * dsc_out, + uint32_t unicode, uint32_t unicode_next) +{ + LV_ASSERT_NULL(font); + + imgfont_dsc_t * dsc = (imgfont_dsc_t *)font->dsc; + LV_ASSERT_NULL(dsc); + if(dsc->path_cb == NULL) return false; + + if(!dsc->path_cb(dsc->font, dsc->path, LV_IMGFONT_PATH_MAX_LEN, unicode, unicode_next)) { + return false; + } + + lv_img_header_t header; + if(lv_img_decoder_get_info(dsc->path, &header) != LV_RES_OK) { + return false; + } + + dsc_out->is_placeholder = 0; + dsc_out->adv_w = header.w; + dsc_out->box_w = header.w; + dsc_out->box_h = header.h; + dsc_out->bpp = LV_IMGFONT_BPP; /* is image identifier */ + dsc_out->ofs_x = 0; + dsc_out->ofs_y = 0; + + return true; +} + +#endif /*LV_USE_IMGFONT*/ diff --git a/lib/lvgl/src/extra/others/imgfont/lv_imgfont.h b/lib/lvgl/src/extra/others/imgfont/lv_imgfont.h new file mode 100644 index 00000000..5069b62f --- /dev/null +++ b/lib/lvgl/src/extra/others/imgfont/lv_imgfont.h @@ -0,0 +1,60 @@ +/** + * @file lv_imgfont.h + * + */ + +#ifndef LV_IMGFONT_H +#define LV_IMGFONT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_IMGFONT + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/* gets the image path name of this character */ +typedef bool (*lv_get_imgfont_path_cb_t)(const lv_font_t * font, void * img_src, + uint16_t len, uint32_t unicode, uint32_t unicode_next); + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Creates a image font with info parameter specified. + * @param height font size + * @param path_cb a function to get the image path name of character. + * @return pointer to the new imgfont or NULL if create error. + */ +lv_font_t * lv_imgfont_create(uint16_t height, lv_get_imgfont_path_cb_t path_cb); + +/** + * Destroy a image font that has been created. + * @param font pointer to image font handle. + */ +void lv_imgfont_destroy(lv_font_t * font); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_IMGFONT*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /* LV_IMGFONT_H */ diff --git a/lib/lvgl/src/extra/others/lv_others.h b/lib/lvgl/src/extra/others/lv_others.h new file mode 100644 index 00000000..106d85e4 --- /dev/null +++ b/lib/lvgl/src/extra/others/lv_others.h @@ -0,0 +1,44 @@ +/** + * @file lv_others.h + * + */ + +#ifndef LV_OTHERS_H +#define LV_OTHERS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "snapshot/lv_snapshot.h" +#include "monkey/lv_monkey.h" +#include "gridnav/lv_gridnav.h" +#include "fragment/lv_fragment.h" +#include "imgfont/lv_imgfont.h" +#include "msg/lv_msg.h" +#include "ime/lv_ime_pinyin.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_OTHERS_H*/ diff --git a/lib/lvgl/src/extra/others/monkey/lv_monkey.c b/lib/lvgl/src/extra/others/monkey/lv_monkey.c new file mode 100755 index 00000000..6ec45e22 --- /dev/null +++ b/lib/lvgl/src/extra/others/monkey/lv_monkey.c @@ -0,0 +1,187 @@ +/** + * @file lv_monkey.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_monkey.h" + +#if LV_USE_MONKEY != 0 + +/********************* + * DEFINES + *********************/ +#define MONKEY_PERIOD_RANGE_MIN_DEF 100 +#define MONKEY_PERIOD_RANGE_MAX_DEF 1000 + +/********************** + * TYPEDEFS + **********************/ +typedef struct _lv_monkey { + lv_monkey_config_t config; + lv_indev_drv_t indev_drv; + lv_indev_data_t indev_data; + lv_indev_t * indev; + lv_timer_t * timer; +#if LV_USE_USER_DATA + void * user_data; +#endif +} lv_monkey_t; + +static const lv_key_t lv_key_map[] = { + LV_KEY_UP, + LV_KEY_DOWN, + LV_KEY_RIGHT, + LV_KEY_LEFT, + LV_KEY_ESC, + LV_KEY_DEL, + LV_KEY_BACKSPACE, + LV_KEY_ENTER, + LV_KEY_NEXT, + LV_KEY_PREV, + LV_KEY_HOME, + LV_KEY_END, +}; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void lv_monkey_read_cb(lv_indev_drv_t * indev_drv, lv_indev_data_t * data); +static int32_t lv_monkey_random(int32_t howsmall, int32_t howbig); +static void lv_monkey_timer_cb(lv_timer_t * timer); + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_monkey_config_init(lv_monkey_config_t * config) +{ + lv_memset_00(config, sizeof(lv_monkey_config_t)); + config->type = LV_INDEV_TYPE_POINTER; + config->period_range.min = MONKEY_PERIOD_RANGE_MIN_DEF; + config->period_range.max = MONKEY_PERIOD_RANGE_MAX_DEF; +} + +lv_monkey_t * lv_monkey_create(const lv_monkey_config_t * config) +{ + lv_monkey_t * monkey = lv_mem_alloc(sizeof(lv_monkey_t)); + LV_ASSERT_MALLOC(monkey); + + lv_memset_00(monkey, sizeof(lv_monkey_t)); + + monkey->config = *config; + + lv_indev_drv_t * drv = &monkey->indev_drv; + lv_indev_drv_init(drv); + drv->type = config->type; + drv->read_cb = lv_monkey_read_cb; + drv->user_data = monkey; + + monkey->timer = lv_timer_create(lv_monkey_timer_cb, monkey->config.period_range.min, monkey); + lv_timer_pause(monkey->timer); + + monkey->indev = lv_indev_drv_register(drv); + + return monkey; +} + +lv_indev_t * lv_monkey_get_indev(lv_monkey_t * monkey) +{ + LV_ASSERT_NULL(monkey); + return monkey->indev; +} + +void lv_monkey_set_enable(lv_monkey_t * monkey, bool en) +{ + LV_ASSERT_NULL(monkey); + en ? lv_timer_resume(monkey->timer) : lv_timer_pause(monkey->timer); +} + +bool lv_monkey_get_enable(lv_monkey_t * monkey) +{ + LV_ASSERT_NULL(monkey); + return !monkey->timer->paused; +} + +#if LV_USE_USER_DATA + +void lv_monkey_set_user_data(lv_monkey_t * monkey, void * user_data) +{ + LV_ASSERT_NULL(monkey); + monkey->user_data = user_data; +} + +void * lv_monkey_get_user_data(lv_monkey_t * monkey) +{ + LV_ASSERT_NULL(monkey); + return monkey->user_data; +} + +#endif + +void lv_monkey_del(lv_monkey_t * monkey) +{ + LV_ASSERT_NULL(monkey); + + lv_timer_del(monkey->timer); + lv_indev_delete(monkey->indev); + lv_mem_free(monkey); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_monkey_read_cb(lv_indev_drv_t * indev_drv, lv_indev_data_t * data) +{ + lv_monkey_t * monkey = indev_drv->user_data; + + data->btn_id = monkey->indev_data.btn_id; + data->point = monkey->indev_data.point; + data->enc_diff = monkey->indev_data.enc_diff; + data->state = monkey->indev_data.state; +} + +static int32_t lv_monkey_random(int32_t howsmall, int32_t howbig) +{ + if(howsmall >= howbig) { + return howsmall; + } + int32_t diff = howbig - howsmall; + return (int32_t)lv_rand(0, diff) + howsmall; +} + +static void lv_monkey_timer_cb(lv_timer_t * timer) +{ + lv_monkey_t * monkey = timer->user_data; + lv_indev_data_t * data = &monkey->indev_data; + + switch(monkey->indev_drv.type) { + case LV_INDEV_TYPE_POINTER: + data->point.x = (lv_coord_t)lv_monkey_random(0, LV_HOR_RES - 1); + data->point.y = (lv_coord_t)lv_monkey_random(0, LV_VER_RES - 1); + break; + case LV_INDEV_TYPE_ENCODER: + data->enc_diff = (int16_t)lv_monkey_random(monkey->config.input_range.min, monkey->config.input_range.max); + break; + case LV_INDEV_TYPE_BUTTON: + data->btn_id = (uint32_t)lv_monkey_random(monkey->config.input_range.min, monkey->config.input_range.max); + break; + case LV_INDEV_TYPE_KEYPAD: { + int32_t index = lv_monkey_random(0, sizeof(lv_key_map) / sizeof(lv_key_map[0]) - 1); + data->key = lv_key_map[index]; + break; + } + default: + break; + } + + data->state = lv_monkey_random(0, 100) < 50 ? LV_INDEV_STATE_RELEASED : LV_INDEV_STATE_PRESSED; + + lv_timer_set_period(monkey->timer, lv_monkey_random(monkey->config.period_range.min, monkey->config.period_range.max)); +} + +#endif /*LV_USE_MONKEY*/ diff --git a/lib/lvgl/src/extra/others/monkey/lv_monkey.h b/lib/lvgl/src/extra/others/monkey/lv_monkey.h new file mode 100755 index 00000000..bf5e13c6 --- /dev/null +++ b/lib/lvgl/src/extra/others/monkey/lv_monkey.h @@ -0,0 +1,118 @@ +/** + * @file lv_monkey.h + * + */ +#ifndef LV_MONKEY_H +#define LV_MONKEY_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_MONKEY != 0 + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +struct _lv_monkey; +typedef struct _lv_monkey lv_monkey_t; + +typedef struct { + /**< Input device type*/ + lv_indev_type_t type; + + /**< Monkey execution period*/ + struct { + uint32_t min; + uint32_t max; + } period_range; + + /**< The range of input value*/ + struct { + int32_t min; + int32_t max; + } input_range; +} lv_monkey_config_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize a monkey config with default values + * @param config pointer to 'lv_monkey_config_t' variable to initialize + */ +void lv_monkey_config_init(lv_monkey_config_t * config); + +/** + * Create monkey for test + * @param config pointer to 'lv_monkey_config_t' variable + * @return pointer to the created monkey + */ +lv_monkey_t * lv_monkey_create(const lv_monkey_config_t * config); + +/** + * Get monkey input device + * @param monkey pointer to a monkey + * @return pointer to the input device + */ +lv_indev_t * lv_monkey_get_indev(lv_monkey_t * monkey); + +/** + * Enable monkey + * @param monkey pointer to a monkey + * @param en set to true to enable + */ +void lv_monkey_set_enable(lv_monkey_t * monkey, bool en); + +/** + * Get whether monkey is enabled + * @param monkey pointer to a monkey + * @return return true if monkey enabled + */ +bool lv_monkey_get_enable(lv_monkey_t * monkey); + +#if LV_USE_USER_DATA + +/** + * Set the user_data field of the monkey + * @param monkey pointer to a monkey + * @param user_data pointer to the new user_data. + */ +void lv_monkey_set_user_data(lv_monkey_t * monkey, void * user_data); + +/** + * Get the user_data field of the monkey + * @param monkey pointer to a monkey + * @return the pointer to the user_data of the monkey + */ +void * lv_monkey_get_user_data(lv_monkey_t * monkey); + +#endif/*LV_USE_USER_DATA*/ + +/** + * Delete monkey + * @param monkey pointer to monkey + */ +void lv_monkey_del(lv_monkey_t * monkey); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_MONKEY*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_MONKEY_H*/ diff --git a/lib/lvgl/src/extra/others/msg/lv_msg.c b/lib/lvgl/src/extra/others/msg/lv_msg.c new file mode 100644 index 00000000..8fd434d0 --- /dev/null +++ b/lib/lvgl/src/extra/others/msg/lv_msg.c @@ -0,0 +1,172 @@ +/** + * @file lv_msg.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_msg.h" +#if LV_USE_MSG + +#include "../../../misc/lv_assert.h" +#include "../../../misc/lv_ll.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + uint32_t msg_id; + lv_msg_subscribe_cb_t callback; + void * user_data; + void * _priv_data; /*Internal: used only store 'obj' in lv_obj_subscribe*/ +} sub_dsc_t; + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void notify(lv_msg_t * m); +static void obj_notify_cb(void * s, lv_msg_t * m); +static void obj_delete_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +static lv_ll_t subs_ll; + +/********************** + * GLOBAL VARIABLES + **********************/ +lv_event_code_t LV_EVENT_MSG_RECEIVED; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_msg_init(void) +{ + LV_EVENT_MSG_RECEIVED = lv_event_register_id(); + _lv_ll_init(&subs_ll, sizeof(sub_dsc_t)); +} + +void * lv_msg_subsribe(uint32_t msg_id, lv_msg_subscribe_cb_t cb, void * user_data) +{ + sub_dsc_t * s = _lv_ll_ins_tail(&subs_ll); + LV_ASSERT_MALLOC(s); + if(s == NULL) return NULL; + + lv_memset_00(s, sizeof(*s)); + + s->msg_id = msg_id; + s->callback = cb; + s->user_data = user_data; + return s; +} + +void * lv_msg_subsribe_obj(uint32_t msg_id, lv_obj_t * obj, void * user_data) +{ + sub_dsc_t * s = lv_msg_subsribe(msg_id, obj_notify_cb, user_data); + if(s == NULL) return NULL; + s->_priv_data = obj; + + /*If not added yet, add a delete event cb which automatically unsubcribes the object*/ + sub_dsc_t * s_first = lv_obj_get_event_user_data(obj, obj_delete_event_cb); + if(s_first == NULL) { + lv_obj_add_event_cb(obj, obj_delete_event_cb, LV_EVENT_DELETE, s); + } + return s; +} + +void lv_msg_unsubscribe(void * s) +{ + LV_ASSERT_NULL(s); + _lv_ll_remove(&subs_ll, s); + lv_mem_free(s); +} + +void lv_msg_send(uint32_t msg_id, const void * payload) +{ + lv_msg_t m; + lv_memset_00(&m, sizeof(m)); + m.id = msg_id; + m.payload = payload; + notify(&m); +} + +uint32_t lv_msg_get_id(lv_msg_t * m) +{ + return m->id; +} + +const void * lv_msg_get_payload(lv_msg_t * m) +{ + return m->payload; +} + +void * lv_msg_get_user_data(lv_msg_t * m) +{ + return m->user_data; +} + +lv_msg_t * lv_event_get_msg(lv_event_t * e) +{ + if(e->code == LV_EVENT_MSG_RECEIVED) { + return lv_event_get_param(e); + } + else { + LV_LOG_WARN("Not interpreted with this event code"); + return NULL; + } +} + + + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void notify(lv_msg_t * m) +{ + sub_dsc_t * s; + _LV_LL_READ(&subs_ll, s) { + if(s->msg_id == m->id && s->callback) { + m->user_data = s->user_data; + m->_priv_data = s->_priv_data; + s->callback(s, m); + } + } +} + +static void obj_notify_cb(void * s, lv_msg_t * m) +{ + LV_UNUSED(s); + lv_event_send(m->_priv_data, LV_EVENT_MSG_RECEIVED, m); +} + +static void obj_delete_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + + sub_dsc_t * s = _lv_ll_get_head(&subs_ll); + sub_dsc_t * s_next; + while(s) { + /*On unsubscribe the list changes s becomes invalid so get next item while it's surely valid*/ + s_next = _lv_ll_get_next(&subs_ll, s); + if(s->_priv_data == obj) { + lv_msg_unsubscribe(s); + } + s = s_next; + } +} + +#endif /*LV_USE_MSG*/ diff --git a/lib/lvgl/src/extra/others/msg/lv_msg.h b/lib/lvgl/src/extra/others/msg/lv_msg.h new file mode 100644 index 00000000..11a55b5a --- /dev/null +++ b/lib/lvgl/src/extra/others/msg/lv_msg.h @@ -0,0 +1,124 @@ +/** + * @file lv_msg.h + * + */ + +#ifndef LV_MSG_H +#define LV_MSG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" +#if LV_USE_MSG + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + uint32_t id; /*Identifier of the message*/ + void * user_data; /*Set the the user_data set in `lv_msg_subscribe`*/ + void * _priv_data; /*Used internally*/ + const void * payload; /*Pointer to the data of the message*/ +} lv_msg_t; + +typedef void (*lv_msg_subscribe_cb_t)(void * s, lv_msg_t * msg); + +typedef void (*lv_msg_request_cb_t)(void * r, uint32_t msg_id); + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Called internally to initialize the message module + */ +void lv_msg_init(void); + +/** + * Subscribe to an `msg_id` + * @param msg_id the message ID to listen to + * @param cb callback to call if a message with `msg_id` was sent + * @param user_data arbitrary data which will be available in `cb` too + * @return pointer to a "subscribe object". It can be used the unsubscribe. + */ +void * lv_msg_subsribe(uint32_t msg_id, lv_msg_subscribe_cb_t cb, void * user_data); + +/** + * Subscribe an `lv_obj` to a message. + * `LV_EVENT_MSG_RECEIVED` will be triggered if a message with matching ID was sent + * @param msg_id the message ID to listen to + * @param obj pointer to an `lv_obj` + * @param user_data arbitrary data which will be available in `cb` too + * @return pointer to a "subscribe object". It can be used the unsubscribe. + */ +void * lv_msg_subsribe_obj(uint32_t msg_id, lv_obj_t * obj, void * user_data); + +/** + * Cancel a previous subscription + * @param s pointer to a "subscibe object". + * Return value of `lv_msg_subsribe` or `lv_msg_subsribe_obj` + */ +void lv_msg_unsubscribe(void * s); + +/** + * Send a message with a given ID and payload + * @param msg_id ID of the message to send + * @param data pointer to the data to send + */ +void lv_msg_send(uint32_t msg_id, const void * payload); + +/** + * Get the ID of a message object. Typically used in the subscriber callback. + * @param m pointer to a message object + * @return the ID of the message + */ +uint32_t lv_msg_get_id(lv_msg_t * m); + +/** + * Get the payload of a message object. Typically used in the subscriber callback. + * @param m pointer to a message object + * @return the payload of the message + */ +const void * lv_msg_get_payload(lv_msg_t * m); + +/** + * Get the user data of a message object. Typically used in the subscriber callback. + * @param m pointer to a message object + * @return the user data of the message + */ +void * lv_msg_get_user_data(lv_msg_t * m); + +/** + * Get the message object from an event object. Can be used in `LV_EVENT_MSG_RECEIVED` events. + * @param e pointer to an event object + * @return the message object or NULL if called with unrelated event code. + */ +lv_msg_t * lv_event_get_msg(lv_event_t * e); + +/********************** + * GLOBAL VARIABLES + **********************/ + +extern lv_event_code_t LV_EVENT_MSG_RECEIVED; + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_MSG*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_MSG_H*/ diff --git a/lib/lvgl/src/extra/others/snapshot/lv_snapshot.c b/lib/lvgl/src/extra/others/snapshot/lv_snapshot.c new file mode 100644 index 00000000..1b227511 --- /dev/null +++ b/lib/lvgl/src/extra/others/snapshot/lv_snapshot.c @@ -0,0 +1,213 @@ +/** + * @file lv_snapshot.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_snapshot.h" +#if LV_USE_SNAPSHOT + +#include <stdbool.h> +#include "../../../core/lv_disp.h" +#include "../../../core/lv_refr.h" +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** Get the buffer needed for object snapshot image. + * + * @param obj The object to generate snapshot. + * @param cf color format for generated image. + * + * @return the buffer size needed in bytes + */ +uint32_t lv_snapshot_buf_size_needed(lv_obj_t * obj, lv_img_cf_t cf) +{ + LV_ASSERT_NULL(obj); + switch(cf) { + case LV_IMG_CF_TRUE_COLOR: + case LV_IMG_CF_TRUE_COLOR_ALPHA: + case LV_IMG_CF_ALPHA_1BIT: + case LV_IMG_CF_ALPHA_2BIT: + case LV_IMG_CF_ALPHA_4BIT: + case LV_IMG_CF_ALPHA_8BIT: + break; + default: + return 0; + } + + lv_obj_update_layout(obj); + + /*Width and height determine snapshot image size.*/ + lv_coord_t w = lv_obj_get_width(obj); + lv_coord_t h = lv_obj_get_height(obj); + lv_coord_t ext_size = _lv_obj_get_ext_draw_size(obj); + w += ext_size * 2; + h += ext_size * 2; + + uint8_t px_size = lv_img_cf_get_px_size(cf); + return w * h * ((px_size + 7) >> 3); +} + +/** Take snapshot for object with its children, save image info to provided buffer. + * + * @param obj The object to generate snapshot. + * @param cf color format for generated image. + * @param dsc image descriptor to store the image result. + * @param buf the buffer to store image data. + * @param buff_size provided buffer size in bytes. + * + * @return LV_RES_OK on success, LV_RES_INV on error. + */ +lv_res_t lv_snapshot_take_to_buf(lv_obj_t * obj, lv_img_cf_t cf, lv_img_dsc_t * dsc, void * buf, uint32_t buff_size) +{ + LV_ASSERT_NULL(obj); + LV_ASSERT_NULL(dsc); + LV_ASSERT_NULL(buf); + + switch(cf) { + case LV_IMG_CF_TRUE_COLOR: + case LV_IMG_CF_TRUE_COLOR_ALPHA: + case LV_IMG_CF_ALPHA_1BIT: + case LV_IMG_CF_ALPHA_2BIT: + case LV_IMG_CF_ALPHA_4BIT: + case LV_IMG_CF_ALPHA_8BIT: + break; + default: + return LV_RES_INV; + } + + if(lv_snapshot_buf_size_needed(obj, cf) > buff_size) + return LV_RES_INV; + + /*Width and height determine snapshot image size.*/ + lv_coord_t w = lv_obj_get_width(obj); + lv_coord_t h = lv_obj_get_height(obj); + lv_coord_t ext_size = _lv_obj_get_ext_draw_size(obj); + w += ext_size * 2; + h += ext_size * 2; + + lv_area_t snapshot_area; + lv_obj_get_coords(obj, &snapshot_area); + lv_area_increase(&snapshot_area, ext_size, ext_size); + + lv_memset(buf, 0x00, buff_size); + lv_memset_00(dsc, sizeof(lv_img_dsc_t)); + + lv_disp_t * obj_disp = lv_obj_get_disp(obj); + lv_disp_drv_t driver; + lv_disp_drv_init(&driver); + /*In lack of a better idea use the resolution of the object's display*/ + driver.hor_res = lv_disp_get_hor_res(obj_disp); + driver.ver_res = lv_disp_get_hor_res(obj_disp); + lv_disp_drv_use_generic_set_px_cb(&driver, cf); + + lv_disp_t fake_disp; + lv_memset_00(&fake_disp, sizeof(lv_disp_t)); + fake_disp.driver = &driver; + + lv_draw_ctx_t * draw_ctx = lv_mem_alloc(obj_disp->driver->draw_ctx_size); + LV_ASSERT_MALLOC(draw_ctx); + if(draw_ctx == NULL) return LV_RES_INV; + obj_disp->driver->draw_ctx_init(fake_disp.driver, draw_ctx); + fake_disp.driver->draw_ctx = draw_ctx; + draw_ctx->clip_area = &snapshot_area; + draw_ctx->buf_area = &snapshot_area; + draw_ctx->buf = (void *)buf; + driver.draw_ctx = draw_ctx; + + lv_disp_t * refr_ori = _lv_refr_get_disp_refreshing(); + _lv_refr_set_disp_refreshing(&fake_disp); + + lv_obj_redraw(draw_ctx, obj); + + _lv_refr_set_disp_refreshing(refr_ori); + obj_disp->driver->draw_ctx_deinit(fake_disp.driver, draw_ctx); + lv_mem_free(draw_ctx); + + dsc->data = buf; + dsc->header.w = w; + dsc->header.h = h; + dsc->header.cf = cf; + return LV_RES_OK; +} + +/** Take snapshot for object with its children, alloc the memory needed. + * + * @param obj The object to generate snapshot. + * @param cf color format for generated image. + * + * @return a pointer to an image descriptor, or NULL if failed. + */ +lv_img_dsc_t * lv_snapshot_take(lv_obj_t * obj, lv_img_cf_t cf) +{ + LV_ASSERT_NULL(obj); + uint32_t buff_size = lv_snapshot_buf_size_needed(obj, cf); + + void * buf = lv_mem_alloc(buff_size); + LV_ASSERT_MALLOC(buf); + if(buf == NULL) { + return NULL; + } + + lv_img_dsc_t * dsc = lv_mem_alloc(sizeof(lv_img_dsc_t)); + LV_ASSERT_MALLOC(buf); + if(dsc == NULL) { + lv_mem_free(buf); + return NULL; + } + + if(lv_snapshot_take_to_buf(obj, cf, dsc, buf, buff_size) == LV_RES_INV) { + lv_mem_free(buf); + lv_mem_free(dsc); + return NULL; + } + + return dsc; +} + +/** Free the snapshot image returned by @ref lv_snapshot_take + * + * It will firstly free the data image takes, then the image descriptor. + * + * @param dsc The image descriptor generated by lv_snapshot_take. + * + */ +void lv_snapshot_free(lv_img_dsc_t * dsc) +{ + if(!dsc) + return; + + if(dsc->data) + lv_mem_free((void *)dsc->data); + + lv_mem_free(dsc); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +#endif /*LV_USE_SNAPSHOT*/ diff --git a/lib/lvgl/src/extra/others/snapshot/lv_snapshot.h b/lib/lvgl/src/extra/others/snapshot/lv_snapshot.h new file mode 100644 index 00000000..64519263 --- /dev/null +++ b/lib/lvgl/src/extra/others/snapshot/lv_snapshot.h @@ -0,0 +1,84 @@ +/** + * @file lv_snapshot.h + * + */ + +#ifndef LV_SNAPSHOT_H +#define LV_SNAPSHOT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include <stdint.h> +#include <stddef.h> + +#include "../../../lv_conf_internal.h" +#include "../../../core/lv_obj.h" + +/********************* + * DEFINES + *********************/ + +#if LV_USE_SNAPSHOT +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** Take snapshot for object with its children. + * + * @param obj The object to generate snapshot. + * @param cf color format for generated image. + * + * @return a pointer to an image descriptor, or NULL if failed. + */ +lv_img_dsc_t * lv_snapshot_take(lv_obj_t * obj, lv_img_cf_t cf); + +/** Free the snapshot image returned by @ref lv_snapshot_take + * + * It will firstly free the data image takes, then the image descriptor. + * + * @param dsc The image descriptor generated by lv_snapshot_take. + * + */ +void lv_snapshot_free(lv_img_dsc_t * dsc); + +/** Get the buffer needed for object snapshot image. + * + * @param obj The object to generate snapshot. + * @param cf color format for generated image. + * + * @return the buffer size needed in bytes + */ +uint32_t lv_snapshot_buf_size_needed(lv_obj_t * obj, lv_img_cf_t cf); + +/** Take snapshot for object with its children, save image info to provided buffer. + * + * @param obj The object to generate snapshot. + * @param cf color format for generated image. + * @param dsc image descriptor to store the image result. + * @param buff the buffer to store image data. + * @param buff_size provided buffer size in bytes. + * + * @return LV_RES_OK on success, LV_RES_INV on error. + */ +lv_res_t lv_snapshot_take_to_buf(lv_obj_t * obj, lv_img_cf_t cf, lv_img_dsc_t * dsc, void * buf, uint32_t buff_size); + + +/********************** + * MACROS + **********************/ +#endif /*LV_USE_SNAPSHOT*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif diff --git a/lib/lvgl/src/extra/themes/basic/lv_theme_basic.c b/lib/lvgl/src/extra/themes/basic/lv_theme_basic.c new file mode 100644 index 00000000..d342455f --- /dev/null +++ b/lib/lvgl/src/extra/themes/basic/lv_theme_basic.c @@ -0,0 +1,428 @@ +/** + * @file lv_theme_basic.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" /*To see all the widgets*/ + +#if LV_USE_THEME_BASIC + +#include "lv_theme_basic.h" +#include "../../../misc/lv_gc.h" + +/********************* + * DEFINES + *********************/ +#define COLOR_SCR lv_palette_lighten(LV_PALETTE_GREY, 4) +#define COLOR_WHITE lv_color_white() +#define COLOR_LIGHT lv_palette_lighten(LV_PALETTE_GREY, 2) +#define COLOR_DARK lv_palette_main(LV_PALETTE_GREY) +#define COLOR_DIM lv_palette_darken(LV_PALETTE_GREY, 2) +#define SCROLLBAR_WIDTH 2 + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_style_t scr; + lv_style_t transp; + lv_style_t white; + lv_style_t light; + lv_style_t dark; + lv_style_t dim; + lv_style_t scrollbar; +#if LV_USE_ARC || LV_USE_COLORWHEEL + lv_style_t arc_line; + lv_style_t arc_knob; +#endif +#if LV_USE_TEXTAREA + lv_style_t ta_cursor; +#endif +} my_theme_styles_t; + + +/********************** + * STATIC PROTOTYPES + **********************/ +static void style_init_reset(lv_style_t * style); +static void theme_apply(lv_theme_t * th, lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +static my_theme_styles_t * styles; +static lv_theme_t theme; +static bool inited; + +/********************** + * MACROS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void style_init(void) +{ + style_init_reset(&styles->scrollbar); + lv_style_set_bg_opa(&styles->scrollbar, LV_OPA_COVER); + lv_style_set_bg_color(&styles->scrollbar, COLOR_DARK); + lv_style_set_width(&styles->scrollbar, SCROLLBAR_WIDTH); + + style_init_reset(&styles->scr); + lv_style_set_bg_opa(&styles->scr, LV_OPA_COVER); + lv_style_set_bg_color(&styles->scr, COLOR_SCR); + lv_style_set_text_color(&styles->scr, COLOR_DIM); + + + style_init_reset(&styles->transp); + lv_style_set_bg_opa(&styles->transp, LV_OPA_TRANSP); + + style_init_reset(&styles->white); + lv_style_set_bg_opa(&styles->white, LV_OPA_COVER); + lv_style_set_bg_color(&styles->white, COLOR_WHITE); + lv_style_set_line_width(&styles->white, 1); + lv_style_set_line_color(&styles->white, COLOR_WHITE); + lv_style_set_arc_width(&styles->white, 2); + lv_style_set_arc_color(&styles->white, COLOR_WHITE); + + style_init_reset(&styles->light); + lv_style_set_bg_opa(&styles->light, LV_OPA_COVER); + lv_style_set_bg_color(&styles->light, COLOR_LIGHT); + lv_style_set_line_width(&styles->light, 1); + lv_style_set_line_color(&styles->light, COLOR_LIGHT); + lv_style_set_arc_width(&styles->light, 2); + lv_style_set_arc_color(&styles->light, COLOR_LIGHT); + + style_init_reset(&styles->dark); + lv_style_set_bg_opa(&styles->dark, LV_OPA_COVER); + lv_style_set_bg_color(&styles->dark, COLOR_DARK); + lv_style_set_line_width(&styles->dark, 1); + lv_style_set_line_color(&styles->dark, COLOR_DARK); + lv_style_set_arc_width(&styles->dark, 2); + lv_style_set_arc_color(&styles->dark, COLOR_DARK); + + style_init_reset(&styles->dim); + lv_style_set_bg_opa(&styles->dim, LV_OPA_COVER); + lv_style_set_bg_color(&styles->dim, COLOR_DIM); + lv_style_set_line_width(&styles->dim, 1); + lv_style_set_line_color(&styles->dim, COLOR_DIM); + lv_style_set_arc_width(&styles->dim, 2); + lv_style_set_arc_color(&styles->dim, COLOR_DIM); + +#if LV_USE_ARC || LV_USE_COLORWHEEL + style_init_reset(&styles->arc_line); + lv_style_set_arc_width(&styles->arc_line, 6); + style_init_reset(&styles->arc_knob); + lv_style_set_pad_all(&styles->arc_knob, 5); +#endif + +#if LV_USE_TEXTAREA + style_init_reset(&styles->ta_cursor); + lv_style_set_border_side(&styles->ta_cursor, LV_BORDER_SIDE_LEFT); + lv_style_set_border_color(&styles->ta_cursor, COLOR_DIM); + lv_style_set_border_width(&styles->ta_cursor, 2); + lv_style_set_bg_opa(&styles->ta_cursor, LV_OPA_TRANSP); + lv_style_set_anim_time(&styles->ta_cursor, 500); +#endif +} + + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +bool lv_theme_basic_is_inited(void) +{ + return LV_GC_ROOT(_lv_theme_basic_styles) == NULL ? false : true; +} + +lv_theme_t * lv_theme_basic_init(lv_disp_t * disp) +{ + + /*This trick is required only to avoid the garbage collection of + *styles' data if LVGL is used in a binding (e.g. Micropython) + *In a general case styles could be in simple `static lv_style_t my_style...` variables*/ + if(!lv_theme_basic_is_inited()) { + inited = false; + LV_GC_ROOT(_lv_theme_basic_styles) = lv_mem_alloc(sizeof(my_theme_styles_t)); + styles = (my_theme_styles_t *)LV_GC_ROOT(_lv_theme_basic_styles); + } + + theme.disp = disp; + theme.font_small = LV_FONT_DEFAULT; + theme.font_normal = LV_FONT_DEFAULT; + theme.font_large = LV_FONT_DEFAULT; + theme.apply_cb = theme_apply; + + style_init(); + + if(disp == NULL || lv_disp_get_theme(disp) == &theme) { + lv_obj_report_style_change(NULL); + } + + inited = true; + + return (lv_theme_t *)&theme; +} + + +static void theme_apply(lv_theme_t * th, lv_obj_t * obj) +{ + LV_UNUSED(th); + + if(lv_obj_get_parent(obj) == NULL) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + return; + } + + if(lv_obj_check_type(obj, &lv_obj_class)) { +#if LV_USE_TABVIEW + lv_obj_t * parent = lv_obj_get_parent(obj); + /*Tabview content area*/ + if(lv_obj_check_type(parent, &lv_tabview_class)) { + lv_obj_add_style(obj, &styles->scr, 0); + return; + } + /*Tabview pages*/ + else if(lv_obj_check_type(lv_obj_get_parent(parent), &lv_tabview_class)) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif + +#if LV_USE_WIN + /*Header*/ + if(lv_obj_get_index(obj) == 0 && lv_obj_check_type(lv_obj_get_parent(obj), &lv_win_class)) { + lv_obj_add_style(obj, &styles->light, 0); + return; + } + /*Content*/ + else if(lv_obj_get_index(obj) == 1 && lv_obj_check_type(lv_obj_get_parent(obj), &lv_win_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif + lv_obj_add_style(obj, &styles->white, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + } +#if LV_USE_BTN + else if(lv_obj_check_type(obj, &lv_btn_class)) { + lv_obj_add_style(obj, &styles->dark, 0); + } +#endif + +#if LV_USE_BTNMATRIX + else if(lv_obj_check_type(obj, &lv_btnmatrix_class)) { +#if LV_USE_MSGBOX + if(lv_obj_check_type(lv_obj_get_parent(obj), &lv_msgbox_class)) { + lv_obj_add_style(obj, &styles->light, LV_PART_ITEMS); + return; + } +#endif +#if LV_USE_TABVIEW + if(lv_obj_check_type(lv_obj_get_parent(obj), &lv_tabview_class)) { + lv_obj_add_style(obj, &styles->light, LV_PART_ITEMS); + return; + } +#endif + lv_obj_add_style(obj, &styles->white, 0); + lv_obj_add_style(obj, &styles->light, LV_PART_ITEMS); + } +#endif + +#if LV_USE_BAR + else if(lv_obj_check_type(obj, &lv_bar_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->dark, LV_PART_INDICATOR); + } +#endif + +#if LV_USE_SLIDER + else if(lv_obj_check_type(obj, &lv_slider_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->dark, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->dim, LV_PART_KNOB); + } +#endif + +#if LV_USE_TABLE + else if(lv_obj_check_type(obj, &lv_table_class)) { + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->light, LV_PART_ITEMS); + } +#endif + +#if LV_USE_CHECKBOX + else if(lv_obj_check_type(obj, &lv_checkbox_class)) { + lv_obj_add_style(obj, &styles->light, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->dark, LV_PART_INDICATOR | LV_STATE_CHECKED); + } +#endif + +#if LV_USE_SWITCH + else if(lv_obj_check_type(obj, &lv_switch_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->dim, LV_PART_KNOB); + } +#endif + +#if LV_USE_CHART + else if(lv_obj_check_type(obj, &lv_chart_class)) { + lv_obj_add_style(obj, &styles->white, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->light, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->dark, LV_PART_TICKS); + lv_obj_add_style(obj, &styles->dark, LV_PART_CURSOR); + } +#endif + +#if LV_USE_ROLLER + else if(lv_obj_check_type(obj, &lv_roller_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->dark, LV_PART_SELECTED); + } +#endif + +#if LV_USE_DROPDOWN + else if(lv_obj_check_type(obj, &lv_dropdown_class)) { + lv_obj_add_style(obj, &styles->white, 0); + } + else if(lv_obj_check_type(obj, &lv_dropdownlist_class)) { + lv_obj_add_style(obj, &styles->white, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->light, LV_PART_SELECTED); + lv_obj_add_style(obj, &styles->dark, LV_PART_SELECTED | LV_STATE_CHECKED); + } +#endif + +#if LV_USE_ARC + else if(lv_obj_check_type(obj, &lv_arc_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->transp, 0); + lv_obj_add_style(obj, &styles->arc_line, 0); + lv_obj_add_style(obj, &styles->dark, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->arc_line, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->dim, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->arc_knob, LV_PART_KNOB); + } +#endif + +#if LV_USE_SPINNER + else if(lv_obj_check_type(obj, &lv_spinner_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->transp, 0); + lv_obj_add_style(obj, &styles->arc_line, 0); + lv_obj_add_style(obj, &styles->dark, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->arc_line, LV_PART_INDICATOR); + } +#endif + +#if LV_USE_COLORWHEEL + else if(lv_obj_check_type(obj, &lv_colorwheel_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->transp, 0); + lv_obj_add_style(obj, &styles->arc_line, 0); + lv_obj_add_style(obj, &styles->dim, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->arc_knob, LV_PART_KNOB); + } +#endif + +#if LV_USE_METER + else if(lv_obj_check_type(obj, &lv_meter_class)) { + lv_obj_add_style(obj, &styles->light, 0); + } +#endif + +#if LV_USE_TEXTAREA + else if(lv_obj_check_type(obj, &lv_textarea_class)) { + lv_obj_add_style(obj, &styles->white, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->ta_cursor, LV_PART_CURSOR | LV_STATE_FOCUSED); + } +#endif + +#if LV_USE_CALENDAR + else if(lv_obj_check_type(obj, &lv_calendar_class)) { + lv_obj_add_style(obj, &styles->light, 0); + } +#endif + +#if LV_USE_KEYBOARD + else if(lv_obj_check_type(obj, &lv_keyboard_class)) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, &styles->white, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->light, LV_PART_ITEMS | LV_STATE_CHECKED); + } +#endif +#if LV_USE_LIST + else if(lv_obj_check_type(obj, &lv_list_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + return; + } + else if(lv_obj_check_type(obj, &lv_list_text_class)) { + + } + else if(lv_obj_check_type(obj, &lv_list_btn_class)) { + lv_obj_add_style(obj, &styles->dark, 0); + + } +#endif +#if LV_USE_MSGBOX + else if(lv_obj_check_type(obj, &lv_msgbox_class)) { + lv_obj_add_style(obj, &styles->light, 0); + return; + } +#endif +#if LV_USE_SPINBOX + else if(lv_obj_check_type(obj, &lv_spinbox_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->dark, LV_PART_CURSOR); + } +#endif +#if LV_USE_TILEVIEW + else if(lv_obj_check_type(obj, &lv_tileview_class)) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + } + else if(lv_obj_check_type(obj, &lv_tileview_tile_class)) { + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + } +#endif + +#if LV_USE_COLORWHEEL + else if(lv_obj_check_type(obj, &lv_colorwheel_class)) { + lv_obj_add_style(obj, &styles->light, 0); + lv_obj_add_style(obj, &styles->light, LV_PART_KNOB); + } +#endif + +#if LV_USE_LED + else if(lv_obj_check_type(obj, &lv_led_class)) { + lv_obj_add_style(obj, &styles->light, 0); + } +#endif +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void style_init_reset(lv_style_t * style) +{ + if(inited) { + lv_style_reset(style); + } + else { + lv_style_init(style); + } +} + +#endif diff --git a/lib/lvgl/src/extra/themes/basic/lv_theme_basic.h b/lib/lvgl/src/extra/themes/basic/lv_theme_basic.h new file mode 100644 index 00000000..93a8fa84 --- /dev/null +++ b/lib/lvgl/src/extra/themes/basic/lv_theme_basic.h @@ -0,0 +1,55 @@ +/** + * @file lv_theme_basic.h + * + */ + +#ifndef LV_THEME_BASIC_H +#define LV_THEME_BASIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" + +#if LV_USE_THEME_BASIC + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize the theme + * @param disp pointer to display to attach the theme + * @return a pointer to reference this theme later + */ +lv_theme_t * lv_theme_basic_init(lv_disp_t * disp); + +/** +* Check if the theme is initialized +* @return true if default theme is initialized, false otherwise +*/ +bool lv_theme_basic_is_inited(void); + +/********************** + * MACROS + **********************/ + +#endif + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_THEME_BASIC_H*/ diff --git a/lib/lvgl/src/extra/themes/default/lv_theme_default.c b/lib/lvgl/src/extra/themes/default/lv_theme_default.c new file mode 100644 index 00000000..47392b0d --- /dev/null +++ b/lib/lvgl/src/extra/themes/default/lv_theme_default.c @@ -0,0 +1,1181 @@ +/** + * @file lv_theme_default.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" /*To see all the widgets*/ + +#if LV_USE_THEME_DEFAULT + +#include "lv_theme_default.h" +#include "../../../misc/lv_gc.h" + +/********************* + * DEFINES + *********************/ +#define MODE_DARK 1 +#define RADIUS_DEFAULT (disp_size == DISP_LARGE ? lv_disp_dpx(theme.disp, 12) : lv_disp_dpx(theme.disp, 8)) + +/*SCREEN*/ +#define LIGHT_COLOR_SCR lv_palette_lighten(LV_PALETTE_GREY, 4) +#define LIGHT_COLOR_CARD lv_color_white() +#define LIGHT_COLOR_TEXT lv_palette_darken(LV_PALETTE_GREY, 4) +#define LIGHT_COLOR_GREY lv_palette_lighten(LV_PALETTE_GREY, 2) +#define DARK_COLOR_SCR lv_color_hex(0x15171A) +#define DARK_COLOR_CARD lv_color_hex(0x282b30) +#define DARK_COLOR_TEXT lv_palette_lighten(LV_PALETTE_GREY, 5) +#define DARK_COLOR_GREY lv_color_hex(0x2f3237) + +#define TRANSITION_TIME LV_THEME_DEFAULT_TRANSITION_TIME +#define BORDER_WIDTH lv_disp_dpx(theme.disp, 2) +#define OUTLINE_WIDTH lv_disp_dpx(theme.disp, 3) + +#define PAD_DEF (disp_size == DISP_LARGE ? lv_disp_dpx(theme.disp, 24) : disp_size == DISP_MEDIUM ? lv_disp_dpx(theme.disp, 20) : lv_disp_dpx(theme.disp, 16)) +#define PAD_SMALL (disp_size == DISP_LARGE ? lv_disp_dpx(theme.disp, 14) : disp_size == DISP_MEDIUM ? lv_disp_dpx(theme.disp, 12) : lv_disp_dpx(theme.disp, 10)) +#define PAD_TINY (disp_size == DISP_LARGE ? lv_disp_dpx(theme.disp, 8) : disp_size == DISP_MEDIUM ? lv_disp_dpx(theme.disp, 6) : lv_disp_dpx(theme.disp, 2)) + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_style_t scr; + lv_style_t scrollbar; + lv_style_t scrollbar_scrolled; + lv_style_t card; + lv_style_t btn; + + /*Utility*/ + lv_style_t bg_color_primary; + lv_style_t bg_color_primary_muted; + lv_style_t bg_color_secondary; + lv_style_t bg_color_secondary_muted; + lv_style_t bg_color_grey; + lv_style_t bg_color_white; + lv_style_t pressed; + lv_style_t disabled; + lv_style_t pad_zero; + lv_style_t pad_tiny; + lv_style_t pad_small; + lv_style_t pad_normal; + lv_style_t pad_gap; + lv_style_t line_space_large; + lv_style_t text_align_center; + lv_style_t outline_primary; + lv_style_t outline_secondary; + lv_style_t circle; + lv_style_t no_radius; + lv_style_t clip_corner; +#if LV_THEME_DEFAULT_GROW + lv_style_t grow; +#endif + lv_style_t transition_delayed; + lv_style_t transition_normal; + lv_style_t anim; + lv_style_t anim_fast; + + /*Parts*/ + lv_style_t knob; + lv_style_t indic; + +#if LV_USE_ARC + lv_style_t arc_indic; + lv_style_t arc_indic_primary; +#endif + +#if LV_USE_CHART + lv_style_t chart_series, chart_indic, chart_ticks, chart_bg; +#endif + +#if LV_USE_DROPDOWN + lv_style_t dropdown_list; +#endif + +#if LV_USE_CHECKBOX + lv_style_t cb_marker, cb_marker_checked; +#endif + +#if LV_USE_SWITCH + lv_style_t switch_knob; +#endif + +#if LV_USE_LINE + lv_style_t line; +#endif + +#if LV_USE_TABLE + lv_style_t table_cell; +#endif + +#if LV_USE_METER + lv_style_t meter_marker, meter_indic; +#endif + +#if LV_USE_TEXTAREA + lv_style_t ta_cursor, ta_placeholder; +#endif + +#if LV_USE_CALENDAR + lv_style_t calendar_btnm_bg, calendar_btnm_day, calendar_header; +#endif + +#if LV_USE_COLORWHEEL + lv_style_t colorwheel_main; +#endif + +#if LV_USE_MENU + lv_style_t menu_bg, menu_cont, menu_sidebar_cont, menu_main_cont, menu_page, menu_header_cont, menu_header_btn, + menu_section, menu_pressed, menu_separator; +#endif + +#if LV_USE_MSGBOX + lv_style_t msgbox_bg, msgbox_btn_bg, msgbox_backdrop_bg; +#endif + +#if LV_USE_KEYBOARD + lv_style_t keyboard_btn_bg; +#endif + +#if LV_USE_LIST + lv_style_t list_bg, list_btn, list_item_grow, list_label; +#endif + +#if LV_USE_TABVIEW + lv_style_t tab_bg_focus, tab_btn; +#endif +#if LV_USE_LED + lv_style_t led; +#endif +} my_theme_styles_t; + +typedef struct { + lv_theme_t base; + uint8_t light : 1; +} my_theme_t; + +typedef enum { + DISP_SMALL = 3, + DISP_MEDIUM = 2, + DISP_LARGE = 1, +} disp_size_t; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void theme_apply(lv_theme_t * th, lv_obj_t * obj); +static void style_init_reset(lv_style_t * style); + +/********************** + * STATIC VARIABLES + **********************/ +static my_theme_styles_t * styles; +static lv_theme_t theme; +static disp_size_t disp_size; +static lv_color_t color_scr; +static lv_color_t color_text; +static lv_color_t color_card; +static lv_color_t color_grey; +static bool inited = false; + + +/********************** + * MACROS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + + +static lv_color_t dark_color_filter_cb(const lv_color_filter_dsc_t * f, lv_color_t c, lv_opa_t opa) +{ + LV_UNUSED(f); + return lv_color_darken(c, opa); +} + +static lv_color_t grey_filter_cb(const lv_color_filter_dsc_t * f, lv_color_t color, lv_opa_t opa) +{ + LV_UNUSED(f); + if(theme.flags & MODE_DARK) return lv_color_mix(lv_palette_darken(LV_PALETTE_GREY, 2), color, opa); + else return lv_color_mix(lv_palette_lighten(LV_PALETTE_GREY, 2), color, opa); +} + +static void style_init(void) +{ + static const lv_style_prop_t trans_props[] = { + LV_STYLE_BG_OPA, LV_STYLE_BG_COLOR, + LV_STYLE_TRANSFORM_WIDTH, LV_STYLE_TRANSFORM_HEIGHT, + LV_STYLE_TRANSLATE_Y, LV_STYLE_TRANSLATE_X, + LV_STYLE_TRANSFORM_ZOOM, LV_STYLE_TRANSFORM_ANGLE, + LV_STYLE_COLOR_FILTER_OPA, LV_STYLE_COLOR_FILTER_DSC, + 0 + }; + + color_scr = theme.flags & MODE_DARK ? DARK_COLOR_SCR : LIGHT_COLOR_SCR; + color_text = theme.flags & MODE_DARK ? DARK_COLOR_TEXT : LIGHT_COLOR_TEXT; + color_card = theme.flags & MODE_DARK ? DARK_COLOR_CARD : LIGHT_COLOR_CARD; + color_grey = theme.flags & MODE_DARK ? DARK_COLOR_GREY : LIGHT_COLOR_GREY; + + style_init_reset(&styles->transition_delayed); + style_init_reset(&styles->transition_normal); +#if TRANSITION_TIME + static lv_style_transition_dsc_t trans_delayed; + lv_style_transition_dsc_init(&trans_delayed, trans_props, lv_anim_path_linear, TRANSITION_TIME, 70, NULL); + + static lv_style_transition_dsc_t trans_normal; + lv_style_transition_dsc_init(&trans_normal, trans_props, lv_anim_path_linear, TRANSITION_TIME, 0, NULL); + + lv_style_set_transition(&styles->transition_delayed, &trans_delayed); /*Go back to default state with delay*/ + + lv_style_set_transition(&styles->transition_normal, &trans_normal); /*Go back to default state with delay*/ +#endif + + style_init_reset(&styles->scrollbar); + lv_color_t sb_color = (theme.flags & MODE_DARK) ? lv_palette_darken(LV_PALETTE_GREY, + 2) : lv_palette_main(LV_PALETTE_GREY); + lv_style_set_bg_color(&styles->scrollbar, sb_color); + + lv_style_set_radius(&styles->scrollbar, LV_RADIUS_CIRCLE); + lv_style_set_pad_all(&styles->scrollbar, lv_disp_dpx(theme.disp, 7)); + lv_style_set_width(&styles->scrollbar, lv_disp_dpx(theme.disp, 5)); + lv_style_set_bg_opa(&styles->scrollbar, LV_OPA_40); +#if TRANSITION_TIME + lv_style_set_transition(&styles->scrollbar, &trans_normal); +#endif + + style_init_reset(&styles->scrollbar_scrolled); + lv_style_set_bg_opa(&styles->scrollbar_scrolled, LV_OPA_COVER); + + style_init_reset(&styles->scr); + lv_style_set_bg_opa(&styles->scr, LV_OPA_COVER); + lv_style_set_bg_color(&styles->scr, color_scr); + lv_style_set_text_color(&styles->scr, color_text); + lv_style_set_pad_row(&styles->scr, PAD_SMALL); + lv_style_set_pad_column(&styles->scr, PAD_SMALL); + + style_init_reset(&styles->card); + lv_style_set_radius(&styles->card, RADIUS_DEFAULT); + lv_style_set_bg_opa(&styles->card, LV_OPA_COVER); + lv_style_set_bg_color(&styles->card, color_card); + lv_style_set_border_color(&styles->card, color_grey); + lv_style_set_border_width(&styles->card, BORDER_WIDTH); + lv_style_set_border_post(&styles->card, true); + lv_style_set_text_color(&styles->card, color_text); + lv_style_set_pad_all(&styles->card, PAD_DEF); + lv_style_set_pad_row(&styles->card, PAD_SMALL); + lv_style_set_pad_column(&styles->card, PAD_SMALL); + lv_style_set_line_color(&styles->card, lv_palette_main(LV_PALETTE_GREY)); + lv_style_set_line_width(&styles->card, lv_disp_dpx(theme.disp, 1)); + + style_init_reset(&styles->outline_primary); + lv_style_set_outline_color(&styles->outline_primary, theme.color_primary); + lv_style_set_outline_width(&styles->outline_primary, OUTLINE_WIDTH); + lv_style_set_outline_pad(&styles->outline_primary, OUTLINE_WIDTH); + lv_style_set_outline_opa(&styles->outline_primary, LV_OPA_50); + + style_init_reset(&styles->outline_secondary); + lv_style_set_outline_color(&styles->outline_secondary, theme.color_secondary); + lv_style_set_outline_width(&styles->outline_secondary, OUTLINE_WIDTH); + lv_style_set_outline_opa(&styles->outline_secondary, LV_OPA_50); + + style_init_reset(&styles->btn); + lv_style_set_radius(&styles->btn, (disp_size == DISP_LARGE ? lv_disp_dpx(theme.disp, + 16) : disp_size == DISP_MEDIUM ? lv_disp_dpx(theme.disp, 12) : lv_disp_dpx(theme.disp, 8))); + lv_style_set_bg_opa(&styles->btn, LV_OPA_COVER); + lv_style_set_bg_color(&styles->btn, color_grey); + if(!(theme.flags & MODE_DARK)) { + lv_style_set_shadow_color(&styles->btn, lv_palette_main(LV_PALETTE_GREY)); + lv_style_set_shadow_width(&styles->btn, LV_DPX(3)); + lv_style_set_shadow_opa(&styles->btn, LV_OPA_50); + lv_style_set_shadow_ofs_y(&styles->btn, lv_disp_dpx(theme.disp, LV_DPX(4))); + } + lv_style_set_text_color(&styles->btn, color_text); + lv_style_set_pad_hor(&styles->btn, PAD_DEF); + lv_style_set_pad_ver(&styles->btn, PAD_SMALL); + lv_style_set_pad_column(&styles->btn, lv_disp_dpx(theme.disp, 5)); + lv_style_set_pad_row(&styles->btn, lv_disp_dpx(theme.disp, 5)); + + static lv_color_filter_dsc_t dark_filter; + lv_color_filter_dsc_init(&dark_filter, dark_color_filter_cb); + + static lv_color_filter_dsc_t grey_filter; + lv_color_filter_dsc_init(&grey_filter, grey_filter_cb); + + style_init_reset(&styles->pressed); + lv_style_set_color_filter_dsc(&styles->pressed, &dark_filter); + lv_style_set_color_filter_opa(&styles->pressed, 35); + + style_init_reset(&styles->disabled); + lv_style_set_color_filter_dsc(&styles->disabled, &grey_filter); + lv_style_set_color_filter_opa(&styles->disabled, LV_OPA_50); + + style_init_reset(&styles->clip_corner); + lv_style_set_clip_corner(&styles->clip_corner, true); + lv_style_set_border_post(&styles->clip_corner, true); + + style_init_reset(&styles->pad_normal); + lv_style_set_pad_all(&styles->pad_normal, PAD_DEF); + lv_style_set_pad_row(&styles->pad_normal, PAD_DEF); + lv_style_set_pad_column(&styles->pad_normal, PAD_DEF); + + style_init_reset(&styles->pad_small); + lv_style_set_pad_all(&styles->pad_small, PAD_SMALL); + lv_style_set_pad_gap(&styles->pad_small, PAD_SMALL); + + style_init_reset(&styles->pad_gap); + lv_style_set_pad_row(&styles->pad_gap, lv_disp_dpx(theme.disp, 10)); + lv_style_set_pad_column(&styles->pad_gap, lv_disp_dpx(theme.disp, 10)); + + style_init_reset(&styles->line_space_large); + lv_style_set_text_line_space(&styles->line_space_large, lv_disp_dpx(theme.disp, 20)); + + style_init_reset(&styles->text_align_center); + lv_style_set_text_align(&styles->text_align_center, LV_TEXT_ALIGN_CENTER); + + style_init_reset(&styles->pad_zero); + lv_style_set_pad_all(&styles->pad_zero, 0); + lv_style_set_pad_row(&styles->pad_zero, 0); + lv_style_set_pad_column(&styles->pad_zero, 0); + + style_init_reset(&styles->pad_tiny); + lv_style_set_pad_all(&styles->pad_tiny, PAD_TINY); + lv_style_set_pad_row(&styles->pad_tiny, PAD_TINY); + lv_style_set_pad_column(&styles->pad_tiny, PAD_TINY); + + style_init_reset(&styles->bg_color_primary); + lv_style_set_bg_color(&styles->bg_color_primary, theme.color_primary); + lv_style_set_text_color(&styles->bg_color_primary, lv_color_white()); + lv_style_set_bg_opa(&styles->bg_color_primary, LV_OPA_COVER); + + style_init_reset(&styles->bg_color_primary_muted); + lv_style_set_bg_color(&styles->bg_color_primary_muted, theme.color_primary); + lv_style_set_text_color(&styles->bg_color_primary_muted, theme.color_primary); + lv_style_set_bg_opa(&styles->bg_color_primary_muted, LV_OPA_20); + + style_init_reset(&styles->bg_color_secondary); + lv_style_set_bg_color(&styles->bg_color_secondary, theme.color_secondary); + lv_style_set_text_color(&styles->bg_color_secondary, lv_color_white()); + lv_style_set_bg_opa(&styles->bg_color_secondary, LV_OPA_COVER); + + style_init_reset(&styles->bg_color_secondary_muted); + lv_style_set_bg_color(&styles->bg_color_secondary_muted, theme.color_secondary); + lv_style_set_text_color(&styles->bg_color_secondary_muted, theme.color_secondary); + lv_style_set_bg_opa(&styles->bg_color_secondary_muted, LV_OPA_20); + + style_init_reset(&styles->bg_color_grey); + lv_style_set_bg_color(&styles->bg_color_grey, color_grey); + lv_style_set_bg_opa(&styles->bg_color_grey, LV_OPA_COVER); + lv_style_set_text_color(&styles->bg_color_grey, color_text); + + style_init_reset(&styles->bg_color_white); + lv_style_set_bg_color(&styles->bg_color_white, color_card); + lv_style_set_bg_opa(&styles->bg_color_white, LV_OPA_COVER); + lv_style_set_text_color(&styles->bg_color_white, color_text); + + style_init_reset(&styles->circle); + lv_style_set_radius(&styles->circle, LV_RADIUS_CIRCLE); + + style_init_reset(&styles->no_radius); + lv_style_set_radius(&styles->no_radius, 0); + +#if LV_THEME_DEFAULT_GROW + style_init_reset(&styles->grow); + lv_style_set_transform_width(&styles->grow, lv_disp_dpx(theme.disp, 3)); + lv_style_set_transform_height(&styles->grow, lv_disp_dpx(theme.disp, 3)); +#endif + + style_init_reset(&styles->knob); + lv_style_set_bg_color(&styles->knob, theme.color_primary); + lv_style_set_bg_opa(&styles->knob, LV_OPA_COVER); + lv_style_set_pad_all(&styles->knob, lv_disp_dpx(theme.disp, 6)); + lv_style_set_radius(&styles->knob, LV_RADIUS_CIRCLE); + + style_init_reset(&styles->anim); + lv_style_set_anim_time(&styles->anim, 200); + + style_init_reset(&styles->anim_fast); + lv_style_set_anim_time(&styles->anim_fast, 120); + +#if LV_USE_ARC + style_init_reset(&styles->arc_indic); + lv_style_set_arc_color(&styles->arc_indic, color_grey); + lv_style_set_arc_width(&styles->arc_indic, lv_disp_dpx(theme.disp, 15)); + lv_style_set_arc_rounded(&styles->arc_indic, true); + + style_init_reset(&styles->arc_indic_primary); + lv_style_set_arc_color(&styles->arc_indic_primary, theme.color_primary); +#endif + +#if LV_USE_DROPDOWN + style_init_reset(&styles->dropdown_list); + lv_style_set_max_height(&styles->dropdown_list, LV_DPI_DEF * 2); +#endif +#if LV_USE_CHECKBOX + style_init_reset(&styles->cb_marker); + lv_style_set_pad_all(&styles->cb_marker, lv_disp_dpx(theme.disp, 3)); + lv_style_set_border_width(&styles->cb_marker, BORDER_WIDTH); + lv_style_set_border_color(&styles->cb_marker, theme.color_primary); + lv_style_set_bg_color(&styles->cb_marker, color_card); + lv_style_set_bg_opa(&styles->cb_marker, LV_OPA_COVER); + lv_style_set_radius(&styles->cb_marker, RADIUS_DEFAULT / 2); + + style_init_reset(&styles->cb_marker_checked); + lv_style_set_bg_img_src(&styles->cb_marker_checked, LV_SYMBOL_OK); + lv_style_set_text_color(&styles->cb_marker_checked, lv_color_white()); + lv_style_set_text_font(&styles->cb_marker_checked, theme.font_small); +#endif + +#if LV_USE_SWITCH + style_init_reset(&styles->switch_knob); + lv_style_set_pad_all(&styles->switch_knob, - lv_disp_dpx(theme.disp, 4)); + lv_style_set_bg_color(&styles->switch_knob, lv_color_white()); +#endif + +#if LV_USE_LINE + style_init_reset(&styles->line); + lv_style_set_line_width(&styles->line, 1); + lv_style_set_line_color(&styles->line, color_text); +#endif + +#if LV_USE_CHART + style_init_reset(&styles->chart_bg); + lv_style_set_border_post(&styles->chart_bg, false); + lv_style_set_pad_column(&styles->chart_bg, lv_disp_dpx(theme.disp, 10)); + lv_style_set_line_color(&styles->chart_bg, color_grey); + + style_init_reset(&styles->chart_series); + lv_style_set_line_width(&styles->chart_series, lv_disp_dpx(theme.disp, 3)); + lv_style_set_radius(&styles->chart_series, lv_disp_dpx(theme.disp, 3)); + lv_style_set_size(&styles->chart_series, lv_disp_dpx(theme.disp, 8)); + lv_style_set_pad_column(&styles->chart_series, lv_disp_dpx(theme.disp, 2)); + + style_init_reset(&styles->chart_indic); + lv_style_set_radius(&styles->chart_indic, LV_RADIUS_CIRCLE); + lv_style_set_size(&styles->chart_indic, lv_disp_dpx(theme.disp, 8)); + lv_style_set_bg_color(&styles->chart_indic, theme.color_primary); + lv_style_set_bg_opa(&styles->chart_indic, LV_OPA_COVER); + + style_init_reset(&styles->chart_ticks); + lv_style_set_line_width(&styles->chart_ticks, lv_disp_dpx(theme.disp, 1)); + lv_style_set_line_color(&styles->chart_ticks, color_text); + lv_style_set_pad_all(&styles->chart_ticks, lv_disp_dpx(theme.disp, 2)); + lv_style_set_text_color(&styles->chart_ticks, lv_palette_main(LV_PALETTE_GREY)); +#endif + +#if LV_USE_MENU + style_init_reset(&styles->menu_bg); + lv_style_set_pad_all(&styles->menu_bg, 0); + lv_style_set_pad_gap(&styles->menu_bg, 0); + lv_style_set_radius(&styles->menu_bg, 0); + lv_style_set_clip_corner(&styles->menu_bg, true); + lv_style_set_border_side(&styles->menu_bg, LV_BORDER_SIDE_NONE); + + style_init_reset(&styles->menu_section); + lv_style_set_radius(&styles->menu_section, RADIUS_DEFAULT); + lv_style_set_clip_corner(&styles->menu_section, true); + lv_style_set_bg_opa(&styles->menu_section, LV_OPA_COVER); + lv_style_set_bg_color(&styles->menu_section, color_card); + lv_style_set_text_color(&styles->menu_section, color_text); + + style_init_reset(&styles->menu_cont); + lv_style_set_pad_hor(&styles->menu_cont, PAD_SMALL); + lv_style_set_pad_ver(&styles->menu_cont, PAD_SMALL); + lv_style_set_pad_gap(&styles->menu_cont, PAD_SMALL); + lv_style_set_border_width(&styles->menu_cont, lv_disp_dpx(theme.disp, 1)); + lv_style_set_border_opa(&styles->menu_cont, LV_OPA_10); + lv_style_set_border_color(&styles->menu_cont, color_text); + lv_style_set_border_side(&styles->menu_cont, LV_BORDER_SIDE_NONE); + + style_init_reset(&styles->menu_sidebar_cont); + lv_style_set_pad_all(&styles->menu_sidebar_cont, 0); + lv_style_set_pad_gap(&styles->menu_sidebar_cont, 0); + lv_style_set_border_width(&styles->menu_sidebar_cont, lv_disp_dpx(theme.disp, 1)); + lv_style_set_border_opa(&styles->menu_sidebar_cont, LV_OPA_10); + lv_style_set_border_color(&styles->menu_sidebar_cont, color_text); + lv_style_set_border_side(&styles->menu_sidebar_cont, LV_BORDER_SIDE_RIGHT); + + style_init_reset(&styles->menu_main_cont); + lv_style_set_pad_all(&styles->menu_main_cont, 0); + lv_style_set_pad_gap(&styles->menu_main_cont, 0); + + style_init_reset(&styles->menu_header_cont); + lv_style_set_pad_hor(&styles->menu_header_cont, PAD_SMALL); + lv_style_set_pad_ver(&styles->menu_header_cont, PAD_TINY); + lv_style_set_pad_gap(&styles->menu_header_cont, PAD_SMALL); + + style_init_reset(&styles->menu_header_btn); + lv_style_set_pad_hor(&styles->menu_header_btn, PAD_TINY); + lv_style_set_pad_ver(&styles->menu_header_btn, PAD_TINY); + lv_style_set_shadow_opa(&styles->menu_header_btn, LV_OPA_TRANSP); + lv_style_set_bg_opa(&styles->menu_header_btn, LV_OPA_TRANSP); + lv_style_set_text_color(&styles->menu_header_btn, color_text); + + style_init_reset(&styles->menu_page); + lv_style_set_pad_hor(&styles->menu_page, 0); + lv_style_set_pad_gap(&styles->menu_page, 0); + + style_init_reset(&styles->menu_pressed); + lv_style_set_bg_opa(&styles->menu_pressed, LV_OPA_20); + lv_style_set_bg_color(&styles->menu_pressed, lv_palette_main(LV_PALETTE_GREY)); + + style_init_reset(&styles->menu_separator); + lv_style_set_bg_opa(&styles->menu_separator, LV_OPA_TRANSP); + lv_style_set_pad_ver(&styles->menu_separator, PAD_TINY); +#endif + +#if LV_USE_METER + style_init_reset(&styles->meter_marker); + lv_style_set_line_width(&styles->meter_marker, lv_disp_dpx(theme.disp, 5)); + lv_style_set_line_color(&styles->meter_marker, color_text); + lv_style_set_size(&styles->meter_marker, lv_disp_dpx(theme.disp, 20)); + lv_style_set_pad_left(&styles->meter_marker, lv_disp_dpx(theme.disp, 15)); + + style_init_reset(&styles->meter_indic); + lv_style_set_radius(&styles->meter_indic, LV_RADIUS_CIRCLE); + lv_style_set_bg_color(&styles->meter_indic, color_text); + lv_style_set_bg_opa(&styles->meter_indic, LV_OPA_COVER); + lv_style_set_size(&styles->meter_indic, lv_disp_dpx(theme.disp, 15)); +#endif + +#if LV_USE_TABLE + style_init_reset(&styles->table_cell); + lv_style_set_border_width(&styles->table_cell, lv_disp_dpx(theme.disp, 1)); + lv_style_set_border_color(&styles->table_cell, color_grey); + lv_style_set_border_side(&styles->table_cell, LV_BORDER_SIDE_TOP | LV_BORDER_SIDE_BOTTOM); +#endif + +#if LV_USE_TEXTAREA + style_init_reset(&styles->ta_cursor); + lv_style_set_border_color(&styles->ta_cursor, color_text); + lv_style_set_border_width(&styles->ta_cursor, lv_disp_dpx(theme.disp, 2)); + lv_style_set_pad_left(&styles->ta_cursor, - lv_disp_dpx(theme.disp, 1)); + lv_style_set_border_side(&styles->ta_cursor, LV_BORDER_SIDE_LEFT); + lv_style_set_anim_time(&styles->ta_cursor, 400); + + style_init_reset(&styles->ta_placeholder); + lv_style_set_text_color(&styles->ta_placeholder, (theme.flags & MODE_DARK) ? lv_palette_darken(LV_PALETTE_GREY, + 2) : lv_palette_lighten(LV_PALETTE_GREY, 1)); +#endif + +#if LV_USE_CALENDAR + style_init_reset(&styles->calendar_btnm_bg); + lv_style_set_pad_all(&styles->calendar_btnm_bg, PAD_SMALL); + lv_style_set_pad_gap(&styles->calendar_btnm_bg, PAD_SMALL / 2); + + style_init_reset(&styles->calendar_btnm_day); + lv_style_set_border_width(&styles->calendar_btnm_day, lv_disp_dpx(theme.disp, 1)); + lv_style_set_border_color(&styles->calendar_btnm_day, color_grey); + lv_style_set_bg_color(&styles->calendar_btnm_day, color_card); + lv_style_set_bg_opa(&styles->calendar_btnm_day, LV_OPA_20); + + style_init_reset(&styles->calendar_header); + lv_style_set_pad_hor(&styles->calendar_header, PAD_SMALL); + lv_style_set_pad_top(&styles->calendar_header, PAD_SMALL); + lv_style_set_pad_bottom(&styles->calendar_header, PAD_TINY); + lv_style_set_pad_gap(&styles->calendar_header, PAD_SMALL); +#endif + +#if LV_USE_COLORWHEEL + style_init_reset(&styles->colorwheel_main); + lv_style_set_arc_width(&styles->colorwheel_main, lv_disp_dpx(theme.disp, 10)); +#endif + +#if LV_USE_MSGBOX + /*To add space for for the button shadow*/ + style_init_reset(&styles->msgbox_btn_bg); + lv_style_set_pad_all(&styles->msgbox_btn_bg, lv_disp_dpx(theme.disp, 4)); + + style_init_reset(&styles->msgbox_bg); + lv_style_set_max_width(&styles->msgbox_bg, lv_pct(100)); + + style_init_reset(&styles->msgbox_backdrop_bg); + lv_style_set_bg_color(&styles->msgbox_backdrop_bg, lv_palette_main(LV_PALETTE_GREY)); + lv_style_set_bg_opa(&styles->msgbox_backdrop_bg, LV_OPA_50); +#endif +#if LV_USE_KEYBOARD + style_init_reset(&styles->keyboard_btn_bg); + lv_style_set_shadow_width(&styles->keyboard_btn_bg, 0); + lv_style_set_radius(&styles->keyboard_btn_bg, disp_size == DISP_SMALL ? RADIUS_DEFAULT / 2 : RADIUS_DEFAULT); +#endif + +#if LV_USE_TABVIEW + style_init_reset(&styles->tab_btn); + lv_style_set_border_color(&styles->tab_btn, theme.color_primary); + lv_style_set_border_width(&styles->tab_btn, BORDER_WIDTH * 2); + lv_style_set_border_side(&styles->tab_btn, LV_BORDER_SIDE_BOTTOM); + + style_init_reset(&styles->tab_bg_focus); + lv_style_set_outline_pad(&styles->tab_bg_focus, -BORDER_WIDTH); +#endif + +#if LV_USE_LIST + style_init_reset(&styles->list_bg); + lv_style_set_pad_hor(&styles->list_bg, PAD_DEF); + lv_style_set_pad_ver(&styles->list_bg, 0); + lv_style_set_pad_gap(&styles->list_bg, 0); + lv_style_set_clip_corner(&styles->list_bg, true); + + style_init_reset(&styles->list_btn); + lv_style_set_border_width(&styles->list_btn, lv_disp_dpx(theme.disp, 1)); + lv_style_set_border_color(&styles->list_btn, color_grey); + lv_style_set_border_side(&styles->list_btn, LV_BORDER_SIDE_BOTTOM); + lv_style_set_pad_all(&styles->list_btn, PAD_SMALL); + lv_style_set_pad_column(&styles->list_btn, PAD_SMALL); + + style_init_reset(&styles->list_item_grow); + lv_style_set_transform_width(&styles->list_item_grow, PAD_DEF); +#endif + + +#if LV_USE_LED + style_init_reset(&styles->led); + lv_style_set_bg_opa(&styles->led, LV_OPA_COVER); + lv_style_set_bg_color(&styles->led, lv_color_white()); + lv_style_set_bg_grad_color(&styles->led, lv_palette_main(LV_PALETTE_GREY)); + lv_style_set_radius(&styles->led, LV_RADIUS_CIRCLE); + lv_style_set_shadow_width(&styles->led, lv_disp_dpx(theme.disp, 15)); + lv_style_set_shadow_color(&styles->led, lv_color_white()); + lv_style_set_shadow_spread(&styles->led, lv_disp_dpx(theme.disp, 5)); +#endif +} + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_theme_t * lv_theme_default_init(lv_disp_t * disp, lv_color_t color_primary, lv_color_t color_secondary, bool dark, + const lv_font_t * font) +{ + + /*This trick is required only to avoid the garbage collection of + *styles' data if LVGL is used in a binding (e.g. Micropython) + *In a general case styles could be in simple `static lv_style_t my_style...` variables*/ + if(!lv_theme_default_is_inited()) { + inited = false; + LV_GC_ROOT(_lv_theme_default_styles) = lv_mem_alloc(sizeof(my_theme_styles_t)); + styles = (my_theme_styles_t *)LV_GC_ROOT(_lv_theme_default_styles); + } + + if(LV_HOR_RES <= 320) disp_size = DISP_SMALL; + else if(LV_HOR_RES < 720) disp_size = DISP_MEDIUM; + else disp_size = DISP_LARGE; + + theme.disp = disp; + theme.color_primary = color_primary; + theme.color_secondary = color_secondary; + theme.font_small = font; + theme.font_normal = font; + theme.font_large = font; + theme.apply_cb = theme_apply; + theme.flags = dark ? MODE_DARK : 0; + + style_init(); + + if(disp == NULL || lv_disp_get_theme(disp) == &theme) lv_obj_report_style_change(NULL); + + inited = true; + + return (lv_theme_t *)&theme; +} + +lv_theme_t * lv_theme_default_get(void) +{ + if(!lv_theme_default_is_inited()) { + return NULL; + } + + return (lv_theme_t *)&theme; +} + +bool lv_theme_default_is_inited(void) +{ + return LV_GC_ROOT(_lv_theme_default_styles) == NULL ? false : true; +} + + +static void theme_apply(lv_theme_t * th, lv_obj_t * obj) +{ + LV_UNUSED(th); + + if(lv_obj_get_parent(obj) == NULL) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } + + if(lv_obj_check_type(obj, &lv_obj_class)) { +#if LV_USE_TABVIEW + lv_obj_t * parent = lv_obj_get_parent(obj); + /*Tabview content area*/ + if(lv_obj_check_type(parent, &lv_tabview_class)) { + return; + } + /*Tabview pages*/ + else if(lv_obj_check_type(lv_obj_get_parent(parent), &lv_tabview_class)) { + lv_obj_add_style(obj, &styles->pad_normal, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif + +#if LV_USE_WIN + /*Header*/ + if(lv_obj_get_index(obj) == 0 && lv_obj_check_type(lv_obj_get_parent(obj), &lv_win_class)) { + lv_obj_add_style(obj, &styles->bg_color_grey, 0); + lv_obj_add_style(obj, &styles->pad_tiny, 0); + return; + } + /*Content*/ + else if(lv_obj_get_index(obj) == 1 && lv_obj_check_type(lv_obj_get_parent(obj), &lv_win_class)) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, &styles->pad_normal, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } +#endif + + +#if LV_USE_CALENDAR + if(lv_obj_check_type(lv_obj_get_parent(obj), &lv_calendar_class)) { + /*No style*/ + return; + } +#endif + + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + } +#if LV_USE_BTN + else if(lv_obj_check_type(obj, &lv_btn_class)) { + lv_obj_add_style(obj, &styles->btn, 0); + lv_obj_add_style(obj, &styles->bg_color_primary, 0); + lv_obj_add_style(obj, &styles->transition_delayed, 0); + lv_obj_add_style(obj, &styles->pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->transition_normal, LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); +#if LV_THEME_DEFAULT_GROW + lv_obj_add_style(obj, &styles->grow, LV_STATE_PRESSED); +#endif + lv_obj_add_style(obj, &styles->bg_color_secondary, LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->disabled, LV_STATE_DISABLED); + +#if LV_USE_MENU + if(lv_obj_check_type(lv_obj_get_parent(obj), &lv_menu_sidebar_header_cont_class) || + lv_obj_check_type(lv_obj_get_parent(obj), &lv_menu_main_header_cont_class)) { + lv_obj_add_style(obj, &styles->menu_header_btn, 0); + lv_obj_add_style(obj, &styles->menu_pressed, LV_STATE_PRESSED); + } +#endif + } +#endif + +#if LV_USE_LINE + else if(lv_obj_check_type(obj, &lv_line_class)) { + lv_obj_add_style(obj, &styles->line, 0); + } +#endif + +#if LV_USE_BTNMATRIX + else if(lv_obj_check_type(obj, &lv_btnmatrix_class)) { +#if LV_USE_MSGBOX + if(lv_obj_check_type(lv_obj_get_parent(obj), &lv_msgbox_class)) { + lv_obj_add_style(obj, &styles->msgbox_btn_bg, 0); + lv_obj_add_style(obj, &styles->pad_gap, 0); + lv_obj_add_style(obj, &styles->btn, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->pressed, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->bg_color_primary_muted, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->bg_color_secondary_muted, LV_PART_ITEMS | LV_STATE_EDITED); + return; + } +#endif +#if LV_USE_TABVIEW + if(lv_obj_check_type(lv_obj_get_parent(obj), &lv_tabview_class)) { + lv_obj_add_style(obj, &styles->bg_color_white, 0); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->tab_bg_focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->pressed, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->bg_color_primary_muted, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->tab_btn, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->outline_primary, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_PART_ITEMS | LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->tab_bg_focus, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + return; + } +#endif + +#if LV_USE_CALENDAR + if(lv_obj_check_type(lv_obj_get_parent(obj), &lv_calendar_class)) { + lv_obj_add_style(obj, &styles->calendar_btnm_bg, 0); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->calendar_btnm_day, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->pressed, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->outline_primary, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_PART_ITEMS | LV_STATE_EDITED); + return; + } +#endif + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->btn, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->pressed, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->outline_primary, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_PART_ITEMS | LV_STATE_EDITED); + } +#endif + +#if LV_USE_BAR + else if(lv_obj_check_type(obj, &lv_bar_class)) { + lv_obj_add_style(obj, &styles->bg_color_primary_muted, 0); + lv_obj_add_style(obj, &styles->circle, 0); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->circle, LV_PART_INDICATOR); + } +#endif + +#if LV_USE_SLIDER + else if(lv_obj_check_type(obj, &lv_slider_class)) { + lv_obj_add_style(obj, &styles->bg_color_primary_muted, 0); + lv_obj_add_style(obj, &styles->circle, 0); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->circle, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->knob, LV_PART_KNOB); +#if LV_THEME_DEFAULT_GROW + lv_obj_add_style(obj, &styles->grow, LV_PART_KNOB | LV_STATE_PRESSED); +#endif + lv_obj_add_style(obj, &styles->transition_delayed, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->transition_normal, LV_PART_KNOB | LV_STATE_PRESSED); + } +#endif + +#if LV_USE_TABLE + else if(lv_obj_check_type(obj, &lv_table_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pad_zero, 0); + lv_obj_add_style(obj, &styles->no_radius, 0); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + lv_obj_add_style(obj, &styles->bg_color_white, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->table_cell, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->pad_normal, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->pressed, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->bg_color_secondary, LV_PART_ITEMS | LV_STATE_EDITED); + } +#endif + +#if LV_USE_CHECKBOX + else if(lv_obj_check_type(obj, &lv_checkbox_class)) { + lv_obj_add_style(obj, &styles->pad_gap, 0); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->disabled, LV_PART_INDICATOR | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->cb_marker, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->cb_marker_checked, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->pressed, LV_PART_INDICATOR | LV_STATE_PRESSED); +#if LV_THEME_DEFAULT_GROW + lv_obj_add_style(obj, &styles->grow, LV_PART_INDICATOR | LV_STATE_PRESSED); +#endif + lv_obj_add_style(obj, &styles->transition_normal, LV_PART_INDICATOR | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->transition_delayed, LV_PART_INDICATOR); + } +#endif + +#if LV_USE_SWITCH + else if(lv_obj_check_type(obj, &lv_switch_class)) { + lv_obj_add_style(obj, &styles->bg_color_grey, 0); + lv_obj_add_style(obj, &styles->circle, 0); + lv_obj_add_style(obj, &styles->anim_fast, 0); + lv_obj_add_style(obj, &styles->disabled, LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->circle, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->disabled, LV_PART_INDICATOR | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->knob, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->bg_color_white, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->switch_knob, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->disabled, LV_PART_KNOB | LV_STATE_DISABLED); + + lv_obj_add_style(obj, &styles->transition_normal, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->transition_normal, LV_PART_INDICATOR); + } +#endif + +#if LV_USE_CHART + else if(lv_obj_check_type(obj, &lv_chart_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pad_small, 0); + lv_obj_add_style(obj, &styles->chart_bg, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + lv_obj_add_style(obj, &styles->chart_series, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->chart_indic, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->chart_ticks, LV_PART_TICKS); + lv_obj_add_style(obj, &styles->chart_series, LV_PART_CURSOR); + } +#endif + +#if LV_USE_ROLLER + else if(lv_obj_check_type(obj, &lv_roller_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->anim, 0); + lv_obj_add_style(obj, &styles->line_space_large, 0); + lv_obj_add_style(obj, &styles->text_align_center, 0); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_SELECTED); + } +#endif + +#if LV_USE_DROPDOWN + else if(lv_obj_check_type(obj, &lv_dropdown_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pad_small, 0); + lv_obj_add_style(obj, &styles->transition_delayed, 0); + lv_obj_add_style(obj, &styles->transition_normal, LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->transition_normal, LV_PART_INDICATOR); + } + else if(lv_obj_check_type(obj, &lv_dropdownlist_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->clip_corner, 0); + lv_obj_add_style(obj, &styles->line_space_large, 0); + lv_obj_add_style(obj, &styles->dropdown_list, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + lv_obj_add_style(obj, &styles->bg_color_white, LV_PART_SELECTED); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_SELECTED | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->pressed, LV_PART_SELECTED | LV_STATE_PRESSED); + } +#endif + +#if LV_USE_ARC + else if(lv_obj_check_type(obj, &lv_arc_class)) { + lv_obj_add_style(obj, &styles->arc_indic, 0); + lv_obj_add_style(obj, &styles->arc_indic, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->arc_indic_primary, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->knob, LV_PART_KNOB); + } +#endif + + +#if LV_USE_SPINNER + else if(lv_obj_check_type(obj, &lv_spinner_class)) { + lv_obj_add_style(obj, &styles->arc_indic, 0); + lv_obj_add_style(obj, &styles->arc_indic, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->arc_indic_primary, LV_PART_INDICATOR); + } +#endif + +#if LV_USE_METER + else if(lv_obj_check_type(obj, &lv_meter_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->circle, 0); + lv_obj_add_style(obj, &styles->meter_indic, LV_PART_INDICATOR); + } +#endif + +#if LV_USE_TEXTAREA + else if(lv_obj_check_type(obj, &lv_textarea_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pad_small, 0); + lv_obj_add_style(obj, &styles->disabled, LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + lv_obj_add_style(obj, &styles->ta_cursor, LV_PART_CURSOR | LV_STATE_FOCUSED); + lv_obj_add_style(obj, &styles->ta_placeholder, LV_PART_TEXTAREA_PLACEHOLDER); + } +#endif + +#if LV_USE_CALENDAR + else if(lv_obj_check_type(obj, &lv_calendar_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pad_zero, 0); + } +#endif + +#if LV_USE_CALENDAR_HEADER_ARROW + else if(lv_obj_check_type(obj, &lv_calendar_header_arrow_class)) { + lv_obj_add_style(obj, &styles->calendar_header, 0); + } +#endif + +#if LV_USE_CALENDAR_HEADER_DROPDOWN + else if(lv_obj_check_type(obj, &lv_calendar_header_dropdown_class)) { + lv_obj_add_style(obj, &styles->calendar_header, 0); + } +#endif + +#if LV_USE_KEYBOARD + else if(lv_obj_check_type(obj, &lv_keyboard_class)) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, disp_size == DISP_LARGE ? &styles->pad_small : &styles->pad_tiny, 0); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->btn, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->bg_color_white, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->keyboard_btn_bg, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->pressed, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->bg_color_grey, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->bg_color_primary_muted, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->bg_color_secondary_muted, LV_PART_ITEMS | LV_STATE_EDITED); + } +#endif +#if LV_USE_LIST + else if(lv_obj_check_type(obj, &lv_list_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->list_bg, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + return; + } + else if(lv_obj_check_type(obj, &lv_list_text_class)) { + lv_obj_add_style(obj, &styles->bg_color_grey, 0); + lv_obj_add_style(obj, &styles->list_item_grow, 0); + } + else if(lv_obj_check_type(obj, &lv_list_btn_class)) { + lv_obj_add_style(obj, &styles->bg_color_white, 0); + lv_obj_add_style(obj, &styles->list_btn, 0); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->list_item_grow, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->list_item_grow, LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->pressed, LV_STATE_PRESSED); + + } +#endif +#if LV_USE_MENU + else if(lv_obj_check_type(obj, &lv_menu_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->menu_bg, 0); + } + else if(lv_obj_check_type(obj, &lv_menu_sidebar_cont_class)) { + lv_obj_add_style(obj, &styles->menu_sidebar_cont, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + } + else if(lv_obj_check_type(obj, &lv_menu_main_cont_class)) { + lv_obj_add_style(obj, &styles->menu_main_cont, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + } + else if(lv_obj_check_type(obj, &lv_menu_cont_class)) { + lv_obj_add_style(obj, &styles->menu_cont, 0); + lv_obj_add_style(obj, &styles->menu_pressed, LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->bg_color_primary_muted, LV_STATE_PRESSED | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->bg_color_primary_muted, LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_STATE_FOCUS_KEY); + } + else if(lv_obj_check_type(obj, &lv_menu_sidebar_header_cont_class) || + lv_obj_check_type(obj, &lv_menu_main_header_cont_class)) { + lv_obj_add_style(obj, &styles->menu_header_cont, 0); + } + else if(lv_obj_check_type(obj, &lv_menu_page_class)) { + lv_obj_add_style(obj, &styles->menu_page, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + } + else if(lv_obj_check_type(obj, &lv_menu_section_class)) { + lv_obj_add_style(obj, &styles->menu_section, 0); + } + else if(lv_obj_check_type(obj, &lv_menu_separator_class)) { + lv_obj_add_style(obj, &styles->menu_separator, 0); + } +#endif +#if LV_USE_MSGBOX + else if(lv_obj_check_type(obj, &lv_msgbox_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->msgbox_bg, 0); + return; + } + else if(lv_obj_check_type(obj, &lv_msgbox_backdrop_class)) { + lv_obj_add_style(obj, &styles->msgbox_backdrop_bg, 0); + } +#endif +#if LV_USE_SPINBOX + else if(lv_obj_check_type(obj, &lv_spinbox_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pad_small, 0); + lv_obj_add_style(obj, &styles->outline_primary, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->outline_secondary, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->bg_color_primary, LV_PART_CURSOR); + } +#endif +#if LV_USE_TILEVIEW + else if(lv_obj_check_type(obj, &lv_tileview_class)) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + } + else if(lv_obj_check_type(obj, &lv_tileview_tile_class)) { + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->scrollbar_scrolled, LV_PART_SCROLLBAR | LV_STATE_SCROLLED); + } +#endif + +#if LV_USE_TABVIEW + else if(lv_obj_check_type(obj, &lv_tabview_class)) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, &styles->pad_zero, 0); + } +#endif + +#if LV_USE_WIN + else if(lv_obj_check_type(obj, &lv_win_class)) { + lv_obj_add_style(obj, &styles->clip_corner, 0); + } +#endif + +#if LV_USE_COLORWHEEL + else if(lv_obj_check_type(obj, &lv_colorwheel_class)) { + lv_obj_add_style(obj, &styles->colorwheel_main, 0); + lv_obj_add_style(obj, &styles->pad_normal, 0); + lv_obj_add_style(obj, &styles->bg_color_white, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->pad_normal, LV_PART_KNOB); + } +#endif + +#if LV_USE_LED + else if(lv_obj_check_type(obj, &lv_led_class)) { + lv_obj_add_style(obj, &styles->led, 0); + } +#endif +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void style_init_reset(lv_style_t * style) +{ + if(inited) { + lv_style_reset(style); + } + else { + lv_style_init(style); + } +} + +#endif diff --git a/lib/lvgl/src/extra/themes/default/lv_theme_default.h b/lib/lvgl/src/extra/themes/default/lv_theme_default.h new file mode 100644 index 00000000..5b1fd918 --- /dev/null +++ b/lib/lvgl/src/extra/themes/default/lv_theme_default.h @@ -0,0 +1,64 @@ +/** + * @file lv_theme_default.h + * + */ + +#ifndef LV_THEME_DEFAULT_H +#define LV_THEME_DEFAULT_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" + +#if LV_USE_THEME_DEFAULT + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize the theme + * @param color_primary the primary color of the theme + * @param color_secondary the secondary color for the theme + * @param font pointer to a font to use. + * @return a pointer to reference this theme later + */ +lv_theme_t * lv_theme_default_init(lv_disp_t * disp, lv_color_t color_primary, lv_color_t color_secondary, bool dark, + const lv_font_t * font); + +/** + * Get default theme + * @return a pointer to default theme, or NULL if this is not initialized + */ +lv_theme_t * lv_theme_default_get(void); + +/** + * Check if default theme is initialized + * @return true if default theme is initialized, false otherwise + */ +bool lv_theme_default_is_inited(void); + +/********************** + * MACROS + **********************/ + +#endif + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_THEME_DEFAULT_H*/ diff --git a/lib/lvgl/src/extra/themes/lv_themes.h b/lib/lvgl/src/extra/themes/lv_themes.h new file mode 100644 index 00000000..372f6260 --- /dev/null +++ b/lib/lvgl/src/extra/themes/lv_themes.h @@ -0,0 +1,40 @@ +/** + * @file lv_themes.h + * + */ + +#ifndef LV_THEMES_H +#define LV_THEMES_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "default/lv_theme_default.h" +#include "mono/lv_theme_mono.h" +#include "basic/lv_theme_basic.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_THEMES_H*/ diff --git a/lib/lvgl/src/extra/themes/mono/lv_theme_mono.c b/lib/lvgl/src/extra/themes/mono/lv_theme_mono.c new file mode 100644 index 00000000..b249e76d --- /dev/null +++ b/lib/lvgl/src/extra/themes/mono/lv_theme_mono.c @@ -0,0 +1,504 @@ +/** + * @file lv_theme_mono.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_THEME_MONO + +#include "lv_theme_mono.h" +#include "../../../misc/lv_gc.h" + +/********************* + * DEFINES + *********************/ + +#define COLOR_FG dark_bg ? lv_color_white() : lv_color_black() +#define COLOR_BG dark_bg ? lv_color_black() : lv_color_white() + +#define BORDER_W_NORMAL 1 +#define BORDER_W_PR 3 +#define BORDER_W_DIS 0 +#define BORDER_W_FOCUS 1 +#define BORDER_W_EDIT 2 +#define PAD_DEF 4 + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_style_t scr; + lv_style_t card; + lv_style_t scrollbar; + lv_style_t btn; + lv_style_t pr; + lv_style_t inv; + lv_style_t disabled; + lv_style_t focus; + lv_style_t edit; + lv_style_t pad_gap; + lv_style_t pad_zero; + lv_style_t no_radius; + lv_style_t radius_circle; + lv_style_t large_border; + lv_style_t large_line_space; + lv_style_t underline; +#if LV_USE_TEXTAREA + lv_style_t ta_cursor; +#endif +} my_theme_styles_t; + + +/********************** + * STATIC PROTOTYPES + **********************/ +static void style_init_reset(lv_style_t * style); +static void theme_apply(lv_theme_t * th, lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +static my_theme_styles_t * styles; +static lv_theme_t theme; +static bool inited; + +/********************** + * MACROS + **********************/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void style_init(bool dark_bg, const lv_font_t * font) +{ + style_init_reset(&styles->scrollbar); + lv_style_set_bg_opa(&styles->scrollbar, LV_OPA_COVER); + lv_style_set_bg_color(&styles->scrollbar, COLOR_FG); + lv_style_set_width(&styles->scrollbar, PAD_DEF); + + style_init_reset(&styles->scr); + lv_style_set_bg_opa(&styles->scr, LV_OPA_COVER); + lv_style_set_bg_color(&styles->scr, COLOR_BG); + lv_style_set_text_color(&styles->scr, COLOR_FG); + lv_style_set_pad_row(&styles->scr, PAD_DEF); + lv_style_set_pad_column(&styles->scr, PAD_DEF); + lv_style_set_text_font(&styles->scr, font); + + style_init_reset(&styles->card); + lv_style_set_bg_opa(&styles->card, LV_OPA_COVER); + lv_style_set_bg_color(&styles->card, COLOR_BG); + lv_style_set_border_color(&styles->card, COLOR_FG); + lv_style_set_radius(&styles->card, 2); + lv_style_set_border_width(&styles->card, BORDER_W_NORMAL); + lv_style_set_pad_all(&styles->card, PAD_DEF); + lv_style_set_pad_gap(&styles->card, PAD_DEF); + lv_style_set_text_color(&styles->card, COLOR_FG); + lv_style_set_line_width(&styles->card, 2); + lv_style_set_line_color(&styles->card, COLOR_FG); + lv_style_set_arc_width(&styles->card, 2); + lv_style_set_arc_color(&styles->card, COLOR_FG); + lv_style_set_outline_color(&styles->card, COLOR_FG); + lv_style_set_anim_time(&styles->card, 300); + + style_init_reset(&styles->pr); + lv_style_set_border_width(&styles->pr, BORDER_W_PR); + + style_init_reset(&styles->inv); + lv_style_set_bg_opa(&styles->inv, LV_OPA_COVER); + lv_style_set_bg_color(&styles->inv, COLOR_FG); + lv_style_set_border_color(&styles->inv, COLOR_BG); + lv_style_set_line_color(&styles->inv, COLOR_BG); + lv_style_set_arc_color(&styles->inv, COLOR_BG); + lv_style_set_text_color(&styles->inv, COLOR_BG); + lv_style_set_outline_color(&styles->inv, COLOR_BG); + + style_init_reset(&styles->disabled); + lv_style_set_border_width(&styles->disabled, BORDER_W_DIS); + + style_init_reset(&styles->focus); + lv_style_set_outline_width(&styles->focus, 1); + lv_style_set_outline_pad(&styles->focus, BORDER_W_FOCUS); + + style_init_reset(&styles->edit); + lv_style_set_outline_width(&styles->edit, BORDER_W_EDIT); + + style_init_reset(&styles->large_border); + lv_style_set_border_width(&styles->large_border, BORDER_W_EDIT); + + style_init_reset(&styles->pad_gap); + lv_style_set_pad_gap(&styles->pad_gap, PAD_DEF); + + style_init_reset(&styles->pad_zero); + lv_style_set_pad_all(&styles->pad_zero, 0); + lv_style_set_pad_gap(&styles->pad_zero, 0); + + style_init_reset(&styles->no_radius); + lv_style_set_radius(&styles->no_radius, 0); + + style_init_reset(&styles->radius_circle); + lv_style_set_radius(&styles->radius_circle, LV_RADIUS_CIRCLE); + + style_init_reset(&styles->large_line_space); + lv_style_set_text_line_space(&styles->large_line_space, 6); + + style_init_reset(&styles->underline); + lv_style_set_text_decor(&styles->underline, LV_TEXT_DECOR_UNDERLINE); + +#if LV_USE_TEXTAREA + style_init_reset(&styles->ta_cursor); + lv_style_set_border_side(&styles->ta_cursor, LV_BORDER_SIDE_LEFT); + lv_style_set_border_color(&styles->ta_cursor, COLOR_FG); + lv_style_set_border_width(&styles->ta_cursor, 2); + lv_style_set_bg_opa(&styles->ta_cursor, LV_OPA_TRANSP); + lv_style_set_anim_time(&styles->ta_cursor, 500); +#endif +} + + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +bool lv_theme_mono_is_inited(void) +{ + return LV_GC_ROOT(_lv_theme_default_styles) == NULL ? false : true; +} + +lv_theme_t * lv_theme_mono_init(lv_disp_t * disp, bool dark_bg, const lv_font_t * font) +{ + + /*This trick is required only to avoid the garbage collection of + *styles' data if LVGL is used in a binding (e.g. Micropython) + *In a general case styles could be in simple `static lv_style_t my_style...` variables*/ + if(!inited) { + inited = false; + LV_GC_ROOT(_lv_theme_default_styles) = lv_mem_alloc(sizeof(my_theme_styles_t)); + styles = (my_theme_styles_t *)LV_GC_ROOT(_lv_theme_default_styles); + } + + theme.disp = disp; + theme.font_small = LV_FONT_DEFAULT; + theme.font_normal = LV_FONT_DEFAULT; + theme.font_large = LV_FONT_DEFAULT; + theme.apply_cb = theme_apply; + + style_init(dark_bg, font); + + if(disp == NULL || lv_disp_get_theme(disp) == &theme) lv_obj_report_style_change(NULL); + + inited = true; + + return (lv_theme_t *)&theme; +} + + +static void theme_apply(lv_theme_t * th, lv_obj_t * obj) +{ + LV_UNUSED(th); + + if(lv_obj_get_parent(obj) == NULL) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + return; + } + + if(lv_obj_check_type(obj, &lv_obj_class)) { +#if LV_USE_TABVIEW + lv_obj_t * parent = lv_obj_get_parent(obj); + /*Tabview content area*/ + if(lv_obj_check_type(parent, &lv_tabview_class)) { + return; + } + /*Tabview pages*/ + else if(lv_obj_check_type(lv_obj_get_parent(parent), &lv_tabview_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->no_radius, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif + +#if LV_USE_WIN + /*Header*/ + if(lv_obj_get_index(obj) == 0 && lv_obj_check_type(lv_obj_get_parent(obj), &lv_win_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->no_radius, 0); + return; + } + /*Content*/ + else if(lv_obj_get_index(obj) == 1 && lv_obj_check_type(lv_obj_get_parent(obj), &lv_win_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->no_radius, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + return; + } +#endif + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + } +#if LV_USE_BTN + else if(lv_obj_check_type(obj, &lv_btn_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pr, LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->inv, LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->disabled, LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } +#endif + +#if LV_USE_BTNMATRIX + else if(lv_obj_check_type(obj, &lv_btnmatrix_class)) { +#if LV_USE_MSGBOX + if(lv_obj_check_type(lv_obj_get_parent(obj), &lv_msgbox_class)) { + lv_obj_add_style(obj, &styles->pad_gap, 0); + lv_obj_add_style(obj, &styles->card, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->pr, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->underline, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->large_border, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + return; + } +#endif +#if LV_USE_TABVIEW + if(lv_obj_check_type(lv_obj_get_parent(obj), &lv_tabview_class)) { + lv_obj_add_style(obj, &styles->pad_gap, 0); + lv_obj_add_style(obj, &styles->card, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->pr, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->inv, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->underline, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->large_border, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + return; + } +#endif + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->card, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->pr, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->inv, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->underline, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->large_border, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + } +#endif + +#if LV_USE_BAR + else if(lv_obj_check_type(obj, &lv_bar_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pad_zero, 0); + lv_obj_add_style(obj, &styles->inv, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + } +#endif + +#if LV_USE_SLIDER + else if(lv_obj_check_type(obj, &lv_slider_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pad_zero, 0); + lv_obj_add_style(obj, &styles->inv, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->card, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->radius_circle, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } +#endif + +#if LV_USE_TABLE + else if(lv_obj_check_type(obj, &lv_table_class)) { + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->card, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->no_radius, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->pr, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->inv, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } +#endif + +#if LV_USE_CHECKBOX + else if(lv_obj_check_type(obj, &lv_checkbox_class)) { + lv_obj_add_style(obj, &styles->pad_gap, LV_PART_MAIN); + lv_obj_add_style(obj, &styles->card, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->disabled, LV_PART_INDICATOR | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->inv, LV_PART_INDICATOR | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->pr, LV_PART_INDICATOR | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } +#endif + +#if LV_USE_SWITCH + else if(lv_obj_check_type(obj, &lv_switch_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->radius_circle, 0); + lv_obj_add_style(obj, &styles->pad_zero, 0); + lv_obj_add_style(obj, &styles->inv, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->radius_circle, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->card, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->radius_circle, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->pad_zero, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } +#endif + +#if LV_USE_CHART + else if(lv_obj_check_type(obj, &lv_chart_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->card, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->card, LV_PART_TICKS); + lv_obj_add_style(obj, &styles->card, LV_PART_CURSOR); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + } +#endif + +#if LV_USE_ROLLER + else if(lv_obj_check_type(obj, &lv_roller_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->large_line_space, 0); + lv_obj_add_style(obj, &styles->inv, LV_PART_SELECTED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } +#endif + +#if LV_USE_DROPDOWN + else if(lv_obj_check_type(obj, &lv_dropdown_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pr, LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } + else if(lv_obj_check_type(obj, &lv_dropdownlist_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->large_line_space, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->inv, LV_PART_SELECTED | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->pr, LV_PART_SELECTED | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } +#endif + +#if LV_USE_ARC + else if(lv_obj_check_type(obj, &lv_arc_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->inv, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->pad_zero, LV_PART_INDICATOR); + lv_obj_add_style(obj, &styles->card, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->radius_circle, LV_PART_KNOB); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } +#endif + +#if LV_USE_METER + else if(lv_obj_check_type(obj, &lv_meter_class)) { + lv_obj_add_style(obj, &styles->card, 0); + } +#endif + +#if LV_USE_TEXTAREA + else if(lv_obj_check_type(obj, &lv_textarea_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + lv_obj_add_style(obj, &styles->ta_cursor, LV_PART_CURSOR | LV_STATE_FOCUSED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUSED); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } +#endif + +#if LV_USE_CALENDAR + else if(lv_obj_check_type(obj, &lv_calendar_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->no_radius, 0); + lv_obj_add_style(obj, &styles->pr, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->disabled, LV_PART_ITEMS | LV_STATE_DISABLED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->large_border, LV_PART_ITEMS | LV_STATE_FOCUS_KEY); + } +#endif + +#if LV_USE_KEYBOARD + else if(lv_obj_check_type(obj, &lv_keyboard_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->card, LV_PART_ITEMS); + lv_obj_add_style(obj, &styles->pr, LV_PART_ITEMS | LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->inv, LV_PART_ITEMS | LV_STATE_CHECKED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + lv_obj_add_style(obj, &styles->large_border, LV_PART_ITEMS | LV_STATE_EDITED); + } +#endif +#if LV_USE_LIST + else if(lv_obj_check_type(obj, &lv_list_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + return; + } + else if(lv_obj_check_type(obj, &lv_list_text_class)) { + + } + else if(lv_obj_check_type(obj, &lv_list_btn_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->pr, LV_STATE_PRESSED); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->large_border, LV_STATE_EDITED); + + } +#endif +#if LV_USE_MSGBOX + else if(lv_obj_check_type(obj, &lv_msgbox_class)) { + lv_obj_add_style(obj, &styles->card, 0); + return; + } +#endif +#if LV_USE_SPINBOX + else if(lv_obj_check_type(obj, &lv_spinbox_class)) { + lv_obj_add_style(obj, &styles->card, 0); + lv_obj_add_style(obj, &styles->inv, LV_PART_CURSOR); + lv_obj_add_style(obj, &styles->focus, LV_STATE_FOCUS_KEY); + lv_obj_add_style(obj, &styles->edit, LV_STATE_EDITED); + } +#endif +#if LV_USE_TILEVIEW + else if(lv_obj_check_type(obj, &lv_tileview_class)) { + lv_obj_add_style(obj, &styles->scr, 0); + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + } + else if(lv_obj_check_type(obj, &lv_tileview_tile_class)) { + lv_obj_add_style(obj, &styles->scrollbar, LV_PART_SCROLLBAR); + } +#endif + +#if LV_USE_LED + else if(lv_obj_check_type(obj, &lv_led_class)) { + lv_obj_add_style(obj, &styles->card, 0); + } +#endif +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void style_init_reset(lv_style_t * style) +{ + if(inited) { + lv_style_reset(style); + } + else { + lv_style_init(style); + } +} + +#endif diff --git a/lib/lvgl/src/extra/themes/mono/lv_theme_mono.h b/lib/lvgl/src/extra/themes/mono/lv_theme_mono.h new file mode 100644 index 00000000..10b8f186 --- /dev/null +++ b/lib/lvgl/src/extra/themes/mono/lv_theme_mono.h @@ -0,0 +1,57 @@ +/** + * @file lv_theme_mono.h + * + */ + +#ifndef LV_USE_THEME_MONO_H +#define LV_USE_THEME_MONO_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" + +#if LV_USE_THEME_MONO + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Initialize the theme + * @param color_primary the primary color of the theme + * @param color_secondary the secondary color for the theme + * @param font pointer to a font to use. + * @return a pointer to reference this theme later + */ +lv_theme_t * lv_theme_mono_init(lv_disp_t * disp, bool dark_bg, const lv_font_t * font); + +/** +* Check if the theme is initialized +* @return true if default theme is initialized, false otherwise +*/ +bool lv_theme_mono_is_inited(void); + +/********************** + * MACROS + **********************/ + +#endif + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_USE_THEME_MONO_H*/ diff --git a/lib/lvgl/src/extra/widgets/animimg/lv_animimg.c b/lib/lvgl/src/extra/widgets/animimg/lv_animimg.c new file mode 100644 index 00000000..135a8a4b --- /dev/null +++ b/lib/lvgl/src/extra/widgets/animimg/lv_animimg.c @@ -0,0 +1,138 @@ +/** + * @file lv_animimg.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_animimg.h" +#if LV_USE_ANIMIMG != 0 + +/*Testing of dependencies*/ +#if LV_USE_IMG == 0 + #error "lv_animimg: lv_img is required. Enable it in lv_conf.h (LV_USE_IMG 1) " +#endif + +#include "../../../misc/lv_assert.h" +#include "../../../draw/lv_img_decoder.h" +#include "../../../misc/lv_fs.h" +#include "../../../misc/lv_txt.h" +#include "../../../misc/lv_math.h" +#include "../../../misc/lv_log.h" +#include "../../../misc/lv_anim.h" + +/********************* + * DEFINES + *********************/ +#define LV_OBJX_NAME "lv_animimg" + +#define MY_CLASS &lv_animimg_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void index_change(lv_obj_t * obj, int32_t index); +static void lv_animimg_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_animimg_class = { + .constructor_cb = lv_animimg_constructor, + .instance_size = sizeof(lv_animimg_t), + .base_class = &lv_img_class +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_animimg_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_animimg_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +void lv_animimg_set_src(lv_obj_t * obj, lv_img_dsc_t * dsc[], uint8_t num) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_animimg_t * animimg = (lv_animimg_t *)obj; + animimg->dsc = dsc; + animimg->pic_count = num; + lv_anim_set_values(&animimg->anim, 0, num); +} + +void lv_animimg_start(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_animimg_t * animimg = (lv_animimg_t *)obj; + lv_anim_start(&animimg->anim); +} + +/*===================== + * Setter functions + *====================*/ + +void lv_animimg_set_duration(lv_obj_t * obj, uint32_t duration) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_animimg_t * animimg = (lv_animimg_t *)obj; + lv_anim_set_time(&animimg->anim, duration); + lv_anim_set_playback_delay(&animimg->anim, duration); +} + +void lv_animimg_set_repeat_count(lv_obj_t * obj, uint16_t count) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_animimg_t * animimg = (lv_animimg_t *)obj; + lv_anim_set_repeat_count(&animimg->anim, count); +} + +/*===================== + * Getter functions + *====================*/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_animimg_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + LV_UNUSED(class_p); + lv_animimg_t * animimg = (lv_animimg_t *)obj; + + animimg->dsc = NULL; + animimg->pic_count = -1; + //initial animation + lv_anim_init(&animimg->anim); + lv_anim_set_var(&animimg->anim, obj); + lv_anim_set_time(&animimg->anim, 30); + lv_anim_set_exec_cb(&animimg->anim, (lv_anim_exec_xcb_t)index_change); + lv_anim_set_values(&animimg->anim, 0, 1); + lv_anim_set_repeat_count(&animimg->anim, LV_ANIM_REPEAT_INFINITE); +} + +static void index_change(lv_obj_t * obj, int32_t index) +{ + lv_coord_t idx; + lv_animimg_t * animimg = (lv_animimg_t *)obj; + + idx = index % animimg->pic_count; + + lv_img_set_src(obj, animimg->dsc[idx]); +} + +#endif diff --git a/lib/lvgl/src/extra/widgets/animimg/lv_animimg.h b/lib/lvgl/src/extra/widgets/animimg/lv_animimg.h new file mode 100644 index 00000000..63294947 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/animimg/lv_animimg.h @@ -0,0 +1,103 @@ +/** + * @file lv_animimg.h + * + */ + +#ifndef LV_ANIM_IMG_H +#define LV_ANIM_IMG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_ANIMIMG != 0 + +/*Testing of dependencies*/ +#if LV_USE_IMG == 0 +#error "lv_animimg: lv_img is required. Enable it in lv_conf.h (LV_USE_IMG 1)" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +extern const lv_obj_class_t lv_animimg_class; + +/*Data of image*/ +typedef struct { + lv_img_t img; + lv_anim_t anim; + /*picture sequence */ + lv_img_dsc_t ** dsc; + int8_t pic_count; +} lv_animimg_t; + + +/*Image parts*/ +enum { + LV_ANIM_IMG_PART_MAIN, +}; +typedef uint8_t lv_animimg_part_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create an animation image objects + * @param parent pointer to an object, it will be the parent of the new button + * @return pointer to the created animation image object + */ +lv_obj_t * lv_animimg_create(lv_obj_t * parent); + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the image animation images source. + * @param img pointer to an animation image object + * @param dsc pointer to a series images + * @param num images' number + */ +void lv_animimg_set_src(lv_obj_t * img, lv_img_dsc_t * dsc[], uint8_t num); + +/** + * Startup the image animation. + * @param obj pointer to an animation image object + */ +void lv_animimg_start(lv_obj_t * obj); + +/** + * Set the image animation duration time. unit:ms + * @param img pointer to an animation image object + */ +void lv_animimg_set_duration(lv_obj_t * img, uint32_t duration); + +/** + * Set the image animation reapeatly play times. + * @param img pointer to an animation image object + * @param count the number of times to repeat the animation + */ +void lv_animimg_set_repeat_count(lv_obj_t * img, uint16_t count); + +/*===================== + * Getter functions + *====================*/ + +#endif /*LV_USE_ANIMIMG*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_ANIM_IMG_H*/ diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar.c b/lib/lvgl/src/extra/widgets/calendar/lv_calendar.c new file mode 100644 index 00000000..b806d252 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar.c @@ -0,0 +1,402 @@ +/** + * @file lv_calendar.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_calendar.h" +#include "../../../lvgl.h" +#if LV_USE_CALENDAR + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define LV_CALENDAR_CTRL_TODAY LV_BTNMATRIX_CTRL_CUSTOM_1 +#define LV_CALENDAR_CTRL_HIGHLIGHT LV_BTNMATRIX_CTRL_CUSTOM_2 + +#define MY_CLASS &lv_calendar_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void draw_part_begin_event_cb(lv_event_t * e); + +static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day); +static uint8_t get_month_length(int32_t year, int32_t month); +static uint8_t is_leap_year(uint32_t year); +static void highlight_update(lv_obj_t * calendar); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_calendar_class = { + .constructor_cb = lv_calendar_constructor, + .width_def = (LV_DPI_DEF * 3) / 2, + .height_def = (LV_DPI_DEF * 3) / 2, + .group_def = LV_OBJ_CLASS_GROUP_DEF_TRUE, + .instance_size = sizeof(lv_calendar_t), + .base_class = &lv_obj_class +}; + +static const char * day_names_def[7] = LV_CALENDAR_DEFAULT_DAY_NAMES; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_calendar_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_calendar_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +void lv_calendar_set_day_names(lv_obj_t * obj, const char * day_names[]) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + uint32_t i; + for(i = 0; i < 7; i++) { + calendar->map[i] = day_names[i]; + } + lv_obj_invalidate(obj); +} + +void lv_calendar_set_today_date(lv_obj_t * obj, uint32_t year, uint32_t month, uint32_t day) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + calendar->today.year = year; + calendar->today.month = month; + calendar->today.day = day; + + highlight_update(obj); +} + +void lv_calendar_set_highlighted_dates(lv_obj_t * obj, lv_calendar_date_t highlighted[], uint16_t date_num) +{ + LV_ASSERT_NULL(highlighted); + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + calendar->highlighted_dates = highlighted; + calendar->highlighted_dates_num = date_num; + + highlight_update(obj); +} + +void lv_calendar_set_showed_date(lv_obj_t * obj, uint32_t year, uint32_t month) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + calendar->showed_date.year = year; + calendar->showed_date.month = month; + calendar->showed_date.day = 1; + + lv_calendar_date_t d; + d.year = calendar->showed_date.year; + d.month = calendar->showed_date.month; + d.day = calendar->showed_date.day; + + uint32_t i; + + /*Remove the disabled state but revert it for day names*/ + lv_btnmatrix_clear_btn_ctrl_all(calendar->btnm, LV_BTNMATRIX_CTRL_DISABLED); + for(i = 0; i < 7; i++) { + lv_btnmatrix_set_btn_ctrl(calendar->btnm, i, LV_BTNMATRIX_CTRL_DISABLED); + } + + uint8_t act_mo_len = get_month_length(d.year, d.month); + uint8_t day_first = get_day_of_week(d.year, d.month, 1); + uint8_t c; + for(i = day_first, c = 1; i < act_mo_len + day_first; i++, c++) { + lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c); + } + + uint8_t prev_mo_len = get_month_length(d.year, d.month - 1); + for(i = 0, c = prev_mo_len - day_first + 1; i < day_first; i++, c++) { + lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c); + lv_btnmatrix_set_btn_ctrl(calendar->btnm, i + 7, LV_BTNMATRIX_CTRL_DISABLED); + } + + for(i = day_first + act_mo_len, c = 1; i < 6 * 7; i++, c++) { + lv_snprintf(calendar->nums[i], sizeof(calendar->nums[0]), "%d", c); + lv_btnmatrix_set_btn_ctrl(calendar->btnm, i + 7, LV_BTNMATRIX_CTRL_DISABLED); + } + + highlight_update(obj); + + /*Reset the focused button if the days changes*/ + if(lv_btnmatrix_get_selected_btn(calendar->btnm) != LV_BTNMATRIX_BTN_NONE) { + lv_btnmatrix_set_selected_btn(calendar->btnm, day_first + 7); + } + + lv_obj_invalidate(obj); + + /* The children of the calendar are probably headers. + * Notify them to let the headers updated to the new date*/ + uint32_t child_cnt = lv_obj_get_child_cnt(obj); + for(i = 0; i < child_cnt; i++) { + lv_obj_t * child = lv_obj_get_child(obj, i); + if(child == calendar->btnm) continue; + lv_event_send(child, LV_EVENT_VALUE_CHANGED, obj); + } +} + +/*===================== + * Getter functions + *====================*/ + +lv_obj_t * lv_calendar_get_btnmatrix(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + const lv_calendar_t * calendar = (lv_calendar_t *)obj; + return calendar->btnm; +} + +const lv_calendar_date_t * lv_calendar_get_today_date(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + const lv_calendar_t * calendar = (lv_calendar_t *)obj; + + return &calendar->today; +} + +const lv_calendar_date_t * lv_calendar_get_showed_date(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + const lv_calendar_t * calendar = (lv_calendar_t *)obj; + + return &calendar->showed_date; +} + +lv_calendar_date_t * lv_calendar_get_highlighted_dates(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + return calendar->highlighted_dates; +} + +uint16_t lv_calendar_get_highlighted_dates_num(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + return calendar->highlighted_dates_num; +} + +lv_res_t lv_calendar_get_pressed_date(const lv_obj_t * obj, lv_calendar_date_t * date) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + uint16_t d = lv_btnmatrix_get_selected_btn(calendar->btnm); + if(d == LV_BTNMATRIX_BTN_NONE) { + date->year = 0; + date->month = 0; + date->day = 0; + return LV_RES_INV; + } + + const char * txt = lv_btnmatrix_get_btn_text(calendar->btnm, lv_btnmatrix_get_selected_btn(calendar->btnm)); + + if(txt[1] == 0) date->day = txt[0] - '0'; + else date->day = (txt[0] - '0') * 10 + (txt[1] - '0'); + + date->year = calendar->showed_date.year; + date->month = calendar->showed_date.month; + + return LV_RES_OK; +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_calendar_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_calendar_t * calendar = (lv_calendar_t *)obj; + + /*Initialize the allocated 'ext'*/ + calendar->today.year = 2020; + calendar->today.month = 1; + calendar->today.day = 1; + + calendar->showed_date.year = 2020; + calendar->showed_date.month = 1; + calendar->showed_date.day = 1; + + calendar->highlighted_dates = NULL; + calendar->highlighted_dates_num = 0; + + lv_memset_00(calendar->nums, sizeof(calendar->nums)); + uint8_t i; + uint8_t j = 0; + for(i = 0; i < 8 * 7; i++) { + /*Every 8th string is "\n"*/ + if(i != 0 && (i + 1) % 8 == 0) { + calendar->map[i] = "\n"; + } + else if(i < 7) { + calendar->map[i] = day_names_def[i]; + } + else { + calendar->nums[j][0] = 'x'; + calendar->map[i] = calendar->nums[j]; + j++; + } + } + calendar->map[8 * 7 - 1] = ""; + + calendar->btnm = lv_btnmatrix_create(obj); + lv_btnmatrix_set_map(calendar->btnm, calendar->map); + lv_btnmatrix_set_btn_ctrl_all(calendar->btnm, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT); + lv_obj_add_event_cb(calendar->btnm, draw_part_begin_event_cb, LV_EVENT_DRAW_PART_BEGIN, NULL); + lv_obj_set_width(calendar->btnm, lv_pct(100)); + + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_grow(calendar->btnm, 1); + + lv_calendar_set_showed_date(obj, calendar->showed_date.year, calendar->showed_date.month); + lv_calendar_set_today_date(obj, calendar->today.year, calendar->today.month, calendar->today.day); + + lv_obj_add_flag(calendar->btnm, LV_OBJ_FLAG_EVENT_BUBBLE); +} + +static void draw_part_begin_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_obj_draw_part_dsc_t * dsc = lv_event_get_param(e); + if(dsc->part == LV_PART_ITEMS) { + /*Day name styles*/ + if(dsc->id < 7) { + dsc->rect_dsc->bg_opa = LV_OPA_TRANSP; + dsc->rect_dsc->border_opa = LV_OPA_TRANSP; + } + else if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_BTNMATRIX_CTRL_DISABLED)) { + dsc->rect_dsc->bg_opa = LV_OPA_TRANSP; + dsc->rect_dsc->border_opa = LV_OPA_TRANSP; + dsc->label_dsc->color = lv_palette_main(LV_PALETTE_GREY); + } + + if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_CALENDAR_CTRL_HIGHLIGHT)) { + dsc->rect_dsc->bg_opa = LV_OPA_40; + dsc->rect_dsc->bg_color = lv_theme_get_color_primary(obj); + if(lv_btnmatrix_get_selected_btn(obj) == dsc->id) { + dsc->rect_dsc->bg_opa = LV_OPA_70; + } + } + + if(lv_btnmatrix_has_btn_ctrl(obj, dsc->id, LV_CALENDAR_CTRL_TODAY)) { + dsc->rect_dsc->border_opa = LV_OPA_COVER; + dsc->rect_dsc->border_color = lv_theme_get_color_primary(obj); + dsc->rect_dsc->border_width += 1; + } + + } +} + +/** + * Get the number of days in a month + * @param year a year + * @param month a month. The range is basically [1..12] but [-11..0] or [13..24] is also + * supported to handle next/prev. year + * @return [28..31] + */ +static uint8_t get_month_length(int32_t year, int32_t month) +{ + month--; + if(month < 0) { + year--; /*Already in the previous year (won't be less then -12 to skip a whole year)*/ + month = 12 + month; /*`month` is negative, the result will be < 12*/ + } + if(month >= 12) { + year++; + month -= 12; + } + + /*month == 1 is february*/ + return (month == 1) ? (28 + is_leap_year(year)) : 31 - month % 7 % 2; +} + +/** + * Tells whether a year is leap year or not + * @param year a year + * @return 0: not leap year; 1: leap year + */ +static uint8_t is_leap_year(uint32_t year) +{ + return (year % 4) || ((year % 100 == 0) && (year % 400)) ? 0 : 1; +} + +/** + * Get the day of the week + * @param year a year + * @param month a month [1..12] + * @param day a day [1..32] + * @return [0..6] which means [Sun..Sat] or [Mon..Sun] depending on LV_CALENDAR_WEEK_STARTS_MONDAY + */ +static uint8_t get_day_of_week(uint32_t year, uint32_t month, uint32_t day) +{ + uint32_t a = month < 3 ? 1 : 0; + uint32_t b = year - a; + +#if LV_CALENDAR_WEEK_STARTS_MONDAY + uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400) - 1) % 7; +#else + uint32_t day_of_week = (day + (31 * (month - 2 + 12 * a) / 12) + b + (b / 4) - (b / 100) + (b / 400)) % 7; +#endif + + return day_of_week ; +} + +static void highlight_update(lv_obj_t * obj) +{ + lv_calendar_t * calendar = (lv_calendar_t *)obj; + uint16_t i; + + /*Clear all kind of selection*/ + lv_btnmatrix_clear_btn_ctrl_all(calendar->btnm, LV_CALENDAR_CTRL_TODAY | LV_CALENDAR_CTRL_HIGHLIGHT); + + uint8_t day_first = get_day_of_week(calendar->showed_date.year, calendar->showed_date.month, 1); + if(calendar->highlighted_dates) { + for(i = 0; i < calendar->highlighted_dates_num; i++) { + if(calendar->highlighted_dates[i].year == calendar->showed_date.year && + calendar->highlighted_dates[i].month == calendar->showed_date.month) { + lv_btnmatrix_set_btn_ctrl(calendar->btnm, calendar->highlighted_dates[i].day - 1 + day_first + 7, + LV_CALENDAR_CTRL_HIGHLIGHT); + } + } + } + + if(calendar->showed_date.year == calendar->today.year && calendar->showed_date.month == calendar->today.month) { + lv_btnmatrix_set_btn_ctrl(calendar->btnm, calendar->today.day - 1 + day_first + 7, LV_CALENDAR_CTRL_TODAY); + } +} + +#endif /*LV_USE_CALENDAR*/ diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar.h b/lib/lvgl/src/extra/widgets/calendar/lv_calendar.h new file mode 100644 index 00000000..2511b2fa --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar.h @@ -0,0 +1,164 @@ +/** + * @file lv_calendar.h + * + */ + +#ifndef LV_CALENDAR_H +#define LV_CALENDAR_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../widgets/lv_btnmatrix.h" + +#if LV_USE_CALENDAR + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/** + * Represents a date on the calendar object (platform-agnostic). + */ +typedef struct { + uint16_t year; + int8_t month; /** 1..12*/ + int8_t day; /** 1..31*/ +} lv_calendar_date_t; + +/*Data of calendar*/ +typedef struct { + lv_obj_t obj; + lv_obj_t * btnm; + /*New data for this type*/ + lv_calendar_date_t today; /*Date of today*/ + lv_calendar_date_t showed_date; /*Currently visible month (day is ignored)*/ + lv_calendar_date_t * + highlighted_dates; /*Apply different style on these days (pointer to an array defined by the user)*/ + uint16_t highlighted_dates_num; /*Number of elements in `highlighted_days`*/ + const char * map[8 * 7]; + char nums [7 * 6][4]; +} lv_calendar_t; + +extern const lv_obj_class_t lv_calendar_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t * lv_calendar_create(lv_obj_t * parent); + +/*====================== + * Add/remove functions + *=====================*/ + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the today's date + * @param obj pointer to a calendar object + * @param year today's year + * @param month today's month [1..12] + * @param day today's day [1..31] + */ +void lv_calendar_set_today_date(lv_obj_t * obj, uint32_t year, uint32_t month, uint32_t day); + +/** + * Set the currently showed + * @param obj pointer to a calendar object + * @param year today's year + * @param month today's month [1..12] + */ +void lv_calendar_set_showed_date(lv_obj_t * obj, uint32_t year, uint32_t month); + +/** + * Set the highlighted dates + * @param obj pointer to a calendar object + * @param highlighted pointer to an `lv_calendar_date_t` array containing the dates. + * Only the pointer will be saved so this variable can't be local which will be destroyed later. + * @param date_num number of dates in the array + */ +void lv_calendar_set_highlighted_dates(lv_obj_t * obj, lv_calendar_date_t highlighted[], uint16_t date_num); + +/** + * Set the name of the days + * @param obj pointer to a calendar object + * @param day_names pointer to an array with the names. + * E.g. `const char * days[7] = {"Sun", "Mon", ...}` + * Only the pointer will be saved so this variable can't be local which will be destroyed later. + */ +void lv_calendar_set_day_names(lv_obj_t * obj, const char ** day_names); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get the button matrix object of the calendar. + * It shows the dates and day names. + * @param obj pointer to a calendar object + * @return pointer to a the button matrix + */ +lv_obj_t * lv_calendar_get_btnmatrix(const lv_obj_t * obj); + +/** + * Get the today's date + * @param calendar pointer to a calendar object + * @return return pointer to an `lv_calendar_date_t` variable containing the date of today. + */ +const lv_calendar_date_t * lv_calendar_get_today_date(const lv_obj_t * calendar); + +/** + * Get the currently showed + * @param calendar pointer to a calendar object + * @return pointer to an `lv_calendar_date_t` variable containing the date is being shown. + */ +const lv_calendar_date_t * lv_calendar_get_showed_date(const lv_obj_t * calendar); + +/** + * Get the highlighted dates + * @param calendar pointer to a calendar object + * @return pointer to an `lv_calendar_date_t` array containing the dates. + */ +lv_calendar_date_t * lv_calendar_get_highlighted_dates(const lv_obj_t * calendar); + +/** + * Get the number of the highlighted dates + * @param calendar pointer to a calendar object + * @return number of highlighted days + */ +uint16_t lv_calendar_get_highlighted_dates_num(const lv_obj_t * calendar); + +/** + * Get the currently pressed day + * @param calendar pointer to a calendar object + * @param date store the pressed date here + * @return LV_RES_OK: there is a valid pressed date; LV_RES_INV: there is no pressed data + */ +lv_res_t lv_calendar_get_pressed_date(const lv_obj_t * calendar, lv_calendar_date_t * date); + +/*===================== + * Other functions + *====================*/ + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_CALENDAR*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_CALENDAR_H*/ diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.c b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.c new file mode 100644 index 00000000..fecb1392 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.c @@ -0,0 +1,149 @@ +/** + * @file lv_calendar_header_arrow.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_calendar_header_arrow.h" +#if LV_USE_CALENDAR_HEADER_ARROW + +#include "lv_calendar.h" +#include "../../../widgets/lv_btn.h" +#include "../../../widgets/lv_label.h" +#include "../../layouts/flex/lv_flex.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void my_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void month_event_cb(lv_event_t * e); +static void value_changed_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_calendar_header_arrow_class = { + .base_class = &lv_obj_class, + .constructor_cb = my_constructor, + .width_def = LV_PCT(100), + .height_def = LV_DPI_DEF / 3 +}; + +static const char * month_names_def[12] = LV_CALENDAR_DEFAULT_MONTH_NAMES; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_calendar_header_arrow_create(lv_obj_t * parent) +{ + lv_obj_t * obj = lv_obj_class_create_obj(&lv_calendar_header_arrow_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void my_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + LV_UNUSED(class_p); + + lv_obj_move_to_index(obj, 0); + + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START); + + lv_obj_t * mo_prev = lv_btn_create(obj); + lv_obj_set_style_bg_img_src(mo_prev, LV_SYMBOL_LEFT, 0); + lv_obj_set_height(mo_prev, lv_pct(100)); + lv_obj_update_layout(mo_prev); + lv_coord_t btn_size = lv_obj_get_height(mo_prev); + lv_obj_set_width(mo_prev, btn_size); + + lv_obj_add_event_cb(mo_prev, month_event_cb, LV_EVENT_CLICKED, NULL); + lv_obj_clear_flag(mo_prev, LV_OBJ_FLAG_CLICK_FOCUSABLE); + + lv_obj_t * label = lv_label_create(obj); + lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); + lv_obj_set_flex_grow(label, 1); + + lv_obj_t * mo_next = lv_btn_create(obj); + lv_obj_set_style_bg_img_src(mo_next, LV_SYMBOL_RIGHT, 0); + lv_obj_set_size(mo_next, btn_size, btn_size); + + lv_obj_add_event_cb(mo_next, month_event_cb, LV_EVENT_CLICKED, NULL); + lv_obj_clear_flag(mo_next, LV_OBJ_FLAG_CLICK_FOCUSABLE); + + lv_obj_add_event_cb(obj, value_changed_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + /*Refresh the drop downs*/ + lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); +} + +static void month_event_cb(lv_event_t * e) +{ + lv_obj_t * btn = lv_event_get_target(e); + + lv_obj_t * header = lv_obj_get_parent(btn); + lv_obj_t * calendar = lv_obj_get_parent(header); + + const lv_calendar_date_t * d; + d = lv_calendar_get_showed_date(calendar); + lv_calendar_date_t newd = *d; + + /*The last child is the right button*/ + if(lv_obj_get_child(header, 0) == btn) { + if(newd.month == 1) { + newd.month = 12; + newd.year --; + } + else { + newd.month --; + } + } + else { + if(newd.month == 12) { + newd.month = 1; + newd.year ++; + } + else { + newd.month ++; + } + } + + lv_calendar_set_showed_date(calendar, newd.year, newd.month); + + lv_obj_t * label = lv_obj_get_child(header, 1); + lv_label_set_text_fmt(label, "%d %s", newd.year, month_names_def[newd.month - 1]); +} + +static void value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * header = lv_event_get_target(e); + lv_obj_t * calendar = lv_obj_get_parent(header); + + const lv_calendar_date_t * cur_date = lv_calendar_get_showed_date(calendar); + lv_obj_t * label = lv_obj_get_child(header, 1); + lv_label_set_text_fmt(label, "%d %s", cur_date->year, month_names_def[cur_date->month - 1]); +} + +#endif /*LV_USE_CALENDAR_HEADER_ARROW*/ + diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.h b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.h new file mode 100644 index 00000000..609ccb0c --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_arrow.h @@ -0,0 +1,49 @@ +/** + * @file lv_calendar_header_arrow.h + * + */ + +#ifndef LV_CALENDAR_HEADER_ARROW_H +#define LV_CALENDAR_HEADER_ARROW_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" +#if LV_USE_CALENDAR_HEADER_ARROW + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +extern const lv_obj_class_t lv_calendar_header_arrow_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a calendar header with drop-drowns to select the year and month + * @param parent pointer to a calendar object. + * @return the created header + */ +lv_obj_t * lv_calendar_header_arrow_create(lv_obj_t * parent); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_CALENDAR_HEADER_ARROW*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_CALENDAR_HEADER_ARROW_H*/ diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.c b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.c new file mode 100644 index 00000000..5e8f90d4 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.c @@ -0,0 +1,142 @@ +/** + * @file lv_calendar_obj_dropdown.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_calendar_header_dropdown.h" +#if LV_USE_CALENDAR_HEADER_DROPDOWN + +#include "lv_calendar.h" +#include "../../../widgets/lv_dropdown.h" +#include "../../layouts/flex/lv_flex.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void my_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void year_event_cb(lv_event_t * e); +static void month_event_cb(lv_event_t * e); +static void value_changed_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_calendar_header_dropdown_class = { + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_SIZE_CONTENT, + .constructor_cb = my_constructor +}; + +static const char * month_list = "01\n02\n03\n04\n05\n06\n07\n08\n09\n10\n11\n12"; +static const char * year_list = { + "2023\n2022\n2021\n" + "2020\n2019\n2018\n2017\n2016\n2015\n2014\n2013\n2012\n2011\n2010\n2009\n2008\n2007\n2006\n2005\n2004\n2003\n2002\n2001\n" + "2000\n1999\n1998\n1997\n1996\n1995\n1994\n1993\n1992\n1991\n1990\n1989\n1988\n1987\n1986\n1985\n1984\n1983\n1982\n1981\n" + "1980\n1979\n1978\n1977\n1976\n1975\n1974\n1973\n1972\n1971\n1970\n1969\n1968\n1967\n1966\n1965\n1964\n1963\n1962\n1961\n" + "1960\n1959\n1958\n1957\n1956\n1955\n1954\n1953\n1952\n1951\n1950\n1949\n1948\n1947\n1946\n1945\n1944\n1943\n1942\n1941\n" + "1940\n1939\n1938\n1937\n1936\n1935\n1934\n1933\n1932\n1931\n1930\n1929\n1928\n1927\n1926\n1925\n1924\n1923\n1922\n1921\n" + "1920\n1919\n1918\n1917\n1916\n1915\n1914\n1913\n1912\n1911\n1910\n1909\n1908\n1907\n1906\n1905\n1904\n1903\n1902\n1901" +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_calendar_header_dropdown_create(lv_obj_t * parent) +{ + lv_obj_t * obj = lv_obj_class_create_obj(&lv_calendar_header_dropdown_class, parent); + lv_obj_class_init_obj(obj); + + return obj; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void my_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + LV_UNUSED(class_p); + + lv_obj_t * calendar = lv_obj_get_parent(obj); + lv_obj_move_to_index(obj, 0); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + + lv_obj_t * year_dd = lv_dropdown_create(obj); + lv_dropdown_set_options(year_dd, year_list); + lv_obj_add_event_cb(year_dd, year_event_cb, LV_EVENT_VALUE_CHANGED, calendar); + lv_obj_set_flex_grow(year_dd, 1); + + lv_obj_t * month_dd = lv_dropdown_create(obj); + lv_dropdown_set_options(month_dd, month_list); + lv_obj_add_event_cb(month_dd, month_event_cb, LV_EVENT_VALUE_CHANGED, calendar); + lv_obj_set_flex_grow(month_dd, 1); + + lv_obj_add_event_cb(obj, value_changed_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + /*Refresh the drop downs*/ + lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); +} + +static void month_event_cb(lv_event_t * e) +{ + lv_obj_t * dropdown = lv_event_get_target(e); + lv_obj_t * calendar = lv_event_get_user_data(e); + + uint16_t sel = lv_dropdown_get_selected(dropdown); + + const lv_calendar_date_t * d; + d = lv_calendar_get_showed_date(calendar); + lv_calendar_date_t newd = *d; + newd.month = sel + 1; + + lv_calendar_set_showed_date(calendar, newd.year, newd.month); +} + +static void year_event_cb(lv_event_t * e) +{ + lv_obj_t * dropdown = lv_event_get_target(e); + lv_obj_t * calendar = lv_event_get_user_data(e); + + uint16_t sel = lv_dropdown_get_selected(dropdown); + + const lv_calendar_date_t * d; + d = lv_calendar_get_showed_date(calendar); + lv_calendar_date_t newd = *d; + newd.year = 2023 - sel; + + lv_calendar_set_showed_date(calendar, newd.year, newd.month); +} + +static void value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * header = lv_event_get_target(e); + lv_obj_t * calendar = lv_obj_get_parent(header); + const lv_calendar_date_t * cur_date = lv_calendar_get_showed_date(calendar); + + lv_obj_t * year_dd = lv_obj_get_child(header, 0); + lv_dropdown_set_selected(year_dd, 2023 - cur_date->year); + + lv_obj_t * month_dd = lv_obj_get_child(header, 1); + lv_dropdown_set_selected(month_dd, cur_date->month - 1); +} + +#endif /*LV_USE_CALENDAR_HEADER_ARROW*/ + diff --git a/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.h b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.h new file mode 100644 index 00000000..fca21976 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/calendar/lv_calendar_header_dropdown.h @@ -0,0 +1,49 @@ +/** + * @file lv_calendar_header_dropdown.h + * + */ + +#ifndef LV_CALENDAR_HEADER_DROPDOWN_H +#define LV_CALENDAR_HEADER_DROPDOWN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" +#if LV_USE_CALENDAR_HEADER_DROPDOWN + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +extern const lv_obj_class_t lv_calendar_header_dropdown_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a calendar header with drop-drowns to select the year and month + * @param parent pointer to a calendar object. + * @return the created header + */ +lv_obj_t * lv_calendar_header_dropdown_create(lv_obj_t * parent); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_CALENDAR_HEADER_ARROW*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_CALENDAR_HEADER_DROPDOWN_H*/ diff --git a/lib/lvgl/src/extra/widgets/chart/lv_chart.c b/lib/lvgl/src/extra/widgets/chart/lv_chart.c new file mode 100644 index 00000000..da6c18c0 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/chart/lv_chart.c @@ -0,0 +1,1802 @@ +/** + * @file lv_chart.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_chart.h" +#if LV_USE_CHART != 0 + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_chart_class + +#define LV_CHART_HDIV_DEF 3 +#define LV_CHART_VDIV_DEF 5 +#define LV_CHART_POINT_CNT_DEF 10 +#define LV_CHART_LABEL_MAX_TEXT_LENGTH 16 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e); + +static void draw_div_lines(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static void draw_series_line(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static void draw_series_bar(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static void draw_series_scatter(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static void draw_cursors(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static void draw_axes(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static uint32_t get_index_from_x(lv_obj_t * obj, lv_coord_t x); +static void invalidate_point(lv_obj_t * obj, uint16_t i); +static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, lv_coord_t ** a); +lv_chart_tick_dsc_t * get_tick_gsc(lv_obj_t * obj, lv_chart_axis_t axis); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_chart_class = { + .constructor_cb = lv_chart_constructor, + .destructor_cb = lv_chart_destructor, + .event_cb = lv_chart_event, + .width_def = LV_PCT(100), + .height_def = LV_DPI_DEF * 2, + .instance_size = sizeof(lv_chart_t), + .base_class = &lv_obj_class +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_chart_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->type == type) return; + + if(chart->type == LV_CHART_TYPE_SCATTER) { + lv_chart_series_t * ser; + _LV_LL_READ_BACK(&chart->series_ll, ser) { + lv_mem_free(ser->x_points); + ser->x_points = NULL; + } + } + + if(type == LV_CHART_TYPE_SCATTER) { + lv_chart_series_t * ser; + _LV_LL_READ_BACK(&chart->series_ll, ser) { + ser->x_points = lv_mem_alloc(sizeof(lv_point_t) * chart->point_cnt); + LV_ASSERT_MALLOC(ser->x_points); + if(ser->x_points == NULL) return; + } + } + + chart->type = type; + + lv_chart_refresh(obj); +} + +void lv_chart_set_point_count(lv_obj_t * obj, uint16_t cnt) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->point_cnt == cnt) return; + + lv_chart_series_t * ser; + + if(cnt < 1) cnt = 1; + + _LV_LL_READ_BACK(&chart->series_ll, ser) { + if(chart->type == LV_CHART_TYPE_SCATTER) { + if(!ser->x_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->x_points); + } + if(!ser->y_ext_buf_assigned) new_points_alloc(obj, ser, cnt, &ser->y_points); + ser->start_point = 0; + } + + chart->point_cnt = cnt; + + lv_chart_refresh(obj); +} + +void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t min, lv_coord_t max) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + max = max == min ? max + 1 : max; + + lv_chart_t * chart = (lv_chart_t *)obj; + switch(axis) { + case LV_CHART_AXIS_PRIMARY_Y: + chart->ymin[0] = min; + chart->ymax[0] = max; + break; + case LV_CHART_AXIS_SECONDARY_Y: + chart->ymin[1] = min; + chart->ymax[1] = max; + break; + case LV_CHART_AXIS_PRIMARY_X: + chart->xmin[0] = min; + chart->xmax[0] = max; + break; + case LV_CHART_AXIS_SECONDARY_X: + chart->xmin[1] = min; + chart->xmax[1] = max; + break; + default: + LV_LOG_WARN("Invalid axis: %d", axis); + return; + } + + lv_chart_refresh(obj); +} + +void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->update_mode == update_mode) return; + + chart->update_mode = update_mode; + lv_obj_invalidate(obj); +} + +void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->hdiv_cnt == hdiv && chart->vdiv_cnt == vdiv) return; + + chart->hdiv_cnt = hdiv; + chart->vdiv_cnt = vdiv; + + lv_obj_invalidate(obj); +} + + +void lv_chart_set_zoom_x(lv_obj_t * obj, uint16_t zoom_x) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->zoom_x == zoom_x) return; + + chart->zoom_x = zoom_x; + lv_obj_refresh_self_size(obj); + /*Be the chart doesn't remain scrolled out*/ + lv_obj_readjust_scroll(obj, LV_ANIM_OFF); + lv_obj_invalidate(obj); +} + +void lv_chart_set_zoom_y(lv_obj_t * obj, uint16_t zoom_y) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->zoom_y == zoom_y) return; + + chart->zoom_y = zoom_y; + lv_obj_refresh_self_size(obj); + /*Be the chart doesn't remain scrolled out*/ + lv_obj_readjust_scroll(obj, LV_ANIM_OFF); + lv_obj_invalidate(obj); +} + +uint16_t lv_chart_get_zoom_x(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + return chart->zoom_x; +} + +uint16_t lv_chart_get_zoom_y(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + return chart->zoom_y; +} + +void lv_chart_set_axis_tick(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t major_len, lv_coord_t minor_len, + lv_coord_t major_cnt, lv_coord_t minor_cnt, bool label_en, lv_coord_t draw_size) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis); + t->major_len = major_len; + t->minor_len = minor_len; + t->minor_cnt = minor_cnt; + t->major_cnt = major_cnt; + t->label_en = label_en; + t->draw_size = draw_size; + + lv_obj_refresh_ext_draw_size(obj); + lv_obj_invalidate(obj); +} + +lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + return chart->type; +} + +uint16_t lv_chart_get_point_count(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + return chart->point_cnt; +} + +uint16_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser) +{ + LV_UNUSED(obj); + LV_ASSERT_NULL(ser); + + return ser->start_point; +} + +void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_point_t * p_out) +{ + LV_ASSERT_NULL(obj); + LV_ASSERT_NULL(ser); + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(id >= chart->point_cnt) { + LV_LOG_WARN("Invalid index: %d", id); + p_out->x = 0; + p_out->y = 0; + return; + } + + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + + if(chart->type == LV_CHART_TYPE_LINE) { + p_out->x = (w * id) / (chart->point_cnt - 1); + } + else if(chart->type == LV_CHART_TYPE_SCATTER) { + p_out->x = lv_map(ser->x_points[id], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w); + } + else if(chart->type == LV_CHART_TYPE_BAR) { + uint32_t ser_cnt = _lv_ll_get_len(&chart->series_ll); + int32_t ser_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_ITEMS) * chart->zoom_x) >> 8; /*Gap between the column on the ~same X*/ + int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the column on ~adjacent X*/ + lv_coord_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt; + lv_coord_t col_w = block_w / ser_cnt; + + p_out->x = (int32_t)((int32_t)w * id) / chart->point_cnt; + + lv_chart_series_t * ser_i = NULL; + _LV_LL_READ_BACK(&chart->series_ll, ser_i) { + if(ser_i == ser) break; + p_out->x += col_w; + } + + p_out->x += (col_w - ser_gap) / 2; + } + + lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + p_out->x += lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width; + p_out->x -= lv_obj_get_scroll_left(obj); + + int32_t temp_y = 0; + temp_y = (int32_t)((int32_t)ser->y_points[id] - chart->ymin[ser->y_axis_sec]) * h; + temp_y = temp_y / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]); + p_out->y = h - temp_y; + p_out->y += lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width; + p_out->y -= lv_obj_get_scroll_top(obj); +} + +void lv_chart_refresh(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_obj_invalidate(obj); +} + +/*====================== + * Series + *=====================*/ + +lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis) +{ + LV_LOG_INFO("begin"); + + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + lv_chart_series_t * ser = _lv_ll_ins_head(&chart->series_ll); + LV_ASSERT_MALLOC(ser); + if(ser == NULL) return NULL; + + lv_coord_t def = LV_CHART_POINT_NONE; + + ser->color = color; + ser->y_points = lv_mem_alloc(sizeof(lv_coord_t) * chart->point_cnt); + LV_ASSERT_MALLOC(ser->y_points); + + if(chart->type == LV_CHART_TYPE_SCATTER) { + ser->x_points = lv_mem_alloc(sizeof(lv_coord_t) * chart->point_cnt); + LV_ASSERT_MALLOC(ser->x_points); + } + if(ser->y_points == NULL) { + _lv_ll_remove(&chart->series_ll, ser); + lv_mem_free(ser); + return NULL; + } + + ser->start_point = 0; + ser->y_ext_buf_assigned = false; + ser->hidden = 0; + ser->x_axis_sec = axis & LV_CHART_AXIS_SECONDARY_X ? 1 : 0; + ser->y_axis_sec = axis & LV_CHART_AXIS_SECONDARY_Y ? 1 : 0; + + uint16_t i; + lv_coord_t * p_tmp = ser->y_points; + for(i = 0; i < chart->point_cnt; i++) { + *p_tmp = def; + p_tmp++; + } + + return ser; +} + +void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(series); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(!series->y_ext_buf_assigned && series->y_points) lv_mem_free(series->y_points); + + _lv_ll_remove(&chart->series_ll, series); + lv_mem_free(series); + + return; +} + +void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide) +{ + LV_ASSERT_OBJ(chart, MY_CLASS); + LV_ASSERT_NULL(series); + + series->hidden = hide ? 1 : 0; + lv_chart_refresh(chart); +} + + +void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color) +{ + LV_ASSERT_OBJ(chart, MY_CLASS); + LV_ASSERT_NULL(series); + + series->color = color; + lv_chart_refresh(chart); +} + +void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(id >= chart->point_cnt) return; + ser->start_point = id; +} + +lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * obj, const lv_chart_series_t * ser) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(ser == NULL) return _lv_ll_get_head(&chart->series_ll); + else return _lv_ll_get_next(&chart->series_ll, ser); +} + +/*===================== + * Cursor + *====================*/ + +/** + * Add a cursor with a given color + * @param chart pointer to chart object + * @param color color of the cursor + * @param dir direction of the cursor. `LV_DIR_RIGHT/LEFT/TOP/DOWN/HOR/VER/ALL`. OR-ed values are possible + * @return pointer to the created cursor + */ +lv_chart_cursor_t * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + lv_chart_cursor_t * cursor = _lv_ll_ins_head(&chart->cursor_ll); + LV_ASSERT_MALLOC(cursor); + if(cursor == NULL) return NULL; + + cursor->pos.x = LV_CHART_POINT_NONE; + cursor->pos.y = LV_CHART_POINT_NONE; + cursor->point_id = LV_CHART_POINT_NONE; + cursor->pos_set = 0; + cursor->color = color; + cursor->dir = dir; + + return cursor; +} + +/** + * Set the coordinate of the cursor with respect + * to the origin of series area of the chart. + * @param chart pointer to a chart object. + * @param cursor pointer to the cursor. + * @param pos the new coordinate of cursor relative to the series area + */ +void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos) +{ + LV_ASSERT_NULL(cursor); + LV_UNUSED(chart); + + cursor->pos.x = pos->x; + cursor->pos.y = pos->y; + cursor->pos_set = 1; + lv_chart_refresh(chart); +} + + +/** + * Set the coordinate of the cursor with respect + * to the origin of series area of the chart. + * @param chart pointer to a chart object. + * @param cursor pointer to the cursor. + * @param pos the new coordinate of cursor relative to the series area + */ +void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser, uint16_t point_id) +{ + LV_ASSERT_NULL(cursor); + LV_UNUSED(chart); + + cursor->point_id = point_id; + cursor->pos_set = 0; + if(ser == NULL) ser = lv_chart_get_series_next(chart, NULL); + cursor->ser = ser; + lv_chart_refresh(chart); +} +/** + * Get the coordinate of the cursor with respect + * to the origin of series area of the chart. + * @param chart pointer to a chart object + * @param cursor pointer to cursor + * @return coordinate of the cursor as lv_point_t + */ +lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor) +{ + LV_ASSERT_NULL(cursor); + LV_UNUSED(chart); + + return cursor->pos; +} + +/*===================== + * Set/Get value(s) + *====================*/ + + +void lv_chart_set_all_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + lv_chart_t * chart = (lv_chart_t *)obj; + uint16_t i; + for(i = 0; i < chart->point_cnt; i++) { + ser->y_points[i] = value; + } + ser->start_point = 0; + lv_chart_refresh(obj); +} + +void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + lv_chart_t * chart = (lv_chart_t *)obj; + ser->y_points[ser->start_point] = value; + invalidate_point(obj, ser->start_point); + ser->start_point = (ser->start_point + 1) % chart->point_cnt; + invalidate_point(obj, ser->start_point); +} + +void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t x_value, lv_coord_t y_value) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + lv_chart_t * chart = (lv_chart_t *)obj; + + if(chart->type != LV_CHART_TYPE_SCATTER) { + LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER"); + return; + } + + ser->x_points[ser->start_point] = x_value; + ser->y_points[ser->start_point] = y_value; + ser->start_point = (ser->start_point + 1) % chart->point_cnt; + invalidate_point(obj, ser->start_point); +} + +void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t value) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + lv_chart_t * chart = (lv_chart_t *)obj; + + if(id >= chart->point_cnt) return; + ser->y_points[id] = value; + invalidate_point(obj, id); +} + +void lv_chart_set_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t x_value, + lv_coord_t y_value) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + lv_chart_t * chart = (lv_chart_t *)obj; + + if(chart->type != LV_CHART_TYPE_SCATTER) { + LV_LOG_WARN("Type must be LV_CHART_TYPE_SCATTER"); + return; + } + + if(id >= chart->point_cnt) return; + ser->x_points[id] = x_value; + ser->y_points[id] = y_value; + invalidate_point(obj, id); +} + +void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[]) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + if(!ser->y_ext_buf_assigned && ser->y_points) lv_mem_free(ser->y_points); + ser->y_ext_buf_assigned = true; + ser->y_points = array; + lv_obj_invalidate(obj); +} + +void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[]) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + + if(!ser->x_ext_buf_assigned && ser->x_points) lv_mem_free(ser->x_points); + ser->x_ext_buf_assigned = true; + ser->x_points = array; + lv_obj_invalidate(obj); +} + +lv_coord_t * lv_chart_get_y_array(const lv_obj_t * obj, lv_chart_series_t * ser) +{ + LV_UNUSED(obj); + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + return ser->y_points; +} + +lv_coord_t * lv_chart_get_x_array(const lv_obj_t * obj, lv_chart_series_t * ser) +{ + LV_UNUSED(obj); + LV_ASSERT_OBJ(obj, MY_CLASS); + LV_ASSERT_NULL(ser); + return ser->x_points; +} + +uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + return chart->pressed_point_id; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_chart_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_chart_t * chart = (lv_chart_t *)obj; + + _lv_ll_init(&chart->series_ll, sizeof(lv_chart_series_t)); + _lv_ll_init(&chart->cursor_ll, sizeof(lv_chart_cursor_t)); + + chart->ymin[0] = 0; + chart->xmin[0] = 0; + chart->ymin[1] = 0; + chart->xmin[1] = 0; + chart->ymax[0] = 100; + chart->xmax[0] = 100; + chart->ymax[1] = 100; + chart->xmax[1] = 100; + + chart->hdiv_cnt = LV_CHART_HDIV_DEF; + chart->vdiv_cnt = LV_CHART_VDIV_DEF; + chart->point_cnt = LV_CHART_POINT_CNT_DEF; + chart->pressed_point_id = LV_CHART_POINT_NONE; + chart->type = LV_CHART_TYPE_LINE; + chart->update_mode = LV_CHART_UPDATE_MODE_SHIFT; + chart->zoom_x = LV_IMG_ZOOM_NONE; + chart->zoom_y = LV_IMG_ZOOM_NONE; + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_chart_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_chart_t * chart = (lv_chart_t *)obj; + lv_chart_series_t * ser; + while(chart->series_ll.head) { + ser = _lv_ll_get_head(&chart->series_ll); + + if(!ser->y_ext_buf_assigned) lv_mem_free(ser->y_points); + + _lv_ll_remove(&chart->series_ll, ser); + lv_mem_free(ser); + } + _lv_ll_clear(&chart->series_ll); + + lv_chart_cursor_t * cur; + while(chart->cursor_ll.head) { + cur = _lv_ll_get_head(&chart->cursor_ll); + _lv_ll_remove(&chart->cursor_ll, cur); + lv_mem_free(cur); + } + _lv_ll_clear(&chart->cursor_ll); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_chart_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + /*Call the ancestor's event handler*/ + lv_res_t res; + + res = lv_obj_event_base(MY_CLASS, e); + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(code == LV_EVENT_PRESSED) { + lv_indev_t * indev = lv_indev_get_act(); + lv_point_t p; + lv_indev_get_point(indev, &p); + + p.x -= obj->coords.x1; + uint32_t id = get_index_from_x(obj, p.x + lv_obj_get_scroll_left(obj)); + if(id != (uint32_t)chart->pressed_point_id) { + invalidate_point(obj, id); + invalidate_point(obj, chart->pressed_point_id); + chart->pressed_point_id = id; + lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + } + } + else if(code == LV_EVENT_RELEASED) { + invalidate_point(obj, chart->pressed_point_id); + chart->pressed_point_id = LV_CHART_POINT_NONE; + } + else if(code == LV_EVENT_SIZE_CHANGED) { + lv_obj_refresh_self_size(obj); + } + else if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) { + lv_event_set_ext_draw_size(e, LV_MAX4(chart->tick[0].draw_size, chart->tick[1].draw_size, chart->tick[2].draw_size, + chart->tick[3].draw_size)); + } + else if(code == LV_EVENT_GET_SELF_SIZE) { + lv_point_t * p = lv_event_get_param(e); + p->x = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + p->y = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + } + else if(code == LV_EVENT_DRAW_MAIN) { + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + draw_div_lines(obj, draw_ctx); + draw_axes(obj, draw_ctx); + + if(_lv_ll_is_empty(&chart->series_ll) == false) { + if(chart->type == LV_CHART_TYPE_LINE) draw_series_line(obj, draw_ctx); + else if(chart->type == LV_CHART_TYPE_BAR) draw_series_bar(obj, draw_ctx); + else if(chart->type == LV_CHART_TYPE_SCATTER) draw_series_scatter(obj, draw_ctx); + } + + draw_cursors(obj, draw_ctx); + } +} + +static void draw_div_lines(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + + lv_area_t series_clip_area; + bool mask_ret = _lv_area_intersect(&series_clip_area, &obj->coords, draw_ctx->clip_area); + if(mask_ret == false) return; + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &series_clip_area; + + int16_t i; + int16_t i_start; + int16_t i_end; + lv_point_t p1; + lv_point_t p2; + lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width; + lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width; + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc); + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.part = LV_PART_MAIN; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_INIT; + part_draw_dsc.line_dsc = &line_dsc; + part_draw_dsc.id = 0xFFFFFFFF; + part_draw_dsc.p1 = NULL; + part_draw_dsc.p2 = NULL; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + lv_opa_t border_opa = lv_obj_get_style_border_opa(obj, LV_PART_MAIN); + lv_coord_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_border_side_t border_side = lv_obj_get_style_border_side(obj, LV_PART_MAIN); + + lv_coord_t scroll_left = lv_obj_get_scroll_left(obj); + lv_coord_t scroll_top = lv_obj_get_scroll_top(obj); + if(chart->hdiv_cnt != 0) { + lv_coord_t y_ofs = obj->coords.y1 + pad_top - scroll_top; + p1.x = obj->coords.x1; + p2.x = obj->coords.x2; + + i_start = 0; + i_end = chart->hdiv_cnt; + if(border_opa > LV_OPA_MIN && border_w > 0) { + if((border_side & LV_BORDER_SIDE_TOP) && (lv_obj_get_style_pad_top(obj, LV_PART_MAIN) == 0)) i_start++; + if((border_side & LV_BORDER_SIDE_BOTTOM) && (lv_obj_get_style_pad_bottom(obj, LV_PART_MAIN) == 0)) i_end--; + } + + for(i = i_start; i < i_end; i++) { + p1.y = (int32_t)((int32_t)h * i) / (chart->hdiv_cnt - 1); + p1.y += y_ofs; + p2.y = p1.y; + + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_HOR; + part_draw_dsc.p1 = &p1; + part_draw_dsc.p2 = &p2; + part_draw_dsc.id = i; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + + if(chart->vdiv_cnt != 0) { + lv_coord_t x_ofs = obj->coords.x1 + pad_left - scroll_left; + p1.y = obj->coords.y1; + p2.y = obj->coords.y2; + i_start = 0; + i_end = chart->vdiv_cnt; + if(border_opa > LV_OPA_MIN && border_w > 0) { + if((border_side & LV_BORDER_SIDE_LEFT) && (lv_obj_get_style_pad_left(obj, LV_PART_MAIN) == 0)) i_start++; + if((border_side & LV_BORDER_SIDE_RIGHT) && (lv_obj_get_style_pad_right(obj, LV_PART_MAIN) == 0)) i_end--; + } + + for(i = i_start; i < i_end; i++) { + p1.x = (int32_t)((int32_t)w * i) / (chart->vdiv_cnt - 1); + p1.x += x_ofs; + p2.x = p1.x; + + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_DIV_LINE_VER; + part_draw_dsc.p1 = &p1; + part_draw_dsc.p2 = &p2; + part_draw_dsc.id = i; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + + part_draw_dsc.id = 0xFFFFFFFF; + part_draw_dsc.p1 = NULL; + part_draw_dsc.p2 = NULL; + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + + draw_ctx->clip_area = clip_area_ori; +} + +static void draw_series_line(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + lv_area_t clip_area; + if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return; + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area; + + lv_chart_t * chart = (lv_chart_t *)obj; + if(chart->point_cnt < 2) return; + + uint16_t i; + lv_point_t p1; + lv_point_t p2; + lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + border_width; + lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN) + border_width; + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + lv_coord_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj); + lv_coord_t y_ofs = obj->coords.y1 + pad_top - lv_obj_get_scroll_top(obj); + lv_chart_series_t * ser; + + lv_area_t series_clip_area; + bool mask_ret = _lv_area_intersect(&series_clip_area, &obj->coords, draw_ctx->clip_area); + if(mask_ret == false) return; + + lv_draw_line_dsc_t line_dsc_default; + lv_draw_line_dsc_init(&line_dsc_default); + lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc_default); + + lv_draw_rect_dsc_t point_dsc_default; + lv_draw_rect_dsc_init(&point_dsc_default); + lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default); + + lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2; + lv_coord_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2; + + /*Do not bother with line ending is the point will over it*/ + if(LV_MIN(point_w, point_h) > line_dsc_default.width / 2) line_dsc_default.raw_end = 1; + if(line_dsc_default.width == 1) line_dsc_default.raw_end = 1; + + /*If there are at least as much points as pixels then draw only vertical lines*/ + bool crowded_mode = chart->point_cnt >= w ? true : false; + + /*Go through all data lines*/ + _LV_LL_READ_BACK(&chart->series_ll, ser) { + if(ser->hidden) continue; + line_dsc_default.color = ser->color; + point_dsc_default.bg_color = ser->color; + + lv_coord_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0; + + p1.x = x_ofs; + p2.x = x_ofs; + + lv_coord_t p_act = start_point; + lv_coord_t p_prev = start_point; + int32_t y_tmp = (int32_t)((int32_t)ser->y_points[p_prev] - chart->ymin[ser->y_axis_sec]) * h; + y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]); + p2.y = h - y_tmp + y_ofs; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_LINE_AND_POINT; + part_draw_dsc.part = LV_PART_ITEMS; + part_draw_dsc.line_dsc = &line_dsc_default; + part_draw_dsc.rect_dsc = &point_dsc_default; + part_draw_dsc.sub_part_ptr = ser; + + lv_coord_t y_min = p2.y; + lv_coord_t y_max = p2.y; + + for(i = 0; i < chart->point_cnt; i++) { + p1.x = p2.x; + p1.y = p2.y; + + if(p1.x > clip_area_ori->x2 + point_w + 1) break; + p2.x = ((w * i) / (chart->point_cnt - 1)) + x_ofs; + + p_act = (start_point + i) % chart->point_cnt; + + y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h; + y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]); + p2.y = h - y_tmp + y_ofs; + + if(p2.x < clip_area_ori->x1 - point_w - 1) { + p_prev = p_act; + continue; + } + + /*Don't draw the first point. A second point is also required to draw the line*/ + if(i != 0) { + if(crowded_mode) { + if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) { + /*Draw only one vertical line between the min and max y-values on the same x-value*/ + y_max = LV_MAX(y_max, p2.y); + y_min = LV_MIN(y_min, p2.y); + if(p1.x != p2.x) { + lv_coord_t y_cur = p2.y; + p2.x--; /*It's already on the next x value*/ + p1.x = p2.x; + p1.y = y_min; + p2.y = y_max; + if(p1.y == p2.y) p2.y++; /*If they are the same no line will be drawn*/ + lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2); + p2.x++; /*Compensate the previous x--*/ + y_min = y_cur; /*Start the line of the next x from the current last y*/ + y_max = y_cur; + } + } + } + else { + lv_area_t point_area; + point_area.x1 = p1.x - point_w; + point_area.x2 = p1.x + point_w; + point_area.y1 = p1.y - point_h; + point_area.y2 = p1.y + point_h; + + part_draw_dsc.id = i - 1; + part_draw_dsc.p1 = ser->y_points[p_prev] != LV_CHART_POINT_NONE ? &p1 : NULL; + part_draw_dsc.p2 = ser->y_points[p_act] != LV_CHART_POINT_NONE ? &p2 : NULL; + part_draw_dsc.draw_area = &point_area; + part_draw_dsc.value = ser->y_points[p_prev]; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) { + lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2); + } + + if(point_w && point_h && ser->y_points[p_prev] != LV_CHART_POINT_NONE) { + lv_draw_rect(draw_ctx, &point_dsc_default, &point_area); + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + + } + p_prev = p_act; + } + + /*Draw the last point*/ + if(!crowded_mode && i == chart->point_cnt) { + + if(ser->y_points[p_act] != LV_CHART_POINT_NONE) { + lv_area_t point_area; + point_area.x1 = p2.x - point_w; + point_area.x2 = p2.x + point_w; + point_area.y1 = p2.y - point_h; + point_area.y2 = p2.y + point_h; + + part_draw_dsc.id = i - 1; + part_draw_dsc.p1 = NULL; + part_draw_dsc.p2 = NULL; + part_draw_dsc.draw_area = &point_area; + part_draw_dsc.value = ser->y_points[p_act]; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_rect(draw_ctx, &point_dsc_default, &point_area); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + } + + draw_ctx->clip_area = clip_area_ori; +} + +static void draw_series_scatter(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + + lv_area_t clip_area; + if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return; + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area; + + lv_chart_t * chart = (lv_chart_t *)obj; + + uint16_t i; + lv_point_t p1; + lv_point_t p2; + lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN); + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + lv_coord_t x_ofs = obj->coords.x1 + pad_left + border_width - lv_obj_get_scroll_left(obj); + lv_coord_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj); + lv_chart_series_t * ser; + + lv_draw_line_dsc_t line_dsc_default; + lv_draw_line_dsc_init(&line_dsc_default); + lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc_default); + + lv_draw_rect_dsc_t point_dsc_default; + lv_draw_rect_dsc_init(&point_dsc_default); + lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &point_dsc_default); + + lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2; + lv_coord_t point_h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2; + + /*Do not bother with line ending is the point will over it*/ + if(LV_MIN(point_w, point_h) > line_dsc_default.width / 2) line_dsc_default.raw_end = 1; + if(line_dsc_default.width == 1) line_dsc_default.raw_end = 1; + + /*Go through all data lines*/ + _LV_LL_READ_BACK(&chart->series_ll, ser) { + if(ser->hidden) continue; + line_dsc_default.color = ser->color; + point_dsc_default.bg_color = ser->color; + + lv_coord_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0; + + p1.x = x_ofs; + p2.x = x_ofs; + + lv_coord_t p_act = start_point; + lv_coord_t p_prev = start_point; + if(ser->y_points[p_act] != LV_CHART_POINT_CNT_DEF) { + p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w); + p2.x += x_ofs; + + p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h); + p2.y = h - p2.y; + p2.y += y_ofs; + } + else { + p2.x = LV_COORD_MIN; + p2.y = LV_COORD_MIN; + } + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.part = LV_PART_ITEMS; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_LINE_AND_POINT; + part_draw_dsc.line_dsc = &line_dsc_default; + part_draw_dsc.rect_dsc = &point_dsc_default; + part_draw_dsc.sub_part_ptr = ser; + + for(i = 0; i < chart->point_cnt; i++) { + p1.x = p2.x; + p1.y = p2.y; + + p_act = (start_point + i) % chart->point_cnt; + if(ser->y_points[p_act] != LV_CHART_POINT_NONE) { + p2.y = lv_map(ser->y_points[p_act], chart->ymin[ser->y_axis_sec], chart->ymax[ser->y_axis_sec], 0, h); + p2.y = h - p2.y; + p2.y += y_ofs; + + p2.x = lv_map(ser->x_points[p_act], chart->xmin[ser->x_axis_sec], chart->xmax[ser->x_axis_sec], 0, w); + p2.x += x_ofs; + } + else { + p_prev = p_act; + continue; + } + + /*Don't draw the first point. A second point is also required to draw the line*/ + if(i != 0) { + lv_area_t point_area; + point_area.x1 = p1.x - point_w; + point_area.x2 = p1.x + point_w; + point_area.y1 = p1.y - point_h; + point_area.y2 = p1.y + point_h; + + part_draw_dsc.id = i - 1; + part_draw_dsc.p1 = ser->y_points[p_prev] != LV_CHART_POINT_NONE ? &p1 : NULL; + part_draw_dsc.p2 = ser->y_points[p_act] != LV_CHART_POINT_NONE ? &p2 : NULL; + part_draw_dsc.draw_area = &point_area; + part_draw_dsc.value = ser->y_points[p_prev]; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + if(ser->y_points[p_prev] != LV_CHART_POINT_NONE && ser->y_points[p_act] != LV_CHART_POINT_NONE) { + lv_draw_line(draw_ctx, &line_dsc_default, &p1, &p2); + if(point_w && point_h) { + lv_draw_rect(draw_ctx, &point_dsc_default, &point_area); + } + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + p_prev = p_act; + } + + /*Draw the last point*/ + if(i == chart->point_cnt) { + + if(ser->y_points[p_act] != LV_CHART_POINT_NONE) { + lv_area_t point_area; + point_area.x1 = p2.x - point_w; + point_area.x2 = p2.x + point_w; + point_area.y1 = p2.y - point_h; + point_area.y2 = p2.y + point_h; + + part_draw_dsc.id = i - 1; + part_draw_dsc.p1 = NULL; + part_draw_dsc.p2 = NULL; + part_draw_dsc.draw_area = &point_area; + part_draw_dsc.value = ser->y_points[p_act]; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_rect(draw_ctx, &point_dsc_default, &point_area); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + } + draw_ctx->clip_area = clip_area_ori; +} + +static void draw_series_bar(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + lv_area_t clip_area; + if(_lv_area_intersect(&clip_area, &obj->coords, draw_ctx->clip_area) == false) return; + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area; + + + lv_chart_t * chart = (lv_chart_t *)obj; + + uint16_t i; + lv_area_t col_a; + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN); + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + int32_t y_tmp; + lv_chart_series_t * ser; + uint32_t ser_cnt = _lv_ll_get_len(&chart->series_ll); + int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the column on ~adjacent X*/ + lv_coord_t block_w = (w - ((chart->point_cnt - 1) * block_gap)) / chart->point_cnt; + int32_t ser_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_ITEMS) * chart->zoom_x) >> 8; /*Gap between the columns on the ~same X*/ + lv_coord_t col_w = (block_w - (ser_cnt - 1) * ser_gap) / ser_cnt; + + lv_coord_t border_w = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t x_ofs = pad_left - lv_obj_get_scroll_left(obj) + border_w; + lv_coord_t y_ofs = pad_top - lv_obj_get_scroll_top(obj) + border_w; + + lv_draw_rect_dsc_t col_dsc; + lv_draw_rect_dsc_init(&col_dsc); + lv_obj_init_draw_rect_dsc(obj, LV_PART_ITEMS, &col_dsc); + col_dsc.bg_grad.dir = LV_GRAD_DIR_NONE; + col_dsc.bg_opa = LV_OPA_COVER; + + /*Make the cols longer with `radius` to clip the rounding from the bottom*/ + col_a.y2 = obj->coords.y2 + col_dsc.radius; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.part = LV_PART_ITEMS; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_BAR; + + /*Go through all points*/ + for(i = 0; i < chart->point_cnt; i++) { + lv_coord_t x_act = (int32_t)((int32_t)(w - block_w) * i) / (chart->point_cnt - 1) + obj->coords.x1 + x_ofs; + + part_draw_dsc.id = i; + + /*Draw the current point of all data line*/ + _LV_LL_READ_BACK(&chart->series_ll, ser) { + if(ser->hidden) continue; + lv_coord_t start_point = chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT ? ser->start_point : 0; + + col_a.x1 = x_act; + col_a.x2 = col_a.x1 + col_w - 1; + x_act += col_w + ser_gap; + + if(col_a.x2 < clip_area.x1) continue; + if(col_a.x1 > clip_area.x2) break; + + col_dsc.bg_color = ser->color; + + lv_coord_t p_act = (start_point + i) % chart->point_cnt; + y_tmp = (int32_t)((int32_t)ser->y_points[p_act] - chart->ymin[ser->y_axis_sec]) * h; + y_tmp = y_tmp / (chart->ymax[ser->y_axis_sec] - chart->ymin[ser->y_axis_sec]); + col_a.y1 = h - y_tmp + obj->coords.y1 + y_ofs; + + if(ser->y_points[p_act] != LV_CHART_POINT_NONE) { + part_draw_dsc.draw_area = &col_a; + part_draw_dsc.rect_dsc = &col_dsc; + part_draw_dsc.sub_part_ptr = ser; + part_draw_dsc.value = ser->y_points[p_act]; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_rect(draw_ctx, &col_dsc, &col_a); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + } + draw_ctx->clip_area = clip_area_ori; +} + +static void draw_cursors(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_chart_t * chart = (lv_chart_t *)obj; + if(_lv_ll_is_empty(&chart->cursor_ll)) return; + + lv_area_t clip_area; + if(!_lv_area_intersect(&clip_area, draw_ctx->clip_area, &obj->coords)) return; + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area; + + lv_point_t p1; + lv_point_t p2; + lv_chart_cursor_t * cursor; + + lv_draw_line_dsc_t line_dsc_ori; + lv_draw_line_dsc_init(&line_dsc_ori); + lv_obj_init_draw_line_dsc(obj, LV_PART_CURSOR, &line_dsc_ori); + + lv_draw_rect_dsc_t point_dsc_ori; + lv_draw_rect_dsc_init(&point_dsc_ori); + point_dsc_ori.bg_opa = line_dsc_ori.opa; + point_dsc_ori.radius = LV_RADIUS_CIRCLE; + + lv_draw_line_dsc_t line_dsc_tmp; + lv_draw_rect_dsc_t point_dsc_tmp; + + lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2; + lv_coord_t point_h = lv_obj_get_style_width(obj, LV_PART_CURSOR) / 2; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.line_dsc = &line_dsc_tmp; + part_draw_dsc.rect_dsc = &point_dsc_tmp; + part_draw_dsc.part = LV_PART_CURSOR; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_CURSOR; + + /*Go through all cursor lines*/ + _LV_LL_READ_BACK(&chart->cursor_ll, cursor) { + lv_memcpy(&line_dsc_tmp, &line_dsc_ori, sizeof(lv_draw_line_dsc_t)); + lv_memcpy(&point_dsc_tmp, &point_dsc_ori, sizeof(lv_draw_rect_dsc_t)); + line_dsc_tmp.color = cursor->color; + point_dsc_tmp.bg_color = cursor->color; + + part_draw_dsc.p1 = &p1; + part_draw_dsc.p2 = &p2; + + lv_coord_t cx; + lv_coord_t cy; + if(cursor->pos_set) { + cx = cursor->pos.x; + cy = cursor->pos.y; + } + else { + if(cursor->point_id == LV_CHART_POINT_NONE) continue; + lv_point_t p; + lv_chart_get_point_pos_by_id(obj, cursor->ser, cursor->point_id, &p); + cx = p.x; + cy = p.y; + } + + cx += obj->coords.x1; + cy += obj->coords.y1; + + lv_area_t point_area; + bool draw_point = point_w && point_h; + if(draw_point) { + point_area.x1 = cx - point_w; + point_area.x2 = cx + point_w; + point_area.y1 = cy - point_h; + point_area.y2 = cy + point_h; + + part_draw_dsc.draw_area = &point_area; + } + else { + part_draw_dsc.draw_area = NULL; + } + + if(cursor->dir & LV_DIR_HOR) { + p1.x = cursor->dir & LV_DIR_LEFT ? obj->coords.x1 : cx; + p1.y = cy; + p2.x = cursor->dir & LV_DIR_RIGHT ? obj->coords.x2 : cx; + p2.y = p1.y; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2); + + if(draw_point) { + lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area); + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + + if(cursor->dir & LV_DIR_VER) { + p1.x = cx; + p1.y = cursor->dir & LV_DIR_TOP ? obj->coords.y1 : cy; + p2.x = p1.x; + p2.y = cursor->dir & LV_DIR_BOTTOM ? obj->coords.y2 : cy; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_line(draw_ctx, &line_dsc_tmp, &p1, &p2); + + if(draw_point) { + lv_draw_rect(draw_ctx, &point_dsc_tmp, &point_area); + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } + + draw_ctx->clip_area = clip_area_ori; +} + +static void draw_y_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + + lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis); + + if(t->major_cnt <= 1) return; + if(!t->label_en && !t->major_len && !t->minor_len) return; + + uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_Y ? 0 : 1; + + uint32_t i; + + lv_point_t p1; + lv_point_t p2; + + lv_coord_t border_width = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t pad_top = lv_obj_get_style_pad_top(obj, LV_PART_MAIN); + lv_coord_t h = ((int32_t)lv_obj_get_content_height(obj) * chart->zoom_y) >> 8; + lv_coord_t y_ofs = obj->coords.y1 + pad_top + border_width - lv_obj_get_scroll_top(obj); + + lv_coord_t label_gap; + lv_coord_t x_ofs; + if(axis == LV_CHART_AXIS_PRIMARY_Y) { + label_gap = lv_obj_get_style_pad_left(obj, LV_PART_TICKS); + x_ofs = obj->coords.x1; + } + else { + label_gap = lv_obj_get_style_pad_right(obj, LV_PART_TICKS); + x_ofs = obj->coords.x2; + } + + lv_coord_t major_len = t->major_len; + lv_coord_t minor_len = t->minor_len; + /*tick lines on secondary y axis are drawn in other direction*/ + if(axis == LV_CHART_AXIS_SECONDARY_Y) { + major_len *= -1; + minor_len *= -1; + } + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc); + + lv_draw_label_dsc_t label_dsc; + lv_draw_label_dsc_init(&label_dsc); + lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc); + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL; + part_draw_dsc.id = axis; + part_draw_dsc.part = LV_PART_TICKS; + part_draw_dsc.line_dsc = &line_dsc; + part_draw_dsc.label_dsc = &label_dsc; + + uint32_t total_tick_num = (t->major_cnt - 1) * (t->minor_cnt); + for(i = 0; i <= total_tick_num; i++) { + /*draw a line at moving y position*/ + p2.y = p1.y = y_ofs + (int32_t)((int32_t)(h - line_dsc.width) * i) / total_tick_num; + + /*first point of the tick*/ + p1.x = x_ofs; + + /*move extra pixel out of chart boundary*/ + if(axis == LV_CHART_AXIS_PRIMARY_Y) p1.x--; + else p1.x++; + + /*second point of the tick*/ + bool major = false; + if(i % t->minor_cnt == 0) major = true; + + if(major) p2.x = p1.x - major_len; /*major tick*/ + else p2.x = p1.x - minor_len; /*minor tick*/ + + part_draw_dsc.p1 = &p1; + part_draw_dsc.p2 = &p2; + + int32_t tick_value = lv_map(total_tick_num - i, 0, total_tick_num, chart->ymin[sec_axis], chart->ymax[sec_axis]); + part_draw_dsc.value = tick_value; + + /*add text only to major tick*/ + if(major && t->label_en) { + char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH]; + lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value); + part_draw_dsc.label_dsc = &label_dsc; + part_draw_dsc.text = buf; + part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + /*reserve appropriate area*/ + lv_point_t size; + lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX, + LV_TEXT_FLAG_NONE); + + /*set the area at some distance of the major tick len left of the tick*/ + lv_area_t a; + a.y1 = p2.y - size.y / 2; + a.y2 = p2.y + size.y / 2; + + if(!sec_axis) { + a.x1 = p2.x - size.x - label_gap; + a.x2 = p2.x - label_gap; + } + else { + a.x1 = p2.x + label_gap; + a.x2 = p2.x + size.x + label_gap; + } + + if(a.y2 >= obj->coords.y1 && + a.y1 <= obj->coords.y2) { + lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL); + } + } + else { + part_draw_dsc.label_dsc = NULL; + part_draw_dsc.text = NULL; + part_draw_dsc.text_length = 0; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + } + + if(p1.y + line_dsc.width / 2 >= obj->coords.y1 && + p2.y - line_dsc.width / 2 <= obj->coords.y2) { + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } +} + +static void draw_x_ticks(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, lv_chart_axis_t axis) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + + lv_chart_tick_dsc_t * t = get_tick_gsc(obj, axis); + if(t->major_cnt <= 1) return; + if(!t->label_en && !t->major_len && !t->minor_len) return; + + uint32_t i; + lv_point_t p1; + lv_point_t p2; + + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN) + lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + + lv_draw_label_dsc_t label_dsc; + lv_draw_label_dsc_init(&label_dsc); + lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc); + + lv_coord_t x_ofs = obj->coords.x1 + pad_left - lv_obj_get_scroll_left(obj); + lv_coord_t y_ofs; + lv_coord_t label_gap; + if(axis == LV_CHART_AXIS_PRIMARY_X) { + label_gap = t->label_en ? lv_obj_get_style_pad_bottom(obj, LV_PART_TICKS) : 0; + y_ofs = obj->coords.y2; + } + else { + label_gap = t->label_en ? lv_obj_get_style_pad_top(obj, LV_PART_TICKS) : 0; + y_ofs = obj->coords.y1; + } + + if(axis == LV_CHART_AXIS_PRIMARY_X) { + if(y_ofs > draw_ctx->clip_area->y2) return; + if(y_ofs + label_gap + label_dsc.font->line_height + t->major_len < draw_ctx->clip_area->y1) return; + } + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc); + line_dsc.dash_gap = 0; + line_dsc.dash_width = 0; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_CHART_DRAW_PART_TICK_LABEL; + part_draw_dsc.id = LV_CHART_AXIS_PRIMARY_X; + part_draw_dsc.part = LV_PART_TICKS; + part_draw_dsc.label_dsc = &label_dsc; + part_draw_dsc.line_dsc = &line_dsc; + + uint8_t sec_axis = axis == LV_CHART_AXIS_PRIMARY_X ? 0 : 1; + + /*The columns ticks should be aligned to the center of blocks*/ + if(chart->type == LV_CHART_TYPE_BAR) { + int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the columns on ~adjacent X*/ + lv_coord_t block_w = (w + block_gap) / (chart->point_cnt); + + x_ofs += (block_w - block_gap) / 2; + w -= block_w - block_gap; + } + + p1.y = y_ofs; + uint32_t total_tick_num = (t->major_cnt - 1) * t->minor_cnt; + for(i = 0; i <= total_tick_num; i++) { /*one extra loop - it may not exist in the list, empty label*/ + bool major = false; + if(i % t->minor_cnt == 0) major = true; + + /*draw a line at moving x position*/ + p2.x = p1.x = x_ofs + (int32_t)((int32_t)(w - line_dsc.width) * i) / total_tick_num; + + if(sec_axis) p2.y = p1.y - (major ? t->major_len : t->minor_len); + else p2.y = p1.y + (major ? t->major_len : t->minor_len); + + part_draw_dsc.p1 = &p1; + part_draw_dsc.p2 = &p2; + + /*add text only to major tick*/ + int32_t tick_value; + if(chart->type == LV_CHART_TYPE_SCATTER) { + tick_value = lv_map(i, 0, total_tick_num, chart->xmin[sec_axis], chart->xmax[sec_axis]); + } + else { + tick_value = i / t->minor_cnt; + } + part_draw_dsc.value = tick_value; + + if(major && t->label_en) { + char buf[LV_CHART_LABEL_MAX_TEXT_LENGTH]; + lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, tick_value); + part_draw_dsc.label_dsc = &label_dsc; + part_draw_dsc.text = buf; + part_draw_dsc.text_length = LV_CHART_LABEL_MAX_TEXT_LENGTH; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + /*reserve appropriate area*/ + lv_point_t size; + lv_txt_get_size(&size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, LV_COORD_MAX, + LV_TEXT_FLAG_NONE); + + /*set the area at some distance of the major tick len under of the tick*/ + lv_area_t a; + a.x1 = (p2.x - size.x / 2); + a.x2 = (p2.x + size.x / 2); + if(sec_axis) { + a.y2 = p2.y - label_gap; + a.y1 = a.y2 - size.y; + } + else { + a.y1 = p2.y + label_gap; + a.y2 = a.y1 + size.y; + } + + if(a.x2 >= obj->coords.x1 && + a.x1 <= obj->coords.x2) { + lv_draw_label(draw_ctx, &label_dsc, &a, part_draw_dsc.text, NULL); + } + } + else { + part_draw_dsc.label_dsc = NULL; + part_draw_dsc.text = NULL; + part_draw_dsc.text_length = 0; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + } + + + if(p1.x + line_dsc.width / 2 >= obj->coords.x1 && + p2.x - line_dsc.width / 2 <= obj->coords.x2) { + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + } + + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } +} + +static void draw_axes(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_Y); + draw_y_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_Y); + draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_PRIMARY_X); + draw_x_ticks(obj, draw_ctx, LV_CHART_AXIS_SECONDARY_X); +} + +/** + * Get the nearest index to an X coordinate + * @param chart pointer to a chart object + * @param coord the coordination of the point relative to the series area. + * @return the found index + */ +static uint32_t get_index_from_x(lv_obj_t * obj, lv_coord_t x) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t pad_left = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + x -= pad_left; + + if(x < 0) return 0; + if(x > w) return chart->point_cnt - 1; + if(chart->type == LV_CHART_TYPE_LINE) return (x * (chart->point_cnt - 1) + w / 2) / w; + if(chart->type == LV_CHART_TYPE_BAR) return (x * chart->point_cnt) / w; + + return 0; +} + +static void invalidate_point(lv_obj_t * obj, uint16_t i) +{ + lv_chart_t * chart = (lv_chart_t *)obj; + if(i >= chart->point_cnt) return; + + lv_coord_t w = ((int32_t)lv_obj_get_content_width(obj) * chart->zoom_x) >> 8; + lv_coord_t scroll_left = lv_obj_get_scroll_left(obj); + + /*In shift mode the whole chart changes so the whole object*/ + if(chart->update_mode == LV_CHART_UPDATE_MODE_SHIFT) { + lv_obj_invalidate(obj); + return; + } + + if(chart->type == LV_CHART_TYPE_LINE) { + lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t pleft = lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + lv_coord_t x_ofs = obj->coords.x1 + pleft + bwidth - scroll_left; + lv_coord_t line_width = lv_obj_get_style_line_width(obj, LV_PART_ITEMS); + lv_coord_t point_w = lv_obj_get_style_width(obj, LV_PART_INDICATOR); + + lv_area_t coords; + lv_area_copy(&coords, &obj->coords); + coords.y1 -= line_width + point_w; + coords.y2 += line_width + point_w; + + if(i < chart->point_cnt - 1) { + coords.x1 = ((w * i) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w; + coords.x2 = ((w * (i + 1)) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w; + lv_obj_invalidate_area(obj, &coords); + } + + if(i > 0) { + coords.x1 = ((w * (i - 1)) / (chart->point_cnt - 1)) + x_ofs - line_width - point_w; + coords.x2 = ((w * i) / (chart->point_cnt - 1)) + x_ofs + line_width + point_w; + lv_obj_invalidate_area(obj, &coords); + } + } + else if(chart->type == LV_CHART_TYPE_BAR) { + lv_area_t col_a; + int32_t block_gap = ((int32_t)lv_obj_get_style_pad_column(obj, + LV_PART_MAIN) * chart->zoom_x) >> 8; /*Gap between the column on ~adjacent X*/ + + lv_coord_t block_w = (w + block_gap) / chart->point_cnt; + + lv_coord_t bwidth = lv_obj_get_style_border_width(obj, LV_PART_MAIN); + lv_coord_t x_act; + x_act = (int32_t)((int32_t)(block_w) * i) ; + x_act += obj->coords.x1 + bwidth + lv_obj_get_style_pad_left(obj, LV_PART_MAIN); + + lv_obj_get_coords(obj, &col_a); + col_a.x1 = x_act - scroll_left; + col_a.x2 = col_a.x1 + block_w; + col_a.x1 -= block_gap; + + lv_obj_invalidate_area(obj, &col_a); + } + else { + lv_obj_invalidate(obj); + } +} + +static void new_points_alloc(lv_obj_t * obj, lv_chart_series_t * ser, uint32_t cnt, lv_coord_t ** a) +{ + if((*a) == NULL) return; + + lv_chart_t * chart = (lv_chart_t *) obj; + uint32_t point_cnt_old = chart->point_cnt; + uint32_t i; + + if(ser->start_point != 0) { + lv_coord_t * new_points = lv_mem_alloc(sizeof(lv_coord_t) * cnt); + LV_ASSERT_MALLOC(new_points); + if(new_points == NULL) return; + + if(cnt >= point_cnt_old) { + for(i = 0; i < point_cnt_old; i++) { + new_points[i] = + (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/ + } + for(i = point_cnt_old; i < cnt; i++) { + new_points[i] = LV_CHART_POINT_NONE; /*Fill up the rest with default value*/ + } + } + else { + for(i = 0; i < cnt; i++) { + new_points[i] = + (*a)[(i + ser->start_point) % point_cnt_old]; /*Copy old contents to new array*/ + } + } + + /*Switch over pointer from old to new*/ + lv_mem_free((*a)); + (*a) = new_points; + } + else { + (*a) = lv_mem_realloc((*a), sizeof(lv_coord_t) * cnt); + LV_ASSERT_MALLOC((*a)); + if((*a) == NULL) return; + /*Initialize the new points*/ + if(cnt > point_cnt_old) { + for(i = point_cnt_old - 1; i < cnt; i++) { + (*a)[i] = LV_CHART_POINT_NONE; + } + } + } +} + +lv_chart_tick_dsc_t * get_tick_gsc(lv_obj_t * obj, lv_chart_axis_t axis) +{ + lv_chart_t * chart = (lv_chart_t *) obj; + switch(axis) { + case LV_CHART_AXIS_PRIMARY_Y: + return &chart->tick[0]; + case LV_CHART_AXIS_PRIMARY_X: + return &chart->tick[1]; + case LV_CHART_AXIS_SECONDARY_Y: + return &chart->tick[2]; + case LV_CHART_AXIS_SECONDARY_X: + return &chart->tick[3]; + default: + return NULL; + } +} + + +#endif diff --git a/lib/lvgl/src/extra/widgets/chart/lv_chart.h b/lib/lvgl/src/extra/widgets/chart/lv_chart.h new file mode 100644 index 00000000..394c0e7b --- /dev/null +++ b/lib/lvgl/src/extra/widgets/chart/lv_chart.h @@ -0,0 +1,460 @@ +/** + * @file lv_chart.h + * + */ + +#ifndef LV_CHART_H +#define LV_CHART_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_CHART != 0 + +/********************* + * DEFINES + *********************/ + +/**Default value of points. Can be used to not draw a point*/ +#if LV_USE_LARGE_COORD +#define LV_CHART_POINT_NONE (INT32_MAX) +#else +#define LV_CHART_POINT_NONE (INT16_MAX) +#endif +LV_EXPORT_CONST_INT(LV_CHART_POINT_NONE); + +/********************** + * TYPEDEFS + **********************/ + +/** + * Chart types + */ +enum { + LV_CHART_TYPE_NONE, /**< Don't draw the series*/ + LV_CHART_TYPE_LINE, /**< Connect the points with lines*/ + LV_CHART_TYPE_BAR, /**< Draw columns*/ + LV_CHART_TYPE_SCATTER, /**< Draw points and lines in 2D (x,y coordinates)*/ +}; +typedef uint8_t lv_chart_type_t; + +/** + * Chart update mode for `lv_chart_set_next` + */ +enum { + LV_CHART_UPDATE_MODE_SHIFT, /**< Shift old data to the left and add the new one the right*/ + LV_CHART_UPDATE_MODE_CIRCULAR, /**< Add the new data in a circular way*/ +}; +typedef uint8_t lv_chart_update_mode_t; + +/** + * Enumeration of the axis' + */ +enum { + LV_CHART_AXIS_PRIMARY_Y = 0x00, + LV_CHART_AXIS_SECONDARY_Y = 0x01, + LV_CHART_AXIS_PRIMARY_X = 0x02, + LV_CHART_AXIS_SECONDARY_X = 0x04, + _LV_CHART_AXIS_LAST +}; +typedef uint8_t lv_chart_axis_t; + +/** + * Descriptor a chart series + */ +typedef struct { + lv_coord_t * x_points; + lv_coord_t * y_points; + lv_color_t color; + uint16_t start_point; + uint8_t hidden : 1; + uint8_t x_ext_buf_assigned : 1; + uint8_t y_ext_buf_assigned : 1; + uint8_t x_axis_sec : 1; + uint8_t y_axis_sec : 1; +} lv_chart_series_t; + +typedef struct { + lv_point_t pos; + lv_coord_t point_id; + lv_color_t color; + lv_chart_series_t * ser; + lv_dir_t dir; + uint8_t pos_set: 1; /*1: pos is set; 0: point_id is set*/ +} lv_chart_cursor_t; + +typedef struct { + lv_coord_t major_len; + lv_coord_t minor_len; + lv_coord_t draw_size; + uint32_t minor_cnt : 15; + uint32_t major_cnt : 15; + uint32_t label_en : 1; +} lv_chart_tick_dsc_t; + + +typedef struct { + lv_obj_t obj; + lv_ll_t series_ll; /**< Linked list for the series (stores lv_chart_series_t)*/ + lv_ll_t cursor_ll; /**< Linked list for the cursors (stores lv_chart_cursor_t)*/ + lv_chart_tick_dsc_t tick[4]; + lv_coord_t ymin[2]; + lv_coord_t ymax[2]; + lv_coord_t xmin[2]; + lv_coord_t xmax[2]; + lv_coord_t pressed_point_id; + uint16_t hdiv_cnt; /**< Number of horizontal division lines*/ + uint16_t vdiv_cnt; /**< Number of vertical division lines*/ + uint16_t point_cnt; /**< Point number in a data line*/ + uint16_t zoom_x; + uint16_t zoom_y; + lv_chart_type_t type : 3; /**< Line or column chart*/ + lv_chart_update_mode_t update_mode : 1; +} lv_chart_t; + +extern const lv_obj_class_t lv_chart_class; + +/** + * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_chart_class` + * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END` + */ +typedef enum { + LV_CHART_DRAW_PART_DIV_LINE_INIT, /**< Used before/after drawn the div lines*/ + LV_CHART_DRAW_PART_DIV_LINE_HOR, /**< Used for each horizontal division lines*/ + LV_CHART_DRAW_PART_DIV_LINE_VER, /**< Used for each vertical division lines*/ + LV_CHART_DRAW_PART_LINE_AND_POINT, /**< Used on line and scatter charts for lines and points*/ + LV_CHART_DRAW_PART_BAR, /**< Used on bar charts for the rectangles*/ + LV_CHART_DRAW_PART_CURSOR, /**< Used on cursor lines and points*/ + LV_CHART_DRAW_PART_TICK_LABEL, /**< Used on tick lines and labels*/ +} lv_chart_draw_part_type_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a chart object + * @param parent pointer to an object, it will be the parent of the new chart + * @return pointer to the created chart + */ +lv_obj_t * lv_chart_create(lv_obj_t * parent); + +/** + * Set a new type for a chart + * @param obj pointer to a chart object + * @param type new type of the chart (from 'lv_chart_type_t' enum) + */ +void lv_chart_set_type(lv_obj_t * obj, lv_chart_type_t type); +/** + * Set the number of points on a data line on a chart + * @param obj pointer to a chart object + * @param cnt new number of points on the data lines + */ +void lv_chart_set_point_count(lv_obj_t * obj, uint16_t cnt); + +/** + * Set the minimal and maximal y values on an axis + * @param obj pointer to a chart object + * @param axis `LV_CHART_AXIS_PRIMARY_Y` or `LV_CHART_AXIS_SECONDARY_Y` + * @param min minimum value of the y axis + * @param max maximum value of the y axis + */ +void lv_chart_set_range(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t min, lv_coord_t max); + +/** + * Set update mode of the chart object. Affects + * @param obj pointer to a chart object + * @param mode the update mode + */ +void lv_chart_set_update_mode(lv_obj_t * obj, lv_chart_update_mode_t update_mode); + +/** + * Set the number of horizontal and vertical division lines + * @param obj pointer to a chart object + * @param hdiv number of horizontal division lines + * @param vdiv number of vertical division lines + */ +void lv_chart_set_div_line_count(lv_obj_t * obj, uint8_t hdiv, uint8_t vdiv); + +/** + * Zoom into the chart in X direction + * @param obj pointer to a chart object + * @param zoom_x zoom in x direction. LV_ZOOM_NONE or 256 for no zoom, 512 double zoom + */ +void lv_chart_set_zoom_x(lv_obj_t * obj, uint16_t zoom_x); + +/** + * Zoom into the chart in Y direction + * @param obj pointer to a chart object + * @param zoom_y zoom in y direction. LV_ZOOM_NONE or 256 for no zoom, 512 double zoom + */ +void lv_chart_set_zoom_y(lv_obj_t * obj, uint16_t zoom_y); + +/** + * Get X zoom of a chart + * @param obj pointer to a chart object + * @return the X zoom value + */ +uint16_t lv_chart_get_zoom_x(const lv_obj_t * obj); + +/** + * Get Y zoom of a chart + * @param obj pointer to a chart object + * @return the Y zoom value + */ +uint16_t lv_chart_get_zoom_y(const lv_obj_t * obj); + +/** + * Set the number of tick lines on an axis + * @param obj pointer to a chart object + * @param axis an axis which ticks count should be set + * @param major_len length of major ticks + * @param minor_len length of minor ticks + * @param major_cnt number of major ticks on the axis + * @param minor_cnt number of minor ticks between two major ticks + * @param label_en true: enable label drawing on major ticks + * @param draw_size extra size required to draw the tick and labels + * (start with 20 px and increase if the ticks/labels are clipped) + */ +void lv_chart_set_axis_tick(lv_obj_t * obj, lv_chart_axis_t axis, lv_coord_t major_len, lv_coord_t minor_len, + lv_coord_t major_cnt, lv_coord_t minor_cnt, bool label_en, lv_coord_t draw_size); + +/** + * Get the type of a chart + * @param obj pointer to chart object + * @return type of the chart (from 'lv_chart_t' enum) + */ +lv_chart_type_t lv_chart_get_type(const lv_obj_t * obj); + +/** + * Get the data point number per data line on chart + * @param chart pointer to chart object + * @return point number on each data line + */ +uint16_t lv_chart_get_point_count(const lv_obj_t * obj); + +/** + * Get the current index of the x-axis start point in the data array + * @param chart pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @return the index of the current x start point in the data array + */ +uint16_t lv_chart_get_x_start_point(const lv_obj_t * obj, lv_chart_series_t * ser); + +/** + * Get the position of a point to the chart. + * @param chart pointer to a chart object + * @param ser pointer to series + * @param id the index. + * @param p_out store the result position here + */ +void lv_chart_get_point_pos_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_point_t * p_out); + +/** + * Refresh a chart if its data line has changed + * @param chart pointer to chart object + */ +void lv_chart_refresh(lv_obj_t * obj); + +/*====================== + * Series + *=====================*/ + +/** + * Allocate and add a data series to the chart + * @param obj pointer to a chart object + * @param color color of the data series + * @param axis the y axis to which the series should be attached (::LV_CHART_AXIS_PRIMARY_Y or ::LV_CHART_AXIS_SECONDARY_Y) + * @return pointer to the allocated data series + */ +lv_chart_series_t * lv_chart_add_series(lv_obj_t * obj, lv_color_t color, lv_chart_axis_t axis); + +/** + * Deallocate and remove a data series from a chart + * @param chart pointer to a chart object + * @param series pointer to a data series on 'chart' + */ +void lv_chart_remove_series(lv_obj_t * obj, lv_chart_series_t * series); + +/** + * Hide/Unhide a single series of a chart. + * @param obj pointer to a chart object. + * @param series pointer to a series object + * @param hide true: hide the series + */ +void lv_chart_hide_series(lv_obj_t * chart, lv_chart_series_t * series, bool hide); + +/** + * Change the color of a series + * @param obj pointer to a chart object. + * @param series pointer to a series object + * @param color the new color of the series + */ +void lv_chart_set_series_color(lv_obj_t * chart, lv_chart_series_t * series, lv_color_t color); + +/** + * Set the index of the x-axis start point in the data array. + * This point will be considers the first (left) point and the other points will be drawn after it. + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @param id the index of the x point in the data array + */ +void lv_chart_set_x_start_point(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id); + +/** + * Get the next series. + * @param chart pointer to a chart + * @param ser the previous series or NULL to get the first + * @return the next series or NULL if there is no more. + */ +lv_chart_series_t * lv_chart_get_series_next(const lv_obj_t * chart, const lv_chart_series_t * ser); + + + +/*===================== + * Cursor + *====================*/ + +/** + * Add a cursor with a given color + * @param obj pointer to chart object + * @param color color of the cursor + * @param dir direction of the cursor. `LV_DIR_RIGHT/LEFT/TOP/DOWN/HOR/VER/ALL`. OR-ed values are possible + * @return pointer to the created cursor + */ +lv_chart_cursor_t * lv_chart_add_cursor(lv_obj_t * obj, lv_color_t color, lv_dir_t dir); + +/** + * Set the coordinate of the cursor with respect to the paddings + * @param obj pointer to a chart object + * @param cursor pointer to the cursor + * @param pos the new coordinate of cursor relative to the chart + */ +void lv_chart_set_cursor_pos(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_point_t * pos); + +/** + * Stick the cursor to a point + * @param obj pointer to a chart object + * @param cursor pointer to the cursor + * @param ser pointer to a series + * @param point_id the point's index or `LV_CHART_POINT_NONE` to not assign to any points. + */ +void lv_chart_set_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor, lv_chart_series_t * ser, + uint16_t point_id); + +/** + * Get the coordinate of the cursor with respect to the paddings + * @param obj pointer to a chart object + * @param cursor pointer to cursor + * @return coordinate of the cursor as lv_point_t + */ +lv_point_t lv_chart_get_cursor_point(lv_obj_t * chart, lv_chart_cursor_t * cursor); + +/*===================== + * Set/Get value(s) + *====================*/ + +/** + * Initialize all data points of a series with a value + * @param obj pointer to chart object + * @param ser pointer to a data series on 'chart' + * @param value the new value for all points. `LV_CHART_POINT_NONE` can be used to hide the points. + */ +void lv_chart_set_all_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value); + +/** + * Set the next point's Y value according to the update mode policy. + * @param obj pointer to chart object + * @param ser pointer to a data series on 'chart' + * @param value the new value of the next data + */ +void lv_chart_set_next_value(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t value); + +/** + * Set the next point's X and Y value according to the update mode policy. + * @param obj pointer to chart object + * @param ser pointer to a data series on 'chart' + * @param x_value the new X value of the next data + * @param y_value the new Y value of the next data + */ +void lv_chart_set_next_value2(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t x_value, lv_coord_t y_value); + +/** + * Set an individual point's y value of a chart's series directly based on its index + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @param id the index of the x point in the array + * @param value value to assign to array point + */ +void lv_chart_set_value_by_id(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t value); + +/** + * Set an individual point's x and y value of a chart's series directly based on its index + * Can be used only with `LV_CHART_TYPE_SCATTER`. + * @param obj pointer to chart object + * @param ser pointer to a data series on 'chart' + * @param id the index of the x point in the array + * @param x_value the new X value of the next data + * @param y_value the new Y value of the next data + */ +void lv_chart_set_value_by_id2(lv_obj_t * obj, lv_chart_series_t * ser, uint16_t id, lv_coord_t x_value, + lv_coord_t y_value); + +/** + * Set an external array for the y data points to use for the chart + * NOTE: It is the users responsibility to make sure the `point_cnt` matches the external array size. + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @param array external array of points for chart + */ +void lv_chart_set_ext_y_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[]); + +/** + * Set an external array for the x data points to use for the chart + * NOTE: It is the users responsibility to make sure the `point_cnt` matches the external array size. + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @param array external array of points for chart + */ +void lv_chart_set_ext_x_array(lv_obj_t * obj, lv_chart_series_t * ser, lv_coord_t array[]); + +/** + * Get the array of y values of a series + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @return the array of values with 'point_count' elements + */ +lv_coord_t * lv_chart_get_y_array(const lv_obj_t * obj, lv_chart_series_t * ser); + +/** + * Get the array of x values of a series + * @param obj pointer to a chart object + * @param ser pointer to a data series on 'chart' + * @return the array of values with 'point_count' elements + */ +lv_coord_t * lv_chart_get_x_array(const lv_obj_t * obj, lv_chart_series_t * ser); + +/** + * Get the index of the currently pressed point. It's the same for every series. + * @param obj pointer to a chart object + * @return the index of the point [0 .. point count] or LV_CHART_POINT_ID_NONE if no point is being pressed + */ +uint32_t lv_chart_get_pressed_point(const lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_CHART*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_CHART_H*/ diff --git a/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.c b/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.c new file mode 100644 index 00000000..daf112e9 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.c @@ -0,0 +1,713 @@ +/** + * @file lv_colorwheel.c + * + * Based on the work of @AloyseTech and @paulpv. + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_colorwheel.h" +#if LV_USE_COLORWHEEL + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_colorwheel_class + +#define LV_CPICKER_DEF_QF 3 + +/** + * The OUTER_MASK_WIDTH define is required to assist with the placing of a mask over the outer ring of the widget as when the + * multicoloured radial lines are calculated for the outer ring of the widget their lengths are jittering because of the + * integer based arithmetic. From tests the maximum delta was found to be 2 so the current value is set to 3 to achieve + * appropriate masking. + */ +#define OUTER_MASK_WIDTH 3 + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_colorwheel_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_colorwheel_event(const lv_obj_class_t * class_p, lv_event_t * e); + +static void draw_disc_grad(lv_event_t * e); +static void draw_knob(lv_event_t * e); +static void invalidate_knob(lv_obj_t * obj); +static lv_area_t get_knob_area(lv_obj_t * obj); + +static void next_color_mode(lv_obj_t * obj); +static lv_res_t double_click_reset(lv_obj_t * obj); +static void refr_knob_pos(lv_obj_t * obj); +static lv_color_t angle_to_mode_color_fast(lv_obj_t * obj, uint16_t angle); +static uint16_t get_angle(lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_colorwheel_class = {.instance_size = sizeof(lv_colorwheel_t), .base_class = &lv_obj_class, + .constructor_cb = lv_colorwheel_constructor, + .event_cb = lv_colorwheel_event, + .width_def = LV_DPI_DEF * 2, + .height_def = LV_DPI_DEF * 2, + .editable = LV_OBJ_CLASS_EDITABLE_TRUE, + }; + +static bool create_knob_recolor; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create a color_picker object + * @param parent pointer to an object, it will be the parent of the new color_picker + * @return pointer to the created color_picker + */ +lv_obj_t * lv_colorwheel_create(lv_obj_t * parent, bool knob_recolor) +{ + LV_LOG_INFO("begin"); + create_knob_recolor = knob_recolor; + + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the current hsv of a color wheel. + * @param colorwheel pointer to color wheel object + * @param color current selected hsv + * @return true if changed, otherwise false + */ +bool lv_colorwheel_set_hsv(lv_obj_t * obj, lv_color_hsv_t hsv) +{ + if(hsv.h > 360) hsv.h %= 360; + if(hsv.s > 100) hsv.s = 100; + if(hsv.v > 100) hsv.v = 100; + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + if(colorwheel->hsv.h == hsv.h && colorwheel->hsv.s == hsv.s && colorwheel->hsv.v == hsv.v) return false; + + colorwheel->hsv = hsv; + + refr_knob_pos(obj); + + lv_obj_invalidate(obj); + + return true; +} + +/** + * Set the current color of a color wheel. + * @param colorwheel pointer to color wheel object + * @param color current selected color + * @return true if changed, otherwise false + */ +bool lv_colorwheel_set_rgb(lv_obj_t * obj, lv_color_t color) +{ + lv_color32_t c32; + c32.full = lv_color_to32(color); + + return lv_colorwheel_set_hsv(obj, lv_color_rgb_to_hsv(c32.ch.red, c32.ch.green, c32.ch.blue)); +} + +/** + * Set the current color mode. + * @param colorwheel pointer to color wheel object + * @param mode color mode (hue/sat/val) + */ +void lv_colorwheel_set_mode(lv_obj_t * obj, lv_colorwheel_mode_t mode) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + colorwheel->mode = mode; + refr_knob_pos(obj); + lv_obj_invalidate(obj); +} + +/** + * Set if the color mode is changed on long press on center + * @param colorwheel pointer to color wheel object + * @param fixed color mode cannot be changed on long press + */ +void lv_colorwheel_set_mode_fixed(lv_obj_t * obj, bool fixed) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + colorwheel->mode_fixed = fixed; +} + +/*===================== + * Getter functions + *====================*/ + + +/** + * Get the current selected hsv of a color wheel. + * @param colorwheel pointer to color wheel object + * @return current selected hsv + */ +lv_color_hsv_t lv_colorwheel_get_hsv(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + return colorwheel->hsv; +} + +/** + * Get the current selected color of a color wheel. + * @param colorwheel pointer to color wheel object + * @return color current selected color + */ +lv_color_t lv_colorwheel_get_rgb(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + return lv_color_hsv_to_rgb(colorwheel->hsv.h, colorwheel->hsv.s, colorwheel->hsv.v); +} + +/** + * Get the current color mode. + * @param colorwheel pointer to color wheel object + * @return color mode (hue/sat/val) + */ +lv_colorwheel_mode_t lv_colorwheel_get_color_mode(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + return colorwheel->mode; +} + +/** + * Get if the color mode is changed on long press on center + * @param colorwheel pointer to color wheel object + * @return mode cannot be changed on long press + */ +bool lv_colorwheel_get_color_mode_fixed(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + return colorwheel->mode_fixed; +} + +/*===================== + * Other functions + *====================*/ + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_colorwheel_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + colorwheel->hsv.h = 0; + colorwheel->hsv.s = 100; + colorwheel->hsv.v = 100; + colorwheel->mode = LV_COLORWHEEL_MODE_HUE; + colorwheel->mode_fixed = 0; + colorwheel->last_click_time = 0; + colorwheel->last_change_time = 0; + colorwheel->knob.recolor = create_knob_recolor; + + lv_obj_add_flag(obj, LV_OBJ_FLAG_ADV_HITTEST); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLL_CHAIN); + refr_knob_pos(obj); +} + +static void draw_disc_grad(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + lv_coord_t w = lv_obj_get_width(obj); + lv_coord_t h = lv_obj_get_height(obj); + lv_coord_t cx = obj->coords.x1 + w / 2; + lv_coord_t cy = obj->coords.y1 + h / 2; + lv_coord_t r = w / 2; + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_MAIN, &line_dsc); + + line_dsc.width = (r * 628 / (256 / LV_CPICKER_DEF_QF)) / 100; + line_dsc.width += 2; + uint16_t i; + uint32_t a = 0; + lv_coord_t cir_w = lv_obj_get_style_arc_width(obj, LV_PART_MAIN); + +#if LV_DRAW_COMPLEX + /*Mask outer and inner ring of widget to tidy up ragged edges of lines while drawing outer ring*/ + lv_draw_mask_radius_param_t mask_out_param; + lv_draw_mask_radius_init(&mask_out_param, &obj->coords, LV_RADIUS_CIRCLE, false); + int16_t mask_out_id = lv_draw_mask_add(&mask_out_param, 0); + + lv_area_t mask_area; + lv_area_copy(&mask_area, &obj->coords); + mask_area.x1 += cir_w; + mask_area.x2 -= cir_w; + mask_area.y1 += cir_w; + mask_area.y2 -= cir_w; + lv_draw_mask_radius_param_t mask_in_param; + lv_draw_mask_radius_init(&mask_in_param, &mask_area, LV_RADIUS_CIRCLE, true); + int16_t mask_in_id = lv_draw_mask_add(&mask_in_param, 0); + + /*The inner and outer line ends will be masked out. + *So make lines a little bit longer because the masking makes a more even result*/ + lv_coord_t cir_w_extra = line_dsc.width; +#else + lv_coord_t cir_w_extra = 0; +#endif + + for(i = 0; i <= 256; i += LV_CPICKER_DEF_QF, a += 360 * LV_CPICKER_DEF_QF) { + line_dsc.color = angle_to_mode_color_fast(obj, i); + uint16_t angle_trigo = (uint16_t)(a >> 8); /*i * 360 / 256 is the scale to apply, but we can skip multiplication here*/ + + lv_point_t p[2]; + p[0].x = cx + ((r + cir_w_extra) * lv_trigo_sin(angle_trigo) >> LV_TRIGO_SHIFT); + p[0].y = cy + ((r + cir_w_extra) * lv_trigo_cos(angle_trigo) >> LV_TRIGO_SHIFT); + p[1].x = cx + ((r - cir_w - cir_w_extra) * lv_trigo_sin(angle_trigo) >> LV_TRIGO_SHIFT); + p[1].y = cy + ((r - cir_w - cir_w_extra) * lv_trigo_cos(angle_trigo) >> LV_TRIGO_SHIFT); + + lv_draw_line(draw_ctx, &line_dsc, &p[0], &p[1]); + } + +#if LV_DRAW_COMPLEX + lv_draw_mask_free_param(&mask_out_param); + lv_draw_mask_free_param(&mask_in_param); + lv_draw_mask_remove_id(mask_out_id); + lv_draw_mask_remove_id(mask_in_id); +#endif +} + +static void draw_knob(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + lv_draw_rect_dsc_t cir_dsc; + lv_draw_rect_dsc_init(&cir_dsc); + lv_obj_init_draw_rect_dsc(obj, LV_PART_KNOB, &cir_dsc); + + cir_dsc.radius = LV_RADIUS_CIRCLE; + + if(colorwheel->knob.recolor) { + cir_dsc.bg_color = lv_colorwheel_get_rgb(obj); + } + + lv_area_t knob_area = get_knob_area(obj); + + lv_draw_rect(draw_ctx, &cir_dsc, &knob_area); +} + +static void invalidate_knob(lv_obj_t * obj) +{ + lv_area_t knob_area = get_knob_area(obj); + + lv_obj_invalidate_area(obj, &knob_area); +} + +static lv_area_t get_knob_area(lv_obj_t * obj) +{ + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + /*Get knob's radius*/ + uint16_t r = 0; + r = lv_obj_get_style_arc_width(obj, LV_PART_MAIN) / 2; + + lv_coord_t left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB); + lv_coord_t right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB); + lv_coord_t top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB); + lv_coord_t bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB); + + lv_area_t knob_area; + knob_area.x1 = obj->coords.x1 + colorwheel->knob.pos.x - r - left; + knob_area.y1 = obj->coords.y1 + colorwheel->knob.pos.y - r - right; + knob_area.x2 = obj->coords.x1 + colorwheel->knob.pos.x + r + top; + knob_area.y2 = obj->coords.y1 + colorwheel->knob.pos.y + r + bottom; + + return knob_area; +} + +static void lv_colorwheel_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + /*Call the ancestor's event handler*/ + lv_res_t res = lv_obj_event_base(MY_CLASS, e); + + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + + if(code == LV_EVENT_REFR_EXT_DRAW_SIZE) { + lv_coord_t left = lv_obj_get_style_pad_left(obj, LV_PART_KNOB); + lv_coord_t right = lv_obj_get_style_pad_right(obj, LV_PART_KNOB); + lv_coord_t top = lv_obj_get_style_pad_top(obj, LV_PART_KNOB); + lv_coord_t bottom = lv_obj_get_style_pad_bottom(obj, LV_PART_KNOB); + + lv_coord_t knob_pad = LV_MAX4(left, right, top, bottom) + 2; + lv_coord_t * s = lv_event_get_param(e); + *s = LV_MAX(*s, knob_pad); + } + else if(code == LV_EVENT_SIZE_CHANGED) { + void * param = lv_event_get_param(e); + /*Refresh extended draw area to make knob visible*/ + if(lv_obj_get_width(obj) != lv_area_get_width(param) || + lv_obj_get_height(obj) != lv_area_get_height(param)) { + refr_knob_pos(obj); + } + } + else if(code == LV_EVENT_STYLE_CHANGED) { + /*Refresh extended draw area to make knob visible*/ + refr_knob_pos(obj); + } + else if(code == LV_EVENT_KEY) { + uint32_t c = *((uint32_t *)lv_event_get_param(e)); /*uint32_t because can be UTF-8*/ + + if(c == LV_KEY_RIGHT || c == LV_KEY_UP) { + lv_color_hsv_t hsv_cur; + hsv_cur = colorwheel->hsv; + + switch(colorwheel->mode) { + case LV_COLORWHEEL_MODE_HUE: + hsv_cur.h = (colorwheel->hsv.h + 1) % 360; + break; + case LV_COLORWHEEL_MODE_SATURATION: + hsv_cur.s = (colorwheel->hsv.s + 1) % 100; + break; + case LV_COLORWHEEL_MODE_VALUE: + hsv_cur.v = (colorwheel->hsv.v + 1) % 100; + break; + } + + if(lv_colorwheel_set_hsv(obj, hsv_cur)) { + res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + if(res != LV_RES_OK) return; + } + } + else if(c == LV_KEY_LEFT || c == LV_KEY_DOWN) { + lv_color_hsv_t hsv_cur; + hsv_cur = colorwheel->hsv; + + switch(colorwheel->mode) { + case LV_COLORWHEEL_MODE_HUE: + hsv_cur.h = colorwheel->hsv.h > 0 ? (colorwheel->hsv.h - 1) : 360; + break; + case LV_COLORWHEEL_MODE_SATURATION: + hsv_cur.s = colorwheel->hsv.s > 0 ? (colorwheel->hsv.s - 1) : 100; + break; + case LV_COLORWHEEL_MODE_VALUE: + hsv_cur.v = colorwheel->hsv.v > 0 ? (colorwheel->hsv.v - 1) : 100; + break; + } + + if(lv_colorwheel_set_hsv(obj, hsv_cur)) { + res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + if(res != LV_RES_OK) return; + } + } + } + else if(code == LV_EVENT_PRESSED) { + colorwheel->last_change_time = lv_tick_get(); + lv_indev_get_point(lv_indev_get_act(), &colorwheel->last_press_point); + res = double_click_reset(obj); + if(res != LV_RES_OK) return; + } + else if(code == LV_EVENT_PRESSING) { + lv_indev_t * indev = lv_indev_get_act(); + if(indev == NULL) return; + + lv_indev_type_t indev_type = lv_indev_get_type(indev); + lv_point_t p; + if(indev_type == LV_INDEV_TYPE_ENCODER || indev_type == LV_INDEV_TYPE_KEYPAD) { + p.x = obj->coords.x1 + lv_obj_get_width(obj) / 2; + p.y = obj->coords.y1 + lv_obj_get_height(obj) / 2; + } + else { + lv_indev_get_point(indev, &p); + } + + lv_coord_t drag_limit = indev->driver->scroll_limit; + if((LV_ABS(p.x - colorwheel->last_press_point.x) > drag_limit) || + (LV_ABS(p.y - colorwheel->last_press_point.y) > drag_limit)) { + colorwheel->last_change_time = lv_tick_get(); + colorwheel->last_press_point.x = p.x; + colorwheel->last_press_point.y = p.y; + } + + p.x -= obj->coords.x1; + p.y -= obj->coords.y1; + + /*Ignore pressing in the inner area*/ + uint16_t w = lv_obj_get_width(obj); + + int16_t angle = 0; + lv_coord_t cir_w = lv_obj_get_style_arc_width(obj, LV_PART_MAIN); + + lv_coord_t r_in = w / 2; + p.x -= r_in; + p.y -= r_in; + bool on_ring = true; + r_in -= cir_w; + if(r_in > LV_DPI_DEF / 2) { + lv_coord_t inner = cir_w / 2; + r_in -= inner; + + if(r_in < LV_DPI_DEF / 2) r_in = LV_DPI_DEF / 2; + } + + if(p.x * p.x + p.y * p.y < r_in * r_in) { + on_ring = false; + } + + /*If the inner area is being pressed, go to the next color mode on long press*/ + uint32_t diff = lv_tick_elaps(colorwheel->last_change_time); + if(!on_ring && diff > indev->driver->long_press_time && !colorwheel->mode_fixed) { + next_color_mode(obj); + lv_indev_wait_release(lv_indev_get_act()); + return; + } + + /*Set the angle only if pressed on the ring*/ + if(!on_ring) return; + + angle = lv_atan2(p.x, p.y) % 360; + + lv_color_hsv_t hsv_cur; + hsv_cur = colorwheel->hsv; + + switch(colorwheel->mode) { + case LV_COLORWHEEL_MODE_HUE: + hsv_cur.h = angle; + break; + case LV_COLORWHEEL_MODE_SATURATION: + hsv_cur.s = (angle * 100) / 360; + break; + case LV_COLORWHEEL_MODE_VALUE: + hsv_cur.v = (angle * 100) / 360; + break; + } + + if(lv_colorwheel_set_hsv(obj, hsv_cur)) { + res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + if(res != LV_RES_OK) return; + } + } + else if(code == LV_EVENT_HIT_TEST) { + lv_hit_test_info_t * info = lv_event_get_param(e);; + + /*Valid clicks can be only in the circle*/ + info->res = _lv_area_is_point_on(&obj->coords, info->point, LV_RADIUS_CIRCLE); + } + else if(code == LV_EVENT_DRAW_MAIN) { + draw_disc_grad(e); + draw_knob(e); + } + else if(code == LV_EVENT_COVER_CHECK) { + lv_cover_check_info_t * info = lv_event_get_param(e); + if(info->res != LV_COVER_RES_MASKED) info->res = LV_COVER_RES_NOT_COVER; + } +} + + + +static void next_color_mode(lv_obj_t * obj) +{ + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + colorwheel->mode = (colorwheel->mode + 1) % 3; + refr_knob_pos(obj); + lv_obj_invalidate(obj); +} + +static void refr_knob_pos(lv_obj_t * obj) +{ + invalidate_knob(obj); + + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + lv_coord_t w = lv_obj_get_width(obj); + + lv_coord_t scale_w = lv_obj_get_style_arc_width(obj, LV_PART_MAIN); + lv_coord_t r = (w - scale_w) / 2; + uint16_t angle = get_angle(obj); + colorwheel->knob.pos.x = (((int32_t)r * lv_trigo_sin(angle)) >> LV_TRIGO_SHIFT); + colorwheel->knob.pos.y = (((int32_t)r * lv_trigo_cos(angle)) >> LV_TRIGO_SHIFT); + colorwheel->knob.pos.x = colorwheel->knob.pos.x + w / 2; + colorwheel->knob.pos.y = colorwheel->knob.pos.y + w / 2; + + invalidate_knob(obj); +} + +static lv_res_t double_click_reset(lv_obj_t * obj) +{ + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + lv_indev_t * indev = lv_indev_get_act(); + /*Double clicked? Use long press time as double click time out*/ + if(lv_tick_elaps(colorwheel->last_click_time) < indev->driver->long_press_time) { + lv_color_hsv_t hsv_cur; + hsv_cur = colorwheel->hsv; + + switch(colorwheel->mode) { + case LV_COLORWHEEL_MODE_HUE: + hsv_cur.h = 0; + break; + case LV_COLORWHEEL_MODE_SATURATION: + hsv_cur.s = 100; + break; + case LV_COLORWHEEL_MODE_VALUE: + hsv_cur.v = 100; + break; + } + + lv_indev_wait_release(indev); + + if(lv_colorwheel_set_hsv(obj, hsv_cur)) { + lv_res_t res = lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + if(res != LV_RES_OK) return res; + } + } + colorwheel->last_click_time = lv_tick_get(); + + return LV_RES_OK; +} + +#define SWAPPTR(A, B) do { uint8_t * t = A; A = B; B = t; } while(0) +#define HSV_PTR_SWAP(sextant,r,g,b) if((sextant) & 2) { SWAPPTR((r), (b)); } if((sextant) & 4) { SWAPPTR((g), (b)); } if(!((sextant) & 6)) { \ + if(!((sextant) & 1)) { SWAPPTR((r), (g)); } } else { if((sextant) & 1) { SWAPPTR((r), (g)); } } + +/** + * Based on the idea from https://www.vagrearg.org/content/hsvrgb + * Here we want to compute an approximate RGB value from a HSV input color space. We don't want to be accurate + * (for that, there's lv_color_hsv_to_rgb), but we want to be fast. + * + * Few tricks are used here: Hue is in range [0; 6 * 256] (so that the sextant is in the high byte and the fractional part is in the low byte) + * both s and v are in [0; 255] range (very convenient to avoid divisions). + * + * We fold all symmetry by swapping the R, G, B pointers so that the code is the same for all sextants. + * We replace division by 255 by a division by 256, a.k.a a shift right by 8 bits. + * This is wrong, but since this is only used to compute the pixels on the screen and not the final color, it's ok. + */ +static void fast_hsv2rgb(uint16_t h, uint8_t s, uint8_t v, uint8_t * r, uint8_t * g, uint8_t * b) +{ + if(!s) { + *r = *g = *b = v; + return; + } + + uint8_t sextant = h >> 8; + HSV_PTR_SWAP(sextant, r, g, b); /*Swap pointers so the conversion code is the same*/ + + *g = v; + + uint8_t bb = ~s; + uint16_t ww = v * bb; /*Don't try to be precise, but instead, be fast*/ + *b = ww >> 8; + + uint8_t h_frac = h & 0xff; + + if(!(sextant & 1)) { + /*Up slope*/ + ww = !h_frac ? ((uint16_t)s << 8) : (s * (uint8_t)(-h_frac)); /*Skip multiply if not required*/ + } + else { + /*Down slope*/ + ww = s * h_frac; + } + bb = ww >> 8; + bb = ~bb; + ww = v * bb; + *r = ww >> 8; +} + +static lv_color_t angle_to_mode_color_fast(lv_obj_t * obj, uint16_t angle) +{ + lv_colorwheel_t * ext = (lv_colorwheel_t *)obj; + uint8_t r = 0, g = 0, b = 0; + static uint16_t h = 0; + static uint8_t s = 0, v = 0, m = 255; + static uint16_t angle_saved = 0xffff; + + /*If the angle is different recalculate scaling*/ + if(angle_saved != angle) m = 255; + angle_saved = angle; + + switch(ext->mode) { + default: + case LV_COLORWHEEL_MODE_HUE: + /*Don't recompute costly scaling if it does not change*/ + if(m != ext->mode) { + s = (uint8_t)(((uint16_t)ext->hsv.s * 51) / 20); + v = (uint8_t)(((uint16_t)ext->hsv.v * 51) / 20); + m = ext->mode; + } + fast_hsv2rgb(angle * 6, s, v, &r, &g, + &b); /*A smart compiler will replace x * 6 by (x << 2) + (x << 1) if it's more efficient*/ + break; + case LV_COLORWHEEL_MODE_SATURATION: + /*Don't recompute costly scaling if it does not change*/ + if(m != ext->mode) { + h = (uint16_t)(((uint32_t)ext->hsv.h * 6 * 256) / 360); + v = (uint8_t)(((uint16_t)ext->hsv.v * 51) / 20); + m = ext->mode; + } + fast_hsv2rgb(h, angle, v, &r, &g, &b); + break; + case LV_COLORWHEEL_MODE_VALUE: + /*Don't recompute costly scaling if it does not change*/ + if(m != ext->mode) { + h = (uint16_t)(((uint32_t)ext->hsv.h * 6 * 256) / 360); + s = (uint8_t)(((uint16_t)ext->hsv.s * 51) / 20); + m = ext->mode; + } + fast_hsv2rgb(h, s, angle, &r, &g, &b); + break; + } + return lv_color_make(r, g, b); +} + +static uint16_t get_angle(lv_obj_t * obj) +{ + lv_colorwheel_t * colorwheel = (lv_colorwheel_t *)obj; + uint16_t angle; + switch(colorwheel->mode) { + default: + case LV_COLORWHEEL_MODE_HUE: + angle = colorwheel->hsv.h; + break; + case LV_COLORWHEEL_MODE_SATURATION: + angle = (colorwheel->hsv.s * 360) / 100; + break; + case LV_COLORWHEEL_MODE_VALUE: + angle = (colorwheel->hsv.v * 360) / 100 ; + break; + } + return angle; +} + +#endif /*LV_USE_COLORWHEEL*/ diff --git a/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.h b/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.h new file mode 100644 index 00000000..e9c9d92e --- /dev/null +++ b/lib/lvgl/src/extra/widgets/colorwheel/lv_colorwheel.h @@ -0,0 +1,142 @@ +/** + * @file lv_colorwheel.h + * + */ + +#ifndef LV_COLORWHEEL_H +#define LV_COLORWHEEL_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_COLORWHEEL + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +enum { + LV_COLORWHEEL_MODE_HUE, + LV_COLORWHEEL_MODE_SATURATION, + LV_COLORWHEEL_MODE_VALUE +}; +typedef uint8_t lv_colorwheel_mode_t; + + +/*Data of color picker*/ +typedef struct { + lv_obj_t obj; + lv_color_hsv_t hsv; + struct { + lv_point_t pos; + uint8_t recolor : 1; + } knob; + uint32_t last_click_time; + uint32_t last_change_time; + lv_point_t last_press_point; + lv_colorwheel_mode_t mode : 2; + uint8_t mode_fixed : 1; +} lv_colorwheel_t; + +extern const lv_obj_class_t lv_colorwheel_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a color picker object with disc shape + * @param parent pointer to an object, it will be the parent of the new color picker + * @param knob_recolor true: set the knob's color to the current color + * @return pointer to the created color picker + */ +lv_obj_t * lv_colorwheel_create(lv_obj_t * parent, bool knob_recolor); + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the current hsv of a color wheel. + * @param colorwheel pointer to color wheel object + * @param color current selected hsv + * @return true if changed, otherwise false + */ +bool lv_colorwheel_set_hsv(lv_obj_t * obj, lv_color_hsv_t hsv); + +/** + * Set the current color of a color wheel. + * @param colorwheel pointer to color wheel object + * @param color current selected color + * @return true if changed, otherwise false + */ +bool lv_colorwheel_set_rgb(lv_obj_t * obj, lv_color_t color); + +/** + * Set the current color mode. + * @param colorwheel pointer to color wheel object + * @param mode color mode (hue/sat/val) + */ +void lv_colorwheel_set_mode(lv_obj_t * obj, lv_colorwheel_mode_t mode); + +/** + * Set if the color mode is changed on long press on center + * @param colorwheel pointer to color wheel object + * @param fixed color mode cannot be changed on long press + */ +void lv_colorwheel_set_mode_fixed(lv_obj_t * obj, bool fixed); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get the current selected hsv of a color wheel. + * @param colorwheel pointer to color wheel object + * @return current selected hsv + */ +lv_color_hsv_t lv_colorwheel_get_hsv(lv_obj_t * obj); + +/** + * Get the current selected color of a color wheel. + * @param colorwheel pointer to color wheel object + * @return color current selected color + */ +lv_color_t lv_colorwheel_get_rgb(lv_obj_t * obj); + +/** + * Get the current color mode. + * @param colorwheel pointer to color wheel object + * @return color mode (hue/sat/val) + */ +lv_colorwheel_mode_t lv_colorwheel_get_color_mode(lv_obj_t * obj); + +/** + * Get if the color mode is changed on long press on center + * @param colorwheel pointer to color wheel object + * @return mode cannot be changed on long press + */ +bool lv_colorwheel_get_color_mode_fixed(lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_COLORWHEEL*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_COLORWHEEL_H*/ + diff --git a/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.c b/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.c new file mode 100644 index 00000000..00c3011c --- /dev/null +++ b/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.c @@ -0,0 +1,377 @@ +/** + * @file lv_imgbtn.c + * + */ + +/********************* + * INCLUDES + *********************/ + +#include "lv_imgbtn.h" + +#if LV_USE_IMGBTN != 0 + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_imgbtn_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_imgbtn_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void draw_main(lv_event_t * e); +static void lv_imgbtn_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void refr_img(lv_obj_t * imgbtn); +static lv_imgbtn_state_t suggest_state(lv_obj_t * imgbtn, lv_imgbtn_state_t state); +lv_imgbtn_state_t get_state(const lv_obj_t * imgbtn); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_imgbtn_class = { + .base_class = &lv_obj_class, + .instance_size = sizeof(lv_imgbtn_t), + .constructor_cb = lv_imgbtn_constructor, + .event_cb = lv_imgbtn_event, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create an image button object + * @param parent pointer to an object, it will be the parent of the new image button + * @return pointer to the created image button + */ +lv_obj_t * lv_imgbtn_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +/** + * Set images for a state of the image button + * @param obj pointer to an image button object + * @param state for which state set the new image + * @param src_left pointer to an image source for the left side of the button (a C array or path to + * a file) + * @param src_mid pointer to an image source for the middle of the button (ideally 1px wide) (a C + * array or path to a file) + * @param src_right pointer to an image source for the right side of the button (a C array or path + * to a file) + */ +void lv_imgbtn_set_src(lv_obj_t * obj, lv_imgbtn_state_t state, const void * src_left, const void * src_mid, + const void * src_right) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + + imgbtn->img_src_left[state] = src_left; + imgbtn->img_src_mid[state] = src_mid; + imgbtn->img_src_right[state] = src_right; + + refr_img(obj); +} + +void lv_imgbtn_set_state(lv_obj_t * obj, lv_imgbtn_state_t state) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_state_t obj_state = LV_STATE_DEFAULT; + if(state == LV_IMGBTN_STATE_PRESSED || state == LV_IMGBTN_STATE_CHECKED_PRESSED) obj_state |= LV_STATE_PRESSED; + if(state == LV_IMGBTN_STATE_DISABLED || state == LV_IMGBTN_STATE_CHECKED_DISABLED) obj_state |= LV_STATE_DISABLED; + if(state == LV_IMGBTN_STATE_CHECKED_DISABLED || state == LV_IMGBTN_STATE_CHECKED_PRESSED || + state == LV_IMGBTN_STATE_CHECKED_RELEASED) { + obj_state |= LV_STATE_CHECKED; + } + + lv_obj_clear_state(obj, LV_STATE_CHECKED | LV_STATE_PRESSED | LV_STATE_DISABLED); + lv_obj_add_state(obj, obj_state); + + refr_img(obj); +} + +/*===================== + * Getter functions + *====================*/ + + +/** + * Get the left image in a given state + * @param obj pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the left image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_left(lv_obj_t * obj, lv_imgbtn_state_t state) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + + return imgbtn->img_src_left[state]; +} + +/** + * Get the middle image in a given state + * @param obj pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the middle image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_middle(lv_obj_t * obj, lv_imgbtn_state_t state) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + + return imgbtn->img_src_mid[state]; +} + +/** + * Get the right image in a given state + * @param obj pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the left image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_right(lv_obj_t * obj, lv_imgbtn_state_t state) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + + return imgbtn->img_src_right[state]; +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_imgbtn_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + /*Initialize the allocated 'ext'*/ + lv_memset_00((void *)imgbtn->img_src_mid, sizeof(imgbtn->img_src_mid)); + lv_memset_00(imgbtn->img_src_left, sizeof(imgbtn->img_src_left)); + lv_memset_00(imgbtn->img_src_right, sizeof(imgbtn->img_src_right)); + + imgbtn->act_cf = LV_IMG_CF_UNKNOWN; +} + + +static void lv_imgbtn_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + lv_res_t res = lv_obj_event_base(&lv_imgbtn_class, e); + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + if(code == LV_EVENT_PRESSED || code == LV_EVENT_RELEASED || code == LV_EVENT_PRESS_LOST) { + refr_img(obj); + } + else if(code == LV_EVENT_DRAW_MAIN) { + draw_main(e); + } + else if(code == LV_EVENT_COVER_CHECK) { + lv_cover_check_info_t * info = lv_event_get_param(e); + if(info->res != LV_COVER_RES_MASKED) info->res = LV_COVER_RES_NOT_COVER; + } + else if(code == LV_EVENT_GET_SELF_SIZE) { + lv_point_t * p = lv_event_get_self_size_info(e); + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + lv_imgbtn_state_t state = suggest_state(obj, get_state(obj)); + if(imgbtn->img_src_left[state] == NULL && + imgbtn->img_src_mid[state] != NULL && + imgbtn->img_src_right[state] == NULL) { + lv_img_header_t header; + lv_img_decoder_get_info(imgbtn->img_src_mid[state], &header); + p->x = LV_MAX(p->x, header.w); + } + } +} + +static void draw_main(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + + /*Just draw_main an image*/ + lv_imgbtn_state_t state = suggest_state(obj, get_state(obj)); + + /*Simply draw the middle src if no tiled*/ + const void * src = imgbtn->img_src_left[state]; + + lv_coord_t tw = lv_obj_get_style_transform_width(obj, LV_PART_MAIN); + lv_coord_t th = lv_obj_get_style_transform_height(obj, LV_PART_MAIN); + lv_area_t coords; + lv_area_copy(&coords, &obj->coords); + coords.x1 -= tw; + coords.x2 += tw; + coords.y1 -= th; + coords.y2 += th; + + lv_draw_img_dsc_t img_dsc; + lv_draw_img_dsc_init(&img_dsc); + lv_obj_init_draw_img_dsc(obj, LV_PART_MAIN, &img_dsc); + + lv_img_header_t header; + lv_area_t coords_part; + lv_coord_t left_w = 0; + lv_coord_t right_w = 0; + + if(src) { + lv_img_decoder_get_info(src, &header); + left_w = header.w; + coords_part.x1 = coords.x1; + coords_part.y1 = coords.y1; + coords_part.x2 = coords.x1 + header.w - 1; + coords_part.y2 = coords.y1 + header.h - 1; + lv_draw_img(draw_ctx, &img_dsc, &coords_part, src); + } + + src = imgbtn->img_src_right[state]; + if(src) { + lv_img_decoder_get_info(src, &header); + right_w = header.w; + coords_part.x1 = coords.x2 - header.w + 1; + coords_part.y1 = coords.y1; + coords_part.x2 = coords.x2; + coords_part.y2 = coords.y1 + header.h - 1; + lv_draw_img(draw_ctx, &img_dsc, &coords_part, src); + } + + src = imgbtn->img_src_mid[state]; + if(src) { + lv_area_t clip_area_center; + clip_area_center.x1 = coords.x1 + left_w; + clip_area_center.x2 = coords.x2 - right_w; + clip_area_center.y1 = coords.y1; + clip_area_center.y2 = coords.y2; + + + bool comm_res; + comm_res = _lv_area_intersect(&clip_area_center, &clip_area_center, draw_ctx->clip_area); + if(comm_res) { + lv_coord_t i; + lv_img_decoder_get_info(src, &header); + + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area_center; + + coords_part.x1 = coords.x1 + left_w; + coords_part.y1 = coords.y1; + coords_part.x2 = coords_part.x1 + header.w - 1; + coords_part.y2 = coords_part.y1 + header.h - 1; + + for(i = coords_part.x1; i < (lv_coord_t)(clip_area_center.x2 + header.w - 1); i += header.w) { + lv_draw_img(draw_ctx, &img_dsc, &coords_part, src); + coords_part.x1 = coords_part.x2 + 1; + coords_part.x2 += header.w; + } + draw_ctx->clip_area = clip_area_ori; + } + } +} + +static void refr_img(lv_obj_t * obj) +{ + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + lv_imgbtn_state_t state = suggest_state(obj, get_state(obj)); + lv_img_header_t header; + + const void * src = imgbtn->img_src_mid[state]; + if(src == NULL) return; + + lv_res_t info_res = LV_RES_OK; + info_res = lv_img_decoder_get_info(src, &header); + + if(info_res == LV_RES_OK) { + imgbtn->act_cf = header.cf; + lv_obj_refresh_self_size(obj); + lv_obj_set_height(obj, header.h); /*Keep the user defined width*/ + } + else { + imgbtn->act_cf = LV_IMG_CF_UNKNOWN; + } + + lv_obj_invalidate(obj); +} + +/** + * If `src` is not defined for the current state try to get a state which is related to the current but has `src`. + * E.g. if the PRESSED src is not set but the RELEASED does, use the RELEASED. + * @param imgbtn pointer to an image button + * @param state the state to convert + * @return the suggested state + */ +static lv_imgbtn_state_t suggest_state(lv_obj_t * obj, lv_imgbtn_state_t state) +{ + lv_imgbtn_t * imgbtn = (lv_imgbtn_t *)obj; + if(imgbtn->img_src_mid[state] == NULL) { + switch(state) { + case LV_IMGBTN_STATE_PRESSED: + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_RELEASED]) return LV_IMGBTN_STATE_RELEASED; + break; + case LV_IMGBTN_STATE_CHECKED_RELEASED: + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_RELEASED]) return LV_IMGBTN_STATE_RELEASED; + break; + case LV_IMGBTN_STATE_CHECKED_PRESSED: + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_CHECKED_RELEASED]) return LV_IMGBTN_STATE_CHECKED_RELEASED; + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_PRESSED]) return LV_IMGBTN_STATE_PRESSED; + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_RELEASED]) return LV_IMGBTN_STATE_RELEASED; + break; + case LV_IMGBTN_STATE_DISABLED: + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_RELEASED]) return LV_IMGBTN_STATE_RELEASED; + break; + case LV_IMGBTN_STATE_CHECKED_DISABLED: + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_CHECKED_RELEASED]) return LV_IMGBTN_STATE_CHECKED_RELEASED; + if(imgbtn->img_src_mid[LV_IMGBTN_STATE_RELEASED]) return LV_IMGBTN_STATE_RELEASED; + break; + default: + break; + } + } + + return state; +} + +lv_imgbtn_state_t get_state(const lv_obj_t * imgbtn) +{ + LV_ASSERT_OBJ(imgbtn, MY_CLASS); + + lv_state_t obj_state = lv_obj_get_state(imgbtn); + + if(obj_state & LV_STATE_DISABLED) { + if(obj_state & LV_STATE_CHECKED) return LV_IMGBTN_STATE_CHECKED_DISABLED; + else return LV_IMGBTN_STATE_DISABLED; + } + + if(obj_state & LV_STATE_CHECKED) { + if(obj_state & LV_STATE_PRESSED) return LV_IMGBTN_STATE_CHECKED_PRESSED; + else return LV_IMGBTN_STATE_CHECKED_RELEASED; + } + else { + if(obj_state & LV_STATE_PRESSED) return LV_IMGBTN_STATE_PRESSED; + else return LV_IMGBTN_STATE_RELEASED; + } +} + +#endif diff --git a/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.h b/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.h new file mode 100644 index 00000000..597faea1 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/imgbtn/lv_imgbtn.h @@ -0,0 +1,131 @@ +/** + * @file lv_imgbtn.h + * + */ + +#ifndef LV_IMGBTN_H +#define LV_IMGBTN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_IMGBTN != 0 + +/********************* + * DEFINES + *********************/ +typedef enum { + LV_IMGBTN_STATE_RELEASED, + LV_IMGBTN_STATE_PRESSED, + LV_IMGBTN_STATE_DISABLED, + LV_IMGBTN_STATE_CHECKED_RELEASED, + LV_IMGBTN_STATE_CHECKED_PRESSED, + LV_IMGBTN_STATE_CHECKED_DISABLED, + _LV_IMGBTN_STATE_NUM, +} lv_imgbtn_state_t; + +/********************** + * TYPEDEFS + **********************/ +/*Data of image button*/ +typedef struct { + lv_obj_t obj; + const void * img_src_mid[_LV_IMGBTN_STATE_NUM]; /*Store center images to each state*/ + const void * img_src_left[_LV_IMGBTN_STATE_NUM]; /*Store left side images to each state*/ + const void * img_src_right[_LV_IMGBTN_STATE_NUM]; /*Store right side images to each state*/ + lv_img_cf_t act_cf; /*Color format of the currently active image*/ +} lv_imgbtn_t; + +extern const lv_obj_class_t lv_imgbtn_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create an image button object + * @param parent pointer to an object, it will be the parent of the new image button + * @return pointer to the created image button + */ +lv_obj_t * lv_imgbtn_create(lv_obj_t * parent); + +/*====================== + * Add/remove functions + *=====================*/ + +/*===================== + * Setter functions + *====================*/ + +/** + * Set images for a state of the image button + * @param imgbtn pointer to an image button object + * @param state for which state set the new image + * @param src_left pointer to an image source for the left side of the button (a C array or path to + * a file) + * @param src_mid pointer to an image source for the middle of the button (ideally 1px wide) (a C + * array or path to a file) + * @param src_right pointer to an image source for the right side of the button (a C array or path + * to a file) + */ +void lv_imgbtn_set_src(lv_obj_t * imgbtn, lv_imgbtn_state_t state, const void * src_left, const void * src_mid, + const void * src_right); + + +/** + * Use this function instead of `lv_obj_add/clear_state` to set a state manually + * @param imgbtn pointer to an image button object + * @param state the new state + */ +void lv_imgbtn_set_state(lv_obj_t * imgbtn, lv_imgbtn_state_t state); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get the left image in a given state + * @param imgbtn pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the left image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_left(lv_obj_t * imgbtn, lv_imgbtn_state_t state); + +/** + * Get the middle image in a given state + * @param imgbtn pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the middle image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_middle(lv_obj_t * imgbtn, lv_imgbtn_state_t state); + +/** + * Get the right image in a given state + * @param imgbtn pointer to an image button object + * @param state the state where to get the image (from `lv_btn_state_t`) ` + * @return pointer to the left image source (a C array or path to a file) + */ +const void * lv_imgbtn_get_src_right(lv_obj_t * imgbtn, lv_imgbtn_state_t state); + + +/*===================== + * Other functions + *====================*/ + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_IMGBTN*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_IMGBTN_H*/ diff --git a/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.c b/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.c new file mode 100644 index 00000000..8e052e33 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.c @@ -0,0 +1,430 @@ + +/** + * @file lv_keyboard.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_keyboard.h" +#if LV_USE_KEYBOARD + +#include "../../../widgets/lv_textarea.h" +#include "../../../misc/lv_assert.h" + +#include <stdlib.h> + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_keyboard_class +#define LV_KB_BTN(width) LV_BTNMATRIX_CTRL_POPOVER | width + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_keyboard_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); + +static void lv_keyboard_update_map(lv_obj_t * obj); + +static void lv_keyboard_update_ctrl_map(lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_keyboard_class = { + .constructor_cb = lv_keyboard_constructor, + .width_def = LV_PCT(100), + .height_def = LV_PCT(50), + .instance_size = sizeof(lv_keyboard_t), + .editable = 1, + .base_class = &lv_btnmatrix_class +}; + +static const char * const default_kb_map_lc[] = {"1#", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", LV_SYMBOL_BACKSPACE, "\n", + "ABC", "a", "s", "d", "f", "g", "h", "j", "k", "l", LV_SYMBOL_NEW_LINE, "\n", + "_", "-", "z", "x", "c", "v", "b", "n", "m", ".", ",", ":", "\n", + LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" + }; + +static const lv_btnmatrix_ctrl_t default_kb_ctrl_lc_map[] = { + LV_KEYBOARD_CTRL_BTN_FLAGS | 5, LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_BTNMATRIX_CTRL_CHECKED | 7, + LV_KEYBOARD_CTRL_BTN_FLAGS | 6, LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_BTNMATRIX_CTRL_CHECKED | 7, + LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), + LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 +}; + +static const char * const default_kb_map_uc[] = {"1#", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", LV_SYMBOL_BACKSPACE, "\n", + "abc", "A", "S", "D", "F", "G", "H", "J", "K", "L", LV_SYMBOL_NEW_LINE, "\n", + "_", "-", "Z", "X", "C", "V", "B", "N", "M", ".", ",", ":", "\n", + LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" + }; + +static const lv_btnmatrix_ctrl_t default_kb_ctrl_uc_map[] = { + LV_KEYBOARD_CTRL_BTN_FLAGS | 5, LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_KB_BTN(4), LV_BTNMATRIX_CTRL_CHECKED | 7, + LV_KEYBOARD_CTRL_BTN_FLAGS | 6, LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_KB_BTN(3), LV_BTNMATRIX_CTRL_CHECKED | 7, + LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | LV_KB_BTN(1), + LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 +}; + +static const char * const default_kb_map_spec[] = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "0", LV_SYMBOL_BACKSPACE, "\n", + "abc", "+", "-", "/", "*", "=", "%", "!", "?", "#", "<", ">", "\n", + "\\", "@", "$", "(", ")", "{", "}", "[", "]", ";", "\"", "'", "\n", + LV_SYMBOL_KEYBOARD, LV_SYMBOL_LEFT, " ", LV_SYMBOL_RIGHT, LV_SYMBOL_OK, "" + }; + +static const lv_btnmatrix_ctrl_t default_kb_ctrl_spec_map[] = { + LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_BTNMATRIX_CTRL_CHECKED | 2, + LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), + LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), LV_KB_BTN(1), + LV_KEYBOARD_CTRL_BTN_FLAGS | 2, LV_BTNMATRIX_CTRL_CHECKED | 2, 6, LV_BTNMATRIX_CTRL_CHECKED | 2, LV_KEYBOARD_CTRL_BTN_FLAGS | 2 +}; + +static const char * const default_kb_map_num[] = {"1", "2", "3", LV_SYMBOL_KEYBOARD, "\n", + "4", "5", "6", LV_SYMBOL_OK, "\n", + "7", "8", "9", LV_SYMBOL_BACKSPACE, "\n", + "+/-", "0", ".", LV_SYMBOL_LEFT, LV_SYMBOL_RIGHT, "" + }; + +static const lv_btnmatrix_ctrl_t default_kb_ctrl_num_map[] = { + 1, 1, 1, LV_KEYBOARD_CTRL_BTN_FLAGS | 2, + 1, 1, 1, LV_KEYBOARD_CTRL_BTN_FLAGS | 2, + 1, 1, 1, 2, + 1, 1, 1, 1, 1 +}; + +static const char * * kb_map[9] = { + (const char * *)default_kb_map_lc, + (const char * *)default_kb_map_uc, + (const char * *)default_kb_map_spec, + (const char * *)default_kb_map_num, + (const char * *)default_kb_map_lc, + (const char * *)default_kb_map_lc, + (const char * *)default_kb_map_lc, + (const char * *)default_kb_map_lc, + (const char * *)NULL, +}; +static const lv_btnmatrix_ctrl_t * kb_ctrl[9] = { + default_kb_ctrl_lc_map, + default_kb_ctrl_uc_map, + default_kb_ctrl_spec_map, + default_kb_ctrl_num_map, + default_kb_ctrl_lc_map, + default_kb_ctrl_lc_map, + default_kb_ctrl_lc_map, + default_kb_ctrl_lc_map, + NULL, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create a Keyboard object + * @param parent pointer to an object, it will be the parent of the new keyboard + * @return pointer to the created keyboard + */ +lv_obj_t * lv_keyboard_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_keyboard_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +/** + * Assign a Text Area to the Keyboard. The pressed characters will be put there. + * @param kb pointer to a Keyboard object + * @param ta pointer to a Text Area object to write there + */ +void lv_keyboard_set_textarea(lv_obj_t * obj, lv_obj_t * ta) +{ + if(ta) { + LV_ASSERT_OBJ(ta, &lv_textarea_class); + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + + /*Hide the cursor of the old Text area if cursor management is enabled*/ + if(keyboard->ta) { + lv_obj_clear_state(obj, LV_STATE_FOCUSED); + } + + keyboard->ta = ta; + + /*Show the cursor of the new Text area if cursor management is enabled*/ + if(keyboard->ta) { + lv_obj_add_flag(obj, LV_STATE_FOCUSED); + } +} + +/** + * Set a new a mode (text or number map) + * @param kb pointer to a Keyboard object + * @param mode the mode from 'lv_keyboard_mode_t' + */ +void lv_keyboard_set_mode(lv_obj_t * obj, lv_keyboard_mode_t mode) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + if(keyboard->mode == mode) return; + + keyboard->mode = mode; + lv_keyboard_update_map(obj); +} + +/** + * Show the button title in a popover when pressed. + * @param kb pointer to a Keyboard object + * @param en whether "popovers" mode is enabled + */ +void lv_keyboard_set_popovers(lv_obj_t * obj, bool en) +{ + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + + if(keyboard->popovers == en) { + return; + } + + keyboard->popovers = en; + lv_keyboard_update_ctrl_map(obj); +} + +/** + * Set a new map for the keyboard + * @param kb pointer to a Keyboard object + * @param mode keyboard map to alter 'lv_keyboard_mode_t' + * @param map pointer to a string array to describe the map. + * See 'lv_btnmatrix_set_map()' for more info. + */ +void lv_keyboard_set_map(lv_obj_t * obj, lv_keyboard_mode_t mode, const char * map[], + const lv_btnmatrix_ctrl_t ctrl_map[]) +{ + kb_map[mode] = map; + kb_ctrl[mode] = ctrl_map; + lv_keyboard_update_map(obj); +} + +/*===================== + * Getter functions + *====================*/ + +/** + * Assign a Text Area to the Keyboard. The pressed characters will be put there. + * @param kb pointer to a Keyboard object + * @return pointer to the assigned Text Area object + */ +lv_obj_t * lv_keyboard_get_textarea(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + return keyboard->ta; +} + +/** + * Set a new a mode (text or number map) + * @param kb pointer to a Keyboard object + * @return the current mode from 'lv_keyboard_mode_t' + */ +lv_keyboard_mode_t lv_keyboard_get_mode(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + return keyboard->mode; +} + +/** + * Tell whether "popovers" mode is enabled or not. + * @param kb pointer to a Keyboard object + * @return true: "popovers" mode is enabled; false: disabled + */ +bool lv_btnmatrix_get_popovers(const lv_obj_t * obj) +{ + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + return keyboard->popovers; +} + +/*===================== + * Other functions + *====================*/ + +/** + * Default keyboard event to add characters to the Text area and change the map. + * If a custom `event_cb` is added to the keyboard this function can be called from it to handle the + * button clicks + * @param kb pointer to a keyboard + * @param event the triggering event + */ +void lv_keyboard_def_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + uint16_t btn_id = lv_btnmatrix_get_selected_btn(obj); + if(btn_id == LV_BTNMATRIX_BTN_NONE) return; + + const char * txt = lv_btnmatrix_get_btn_text(obj, lv_btnmatrix_get_selected_btn(obj)); + if(txt == NULL) return; + + if(strcmp(txt, "abc") == 0) { + keyboard->mode = LV_KEYBOARD_MODE_TEXT_LOWER; + lv_btnmatrix_set_map(obj, kb_map[LV_KEYBOARD_MODE_TEXT_LOWER]); + lv_keyboard_update_ctrl_map(obj); + return; + } + else if(strcmp(txt, "ABC") == 0) { + keyboard->mode = LV_KEYBOARD_MODE_TEXT_UPPER; + lv_btnmatrix_set_map(obj, kb_map[LV_KEYBOARD_MODE_TEXT_UPPER]); + lv_keyboard_update_ctrl_map(obj); + return; + } + else if(strcmp(txt, "1#") == 0) { + keyboard->mode = LV_KEYBOARD_MODE_SPECIAL; + lv_btnmatrix_set_map(obj, kb_map[LV_KEYBOARD_MODE_SPECIAL]); + lv_keyboard_update_ctrl_map(obj); + return; + } + else if(strcmp(txt, LV_SYMBOL_CLOSE) == 0 || strcmp(txt, LV_SYMBOL_KEYBOARD) == 0) { + lv_res_t res = lv_event_send(obj, LV_EVENT_CANCEL, NULL); + if(res != LV_RES_OK) return; + + if(keyboard->ta) { + res = lv_event_send(keyboard->ta, LV_EVENT_CANCEL, NULL); + if(res != LV_RES_OK) return; + } + return; + } + else if(strcmp(txt, LV_SYMBOL_OK) == 0) { + lv_res_t res = lv_event_send(obj, LV_EVENT_READY, NULL); + if(res != LV_RES_OK) return; + + if(keyboard->ta) { + res = lv_event_send(keyboard->ta, LV_EVENT_READY, NULL); + if(res != LV_RES_OK) return; + } + return; + } + + /*Add the characters to the text area if set*/ + if(keyboard->ta == NULL) return; + + if(strcmp(txt, "Enter") == 0 || strcmp(txt, LV_SYMBOL_NEW_LINE) == 0) { + lv_textarea_add_char(keyboard->ta, '\n'); + if(lv_textarea_get_one_line(keyboard->ta)) { + lv_res_t res = lv_event_send(keyboard->ta, LV_EVENT_READY, NULL); + if(res != LV_RES_OK) return; + } + } + else if(strcmp(txt, LV_SYMBOL_LEFT) == 0) { + lv_textarea_cursor_left(keyboard->ta); + } + else if(strcmp(txt, LV_SYMBOL_RIGHT) == 0) { + lv_textarea_cursor_right(keyboard->ta); + } + else if(strcmp(txt, LV_SYMBOL_BACKSPACE) == 0) { + lv_textarea_del_char(keyboard->ta); + } + else if(strcmp(txt, "+/-") == 0) { + uint16_t cur = lv_textarea_get_cursor_pos(keyboard->ta); + const char * ta_txt = lv_textarea_get_text(keyboard->ta); + if(ta_txt[0] == '-') { + lv_textarea_set_cursor_pos(keyboard->ta, 1); + lv_textarea_del_char(keyboard->ta); + lv_textarea_add_char(keyboard->ta, '+'); + lv_textarea_set_cursor_pos(keyboard->ta, cur); + } + else if(ta_txt[0] == '+') { + lv_textarea_set_cursor_pos(keyboard->ta, 1); + lv_textarea_del_char(keyboard->ta); + lv_textarea_add_char(keyboard->ta, '-'); + lv_textarea_set_cursor_pos(keyboard->ta, cur); + } + else { + lv_textarea_set_cursor_pos(keyboard->ta, 0); + lv_textarea_add_char(keyboard->ta, '-'); + lv_textarea_set_cursor_pos(keyboard->ta, cur + 1); + } + } + else { + lv_textarea_add_text(keyboard->ta, txt); + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_keyboard_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICK_FOCUSABLE); + + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + keyboard->ta = NULL; + keyboard->mode = LV_KEYBOARD_MODE_TEXT_LOWER; + keyboard->popovers = 0; + + lv_obj_align(obj, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_add_event_cb(obj, lv_keyboard_def_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_set_style_base_dir(obj, LV_BASE_DIR_LTR, 0); + + lv_keyboard_update_map(obj); +} + +/** + * Update the key and control map for the current mode + * @param obj pointer to a keyboard object + */ +static void lv_keyboard_update_map(lv_obj_t * obj) +{ + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + lv_btnmatrix_set_map(obj, kb_map[keyboard->mode]); + lv_keyboard_update_ctrl_map(obj); +} + +/** + * Update the control map for the current mode + * @param obj pointer to a keyboard object + */ +static void lv_keyboard_update_ctrl_map(lv_obj_t * obj) +{ + lv_keyboard_t * keyboard = (lv_keyboard_t *)obj; + + if(keyboard->popovers) { + /*Apply the current control map (already includes LV_BTNMATRIX_CTRL_POPOVER flags)*/ + lv_btnmatrix_set_ctrl_map(obj, kb_ctrl[keyboard->mode]); + } + else { + /*Make a copy of the current control map*/ + lv_btnmatrix_t * btnm = (lv_btnmatrix_t *)obj; + lv_btnmatrix_ctrl_t * ctrl_map = lv_mem_alloc(btnm->btn_cnt * sizeof(lv_btnmatrix_ctrl_t)); + lv_memcpy(ctrl_map, kb_ctrl[keyboard->mode], sizeof(lv_btnmatrix_ctrl_t) * btnm->btn_cnt); + + /*Remove all LV_BTNMATRIX_CTRL_POPOVER flags*/ + for(uint16_t i = 0; i < btnm->btn_cnt; i++) { + ctrl_map[i] &= (~LV_BTNMATRIX_CTRL_POPOVER); + } + + /*Apply new control map and clean up*/ + lv_btnmatrix_set_ctrl_map(obj, ctrl_map); + lv_mem_free(ctrl_map); + } +} + +#endif /*LV_USE_KEYBOARD*/ diff --git a/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.h b/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.h new file mode 100644 index 00000000..7f65cd75 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/keyboard/lv_keyboard.h @@ -0,0 +1,187 @@ +/** + * @file lv_keyboard.h + * + */ + +#ifndef LV_KEYBOARD_H +#define LV_KEYBOARD_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../widgets/lv_btnmatrix.h" + +#if LV_USE_KEYBOARD + +/*Testing of dependencies*/ +#if LV_USE_BTNMATRIX == 0 +#error "lv_kb: lv_btnm is required. Enable it in lv_conf.h (LV_USE_BTNMATRIX 1) " +#endif + +#if LV_USE_TEXTAREA == 0 +#error "lv_kb: lv_ta is required. Enable it in lv_conf.h (LV_USE_TEXTAREA 1) " +#endif + +/********************* + * DEFINES + *********************/ +#define LV_KEYBOARD_CTRL_BTN_FLAGS (LV_BTNMATRIX_CTRL_NO_REPEAT | LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_CHECKED) + +/********************** + * TYPEDEFS + **********************/ + +/** Current keyboard mode.*/ +enum { + LV_KEYBOARD_MODE_TEXT_LOWER, + LV_KEYBOARD_MODE_TEXT_UPPER, + LV_KEYBOARD_MODE_SPECIAL, + LV_KEYBOARD_MODE_NUMBER, + LV_KEYBOARD_MODE_USER_1, + LV_KEYBOARD_MODE_USER_2, + LV_KEYBOARD_MODE_USER_3, + LV_KEYBOARD_MODE_USER_4, +}; +typedef uint8_t lv_keyboard_mode_t; + +/*Data of keyboard*/ +typedef struct { + lv_btnmatrix_t btnm; + lv_obj_t * ta; /*Pointer to the assigned text area*/ + lv_keyboard_mode_t mode; /*Key map type*/ + uint8_t popovers : 1; /*Show button titles in popovers on press*/ +} lv_keyboard_t; + +extern const lv_obj_class_t lv_keyboard_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a Keyboard object + * @param parent pointer to an object, it will be the parent of the new keyboard + * @return pointer to the created keyboard + */ +lv_obj_t * lv_keyboard_create(lv_obj_t * parent); + +/*===================== + * Setter functions + *====================*/ + +/** + * Assign a Text Area to the Keyboard. The pressed characters will be put there. + * @param kb pointer to a Keyboard object + * @param ta pointer to a Text Area object to write there + */ +void lv_keyboard_set_textarea(lv_obj_t * kb, lv_obj_t * ta); + +/** + * Set a new a mode (text or number map) + * @param kb pointer to a Keyboard object + * @param mode the mode from 'lv_keyboard_mode_t' + */ +void lv_keyboard_set_mode(lv_obj_t * kb, lv_keyboard_mode_t mode); + +/** + * Show the button title in a popover when pressed. + * @param kb pointer to a Keyboard object + * @param en whether "popovers" mode is enabled + */ +void lv_keyboard_set_popovers(lv_obj_t * kb, bool en); + +/** + * Set a new map for the keyboard + * @param kb pointer to a Keyboard object + * @param mode keyboard map to alter 'lv_keyboard_mode_t' + * @param map pointer to a string array to describe the map. + * See 'lv_btnmatrix_set_map()' for more info. + */ +void lv_keyboard_set_map(lv_obj_t * kb, lv_keyboard_mode_t mode, const char * map[], + const lv_btnmatrix_ctrl_t ctrl_map[]); + +/*===================== + * Getter functions + *====================*/ + +/** + * Assign a Text Area to the Keyboard. The pressed characters will be put there. + * @param kb pointer to a Keyboard object + * @return pointer to the assigned Text Area object + */ +lv_obj_t * lv_keyboard_get_textarea(const lv_obj_t * kb); + +/** + * Set a new a mode (text or number map) + * @param kb pointer to a Keyboard object + * @return the current mode from 'lv_keyboard_mode_t' + */ +lv_keyboard_mode_t lv_keyboard_get_mode(const lv_obj_t * kb); + +/** + * Tell whether "popovers" mode is enabled or not. + * @param kb pointer to a Keyboard object + * @return true: "popovers" mode is enabled; false: disabled + */ +bool lv_btnmatrix_get_popovers(const lv_obj_t * obj); + +/** + * Get the current map of a keyboard + * @param kb pointer to a keyboard object + * @return the current map + */ +static inline const char ** lv_keyboard_get_map_array(const lv_obj_t * kb) +{ + return lv_btnmatrix_get_map(kb); +} + +/** + * Get the index of the lastly "activated" button by the user (pressed, released, focused etc) + * Useful in the `event_cb` to get the text of the button, check if hidden etc. + * @param obj pointer to button matrix object + * @return index of the last released button (LV_BTNMATRIX_BTN_NONE: if unset) + */ +static inline uint16_t lv_keyboard_get_selected_btn(const lv_obj_t * obj) +{ + return lv_btnmatrix_get_selected_btn(obj); +} + +/** + * Get the button's text + * @param obj pointer to button matrix object + * @param btn_id the index a button not counting new line characters. + * @return text of btn_index` button + */ +static inline const char * lv_keyboard_get_btn_text(const lv_obj_t * obj, uint16_t btn_id) +{ + return lv_btnmatrix_get_btn_text(obj, btn_id); +} + +/*===================== + * Other functions + *====================*/ + +/** + * Default keyboard event to add characters to the Text area and change the map. + * If a custom `event_cb` is added to the keyboard this function can be called from it to handle the + * button clicks + * @param kb pointer to a keyboard + * @param event the triggering event + */ +void lv_keyboard_def_event_cb(lv_event_t * e); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_KEYBOARD*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_KEYBOARD_H*/ diff --git a/lib/lvgl/src/extra/widgets/led/lv_led.c b/lib/lvgl/src/extra/widgets/led/lv_led.c new file mode 100644 index 00000000..88b7b87d --- /dev/null +++ b/lib/lvgl/src/extra/widgets/led/lv_led.c @@ -0,0 +1,221 @@ +/** + * @file lv_led.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_led.h" +#if LV_USE_LED + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_led_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_led_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_led_event(const lv_obj_class_t * class_p, lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_led_class = { + .base_class = &lv_obj_class, + .constructor_cb = lv_led_constructor, + .width_def = LV_DPI_DEF / 5, + .height_def = LV_DPI_DEF / 5, + .event_cb = lv_led_event, + .instance_size = sizeof(lv_led_t), +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create a led object + * @param parent pointer to an object, it will be the parent of the new led + * @return pointer to the created led + */ +lv_obj_t * lv_led_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +/** + * Set the color of the LED + * @param led pointer to a LED object + * @param color the color of the LED + */ +void lv_led_set_color(lv_obj_t * obj, lv_color_t color) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_led_t * led = (lv_led_t *)obj; + led->color = color; + lv_obj_invalidate(obj); +} + +/** + * Set the brightness of a LED object + * @param led pointer to a LED object + * @param bright LV_LED_BRIGHT_MIN (max. dark) ... LV_LED_BRIGHT_MAX (max. light) + */ +void lv_led_set_brightness(lv_obj_t * obj, uint8_t bright) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_led_t * led = (lv_led_t *)obj; + if(led->bright == bright) return; + + led->bright = LV_CLAMP(LV_LED_BRIGHT_MIN, bright, LV_LED_BRIGHT_MAX); + + /*Invalidate the object there fore it will be redrawn*/ + lv_obj_invalidate(obj); +} + +/** + * Light on a LED + * @param led pointer to a LED object + */ +void lv_led_on(lv_obj_t * led) +{ + lv_led_set_brightness(led, LV_LED_BRIGHT_MAX); +} + +/** + * Light off a LED + * @param led pointer to a LED object + */ +void lv_led_off(lv_obj_t * led) +{ + lv_led_set_brightness(led, LV_LED_BRIGHT_MIN); +} + +/** + * Toggle the state of a LED + * @param led pointer to a LED object + */ +void lv_led_toggle(lv_obj_t * obj) +{ + uint8_t bright = lv_led_get_brightness(obj); + if(bright > (LV_LED_BRIGHT_MIN + LV_LED_BRIGHT_MAX) >> 1) + lv_led_off(obj); + else + lv_led_on(obj); +} + +/*===================== + * Getter functions + *====================*/ + +/** + * Get the brightness of a LEd object + * @param led pointer to LED object + * @return bright 0 (max. dark) ... 255 (max. light) + */ +uint8_t lv_led_get_brightness(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_led_t * led = (lv_led_t *)obj; + return led->bright; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_led_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_led_t * led = (lv_led_t *)obj; + led->color = lv_theme_get_color_primary(obj); + led->bright = LV_LED_BRIGHT_MAX; +} + +static void lv_led_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + lv_res_t res; + + /* Call the ancestor's event handler */ + lv_event_code_t code = lv_event_get_code(e); + if(code != LV_EVENT_DRAW_MAIN && code != LV_EVENT_DRAW_MAIN_END) { + res = lv_obj_event_base(MY_CLASS, e); + if(res != LV_RES_OK) return; + } + + lv_obj_t * obj = lv_event_get_target(e); + if(code == LV_EVENT_DRAW_MAIN) { + /*Make darker colors in a temporary style according to the brightness*/ + lv_led_t * led = (lv_led_t *)obj; + + lv_draw_rect_dsc_t rect_dsc; + lv_draw_rect_dsc_init(&rect_dsc); + lv_obj_init_draw_rect_dsc(obj, LV_PART_MAIN, &rect_dsc); + + /*Use the original colors brightness to modify color->led*/ + rect_dsc.bg_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.bg_color)); + rect_dsc.bg_grad.stops[0].color = lv_color_mix(led->color, lv_color_black(), + lv_color_brightness(rect_dsc.bg_grad.stops[0].color)); + rect_dsc.bg_grad.stops[1].color = lv_color_mix(led->color, lv_color_black(), + lv_color_brightness(rect_dsc.bg_grad.stops[1].color)); + rect_dsc.shadow_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.shadow_color)); + rect_dsc.border_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.border_color)); + rect_dsc.outline_color = lv_color_mix(led->color, lv_color_black(), lv_color_brightness(rect_dsc.outline_color)); + + /*Mix. the color with black proportionally with brightness*/ + rect_dsc.bg_color = lv_color_mix(rect_dsc.bg_color, lv_color_black(), led->bright); + rect_dsc.bg_grad.stops[0].color = lv_color_mix(rect_dsc.bg_grad.stops[0].color, lv_color_black(), led->bright); + rect_dsc.bg_grad.stops[1].color = lv_color_mix(rect_dsc.bg_grad.stops[1].color, lv_color_black(), led->bright); + rect_dsc.border_color = lv_color_mix(rect_dsc.border_color, lv_color_black(), led->bright); + rect_dsc.shadow_color = lv_color_mix(rect_dsc.shadow_color, lv_color_black(), led->bright); + rect_dsc.outline_color = lv_color_mix(rect_dsc.outline_color, lv_color_black(), led->bright); + + /*Set the current shadow width according to brightness proportionally between LV_LED_BRIGHT_OFF + * and LV_LED_BRIGHT_ON*/ + rect_dsc.shadow_width = ((led->bright - LV_LED_BRIGHT_MIN) * rect_dsc.shadow_width) / + (LV_LED_BRIGHT_MAX - LV_LED_BRIGHT_MIN); + rect_dsc.shadow_spread = ((led->bright - LV_LED_BRIGHT_MIN) * rect_dsc.shadow_spread) / + (LV_LED_BRIGHT_MAX - LV_LED_BRIGHT_MIN); + + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.draw_area = &obj->coords; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_LED_DRAW_PART_RECTANGLE; + part_draw_dsc.rect_dsc = &rect_dsc; + part_draw_dsc.part = LV_PART_MAIN; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_rect(draw_ctx, &rect_dsc, &obj->coords); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } +} + +#endif diff --git a/lib/lvgl/src/extra/widgets/led/lv_led.h b/lib/lvgl/src/extra/widgets/led/lv_led.h new file mode 100644 index 00000000..368bcd23 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/led/lv_led.h @@ -0,0 +1,116 @@ +/** + * @file lv_led.h + * + */ + +#ifndef LV_LED_H +#define LV_LED_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_LED + + +/********************* + * DEFINES + *********************/ +/** Brightness when the LED if OFF */ +#ifndef LV_LED_BRIGHT_MIN +# define LV_LED_BRIGHT_MIN 80 +#endif + +/** Brightness when the LED if ON */ +#ifndef LV_LED_BRIGHT_MAX +# define LV_LED_BRIGHT_MAX 255 +#endif + +/********************** + * TYPEDEFS + **********************/ + +/*Data of led*/ +typedef struct { + lv_obj_t obj; + lv_color_t color; + uint8_t bright; /**< Current brightness of the LED (0..255)*/ +} lv_led_t; + +extern const lv_obj_class_t lv_led_class; + +/** + * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_led_class` + * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END` + */ +typedef enum { + LV_LED_DRAW_PART_RECTANGLE, /**< The main rectangle*/ +} lv_led_draw_part_type_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a led object + * @param parent pointer to an object, it will be the parent of the new led + * @return pointer to the created led + */ +lv_obj_t * lv_led_create(lv_obj_t * parent); + +/** + * Set the color of the LED + * @param led pointer to a LED object + * @param color the color of the LED + */ +void lv_led_set_color(lv_obj_t * led, lv_color_t color); + +/** + * Set the brightness of a LED object + * @param led pointer to a LED object + * @param bright LV_LED_BRIGHT_MIN (max. dark) ... LV_LED_BRIGHT_MAX (max. light) + */ +void lv_led_set_brightness(lv_obj_t * led, uint8_t bright); + +/** + * Light on a LED + * @param led pointer to a LED object + */ +void lv_led_on(lv_obj_t * led); + +/** + * Light off a LED + * @param led pointer to a LED object + */ +void lv_led_off(lv_obj_t * led); + +/** + * Toggle the state of a LED + * @param led pointer to a LED object + */ +void lv_led_toggle(lv_obj_t * led); + +/** + * Get the brightness of a LEd object + * @param led pointer to LED object + * @return bright 0 (max. dark) ... 255 (max. light) + */ +uint8_t lv_led_get_brightness(const lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_LED*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + + +#endif /*LV_LED_H*/ diff --git a/lib/lvgl/src/extra/widgets/list/lv_list.c b/lib/lvgl/src/extra/widgets/list/lv_list.c new file mode 100644 index 00000000..29355fd3 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/list/lv_list.c @@ -0,0 +1,120 @@ +/** + * @file lv_list.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_list.h" +#include "../../../core/lv_disp.h" +#include "../../../widgets/lv_label.h" +#include "../../../widgets/lv_img.h" +#include "../../../widgets/lv_btn.h" + +#if LV_USE_LIST + +/********************* + * DEFINES + *********************/ +#define MV_CLASS &lv_list + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +const lv_obj_class_t lv_list_class = { + .base_class = &lv_obj_class, + .width_def = (LV_DPI_DEF * 3) / 2, + .height_def = LV_DPI_DEF * 2 +}; + +const lv_obj_class_t lv_list_btn_class = { + .base_class = &lv_btn_class, +}; + +const lv_obj_class_t lv_list_text_class = { + .base_class = &lv_label_class, +}; + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_list_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_list_class, parent); + lv_obj_class_init_obj(obj); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + return obj; +} + +lv_obj_t * lv_list_add_text(lv_obj_t * list, const char * txt) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_list_text_class, list); + lv_obj_class_init_obj(obj); + lv_label_set_text(obj, txt); + lv_label_set_long_mode(obj, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_width(obj, LV_PCT(100)); + return obj; +} + +lv_obj_t * lv_list_add_btn(lv_obj_t * list, const void * icon, const char * txt) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_list_btn_class, list); + lv_obj_class_init_obj(obj); + lv_obj_set_size(obj, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + +#if LV_USE_IMG == 1 + if(icon) { + lv_obj_t * img = lv_img_create(obj); + lv_img_set_src(img, icon); + } +#endif + + if(txt) { + lv_obj_t * label = lv_label_create(obj); + lv_label_set_text(label, txt); + lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); + lv_obj_set_flex_grow(label, 1); + } + + return obj; +} + +const char * lv_list_get_btn_text(lv_obj_t * list, lv_obj_t * btn) +{ + LV_UNUSED(list); + uint32_t i; + for(i = 0; i < lv_obj_get_child_cnt(btn); i++) { + lv_obj_t * child = lv_obj_get_child(btn, i); + if(lv_obj_check_type(child, &lv_label_class)) { + return lv_label_get_text(child); + } + + } + + return ""; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +#endif /*LV_USE_LIST*/ diff --git a/lib/lvgl/src/extra/widgets/list/lv_list.h b/lib/lvgl/src/extra/widgets/list/lv_list.h new file mode 100644 index 00000000..0da5595b --- /dev/null +++ b/lib/lvgl/src/extra/widgets/list/lv_list.h @@ -0,0 +1,54 @@ +/** + * @file lv_win.h + * + */ + +#ifndef LV_LIST_H +#define LV_LIST_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" +#include "../../layouts/flex/lv_flex.h" + +#if LV_USE_LIST + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +extern const lv_obj_class_t lv_list_class; +extern const lv_obj_class_t lv_list_text_class; +extern const lv_obj_class_t lv_list_btn_class; +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t * lv_list_create(lv_obj_t * parent); + +lv_obj_t * lv_list_add_text(lv_obj_t * list, const char * txt); + +lv_obj_t * lv_list_add_btn(lv_obj_t * list, const void * icon, const char * txt); + +const char * lv_list_get_btn_text(lv_obj_t * list, lv_obj_t * btn); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_LIST*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_LIST_H*/ diff --git a/lib/lvgl/src/extra/widgets/lv_widgets.h b/lib/lvgl/src/extra/widgets/lv_widgets.h new file mode 100644 index 00000000..11418102 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/lv_widgets.h @@ -0,0 +1,56 @@ +/** + * @file lv_widgets.h + * + */ + +#ifndef LV_WIDGETS_H +#define LV_WIDGETS_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "animimg/lv_animimg.h" +#include "calendar/lv_calendar.h" +#include "calendar/lv_calendar_header_arrow.h" +#include "calendar/lv_calendar_header_dropdown.h" +#include "chart/lv_chart.h" +#include "keyboard/lv_keyboard.h" +#include "list/lv_list.h" +#include "menu/lv_menu.h" +#include "msgbox/lv_msgbox.h" +#include "meter/lv_meter.h" +#include "spinbox/lv_spinbox.h" +#include "spinner/lv_spinner.h" +#include "tabview/lv_tabview.h" +#include "tileview/lv_tileview.h" +#include "win/lv_win.h" +#include "colorwheel/lv_colorwheel.h" +#include "led/lv_led.h" +#include "imgbtn/lv_imgbtn.h" +#include "span/lv_span.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_WIDGETS_H*/ diff --git a/lib/lvgl/src/extra/widgets/menu/lv_menu.c b/lib/lvgl/src/extra/widgets/menu/lv_menu.c new file mode 100644 index 00000000..78577e77 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/menu/lv_menu.c @@ -0,0 +1,767 @@ +/** + * @file lv_menu.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_menu.h" + +#if LV_USE_MENU + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_menu_class + +#include "../../../core/lv_obj.h" +#include "../../layouts/flex/lv_flex.h" +#include "../../../widgets/lv_label.h" +#include "../../../widgets/lv_btn.h" +#include "../../../widgets/lv_img.h" + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_menu_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_menu_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_menu_page_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_menu_page_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_menu_cont_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_menu_section_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); + +const lv_obj_class_t lv_menu_class = { + .constructor_cb = lv_menu_constructor, + .destructor_cb = lv_menu_destructor, + .base_class = &lv_obj_class, + .width_def = (LV_DPI_DEF * 3) / 2, + .height_def = LV_DPI_DEF * 2, + .instance_size = sizeof(lv_menu_t) +}; +const lv_obj_class_t lv_menu_page_class = { + .constructor_cb = lv_menu_page_constructor, + .destructor_cb = lv_menu_page_destructor, + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_SIZE_CONTENT, + .instance_size = sizeof(lv_menu_page_t) +}; + +const lv_obj_class_t lv_menu_cont_class = { + .constructor_cb = lv_menu_cont_constructor, + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_SIZE_CONTENT +}; + +const lv_obj_class_t lv_menu_section_class = { + .constructor_cb = lv_menu_section_constructor, + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_SIZE_CONTENT +}; + +const lv_obj_class_t lv_menu_separator_class = { + .base_class = &lv_obj_class, + .width_def = LV_SIZE_CONTENT, + .height_def = LV_SIZE_CONTENT +}; + +const lv_obj_class_t lv_menu_sidebar_cont_class = { + .base_class = &lv_obj_class +}; + +const lv_obj_class_t lv_menu_main_cont_class = { + .base_class = &lv_obj_class +}; + +const lv_obj_class_t lv_menu_main_header_cont_class = { + .base_class = &lv_obj_class +}; + +const lv_obj_class_t lv_menu_sidebar_header_cont_class = { + .base_class = &lv_obj_class +}; + +static void lv_menu_refr(lv_obj_t * obj); +static void lv_menu_refr_sidebar_header_mode(lv_obj_t * obj); +static void lv_menu_refr_main_header_mode(lv_obj_t * obj); +static void lv_menu_load_page_event_cb(lv_event_t * e); +static void lv_menu_obj_del_event_cb(lv_event_t * e); +static void lv_menu_back_event_cb(lv_event_t * e); +static void lv_menu_value_changed_event_cb(lv_event_t * e); +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ +bool lv_menu_item_back_btn_is_root(lv_obj_t * menu, lv_obj_t * obj); +void lv_menu_clear_history(lv_obj_t * obj); + +lv_obj_t * lv_menu_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t * lv_menu_page_create(lv_obj_t * parent, char * title) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_page_class, parent); + lv_obj_class_init_obj(obj); + + lv_menu_page_t * page = (lv_menu_page_t *)obj; + if(title) { + page->title = lv_mem_alloc(strlen(title) + 1); + LV_ASSERT_MALLOC(page->title); + if(page->title == NULL) return NULL; + strcpy(page->title, title); + } + else { + page->title = NULL; + } + + return obj; +} + +lv_obj_t * lv_menu_cont_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_cont_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t * lv_menu_section_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_section_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t * lv_menu_separator_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_menu_separator_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +void lv_menu_refr(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + lv_ll_t * history_ll = &(menu->history_ll); + + /* The current menu */ + lv_menu_history_t * act_hist = _lv_ll_get_head(history_ll); + + lv_obj_t * page = NULL; + + if(act_hist != NULL) { + page = act_hist->page; + /* Delete the current item from the history */ + _lv_ll_remove(history_ll, act_hist); + lv_mem_free(act_hist); + menu->cur_depth--; + } + + /* Set it */ + lv_menu_set_page(obj, page); +} + +/*===================== + * Setter functions + *====================*/ + +void lv_menu_set_page(lv_obj_t * obj, lv_obj_t * page) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + /* Hide previous page */ + if(menu->main_page != NULL) { + lv_obj_set_parent(menu->main_page, menu->storage); + } + + if(page != NULL) { + /* Add a new node */ + lv_ll_t * history_ll = &(menu->history_ll); + lv_menu_history_t * new_node = _lv_ll_ins_head(history_ll); + LV_ASSERT_MALLOC(new_node); + new_node->page = page; + menu->cur_depth++; + + /* Place page in main */ + lv_obj_set_parent(page, menu->main); + } + else { + /* Empty page, clear history */ + lv_menu_clear_history(obj); + } + + menu->main_page = page; + + /* If there is a selected tab, update checked state */ + if(menu->selected_tab != NULL) { + if(menu->sidebar_page != NULL) { + lv_obj_add_state(menu->selected_tab, LV_STATE_CHECKED); + } + else { + lv_obj_clear_state(menu->selected_tab, LV_STATE_CHECKED); + } + } + + /* Back btn management */ + if(menu->sidebar_page != NULL) { + /* With sidebar enabled */ + if(menu->sidebar_generated) { + if(menu->mode_root_back_btn == LV_MENU_ROOT_BACK_BTN_ENABLED) { + /* Root back btn is always shown if enabled*/ + lv_obj_clear_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + else { + lv_obj_add_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(menu->sidebar_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + } + + if(menu->cur_depth >= 2) { + lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + else { + lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + } + else { + /* With sidebar disabled */ + if(menu->cur_depth >= 2 || menu->mode_root_back_btn == LV_MENU_ROOT_BACK_BTN_ENABLED) { + lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + else { + lv_obj_add_flag(menu->main_header_back_btn, LV_OBJ_FLAG_HIDDEN); + lv_obj_clear_flag(menu->main_header_back_btn, LV_OBJ_FLAG_CLICKABLE); + } + } + + lv_event_send((lv_obj_t *)menu, LV_EVENT_VALUE_CHANGED, NULL); + + lv_menu_refr_main_header_mode(obj); +} + +void lv_menu_set_sidebar_page(lv_obj_t * obj, lv_obj_t * page) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + /* Sidebar management*/ + if(page != NULL) { + /* Sidebar should be enabled */ + if(!menu->sidebar_generated) { + /* Create sidebar */ + lv_obj_t * sidebar_cont = lv_obj_class_create_obj(&lv_menu_sidebar_cont_class, obj); + lv_obj_class_init_obj(sidebar_cont); + lv_obj_move_to_index(sidebar_cont, 1); + lv_obj_set_size(sidebar_cont, LV_PCT(30), LV_PCT(100)); + lv_obj_set_flex_flow(sidebar_cont, LV_FLEX_FLOW_COLUMN); + lv_obj_add_flag(sidebar_cont, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_clear_flag(sidebar_cont, LV_OBJ_FLAG_CLICKABLE); + menu->sidebar = sidebar_cont; + + lv_obj_t * sidebar_header = lv_obj_class_create_obj(&lv_menu_sidebar_header_cont_class, sidebar_cont); + lv_obj_class_init_obj(sidebar_header); + lv_obj_set_size(sidebar_header, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(sidebar_header, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(sidebar_header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_clear_flag(sidebar_header, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_flag(sidebar_header, LV_OBJ_FLAG_EVENT_BUBBLE); + menu->sidebar_header = sidebar_header; + + lv_obj_t * sidebar_header_back_btn = lv_btn_create(menu->sidebar_header); + lv_obj_add_event_cb(sidebar_header_back_btn, lv_menu_back_event_cb, LV_EVENT_CLICKED, menu); + lv_obj_add_flag(sidebar_header_back_btn, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_set_flex_flow(sidebar_header_back_btn, LV_FLEX_FLOW_ROW); + menu->sidebar_header_back_btn = sidebar_header_back_btn; + + lv_obj_t * sidebar_header_back_icon = lv_img_create(menu->sidebar_header_back_btn); + lv_img_set_src(sidebar_header_back_icon, LV_SYMBOL_LEFT); + + lv_obj_t * sidebar_header_title = lv_label_create(menu->sidebar_header); + lv_obj_add_flag(sidebar_header_title, LV_OBJ_FLAG_HIDDEN); + menu->sidebar_header_title = sidebar_header_title; + + menu->sidebar_generated = true; + } + + lv_obj_set_parent(page, menu->sidebar); + + lv_menu_refr_sidebar_header_mode(obj); + } + else { + /* Sidebar should be disabled */ + if(menu->sidebar_generated) { + lv_obj_set_parent(menu->sidebar_page, menu->storage); + lv_obj_del(menu->sidebar); + + menu->sidebar_generated = false; + } + } + + menu->sidebar_page = page; + lv_menu_refr(obj); +} + +void lv_menu_set_mode_header(lv_obj_t * obj, lv_menu_mode_header_t mode_header) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + if(menu->mode_header != mode_header) { + menu->mode_header = mode_header; + lv_menu_refr_main_header_mode(obj); + if(menu->sidebar_generated) lv_menu_refr_sidebar_header_mode(obj); + } +} + +void lv_menu_set_mode_root_back_btn(lv_obj_t * obj, lv_menu_mode_root_back_btn_t mode_root_back_btn) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + if(menu->mode_root_back_btn != mode_root_back_btn) { + menu->mode_root_back_btn = mode_root_back_btn; + lv_menu_refr(obj); + } +} + +void lv_menu_set_load_page_event(lv_obj_t * menu, lv_obj_t * obj, lv_obj_t * page) +{ + LV_ASSERT_OBJ(menu, MY_CLASS); + + lv_obj_add_flag(obj, LV_OBJ_FLAG_CLICKABLE); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_SCROLLABLE); + lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ON_FOCUS); + + /* Remove old event */ + if(lv_obj_remove_event_cb(obj, lv_menu_load_page_event_cb)) { + lv_event_send(obj, LV_EVENT_DELETE, NULL); + lv_obj_remove_event_cb(obj, lv_menu_obj_del_event_cb); + } + + lv_menu_load_page_event_data_t * event_data = lv_mem_alloc(sizeof(lv_menu_load_page_event_data_t)); + event_data->menu = menu; + event_data->page = page; + + lv_obj_add_event_cb(obj, lv_menu_load_page_event_cb, LV_EVENT_CLICKED, event_data); + lv_obj_add_event_cb(obj, lv_menu_obj_del_event_cb, LV_EVENT_DELETE, event_data); +} + +/*===================== + * Getter functions + *====================*/ +lv_obj_t * lv_menu_get_cur_main_page(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->main_page; +} + +lv_obj_t * lv_menu_get_cur_sidebar_page(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->sidebar_page; +} + +lv_obj_t * lv_menu_get_main_header(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->main_header; +} + +lv_obj_t * lv_menu_get_main_header_back_btn(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->main_header_back_btn; +} + +lv_obj_t * lv_menu_get_sidebar_header(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->sidebar_header; +} + +lv_obj_t * lv_menu_get_sidebar_header_back_btn(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + return menu->sidebar_header_back_btn; +} + +bool lv_menu_back_btn_is_root(lv_obj_t * menu, lv_obj_t * obj) +{ + LV_ASSERT_OBJ(menu, MY_CLASS); + + if(obj == ((lv_menu_t *)menu)->sidebar_header_back_btn) { + return true; + } + + if(obj == ((lv_menu_t *)menu)->main_header_back_btn && ((lv_menu_t *)menu)->prev_depth <= 1) { + return true; + } + + return false; +} + +void lv_menu_clear_history(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + lv_ll_t * history_ll = &(menu->history_ll); + + _lv_ll_clear(history_ll); + + menu->cur_depth = 0; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_menu_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_obj_set_layout(obj, LV_LAYOUT_FLEX); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + + lv_menu_t * menu = (lv_menu_t *)obj; + + menu->mode_header = LV_MENU_HEADER_TOP_FIXED; + menu->mode_root_back_btn = LV_MENU_ROOT_BACK_BTN_DISABLED; + menu->cur_depth = 0; + menu->prev_depth = 0; + menu->sidebar_generated = false; + + _lv_ll_init(&(menu->history_ll), sizeof(lv_menu_history_t)); + + menu->storage = lv_obj_create(obj); + lv_obj_add_flag(menu->storage, LV_OBJ_FLAG_HIDDEN); + + menu->sidebar = NULL; + menu->sidebar_header = NULL; + menu->sidebar_header_back_btn = NULL; + menu->sidebar_header_title = NULL; + menu->sidebar_page = NULL; + + lv_obj_t * main_cont = lv_obj_class_create_obj(&lv_menu_main_cont_class, obj); + lv_obj_class_init_obj(main_cont); + lv_obj_set_height(main_cont, LV_PCT(100)); + lv_obj_set_flex_grow(main_cont, 1); + lv_obj_set_flex_flow(main_cont, LV_FLEX_FLOW_COLUMN); + lv_obj_add_flag(main_cont, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_clear_flag(main_cont, LV_OBJ_FLAG_CLICKABLE); + menu->main = main_cont; + + lv_obj_t * main_header = lv_obj_class_create_obj(&lv_menu_main_header_cont_class, main_cont); + lv_obj_class_init_obj(main_header); + lv_obj_set_size(main_header, LV_PCT(100), LV_SIZE_CONTENT); + lv_obj_set_flex_flow(main_header, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(main_header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_clear_flag(main_header, LV_OBJ_FLAG_CLICKABLE); + lv_obj_add_flag(main_header, LV_OBJ_FLAG_EVENT_BUBBLE); + menu->main_header = main_header; + + /* Create the default simple back btn and title */ + lv_obj_t * main_header_back_btn = lv_btn_create(menu->main_header); + lv_obj_add_event_cb(main_header_back_btn, lv_menu_back_event_cb, LV_EVENT_CLICKED, menu); + lv_obj_add_flag(main_header_back_btn, LV_OBJ_FLAG_EVENT_BUBBLE); + lv_obj_set_flex_flow(main_header_back_btn, LV_FLEX_FLOW_ROW); + menu->main_header_back_btn = main_header_back_btn; + + lv_obj_t * main_header_back_icon = lv_img_create(menu->main_header_back_btn); + lv_img_set_src(main_header_back_icon, LV_SYMBOL_LEFT); + + lv_obj_t * main_header_title = lv_label_create(menu->main_header); + lv_obj_add_flag(main_header_title, LV_OBJ_FLAG_HIDDEN); + menu->main_header_title = main_header_title; + + menu->main_page = NULL; + menu->selected_tab = NULL; + + lv_obj_add_event_cb(obj, lv_menu_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, menu); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_menu_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_menu_t * menu = (lv_menu_t *)obj; + lv_ll_t * history_ll = &(menu->history_ll); + + _lv_ll_clear(history_ll); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_menu_page_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + + lv_menu_t * menu = (lv_menu_t *)lv_obj_get_parent(obj); + + lv_obj_set_parent(obj, ((lv_menu_t *)menu)->storage); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_add_flag(obj, LV_OBJ_FLAG_EVENT_BUBBLE); +} + +static void lv_menu_page_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + + lv_menu_page_t * page = (lv_menu_page_t *)obj; + + if(page->title != NULL) { + lv_mem_free(page->title); + page->title = NULL; + } +} + +static void lv_menu_cont_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(obj, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE); +} + +static void lv_menu_section_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE); +} + +static void lv_menu_refr_sidebar_header_mode(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + if(menu->sidebar_header == NULL || menu->sidebar_page == NULL) return; + + switch(menu->mode_header) { + case LV_MENU_HEADER_TOP_FIXED: + /* Content should fill the remaining space */ + lv_obj_move_to_index(menu->sidebar_header, 0); + lv_obj_set_flex_grow(menu->sidebar_page, 1); + break; + case LV_MENU_HEADER_TOP_UNFIXED: + lv_obj_move_to_index(menu->sidebar_header, 0); + lv_obj_set_flex_grow(menu->sidebar_page, 0); + break; + case LV_MENU_HEADER_BOTTOM_FIXED: + lv_obj_move_to_index(menu->sidebar_header, 1); + lv_obj_set_flex_grow(menu->sidebar_page, 1); + break; + } + + lv_obj_refr_size(menu->sidebar_header); + lv_obj_refr_size(menu->sidebar_page); + + if(lv_obj_get_content_height(menu->sidebar_header) == 0) { + lv_obj_add_flag(menu->sidebar_header, LV_OBJ_FLAG_HIDDEN); + } + else { + lv_obj_clear_flag(menu->sidebar_header, LV_OBJ_FLAG_HIDDEN); + } +} + +static void lv_menu_refr_main_header_mode(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + lv_menu_t * menu = (lv_menu_t *)obj; + + if(menu->main_header == NULL || menu->main_page == NULL) return; + + switch(menu->mode_header) { + case LV_MENU_HEADER_TOP_FIXED: + /* Content should fill the remaining space */ + lv_obj_move_to_index(menu->main_header, 0); + lv_obj_set_flex_grow(menu->main_page, 1); + break; + case LV_MENU_HEADER_TOP_UNFIXED: + lv_obj_move_to_index(menu->main_header, 0); + lv_obj_set_flex_grow(menu->main_page, 0); + break; + case LV_MENU_HEADER_BOTTOM_FIXED: + lv_obj_move_to_index(menu->main_header, 1); + lv_obj_set_flex_grow(menu->main_page, 1); + break; + } + + lv_obj_refr_size(menu->main_header); + lv_obj_refr_size(menu->main_page); + lv_obj_update_layout(menu->main_header); + + if(lv_obj_get_content_height(menu->main_header) == 0) { + lv_obj_add_flag(menu->main_header, LV_OBJ_FLAG_HIDDEN); + } + else { + lv_obj_clear_flag(menu->main_header, LV_OBJ_FLAG_HIDDEN); + } +} + +static void lv_menu_load_page_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_menu_load_page_event_data_t * event_data = lv_event_get_user_data(e); + lv_menu_t * menu = (lv_menu_t *)(event_data->menu); + lv_obj_t * page = event_data->page; + + if(menu->sidebar_page != NULL) { + /* Check if clicked obj is in the sidebar */ + bool sidebar = false; + lv_obj_t * parent = obj; + + while(parent) { + if(parent == (lv_obj_t *)menu) break; + if(parent == menu->sidebar) { + sidebar = true; + break; + } + parent = lv_obj_get_parent(parent); + } + + if(sidebar) { + /* Clear checked state of previous obj */ + if(menu->selected_tab != obj && menu->selected_tab != NULL) { + lv_obj_clear_state(menu->selected_tab, LV_STATE_CHECKED); + } + + lv_menu_clear_history((lv_obj_t *)menu); + + menu->selected_tab = obj; + } + } + + lv_menu_set_page((lv_obj_t *)menu, page); + + if(lv_group_get_default() != NULL && menu->sidebar_page == NULL) { + /* Sidebar is not supported for now*/ + lv_group_focus_next(lv_group_get_default()); + } +} + +static void lv_menu_obj_del_event_cb(lv_event_t * e) +{ + lv_menu_load_page_event_data_t * event_data = lv_event_get_user_data(e); + lv_mem_free(event_data); +} + +static void lv_menu_back_event_cb(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + /* LV_EVENT_CLICKED */ + if(code == LV_EVENT_CLICKED) { + lv_obj_t * obj = lv_event_get_target(e); + lv_menu_t * menu = (lv_menu_t *)lv_event_get_user_data(e); + + if(!(obj == menu->main_header_back_btn || obj == menu->sidebar_header_back_btn)) return; + + menu->prev_depth = menu->cur_depth; /* Save the previous value for user event handler */ + + if(lv_menu_back_btn_is_root((lv_obj_t *)menu, obj)) return; + + lv_ll_t * history_ll = &(menu->history_ll); + + /* The current menu */ + lv_menu_history_t * act_hist = _lv_ll_get_head(history_ll); + + /* The previous menu */ + lv_menu_history_t * prev_hist = _lv_ll_get_next(history_ll, act_hist); + + if(prev_hist != NULL) { + /* Previous menu exists */ + /* Delete the current item from the history */ + _lv_ll_remove(history_ll, act_hist); + lv_mem_free(act_hist); + menu->cur_depth--; + /* Create the previous menu. + * Remove it from the history because `lv_menu_set_page` will add it again */ + _lv_ll_remove(history_ll, prev_hist); + menu->cur_depth--; + lv_menu_set_page(&(menu->obj), prev_hist->page); + + lv_mem_free(prev_hist); + } + } +} + +static void lv_menu_value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_user_data(e); + lv_menu_t * menu = (lv_menu_t *)obj; + + lv_menu_page_t * main_page = (lv_menu_page_t *)lv_menu_get_cur_main_page(obj); + if(main_page != NULL && menu->main_header_title != NULL) { + if(main_page->title != NULL) { + lv_label_set_text(menu->main_header_title, main_page->title); + lv_obj_clear_flag(menu->main_header_title, LV_OBJ_FLAG_HIDDEN); + } + else { + lv_obj_add_flag(menu->main_header_title, LV_OBJ_FLAG_HIDDEN); + } + } + + lv_menu_page_t * sidebar_page = (lv_menu_page_t *)lv_menu_get_cur_sidebar_page(obj); + if(sidebar_page != NULL && menu->sidebar_header_title != NULL) { + if(sidebar_page->title != NULL) { + lv_label_set_text(menu->sidebar_header_title, sidebar_page->title); + lv_obj_clear_flag(menu->sidebar_header_title, LV_OBJ_FLAG_HIDDEN); + } + else { + lv_obj_add_flag(menu->sidebar_header_title, LV_OBJ_FLAG_HIDDEN); + } + } +} +#endif /*LV_USE_MENU*/ diff --git a/lib/lvgl/src/extra/widgets/menu/lv_menu.h b/lib/lvgl/src/extra/widgets/menu/lv_menu.h new file mode 100644 index 00000000..0449059f --- /dev/null +++ b/lib/lvgl/src/extra/widgets/menu/lv_menu.h @@ -0,0 +1,233 @@ +/** + * @file lv_menu.h + * + */ + +#ifndef LV_MENU_H +#define LV_MENU_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" + +#if LV_USE_MENU + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +enum { + LV_MENU_HEADER_TOP_FIXED, /* Header is positioned at the top */ + LV_MENU_HEADER_TOP_UNFIXED, /* Header is positioned at the top and can be scrolled out of view*/ + LV_MENU_HEADER_BOTTOM_FIXED /* Header is positioned at the bottom */ +}; +typedef uint8_t lv_menu_mode_header_t; + +enum { + LV_MENU_ROOT_BACK_BTN_DISABLED, + LV_MENU_ROOT_BACK_BTN_ENABLED +}; +typedef uint8_t lv_menu_mode_root_back_btn_t; + +typedef struct lv_menu_load_page_event_data_t { + lv_obj_t * menu; + lv_obj_t * page; +} lv_menu_load_page_event_data_t; + +typedef struct { + lv_obj_t * page; +} lv_menu_history_t; + +typedef struct { + lv_obj_t obj; + lv_obj_t * storage; /* a pointer to obj that is the parent of all pages not displayed */ + lv_obj_t * main; + lv_obj_t * main_page; + lv_obj_t * main_header; + lv_obj_t * + main_header_back_btn; /* a pointer to obj that on click triggers back btn event handler, can be same as 'main_header' */ + lv_obj_t * main_header_title; + lv_obj_t * sidebar; + lv_obj_t * sidebar_page; + lv_obj_t * sidebar_header; + lv_obj_t * + sidebar_header_back_btn; /* a pointer to obj that on click triggers back btn event handler, can be same as 'sidebar_header' */ + lv_obj_t * sidebar_header_title; + lv_obj_t * selected_tab; + lv_ll_t history_ll; + uint8_t cur_depth; + uint8_t prev_depth; + uint8_t sidebar_generated : 1; + lv_menu_mode_header_t mode_header : 2; + lv_menu_mode_root_back_btn_t mode_root_back_btn : 1; +} lv_menu_t; + +typedef struct { + lv_obj_t obj; + char * title; +} lv_menu_page_t; + +extern const lv_obj_class_t lv_menu_class; +extern const lv_obj_class_t lv_menu_page_class; +extern const lv_obj_class_t lv_menu_cont_class; +extern const lv_obj_class_t lv_menu_section_class; +extern const lv_obj_class_t lv_menu_separator_class; +extern const lv_obj_class_t lv_menu_sidebar_cont_class; +extern const lv_obj_class_t lv_menu_main_cont_class; +extern const lv_obj_class_t lv_menu_sidebar_header_cont_class; +extern const lv_obj_class_t lv_menu_main_header_cont_class; +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a menu object + * @param parent pointer to an object, it will be the parent of the new menu + * @return pointer to the created menu + */ +lv_obj_t * lv_menu_create(lv_obj_t * parent); + +/** + * Create a menu page object + * @param parent pointer to menu object + * @param title pointer to text for title in header (NULL to not display title) + * @return pointer to the created menu page + */ +lv_obj_t * lv_menu_page_create(lv_obj_t * parent, char * title); + +/** + * Create a menu cont object + * @param parent pointer to an object, it will be the parent of the new menu cont object + * @return pointer to the created menu cont + */ +lv_obj_t * lv_menu_cont_create(lv_obj_t * parent); + +/** + * Create a menu section object + * @param parent pointer to an object, it will be the parent of the new menu section object + * @return pointer to the created menu section + */ +lv_obj_t * lv_menu_section_create(lv_obj_t * parent); + +/** + * Create a menu separator object + * @param parent pointer to an object, it will be the parent of the new menu separator object + * @return pointer to the created menu separator + */ +lv_obj_t * lv_menu_separator_create(lv_obj_t * parent); +/*===================== + * Setter functions + *====================*/ +/** + * Set menu page to display in main + * @param obj pointer to the menu + * @param page pointer to the menu page to set (NULL to clear main and clear menu history) + */ +void lv_menu_set_page(lv_obj_t * obj, lv_obj_t * page); + +/** + * Set menu page to display in sidebar + * @param obj pointer to the menu + * @param page pointer to the menu page to set (NULL to clear sidebar) + */ +void lv_menu_set_sidebar_page(lv_obj_t * obj, lv_obj_t * page); + +/** + * Set the how the header should behave and its position + * @param obj pointer to a menu + * @param mode_header + */ +void lv_menu_set_mode_header(lv_obj_t * obj, lv_menu_mode_header_t mode_header); + +/** + * Set whether back button should appear at root + * @param obj pointer to a menu + * @param mode_root_back_btn + */ +void lv_menu_set_mode_root_back_btn(lv_obj_t * obj, lv_menu_mode_root_back_btn_t mode_root_back_btn); + +/** + * Add menu to the menu item + * @param menu pointer to the menu + * @param obj pointer to the obj + * @param page pointer to the page to load when obj is clicked + */ +void lv_menu_set_load_page_event(lv_obj_t * menu, lv_obj_t * obj, lv_obj_t * page); + +/*===================== + * Getter functions + *====================*/ +/** +* Get a pointer to menu page that is currently displayed in main +* @param obj pointer to the menu +* @return pointer to current page +*/ +lv_obj_t * lv_menu_get_cur_main_page(lv_obj_t * obj); + +/** +* Get a pointer to menu page that is currently displayed in sidebar +* @param obj pointer to the menu +* @return pointer to current page +*/ +lv_obj_t * lv_menu_get_cur_sidebar_page(lv_obj_t * obj); + +/** +* Get a pointer to main header obj +* @param obj pointer to the menu +* @return pointer to main header obj +*/ +lv_obj_t * lv_menu_get_main_header(lv_obj_t * obj); + +/** +* Get a pointer to main header back btn obj +* @param obj pointer to the menu +* @return pointer to main header back btn obj +*/ +lv_obj_t * lv_menu_get_main_header_back_btn(lv_obj_t * obj); + +/** +* Get a pointer to sidebar header obj +* @param obj pointer to the menu +* @return pointer to sidebar header obj +*/ +lv_obj_t * lv_menu_get_sidebar_header(lv_obj_t * obj); + +/** +* Get a pointer to sidebar header obj +* @param obj pointer to the menu +* @return pointer to sidebar header back btn obj +*/ +lv_obj_t * lv_menu_get_sidebar_header_back_btn(lv_obj_t * obj); + +/** + * Check if an obj is a root back btn + * @param menu pointer to the menu + * @return true if it is a root back btn + */ +bool lv_menu_back_btn_is_root(lv_obj_t * menu, lv_obj_t * obj); + +/** + * Clear menu history + * @param obj pointer to the menu + */ +void lv_menu_clear_history(lv_obj_t * obj); +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_MENU*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_MENU_H*/ diff --git a/lib/lvgl/src/extra/widgets/meter/lv_meter.c b/lib/lvgl/src/extra/widgets/meter/lv_meter.c new file mode 100644 index 00000000..668ab97e --- /dev/null +++ b/lib/lvgl/src/extra/widgets/meter/lv_meter.c @@ -0,0 +1,697 @@ +/** + * @file lv_meter.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_meter.h" +#if LV_USE_METER != 0 + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_meter_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_meter_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_meter_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_meter_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void draw_arcs(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area); +static void draw_ticks_and_labels(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area); +static void draw_needles(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area); +static void inv_arc(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t old_value, int32_t new_value); +static void inv_line(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_meter_class = { + .constructor_cb = lv_meter_constructor, + .destructor_cb = lv_meter_destructor, + .event_cb = lv_meter_event, + .instance_size = sizeof(lv_meter_t), + .base_class = &lv_obj_class +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_meter_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Add scale + *====================*/ + +lv_meter_scale_t * lv_meter_add_scale(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + + lv_meter_scale_t * scale = _lv_ll_ins_head(&meter->scale_ll); + LV_ASSERT_MALLOC(scale); + lv_memset_00(scale, sizeof(lv_meter_scale_t)); + + scale->angle_range = 270; + scale->rotation = 90 + (360 - scale->angle_range) / 2; + scale->min = 0; + scale->max = 100; + scale->tick_cnt = 6; + scale->tick_length = 8; + scale->tick_width = 2; + scale->label_gap = 2; + + return scale; +} + +void lv_meter_set_scale_ticks(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t cnt, uint16_t width, uint16_t len, + lv_color_t color) +{ + scale->tick_cnt = cnt; + scale->tick_width = width; + scale->tick_length = len; + scale->tick_color = color; + lv_obj_invalidate(obj); +} + +void lv_meter_set_scale_major_ticks(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t nth, uint16_t width, + uint16_t len, lv_color_t color, int16_t label_gap) +{ + scale->tick_major_nth = nth; + scale->tick_major_width = width; + scale->tick_major_length = len; + scale->tick_major_color = color; + scale->label_gap = label_gap; + lv_obj_invalidate(obj); +} + +void lv_meter_set_scale_range(lv_obj_t * obj, lv_meter_scale_t * scale, int32_t min, int32_t max, uint32_t angle_range, + uint32_t rotation) +{ + scale->min = min; + scale->max = max; + scale->angle_range = angle_range; + scale->rotation = rotation; + lv_obj_invalidate(obj); +} + +/*===================== + * Add indicator + *====================*/ + +lv_meter_indicator_t * lv_meter_add_needle_line(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t width, + lv_color_t color, int16_t r_mod) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + lv_meter_indicator_t * indic = _lv_ll_ins_head(&meter->indicator_ll); + LV_ASSERT_MALLOC(indic); + lv_memset_00(indic, sizeof(lv_meter_indicator_t)); + indic->scale = scale; + indic->opa = LV_OPA_COVER; + + indic->type = LV_METER_INDICATOR_TYPE_NEEDLE_LINE; + indic->type_data.needle_line.width = width; + indic->type_data.needle_line.color = color; + indic->type_data.needle_line.r_mod = r_mod; + lv_obj_invalidate(obj); + + return indic; +} + +lv_meter_indicator_t * lv_meter_add_needle_img(lv_obj_t * obj, lv_meter_scale_t * scale, const void * src, + lv_coord_t pivot_x, lv_coord_t pivot_y) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + lv_meter_indicator_t * indic = _lv_ll_ins_head(&meter->indicator_ll); + LV_ASSERT_MALLOC(indic); + lv_memset_00(indic, sizeof(lv_meter_indicator_t)); + indic->scale = scale; + indic->opa = LV_OPA_COVER; + + indic->type = LV_METER_INDICATOR_TYPE_NEEDLE_IMG; + indic->type_data.needle_img.src = src; + indic->type_data.needle_img.pivot.x = pivot_x; + indic->type_data.needle_img.pivot.y = pivot_y; + lv_obj_invalidate(obj); + + return indic; +} + +lv_meter_indicator_t * lv_meter_add_arc(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t width, lv_color_t color, + int16_t r_mod) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + lv_meter_indicator_t * indic = _lv_ll_ins_head(&meter->indicator_ll); + LV_ASSERT_MALLOC(indic); + lv_memset_00(indic, sizeof(lv_meter_indicator_t)); + indic->scale = scale; + indic->opa = LV_OPA_COVER; + + indic->type = LV_METER_INDICATOR_TYPE_ARC; + indic->type_data.arc.width = width; + indic->type_data.arc.color = color; + indic->type_data.arc.r_mod = r_mod; + + lv_obj_invalidate(obj); + return indic; +} + +lv_meter_indicator_t * lv_meter_add_scale_lines(lv_obj_t * obj, lv_meter_scale_t * scale, lv_color_t color_start, + lv_color_t color_end, bool local, int16_t width_mod) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + lv_meter_indicator_t * indic = _lv_ll_ins_head(&meter->indicator_ll); + LV_ASSERT_MALLOC(indic); + lv_memset_00(indic, sizeof(lv_meter_indicator_t)); + indic->scale = scale; + indic->opa = LV_OPA_COVER; + + indic->type = LV_METER_INDICATOR_TYPE_SCALE_LINES; + indic->type_data.scale_lines.color_start = color_start; + indic->type_data.scale_lines.color_end = color_end; + indic->type_data.scale_lines.local_grad = local; + indic->type_data.scale_lines.width_mod = width_mod; + + lv_obj_invalidate(obj); + return indic; +} + +/*===================== + * Set indicator value + *====================*/ + +void lv_meter_set_indicator_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value) +{ + int32_t old_start = indic->start_value; + int32_t old_end = indic->end_value; + indic->start_value = value; + indic->end_value = value; + + if(indic->type == LV_METER_INDICATOR_TYPE_ARC) { + inv_arc(obj, indic, old_start, value); + inv_arc(obj, indic, old_end, value); + } + else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG || indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_LINE) { + inv_line(obj, indic, old_start); + inv_line(obj, indic, old_end); + inv_line(obj, indic, value); + } + else { + lv_obj_invalidate(obj); + } +} + +void lv_meter_set_indicator_start_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value) +{ + int32_t old_value = indic->start_value; + indic->start_value = value; + + if(indic->type == LV_METER_INDICATOR_TYPE_ARC) { + inv_arc(obj, indic, old_value, value); + } + else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG || indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_LINE) { + inv_line(obj, indic, old_value); + inv_line(obj, indic, value); + } + else { + lv_obj_invalidate(obj); + } +} + +void lv_meter_set_indicator_end_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value) +{ + int32_t old_value = indic->end_value; + indic->end_value = value; + + if(indic->type == LV_METER_INDICATOR_TYPE_ARC) { + inv_arc(obj, indic, old_value, value); + } + else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG || indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_LINE) { + inv_line(obj, indic, old_value); + inv_line(obj, indic, value); + } + else { + lv_obj_invalidate(obj); + } +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_meter_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_TRACE_OBJ_CREATE("begin"); + + lv_meter_t * meter = (lv_meter_t *)obj; + + _lv_ll_init(&meter->scale_ll, sizeof(lv_meter_scale_t)); + _lv_ll_init(&meter->indicator_ll, sizeof(lv_meter_indicator_t)); + + LV_TRACE_OBJ_CREATE("finished"); +} + +static void lv_meter_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_meter_t * meter = (lv_meter_t *)obj; + _lv_ll_clear(&meter->indicator_ll); + _lv_ll_clear(&meter->scale_ll); + +} + +static void lv_meter_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + lv_res_t res = lv_obj_event_base(MY_CLASS, e); + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + if(code == LV_EVENT_DRAW_MAIN) { + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + lv_area_t scale_area; + lv_obj_get_content_coords(obj, &scale_area); + + draw_arcs(obj, draw_ctx, &scale_area); + draw_ticks_and_labels(obj, draw_ctx, &scale_area); + draw_needles(obj, draw_ctx, &scale_area); + + lv_coord_t r_edge = lv_area_get_width(&scale_area) / 2; + lv_point_t scale_center; + scale_center.x = scale_area.x1 + r_edge; + scale_center.y = scale_area.y1 + r_edge; + + lv_draw_rect_dsc_t mid_dsc; + lv_draw_rect_dsc_init(&mid_dsc); + lv_obj_init_draw_rect_dsc(obj, LV_PART_INDICATOR, &mid_dsc); + lv_coord_t w = lv_obj_get_style_width(obj, LV_PART_INDICATOR) / 2; + lv_coord_t h = lv_obj_get_style_height(obj, LV_PART_INDICATOR) / 2; + lv_area_t nm_cord; + nm_cord.x1 = scale_center.x - w; + nm_cord.y1 = scale_center.y - h; + nm_cord.x2 = scale_center.x + w; + nm_cord.y2 = scale_center.y + h; + lv_draw_rect(draw_ctx, &mid_dsc, &nm_cord); + } +} + +static void draw_arcs(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area) +{ + lv_meter_t * meter = (lv_meter_t *)obj; + + lv_draw_arc_dsc_t arc_dsc; + lv_draw_arc_dsc_init(&arc_dsc); + arc_dsc.rounded = lv_obj_get_style_arc_rounded(obj, LV_PART_ITEMS); + + lv_coord_t r_out = lv_area_get_width(scale_area) / 2 ; + lv_point_t scale_center; + scale_center.x = scale_area->x1 + r_out; + scale_center.y = scale_area->y1 + r_out; + + lv_opa_t opa_main = lv_obj_get_style_opa(obj, LV_PART_MAIN); + lv_meter_indicator_t * indic; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.arc_dsc = &arc_dsc; + part_draw_dsc.part = LV_PART_INDICATOR; + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.type = LV_METER_DRAW_PART_ARC; + + _LV_LL_READ_BACK(&meter->indicator_ll, indic) { + if(indic->type != LV_METER_INDICATOR_TYPE_ARC) continue; + + arc_dsc.color = indic->type_data.arc.color; + arc_dsc.width = indic->type_data.arc.width; + arc_dsc.opa = indic->opa > LV_OPA_MAX ? opa_main : (opa_main * indic->opa) >> 8; + + lv_meter_scale_t * scale = indic->scale; + + int32_t start_angle = lv_map(indic->start_value, scale->min, scale->max, scale->rotation, + scale->rotation + scale->angle_range); + int32_t end_angle = lv_map(indic->end_value, scale->min, scale->max, scale->rotation, + scale->rotation + scale->angle_range); + + part_draw_dsc.radius = r_out + indic->type_data.arc.r_mod; + part_draw_dsc.sub_part_ptr = indic; + part_draw_dsc.p1 = &scale_center; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_arc(draw_ctx, &arc_dsc, &scale_center, part_draw_dsc.radius, start_angle, end_angle); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } +} + +static void draw_ticks_and_labels(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area) +{ + lv_meter_t * meter = (lv_meter_t *)obj; + + lv_point_t p_center; + lv_coord_t r_edge = LV_MIN(lv_area_get_width(scale_area) / 2, lv_area_get_height(scale_area) / 2); + p_center.x = scale_area->x1 + r_edge; + p_center.y = scale_area->y1 + r_edge; + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_TICKS, &line_dsc); + line_dsc.raw_end = 1; + + lv_draw_label_dsc_t label_dsc; + lv_draw_label_dsc_init(&label_dsc); + lv_obj_init_draw_label_dsc(obj, LV_PART_TICKS, &label_dsc); + + lv_meter_scale_t * scale; + + lv_draw_mask_radius_param_t inner_minor_mask; + lv_draw_mask_radius_param_t inner_major_mask; + lv_draw_mask_radius_param_t outer_mask; + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.part = LV_PART_TICKS; + part_draw_dsc.type = LV_METER_DRAW_PART_TICK; + part_draw_dsc.line_dsc = &line_dsc; + + _LV_LL_READ_BACK(&meter->scale_ll, scale) { + part_draw_dsc.sub_part_ptr = scale; + + lv_coord_t r_out = r_edge; + lv_coord_t r_in_minor = r_out - scale->tick_length; + lv_coord_t r_in_major = r_out - scale->tick_major_length; + + lv_area_t area_inner_minor; + area_inner_minor.x1 = p_center.x - r_in_minor; + area_inner_minor.y1 = p_center.y - r_in_minor; + area_inner_minor.x2 = p_center.x + r_in_minor; + area_inner_minor.y2 = p_center.y + r_in_minor; + lv_draw_mask_radius_init(&inner_minor_mask, &area_inner_minor, LV_RADIUS_CIRCLE, true); + + lv_area_t area_inner_major; + area_inner_major.x1 = p_center.x - r_in_major; + area_inner_major.y1 = p_center.y - r_in_major; + area_inner_major.x2 = p_center.x + r_in_major - 1; + area_inner_major.y2 = p_center.y + r_in_major - 1; + lv_draw_mask_radius_init(&inner_major_mask, &area_inner_major, LV_RADIUS_CIRCLE, true); + + lv_area_t area_outer; + area_outer.x1 = p_center.x - r_out; + area_outer.y1 = p_center.y - r_out; + area_outer.x2 = p_center.x + r_out - 1; + area_outer.y2 = p_center.y + r_out - 1; + lv_draw_mask_radius_init(&outer_mask, &area_outer, LV_RADIUS_CIRCLE, false); + int16_t outer_mask_id = lv_draw_mask_add(&outer_mask, NULL); + + int16_t inner_act_mask_id = LV_MASK_ID_INV; /*Will be added later*/ + + uint32_t minor_cnt = scale->tick_major_nth ? scale->tick_major_nth - 1 : 0xFFFF; + uint16_t i; + for(i = 0; i < scale->tick_cnt; i++) { + minor_cnt++; + bool major = false; + if(minor_cnt == scale->tick_major_nth) { + minor_cnt = 0; + major = true; + } + + int32_t value_of_line = lv_map(i, 0, scale->tick_cnt - 1, scale->min, scale->max); + part_draw_dsc.value = value_of_line; + + lv_color_t line_color = major ? scale->tick_major_color : scale->tick_color; + lv_color_t line_color_ori = line_color; + + lv_coord_t line_width_ori = major ? scale->tick_major_width : scale->tick_width; + lv_coord_t line_width = line_width_ori; + + lv_meter_indicator_t * indic; + _LV_LL_READ_BACK(&meter->indicator_ll, indic) { + if(indic->type != LV_METER_INDICATOR_TYPE_SCALE_LINES) continue; + if(value_of_line >= indic->start_value && value_of_line <= indic->end_value) { + line_width += indic->type_data.scale_lines.width_mod; + + if(indic->type_data.scale_lines.color_start.full == indic->type_data.scale_lines.color_end.full) { + line_color = indic->type_data.scale_lines.color_start; + } + else { + lv_opa_t ratio; + if(indic->type_data.scale_lines.local_grad) { + ratio = lv_map(value_of_line, indic->start_value, indic->end_value, LV_OPA_TRANSP, LV_OPA_COVER); + } + else { + ratio = lv_map(value_of_line, scale->min, scale->max, LV_OPA_TRANSP, LV_OPA_COVER); + } + line_color = lv_color_mix(indic->type_data.scale_lines.color_end, indic->type_data.scale_lines.color_start, ratio); + } + } + } + + int32_t angle_upscale = ((i * scale->angle_range) * 10) / (scale->tick_cnt - 1) + + scale->rotation * 10; + + line_dsc.color = line_color; + line_dsc.width = line_width; + + /*Draw a little bit longer lines to be sure the mask will clip them correctly + *and to get a better precision*/ + lv_point_t p_outer; + p_outer.x = p_center.x + r_out + LV_MAX(LV_DPI_DEF, r_out); + p_outer.y = p_center.y; + lv_point_transform(&p_outer, angle_upscale, 256, &p_center); + + part_draw_dsc.p1 = &p_center; + part_draw_dsc.p2 = &p_outer; + part_draw_dsc.id = i; + part_draw_dsc.label_dsc = &label_dsc; + + /*Draw the text*/ + if(major) { + lv_draw_mask_remove_id(outer_mask_id); + uint32_t r_text = r_in_major - scale->label_gap; + lv_point_t p; + p.x = p_center.x + r_text; + p.y = p_center.y; + lv_point_transform(&p, angle_upscale, 256, &p_center); + + lv_draw_label_dsc_t label_dsc_tmp; + lv_memcpy(&label_dsc_tmp, &label_dsc, sizeof(label_dsc_tmp)); + + part_draw_dsc.label_dsc = &label_dsc_tmp; + char buf[16]; + + lv_snprintf(buf, sizeof(buf), "%" LV_PRId32, value_of_line); + part_draw_dsc.text = buf; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + + lv_point_t label_size; + lv_txt_get_size(&label_size, part_draw_dsc.text, label_dsc.font, label_dsc.letter_space, label_dsc.line_space, + LV_COORD_MAX, LV_TEXT_FLAG_NONE); + + lv_area_t label_cord; + label_cord.x1 = p.x - label_size.x / 2; + label_cord.y1 = p.y - label_size.y / 2; + label_cord.x2 = label_cord.x1 + label_size.x; + label_cord.y2 = label_cord.y1 + label_size.y; + + lv_draw_label(draw_ctx, part_draw_dsc.label_dsc, &label_cord, part_draw_dsc.text, NULL); + + outer_mask_id = lv_draw_mask_add(&outer_mask, NULL); + } + else { + part_draw_dsc.label_dsc = NULL; + part_draw_dsc.text = NULL; + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + } + + inner_act_mask_id = lv_draw_mask_add(major ? &inner_major_mask : &inner_minor_mask, NULL); + lv_draw_line(draw_ctx, &line_dsc, &p_outer, &p_center); + lv_draw_mask_remove_id(inner_act_mask_id); + lv_event_send(obj, LV_EVENT_DRAW_MAIN_END, &part_draw_dsc); + + line_dsc.color = line_color_ori; + line_dsc.width = line_width_ori; + + } + lv_draw_mask_free_param(&inner_minor_mask); + lv_draw_mask_free_param(&inner_major_mask); + lv_draw_mask_free_param(&outer_mask); + lv_draw_mask_remove_id(outer_mask_id); + } +} + + +static void draw_needles(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx, const lv_area_t * scale_area) +{ + lv_meter_t * meter = (lv_meter_t *)obj; + + lv_coord_t r_edge = lv_area_get_width(scale_area) / 2; + lv_point_t scale_center; + scale_center.x = scale_area->x1 + r_edge; + scale_center.y = scale_area->y1 + r_edge; + + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + lv_obj_init_draw_line_dsc(obj, LV_PART_ITEMS, &line_dsc); + + lv_draw_img_dsc_t img_dsc; + lv_draw_img_dsc_init(&img_dsc); + lv_obj_init_draw_img_dsc(obj, LV_PART_ITEMS, &img_dsc); + lv_opa_t opa_main = lv_obj_get_style_opa(obj, LV_PART_MAIN); + + lv_obj_draw_part_dsc_t part_draw_dsc; + lv_obj_draw_dsc_init(&part_draw_dsc, draw_ctx); + part_draw_dsc.class_p = MY_CLASS; + part_draw_dsc.p1 = &scale_center; + + lv_meter_indicator_t * indic; + _LV_LL_READ_BACK(&meter->indicator_ll, indic) { + lv_meter_scale_t * scale = indic->scale; + part_draw_dsc.sub_part_ptr = indic; + + if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_LINE) { + int32_t angle = lv_map(indic->end_value, scale->min, scale->max, scale->rotation, scale->rotation + scale->angle_range); + lv_coord_t r_out = r_edge + scale->r_mod + indic->type_data.needle_line.r_mod; + lv_point_t p_end; + p_end.y = (lv_trigo_sin(angle) * (r_out)) / LV_TRIGO_SIN_MAX + scale_center.y; + p_end.x = (lv_trigo_cos(angle) * (r_out)) / LV_TRIGO_SIN_MAX + scale_center.x; + line_dsc.color = indic->type_data.needle_line.color; + line_dsc.width = indic->type_data.needle_line.width; + line_dsc.opa = indic->opa > LV_OPA_MAX ? opa_main : (opa_main * indic->opa) >> 8; + + part_draw_dsc.id = LV_METER_DRAW_PART_NEEDLE_LINE; + part_draw_dsc.line_dsc = &line_dsc; + part_draw_dsc.p2 = &p_end; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_line(draw_ctx, &line_dsc, &scale_center, &p_end); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG) { + if(indic->type_data.needle_img.src == NULL) continue; + + int32_t angle = lv_map(indic->end_value, scale->min, scale->max, scale->rotation, scale->rotation + scale->angle_range); + lv_img_header_t info; + lv_img_decoder_get_info(indic->type_data.needle_img.src, &info); + lv_area_t a; + a.x1 = scale_center.x - indic->type_data.needle_img.pivot.x; + a.y1 = scale_center.y - indic->type_data.needle_img.pivot.y; + a.x2 = a.x1 + info.w - 1; + a.y2 = a.y1 + info.h - 1; + + img_dsc.opa = indic->opa > LV_OPA_MAX ? opa_main : (opa_main * indic->opa) >> 8; + img_dsc.pivot.x = indic->type_data.needle_img.pivot.x; + img_dsc.pivot.y = indic->type_data.needle_img.pivot.y; + angle = angle * 10; + if(angle > 3600) angle -= 3600; + img_dsc.angle = angle; + + part_draw_dsc.img_dsc = &img_dsc; + + lv_event_send(obj, LV_EVENT_DRAW_PART_BEGIN, &part_draw_dsc); + lv_draw_img(draw_ctx, &img_dsc, &a, indic->type_data.needle_img.src); + lv_event_send(obj, LV_EVENT_DRAW_PART_END, &part_draw_dsc); + } + } +} + +static void inv_arc(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t old_value, int32_t new_value) +{ + bool rounded = lv_obj_get_style_arc_rounded(obj, LV_PART_ITEMS); + + lv_area_t scale_area; + lv_obj_get_content_coords(obj, &scale_area); + + lv_coord_t r_out = lv_area_get_width(&scale_area) / 2; + lv_point_t scale_center; + scale_center.x = scale_area.x1 + r_out; + scale_center.y = scale_area.y1 + r_out; + + r_out += indic->type_data.arc.r_mod; + + lv_meter_scale_t * scale = indic->scale; + + int32_t start_angle = lv_map(old_value, scale->min, scale->max, scale->rotation, scale->angle_range + scale->rotation); + int32_t end_angle = lv_map(new_value, scale->min, scale->max, scale->rotation, scale->angle_range + scale->rotation); + + lv_area_t a; + lv_draw_arc_get_area(scale_center.x, scale_center.y, r_out, LV_MIN(start_angle, end_angle), LV_MAX(start_angle, + end_angle), indic->type_data.arc.width, rounded, &a); + lv_obj_invalidate_area(obj, &a); +} + + +static void inv_line(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value) +{ + lv_area_t scale_area; + lv_obj_get_content_coords(obj, &scale_area); + + lv_coord_t r_out = lv_area_get_width(&scale_area) / 2; + lv_point_t scale_center; + scale_center.x = scale_area.x1 + r_out; + scale_center.y = scale_area.y1 + r_out; + + lv_meter_scale_t * scale = indic->scale; + + if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_LINE) { + int32_t angle = lv_map(value, scale->min, scale->max, scale->rotation, scale->rotation + scale->angle_range); + r_out += scale->r_mod + indic->type_data.needle_line.r_mod; + lv_point_t p_end; + p_end.y = (lv_trigo_sin(angle) * (r_out)) / LV_TRIGO_SIN_MAX + scale_center.y; + p_end.x = (lv_trigo_cos(angle) * (r_out)) / LV_TRIGO_SIN_MAX + scale_center.x; + + lv_area_t a; + a.x1 = LV_MIN(scale_center.x, p_end.x) - indic->type_data.needle_line.width - 2; + a.y1 = LV_MIN(scale_center.y, p_end.y) - indic->type_data.needle_line.width - 2; + a.x2 = LV_MAX(scale_center.x, p_end.x) + indic->type_data.needle_line.width + 2; + a.y2 = LV_MAX(scale_center.y, p_end.y) + indic->type_data.needle_line.width + 2; + + lv_obj_invalidate_area(obj, &a); + } + else if(indic->type == LV_METER_INDICATOR_TYPE_NEEDLE_IMG) { + int32_t angle = lv_map(value, scale->min, scale->max, scale->rotation, scale->rotation + scale->angle_range); + lv_img_header_t info; + lv_img_decoder_get_info(indic->type_data.needle_img.src, &info); + + angle = angle * 10; + if(angle > 3600) angle -= 3600; + + scale_center.x -= indic->type_data.needle_img.pivot.x; + scale_center.y -= indic->type_data.needle_img.pivot.y; + lv_area_t a; + _lv_img_buf_get_transformed_area(&a, info.w, info.h, angle, LV_IMG_ZOOM_NONE, &indic->type_data.needle_img.pivot); + a.x1 += scale_center.x - 2; + a.y1 += scale_center.y - 2; + a.x2 += scale_center.x + 2; + a.y2 += scale_center.y + 2; + + lv_obj_invalidate_area(obj, &a); + } +} +#endif diff --git a/lib/lvgl/src/extra/widgets/meter/lv_meter.h b/lib/lvgl/src/extra/widgets/meter/lv_meter.h new file mode 100644 index 00000000..24c1dae0 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/meter/lv_meter.h @@ -0,0 +1,267 @@ +/** + * @file lv_meter.h + * + */ + +#ifndef LV_METER_H +#define LV_METER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_METER != 0 + +/*Testing of dependencies*/ +#if LV_DRAW_COMPLEX == 0 +#error "lv_meter: Complex drawing is required. Enable it in lv_conf.h (LV_DRAW_COMPLEX 1)" +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_color_t tick_color; + uint16_t tick_cnt; + uint16_t tick_length; + uint16_t tick_width; + + lv_color_t tick_major_color; + uint16_t tick_major_nth; + uint16_t tick_major_length; + uint16_t tick_major_width; + + int16_t label_gap; + int16_t label_color; + + int32_t min; + int32_t max; + int16_t r_mod; + uint16_t angle_range; + int16_t rotation; +} lv_meter_scale_t; + +enum { + LV_METER_INDICATOR_TYPE_NEEDLE_IMG, + LV_METER_INDICATOR_TYPE_NEEDLE_LINE, + LV_METER_INDICATOR_TYPE_SCALE_LINES, + LV_METER_INDICATOR_TYPE_ARC, +}; +typedef uint8_t lv_meter_indicator_type_t; + +typedef struct { + lv_meter_scale_t * scale; + lv_meter_indicator_type_t type; + lv_opa_t opa; + int32_t start_value; + int32_t end_value; + union { + struct { + const void * src; + lv_point_t pivot; + } needle_img; + struct { + uint16_t width; + int16_t r_mod; + lv_color_t color; + } needle_line; + struct { + uint16_t width; + const void * src; + lv_color_t color; + int16_t r_mod; + } arc; + struct { + int16_t width_mod; + lv_color_t color_start; + lv_color_t color_end; + uint8_t local_grad : 1; + } scale_lines; + } type_data; +} lv_meter_indicator_t; + +/*Data of line meter*/ +typedef struct { + lv_obj_t obj; + lv_ll_t scale_ll; + lv_ll_t indicator_ll; +} lv_meter_t; + +extern const lv_obj_class_t lv_meter_class; + +/** + * `type` field in `lv_obj_draw_part_dsc_t` if `class_p = lv_meter_class` + * Used in `LV_EVENT_DRAW_PART_BEGIN` and `LV_EVENT_DRAW_PART_END` + */ +typedef enum { + LV_METER_DRAW_PART_ARC, /**< The arc indicator*/ + LV_METER_DRAW_PART_NEEDLE_LINE, /**< The needle lines*/ + LV_METER_DRAW_PART_NEEDLE_IMG, /**< The needle images*/ + LV_METER_DRAW_PART_TICK, /**< The tick lines and labels*/ +} lv_meter_draw_part_type_t; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a Meter object + * @param parent pointer to an object, it will be the parent of the new bar. + * @return pointer to the created meter + */ +lv_obj_t * lv_meter_create(lv_obj_t * parent); + +/*===================== + * Add scale + *====================*/ + +/** + * Add a new scale to the meter. + * @param obj pointer to a meter object + * @return the new scale + * @note Indicators can be attached to scales. + */ +lv_meter_scale_t * lv_meter_add_scale(lv_obj_t * obj); + +/** + * Set the properties of the ticks of a scale + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param cnt number of tick lines + * @param width width of tick lines + * @param len length of tick lines + * @param color color of tick lines + */ +void lv_meter_set_scale_ticks(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t cnt, uint16_t width, uint16_t len, + lv_color_t color); + +/** + * Make some "normal" ticks major ticks and set their attributes. + * Texts with the current value are also added to the major ticks. + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param nth make every Nth normal tick major tick. (start from the first on the left) + * @param width width of the major ticks + * @param len length of the major ticks + * @param color color of the major ticks + * @param label_gap gap between the major ticks and the labels + */ +void lv_meter_set_scale_major_ticks(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t nth, uint16_t width, + uint16_t len, lv_color_t color, int16_t label_gap); + +/** + * Set the value and angular range of a scale. + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param min the minimum value + * @param max the maximal value + * @param angle_range the angular range of the scale + * @param rotation the angular offset from the 3 o'clock position (clock-wise) + */ +void lv_meter_set_scale_range(lv_obj_t * obj, lv_meter_scale_t * scale, int32_t min, int32_t max, uint32_t angle_range, + uint32_t rotation); + +/*===================== + * Add indicator + *====================*/ + +/** + * Add a needle line indicator the scale + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param width width of the line + * @param color color of the line + * @param r_mod the radius modifier (added to the scale's radius) to get the lines length + * @return the new indicator + */ +lv_meter_indicator_t * lv_meter_add_needle_line(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t width, + lv_color_t color, int16_t r_mod); + +/** + * Add a needle image indicator the scale + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param src the image source of the indicator. path or pointer to ::lv_img_dsc_t + * @param pivot_x the X pivot point of the needle + * @param pivot_y the Y pivot point of the needle + * @return the new indicator + * @note the needle image should point to the right, like -O-----> + */ +lv_meter_indicator_t * lv_meter_add_needle_img(lv_obj_t * obj, lv_meter_scale_t * scale, const void * src, + lv_coord_t pivot_x, lv_coord_t pivot_y); + +/** + * Add an arc indicator the scale + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param width width of the arc + * @param color color of the arc + * @param r_mod the radius modifier (added to the scale's radius) to get the outer radius of the arc + * @return the new indicator + */ +lv_meter_indicator_t * lv_meter_add_arc(lv_obj_t * obj, lv_meter_scale_t * scale, uint16_t width, lv_color_t color, + int16_t r_mod); + + +/** + * Add a scale line indicator the scale. It will modify the ticks. + * @param obj pointer to a meter object + * @param scale pointer to scale (added to `meter`) + * @param color_start the start color + * @param color_end the end color + * @param local tell how to map start and end color. true: the indicator's start and end_value; false: the scale's min max value + * @param width_mod add this the affected tick's width + * @return the new indicator + */ +lv_meter_indicator_t * lv_meter_add_scale_lines(lv_obj_t * obj, lv_meter_scale_t * scale, lv_color_t color_start, + lv_color_t color_end, bool local, int16_t width_mod); + +/*===================== + * Set indicator value + *====================*/ + +/** + * Set the value of the indicator. It will set start and and value to the same value + * @param obj pointer to a meter object + * @param indic pointer to an indicator + * @param value the new value + */ +void lv_meter_set_indicator_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value); + +/** + * Set the start value of the indicator. + * @param obj pointer to a meter object + * @param indic pointer to an indicator + * @param value the new value + */ +void lv_meter_set_indicator_start_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value); + +/** + * Set the start value of the indicator. + * @param obj pointer to a meter object + * @param indic pointer to an indicator + * @param value the new value + */ +void lv_meter_set_indicator_end_value(lv_obj_t * obj, lv_meter_indicator_t * indic, int32_t value); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_METER*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_METER_H*/ diff --git a/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.c b/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.c new file mode 100644 index 00000000..8db5df7e --- /dev/null +++ b/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.c @@ -0,0 +1,209 @@ +/** + * @file lv_msgbox.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_msgbox.h" +#if LV_USE_MSGBOX + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define LV_MSGBOX_FLAG_AUTO_PARENT LV_OBJ_FLAG_WIDGET_1 /*Mark that the parent was automatically created*/ +#define MY_CLASS &lv_msgbox_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void msgbox_close_click_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_msgbox_class = { + .base_class = &lv_obj_class, + .width_def = LV_DPI_DEF * 2, + .height_def = LV_SIZE_CONTENT, + .instance_size = sizeof(lv_msgbox_t) +}; + +const lv_obj_class_t lv_msgbox_content_class = { + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_SIZE_CONTENT, + .instance_size = sizeof(lv_obj_t) +}; + +const lv_obj_class_t lv_msgbox_backdrop_class = { + .base_class = &lv_obj_class, + .width_def = LV_PCT(100), + .height_def = LV_PCT(100), + .instance_size = sizeof(lv_obj_t) +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_msgbox_create(lv_obj_t * parent, const char * title, const char * txt, const char * btn_txts[], + bool add_close_btn) +{ + LV_LOG_INFO("begin"); + bool auto_parent = false; + if(parent == NULL) { + auto_parent = true; + parent = lv_obj_class_create_obj(&lv_msgbox_backdrop_class, lv_layer_top()); + LV_ASSERT_MALLOC(parent); + lv_obj_class_init_obj(parent); + lv_obj_clear_flag(parent, LV_OBJ_FLAG_IGNORE_LAYOUT); + lv_obj_set_size(parent, LV_PCT(100), LV_PCT(100)); + } + + lv_obj_t * obj = lv_obj_class_create_obj(&lv_msgbox_class, parent); + LV_ASSERT_MALLOC(obj); + if(obj == NULL) return NULL; + lv_obj_class_init_obj(obj); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + + if(auto_parent) lv_obj_add_flag(obj, LV_MSGBOX_FLAG_AUTO_PARENT); + + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW_WRAP); + + bool has_title = title && strlen(title) > 0; + + /*When a close button is required, we need the empty label as spacer to push the button to the right*/ + if(add_close_btn || has_title) { + mbox->title = lv_label_create(obj); + lv_label_set_text(mbox->title, has_title ? title : ""); + lv_label_set_long_mode(mbox->title, LV_LABEL_LONG_SCROLL_CIRCULAR); + if(add_close_btn) lv_obj_set_flex_grow(mbox->title, 1); + else lv_obj_set_width(mbox->title, LV_PCT(100)); + } + + if(add_close_btn) { + mbox->close_btn = lv_btn_create(obj); + lv_obj_set_ext_click_area(mbox->close_btn, LV_DPX(10)); + lv_obj_add_event_cb(mbox->close_btn, msgbox_close_click_event_cb, LV_EVENT_CLICKED, NULL); + lv_obj_t * label = lv_label_create(mbox->close_btn); + lv_label_set_text(label, LV_SYMBOL_CLOSE); + const lv_font_t * font = lv_obj_get_style_text_font(mbox->close_btn, LV_PART_MAIN); + lv_coord_t close_btn_size = lv_font_get_line_height(font) + LV_DPX(10); + lv_obj_set_size(mbox->close_btn, close_btn_size, close_btn_size); + lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); + } + + mbox->content = lv_obj_class_create_obj(&lv_msgbox_content_class, obj); + + bool has_txt = txt && strlen(txt) > 0; + if(has_txt) { + mbox->text = lv_label_create(mbox->content); + lv_label_set_text(mbox->text, txt); + lv_label_set_long_mode(mbox->text, LV_LABEL_LONG_WRAP); + lv_obj_set_width(mbox->text, lv_pct(100)); + } + + if(btn_txts) { + mbox->btns = lv_btnmatrix_create(obj); + lv_btnmatrix_set_map(mbox->btns, btn_txts); + lv_btnmatrix_set_btn_ctrl_all(mbox->btns, LV_BTNMATRIX_CTRL_CLICK_TRIG | LV_BTNMATRIX_CTRL_NO_REPEAT); + + uint32_t btn_cnt = 0; + while(btn_txts[btn_cnt] && btn_txts[btn_cnt][0] != '\0') { + btn_cnt++; + } + + const lv_font_t * font = lv_obj_get_style_text_font(mbox->btns, LV_PART_ITEMS); + lv_coord_t btn_h = lv_font_get_line_height(font) + LV_DPI_DEF / 10; + lv_obj_set_size(mbox->btns, btn_cnt * (2 * LV_DPI_DEF / 3), btn_h); + lv_obj_set_style_max_width(mbox->btns, lv_pct(100), 0); + lv_obj_add_flag(mbox->btns, LV_OBJ_FLAG_EVENT_BUBBLE); /*To see the event directly on the message box*/ + } + + return obj; +} + + +lv_obj_t * lv_msgbox_get_title(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + return mbox->title; +} + +lv_obj_t * lv_msgbox_get_close_btn(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + return mbox->close_btn; +} + +lv_obj_t * lv_msgbox_get_text(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + return mbox->text; +} + +lv_obj_t * lv_msgbox_get_content(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + return mbox->content; +} + +lv_obj_t * lv_msgbox_get_btns(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_msgbox_t * mbox = (lv_msgbox_t *)obj; + return mbox->btns; +} + +uint16_t lv_msgbox_get_active_btn(lv_obj_t * mbox) +{ + lv_obj_t * btnm = lv_msgbox_get_btns(mbox); + return lv_btnmatrix_get_selected_btn(btnm); +} + +const char * lv_msgbox_get_active_btn_text(lv_obj_t * mbox) +{ + lv_obj_t * btnm = lv_msgbox_get_btns(mbox); + return lv_btnmatrix_get_btn_text(btnm, lv_btnmatrix_get_selected_btn(btnm)); +} + +void lv_msgbox_close(lv_obj_t * mbox) +{ + if(lv_obj_has_flag(mbox, LV_MSGBOX_FLAG_AUTO_PARENT)) lv_obj_del(lv_obj_get_parent(mbox)); + else lv_obj_del(mbox); +} + +void lv_msgbox_close_async(lv_obj_t * dialog) +{ + if(lv_obj_has_flag(dialog, LV_MSGBOX_FLAG_AUTO_PARENT)) lv_obj_del_async(lv_obj_get_parent(dialog)); + else lv_obj_del_async(dialog); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void msgbox_close_click_event_cb(lv_event_t * e) +{ + lv_obj_t * btn = lv_event_get_target(e); + lv_obj_t * mbox = lv_obj_get_parent(btn); + lv_msgbox_close(mbox); +} + +#endif /*LV_USE_MSGBOX*/ diff --git a/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.h b/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.h new file mode 100644 index 00000000..2eaf0d39 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/msgbox/lv_msgbox.h @@ -0,0 +1,99 @@ +/** + * @file lv_mbox.h + * + */ + +#ifndef LV_MSGBOX_H +#define LV_MSGBOX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_MSGBOX + +/*Testing of dependencies*/ +#if LV_USE_BTNMATRIX == 0 +#error "lv_mbox: lv_btnm is required. Enable it in lv_conf.h (LV_USE_BTNMATRIX 1) " +#endif + +#if LV_USE_LABEL == 0 +#error "lv_mbox: lv_label is required. Enable it in lv_conf.h (LV_USE_LABEL 1) " +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_obj_t obj; + lv_obj_t * title; + lv_obj_t * close_btn; + lv_obj_t * content; + lv_obj_t * text; + lv_obj_t * btns; +} lv_msgbox_t; + +extern const lv_obj_class_t lv_msgbox_class; +extern const lv_obj_class_t lv_msgbox_content_class; +extern const lv_obj_class_t lv_msgbox_backdrop_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a message box object + * @param parent pointer to parent or NULL to create a full screen modal message box + * @param title the title of the message box + * @param txt the text of the message box + * @param btn_txts the buttons as an array of texts terminated by an "" element. E.g. {"btn1", "btn2", ""} + * @param add_close_btn true: add a close button + * @return pointer to the message box object + */ +lv_obj_t * lv_msgbox_create(lv_obj_t * parent, const char * title, const char * txt, const char * btn_txts[], + bool add_close_btn); + +lv_obj_t * lv_msgbox_get_title(lv_obj_t * obj); + +lv_obj_t * lv_msgbox_get_close_btn(lv_obj_t * obj); + +lv_obj_t * lv_msgbox_get_text(lv_obj_t * obj); + +lv_obj_t * lv_msgbox_get_content(lv_obj_t * obj); + +lv_obj_t * lv_msgbox_get_btns(lv_obj_t * obj); + +/** + * Get the index of the selected button + * @param mbox message box object + * @return index of the button (LV_BTNMATRIX_BTN_NONE: if unset) + */ +uint16_t lv_msgbox_get_active_btn(lv_obj_t * mbox); + +const char * lv_msgbox_get_active_btn_text(lv_obj_t * mbox); + +void lv_msgbox_close(lv_obj_t * mbox); + +void lv_msgbox_close_async(lv_obj_t * mbox); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_MSGBOX*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_MSGBOX_H*/ diff --git a/lib/lvgl/src/extra/widgets/span/lv_span.c b/lib/lvgl/src/extra/widgets/span/lv_span.c new file mode 100644 index 00000000..96f04476 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/span/lv_span.c @@ -0,0 +1,1041 @@ +/** + * @file lv_span.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_span.h" + +#if LV_USE_SPAN != 0 + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_spangroup_class + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_span_t * span; + const char * txt; + const lv_font_t * font; + uint16_t bytes; + lv_coord_t txt_w; + lv_coord_t line_h; + lv_coord_t letter_space; +} lv_snippet_t; + +struct _snippet_stack { + lv_snippet_t stack[LV_SPAN_SNIPPET_STACK_SIZE]; + uint16_t index; +}; + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_spangroup_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_spangroup_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_spangroup_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void draw_main(lv_event_t * e); +static void refresh_self_size(lv_obj_t * obj); + +static const lv_font_t * lv_span_get_style_text_font(lv_obj_t * par, lv_span_t * span); +static lv_coord_t lv_span_get_style_text_letter_space(lv_obj_t * par, lv_span_t * span); +static lv_color_t lv_span_get_style_text_color(lv_obj_t * par, lv_span_t * span); +static lv_opa_t lv_span_get_style_text_opa(lv_obj_t * par, lv_span_t * span); +static lv_opa_t lv_span_get_style_text_blend_mode(lv_obj_t * par, lv_span_t * span); +static int32_t lv_span_get_style_text_decor(lv_obj_t * par, lv_span_t * span); + +static inline void span_text_check(const char ** text); +static void lv_draw_span(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx); +static bool lv_txt_get_snippet(const char * txt, const lv_font_t * font, lv_coord_t letter_space, + lv_coord_t max_width, lv_text_flag_t flag, lv_coord_t * use_width, + uint32_t * end_ofs); + +static void lv_snippet_clear(void); +static uint16_t lv_get_snippet_cnt(void); +static void lv_snippet_push(lv_snippet_t * item); +static lv_snippet_t * lv_get_snippet(uint16_t index); +static lv_coord_t convert_indent_pct(lv_obj_t * spans, lv_coord_t width); + +/********************** + * STATIC VARIABLES + **********************/ +static struct _snippet_stack snippet_stack; + +const lv_obj_class_t lv_spangroup_class = { + .base_class = &lv_obj_class, + .constructor_cb = lv_spangroup_constructor, + .destructor_cb = lv_spangroup_destructor, + .event_cb = lv_spangroup_event, + .instance_size = sizeof(lv_spangroup_t), + .width_def = LV_SIZE_CONTENT, + .height_def = LV_SIZE_CONTENT, +}; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_spangroup_create(lv_obj_t * par) +{ + lv_obj_t * obj = lv_obj_class_create_obj(&lv_spangroup_class, par); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_span_t * lv_spangroup_new_span(lv_obj_t * obj) +{ + if(obj == NULL) { + return NULL; + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + lv_span_t * span = _lv_ll_ins_tail(&spans->child_ll); + LV_ASSERT_MALLOC(span); + + lv_style_init(&span->style); + span->txt = (char *)""; + span->static_flag = 1; + span->spangroup = obj; + + refresh_self_size(obj); + + return span; +} + +void lv_spangroup_del_span(lv_obj_t * obj, lv_span_t * span) +{ + if(obj == NULL || span == NULL) { + return; + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + lv_span_t * cur_span; + _LV_LL_READ(&spans->child_ll, cur_span) { + if(cur_span == span) { + _lv_ll_remove(&spans->child_ll, cur_span); + if(cur_span->txt && cur_span->static_flag == 0) { + lv_mem_free(cur_span->txt); + } + lv_style_reset(&cur_span->style); + lv_mem_free(cur_span); + break; + } + } + + refresh_self_size(obj); +} + +/*===================== + * Setter functions + *====================*/ + +void lv_span_set_text(lv_span_t * span, const char * text) +{ + if(span == NULL || text == NULL) { + return; + } + + if(span->txt == NULL || span->static_flag == 1) { + span->txt = lv_mem_alloc(strlen(text) + 1); + } + else { + span->txt = lv_mem_realloc(span->txt, strlen(text) + 1); + } + span->static_flag = 0; + strcpy(span->txt, text); + + refresh_self_size(span->spangroup); +} + +void lv_span_set_text_static(lv_span_t * span, const char * text) +{ + if(span == NULL || text == NULL) { + return; + } + + if(span->txt && span->static_flag == 0) { + lv_mem_free(span->txt); + } + span->static_flag = 1; + span->txt = (char *)text; + + refresh_self_size(span->spangroup); +} + +void lv_spangroup_set_align(lv_obj_t * obj, lv_text_align_t align) +{ + lv_obj_set_style_text_align(obj, align, LV_PART_MAIN); +} + +void lv_spangroup_set_overflow(lv_obj_t * obj, lv_span_overflow_t overflow) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + if(spans->overflow == overflow) return; + + spans->overflow = overflow; + lv_obj_invalidate(obj); +} + +void lv_spangroup_set_indent(lv_obj_t * obj, lv_coord_t indent) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + if(spans->indent == indent) return; + + spans->indent = indent; + + refresh_self_size(obj); +} + +void lv_spangroup_set_mode(lv_obj_t * obj, lv_span_mode_t mode) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + spans->mode = mode; + lv_spangroup_refr_mode(obj); +} + +void lv_spangroup_set_lines(lv_obj_t * obj, int32_t lines) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + spans->lines = lines; + lv_spangroup_refr_mode(obj); +} + +/*===================== + * Getter functions + *====================*/ + +lv_span_t * lv_spangroup_get_child(const lv_obj_t * obj, int32_t id) +{ + if(obj == NULL) { + return NULL; + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + lv_ll_t * linked_list = &spans->child_ll; + + bool traverse_forwards = (id >= 0); + int32_t cur_idx = 0; + lv_ll_node_t * cur_node = linked_list->head; + + /*If using a negative index, start from the tail and use cur -1 to indicate the end*/ + if(!traverse_forwards) { + cur_idx = -1; + cur_node = linked_list->tail; + } + + while(cur_node != NULL) { + if(cur_idx == id) { + return (lv_span_t *) cur_node; + } + if(traverse_forwards) { + cur_node = (lv_ll_node_t *) _lv_ll_get_next(linked_list, cur_node); + cur_idx++; + } + else { + cur_node = (lv_ll_node_t *) _lv_ll_get_prev(linked_list, cur_node); + cur_idx--; + } + } + + return NULL; +} + +uint32_t lv_spangroup_get_child_cnt(const lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + + if(obj == NULL) { + return 0; + } + + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + return _lv_ll_get_len(&(spans->child_ll)); +} + +lv_text_align_t lv_spangroup_get_align(lv_obj_t * obj) +{ + return lv_obj_get_style_text_align(obj, LV_PART_MAIN); +} + +lv_span_overflow_t lv_spangroup_get_overflow(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + return spans->overflow; +} + +lv_coord_t lv_spangroup_get_indent(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + return spans->indent; +} + +lv_span_mode_t lv_spangroup_get_mode(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + return spans->mode; +} + +int32_t lv_spangroup_get_lines(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + return spans->lines; +} + +void lv_spangroup_refr_mode(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + if(spans->mode == LV_SPAN_MODE_EXPAND) { + lv_obj_set_width(obj, LV_SIZE_CONTENT); + lv_obj_set_height(obj, LV_SIZE_CONTENT); + } + else if(spans->mode == LV_SPAN_MODE_BREAK) { + if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT) { + lv_obj_set_width(obj, 100); + } + lv_obj_set_height(obj, LV_SIZE_CONTENT); + } + else if(spans->mode == LV_SPAN_MODE_FIXED) { + /* use this mode, The user needs to set the size. */ + /* This is just to prevent an infinite loop. */ + if(lv_obj_get_style_width(obj, LV_PART_MAIN) == LV_SIZE_CONTENT) { + lv_obj_set_width(obj, 100); + } + if(lv_obj_get_style_height(obj, LV_PART_MAIN) == LV_SIZE_CONTENT) { + lv_coord_t width = lv_obj_get_style_width(obj, LV_PART_MAIN); + if(LV_COORD_IS_PCT(width)) { + width = 100; + } + lv_coord_t height = lv_spangroup_get_expand_height(obj, width); + lv_obj_set_content_height(obj, height); + } + } + + refresh_self_size(obj); +} + +lv_coord_t lv_spangroup_get_max_line_h(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + lv_coord_t max_line_h = 0; + lv_span_t * cur_span; + _LV_LL_READ(&spans->child_ll, cur_span) { + const lv_font_t * font = lv_span_get_style_text_font(obj, cur_span); + lv_coord_t line_h = lv_font_get_line_height(font); + if(line_h > max_line_h) { + max_line_h = line_h; + } + } + + return max_line_h; +} + +uint32_t lv_spangroup_get_expand_width(lv_obj_t * obj, uint32_t max_width) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + if(_lv_ll_get_head(&spans->child_ll) == NULL) { + return 0; + } + + uint32_t width = LV_COORD_IS_PCT(spans->indent) ? 0 : spans->indent; + lv_span_t * cur_span; + lv_coord_t letter_space = 0; + _LV_LL_READ(&spans->child_ll, cur_span) { + const lv_font_t * font = lv_span_get_style_text_font(obj, cur_span); + letter_space = lv_span_get_style_text_letter_space(obj, cur_span); + uint32_t j = 0; + const char * cur_txt = cur_span->txt; + span_text_check(&cur_txt); + while(cur_txt[j] != '\0') { + if(max_width > 0 && width >= max_width) { + return max_width; + } + uint32_t letter = _lv_txt_encoded_next(cur_txt, &j); + uint32_t letter_next = _lv_txt_encoded_next(&cur_txt[j], NULL); + uint16_t letter_w = lv_font_get_glyph_width(font, letter, letter_next); + width = width + letter_w + letter_space; + } + } + + return width - letter_space; +} + +lv_coord_t lv_spangroup_get_expand_height(lv_obj_t * obj, lv_coord_t width) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + if(_lv_ll_get_head(&spans->child_ll) == NULL || width <= 0) { + return 0; + } + + /* init draw variable */ + lv_text_flag_t txt_flag = LV_TEXT_FLAG_NONE; + lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN); + lv_coord_t max_width = width; + lv_coord_t indent = convert_indent_pct(obj, max_width); + lv_coord_t max_w = max_width - indent; /* first line need minus indent */ + + /* coords of draw span-txt */ + lv_point_t txt_pos; + txt_pos.y = 0; + txt_pos.x = 0 + indent; /* first line need add indent */ + + lv_span_t * cur_span = _lv_ll_get_head(&spans->child_ll); + const char * cur_txt = cur_span->txt; + span_text_check(&cur_txt); + uint32_t cur_txt_ofs = 0; + lv_snippet_t snippet; /* use to save cur_span info and push it to stack */ + memset(&snippet, 0, sizeof(snippet)); + + int32_t line_cnt = 0; + int32_t lines = spans->lines < 0 ? INT32_MAX : spans->lines; + /* the loop control how many lines need to draw */ + while(cur_span) { + int snippet_cnt = 0; + lv_coord_t max_line_h = 0; /* the max height of span-font when a line have a lot of span */ + + /* the loop control to find a line and push the relevant span info into stack */ + while(1) { + /* switch to the next span when current is end */ + if(cur_txt[cur_txt_ofs] == '\0') { + cur_span = _lv_ll_get_next(&spans->child_ll, cur_span); + if(cur_span == NULL) break; + cur_txt = cur_span->txt; + span_text_check(&cur_txt); + cur_txt_ofs = 0; + /* maybe also cur_txt[cur_txt_ofs] == '\0' */ + continue; + } + + /* init span info to snippet. */ + if(cur_txt_ofs == 0) { + snippet.span = cur_span; + snippet.font = lv_span_get_style_text_font(obj, cur_span); + snippet.letter_space = lv_span_get_style_text_letter_space(obj, cur_span); + snippet.line_h = lv_font_get_line_height(snippet.font) + line_space; + } + + /* get current span text line info */ + uint32_t next_ofs = 0; + lv_coord_t use_width = 0; + bool isfill = lv_txt_get_snippet(&cur_txt[cur_txt_ofs], snippet.font, snippet.letter_space, + max_w, txt_flag, &use_width, &next_ofs); + + /* break word deal width */ + if(isfill && next_ofs > 0 && snippet_cnt > 0) { + if(max_w < use_width) { + break; + } + + uint32_t tmp_ofs = next_ofs; + uint32_t letter = _lv_txt_encoded_prev(&cur_txt[cur_txt_ofs], &tmp_ofs); + if(!(letter == '\0' || letter == '\n' || letter == '\r' || _lv_txt_is_break_char(letter))) { + tmp_ofs = 0; + letter = _lv_txt_encoded_next(&cur_txt[cur_txt_ofs + next_ofs], &tmp_ofs); + if(!(letter == '\0' || letter == '\n' || letter == '\r' || _lv_txt_is_break_char(letter))) { + break; + } + } + } + + snippet.txt = &cur_txt[cur_txt_ofs]; + snippet.bytes = next_ofs; + snippet.txt_w = use_width; + cur_txt_ofs += next_ofs; + if(max_line_h < snippet.line_h) { + max_line_h = snippet.line_h; + } + snippet_cnt ++; + max_w = max_w - use_width - snippet.letter_space; + if(isfill || max_w <= 0) { + break; + } + } + + /* next line init */ + txt_pos.x = 0; + txt_pos.y += max_line_h; + max_w = max_width; + line_cnt += 1; + if(line_cnt >= lines) { + break; + } + } + txt_pos.y -= line_space; + + return txt_pos.y; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_spangroup_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + _lv_ll_init(&spans->child_ll, sizeof(lv_span_t)); + spans->indent = 0; + spans->lines = -1; + spans->mode = LV_SPAN_MODE_EXPAND; + spans->overflow = LV_SPAN_OVERFLOW_CLIP; + spans->cache_w = 0; + spans->cache_h = 0; + spans->refresh = 1; +} + +static void lv_spangroup_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + lv_span_t * cur_span = _lv_ll_get_head(&spans->child_ll); + while(cur_span) { + _lv_ll_remove(&spans->child_ll, cur_span); + if(cur_span->txt && cur_span->static_flag == 0) { + lv_mem_free(cur_span->txt); + } + lv_style_reset(&cur_span->style); + lv_mem_free(cur_span); + cur_span = _lv_ll_get_head(&spans->child_ll); + } +} + +static void lv_spangroup_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + /* Call the ancestor's event handler */ + if(lv_obj_event_base(MY_CLASS, e) != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + if(code == LV_EVENT_DRAW_MAIN) { + draw_main(e); + } + else if(code == LV_EVENT_STYLE_CHANGED) { + refresh_self_size(obj); + } + else if(code == LV_EVENT_SIZE_CHANGED) { + refresh_self_size(obj); + } + else if(code == LV_EVENT_GET_SELF_SIZE) { + lv_coord_t width = 0; + lv_coord_t height = 0; + lv_point_t * self_size = lv_event_get_param(e); + + if(spans->mode == LV_SPAN_MODE_EXPAND) { + if(spans->refresh) { + spans->cache_w = (lv_coord_t)lv_spangroup_get_expand_width(obj, 0); + spans->cache_h = lv_spangroup_get_max_line_h(obj); + spans->refresh = 0; + } + width = spans->cache_w; + height = spans->cache_h; + } + else if(spans->mode == LV_SPAN_MODE_BREAK) { + width = lv_obj_get_content_width(obj); + if(self_size->y >= 0) { + if(width != spans->cache_w || spans->refresh) { + height = lv_spangroup_get_expand_height(obj, width); + spans->cache_w = width; + spans->cache_h = height; + spans->refresh = 0; + } + else { + height = spans->cache_h; + } + } + } + else if(spans->mode == LV_SPAN_MODE_FIXED) { + width = self_size->x >= 0 ? lv_obj_get_content_width(obj) : 0; + height = self_size->y >= 0 ? lv_obj_get_content_height(obj) : 0; + } + self_size->x = LV_MAX(self_size->x, width); + self_size->y = LV_MAX(self_size->y, height); + } +} + +static void draw_main(lv_event_t * e) +{ + lv_obj_t * obj = lv_event_get_target(e); + lv_draw_ctx_t * draw_ctx = lv_event_get_draw_ctx(e); + + lv_draw_span(obj, draw_ctx); +} + +/** + * @return true for txt fill the max_width. + */ +static bool lv_txt_get_snippet(const char * txt, const lv_font_t * font, + lv_coord_t letter_space, lv_coord_t max_width, lv_text_flag_t flag, + lv_coord_t * use_width, uint32_t * end_ofs) +{ + if(txt == NULL || txt[0] == '\0') { + *end_ofs = 0; + *use_width = 0; + return false; + } + + uint32_t ofs = _lv_txt_get_next_line(txt, font, letter_space, max_width, use_width, flag); + *end_ofs = ofs; + + if(txt[ofs] == '\0' && *use_width < max_width) { + return false; + } + else { + return true; + } +} + +static void lv_snippet_push(lv_snippet_t * item) +{ + if(snippet_stack.index < LV_SPAN_SNIPPET_STACK_SIZE) { + memcpy(&snippet_stack.stack[snippet_stack.index], item, sizeof(lv_snippet_t)); + snippet_stack.index++; + } + else { + LV_LOG_ERROR("span draw stack overflow, please set LV_SPAN_SNIPPET_STACK_SIZE too larger"); + } +} + +static uint16_t lv_get_snippet_cnt(void) +{ + return snippet_stack.index; +} + +static lv_snippet_t * lv_get_snippet(uint16_t index) +{ + return &snippet_stack.stack[index]; +} + +static void lv_snippet_clear(void) +{ + snippet_stack.index = 0; +} + +static const lv_font_t * lv_span_get_style_text_font(lv_obj_t * par, lv_span_t * span) +{ + const lv_font_t * font; + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_FONT, &value); + if(res != LV_RES_OK) { + font = lv_obj_get_style_text_font(par, LV_PART_MAIN); + } + else { + font = (const lv_font_t *)value.ptr; + } + return font; +} + +static lv_coord_t lv_span_get_style_text_letter_space(lv_obj_t * par, lv_span_t * span) +{ + lv_coord_t letter_space; + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_LETTER_SPACE, &value); + if(res != LV_RES_OK) { + letter_space = lv_obj_get_style_text_letter_space(par, LV_PART_MAIN); + } + else { + letter_space = (lv_coord_t)value.num; + } + return letter_space; +} + +static lv_color_t lv_span_get_style_text_color(lv_obj_t * par, lv_span_t * span) +{ + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_COLOR, &value); + if(res != LV_RES_OK) { + value.color = lv_obj_get_style_text_color(par, LV_PART_MAIN); + } + return value.color; +} + +static lv_opa_t lv_span_get_style_text_opa(lv_obj_t * par, lv_span_t * span) +{ + lv_opa_t opa; + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_OPA, &value); + if(res != LV_RES_OK) { + opa = (lv_opa_t)lv_obj_get_style_text_opa(par, LV_PART_MAIN); + } + else { + opa = (lv_opa_t)value.num; + } + return opa; +} + +static lv_blend_mode_t lv_span_get_style_text_blend_mode(lv_obj_t * par, lv_span_t * span) +{ + lv_blend_mode_t mode; + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_BLEND_MODE, &value); + if(res != LV_RES_OK) { + mode = (lv_blend_mode_t)lv_obj_get_style_blend_mode(par, LV_PART_MAIN); + } + else { + mode = (lv_blend_mode_t)value.num; + } + return mode; +} + +static int32_t lv_span_get_style_text_decor(lv_obj_t * par, lv_span_t * span) +{ + int32_t decor; + lv_style_value_t value; + lv_res_t res = lv_style_get_prop(&span->style, LV_STYLE_TEXT_DECOR, &value); + if(res != LV_RES_OK) { + decor = (lv_text_decor_t)lv_obj_get_style_text_decor(par, LV_PART_MAIN);; + } + else { + decor = (int32_t)value.num; + } + return decor; +} + +static inline void span_text_check(const char ** text) +{ + if(*text == NULL) { + *text = ""; + LV_LOG_ERROR("occur an error that span text == NULL"); + } +} + +static lv_coord_t convert_indent_pct(lv_obj_t * obj, lv_coord_t width) +{ + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + lv_coord_t indent = spans->indent; + if(LV_COORD_IS_PCT(spans->indent)) { + if(spans->mode == LV_SPAN_MODE_EXPAND) { + indent = 0; + } + else { + indent = (width * LV_COORD_GET_PCT(spans->indent)) / 100; + } + } + + return indent; +} + +/** + * draw span group + * @param spans obj handle + * @param coords coordinates of the label + * @param mask the label will be drawn only in this area + */ +static void lv_draw_span(lv_obj_t * obj, lv_draw_ctx_t * draw_ctx) +{ + + lv_area_t coords; + lv_obj_get_content_coords(obj, &coords); + + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + + /* return if not span */ + if(_lv_ll_get_head(&spans->child_ll) == NULL) { + return; + } + + /* return if no draw area */ + lv_area_t clip_area; + if(!_lv_area_intersect(&clip_area, &coords, draw_ctx->clip_area)) return; + const lv_area_t * clip_area_ori = draw_ctx->clip_area; + draw_ctx->clip_area = &clip_area; + + /* init draw variable */ + lv_text_flag_t txt_flag = LV_TEXT_FLAG_NONE; + lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);; + lv_coord_t max_width = lv_area_get_width(&coords); + lv_coord_t indent = convert_indent_pct(obj, max_width); + lv_coord_t max_w = max_width - indent; /* first line need minus indent */ + lv_opa_t obj_opa = lv_obj_get_style_opa(obj, LV_PART_MAIN); + + /* coords of draw span-txt */ + lv_point_t txt_pos; + txt_pos.y = coords.y1; + txt_pos.x = coords.x1 + indent; /* first line need add indent */ + + lv_span_t * cur_span = _lv_ll_get_head(&spans->child_ll); + const char * cur_txt = cur_span->txt; + span_text_check(&cur_txt); + uint32_t cur_txt_ofs = 0; + lv_snippet_t snippet; /* use to save cur_span info and push it to stack */ + lv_memset_00(&snippet, sizeof(snippet)); + + lv_draw_label_dsc_t label_draw_dsc; + lv_draw_label_dsc_init(&label_draw_dsc); + + bool is_first_line = true; + /* the loop control how many lines need to draw */ + while(cur_span) { + bool is_end_line = false; + bool ellipsis_valid = false; + lv_coord_t max_line_h = 0; /* the max height of span-font when a line have a lot of span */ + lv_coord_t max_baseline = 0; /*baseline of the highest span*/ + lv_snippet_clear(); + + /* the loop control to find a line and push the relevant span info into stack */ + while(1) { + /* switch to the next span when current is end */ + if(cur_txt[cur_txt_ofs] == '\0') { + cur_span = _lv_ll_get_next(&spans->child_ll, cur_span); + if(cur_span == NULL) break; + cur_txt = cur_span->txt; + span_text_check(&cur_txt); + cur_txt_ofs = 0; + /* maybe also cur_txt[cur_txt_ofs] == '\0' */ + continue; + } + + /* init span info to snippet. */ + if(cur_txt_ofs == 0) { + snippet.span = cur_span; + snippet.font = lv_span_get_style_text_font(obj, cur_span); + snippet.letter_space = lv_span_get_style_text_letter_space(obj, cur_span); + snippet.line_h = lv_font_get_line_height(snippet.font) + line_space; + } + + /* get current span text line info */ + uint32_t next_ofs = 0; + lv_coord_t use_width = 0; + bool isfill = lv_txt_get_snippet(&cur_txt[cur_txt_ofs], snippet.font, snippet.letter_space, + max_w, txt_flag, &use_width, &next_ofs); + + if(isfill) { + if(next_ofs > 0 && lv_get_snippet_cnt() > 0) { + /* To prevent infinite loops, the _lv_txt_get_next_line() may return incomplete words, */ + /* This phenomenon should be avoided when lv_get_snippet_cnt() > 0 */ + if(max_w < use_width) { + break; + } + uint32_t tmp_ofs = next_ofs; + uint32_t letter = _lv_txt_encoded_prev(&cur_txt[cur_txt_ofs], &tmp_ofs); + if(!(letter == '\0' || letter == '\n' || letter == '\r' || _lv_txt_is_break_char(letter))) { + tmp_ofs = 0; + letter = _lv_txt_encoded_next(&cur_txt[cur_txt_ofs + next_ofs], &tmp_ofs); + if(!(letter == '\0' || letter == '\n' || letter == '\r' || _lv_txt_is_break_char(letter))) { + break; + } + } + } + } + + snippet.txt = &cur_txt[cur_txt_ofs]; + snippet.bytes = next_ofs; + snippet.txt_w = use_width; + cur_txt_ofs += next_ofs; + if(max_line_h < snippet.line_h) { + max_line_h = snippet.line_h; + max_baseline = snippet.font->base_line; + } + + lv_snippet_push(&snippet); + max_w = max_w - use_width - snippet.letter_space; + if(isfill || max_w <= 0) { + break; + } + } + + /* start current line deal with */ + + uint16_t item_cnt = lv_get_snippet_cnt(); + if(item_cnt == 0) { /* break if stack is empty */ + break; + } + + /* Whether the current line is the end line and does overflow processing */ + { + lv_snippet_t * last_snippet = lv_get_snippet(item_cnt - 1); + lv_coord_t next_line_h = last_snippet->line_h; + if(last_snippet->txt[last_snippet->bytes] == '\0') { + next_line_h = 0; + lv_span_t * next_span = _lv_ll_get_next(&spans->child_ll, last_snippet->span); + if(next_span) { /* have the next line */ + next_line_h = lv_font_get_line_height(lv_span_get_style_text_font(obj, next_span)) + line_space; + } + } + if(txt_pos.y + max_line_h + next_line_h - line_space > coords.y2 + 1) { /* for overflow if is end line. */ + if(last_snippet->txt[last_snippet->bytes] != '\0') { + last_snippet->bytes = strlen(last_snippet->txt); + last_snippet->txt_w = lv_txt_get_width(last_snippet->txt, last_snippet->bytes, last_snippet->font, + last_snippet->letter_space, txt_flag); + } + ellipsis_valid = spans->overflow == LV_SPAN_OVERFLOW_ELLIPSIS ? true : false; + is_end_line = true; + } + } + + /*Go the first visible line*/ + if(txt_pos.y + max_line_h < clip_area.y1) { + goto Next_line_init; + } + + /* align deal with */ + lv_text_align_t align = lv_obj_get_style_text_align(obj, LV_PART_MAIN); + if(align == LV_TEXT_ALIGN_CENTER || align == LV_TEXT_ALIGN_RIGHT) { + lv_coord_t align_ofs = 0; + lv_coord_t txts_w = is_first_line ? indent : 0; + for(int i = 0; i < item_cnt; i++) { + lv_snippet_t * pinfo = lv_get_snippet(i); + txts_w = txts_w + pinfo->txt_w + pinfo->letter_space; + } + txts_w -= lv_get_snippet(item_cnt - 1)->letter_space; + align_ofs = max_width > txts_w ? max_width - txts_w : 0; + if(align == LV_TEXT_ALIGN_CENTER) { + align_ofs = align_ofs >> 1; + } + txt_pos.x += align_ofs; + } + + /* draw line letters */ + int i; + for(i = 0; i < item_cnt; i++) { + lv_snippet_t * pinfo = lv_get_snippet(i); + + /* bidi deal with:todo */ + const char * bidi_txt = pinfo->txt; + + lv_point_t pos; + pos.x = txt_pos.x; + pos.y = txt_pos.y + max_line_h - pinfo->line_h - (max_baseline - pinfo->font->base_line); + label_draw_dsc.color = lv_span_get_style_text_color(obj, pinfo->span); + label_draw_dsc.opa = lv_span_get_style_text_opa(obj, pinfo->span); + label_draw_dsc.font = lv_span_get_style_text_font(obj, pinfo->span); + label_draw_dsc.blend_mode = lv_span_get_style_text_blend_mode(obj, pinfo->span); + if(obj_opa < LV_OPA_MAX) { + label_draw_dsc.opa = (uint16_t)((uint16_t)label_draw_dsc.opa * obj_opa) >> 8; + } + uint32_t txt_bytes = pinfo->bytes; + + /* overflow */ + uint16_t dot_letter_w = 0; + uint16_t dot_width = 0; + if(ellipsis_valid) { + dot_letter_w = lv_font_get_glyph_width(pinfo->font, '.', '.'); + dot_width = dot_letter_w * 3; + } + lv_coord_t ellipsis_width = coords.x1 + max_width - dot_width; + + uint32_t j = 0; + while(j < txt_bytes) { + /* skip invalid fields */ + if(pos.x > clip_area.x2) { + break; + } + uint32_t letter = _lv_txt_encoded_next(bidi_txt, &j); + uint32_t letter_next = _lv_txt_encoded_next(&bidi_txt[j], NULL); + int32_t letter_w = lv_font_get_glyph_width(pinfo->font, letter, letter_next); + + /* skip invalid fields */ + if(pos.x + letter_w + pinfo->letter_space < clip_area.x1) { + if(letter_w > 0) { + pos.x = pos.x + letter_w + pinfo->letter_space; + } + continue; + } + + if(ellipsis_valid && pos.x + letter_w + pinfo->letter_space > ellipsis_width) { + for(int ell = 0; ell < 3; ell++) { + lv_draw_letter(draw_ctx, &label_draw_dsc, &pos, '.'); + pos.x = pos.x + dot_letter_w + pinfo->letter_space; + } + if(pos.x <= ellipsis_width) { + pos.x = ellipsis_width + 1; + } + break; + } + else { + lv_draw_letter(draw_ctx, &label_draw_dsc, &pos, letter); + if(letter_w > 0) { + pos.x = pos.x + letter_w + pinfo->letter_space; + } + } + } + + /* draw decor */ + lv_text_decor_t decor = lv_span_get_style_text_decor(obj, pinfo->span); + if(decor != LV_TEXT_DECOR_NONE) { + lv_draw_line_dsc_t line_dsc; + lv_draw_line_dsc_init(&line_dsc); + line_dsc.color = label_draw_dsc.color; + line_dsc.width = label_draw_dsc.font->underline_thickness ? pinfo->font->underline_thickness : 1; + line_dsc.opa = label_draw_dsc.opa; + line_dsc.blend_mode = label_draw_dsc.blend_mode; + + if(decor & LV_TEXT_DECOR_STRIKETHROUGH) { + lv_point_t p1; + lv_point_t p2; + p1.x = txt_pos.x; + p1.y = pos.y + ((pinfo->line_h - line_space) >> 1) + (line_dsc.width >> 1); + p2.x = pos.x; + p2.y = p1.y; + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + } + + if(decor & LV_TEXT_DECOR_UNDERLINE) { + lv_point_t p1; + lv_point_t p2; + p1.x = txt_pos.x; + p1.y = pos.y + pinfo->line_h - line_space - pinfo->font->base_line - pinfo->font->underline_position; + p2.x = pos.x; + p2.y = p1.y; + lv_draw_line(draw_ctx, &line_dsc, &p1, &p2); + } + } + txt_pos.x = pos.x; + } + +Next_line_init: + /* next line init */ + is_first_line = false; + txt_pos.x = coords.x1; + txt_pos.y += max_line_h; + if(is_end_line || txt_pos.y > clip_area.y2 + 1) { + draw_ctx->clip_area = clip_area_ori; + return; + } + max_w = max_width; + } + draw_ctx->clip_area = clip_area_ori; +} + +static void refresh_self_size(lv_obj_t * obj) +{ + lv_spangroup_t * spans = (lv_spangroup_t *)obj; + spans->refresh = 1; + lv_obj_invalidate(obj); + lv_obj_refresh_self_size(obj); +} + +#endif diff --git a/lib/lvgl/src/extra/widgets/span/lv_span.h b/lib/lvgl/src/extra/widgets/span/lv_span.h new file mode 100644 index 00000000..f00d04db --- /dev/null +++ b/lib/lvgl/src/extra/widgets/span/lv_span.h @@ -0,0 +1,245 @@ +/** + * @file lv_span.h + * + */ + +#ifndef LV_SPAN_H +#define LV_SPAN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_SPAN != 0 + +/********************* + * DEFINES + *********************/ +#ifndef LV_SPAN_SNIPPET_STACK_SIZE +#define LV_SPAN_SNIPPET_STACK_SIZE 64 +#endif + +/********************** + * TYPEDEFS + **********************/ +enum { + LV_SPAN_OVERFLOW_CLIP, + LV_SPAN_OVERFLOW_ELLIPSIS, +}; +typedef uint8_t lv_span_overflow_t; + +enum { + LV_SPAN_MODE_FIXED, /**< fixed the obj size*/ + LV_SPAN_MODE_EXPAND, /**< Expand the object size to the text size*/ + LV_SPAN_MODE_BREAK, /**< Keep width, break the too long lines and expand height*/ +}; +typedef uint8_t lv_span_mode_t; + +typedef struct { + char * txt; /* a pointer to display text */ + lv_obj_t * spangroup; /* a pointer to spangroup */ + lv_style_t style; /* display text style */ + uint8_t static_flag : 1;/* the text is static flag */ +} lv_span_t; + +/** Data of label*/ +typedef struct { + lv_obj_t obj; + int32_t lines; + lv_coord_t indent; /* first line indent */ + lv_coord_t cache_w; /* the cache automatically calculates the width */ + lv_coord_t cache_h; /* similar cache_w */ + lv_ll_t child_ll; + uint8_t mode : 2; /* details see lv_span_mode_t */ + uint8_t overflow : 1; /* details see lv_span_overflow_t */ + uint8_t refresh : 1; /* the spangroup need refresh cache_w and cache_h */ +} lv_spangroup_t; + +extern const lv_obj_class_t lv_spangroup_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a spangroup object + * @param par pointer to an object, it will be the parent of the new spangroup + * @return pointer to the created spangroup + */ +lv_obj_t * lv_spangroup_create(lv_obj_t * par); + +/** + * Create a span string descriptor and add to spangroup. + * @param obj pointer to a spangroup object. + * @return pointer to the created span. + */ +lv_span_t * lv_spangroup_new_span(lv_obj_t * obj); + +/** + * Remove the span from the spangroup and free memory. + * @param obj pointer to a spangroup object. + * @param span pointer to a span. + */ +void lv_spangroup_del_span(lv_obj_t * obj, lv_span_t * span); + +/*===================== + * Setter functions + *====================*/ + +/** + * Set a new text for a span. Memory will be allocated to store the text by the span. + * @param span pointer to a span. + * @param text pointer to a text. + */ +void lv_span_set_text(lv_span_t * span, const char * text); + +/** + * Set a static text. It will not be saved by the span so the 'text' variable + * has to be 'alive' while the span exist. + * @param span pointer to a span. + * @param text pointer to a text. + */ +void lv_span_set_text_static(lv_span_t * span, const char * text); + +/** + * Set the align of the spangroup. + * @param obj pointer to a spangroup object. + * @param align see lv_text_align_t for details. + */ +void lv_spangroup_set_align(lv_obj_t * obj, lv_text_align_t align); + +/** + * Set the overflow of the spangroup. + * @param obj pointer to a spangroup object. + * @param overflow see lv_span_overflow_t for details. + */ +void lv_spangroup_set_overflow(lv_obj_t * obj, lv_span_overflow_t overflow); + +/** + * Set the indent of the spangroup. + * @param obj pointer to a spangroup object. + * @param indent The first line indentation + */ +void lv_spangroup_set_indent(lv_obj_t * obj, lv_coord_t indent); + +/** + * Set the mode of the spangroup. + * @param obj pointer to a spangroup object. + * @param mode see lv_span_mode_t for details. + */ +void lv_spangroup_set_mode(lv_obj_t * obj, lv_span_mode_t mode); + +/** + * Set lines of the spangroup. + * @param obj pointer to a spangroup object. + * @param lines max lines that can be displayed in LV_SPAN_MODE_BREAK mode. < 0 means no limit. + */ +void lv_spangroup_set_lines(lv_obj_t * obj, int32_t lines); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get a spangroup child by its index. + * + * @param obj The spangroup object + * @param id the index of the child. + * 0: the oldest (firstly created) child + * 1: the second oldest + * child count-1: the youngest + * -1: the youngest + * -2: the second youngest + * @return The child span at index `id`, or NULL if the ID does not exist + */ +lv_span_t * lv_spangroup_get_child(const lv_obj_t * obj, int32_t id); + +/** + * + * @param obj The spangroup object to get the child count of. + * @return The span count of the spangroup. + */ +uint32_t lv_spangroup_get_child_cnt(const lv_obj_t * obj); + +/** + * get the align of the spangroup. + * @param obj pointer to a spangroup object. + * @return the align value. + */ +lv_text_align_t lv_spangroup_get_align(lv_obj_t * obj); + +/** + * get the overflow of the spangroup. + * @param obj pointer to a spangroup object. + * @return the overflow value. + */ +lv_span_overflow_t lv_spangroup_get_overflow(lv_obj_t * obj); + +/** + * get the indent of the spangroup. + * @param obj pointer to a spangroup object. + * @return the indent value. + */ +lv_coord_t lv_spangroup_get_indent(lv_obj_t * obj); + +/** + * get the mode of the spangroup. + * @param obj pointer to a spangroup object. + */ +lv_span_mode_t lv_spangroup_get_mode(lv_obj_t * obj); + +/** + * get lines of the spangroup. + * @param obj pointer to a spangroup object. + * @return the lines value. + */ +int32_t lv_spangroup_get_lines(lv_obj_t * obj); + +/** + * get max line height of all span in the spangroup. + * @param obj pointer to a spangroup object. + */ +lv_coord_t lv_spangroup_get_max_line_h(lv_obj_t * obj); + +/** + * get the text content width when all span of spangroup on a line. + * @param obj pointer to a spangroup object. + * @param max_width if text content width >= max_width, return max_width + * to reduce computation, if max_width == 0, returns the text content width. + * @return text content width or max_width. + */ +uint32_t lv_spangroup_get_expand_width(lv_obj_t * obj, uint32_t max_width); + +/** + * get the text content height with width fixed. + * @param obj pointer to a spangroup object. + */ +lv_coord_t lv_spangroup_get_expand_height(lv_obj_t * obj, lv_coord_t width); + + +/*===================== + * Other functions + *====================*/ + +/** + * update the mode of the spangroup. + * @param obj pointer to a spangroup object. + */ +void lv_spangroup_refr_mode(lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_SPAN*/ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /*LV_SPAN_H*/ diff --git a/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.c b/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.c new file mode 100644 index 00000000..34691053 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.c @@ -0,0 +1,516 @@ +/** + * @file lv_spinbox.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_spinbox.h" +#if LV_USE_SPINBOX + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_spinbox_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void lv_spinbox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_spinbox_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void lv_spinbox_updatevalue(lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_spinbox_class = { + .constructor_cb = lv_spinbox_constructor, + .event_cb = lv_spinbox_event, + .width_def = LV_DPI_DEF, + .instance_size = sizeof(lv_spinbox_t), + .editable = LV_OBJ_CLASS_EDITABLE_TRUE, + .base_class = &lv_textarea_class +}; +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_spinbox_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*===================== + * Setter functions + *====================*/ + +/** + * Set spinbox value + * @param obj pointer to spinbox + * @param i value to be set + */ +void lv_spinbox_set_value(lv_obj_t * obj, int32_t i) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + if(i > spinbox->range_max) i = spinbox->range_max; + if(i < spinbox->range_min) i = spinbox->range_min; + + spinbox->value = i; + + lv_spinbox_updatevalue(obj); +} + +/** + * Set spinbox rollover function + * @param spinbox pointer to spinbox + * @param b true or false to enable or disable (default) + */ +void lv_spinbox_set_rollover(lv_obj_t * obj, bool b) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + spinbox->rollover = b; +} + +/** + * Set spinbox digit format (digit count and decimal format) + * @param spinbox pointer to spinbox + * @param digit_count number of digit excluding the decimal separator and the sign + * @param separator_position number of digit before the decimal point. If 0, decimal point is not + * shown + */ +void lv_spinbox_set_digit_format(lv_obj_t * obj, uint8_t digit_count, uint8_t separator_position) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + if(digit_count > LV_SPINBOX_MAX_DIGIT_COUNT) digit_count = LV_SPINBOX_MAX_DIGIT_COUNT; + + if(separator_position >= digit_count) separator_position = 0; + + if(digit_count < LV_SPINBOX_MAX_DIGIT_COUNT) { + int64_t max_val = lv_pow(10, digit_count); + if(spinbox->range_max > max_val - 1) spinbox->range_max = max_val - 1; + if(spinbox->range_min < - max_val + 1) spinbox->range_min = - max_val + 1; + } + + spinbox->digit_count = digit_count; + spinbox->dec_point_pos = separator_position; + + lv_spinbox_updatevalue(obj); +} + +/** + * Set spinbox step + * @param spinbox pointer to spinbox + * @param step steps on increment/decrement + */ +void lv_spinbox_set_step(lv_obj_t * obj, uint32_t step) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + spinbox->step = step; + lv_spinbox_updatevalue(obj); +} + +/** + * Set spinbox value range + * @param spinbox pointer to spinbox + * @param range_min maximum value, inclusive + * @param range_max minimum value, inclusive + */ +void lv_spinbox_set_range(lv_obj_t * obj, int32_t range_min, int32_t range_max) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + spinbox->range_max = range_max; + spinbox->range_min = range_min; + + if(spinbox->value > spinbox->range_max) spinbox->value = spinbox->range_max; + if(spinbox->value < spinbox->range_min) spinbox->value = spinbox->range_min; + + lv_spinbox_updatevalue(obj); +} + +/** + * Set cursor position to a specific digit for edition + * @param spinbox pointer to spinbox + * @param pos selected position in spinbox + */ +void lv_spinbox_set_cursor_pos(lv_obj_t * obj, uint8_t pos) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + int32_t step_limit; + step_limit = LV_MAX(spinbox->range_max, (spinbox->range_min < 0 ? (-spinbox->range_min) : spinbox->range_min)); + int32_t new_step = spinbox->step * lv_pow(10, pos); + if(pos <= 0) spinbox->step = 1; + else if(new_step <= step_limit) spinbox->step = new_step; + + lv_spinbox_updatevalue(obj); +} + +/** + * Set direction of digit step when clicking an encoder button while in editing mode + * @param spinbox pointer to spinbox + * @param direction the direction (LV_DIR_RIGHT or LV_DIR_LEFT) + */ +void lv_spinbox_set_digit_step_direction(lv_obj_t * obj, lv_dir_t direction) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + spinbox->digit_step_dir = direction; + + lv_spinbox_updatevalue(obj); +} +/*===================== + * Getter functions + *====================*/ + +/** + * Get the spinbox numeral value (user has to convert to float according to its digit format) + * @param obj pointer to spinbox + * @return value integer value of the spinbox + */ +int32_t lv_spinbox_get_value(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + return spinbox->value; +} +/** + * Get the spinbox step value (user has to convert to float according to its digit format) + * @param obj pointer to spinbox + * @return value integer step value of the spinbox + */ +int32_t lv_spinbox_get_step(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + return spinbox->step; +} + +/*===================== + * Other functions + *====================*/ + +/** + * Select next lower digit for edition + * @param obj pointer to spinbox + */ +void lv_spinbox_step_next(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + int32_t new_step = spinbox->step / 10; + if((new_step) > 0) + spinbox->step = new_step; + else + spinbox->step = 1; + + lv_spinbox_updatevalue(obj); +} + +/** + * Select next higher digit for edition + * @param obj pointer to spinbox + */ +void lv_spinbox_step_prev(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + int32_t step_limit; + step_limit = LV_MAX(spinbox->range_max, (spinbox->range_min < 0 ? (-spinbox->range_min) : spinbox->range_min)); + int32_t new_step = spinbox->step * 10; + if(new_step <= step_limit) spinbox->step = new_step; + + lv_spinbox_updatevalue(obj); +} + +/** + * Get spinbox rollover function status + * @param obj pointer to spinbox + */ +bool lv_spinbox_get_rollover(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + return spinbox->rollover; +} + +/** + * Increment spinbox value by one step + * @param obj pointer to spinbox + */ +void lv_spinbox_increment(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + if(spinbox->value + spinbox->step <= spinbox->range_max) { + /*Special mode when zero crossing*/ + if((spinbox->value + spinbox->step) > 0 && spinbox->value < 0) spinbox->value = -spinbox->value; + spinbox->value += spinbox->step; + + } + else { + // Rollover? + if((spinbox->rollover) && (spinbox->value == spinbox->range_max)) + spinbox->value = spinbox->range_min; + else + spinbox->value = spinbox->range_max; + } + + lv_spinbox_updatevalue(obj); +} + +/** + * Decrement spinbox value by one step + * @param obj pointer to spinbox + */ +void lv_spinbox_decrement(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + if(spinbox->value - spinbox->step >= spinbox->range_min) { + /*Special mode when zero crossing*/ + if((spinbox->value - spinbox->step) < 0 && spinbox->value > 0) spinbox->value = -spinbox->value; + spinbox->value -= spinbox->step; + } + else { + /*Rollover?*/ + if((spinbox->rollover) && (spinbox->value == spinbox->range_min)) + spinbox->value = spinbox->range_max; + else + spinbox->value = spinbox->range_min; + } + + lv_spinbox_updatevalue(obj); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_spinbox_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + LV_LOG_TRACE("begin"); + + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + /*Initialize the allocated 'ext'*/ + spinbox->value = 0; + spinbox->dec_point_pos = 0; + spinbox->digit_count = 5; + spinbox->step = 1; + spinbox->range_max = 99999; + spinbox->range_min = -99999; + spinbox->rollover = false; + spinbox->digit_step_dir = LV_DIR_RIGHT; + + lv_textarea_set_one_line(obj, true); + lv_textarea_set_cursor_click_pos(obj, true); + + lv_spinbox_updatevalue(obj); + + LV_LOG_TRACE("Spinbox constructor finished"); +} + +static void lv_spinbox_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + + /*Call the ancestor's event handler*/ + lv_res_t res = LV_RES_OK; + res = lv_obj_event_base(MY_CLASS, e); + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + if(code == LV_EVENT_RELEASED) { + /*If released with an ENCODER then move to the next digit*/ + lv_indev_t * indev = lv_indev_get_act(); + if(lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER) { + if(lv_group_get_editing(lv_obj_get_group(obj))) { + if(spinbox->digit_count > 1) { + if(spinbox->digit_step_dir == LV_DIR_RIGHT) { + if(spinbox->step > 1) { + lv_spinbox_step_next(obj); + } + else { + /*Restart from the MSB*/ + spinbox->step = lv_pow(10, spinbox->digit_count - 2); + lv_spinbox_step_prev(obj); + } + } + else { + if(spinbox->step < lv_pow(10, spinbox->digit_count - 1)) { + lv_spinbox_step_prev(obj); + } + else { + /*Restart from the LSB*/ + spinbox->step = 10; + lv_spinbox_step_next(obj); + } + } + } + } + } + /*The cursor has been positioned to a digit. + * Set `step` accordingly*/ + else { + const char * txt = lv_textarea_get_text(obj); + size_t txt_len = strlen(txt); + + if(txt[spinbox->ta.cursor.pos] == '.') { + lv_textarea_cursor_left(obj); + } + else if(spinbox->ta.cursor.pos == (uint32_t)txt_len) { + lv_textarea_set_cursor_pos(obj, txt_len - 1); + } + else if(spinbox->ta.cursor.pos == 0 && spinbox->range_min < 0) { + lv_textarea_set_cursor_pos(obj, 1); + } + + size_t len = spinbox->digit_count - 1; + uint16_t cp = spinbox->ta.cursor.pos; + + if(spinbox->ta.cursor.pos > spinbox->dec_point_pos && spinbox->dec_point_pos != 0) cp--; + uint32_t pos = len - cp; + + if(spinbox->range_min < 0) pos++; + + spinbox->step = 1; + uint16_t i; + for(i = 0; i < pos; i++) spinbox->step *= 10; + } + } + else if(code == LV_EVENT_KEY) { + lv_indev_type_t indev_type = lv_indev_get_type(lv_indev_get_act()); + + uint32_t c = *((uint32_t *)lv_event_get_param(e)); /*uint32_t because can be UTF-8*/ + if(c == LV_KEY_RIGHT) { + if(indev_type == LV_INDEV_TYPE_ENCODER) + lv_spinbox_increment(obj); + else + lv_spinbox_step_next(obj); + } + else if(c == LV_KEY_LEFT) { + if(indev_type == LV_INDEV_TYPE_ENCODER) + lv_spinbox_decrement(obj); + else + lv_spinbox_step_prev(obj); + } + else if(c == LV_KEY_UP) { + lv_spinbox_increment(obj); + } + else if(c == LV_KEY_DOWN) { + lv_spinbox_decrement(obj); + } + else { + lv_textarea_add_char(obj, c); + } + } +} + +static void lv_spinbox_updatevalue(lv_obj_t * obj) +{ + lv_spinbox_t * spinbox = (lv_spinbox_t *)obj; + + char buf[LV_SPINBOX_MAX_DIGIT_COUNT + 8]; + lv_memset_00(buf, sizeof(buf)); + char * buf_p = buf; + uint8_t cur_shift_left = 0; + + if(spinbox->range_min < 0) { // hide sign if there are only positive values + /*Add the sign*/ + (*buf_p) = spinbox->value >= 0 ? '+' : '-'; + buf_p++; + } + else { + /*Cursor need shift to left*/ + cur_shift_left++; + } + + int32_t i; + char digits[LV_SPINBOX_MAX_DIGIT_COUNT + 4]; + /*Convert the numbers to string (the sign is already handled so always covert positive number)*/ + lv_snprintf(digits, sizeof(digits), "%" LV_PRId32, LV_ABS(spinbox->value)); + + /*Add leading zeros*/ + int lz_cnt = spinbox->digit_count - (int)strlen(digits); + if(lz_cnt > 0) { + for(i = (uint16_t)strlen(digits); i >= 0; i--) { + digits[i + lz_cnt] = digits[i]; + } + for(i = 0; i < lz_cnt; i++) { + digits[i] = '0'; + } + } + + int32_t intDigits; + intDigits = (spinbox->dec_point_pos == 0) ? spinbox->digit_count : spinbox->dec_point_pos; + + /*Add the decimal part*/ + for(i = 0; i < intDigits && digits[i] != '\0'; i++) { + (*buf_p) = digits[i]; + buf_p++; + } + + if(spinbox->dec_point_pos != 0) { + /*Insert the decimal point*/ + (*buf_p) = '.'; + buf_p++; + + for(/*Leave i*/; i < spinbox->digit_count && digits[i] != '\0'; i++) { + (*buf_p) = digits[i]; + buf_p++; + } + } + + /*Refresh the text*/ + lv_textarea_set_text(obj, (char *)buf); + + /*Set the cursor position*/ + int32_t step = spinbox->step; + uint8_t cur_pos = (uint8_t)spinbox->digit_count; + while(step >= 10) { + step /= 10; + cur_pos--; + } + + if(cur_pos > intDigits) cur_pos++; /*Skip the decimal point*/ + + cur_pos -= cur_shift_left; + + lv_textarea_set_cursor_pos(obj, cur_pos); +} + +#endif /*LV_USE_SPINBOX*/ diff --git a/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.h b/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.h new file mode 100644 index 00000000..1a4bc322 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/spinbox/lv_spinbox.h @@ -0,0 +1,182 @@ +/** + * @file lv_spinbox.h + * + */ + +#ifndef LV_SPINBOX_H +#define LV_SPINBOX_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_SPINBOX + +/*Testing of dependencies*/ +#if LV_USE_TEXTAREA == 0 +#error "lv_spinbox: lv_ta is required. Enable it in lv_conf.h (LV_USE_TEXTAREA 1) " +#endif + +/********************* + * DEFINES + *********************/ +#define LV_SPINBOX_MAX_DIGIT_COUNT 10 + +/********************** + * TYPEDEFS + **********************/ + +/*Data of spinbox*/ +typedef struct { + lv_textarea_t ta; /*Ext. of ancestor*/ + /*New data for this type*/ + int32_t value; + int32_t range_max; + int32_t range_min; + int32_t step; + uint16_t digit_count : 4; + uint16_t dec_point_pos : 4; /*if 0, there is no separator and the number is an integer*/ + uint16_t rollover : 1; // Set to true for rollover functionality + uint16_t digit_step_dir : 2; // the direction the digit will step on encoder button press when editing +} lv_spinbox_t; + +extern const lv_obj_class_t lv_spinbox_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a Spinbox object + * @param parent pointer to an object, it will be the parent of the new spinbox + * @return pointer to the created spinbox + */ +lv_obj_t * lv_spinbox_create(lv_obj_t * parent); + +/*===================== + * Setter functions + *====================*/ + +/** + * Set spinbox value + * @param obj pointer to spinbox + * @param i value to be set + */ +void lv_spinbox_set_value(lv_obj_t * obj, int32_t i); + +/** + * Set spinbox rollover function + * @param obj pointer to spinbox + * @param b true or false to enable or disable (default) + */ +void lv_spinbox_set_rollover(lv_obj_t * obj, bool b); + +/** + * Set spinbox digit format (digit count and decimal format) + * @param obj pointer to spinbox + * @param digit_count number of digit excluding the decimal separator and the sign + * @param separator_position number of digit before the decimal point. If 0, decimal point is not + * shown + */ +void lv_spinbox_set_digit_format(lv_obj_t * obj, uint8_t digit_count, uint8_t separator_position); + +/** + * Set spinbox step + * @param obj pointer to spinbox + * @param step steps on increment/decrement. Can be 1, 10, 100, 1000, etc the digit that will change. + */ +void lv_spinbox_set_step(lv_obj_t * obj, uint32_t step); + +/** + * Set spinbox value range + * @param obj pointer to spinbox + * @param range_min maximum value, inclusive + * @param range_max minimum value, inclusive + */ +void lv_spinbox_set_range(lv_obj_t * obj, int32_t range_min, int32_t range_max); + +/** + * Set cursor position to a specific digit for edition + * @param obj pointer to spinbox + * @param pos selected position in spinbox + */ +void lv_spinbox_set_cursor_pos(lv_obj_t * obj, uint8_t pos); + +/** + * Set direction of digit step when clicking an encoder button while in editing mode + * @param obj pointer to spinbox + * @param direction the direction (LV_DIR_RIGHT or LV_DIR_LEFT) + */ +void lv_spinbox_set_digit_step_direction(lv_obj_t * obj, lv_dir_t direction); + +/*===================== + * Getter functions + *====================*/ + +/** + * Get spinbox rollover function status + * @param obj pointer to spinbox + */ +bool lv_spinbox_get_rollover(lv_obj_t * obj); + +/** + * Get the spinbox numeral value (user has to convert to float according to its digit format) + * @param obj pointer to spinbox + * @return value integer value of the spinbox + */ +int32_t lv_spinbox_get_value(lv_obj_t * obj); + +/** + * Get the spinbox step value (user has to convert to float according to its digit format) + * @param obj pointer to spinbox + * @return value integer step value of the spinbox + */ +int32_t lv_spinbox_get_step(lv_obj_t * obj); + +/*===================== + * Other functions + *====================*/ + +/** + * Select next lower digit for edition by dividing the step by 10 + * @param obj pointer to spinbox + */ +void lv_spinbox_step_next(lv_obj_t * obj); + +/** + * Select next higher digit for edition by multiplying the step by 10 + * @param obj pointer to spinbox + */ +void lv_spinbox_step_prev(lv_obj_t * obj); + +/** + * Increment spinbox value by one step + * @param obj pointer to spinbox + */ +void lv_spinbox_increment(lv_obj_t * obj); + +/** + * Decrement spinbox value by one step + * @param obj pointer to spinbox + */ +void lv_spinbox_decrement(lv_obj_t * obj); + +/********************** + * MACROS + **********************/ + +/* It was ambiguous in MicroPython. See https://github.com/lvgl/lvgl/issues/3301 + * TODO remove in v9*/ +#define lv_spinbox_set_pos lv_spinbox_set_cursor_pos + +#endif /*LV_USE_SPINBOX*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif +#endif /*LV_SPINBOX_H*/ diff --git a/lib/lvgl/src/extra/widgets/spinner/lv_spinner.c b/lib/lvgl/src/extra/widgets/spinner/lv_spinner.c new file mode 100644 index 00000000..6fc6d742 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/spinner/lv_spinner.c @@ -0,0 +1,104 @@ +/** + * @file lv_spinner.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_spinner.h" +#if LV_USE_SPINNER + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_spinner_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void arc_anim_start_angle(void * obj, int32_t v); +static void arc_anim_end_angle(void * obj, int32_t v); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_spinner_class = { + .base_class = &lv_arc_class, + .constructor_cb = lv_spinner_constructor +}; + +static uint32_t time_param; +static uint32_t arc_length_param; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +/** + * Create a spinner object + * @param parent pointer to an object, it will be the parent of the new spinner + * @return pointer to the created spinner + */ +lv_obj_t * lv_spinner_create(lv_obj_t * parent, uint32_t time, uint32_t arc_length) +{ + time_param = time; + arc_length_param = arc_length; + + lv_obj_t * obj = lv_obj_class_create_obj(&lv_spinner_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_spinner_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_TRACE_OBJ_CREATE("begin"); + + LV_UNUSED(class_p); + + lv_obj_clear_flag(obj, LV_OBJ_FLAG_CLICKABLE); + + lv_anim_t a; + lv_anim_init(&a); + lv_anim_set_var(&a, obj); + lv_anim_set_exec_cb(&a, arc_anim_end_angle); + lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); + lv_anim_set_time(&a, time_param); + lv_anim_set_values(&a, arc_length_param, 360 + arc_length_param); + lv_anim_start(&a); + + lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out); + lv_anim_set_values(&a, 0, 360); + lv_anim_set_exec_cb(&a, arc_anim_start_angle); + lv_anim_start(&a); + + lv_arc_set_bg_angles(obj, 0, 360); + lv_arc_set_rotation(obj, 270); +} + + +static void arc_anim_start_angle(void * obj, int32_t v) +{ + lv_arc_set_start_angle(obj, (uint16_t) v); +} + + +static void arc_anim_end_angle(void * obj, int32_t v) +{ + lv_arc_set_end_angle(obj, (uint16_t) v); +} + +#endif /*LV_USE_SPINNER*/ diff --git a/lib/lvgl/src/extra/widgets/spinner/lv_spinner.h b/lib/lvgl/src/extra/widgets/spinner/lv_spinner.h new file mode 100644 index 00000000..2ab36f64 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/spinner/lv_spinner.h @@ -0,0 +1,50 @@ +/** + * @file lv_spinner.h + * + */ + +#ifndef LV_SPINNER_H +#define LV_SPINNER_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_SPINNER + +/*Testing of dependencies*/ +#if LV_USE_ARC == 0 +#error "lv_spinner: lv_arc is required. Enable it in lv_conf.h (LV_USE_ARC 1) " +#endif + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +extern const lv_obj_class_t lv_spinner_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t * lv_spinner_create(lv_obj_t * parent, uint32_t time, uint32_t arc_length); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_SPINNER*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_SPINNER_H*/ diff --git a/lib/lvgl/src/extra/widgets/tabview/lv_tabview.c b/lib/lvgl/src/extra/widgets/tabview/lv_tabview.c new file mode 100755 index 00000000..81addc66 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/tabview/lv_tabview.c @@ -0,0 +1,352 @@ +/** + * @file lv_tabview.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_tabview.h" +#if LV_USE_TABVIEW + +#include "../../../misc/lv_assert.h" + +/********************* + * DEFINES + *********************/ +#define MY_CLASS &lv_tabview_class + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_tabview_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_tabview_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_tabview_event(const lv_obj_class_t * class_p, lv_event_t * e); +static void btns_value_changed_event_cb(lv_event_t * e); +static void cont_scroll_end_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_tabview_class = { + .constructor_cb = lv_tabview_constructor, + .destructor_cb = lv_tabview_destructor, + .event_cb = lv_tabview_event, + .width_def = LV_PCT(100), + .height_def = LV_PCT(100), + .base_class = &lv_obj_class, + .instance_size = sizeof(lv_tabview_t) +}; + +static lv_dir_t tabpos_create; +static lv_coord_t tabsize_create; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_tabview_create(lv_obj_t * parent, lv_dir_t tab_pos, lv_coord_t tab_size) +{ + LV_LOG_INFO("begin"); + tabpos_create = tab_pos; + tabsize_create = tab_size; + + lv_obj_t * obj = lv_obj_class_create_obj(&lv_tabview_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t * lv_tabview_add_tab(lv_obj_t * obj, const char * name) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + lv_obj_t * cont = lv_tabview_get_content(obj); + + lv_obj_t * page = lv_obj_create(cont); + lv_obj_set_size(page, LV_PCT(100), LV_PCT(100)); + lv_obj_clear_flag(page, LV_OBJ_FLAG_CLICK_FOCUSABLE); + uint32_t tab_id = lv_obj_get_child_cnt(cont); + + lv_obj_t * btns = lv_tabview_get_tab_btns(obj); + + char ** old_map = tabview->map; + char ** new_map; + + /*top or bottom dir*/ + if(tabview->tab_pos & LV_DIR_VER) { + new_map = lv_mem_alloc((tab_id + 1) * sizeof(const char *)); + lv_memcpy_small(new_map, old_map, sizeof(const char *) * (tab_id - 1)); + new_map[tab_id - 1] = lv_mem_alloc(strlen(name) + 1); + strcpy((char *)new_map[tab_id - 1], name); + new_map[tab_id] = ""; + } + /*left or right dir*/ + else { + new_map = lv_mem_alloc((tab_id * 2) * sizeof(const char *)); + lv_memcpy_small(new_map, old_map, sizeof(const char *) * (tab_id - 1) * 2); + if(tabview->tab_cnt == 0) { + new_map[0] = lv_mem_alloc(strlen(name) + 1); + strcpy((char *)new_map[0], name); + new_map[1] = ""; + } + else { + new_map[tab_id * 2 - 3] = "\n"; + new_map[tab_id * 2 - 2] = lv_mem_alloc(strlen(name) + 1); + new_map[tab_id * 2 - 1] = ""; + strcpy((char *)new_map[(tab_id * 2) - 2], name); + } + } + tabview->map = new_map; + lv_btnmatrix_set_map(btns, (const char **)new_map); + lv_mem_free(old_map); + + lv_btnmatrix_set_btn_ctrl_all(btns, LV_BTNMATRIX_CTRL_CHECKABLE | LV_BTNMATRIX_CTRL_CLICK_TRIG | + LV_BTNMATRIX_CTRL_NO_REPEAT); + + tabview->tab_cnt++; + if(tabview->tab_cnt == 1) { + lv_tabview_set_act(obj, 0, LV_ANIM_OFF); + } + + lv_btnmatrix_set_btn_ctrl(btns, tabview->tab_cur, LV_BTNMATRIX_CTRL_CHECKED); + + return page; +} + +void lv_tabview_rename_tab(lv_obj_t * obj, uint32_t id, const char * new_name) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + + if(id >= tabview->tab_cnt) return; + if(tabview->tab_pos & LV_DIR_HOR) id *= 2; + + lv_mem_free(tabview->map[id]); + tabview->map[id] = lv_mem_alloc(strlen(new_name) + 1); + strcpy(tabview->map[id], new_name); + lv_obj_invalidate(obj); +} + +void lv_tabview_set_act(lv_obj_t * obj, uint32_t id, lv_anim_enable_t anim_en) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + + if(id >= tabview->tab_cnt) { + id = tabview->tab_cnt - 1; + } + + /*To be sure lv_obj_get_content_width will return valid value*/ + lv_obj_update_layout(obj); + + lv_obj_t * cont = lv_tabview_get_content(obj); + if(cont == NULL) return; + + if((tabview->tab_pos & LV_DIR_VER) != 0) { + lv_coord_t gap = lv_obj_get_style_pad_column(cont, LV_PART_MAIN); + lv_coord_t w = lv_obj_get_content_width(cont); + if(lv_obj_get_style_base_dir(obj, LV_PART_MAIN) != LV_BASE_DIR_RTL) { + lv_obj_scroll_to_x(cont, id * (gap + w), anim_en); + } + else { + int32_t id_rtl = -(int32_t)id; + lv_obj_scroll_to_x(cont, (gap + w) * id_rtl, anim_en); + } + } + else { + lv_coord_t gap = lv_obj_get_style_pad_row(cont, LV_PART_MAIN); + lv_coord_t h = lv_obj_get_content_height(cont); + lv_obj_scroll_to_y(cont, id * (gap + h), anim_en); + } + + lv_obj_t * btns = lv_tabview_get_tab_btns(obj); + lv_btnmatrix_set_btn_ctrl(btns, id, LV_BTNMATRIX_CTRL_CHECKED); + tabview->tab_cur = id; +} + +uint16_t lv_tabview_get_tab_act(lv_obj_t * obj) +{ + LV_ASSERT_OBJ(obj, MY_CLASS); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + return tabview->tab_cur; +} + +lv_obj_t * lv_tabview_get_content(lv_obj_t * tv) +{ + return lv_obj_get_child(tv, 1); +} + +lv_obj_t * lv_tabview_get_tab_btns(lv_obj_t * tv) +{ + return lv_obj_get_child(tv, 0); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_tabview_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + + tabview->tab_pos = tabpos_create; + + switch(tabview->tab_pos) { + case LV_DIR_TOP: + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + break; + case LV_DIR_BOTTOM: + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN_REVERSE); + break; + case LV_DIR_LEFT: + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW); + break; + case LV_DIR_RIGHT: + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_ROW_REVERSE); + break; + } + + lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100)); + + lv_obj_t * btnm; + lv_obj_t * cont; + + btnm = lv_btnmatrix_create(obj); + cont = lv_obj_create(obj); + + lv_btnmatrix_set_one_checked(btnm, true); + tabview->map = lv_mem_alloc(sizeof(const char *)); + tabview->map[0] = ""; + lv_btnmatrix_set_map(btnm, (const char **)tabview->map); + lv_obj_add_event_cb(btnm, btns_value_changed_event_cb, LV_EVENT_VALUE_CHANGED, NULL); + lv_obj_add_flag(btnm, LV_OBJ_FLAG_EVENT_BUBBLE); + + lv_obj_add_event_cb(cont, cont_scroll_end_event_cb, LV_EVENT_ALL, NULL); + lv_obj_set_scrollbar_mode(cont, LV_SCROLLBAR_MODE_OFF); + + switch(tabview->tab_pos) { + case LV_DIR_TOP: + case LV_DIR_BOTTOM: + lv_obj_set_size(btnm, LV_PCT(100), tabsize_create); + lv_obj_set_width(cont, LV_PCT(100)); + lv_obj_set_flex_grow(cont, 1); + break; + case LV_DIR_LEFT: + case LV_DIR_RIGHT: + lv_obj_set_size(btnm, tabsize_create, LV_PCT(100)); + lv_obj_set_height(cont, LV_PCT(100)); + lv_obj_set_flex_grow(cont, 1); + break; + } + + lv_group_t * g = lv_group_get_default(); + if(g) lv_group_add_obj(g, btnm); + + if((tabview->tab_pos & LV_DIR_VER) != 0) { + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW); + lv_obj_set_scroll_snap_x(cont, LV_SCROLL_SNAP_CENTER); + } + else { + lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN); + lv_obj_set_scroll_snap_y(cont, LV_SCROLL_SNAP_CENTER); + } + lv_obj_add_flag(cont, LV_OBJ_FLAG_SCROLL_ONE); + lv_obj_clear_flag(cont, LV_OBJ_FLAG_SCROLL_ON_FOCUS); +} + +static void lv_tabview_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_tabview_t * tabview = (lv_tabview_t *)obj; + + uint32_t i; + if(tabview->tab_pos & LV_DIR_VER) { + for(i = 0; i < tabview->tab_cnt; i++) { + lv_mem_free(tabview->map[i]); + tabview->map[i] = NULL; + } + } + if(tabview->tab_pos & LV_DIR_HOR) { + for(i = 0; i < tabview->tab_cnt; i++) { + lv_mem_free(tabview->map[i * 2]); + tabview->map[i * 2] = NULL; + } + } + + + lv_mem_free(tabview->map); + tabview->map = NULL; +} + +static void lv_tabview_event(const lv_obj_class_t * class_p, lv_event_t * e) +{ + LV_UNUSED(class_p); + lv_res_t res = lv_obj_event_base(&lv_tabview_class, e); + if(res != LV_RES_OK) return; + + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * target = lv_event_get_target(e); + + if(code == LV_EVENT_SIZE_CHANGED) { + lv_tabview_set_act(target, lv_tabview_get_tab_act(target), LV_ANIM_OFF); + } +} + + +static void btns_value_changed_event_cb(lv_event_t * e) +{ + lv_obj_t * btns = lv_event_get_target(e); + + lv_obj_t * tv = lv_obj_get_parent(btns); + uint32_t id = lv_btnmatrix_get_selected_btn(btns); + lv_tabview_set_act(tv, id, LV_ANIM_ON); +} + +static void cont_scroll_end_event_cb(lv_event_t * e) +{ + lv_obj_t * cont = lv_event_get_target(e); + lv_event_code_t code = lv_event_get_code(e); + + lv_obj_t * tv = lv_obj_get_parent(cont); + lv_tabview_t * tv_obj = (lv_tabview_t *)tv; + if(code == LV_EVENT_LAYOUT_CHANGED) { + lv_tabview_set_act(tv, lv_tabview_get_tab_act(tv), LV_ANIM_OFF); + } + else if(code == LV_EVENT_SCROLL_END) { + lv_indev_t * indev = lv_indev_get_act(); + if(indev && indev->proc.state == LV_INDEV_STATE_PRESSED) { + return; + } + + lv_point_t p; + lv_obj_get_scroll_end(cont, &p); + + lv_coord_t t; + if((tv_obj->tab_pos & LV_DIR_VER) != 0) { + lv_coord_t w = lv_obj_get_content_width(cont); + if(lv_obj_get_style_base_dir(tv, LV_PART_MAIN) == LV_BASE_DIR_RTL) t = -(p.x - w / 2) / w; + else t = (p.x + w / 2) / w; + } + else { + lv_coord_t h = lv_obj_get_content_height(cont); + t = (p.y + h / 2) / h; + } + + if(t < 0) t = 0; + bool new_tab = false; + if(t != lv_tabview_get_tab_act(tv)) new_tab = true; + lv_tabview_set_act(tv, t, LV_ANIM_ON); + + if(new_tab) lv_event_send(tv, LV_EVENT_VALUE_CHANGED, NULL); + } +} +#endif /*LV_USE_TABVIEW*/ diff --git a/lib/lvgl/src/extra/widgets/tabview/lv_tabview.h b/lib/lvgl/src/extra/widgets/tabview/lv_tabview.h new file mode 100644 index 00000000..388c6547 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/tabview/lv_tabview.h @@ -0,0 +1,65 @@ +/** + * @file lv_templ.h + * + */ + +#ifndef LV_TABVIEW_H +#define LV_TABVIEW_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_TABVIEW + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +typedef struct { + lv_obj_t obj; + char ** map; + uint16_t tab_cnt; + uint16_t tab_cur; + lv_dir_t tab_pos; +} lv_tabview_t; + +extern const lv_obj_class_t lv_tabview_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ +lv_obj_t * lv_tabview_create(lv_obj_t * parent, lv_dir_t tab_pos, lv_coord_t tab_size); + +lv_obj_t * lv_tabview_add_tab(lv_obj_t * tv, const char * name); + +void lv_tabview_rename_tab(lv_obj_t * obj, uint32_t tab_id, const char * new_name); + +lv_obj_t * lv_tabview_get_content(lv_obj_t * tv); + +lv_obj_t * lv_tabview_get_tab_btns(lv_obj_t * tv); + +void lv_tabview_set_act(lv_obj_t * obj, uint32_t id, lv_anim_enable_t anim_en); + +uint16_t lv_tabview_get_tab_act(lv_obj_t * tv); + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_TABVIEW*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_TABVIEW_H*/ diff --git a/lib/lvgl/src/extra/widgets/tileview/lv_tileview.c b/lib/lvgl/src/extra/widgets/tileview/lv_tileview.c new file mode 100644 index 00000000..17fdb519 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/tileview/lv_tileview.c @@ -0,0 +1,194 @@ +/** + * @file lv_tileview.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_tileview.h" +#include "../../../core/lv_indev.h" +#if LV_USE_TILEVIEW + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_tileview_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void lv_tileview_tile_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); +static void tileview_event_cb(lv_event_t * e); + +/********************** + * STATIC VARIABLES + **********************/ + +const lv_obj_class_t lv_tileview_class = {.constructor_cb = lv_tileview_constructor, + .base_class = &lv_obj_class, + .instance_size = sizeof(lv_tileview_t) + }; + +const lv_obj_class_t lv_tileview_tile_class = {.constructor_cb = lv_tileview_tile_constructor, + .base_class = &lv_obj_class, + .instance_size = sizeof(lv_tileview_tile_t) + }; + +static lv_dir_t create_dir; +static uint32_t create_col_id; +static uint32_t create_row_id; + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_tileview_create(lv_obj_t * parent) +{ + LV_LOG_INFO("begin"); + lv_obj_t * obj = lv_obj_class_create_obj(&lv_tileview_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +/*====================== + * Add/remove functions + *=====================*/ + +lv_obj_t * lv_tileview_add_tile(lv_obj_t * tv, uint8_t col_id, uint8_t row_id, lv_dir_t dir) +{ + LV_LOG_INFO("begin"); + create_dir = dir; + create_col_id = col_id; + create_row_id = row_id; + + lv_obj_t * obj = lv_obj_class_create_obj(&lv_tileview_tile_class, tv); + lv_obj_class_init_obj(obj); + return obj; +} + +void lv_obj_set_tile(lv_obj_t * obj, lv_obj_t * tile_obj, lv_anim_enable_t anim_en) +{ + lv_coord_t tx = lv_obj_get_x(tile_obj); + lv_coord_t ty = lv_obj_get_y(tile_obj); + + lv_tileview_tile_t * tile = (lv_tileview_tile_t *)tile_obj; + lv_tileview_t * tv = (lv_tileview_t *) obj; + tv->tile_act = (lv_obj_t *)tile; + + lv_obj_set_scroll_dir(obj, tile->dir); + lv_obj_scroll_to(obj, tx, ty, anim_en); +} + +void lv_obj_set_tile_id(lv_obj_t * tv, uint32_t col_id, uint32_t row_id, lv_anim_enable_t anim_en) +{ + lv_obj_update_layout(tv); + + lv_coord_t w = lv_obj_get_content_width(tv); + lv_coord_t h = lv_obj_get_content_height(tv); + + lv_coord_t tx = col_id * w; + lv_coord_t ty = row_id * h; + + uint32_t i; + for(i = 0; i < lv_obj_get_child_cnt(tv); i++) { + lv_obj_t * tile_obj = lv_obj_get_child(tv, i); + lv_coord_t x = lv_obj_get_x(tile_obj); + lv_coord_t y = lv_obj_get_y(tile_obj); + if(x == tx && y == ty) { + lv_obj_set_tile(tv, tile_obj, anim_en); + return; + } + } + + LV_LOG_WARN("No tile found with at (%d,%d) index", (int)col_id, (int)row_id); +} + +lv_obj_t * lv_tileview_get_tile_act(lv_obj_t * obj) +{ + lv_tileview_t * tv = (lv_tileview_t *) obj; + return tv->tile_act; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_tileview_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100)); + lv_obj_add_event_cb(obj, tileview_event_cb, LV_EVENT_ALL, NULL); + lv_obj_add_flag(obj, LV_OBJ_FLAG_SCROLL_ONE); + lv_obj_set_scroll_snap_x(obj, LV_SCROLL_SNAP_CENTER); + lv_obj_set_scroll_snap_y(obj, LV_SCROLL_SNAP_CENTER); + +} + +static void lv_tileview_tile_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + + LV_UNUSED(class_p); + lv_obj_t * parent = lv_obj_get_parent(obj); + lv_obj_set_size(obj, LV_PCT(100), LV_PCT(100)); + lv_obj_update_layout(obj); /*Be sure the size is correct*/ + lv_obj_set_pos(obj, create_col_id * lv_obj_get_content_width(parent), + create_row_id * lv_obj_get_content_height(parent)); + + lv_tileview_tile_t * tile = (lv_tileview_tile_t *)obj; + tile->dir = create_dir; + + if(create_col_id == 0 && create_row_id == 0) { + lv_obj_set_scroll_dir(parent, create_dir); + } +} + +static void tileview_event_cb(lv_event_t * e) +{ + lv_event_code_t code = lv_event_get_code(e); + lv_obj_t * obj = lv_event_get_target(e); + lv_tileview_t * tv = (lv_tileview_t *) obj; + + if(code == LV_EVENT_SCROLL_END) { + lv_indev_t * indev = lv_indev_get_act(); + if(indev && indev->proc.state == LV_INDEV_STATE_PRESSED) { + return; + } + + lv_coord_t w = lv_obj_get_content_width(obj); + lv_coord_t h = lv_obj_get_content_height(obj); + + lv_point_t scroll_end; + lv_obj_get_scroll_end(obj, &scroll_end); + lv_coord_t left = scroll_end.x; + lv_coord_t top = scroll_end.y; + + lv_coord_t tx = ((left + (w / 2)) / w) * w; + lv_coord_t ty = ((top + (h / 2)) / h) * h; + + lv_dir_t dir = LV_DIR_ALL; + uint32_t i; + for(i = 0; i < lv_obj_get_child_cnt(obj); i++) { + lv_obj_t * tile_obj = lv_obj_get_child(obj, i); + lv_coord_t x = lv_obj_get_x(tile_obj); + lv_coord_t y = lv_obj_get_y(tile_obj); + if(x == tx && y == ty) { + lv_tileview_tile_t * tile = (lv_tileview_tile_t *)tile_obj; + tv->tile_act = (lv_obj_t *)tile; + dir = tile->dir; + lv_event_send(obj, LV_EVENT_VALUE_CHANGED, NULL); + break; + } + } + lv_obj_set_scroll_dir(obj, dir); + } +} +#endif /*LV_USE_TILEVIEW*/ diff --git a/lib/lvgl/src/extra/widgets/tileview/lv_tileview.h b/lib/lvgl/src/extra/widgets/tileview/lv_tileview.h new file mode 100644 index 00000000..7adeec33 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/tileview/lv_tileview.h @@ -0,0 +1,72 @@ +/** + * @file lv_tileview.h + * + */ + +#ifndef LV_TILEVIEW_H +#define LV_TILEVIEW_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../core/lv_obj.h" + +#if LV_USE_TILEVIEW + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_obj_t obj; + lv_obj_t * tile_act; +} lv_tileview_t; + +typedef struct { + lv_obj_t obj; + lv_dir_t dir; +} lv_tileview_tile_t; + +extern const lv_obj_class_t lv_tileview_class; +extern const lv_obj_class_t lv_tileview_tile_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/** + * Create a Tileview object + * @param parent pointer to an object, it will be the parent of the new tileview + * @return pointer to the created tileview + */ +lv_obj_t * lv_tileview_create(lv_obj_t * parent); + +lv_obj_t * lv_tileview_add_tile(lv_obj_t * tv, uint8_t col_id, uint8_t row_id, lv_dir_t dir); + +void lv_obj_set_tile(lv_obj_t * tv, lv_obj_t * tile_obj, lv_anim_enable_t anim_en); +void lv_obj_set_tile_id(lv_obj_t * tv, uint32_t col_id, uint32_t row_id, lv_anim_enable_t anim_en); + +lv_obj_t * lv_tileview_get_tile_act(lv_obj_t * obj); + +/*===================== + * Other functions + *====================*/ + +/********************** + * MACROS + **********************/ + +#endif /*LV_USE_TILEVIEW*/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_TILEVIEW_H*/ diff --git a/lib/lvgl/src/extra/widgets/win/lv_win.c b/lib/lvgl/src/extra/widgets/win/lv_win.c new file mode 100644 index 00000000..92c3b8ba --- /dev/null +++ b/lib/lvgl/src/extra/widgets/win/lv_win.c @@ -0,0 +1,110 @@ +/** + * @file lv_win.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "lv_win.h" +#if LV_USE_WIN + + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ +static void lv_win_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj); + +/********************** + * STATIC VARIABLES + **********************/ +const lv_obj_class_t lv_win_class = { + .constructor_cb = lv_win_constructor, + .width_def = LV_PCT(100), + .height_def = LV_PCT(100), + .base_class = &lv_obj_class, + .instance_size = sizeof(lv_win_t) +}; +static lv_coord_t create_header_height; +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +lv_obj_t * lv_win_create(lv_obj_t * parent, lv_coord_t header_height) +{ + LV_LOG_INFO("begin"); + create_header_height = header_height; + + lv_obj_t * obj = lv_obj_class_create_obj(&lv_win_class, parent); + lv_obj_class_init_obj(obj); + return obj; +} + +lv_obj_t * lv_win_add_title(lv_obj_t * win, const char * txt) +{ + lv_obj_t * header = lv_win_get_header(win); + lv_obj_t * title = lv_label_create(header); + lv_label_set_long_mode(title, LV_LABEL_LONG_DOT); + lv_label_set_text(title, txt); + lv_obj_set_flex_grow(title, 1); + return title; +} + +lv_obj_t * lv_win_add_btn(lv_obj_t * win, const void * icon, lv_coord_t btn_w) +{ + lv_obj_t * header = lv_win_get_header(win); + lv_obj_t * btn = lv_btn_create(header); + lv_obj_set_size(btn, btn_w, LV_PCT(100)); + + lv_obj_t * img = lv_img_create(btn); + lv_img_set_src(img, icon); + lv_obj_align(img, LV_ALIGN_CENTER, 0, 0); + + return btn; +} + +lv_obj_t * lv_win_get_header(lv_obj_t * win) +{ + return lv_obj_get_child(win, 0); +} + +lv_obj_t * lv_win_get_content(lv_obj_t * win) +{ + return lv_obj_get_child(win, 1); +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +static void lv_win_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj) +{ + LV_UNUSED(class_p); + lv_obj_t * parent = lv_obj_get_parent(obj); + lv_obj_set_size(obj, lv_obj_get_width(parent), lv_obj_get_height(parent)); + lv_obj_set_flex_flow(obj, LV_FLEX_FLOW_COLUMN); + + lv_obj_t * header = lv_obj_create(obj); + lv_obj_set_size(header, LV_PCT(100), create_header_height); + lv_obj_set_flex_flow(header, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER); + + lv_obj_t * cont = lv_obj_create(obj); + lv_obj_set_flex_grow(cont, 1); + lv_obj_set_width(cont, LV_PCT(100)); +} + +#endif + diff --git a/lib/lvgl/src/extra/widgets/win/lv_win.h b/lib/lvgl/src/extra/widgets/win/lv_win.h new file mode 100644 index 00000000..4342b310 --- /dev/null +++ b/lib/lvgl/src/extra/widgets/win/lv_win.h @@ -0,0 +1,51 @@ +/** + * @file lv_win.h + * + */ + +#ifndef LV_WIN_H +#define LV_WIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +/********************* + * DEFINES + *********************/ + +/********************** + * TYPEDEFS + **********************/ +typedef struct { + lv_obj_t obj; +} lv_win_t; + +extern const lv_obj_class_t lv_win_class; + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +lv_obj_t * lv_win_create(lv_obj_t * parent, lv_coord_t header_height); + + +lv_obj_t * lv_win_add_title(lv_obj_t * win, const char * txt); +lv_obj_t * lv_win_add_btn(lv_obj_t * win, const void * icon, lv_coord_t btn_w); + +lv_obj_t * lv_win_get_header(lv_obj_t * win); +lv_obj_t * lv_win_get_content(lv_obj_t * win); +/********************** + * MACROS + **********************/ + +#ifdef __cplusplus +} /*extern "C"*/ +#endif + +#endif /*LV_WIN_H*/ |
