This commit is contained in:
2026-02-26 19:56:43 -08:00
commit f000ee7c19
1137 changed files with 603879 additions and 0 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,339 @@
/*
@file EVE_commands.h
@brief contains FT8xx / BT8xx function prototypes
@version 5.0
@date 2023-12-29
@author Rudolph Riedel
@section LICENSE
MIT License
Copyright (c) 2016-2023 Rudolph Riedel
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.
@section History
5.0
- added prototype for EVE_cmd_plkfreq()
- replaced BT81X_ENABLE with "EVE_GEN > 2"
- removed FT81X_ENABLE as FT81x already is the lowest supported chip revision now
- removed the formerly as deprected marked EVE_get_touch_tag()
- changed EVE_color_rgb() to use a 32 bit value like the rest of the color commands
- removed the meta-commands EVE_cmd_point(), EVE_cmd_line() and EVE_cmd_rect()
- removed obsolete functions EVE_get_cmdoffset(void) and EVE_report_cmdoffset(void) - cmdoffset is gone
- renamed EVE_LIB_GetProps() back to EVE_cmd_getprops() since it does not do anything special to justify a special name
- added prototype for helper function EVE_memWrite_sram_buffer()
- added prototypes for EVE_cmd_bitmap_transform() and EVE_cmd_bitmap_transform_burst()
- added prototype for EVE_cmd_playvideo()
- added prototypes for EVE_cmd_setfont_burst() and EVE_cmd_setfont2_burst()
- added prototype for EVE_cmd_videoframe()
- restructured: functions are sorted by chip-generation and within their group in alphabetical order
- reimplementedEVE_cmd_getmatrix() again, it needs to read values, not write them
- added prototypes for EVE_cmd_fontcache() and EVE_cmd_fontcachequery()
- added prototype for EVE_cmd_flashprogram()
- added prototype for EVE_cmd_calibratesub()
- added prototypes for EVE_cmd_animframeram(), EVE_cmd_animframeram_burst(), EVE_cmd_animstartram(),
EVE_cmd_animstartram_burst()
- added prototypes for EVE_cmd_apilevel(), EVE_cmd_apilevel_burst()
- added prototypes for EVE_cmd_calllist(), EVE_cmd_calllist_burst()
- added prototype for EVE_cmd_getimage()
- added prototypes for EVE_cmd_hsf(), EVE_cmd_hsf_burst()
- added prototype for EVE_cmd_linetime()
- added prototypes for EVE_cmd_newlist(), EVE_cmd_newlist_burst()
- added prototypes for EVE_cmd_runanim(), EVE_cmd_runanim_burst()
- added prototype for EVE_cmd_wait()
- removed the history from before 4.0
- added an enum with return codes to have the functions return something more meaningfull
- finally removed EVE_cmd_start() after setting it to deprecatd with the first 5.0 release
- renamed EVE_cmd_execute() to EVE_execute_cmd() to be more consistent, this is is not an EVE command
- added the return-value of EVE_FIFO_HALF_EMPTY to EVE_busy() to indicate there is more than 2048 bytes available
- removed the 4.0 history
- added parameter width to EVE_calibrate_manual()
- changed the varargs versions of cmd_button, cmd_text and cmd_toggle to use an array of uint32_t values to comply with MISRA-C
- fixed some MISRA-C issues
- basic maintenance: checked for violations of white space and indent rules
- more linter fixes for minor issues like variables shorter than 3 characters
- added EVE_color_a() / EVE_color_a_burst()
- removed EVE_cmd_newlist_burst() prototype as the function got removed earlier
- added prototype for EVE_write_display_parameters()
- added EVE_memRead_sram_buffer()
- added EVE_FAULT_RECOVERED to the list of return codes
- added defines for the state of the external flash
- added protype for EVE_get_and_reset_fault_state()
- put E_OK and E_NOT_OK in #ifndef/#endif guards as these are usually defined
already in AUTOSAR projects
- renamed EVE_FAIL_CHIPID_TIMEOUT to EVE_FAIL_REGID_TIMEOUT as suggested by #93 on github
- changed a number of function parameters from signed to unsigned following the
updated BT81x series programming guide V2.4
- commented out EVE_cmd_regread() prototype
- removed prototype for EVE_cmd_hsf_burst()
*/
#ifndef EVE_COMMANDS_H
#define EVE_COMMANDS_H
#include "EVE.h"
#if !defined E_OK
#define E_OK 0U
#endif
#if !defined E_NOT_OK
#define E_NOT_OK 1U
#endif
#define EVE_FAIL_REGID_TIMEOUT 2U
#define EVE_FAIL_RESET_TIMEOUT 3U
#define EVE_FAIL_PCLK_FREQ 4U
#define EVE_FAIL_FLASH_STATUS_INIT 5U
#define EVE_FAIL_FLASH_STATUS_DETACHED 6U
#define EVE_FAIL_FLASHFAST_NOT_SUPPORTED 7U
#define EVE_FAIL_FLASHFAST_NO_HEADER_DETECTED 8U
#define EVE_FAIL_FLASHFAST_SECTOR0_FAILED 9U
#define EVE_FAIL_FLASHFAST_BLOB_MISMATCH 10U
#define EVE_FAIL_FLASHFAST_SPEED_TEST 11U
#define EVE_IS_BUSY 12U
#define EVE_FIFO_HALF_EMPTY 13U
#define EVE_FAULT_RECOVERED 14U
#define EVE_FLASH_STATUS_INIT 0U
#define EVE_FLASH_STATUS_DETACHED 1U
#define EVE_FLASH_STATUS_BASIC 2U
#define EVE_FLASH_STATUS_FULL 3U
/* ##################################################################
helper functions
##################################################################### */
void EVE_cmdWrite(uint8_t const command, uint8_t const parameter);
uint8_t EVE_memRead8(uint32_t const ft_address);
uint16_t EVE_memRead16(uint32_t const ft_address);
uint32_t EVE_memRead32(uint32_t const ft_address);
void EVE_memWrite8(uint32_t const ft_address, uint8_t const ft_data);
void EVE_memWrite16(uint32_t const ft_address, uint16_t const ft_data);
void EVE_memWrite32(uint32_t const ft_address, uint32_t const ft_data);
void EVE_memWrite_flash_buffer(uint32_t const ft_address, const uint8_t *p_data, uint32_t const len);
void EVE_memWrite_sram_buffer(uint32_t const ft_address, const uint8_t *p_data, uint32_t const len);
void EVE_memRead_sram_buffer(uint32_t const ft_address, uint8_t *p_data, uint32_t const len);
uint8_t EVE_busy(void);
uint8_t EVE_get_and_reset_fault_state(void);
void EVE_execute_cmd(void);
/* ##################################################################
commands and functions to be used outside of display-lists
##################################################################### */
/* EVE4: BT817 / BT818 */
#if EVE_GEN > 3
void EVE_cmd_flashprogram(uint32_t dest, uint32_t src, uint32_t num);
void EVE_cmd_fontcache(uint32_t font, uint32_t ptr, uint32_t num);
void EVE_cmd_fontcachequery(uint32_t *p_total, uint32_t *p_used);
void EVE_cmd_getimage(uint32_t *p_source, uint32_t *p_fmt, uint32_t *p_width, uint32_t *p_height, uint32_t *p_palette);
void EVE_cmd_linetime(uint32_t dest);
void EVE_cmd_newlist(uint32_t adr);
uint32_t EVE_cmd_pclkfreq(uint32_t ftarget, int32_t rounding);
void EVE_cmd_wait(uint32_t usec);
#endif /* EVE_GEN > 3 */
/* EVE3: BT815 / BT816 */
#if EVE_GEN > 2
void EVE_cmd_clearcache(void);
void EVE_cmd_flashattach(void);
void EVE_cmd_flashdetach(void);
void EVE_cmd_flasherase(void);
uint32_t EVE_cmd_flashfast(void);
void EVE_cmd_flashspidesel(void);
void EVE_cmd_flashread(uint32_t dest, uint32_t src, uint32_t num);
void EVE_cmd_flashsource(uint32_t ptr);
void EVE_cmd_flashspirx(uint32_t dest, uint32_t num);
void EVE_cmd_flashspitx(uint32_t num, const uint8_t *p_data);
void EVE_cmd_flashupdate(uint32_t dest, uint32_t src, uint32_t num);
void EVE_cmd_flashwrite(uint32_t ptr, uint32_t num, const uint8_t *p_data);
void EVE_cmd_inflate2(uint32_t ptr, uint32_t options, const uint8_t *p_data, uint32_t len);
#endif /* EVE_GEN > 2 */
void EVE_cmd_getprops(uint32_t *p_pointer, uint32_t *p_width, uint32_t *p_height);
uint32_t EVE_cmd_getptr(void);
void EVE_cmd_inflate(uint32_t ptr, const uint8_t *p_data, uint32_t len);
void EVE_cmd_interrupt(uint32_t msec);
void EVE_cmd_loadimage(uint32_t ptr, uint32_t options, const uint8_t *p_data, uint32_t len);
void EVE_cmd_mediafifo(uint32_t ptr, uint32_t size);
void EVE_cmd_memcpy(uint32_t dest, uint32_t src, uint32_t num);
uint32_t EVE_cmd_memcrc(uint32_t ptr, uint32_t num);
void EVE_cmd_memset(uint32_t ptr, uint8_t value, uint32_t num);
void EVE_cmd_memzero(uint32_t ptr, uint32_t num);
void EVE_cmd_playvideo(uint32_t options, const uint8_t *p_data, uint32_t len);
void EVE_cmd_setrotate(uint32_t rotation);
void EVE_cmd_snapshot(uint32_t ptr);
void EVE_cmd_snapshot2(uint32_t fmt, uint32_t ptr, int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt);
void EVE_cmd_track(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t tag);
void EVE_cmd_videoframe(uint32_t dest, uint32_t result_ptr);
/*void EVE_cmd_memwrite(uint32_t dest, uint32_t num, const uint8_t *p_data);*/
/*uint32_t EVE_cmd_regread(uint32_t ptr);*/
/* ##################################################################
patching and initialization
##################################################################### */
#if EVE_GEN > 2
uint8_t EVE_init_flash(void);
#endif /* EVE_GEN > 2 */
void EVE_write_display_parameters(void);
uint8_t EVE_init(void);
/* ##################################################################
functions for display lists
##################################################################### */
void EVE_start_cmd_burst(void);
void EVE_end_cmd_burst(void);
/* EVE4: BT817 / BT818 */
#if EVE_GEN > 3
void EVE_cmd_animframeram(int16_t xc0, int16_t yc0, uint32_t aoptr, uint32_t frame);
void EVE_cmd_animframeram_burst(int16_t xc0, int16_t yc0, uint32_t aoptr, uint32_t frame);
void EVE_cmd_animstartram(int32_t chnl, uint32_t aoptr, uint32_t loop);
void EVE_cmd_animstartram_burst(int32_t chnl, uint32_t aoptr, uint32_t loop);
void EVE_cmd_apilevel(uint32_t level);
void EVE_cmd_apilevel_burst(uint32_t level);
void EVE_cmd_calibratesub(uint16_t xc0, uint16_t yc0, uint16_t width, uint16_t height);
void EVE_cmd_calllist(uint32_t adr);
void EVE_cmd_calllist_burst(uint32_t adr);
void EVE_cmd_hsf(uint32_t hsf);
void EVE_cmd_runanim(uint32_t waitmask, uint32_t play);
void EVE_cmd_runanim_burst(uint32_t waitmask, uint32_t play);
#endif /* EVE_GEN > 3 */
/* EVE3: BT815 / BT816 */
#if EVE_GEN > 2
void EVE_cmd_animdraw(int32_t chnl);
void EVE_cmd_animdraw_burst(int32_t chnl);
void EVE_cmd_animframe(int16_t xc0, int16_t yc0, uint32_t aoptr, uint32_t frame);
void EVE_cmd_animframe_burst(int16_t xc0, int16_t yc0, uint32_t aoptr, uint32_t frame);
void EVE_cmd_animstart(int32_t chnl, uint32_t aoptr, uint32_t loop);
void EVE_cmd_animstart_burst(int32_t chnl, uint32_t aoptr, uint32_t loop);
void EVE_cmd_animstop(int32_t chnl);
void EVE_cmd_animstop_burst(int32_t chnl);
void EVE_cmd_animxy(int32_t chnl, int16_t xc0, int16_t yc0);
void EVE_cmd_animxy_burst(int32_t chnl, int16_t xc0, int16_t yc0);
void EVE_cmd_appendf(uint32_t ptr, uint32_t num);
void EVE_cmd_appendf_burst(uint32_t ptr, uint32_t num);
uint16_t EVE_cmd_bitmap_transform(int32_t xc0, int32_t yc0, int32_t xc1, int32_t yc1, int32_t xc2, int32_t yc2, int32_t tx0,
int32_t ty0, int32_t tx1, int32_t ty1, int32_t tx2, int32_t ty2);
void EVE_cmd_bitmap_transform_burst(int32_t xc0, int32_t yc0, int32_t xc1, int32_t yc1, int32_t xc2, int32_t yc2, int32_t tx0,
int32_t ty0, int32_t tx1, int32_t ty1, int32_t tx2, int32_t ty2);
void EVE_cmd_fillwidth(uint32_t pixel);
void EVE_cmd_fillwidth_burst(uint32_t pixel);
void EVE_cmd_gradienta(int16_t xc0, int16_t yc0, uint32_t argb0, int16_t xc1, int16_t yc1, uint32_t argb1);
void EVE_cmd_gradienta_burst(int16_t xc0, int16_t yc0, uint32_t argb0, int16_t xc1, int16_t yc1, uint32_t argb1);
void EVE_cmd_rotatearound(int32_t xc0, int32_t yc0, uint32_t angle, int32_t scale);
void EVE_cmd_rotatearound_burst(int32_t xc0, int32_t yc0, uint32_t angle, int32_t scale);
void EVE_cmd_button_var(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t font, uint16_t options, const char *p_text, uint8_t num_args, const uint32_t p_arguments[]);
void EVE_cmd_button_var_burst(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t font, uint16_t options, const char *p_text, uint8_t num_args, const uint32_t p_arguments[]);
void EVE_cmd_text_var(int16_t xc0, int16_t yc0, uint16_t font, uint16_t options, const char *p_text, uint8_t num_args, const uint32_t p_arguments[]);
void EVE_cmd_text_var_burst(int16_t xc0, int16_t yc0, uint16_t font, uint16_t options, const char *p_text, uint8_t num_args, const uint32_t p_arguments[]);
void EVE_cmd_toggle_var(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t font, uint16_t options, uint16_t state, const char *p_text, uint8_t num_args, const uint32_t p_arguments[]);
void EVE_cmd_toggle_var_burst(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t font, uint16_t options, uint16_t state, const char *p_text, uint8_t num_args, const uint32_t p_arguments[]);
#endif /* EVE_GEN > 2 */
void EVE_cmd_dl(uint32_t command);
void EVE_cmd_dl_burst(uint32_t command);
void EVE_cmd_append(uint32_t ptr, uint32_t num);
void EVE_cmd_append_burst(uint32_t ptr, uint32_t num);
void EVE_cmd_bgcolor(uint32_t color);
void EVE_cmd_bgcolor_burst(uint32_t color);
void EVE_cmd_button(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t font, uint16_t options, const char *p_text);
void EVE_cmd_button_burst(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t font, uint16_t options, const char *p_text);
void EVE_cmd_calibrate(void);
void EVE_cmd_clock(int16_t xc0, int16_t yc0, uint16_t rad, uint16_t options, uint16_t hours, uint16_t mins, uint16_t secs, uint16_t msecs);
void EVE_cmd_clock_burst(int16_t xc0, int16_t yc0, uint16_t rad, uint16_t options, uint16_t hours, uint16_t mins, uint16_t secs, uint16_t msecs);
void EVE_cmd_dial(int16_t xc0, int16_t yc0, uint16_t rad, uint16_t options, uint16_t val);
void EVE_cmd_dial_burst(int16_t xc0, int16_t yc0, uint16_t rad, uint16_t options, uint16_t val);
void EVE_cmd_fgcolor(uint32_t color);
void EVE_cmd_fgcolor_burst(uint32_t color);
void EVE_cmd_gauge(int16_t xc0, int16_t yc0, uint16_t rad, uint16_t options, uint16_t major, uint16_t minor, uint16_t val, uint16_t range);
void EVE_cmd_gauge_burst(int16_t xc0, int16_t yc0, uint16_t rad, uint16_t options, uint16_t major, uint16_t minor, uint16_t val, uint16_t range);
void EVE_cmd_getmatrix(int32_t *p_a, int32_t *p_b, int32_t *p_c, int32_t *p_d, int32_t *p_e, int32_t *p_f);
void EVE_cmd_gradcolor(uint32_t color);
void EVE_cmd_gradcolor_burst(uint32_t color);
void EVE_cmd_gradient(int16_t xc0, int16_t yc0, uint32_t rgb0, int16_t xc1, int16_t yc1, uint32_t rgb1);
void EVE_cmd_gradient_burst(int16_t xc0, int16_t yc0, uint32_t rgb0, int16_t xc1, int16_t yc1, uint32_t rgb1);
void EVE_cmd_keys(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t font, uint16_t options, const char *p_text);
void EVE_cmd_keys_burst(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t font, uint16_t options, const char *p_text);
void EVE_cmd_number(int16_t xc0, int16_t yc0, uint16_t font, uint16_t options, int32_t number);
void EVE_cmd_number_burst(int16_t xc0, int16_t yc0, uint16_t font, uint16_t options, int32_t number);
void EVE_cmd_progress(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t options, uint16_t val, uint16_t range);
void EVE_cmd_progress_burst(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t options, uint16_t val, uint16_t range);
void EVE_cmd_romfont(uint32_t font, uint32_t romslot);
void EVE_cmd_romfont_burst(uint32_t font, uint32_t romslot);
void EVE_cmd_rotate(uint32_t angle);
void EVE_cmd_rotate_burst(uint32_t angle);
void EVE_cmd_scale(int32_t scx, int32_t scy);
void EVE_cmd_scale_burst(int32_t scx, int32_t scy);
void EVE_cmd_scrollbar(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t options, uint16_t val, uint16_t size, uint16_t range);
void EVE_cmd_scrollbar_burst(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t options, uint16_t val, uint16_t size, uint16_t range);
void EVE_cmd_setbase(uint32_t base);
void EVE_cmd_setbase_burst(uint32_t base);
void EVE_cmd_setbitmap(uint32_t addr, uint16_t fmt, uint16_t width, uint16_t height);
void EVE_cmd_setbitmap_burst(uint32_t addr, uint16_t fmt, uint16_t width, uint16_t height);
void EVE_cmd_setfont(uint32_t font, uint32_t ptr);
void EVE_cmd_setfont_burst(uint32_t font, uint32_t ptr);
void EVE_cmd_setfont2(uint32_t font, uint32_t ptr, uint32_t firstchar);
void EVE_cmd_setfont2_burst(uint32_t font, uint32_t ptr, uint32_t firstchar);
void EVE_cmd_setscratch(uint32_t handle);
void EVE_cmd_setscratch_burst(uint32_t handle);
void EVE_cmd_sketch(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint32_t ptr, uint16_t format);
void EVE_cmd_sketch_burst(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint32_t ptr, uint16_t format);
void EVE_cmd_slider(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t options, uint16_t val, uint16_t range);
void EVE_cmd_slider_burst(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t hgt, uint16_t options, uint16_t val, uint16_t range);
void EVE_cmd_spinner(int16_t xc0, int16_t yc0, uint16_t style, uint16_t scale);
void EVE_cmd_spinner_burst(int16_t xc0, int16_t yc0, uint16_t style, uint16_t scale);
void EVE_cmd_text(int16_t xc0, int16_t yc0, uint16_t font, uint16_t options, const char *p_text);
void EVE_cmd_text_burst(int16_t xc0, int16_t yc0, uint16_t font, uint16_t options, const char *p_text);
void EVE_cmd_toggle(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t font, uint16_t options, uint16_t state, const char *p_text);
void EVE_cmd_toggle_burst(int16_t xc0, int16_t yc0, uint16_t wid, uint16_t font, uint16_t options, uint16_t state, const char *p_text);
void EVE_cmd_translate(int32_t tr_x, int32_t tr_y);
void EVE_cmd_translate_burst(int32_t tr_x, int32_t tr_y);
void EVE_color_rgb(uint32_t color);
void EVE_color_rgb_burst(uint32_t color);
void EVE_color_a(uint8_t alpha);
void EVE_color_a_burst(uint8_t alpha);
/* ##################################################################
special purpose functions
##################################################################### */
void EVE_calibrate_manual(uint16_t width, uint16_t height);
#endif /* EVE_COMMANDS_H */

View File

@@ -0,0 +1,26 @@
#ifndef EVE_CONFIG_H
#define EVE_CONFIG_H
#include "../../draw/eve/lv_draw_eve_private.h"
#define EVE_HSIZE (lv_draw_eve_unit_g->params.hor_res)
#define EVE_VSIZE (lv_draw_eve_unit_g->params.ver_res)
#define EVE_VSYNC0 (lv_draw_eve_unit_g->params.vsync0)
#define EVE_VSYNC1 (lv_draw_eve_unit_g->params.vsync1)
#define EVE_VOFFSET (lv_draw_eve_unit_g->params.voffset)
#define EVE_VCYCLE (lv_draw_eve_unit_g->params.vcycle)
#define EVE_HSYNC0 (lv_draw_eve_unit_g->params.hsync0)
#define EVE_HSYNC1 (lv_draw_eve_unit_g->params.hsync1)
#define EVE_HOFFSET (lv_draw_eve_unit_g->params.hoffset)
#define EVE_HCYCLE (lv_draw_eve_unit_g->params.hcycle)
#define EVE_PCLK (lv_draw_eve_unit_g->params.pclk)
#define EVE_PCLKPOL (lv_draw_eve_unit_g->params.pclkpol)
#define EVE_SWIZZLE (lv_draw_eve_unit_g->params.swizzle)
#define EVE_CSPREAD (lv_draw_eve_unit_g->params.cspread)
#define EVE_HAS_CRYSTAL (lv_draw_eve_unit_g->params.has_crystal)
#define EVE_HAS_GT911 (lv_draw_eve_unit_g->params.has_gt911)
#define EVE_GEN LV_DRAW_EVE_EVE_GENERATION
#define EVE_BACKLIGHT_PWM (lv_draw_eve_unit_g->params.backlight_pwm)
#define EVE_BACKLIGHT_FREQ (lv_draw_eve_unit_g->params.backlight_freq)
#endif /* EVE_CONFIG_H */

View File

@@ -0,0 +1,145 @@
#include "../../lv_conf_internal.h"
#if LV_USE_DRAW_EVE
/*
@file EVE_supplemental.h
@brief supplemental functions
@version 5.0
@date 2023-12-23
@author Rudolph Riedel
@section LICENSE
MIT License
Copyright (c) 2016-2023 Rudolph Riedel
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.
@section History
5.0
- added EVE_polar_cartesian()
*/
#include "EVE_suppplemental.h"
/* define NULL if it not already is */
#ifndef NULL
#include <stdio.h>
#endif
#if defined (__AVR__)
#include <avr/pgmspace.h>
#else
#define PROGMEM
#endif
/*
* @brief widget function to draw a circle
*/
void EVE_widget_circle(int16_t xc0, int16_t yc0, uint16_t radius, uint16_t border, uint32_t bgcolor)
{
EVE_cmd_dl_burst(DL_SAVE_CONTEXT);
EVE_cmd_dl(DL_BEGIN | EVE_POINTS);
EVE_cmd_dl(POINT_SIZE(radius));
EVE_cmd_dl(VERTEX2F(xc0, yc0));
EVE_color_rgb(bgcolor);
EVE_cmd_dl(POINT_SIZE(radius - border));
EVE_cmd_dl(VERTEX2F(xc0, yc0));
EVE_cmd_dl(DL_END);
EVE_cmd_dl_burst(DL_RESTORE_CONTEXT);
}
/*
* @brief widget function to draw a rectangle
*/
void EVE_widget_rectangle(int16_t xc0, int16_t yc0, int16_t wid, int16_t hgt, int16_t border, uint16_t linewidth, uint32_t bgcolor)
{
EVE_cmd_dl_burst(DL_SAVE_CONTEXT);
EVE_cmd_dl(DL_BEGIN | EVE_RECTS);
EVE_cmd_dl(LINE_WIDTH(linewidth));
EVE_cmd_dl(VERTEX2F(xc0, yc0));
EVE_cmd_dl(VERTEX2F(xc0 + wid, yc0 + hgt));
EVE_color_rgb(bgcolor);
EVE_cmd_dl(VERTEX2F(xc0 + border, yc0 + border));
EVE_cmd_dl(VERTEX2F(xc0 + wid - border, yc0 + hgt - border));
EVE_cmd_dl(DL_END);
EVE_cmd_dl_burst(DL_RESTORE_CONTEXT);
}
static const int8_t sine_table[360] PROGMEM =
{
0, 2, 4, 7, 9, 11, 13, 15, 18, 20, 22, 24, 26, 29, 31, 33, 35, 37, 39, 41,
43, 46, 48, 50, 52, 54, 56, 58, 60, 62, 63, 65, 67, 69, 71, 73, 75, 76, 78,
80, 82, 83, 85, 87, 88, 90, 91, 93, 94, 96, 97, 99, 100, 101, 103, 104, 105,
107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 119, 120,
121, 121, 122, 123, 123, 124, 124, 125, 125, 125, 126, 126, 126, 127, 127,
127, 127, 127, 127, 127, 127, 127, 127, 127, 126, 126, 126, 125, 125, 125,
124, 124, 123, 123, 122, 121, 121, 120, 119, 119, 118, 117, 116, 115, 114,
113, 112, 111, 110, 109, 108, 107, 105, 104, 103, 101, 100, 99, 97, 96, 94,
93, 91, 90, 88, 87, 85, 83, 82, 80, 78, 76, 75, 73, 71, 69, 67, 65, 63, 62,
60, 58, 56, 54, 52, 50, 48, 46, 43, 41, 39, 37, 35, 33, 31, 29, 26, 24, 22,
20, 18, 15, 13, 11, 9, 7, 4, 2, 0, -2, -4, -7, -9, -11, -13, -15, -18, -20,
-22, -24, -26, -29, -31, -33, -35, -37, -39, -41, -43, -46, -48, -50, -52,
-54, -56, -58, -60, -62, -63, -65, -67, -69, -71, -73, -75, -76, -78, -80,
-82, -83, -85, -87, -88, -90, -91, -93, -94, -96, -97, -99,-100,-101,-103,
-104, -105, -107, -108, -109, -110, -111, -112, -113, -114, -115, -116,
-117, -118, -119, -119, -120, -121, -121, -122, -123, -123, -124, -124,
-125, -125, -125, -126, -126, -126, -127, -127, -127, -127, -127, -127,
-127, -127, -127, -127, -127, -126, -126, -126, -125, -125, -125, -124,
-124, -123, -123, -122, -121, -121, -120, -119, -119, -118, -117, -116,
-115, -114, -113, -112, -111, -110, -109, -108, -107, -105, -104, -103,
-101, -100, -99, -97, -96, -94, -93, -91, -90, -88, -87, -85, -83, -82,
-80, -78, -76, -75, -73, -71, -69, -67, -65, -63, -62, -60, -58, -56, -54,
-52, -50, -48, -46, -43, -41, -39, -37, -35, -33, -31, -29, -26, -24,
-22, -20, -18, -15, -13, -11, -9, -7, -4, -2
};
/**
* @brief Calculate coordinates from an angle and a length.
* @param length distance from coordinate origin (0,0)
* @param angle rotation in degrees
* @return signed X/Y coordinates for use with VERTEX2F
* @note - resolution for angle is 1° and rotation is clockwise
* @note - angle should be limited to a (n*360)-1
*/
void EVE_polar_cartesian(uint16_t length, uint16_t angle, int16_t *p_xc0, int16_t *p_yc0)
{
uint16_t anglev;
anglev = angle % 360U;
if (p_xc0 != NULL)
{
int32_t calc = (int16_t) length;
calc = ((calc * (sine_table[anglev])) + 64) / 128;
*p_xc0 = (int16_t) calc;
}
if (p_yc0 != NULL)
{
anglev = anglev + 270U;
anglev = anglev % 360U;
int32_t calc = (int16_t) length;
calc = ((calc * (sine_table[anglev])) + 64) / 128;
*p_yc0 = (int16_t) calc;
}
}
#endif /*LV_USE_DRAW_EVE*/

View File

@@ -0,0 +1,53 @@
/*
@file EVE_supplemental.h
@brief prototypes for supplemental functions
@version 5.0
@date 2023-12-23
@author Rudolph Riedel
@section LICENSE
MIT License
Copyright (c) 2016-2023 Rudolph Riedel
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.
@section History
5.0
- added EVE_polar_cartesian()
*/
#ifndef EVE_SUPPLEMENTAL_H
#define EVE_SUPPLEMENTAL_H
#include "EVE.h"
#include "EVE_commands.h"
#ifdef __cplusplus
extern "C"
{
#endif
void EVE_widget_circle(int16_t xc0, int16_t yc0, uint16_t radius, uint16_t border, uint32_t bgcolor);
void EVE_widget_rectangle(int16_t xc0, int16_t yc0, int16_t wid, int16_t hgt, int16_t border, uint16_t linewidth, uint32_t bgcolor);
void EVE_polar_cartesian(uint16_t length, uint16_t angle, int16_t *p_xc0, int16_t *p_yc0);
#endif /* EVE_SUPPLEMENTAL_H */

View File

@@ -0,0 +1,111 @@
#ifndef EVE_TARGET_H
#define EVE_TARGET_H
#include "../../draw/eve/lv_draw_eve_private.h"
#include "../../tick/lv_tick.h"
#include "../../misc/lv_utils.h"
#include "../../core/lv_global.h"
#define lv_eve_write_buf (LV_GLOBAL_DEFAULT()->draw_eve_unit->lv_eve_write_buf)
#define lv_eve_write_buf_len (LV_GLOBAL_DEFAULT()->draw_eve_unit->lv_eve_write_buf_len)
static inline void lv_eve_target_flush_write_buf(void);
static inline void DELAY_MS(uint16_t ms)
{
lv_delay_ms(ms);
}
static inline void EVE_cs_set(void)
{
lv_draw_eve_unit_g->op_cb(lv_draw_eve_unit_g->disp, LV_DRAW_EVE_OPERATION_CS_ASSERT, NULL, 0);
}
static inline void EVE_cs_clear(void)
{
lv_eve_target_flush_write_buf();
lv_draw_eve_unit_g->op_cb(lv_draw_eve_unit_g->disp, LV_DRAW_EVE_OPERATION_CS_DEASSERT, NULL, 0);
}
static inline void EVE_pdn_set(void)
{
lv_draw_eve_unit_g->op_cb(lv_draw_eve_unit_g->disp, LV_DRAW_EVE_OPERATION_POWERDOWN_SET, NULL, 0);
}
static inline void EVE_pdn_clear(void)
{
lv_draw_eve_unit_g->op_cb(lv_draw_eve_unit_g->disp, LV_DRAW_EVE_OPERATION_POWERDOWN_CLEAR, NULL, 0);
}
static inline void spi_transmit(uint8_t data)
{
#if LV_DRAW_EVE_WRITE_BUFFER_SIZE_INTERNAL
if(lv_eve_write_buf_len == sizeof(lv_eve_write_buf)) {
lv_eve_target_flush_write_buf();
}
lv_eve_write_buf[lv_eve_write_buf_len++] = data;
#else
lv_draw_eve_unit_g->op_cb(lv_draw_eve_unit_g->disp, LV_DRAW_EVE_OPERATION_SPI_SEND, &data, sizeof(data));
#endif
}
static inline void spi_transmit_32(uint32_t data)
{
#if LV_BIG_ENDIAN_SYSTEM
data = lv_swap_bytes_32(data);
#endif
#if LV_DRAW_EVE_WRITE_BUFFER_SIZE_INTERNAL
if(lv_eve_write_buf_len + 4 > sizeof(lv_eve_write_buf)) {
lv_eve_target_flush_write_buf();
}
uint8_t * buf4 = (uint8_t *) &data;
lv_eve_write_buf[lv_eve_write_buf_len++] = buf4[0];
lv_eve_write_buf[lv_eve_write_buf_len++] = buf4[1];
lv_eve_write_buf[lv_eve_write_buf_len++] = buf4[2];
lv_eve_write_buf[lv_eve_write_buf_len++] = buf4[3];
#else
lv_draw_eve_unit_g->op_cb(lv_draw_eve_unit_g->disp, LV_DRAW_EVE_OPERATION_SPI_SEND, &data, sizeof(data));
#endif
}
static inline void lv_eve_target_spi_transmit_buf(const void * data, uint32_t size)
{
lv_eve_target_flush_write_buf();
lv_draw_eve_unit_g->op_cb(lv_draw_eve_unit_g->disp, LV_DRAW_EVE_OPERATION_SPI_SEND, (void *) data, size);
}
static inline void lv_eve_target_flush_write_buf(void)
{
#if LV_DRAW_EVE_WRITE_BUFFER_SIZE_INTERNAL
if(lv_eve_write_buf_len == 0) {
return;
}
lv_draw_eve_unit_g->op_cb(lv_draw_eve_unit_g->disp, LV_DRAW_EVE_OPERATION_SPI_SEND, lv_eve_write_buf, lv_eve_write_buf_len);
lv_eve_write_buf_len = 0;
#endif
}
static inline void spi_transmit_burst(uint32_t data)
{
spi_transmit_32(data);
}
static inline uint8_t spi_receive(uint8_t data)
{
/* `data` is 0 everywhere `spi_receive` is called in the FT800-FT813 library */
LV_UNUSED(data);
lv_eve_target_flush_write_buf();
uint8_t byte;
lv_draw_eve_unit_g->op_cb(lv_draw_eve_unit_g->disp, LV_DRAW_EVE_OPERATION_SPI_RECEIVE, &byte, 1);
return byte;
}
static inline uint8_t fetch_flash_byte(const uint8_t *p_data)
{
return (*p_data);
}
#endif /* EVE_TARGET_H_ */

View File

@@ -0,0 +1,20 @@
MIT License
Copyright (c) 2016-2024 Rudolph Riedel
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.

View File

@@ -0,0 +1,245 @@
# EVE2 / EVE3 / EVE4 code library
This is a code library for EVE2/EVE3/EVE4 graphics controller ICs from FTDI/Bridgetek:
http://www.ftdichip.com/EVE.htm
http://brtchip.com/eve/
http://brtchip.com/ft81x/
https://brtchip.com/bt81x/
It contains code for and has been used with various micro-controllers and displays.
## Controllers
I have used it so far with:
- 8-Bit AVR, specifically the 90CAN series
- Arduino: Uno R3, mini-pro, ESP8266, ESP32, Metro-M4 (DMA), STM32 Nucleo_F446RE (DMA), XMC1100, Uno R4
- Renesas F1L RH850
- Infineon Aurix TC222
- GD32VF103
- ATSAMC21J18A (DMA)
- ATSAME51J19A (DMA)
- ESP32 (DMA)
- RP2040 Baremetal (DMA) + Arduino (DMA) - Raspberry Pi Pico
- S32K144 (DMA)
- GD32C103CBT6 (DMA)
- STM32G0B1CET6
I have reports of successfully using it with:
- ATSAMV70
- ATSAMD20
- ATSAME4
- MSP430
- MSP432
- some PICs
- ATxmega128A1
- TMS320F28335
## Displays
The TFTs tested so far:
- FT810CB-HY50HD http://www.hotmcu.com/5-graphical-lcd-touchscreen-800x480-spi-ft810-p-286.html
- FT811CB-HY50HD http://www.hotmcu.com/5-graphical-lcd-capacitive-touch-screen-800x480-spi-ft811-p-301.html
- RVT70UQFNWC0x https://riverdi.com/product/rvt70uqfnwc0x/
- RVT50
- ADAM101-LCP-SWVGA-NEW from Glyn, 10.1" 1024x600 cap-touch
- EVE2-38A https://www.matrixorbital.com/eve2-38a
- EVE2-35G https://www.matrixorbital.com/eve2-35g
- EVE2-43G https://www.matrixorbital.com/eve2-43g
- EVE2-50G https://www.matrixorbital.com/eve2-50g
- EVE2-70G https://www.matrixorbital.com/eve2-70g
- NHD-3.5-320240FT-CSXV-CTP
- RVT43ULBNWC00 (RiTFT-43-CAP-UX) https://riverdi.com/product/ritft43capux/
- RVT50AQBNWC00 (RiTFT-50-CAP) https://riverdi.com/product/ritft50cap/
- EVE3-50G https://www.matrixorbital.com/eve3-50g
- PAF90B5WFNWC01 http://www.panadisplay.com/ftdi-intelligent-display/9-inch-lcd-with-touch-with-bt815-board.html
- EVE3-43G https://www.matrixorbital.com/eve3-43g
- EVE3-35G https://www.matrixorbital.com/eve3-35g
- CFAF240400C0-030SC https://www.crystalfontz.com/product/cfaf240400c0030sca11-240x400-eve-touchscreen-tft-ft813
- CFAF320240F-035T https://www.crystalfontz.com/product/cfaf320240f035ttsa11-320x240-eve-tft-lcd-display-kit
- CFAF480128A0-039TC
- CFAF800480E0-050SC https://www.crystalfontz.com/product/cfaf800480e1050sca11-800x480-eve-accelerated-tft
- GEN4-FT813-50CTP-CLB https://4dsystems.com.au/gen4-ft813-50ctp-clb
- RVT101HVBNWC00-B https://riverdi.com/product/rvt101hvbnwc00-b/
- RVT70HSBNWC00-B https://riverdi.com/product/rvt70hsbnwc00-b/
- RVT50HQBNWC00-B https://riverdi.com/product/rvt50hqbnwc00-b/
- CFAF1024600B0-070SC-A1 https://www.crystalfontz.com/product/cfaf1024600b0070sca1-1024x600-7-inch-eve-tft
- Sunflower shield from Cowfish Studios
- GD3X Gameduino shield
## This is version 5
This is version 5 of this code library and there are a couple of changes from V4.
First of all, support for FT80x is gone. The main reason is that this allowed a nice speed improvement modification that only works with FT81x and beyond.
Then there is a hard break from FT80x to FT81x with ony 256k of memory in FT80x but 1MB in FT81x. The memory map is different and all the registers are located elsewhere.
FT810, FT811, FT812, FT813, BT815, BT816, BT817 and BT818 can use the exact same code as long none of the new features of BT81x are used - and there are plenty of modules with these available to choose from
As a side effect all commands are automatically started now.
Second is that there are two sets of display-list building command functions now: EVE_cmd_xxx() and EVE_cmd_xxx_burst().
The EVE_cmd_xxx_burst() functions are optimized for speed, these are pure data transfer functions and do not even check anymore if burst mode is active.
## Structure
This library currently has nine files that I hope are named to make clear what these do:
- EVE.h - this has all defines for FT81x / BT81x itself, so here are options, registers, commands and macros defined
- EVE_commands.c - this has all the API functions that are to be called from an application
- EVE_commands.h - this contains the prototypes for the functions in EVE_commands.c
- EVE_config.h - this has all the parameters for the numerous supported display modules, here is definded which set of parameters is to be used
- EVE_target.c - this has non-portable specific code for a number of supported controllers, mostly to support DMA
- EVE_target.h - this has non-portable pin defines and code as "static inline" functions for all supported controllers
- EVE_target.cpp - this is for Arduino C++ targets
- EVE_cpp_wrapper.cpp - this is for Arduino C++ targets
- EVE_cpp_wrapper.h - this is for Arduino C++ targets
Addtionally there are these two:
- EVE_supplemental.c
- EVE_suppplemental.h
This has the prototype and implementation for extra functions, so far:
- EVE_widget_circle() - widget function to draw a circle
- EVE_widget_rectangle() - widget function to draw a rectangle
- EVE_polar_cartesian() - calculate coordinates from an angle and a length
## Examples
Generate a basic display list and tell EVE to use it:
````
EVE_cmd_dl(CMD_DLSTART); // tells EVE to start a new display-list
EVE_cmd_dl(DL_CLEAR_COLOR_RGB | WHITE); // sets the background color
EVE_cmd_dl(DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG);
EVE_color_rgb(BLACK);
EVE_cmd_text(5, 15, 28, 0, "Hello there!");
EVE_cmd_dl(DL_DISPLAY); // put in the display list to mark its end
EVE_cmd_dl(CMD_SWAP); // tell EVE to use the new display list
while (EVE_busy());
````
Note, these commands are executed one by one, for each command chip-select is pulled low, a three byte address is send, the data for the command and its parameters is send and then chip-select is pulled high again which also makes EVE execute the command.
But there is a way to speed things up, we can get away with only sending the address once:
````
EVE_start_cmd_burst();
EVE_cmd_dl_burst(CMD_DLSTART);
EVE_cmd_dl_burst(DL_CLEAR_COLOR_RGB | WHITE);
EVE_cmd_dl_burst(DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG);
EVE_color_rgb_burst(BLACK);
EVE_cmd_text_burst(5, 15, 28, 0, "Hello there!");
EVE_cmd_dl_burst(DL_DISPLAY);
EVE_cmd_dl_burst(CMD_SWAP);
EVE_end_cmd_burst();
while (EVE_busy());
````
This does the same as the first example but faster.
The preceding EVE_start_cmd_burst() either sets chip-select to low and sends out the three byte address.
Or if DMA is available for the target you are compiling for with support code in EVE_target.c / EVE_target.cpp and EVE_target.h, it writes the address to EVE_dma_buffer and sets EVE_dma_buffer_index to 1.
Note the trailing "_burst" in the following functions, these are special versions of these commands that can only be used within an EVE_start_cmd_burst()/EVE_end_cmd_bust() pair.
These functions are optimized to push out data and nothing else.
The final EVE_end_cmd_burst() either pulls back the chip-select to high.
Or if we have DMA it calls EVE_start_dma_transfer() to start pushing out the buffer in the background.
As we have 7 commands for EVE in these simple examples, the second one has the address overhead removed from six commands and therefore needs to transfer 18 bytes less over SPI.
So even with a small 8-bit controller that does not support DMA this is a usefull optimization for building display lists.
Using DMA has one caveat: we need to limit the transfer to <4k as we are writing to the FIFO of EVEs command co-processor. This is usually not an issue though as we can shorten the display list generation with previously generated snippets that we attach to the current list with CMD_APPEND. And when we use widgets like CMD_BUTTON or CMD_CLOCK the generated display list grows by a larger amount than what we need to put into the command-FIFO so we likely reach the 8k limit of the display-list before we hit the 4k limit of the command-FIFO.
It is possible to use two or more DMA transfers to the FIFO to build a single display list, either to get around the 4k limit of the FIFO or in order to distribute the workload better of the time necessary between two display renewals.
You could for example do this, spread over three consecutive calls:
````
EVE_start_cmd_burst();
EVE_cmd_dl_burst(CMD_DLSTART);
EVE_cmd_dl_burst(DL_CLEAR_COLOR_RGB | WHITE);
EVE_end_cmd_burst();
````
````
EVE_start_cmd_burst();
EVE_cmd_dl_burst(DL_CLEAR | CLR_COL | CLR_STN | CLR_TAG);
EVE_color_rgb_burst(BLACK);
EVE_end_cmd_burst();
````
````
EVE_start_cmd_burst();
EVE_cmd_text_burst(5, 15, 28, 0, "Hello there!");
EVE_cmd_dl_burst(DL_DISPLAY);
EVE_cmd_dl_burst(CMD_SWAP);
EVE_end_cmd_burst();
````
But you need to check with EVE_busy() before each of these blocks.
Maybe similar like this never compiled pseudo-code:
thread_1ms_update_display()
{
static uint8_t state = 0;
static uint8_t count = 0;
count++;
if (E_OK == EVE_busy())
{
switch (state)
{
case 0:
update_first();
state = 1;
break;
case 1:
update_second();
state = 2;
break;
case 2:
if (counter > 19)
{
update_last_swap_list();
count = 0;
state = 0;
}
break;
}
}
}
## Remarks
The examples in the "example_projects" drawer are for use with AtmelStudio7.
For Arduino I am using PlatformIO with Visual Studio Code.
The platform the code is compiled for is automatically detected thru compiler flags in EVE_target.h.
The desired TFT is selected by adding a define for it to the build-environment, e.g. -DEVE_EVE3_50G
There is a list of available options at the start of EVE_config.h sorted by chipset.
The pins used for Chip-Select and Power-Down setup in the EVE_target/EVE_target_XXXXX.h file for your target with defines and these defines can be bypassed with defines in the build-environment.
Check the apropriate header file for your desired target.
When compiling for AVR you need to provide the clock it is running at in order to make the _delay_ms() calls used to initialize the TFT work with the intended timing.
For other plattforms you need to provide a DELAY_MS(ms) function that works at least between 1ms and 56ms and is not performing these delays shorter than requested.
The DELAY_MS(ms) is only used during initialization of the FT8xx/BT8xx.
In Addition you need to initialize the pins used for Chip-Select and Power-Down in your hardware correctly to output.
Plus setup the SPI accordingly, mode-0, 8-bit, MSB-first, not more than 11MHz for the initialization.
A couple of targets already have a function EVE_init_spi() in EVE_target.c.
A word of "warning", you have to take care yourself to not send more than 4kiB at once to the command co-processor
or to not generate display lists that are longer than 8kiB.
My library does not check and re-check the command-FIFO on every step.
Also there are no checks for the validity of function arguments.
This is optimized for speed, so the training wheels are off.
## Post questions here
Originally the project went public in the German mikrocontroller.net forum, the thread contains some insight: https://www.mikrocontroller.net/topic/395608
Feel free to add to the discussion with questions or remarks.
New: Github has a discussions feature as well: https://github.com/RudolphRiedel/FT800-FT813/discussions

View File

@@ -0,0 +1,22 @@
Copyright (c) 2013-2015, LKC Technologies, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. Redistributions in binary form
must reproduce the above copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other materials provided with
the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.

View File

@@ -0,0 +1,582 @@
// Copyright (c) 2013, LKC Technologies, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer. Redistributions in binary
// form must reproduce the above copyright notice, this list of conditions and
// the following disclaimer in the documentation and/or other materials
// provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
// HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "../../../lvgl.h"
#if LV_USE_BARCODE
#include "code128.h"
#include <string.h>
#define CODE128_MALLOC lv_malloc
#define CODE128_REALLOC lv_realloc
#define CODE128_FREE lv_free
#define CODE128_MEMSET lv_memset
#define CODE128_STRLEN lv_strlen
#define CODE128_ASSERT LV_ASSERT
#define CODE128_QUIET_ZONE_LEN 10
#define CODE128_CHAR_LEN 11
#define CODE128_STOP_CODE_LEN 13
#define CODE128_START_CODE_A 103
#define CODE128_START_CODE_B 104
#define CODE128_START_CODE_C 105
#define CODE128_MODE_A 'a'
#define CODE128_MODE_B 'b'
#define CODE128_MODE_C 'c'
#define CODE128_MIN_ENCODE_LEN (CODE128_QUIET_ZONE_LEN * 2 + CODE128_CHAR_LEN * 2 + CODE128_STOP_CODE_LEN)
static const int code128_pattern[] = {
// value: pattern, bar/space widths
1740, // 0: 11011001100, 212222
1644, // 1: 11001101100, 222122
1638, // 2: 11001100110, 222221
1176, // 3: 10010011000, 121223
1164, // 4: 10010001100, 121322
1100, // 5: 10001001100, 131222
1224, // 6: 10011001000, 122213
1220, // 7: 10011000100, 122312
1124, // 8: 10001100100, 132212
1608, // 9: 11001001000, 221213
1604, // 10: 11001000100, 221312
1572, // 11: 11000100100, 231212
1436, // 12: 10110011100, 112232
1244, // 13: 10011011100, 122132
1230, // 14: 10011001110, 122231
1484, // 15: 10111001100, 113222
1260, // 16: 10011101100, 123122
1254, // 17: 10011100110, 123221
1650, // 18: 11001110010, 223211
1628, // 19: 11001011100, 221132
1614, // 20: 11001001110, 221231
1764, // 21: 11011100100, 213212
1652, // 22: 11001110100, 223112
1902, // 23: 11101101110, 312131
1868, // 24: 11101001100, 311222
1836, // 25: 11100101100, 321122
1830, // 26: 11100100110, 321221
1892, // 27: 11101100100, 312212
1844, // 28: 11100110100, 322112
1842, // 29: 11100110010, 322211
1752, // 30: 11011011000, 212123
1734, // 31: 11011000110, 212321
1590, // 32: 11000110110, 232121
1304, // 33: 10100011000, 111323
1112, // 34: 10001011000, 131123
1094, // 35: 10001000110, 131321
1416, // 36: 10110001000, 112313
1128, // 37: 10001101000, 132113
1122, // 38: 10001100010, 132311
1672, // 39: 11010001000, 211313
1576, // 40: 11000101000, 231113
1570, // 41: 11000100010, 231311
1464, // 42: 10110111000, 112133
1422, // 43: 10110001110
1134, // 44: 10001101110
1496, // 45: 10111011000, 113123
1478, // 46: 10111000110, 113321
1142, // 47: 10001110110, 133121
1910, // 48: 11101110110, 313121
1678, // 49: 11010001110, 211331
1582, // 50: 11000101110, 231131
1768, // 51: 11011101000, 213113
1762, // 52: 11011100010, 213311
1774, // 53: 11011101110, 213131
1880, // 54: 11101011000, 311123
1862, // 55: 11101000110, 311321
1814, // 56: 11100010110, 331121
1896, // 57: 11101101000, 312113
1890, // 58: 11101100010, 312311
1818, // 59: 11100011010, 332111
1914, // 60: 11101111010, 314111
1602, // 61: 11001000010, 221411
1930, // 62: 11110001010, 431111
1328, // 63: 10100110000, 111224
1292, // 64: 10100001100, 111422
1200, // 65: 10010110000, 121124
1158, // 66: 10010000110, 121421
1068, // 67: 10000101100, 141122
1062, // 68: 10000100110, 141221
1424, // 69: 10110010000, 112214
1412, // 70: 10110000100, 112412
1232, // 71: 10011010000, 122114
1218, // 72: 10011000010, 122411
1076, // 73: 10000110100, 142112
1074, // 74: 10000110010, 142211
1554, // 75: 11000010010, 241211
1616, // 76: 11001010000, 221114
1978, // 77: 11110111010, 413111
1556, // 78: 11000010100, 241112
1146, // 79: 10001111010, 134111
1340, // 80: 10100111100, 111242
1212, // 81: 10010111100, 121142
1182, // 82: 10010011110, 121241
1508, // 83: 10111100100, 114212
1268, // 84: 10011110100, 124112
1266, // 85: 10011110010, 124211
1956, // 86: 11110100100, 411212
1940, // 87: 11110010100, 421112
1938, // 88: 11110010010, 421211
1758, // 89: 11011011110, 212141
1782, // 90: 11011110110, 214121
1974, // 91: 11110110110, 412121
1400, // 92: 10101111000, 111143
1310, // 93: 10100011110, 111341
1118, // 94: 10001011110, 131141
1512, // 95: 10111101000, 114113
1506, // 96: 10111100010, 114311
1960, // 97: 11110101000, 411113
1954, // 98: 11110100010, 411311
1502, // 99: 10111011110, 113141
1518, // 100: 10111101110, 114131
1886, // 101: 11101011110, 311141
1966, // 102: 11110101110, 411131
1668, // 103: 11010000100, 211412
1680, // 104: 11010010000, 211214
1692 // 105: 11010011100, 211232
};
static const int code128_stop_pattern = 6379; // 1100011101011, 2331112
struct code128_step {
int prev_ix; // Index of previous step, if any
const char * next_input; // Remaining input
unsigned short len; // The length of the pattern so far (includes this step)
char mode; // State for the current encoding
signed char code; // What code should be written for this step
};
struct code128_state {
struct code128_step * steps;
int allocated_steps;
int current_ix;
int todo_ix;
int best_ix;
size_t maxlength;
};
size_t code128_estimate_len(const char * s)
{
return CODE128_QUIET_ZONE_LEN
+ CODE128_CHAR_LEN // start code
+ CODE128_CHAR_LEN * (CODE128_STRLEN(s) * 11 / 10) // contents + 10% padding
+ CODE128_CHAR_LEN // checksum
+ CODE128_STOP_CODE_LEN
+ CODE128_QUIET_ZONE_LEN;
}
static void code128_append_pattern(int pattern, int pattern_length, char * out)
{
// All patterns have their first bit set by design
CODE128_ASSERT(pattern & (1 << (pattern_length - 1)));
int i;
for(i = pattern_length - 1; i >= 0; i--) {
// cast avoids warning: implicit conversion from 'int' to 'char' changes value from 255 to -1 [-Wconstant-conversion]
*out++ = (unsigned char)((pattern & (1 << i)) ? 255 : 0);
}
}
static int code128_append_code(int code, char * out)
{
CODE128_ASSERT(code >= 0 && code < (int)(sizeof(code128_pattern) / sizeof(code128_pattern[0])));
code128_append_pattern(code128_pattern[code], CODE128_CHAR_LEN, out);
return CODE128_CHAR_LEN;
}
static int code128_append_stop_code(char * out)
{
code128_append_pattern(code128_stop_pattern, CODE128_STOP_CODE_LEN, out);
return CODE128_STOP_CODE_LEN;
}
static signed char code128_switch_code(char from_mode, char to_mode)
{
switch(from_mode) {
case CODE128_MODE_A:
switch(to_mode) {
case CODE128_MODE_B:
return 100;
case CODE128_MODE_C:
return 99;
}
break;
case CODE128_MODE_B:
switch(to_mode) {
case CODE128_MODE_A:
return 101;
case CODE128_MODE_C:
return 99;
}
break;
case CODE128_MODE_C:
switch(to_mode) {
case CODE128_MODE_B:
return 100;
case CODE128_MODE_A:
return 101;
}
break;
default:
break;
}
CODE128_ASSERT(0); // Invalid mode switch
return -1;
}
static signed char code128a_ascii_to_code(signed char value)
{
if(value >= ' ' && value <= '_')
return (signed char)(value - ' ');
else if(value >= 0 && value < ' ')
return (signed char)(value + 64);
else if(value == (signed char)CODE128_FNC1)
return 102;
else if(value == (signed char)CODE128_FNC2)
return 97;
else if(value == (signed char)CODE128_FNC3)
return 96;
else if(value == (signed char)CODE128_FNC4)
return 101;
else
return -1;
}
static signed char code128b_ascii_to_code(signed char value)
{
if(value >= ' ') // value <= 127 is implied
return (signed char)(value - ' ');
else if(value == (signed char)CODE128_FNC1)
return 102;
else if(value == (signed char)CODE128_FNC2)
return 97;
else if(value == (signed char)CODE128_FNC3)
return 96;
else if(value == (signed char)CODE128_FNC4)
return 100;
else
return -1;
}
static signed char code128c_ascii_to_code(const char * values)
{
if(values[0] == CODE128_FNC1)
return 102;
if(values[0] >= '0' && values[0] <= '9' &&
values[1] >= '0' && values[1] <= '9') {
char code = 10 * (values[0] - '0') + (values[1] - '0');
return code;
}
return -1;
}
static int code128_do_a_step(struct code128_step * base, int prev_ix, int ix)
{
struct code128_step * previous_step = &base[prev_ix];
struct code128_step * step = &base[ix];
char value = *previous_step->next_input;
// NOTE: Currently we can't encode NULL
if(value == 0)
return 0;
step->code = code128a_ascii_to_code(value);
if(step->code < 0)
return 0;
step->prev_ix = prev_ix;
step->next_input = previous_step->next_input + 1;
step->mode = CODE128_MODE_A;
step->len = previous_step->len + CODE128_CHAR_LEN;
if(step->mode != previous_step->mode)
step->len += CODE128_CHAR_LEN; // Need to switch modes
return 1;
}
static int code128_do_b_step(struct code128_step * base, int prev_ix, int ix)
{
struct code128_step * previous_step = &base[prev_ix];
struct code128_step * step = &base[ix];
char value = *previous_step->next_input;
// NOTE: Currently we can't encode NULL
if(value == 0)
return 0;
step->code = code128b_ascii_to_code(value);
if(step->code < 0)
return 0;
step->prev_ix = prev_ix;
step->next_input = previous_step->next_input + 1;
step->mode = CODE128_MODE_B;
step->len = previous_step->len + CODE128_CHAR_LEN;
if(step->mode != previous_step->mode)
step->len += CODE128_CHAR_LEN; // Need to switch modes
return 1;
}
static int code128_do_c_step(struct code128_step * base, int prev_ix, int ix)
{
struct code128_step * previous_step = &base[prev_ix];
struct code128_step * step = &base[ix];
char value = *previous_step->next_input;
// NOTE: Currently we can't encode NULL
if(value == 0)
return 0;
step->code = code128c_ascii_to_code(previous_step->next_input);
if(step->code < 0)
return 0;
step->prev_ix = prev_ix;
step->next_input = previous_step->next_input + 1;
// Mode C consumes 2 characters for codes 0-99
if(step->code < 100)
step->next_input++;
step->mode = CODE128_MODE_C;
step->len = previous_step->len + CODE128_CHAR_LEN;
if(step->mode != previous_step->mode)
step->len += CODE128_CHAR_LEN; // Need to switch modes
return 1;
}
static struct code128_step * code128_alloc_step(struct code128_state * state)
{
if(state->todo_ix >= state->allocated_steps) {
state->allocated_steps += 1024;
state->steps = (struct code128_step *) CODE128_REALLOC(state->steps,
state->allocated_steps * sizeof(struct code128_step));
}
struct code128_step * step = &state->steps[state->todo_ix];
CODE128_MEMSET(step, 0, sizeof(*step));
return step;
}
static void code128_do_step(struct code128_state * state)
{
struct code128_step * step = &state->steps[state->current_ix];
if(*step->next_input == 0) {
// Done, so see if we have a new shortest encoding.
if((step->len < state->maxlength) ||
(state->best_ix < 0 && step->len == state->maxlength)) {
state->best_ix = state->current_ix;
// Update maxlength to avoid considering anything longer
state->maxlength = step->len;
}
return;
}
// Don't try if we're already at or beyond the max acceptable
// length;
if(step->len >= state->maxlength)
return;
char mode = step->mode;
code128_alloc_step(state);
int mode_c_worked = 0;
// Always try mode C
if(code128_do_c_step(state->steps, state->current_ix, state->todo_ix)) {
state->todo_ix++;
code128_alloc_step(state);
mode_c_worked = 1;
}
if(mode == CODE128_MODE_A) {
// If A works, stick with A. There's no advantage to switching
// to B proactively if A still works.
if(code128_do_a_step(state->steps, state->current_ix, state->todo_ix) ||
code128_do_b_step(state->steps, state->current_ix, state->todo_ix))
state->todo_ix++;
}
else if(mode == CODE128_MODE_B) {
// The same logic applies here. There's no advantage to switching
// proactively to A if B still works.
if(code128_do_b_step(state->steps, state->current_ix, state->todo_ix) ||
code128_do_a_step(state->steps, state->current_ix, state->todo_ix))
state->todo_ix++;
}
else if(!mode_c_worked) {
// In mode C. If mode C worked and we're in mode C, trying anything
// else is pointless since the mode C encoding will be shorter and
// there won't be any mode switches.
// If we're leaving mode C, though, try both in case one ends up
// better than the other.
if(code128_do_a_step(state->steps, state->current_ix, state->todo_ix)) {
state->todo_ix++;
code128_alloc_step(state);
}
if(code128_do_b_step(state->steps, state->current_ix, state->todo_ix))
state->todo_ix++;
}
}
size_t code128_encode_raw(const char * s, char * out, size_t maxlength)
{
struct code128_state state;
const size_t overhead = CODE128_QUIET_ZONE_LEN
+ CODE128_CHAR_LEN // checksum
+ CODE128_STOP_CODE_LEN
+ CODE128_QUIET_ZONE_LEN;
if(maxlength < overhead + CODE128_CHAR_LEN + CODE128_CHAR_LEN) {
// Need space to encode the start character and one additional
// character.
return 0;
}
state.allocated_steps = 256;
state.steps = (struct code128_step *) CODE128_MALLOC(state.allocated_steps * sizeof(struct code128_step));
state.current_ix = 0;
state.todo_ix = 0;
state.maxlength = maxlength - overhead;
state.best_ix = -1;
// Initialize the first 3 steps for the 3 encoding routes (A, B, C)
state.steps[0].prev_ix = -1;
state.steps[0].next_input = s;
state.steps[0].len = CODE128_CHAR_LEN;
state.steps[0].mode = CODE128_MODE_C;
state.steps[0].code = CODE128_START_CODE_C;
state.steps[1].prev_ix = -1;
state.steps[1].next_input = s;
state.steps[1].len = CODE128_CHAR_LEN;
state.steps[1].mode = CODE128_MODE_A;
state.steps[1].code = CODE128_START_CODE_A;
state.steps[2].prev_ix = -1;
state.steps[2].next_input = s;
state.steps[2].len = CODE128_CHAR_LEN;
state.steps[2].mode = CODE128_MODE_B;
state.steps[2].code = CODE128_START_CODE_B;
state.todo_ix = 3;
// Keep going until no more work
do {
code128_do_step(&state);
state.current_ix++;
} while(state.current_ix != state.todo_ix);
// If no best_step, then fail.
if(state.best_ix < 0) {
CODE128_FREE(state.steps);
return 0;
}
// Determine the list of codes
size_t num_codes = state.maxlength / CODE128_CHAR_LEN;
char * codes = CODE128_MALLOC(num_codes);
CODE128_ASSERT(codes);
struct code128_step * step = &state.steps[state.best_ix];
size_t i;
for(i = num_codes - 1; i > 0; --i) {
struct code128_step * prev_step = &state.steps[step->prev_ix];
codes[i] = step->code;
if(step->mode != prev_step->mode) {
--i;
codes[i] = code128_switch_code(prev_step->mode, step->mode);
}
step = prev_step;
}
codes[0] = step->code;
// Encode everything up to the checksum
size_t actual_length = state.maxlength + overhead;
CODE128_MEMSET(out, 0, CODE128_QUIET_ZONE_LEN);
out += CODE128_QUIET_ZONE_LEN;
for(i = 0; i < num_codes; i++)
out += code128_append_code(codes[i], out);
// Compute the checksum
int sum = codes[0];
for(i = 1; i < num_codes; i++)
sum += (int)(codes[i] * i);
out += code128_append_code(sum % 103, out);
// Finalize the code.
out += code128_append_stop_code(out);
CODE128_MEMSET(out, 0, CODE128_QUIET_ZONE_LEN);
CODE128_FREE(codes);
CODE128_FREE(state.steps);
return actual_length;
}
/**
* @brief Encode the GS1 string
*
* This converts [FNC1] sequences to raw FNC1 characters and
* removes spaces before encoding the barcodes.
*
* @return the length of barcode data in bytes
*/
size_t code128_encode_gs1(const char * s, char * out, size_t maxlength)
{
size_t raw_size = CODE128_STRLEN(s) + 1;
char * raw = CODE128_MALLOC(raw_size);
CODE128_ASSERT(raw);
if(!raw) {
return 0;
}
char * p = raw;
for(; *s != '\0'; s++) {
if(strncmp(s, "[FNC1]", 6) == 0) {
*p++ = CODE128_FNC1;
s += 5;
}
else if(*s != ' ') {
*p++ = *s;
}
}
*p = '\0';
size_t length = code128_encode_raw(raw, out, maxlength);
CODE128_FREE(raw);
return length;
}
#endif /*LV_USE_BARCODE*/

View File

@@ -0,0 +1,47 @@
// Copyright (c) 2013-15, LKC Technologies, Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer. Redistributions in binary
// form must reproduce the above copyright notice, this list of conditions and
// the following disclaimer in the documentation and/or other materials
// provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT
// HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef CODE128_H
#define CODE128_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
// Since the FNCn characters are not ASCII, define versions here to
// simplify encoding strings that include them.
#define CODE128_FNC1 '\xf1'
#define CODE128_FNC2 '\xf2'
#define CODE128_FNC3 '\xf3'
#define CODE128_FNC4 '\xf4'
size_t code128_estimate_len(const char * s);
size_t code128_encode_gs1(const char * s, char * out, size_t maxlength);
size_t code128_encode_raw(const char * s, char * out, size_t maxlength);
#ifdef __cplusplus
}
#endif
#endif // CODE128_H

View File

@@ -0,0 +1,327 @@
/**
* @file lv_barcode.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../core/lv_obj_class_private.h"
#include "lv_barcode_private.h"
#include "../../lvgl.h"
#if LV_USE_BARCODE
#include "code128.h"
#include "../../misc/cache/lv_cache.h"
/*********************
* DEFINES
*********************/
#define MY_CLASS (&lv_barcode_class)
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void lv_barcode_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_barcode_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static bool lv_barcode_change_buf_size(lv_obj_t * obj, int32_t w, int32_t h);
static void lv_barcode_clear(lv_obj_t * obj);
/**********************
* STATIC VARIABLES
**********************/
const lv_obj_class_t lv_barcode_class = {
.constructor_cb = lv_barcode_constructor,
.destructor_cb = lv_barcode_destructor,
.width_def = LV_SIZE_CONTENT,
.instance_size = sizeof(lv_barcode_t),
.base_class = &lv_canvas_class,
.name = "lv_barcode",
};
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_obj_t * lv_barcode_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_barcode_set_dark_color(lv_obj_t * obj, lv_color_t color)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_barcode_t * barcode = (lv_barcode_t *)obj;
barcode->dark_color = color;
}
void lv_barcode_set_light_color(lv_obj_t * obj, lv_color_t color)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_barcode_t * barcode = (lv_barcode_t *)obj;
barcode->light_color = color;
}
void lv_barcode_set_scale(lv_obj_t * obj, uint16_t scale)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
if(scale == 0) {
scale = 1;
}
lv_barcode_t * barcode = (lv_barcode_t *)obj;
barcode->scale = scale;
}
void lv_barcode_set_direction(lv_obj_t * obj, lv_dir_t direction)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_barcode_t * barcode = (lv_barcode_t *)obj;
barcode->direction = direction;
}
void lv_barcode_set_tiled(lv_obj_t * obj, bool tiled)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_barcode_t * barcode = (lv_barcode_t *)obj;
barcode->tiled = tiled;
lv_image_set_inner_align(obj, tiled ? LV_IMAGE_ALIGN_TILE : LV_IMAGE_ALIGN_DEFAULT);
}
void lv_barcode_set_encoding(lv_obj_t * obj, lv_barcode_encoding_t encoding)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_barcode_t * barcode = (lv_barcode_t *)obj;
barcode->encoding = encoding;
}
lv_result_t lv_barcode_update(lv_obj_t * obj, const char * data)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
LV_ASSERT_NULL(data);
if(data == NULL || lv_strlen(data) == 0) {
LV_LOG_WARN("data is empty");
lv_barcode_clear(obj);
return LV_RESULT_INVALID;
}
size_t len = code128_estimate_len(data);
LV_LOG_INFO("data: %s, len = %zu", data, len);
char * out_buf = lv_malloc(len);
LV_ASSERT_MALLOC(out_buf);
if(!out_buf) {
LV_LOG_ERROR("malloc failed for out_buf");
lv_barcode_clear(obj);
return LV_RESULT_INVALID;
}
lv_barcode_t * barcode = (lv_barcode_t *)obj;
int32_t barcode_w = 0;
switch(barcode->encoding) {
case LV_BARCODE_ENCODING_CODE128_GS1:
barcode_w = (int32_t) code128_encode_gs1(data, out_buf, len);
break;
case LV_BARCODE_ENCODING_CODE128_RAW:
barcode_w = (int32_t) code128_encode_raw(data, out_buf, len);
break;
}
LV_LOG_INFO("barcode width = %" LV_PRId32, barcode_w);
LV_ASSERT(barcode->scale > 0);
uint16_t scale = barcode->scale;
int32_t buf_w;
int32_t buf_h;
if(barcode->tiled) {
buf_w = (barcode->direction == LV_DIR_HOR) ? barcode_w * scale : 1;
buf_h = (barcode->direction == LV_DIR_VER) ? barcode_w * scale : 1;
}
else {
lv_obj_update_layout(obj);
buf_w = (barcode->direction == LV_DIR_HOR) ? barcode_w * scale : lv_obj_get_width(obj);
buf_h = (barcode->direction == LV_DIR_VER) ? barcode_w * scale : lv_obj_get_height(obj);
}
if(!lv_barcode_change_buf_size(obj, buf_w, buf_h)) {
lv_barcode_clear(obj);
lv_free(out_buf);
return LV_RESULT_INVALID;
}
/* Temporarily disable invalidation to improve the efficiency of lv_canvas_set_px */
lv_display_enable_invalidation(lv_obj_get_display(obj), false);
lv_draw_buf_t * draw_buf = lv_canvas_get_draw_buf(obj);
uint32_t stride = draw_buf->header.stride;
const lv_color_t color = lv_color_hex(1);
/* Clear the canvas */
lv_draw_buf_clear(draw_buf, NULL);
/* Set the palette */
lv_canvas_set_palette(obj, 0, lv_color_to_32(barcode->light_color, LV_OPA_COVER));
lv_canvas_set_palette(obj, 1, lv_color_to_32(barcode->dark_color, LV_OPA_COVER));
for(int32_t x = 0; x < barcode_w; x++) {
/*skip empty data*/
if(out_buf[x] == 0) {
continue;
}
for(uint16_t i = 0; i < scale; i++) {
int32_t offset = x * scale + i;
if(barcode->direction == LV_DIR_HOR) {
lv_canvas_set_px(obj, offset, 0, color, LV_OPA_COVER);
}
else { /*LV_DIR_VER*/
if(barcode->tiled) {
lv_canvas_set_px(obj, 0, offset, color, LV_OPA_COVER);
}
else {
uint8_t * dest = lv_draw_buf_goto_xy(draw_buf, 0, offset);
lv_memset(dest, 0xFF, stride);
}
}
}
}
/* Copy pixels by row */
if(!barcode->tiled && barcode->direction == LV_DIR_HOR && buf_h > 1) {
/* Skip the first row */
int32_t h = buf_h - 1;
const uint8_t * src = lv_draw_buf_goto_xy(draw_buf, 0, 0);
uint8_t * dest = lv_draw_buf_goto_xy(draw_buf, 0, 1);
while(h--) {
lv_memcpy(dest, src, stride);
dest += stride;
}
}
/* invalidate the canvas to refresh it */
lv_display_enable_invalidation(lv_obj_get_display(obj), true);
lv_obj_invalidate(obj);
lv_free(out_buf);
return LV_RESULT_OK;
}
lv_color_t lv_barcode_get_dark_color(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_barcode_t * barcode = (lv_barcode_t *)obj;
return barcode->dark_color;
}
lv_color_t lv_barcode_get_light_color(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_barcode_t * barcode = (lv_barcode_t *)obj;
return barcode->light_color;
}
uint16_t lv_barcode_get_scale(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_barcode_t * barcode = (lv_barcode_t *)obj;
return barcode->scale;
}
lv_barcode_encoding_t lv_barcode_get_encoding(const lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
const lv_barcode_t * barcode = (const lv_barcode_t *)obj;
return barcode->encoding;
}
/**********************
* STATIC FUNCTIONS
**********************/
static void lv_barcode_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
lv_barcode_t * barcode = (lv_barcode_t *)obj;
barcode->dark_color = lv_color_black();
barcode->light_color = lv_color_white();
barcode->scale = 1;
barcode->direction = LV_DIR_HOR;
barcode->encoding = LV_BARCODE_ENCODING_CODE128_GS1;
lv_image_set_inner_align(obj, LV_IMAGE_ALIGN_DEFAULT);
}
static void lv_barcode_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
lv_draw_buf_t * draw_buf = lv_canvas_get_draw_buf(obj);
if(draw_buf == NULL) return;
lv_image_cache_drop(draw_buf);
/*@fixme destroy buffer in cache free_cb.*/
lv_draw_buf_destroy(draw_buf);
}
static bool lv_barcode_change_buf_size(lv_obj_t * obj, int32_t w, int32_t h)
{
LV_ASSERT_NULL(obj);
if(w <= 0 || h <= 0) {
LV_LOG_WARN("invalid size: %" LV_PRId32 " x %" LV_PRId32, w, h);
return false;
}
lv_draw_buf_t * old_buf = lv_canvas_get_draw_buf(obj);
lv_draw_buf_t * new_buf = lv_draw_buf_create(w, h, LV_COLOR_FORMAT_I1, LV_STRIDE_AUTO);
if(new_buf == NULL) {
LV_LOG_ERROR("malloc failed for canvas buffer");
return false;
}
lv_canvas_set_draw_buf(obj, new_buf);
LV_LOG_INFO("set canvas buffer: %p, width = %" LV_PRId32, (void *)new_buf, w);
if(old_buf != NULL) lv_draw_buf_destroy(old_buf);
return true;
}
static void lv_barcode_clear(lv_obj_t * obj)
{
lv_draw_buf_t * draw_buf = lv_canvas_get_draw_buf(obj);
if(!draw_buf) {
return;
}
lv_draw_buf_clear(draw_buf, NULL);
lv_image_cache_drop(draw_buf);
lv_obj_invalidate(obj);
}
#endif /*LV_USE_BARCODE*/

View File

@@ -0,0 +1,143 @@
/**
* @file lv_barcode.h
*
*/
#ifndef LV_BARCODE_H
#define LV_BARCODE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
#include "../../misc/lv_types.h"
#include "../../misc/lv_color.h"
#include "../../widgets/canvas/lv_canvas.h"
#if LV_USE_BARCODE
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
typedef enum {
/**
* Code 128 with GS1 encoding. Strips `[FCN1]` and spaces.
*/
LV_BARCODE_ENCODING_CODE128_GS1,
/**
* Code 128 with raw encoding.
*/
LV_BARCODE_ENCODING_CODE128_RAW,
} lv_barcode_encoding_t;
LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_barcode_class;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Create an empty barcode (an `lv_canvas`) object.
* @param parent point to an object where to create the barcode
* @return pointer to the created barcode object
*/
lv_obj_t * lv_barcode_create(lv_obj_t * parent);
/**
* Set the dark color of a barcode object
* @param obj pointer to barcode object
* @param color dark color of the barcode
*/
void lv_barcode_set_dark_color(lv_obj_t * obj, lv_color_t color);
/**
* Set the light color of a barcode object
* @param obj pointer to barcode object
* @param color light color of the barcode
*/
void lv_barcode_set_light_color(lv_obj_t * obj, lv_color_t color);
/**
* Set the scale of a barcode object
* @param obj pointer to barcode object
* @param scale scale factor
*/
void lv_barcode_set_scale(lv_obj_t * obj, uint16_t scale);
/**
* Set the direction of a barcode object
* @param obj pointer to barcode object
* @param direction draw direction (`LV_DIR_HOR` or `LB_DIR_VER`)
*/
void lv_barcode_set_direction(lv_obj_t * obj, lv_dir_t direction);
/**
* Set the tiled mode of a barcode object
* @param obj pointer to barcode object
* @param tiled true: tiled mode, false: normal mode (default)
*/
void lv_barcode_set_tiled(lv_obj_t * obj, bool tiled);
/**
* Set the encoding of a barcode object
* @param obj pointer to barcode object
* @param encoding encoding (default is `LV_BARCODE_CODE128_GS1`)
*/
void lv_barcode_set_encoding(lv_obj_t * obj, lv_barcode_encoding_t encoding);
/**
* Set the data of a barcode object
* @param obj pointer to barcode object
* @param data data to display
* @return LV_RESULT_OK: if no error; LV_RESULT_INVALID: on error
*/
lv_result_t lv_barcode_update(lv_obj_t * obj, const char * data);
/**
* Get the dark color of a barcode object
* @param obj pointer to barcode object
* @return dark color of the barcode
*/
lv_color_t lv_barcode_get_dark_color(lv_obj_t * obj);
/**
* Get the light color of a barcode object
* @param obj pointer to barcode object
* @return light color of the barcode
*/
lv_color_t lv_barcode_get_light_color(lv_obj_t * obj);
/**
* Get the scale of a barcode object
* @param obj pointer to barcode object
* @return scale factor
*/
uint16_t lv_barcode_get_scale(lv_obj_t * obj);
/**
* Get the encoding of a barcode object
* @param obj pointer to barcode object
* @return encoding
*/
lv_barcode_encoding_t lv_barcode_get_encoding(const lv_obj_t * obj);
/**********************
* MACROS
**********************/
#endif /*LV_USE_BARCODE*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /*LV_BARCODE_H*/

View File

@@ -0,0 +1,56 @@
/**
* @file lv_barcode_private.h
*
*/
#ifndef LV_BARCODE_PRIVATE_H
#define LV_BARCODE_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../widgets/canvas/lv_canvas_private.h"
#include "lv_barcode.h"
#if LV_USE_BARCODE
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/*Data of barcode*/
struct _lv_barcode_t {
lv_canvas_t canvas;
lv_color_t dark_color;
lv_color_t light_color;
uint16_t scale;
lv_dir_t direction;
bool tiled;
lv_barcode_encoding_t encoding;
};
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
#endif /* LV_USE_BARCODE */
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_BARCODE_PRIVATE_H*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
/**
* @file lv_bin_decoder.h
*
*/
#ifndef LV_BIN_DECODER_H
#define LV_BIN_DECODER_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../draw/lv_image_decoder.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Initialize the binary image decoder module
*/
void lv_bin_decoder_init(void);
/**
* Get info about a lvgl binary image
* @param decoder the decoder where this function belongs
* @param dsc image descriptor containing the source and type of the image and other info.
* @param header store the image data here
* @return LV_RESULT_OK: the info is successfully stored in `header`; LV_RESULT_INVALID: unknown format or other error.
*/
lv_result_t lv_bin_decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc, lv_image_header_t * header);
lv_result_t lv_bin_decoder_get_area(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc,
const lv_area_t * full_area, lv_area_t * decoded_area);
/**
* Open a lvgl binary image
* @param decoder the decoder where this function belongs
* @param dsc pointer to decoder descriptor. `src`, `style` are already initialized in it.
* @return LV_RESULT_OK: the info is successfully stored in `header`; LV_RESULT_INVALID: unknown format or other error.
*/
lv_result_t lv_bin_decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
/**
* Close the pending decoding. Free resources etc.
* @param decoder pointer to the decoder the function associated with
* @param dsc pointer to decoder descriptor
*/
void lv_bin_decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_BIN_DECODER_H*/

View File

@@ -0,0 +1,253 @@
/**
* @file lv_bmp.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../draw/lv_image_decoder_private.h"
#include "../../../lvgl.h"
#if LV_USE_BMP
#include <string.h>
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#define DECODER_NAME "BMP"
#define image_cache_draw_buf_handlers &(LV_GLOBAL_DEFAULT()->image_cache_draw_buf_handlers)
/**********************
* 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_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * src, lv_image_header_t * header);
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
static lv_result_t decoder_get_area(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc,
const lv_area_t * full_area, lv_area_t * decoded_area);
static void decoder_close(lv_image_decoder_t * dec, lv_image_decoder_dsc_t * dsc);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_bmp_init(void)
{
lv_image_decoder_t * dec = lv_image_decoder_create();
lv_image_decoder_set_info_cb(dec, decoder_info);
lv_image_decoder_set_open_cb(dec, decoder_open);
lv_image_decoder_set_get_area_cb(dec, decoder_get_area);
lv_image_decoder_set_close_cb(dec, decoder_close);
dec->name = DECODER_NAME;
}
void lv_bmp_deinit(void)
{
lv_image_decoder_t * dec = NULL;
while((dec = lv_image_decoder_get_next(dec)) != NULL) {
if(dec->info_cb == decoder_info) {
lv_image_decoder_delete(dec);
break;
}
}
}
/**********************
* STATIC FUNCTIONS
**********************/
/**
* Get info about a BMP image
* @param dsc image descriptor containing the source and type of the image and other info.
* @param header store the info here
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't get the info
*/
static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc, lv_image_header_t * header)
{
LV_UNUSED(decoder);
const void * src = dsc->src;
lv_image_src_t src_type = dsc->src_type; /*Get the source type*/
/*If it's a BMP file...*/
if(src_type == LV_IMAGE_SRC_FILE) {
const char * fn = src;
if(lv_strcmp(lv_fs_get_ext(fn), "bmp") == 0) { /*Check the extension*/
/*Save the data in the header*/
uint8_t headers[54];
lv_fs_read(&dsc->file, headers, 54, NULL);
uint32_t w;
uint32_t h;
lv_memcpy(&w, headers + 18, 4);
lv_memcpy(&h, headers + 22, 4);
header->w = w;
header->h = h;
uint16_t bpp;
lv_memcpy(&bpp, headers + 28, 2);
switch(bpp) {
case 16:
header->cf = LV_COLOR_FORMAT_RGB565;
break;
case 24:
header->cf = LV_COLOR_FORMAT_RGB888;
break;
case 32:
header->cf = LV_COLOR_FORMAT_ARGB8888;
break;
default:
LV_LOG_WARN("Not supported bpp: %d", bpp);
return LV_RESULT_OK;
}
return LV_RESULT_OK;
}
}
/* BMP file as data not supported for simplicity.
* Convert them to LVGL compatible C arrays directly. */
else if(src_type == LV_IMAGE_SRC_VARIABLE) {
return LV_RESULT_INVALID;
}
return LV_RESULT_INVALID; /*If didn't succeeded earlier then it's an error*/
}
/**
* Open a BMP image and return the decided image
* @param decoder pointer to the decoder
* @param dsc pointer to the decoder descriptor
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't open the image
*/
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
LV_UNUSED(decoder);
/*If it's a BMP file...*/
if(dsc->src_type == LV_IMAGE_SRC_FILE) {
const char * fn = dsc->src;
if(lv_strcmp(lv_fs_get_ext(fn), "bmp") != 0) {
return LV_RESULT_INVALID; /*Check the extension*/
}
bmp_dsc_t b;
lv_memset(&b, 0x00, sizeof(b));
lv_fs_res_t res = lv_fs_open(&b.f, dsc->src, LV_FS_MODE_RD);
if(res != LV_FS_RES_OK) return LV_RESULT_INVALID;
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_RESULT_INVALID;
}
lv_memcpy(&b.px_offset, header + 10, 4);
lv_memcpy(&b.px_width, header + 18, 4);
lv_memcpy(&b.px_height, header + 22, 4);
lv_memcpy(&b.bpp, header + 28, 2);
b.row_size_bytes = ((b.bpp * b.px_width + 31) / 32) * 4;
dsc->user_data = lv_malloc(sizeof(bmp_dsc_t));
LV_ASSERT_MALLOC(dsc->user_data);
if(dsc->user_data == NULL) return LV_RESULT_INVALID;
lv_memcpy(dsc->user_data, &b, sizeof(b));
return LV_RESULT_OK;
}
/* BMP file as data not supported for simplicity.
* Convert them to LVGL compatible C arrays directly. */
else if(dsc->src_type == LV_IMAGE_SRC_VARIABLE) {
return LV_RESULT_INVALID;
}
return LV_RESULT_INVALID; /*If not returned earlier then it failed*/
}
static lv_result_t decoder_get_area(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc,
const lv_area_t * full_area, lv_area_t * decoded_area)
{
LV_UNUSED(decoder);
bmp_dsc_t * b = dsc->user_data;
lv_draw_buf_t * decoded = (void *)dsc->decoded;
if(decoded_area->y1 == LV_COORD_MIN) {
*decoded_area = *full_area;
decoded_area->y2 = decoded_area->y1;
int32_t w_px = lv_area_get_width(full_area);
lv_draw_buf_t * reshaped = lv_draw_buf_reshape(decoded, dsc->header.cf, w_px, 1, LV_STRIDE_AUTO);
if(reshaped == NULL) {
if(decoded != NULL) {
lv_draw_buf_destroy(decoded);
decoded = NULL;
dsc->decoded = NULL;
}
decoded = lv_draw_buf_create_ex(image_cache_draw_buf_handlers, w_px, 1, dsc->header.cf, LV_STRIDE_AUTO);
if(decoded == NULL) return LV_RESULT_INVALID;
}
else {
decoded = reshaped;
}
dsc->decoded = decoded;
}
else {
decoded_area->y1++;
decoded_area->y2++;
}
if(decoded_area->y1 > full_area->y2) {
return LV_RESULT_INVALID;
}
else {
int32_t y = (b->px_height - 1) - (decoded_area->y1); /*BMP images are stored upside down*/
uint32_t p = b->px_offset + b->row_size_bytes * y;
p += (decoded_area->x1) * (b->bpp / 8);
lv_fs_seek(&b->f, p, LV_FS_SEEK_SET);
uint32_t line_width_byte = lv_area_get_width(full_area) * (b->bpp / 8);
lv_fs_read(&b->f, decoded->data, line_width_byte, NULL);
return LV_RESULT_OK;
}
}
/**
* Free the allocated resources
*/
static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
LV_UNUSED(decoder);
bmp_dsc_t * b = dsc->user_data;
lv_fs_close(&b->f);
lv_free(dsc->user_data);
if(dsc->decoded) lv_draw_buf_destroy((void *)dsc->decoded);
}
#endif /*LV_USE_BMP*/

View File

@@ -0,0 +1,43 @@
/**
* @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);
void lv_bmp_deinit(void);
/**********************
* MACROS
**********************/
#endif /*LV_USE_BMP*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /*LV_BMP_H*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
/**
* @file lv_ffmpeg.h
*
*/
#ifndef LV_FFMPEG_H
#define LV_FFMPEG_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
#if LV_USE_FFMPEG != 0
#include "../../misc/lv_types.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
struct ffmpeg_context_s;
LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_ffmpeg_player_class;
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);
/**
* De-initialize FFMPEG image decoder
*/
void lv_ffmpeg_deinit(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_RESULT_OK: no error; LV_RESULT_INVALID: can't get the info.
*/
lv_result_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);
/**
* Set the video decoder
* @param obj pointer to a ffmpeg_player object
* @param decoder_name decoder name
*/
void lv_ffmpeg_player_set_decoder(lv_obj_t * obj, const char * decoder_name);
/*=====================
* Other functions
*====================*/
/**********************
* MACROS
**********************/
#endif /*LV_USE_FFMPEG*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_FFMPEG_H*/

View File

@@ -0,0 +1,52 @@
/**
* @file lv_ffmpeg_private.h
*
*/
#ifndef LV_FFMPEG_PRIVATE_H
#define LV_FFMPEG_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_ffmpeg.h"
#if LV_USE_FFMPEG != 0
#include "../../widgets/image/lv_image_private.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
struct _lv_ffmpeg_player_t {
lv_image_t img;
lv_timer_t * timer;
lv_image_dsc_t imgdsc;
bool auto_restart;
struct ffmpeg_context_s * ffmpeg_ctx;
const char * decoder_name;
};
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
#endif /*LV_USE_FFMPEG*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_FFMPEG_PRIVATE_H*/

View File

@@ -0,0 +1,102 @@
Digitized data copyright (c) 2010 Google Corporation
with Reserved Font Arimo, Tinos and Cousine.
Copyright (c) 2012 Red Hat, Inc.
with Reserved Font Name Liberation.
This Font Software is licensed under the SIL Open Font License,
Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
PREAMBLE The goals of the Open Font License (OFL) are to stimulate
worldwide development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to provide
a free and open framework in which fonts may be shared and improved in
partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves.
The fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such.
This may include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components
as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting ? in part or in whole ?
any of the components of the Original Version, by changing formats or
by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer
or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a
copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,in
Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the
corresponding Copyright Holder. This restriction only applies to the
primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole, must
be distributed entirely under this license, and must not be distributed
under any other license. The requirement for fonts to remain under
this license does not apply to any document created using the Font
Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View File

@@ -0,0 +1,170 @@
The FreeType Project LICENSE
----------------------------
2006-Jan-27
Copyright 1996-2002, 2006 by
David Turner, Robert Wilhelm, and Werner Lemberg
Introduction
============
The FreeType Project is distributed in several archive packages;
some of them may contain, in addition to the FreeType font engine,
various tools and contributions which rely on, or relate to, the
FreeType Project.
This license applies to all files found in such packages, and
which do not fall under their own explicit license. The license
affects thus the FreeType font engine, the test programs,
documentation and makefiles, at the very least.
This license was inspired by the BSD, Artistic, and IJG
(Independent JPEG Group) licenses, which all encourage inclusion
and use of free software in commercial and freeware products
alike. As a consequence, its main points are that:
o We don't promise that this software works. However, we will be
interested in any kind of bug reports. (`as is' distribution)
o You can use this software for whatever you want, in parts or
full form, without having to pay us. (`royalty-free' usage)
o You may not pretend that you wrote this software. If you use
it, or only parts of it, in a program, you must acknowledge
somewhere in your documentation that you have used the
FreeType code. (`credits')
We specifically permit and encourage the inclusion of this
software, with or without modifications, in commercial products.
We disclaim all warranties covering The FreeType Project and
assume no liability related to The FreeType Project.
Finally, many people asked us for a preferred form for a
credit/disclaimer to use in compliance with this license. We thus
encourage you to use the following text:
"""
Portions of this software are copyright © <year> The FreeType
Project (www.freetype.org). All rights reserved.
"""
Please replace <year> with the value from the FreeType version you
actually use.
Legal Terms
===========
0. Definitions
--------------
Throughout this license, the terms `package', `FreeType Project',
and `FreeType archive' refer to the set of files originally
distributed by the authors (David Turner, Robert Wilhelm, and
Werner Lemberg) as the `FreeType Project', be they named as alpha,
beta or final release.
`You' refers to the licensee, or person using the project, where
`using' is a generic term including compiling the project's source
code as well as linking it to form a `program' or `executable'.
This program is referred to as `a program using the FreeType
engine'.
This license applies to all files distributed in the original
FreeType Project, including all source code, binaries and
documentation, unless otherwise stated in the file in its
original, unmodified form as distributed in the original archive.
If you are unsure whether or not a particular file is covered by
this license, you must contact us to verify this.
The FreeType Project is copyright (C) 1996-2000 by David Turner,
Robert Wilhelm, and Werner Lemberg. All rights reserved except as
specified below.
1. No Warranty
--------------
THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO
USE, OF THE FREETYPE PROJECT.
2. Redistribution
-----------------
This license grants a worldwide, royalty-free, perpetual and
irrevocable right and license to use, execute, perform, compile,
display, copy, create derivative works of, distribute and
sublicense the FreeType Project (in both source and object code
forms) and derivative works thereof for any purpose; and to
authorize others to exercise some or all of the rights granted
herein, subject to the following conditions:
o Redistribution of source code must retain this license file
(`FTL.TXT') unaltered; any additions, deletions or changes to
the original files must be clearly indicated in accompanying
documentation. The copyright notices of the unaltered,
original files must be preserved in all copies of source
files.
o Redistribution in binary form must provide a disclaimer that
states that the software is based in part of the work of the
FreeType Team, in the distribution documentation. We also
encourage you to put an URL to the FreeType web page in your
documentation, though this isn't mandatory.
These conditions apply to any software derived from or based on
the FreeType Project, not just the unmodified files. If you use
our work, you must acknowledge us. However, no fee need be paid
to us.
3. Advertising
--------------
Neither the FreeType authors and contributors nor you shall use
the name of the other for commercial, advertising, or promotional
purposes without specific prior written permission.
We suggest, but do not require, that you use one or more of the
following phrases to refer to this software in your documentation
or advertising materials: `FreeType Project', `FreeType Engine',
`FreeType library', or `FreeType Distribution'.
As you have not signed this license, you are not required to
accept it. However, as the FreeType Project is copyrighted
material, only this license, or another one contracted with the
authors, grants you the right to use, distribute, and modify it.
Therefore, by using, distributing, or modifying the FreeType
Project, you indicate that you understand and accept all the terms
of this license.
4. Contacts
-----------
There are two mailing lists related to FreeType:
o freetype@nongnu.org
Discusses general use and applications of FreeType, as well as
future and wanted additions to the library and distribution.
If you are looking for support, start in this list if you
haven't found anything to help you in the documentation.
o freetype-devel@nongnu.org
Discusses bugs, as well as engine internals, design issues,
specific licenses, porting, etc.
Our home page can be found at
https://www.freetype.org
--- end of FTL.TXT ---

View File

@@ -0,0 +1,33 @@
/*
* This file registers the FreeType modules compiled into the library.
*
* If you use GNU make, this file IS NOT USED! Instead, it is created in
* the objects directory (normally `<topdir>/objs/`) based on information
* from `<topdir>/modules.cfg`.
*
* Please read `docs/INSTALL.ANY` and `docs/CUSTOMIZE` how to compile
* FreeType without GNU make.
*
*/
/* FT_USE_MODULE( FT_Module_Class, autofit_module_class ) */
FT_USE_MODULE(FT_Driver_ClassRec, tt_driver_class)
/* FT_USE_MODULE( FT_Driver_ClassRec, t1_driver_class ) */
/* FT_USE_MODULE( FT_Driver_ClassRec, cff_driver_class ) */
/* FT_USE_MODULE( FT_Driver_ClassRec, t1cid_driver_class ) */
/* FT_USE_MODULE( FT_Driver_ClassRec, pfr_driver_class ) */
/* FT_USE_MODULE( FT_Driver_ClassRec, t42_driver_class ) */
/* FT_USE_MODULE( FT_Driver_ClassRec, winfnt_driver_class ) */
/* FT_USE_MODULE( FT_Driver_ClassRec, pcf_driver_class ) */
/* FT_USE_MODULE( FT_Driver_ClassRec, bdf_driver_class ) */
/* FT_USE_MODULE( FT_Module_Class, psaux_module_class ) */
/* FT_USE_MODULE( FT_Module_Class, psnames_module_class ) */
/* FT_USE_MODULE( FT_Module_Class, pshinter_module_class ) */
FT_USE_MODULE(FT_Module_Class, sfnt_module_class)
FT_USE_MODULE(FT_Renderer_Class, ft_smooth_renderer_class)
/* FT_USE_MODULE( FT_Renderer_Class, ft_raster1_renderer_class ) */
/* FT_USE_MODULE( FT_Renderer_Class, ft_sdf_renderer_class ) */
/* FT_USE_MODULE( FT_Renderer_Class, ft_bitmap_sdf_renderer_class ) */
/* FT_USE_MODULE( FT_Renderer_Class, ft_svg_renderer_class ) */
/* EOF */

View File

@@ -0,0 +1,964 @@
/****************************************************************************
*
* ftoption.h
*
* User-selectable configuration macros (specification only).
*
* Copyright (C) 1996-2022 by
* David Turner, Robert Wilhelm, and Werner Lemberg.
*
* This file is part of the FreeType project, and may only be used,
* modified, and distributed under the terms of the FreeType project
* license, LICENSE.TXT. By continuing to use, modify, or distribute
* this file you indicate that you have read the license and
* understand and accept it fully.
*
*/
#ifndef FTOPTION_H_
#define FTOPTION_H_
#include <ft2build.h>
FT_BEGIN_HEADER
/**************************************************************************
*
* USER-SELECTABLE CONFIGURATION MACROS
*
* This file contains the default configuration macro definitions for a
* standard build of the FreeType library. There are three ways to use
* this file to build project-specific versions of the library:
*
* - You can modify this file by hand, but this is not recommended in
* cases where you would like to build several versions of the library
* from a single source directory.
*
* - You can put a copy of this file in your build directory, more
* precisely in `$BUILD/freetype/config/ftoption.h`, where `$BUILD` is
* the name of a directory that is included _before_ the FreeType include
* path during compilation.
*
* The default FreeType Makefiles use the build directory
* `builds/<system>` by default, but you can easily change that for your
* own projects.
*
* - Copy the file <ft2build.h> to `$BUILD/ft2build.h` and modify it
* slightly to pre-define the macro `FT_CONFIG_OPTIONS_H` used to locate
* this file during the build. For example,
*
* ```
* #define FT_CONFIG_OPTIONS_H <myftoptions.h>
* #include <freetype/config/ftheader.h>
* ```
*
* will use `$BUILD/myftoptions.h` instead of this file for macro
* definitions.
*
* Note also that you can similarly pre-define the macro
* `FT_CONFIG_MODULES_H` used to locate the file listing of the modules
* that are statically linked to the library at compile time. By
* default, this file is `<freetype/config/ftmodule.h>`.
*
* We highly recommend using the third method whenever possible.
*
*/
/*************************************************************************/
/*************************************************************************/
/**** ****/
/**** G E N E R A L F R E E T Y P E 2 C O N F I G U R A T I O N ****/
/**** ****/
/*************************************************************************/
/*************************************************************************/
/*#************************************************************************
*
* If you enable this configuration option, FreeType recognizes an
* environment variable called `FREETYPE_PROPERTIES`, which can be used to
* control the various font drivers and modules. The controllable
* properties are listed in the section @properties.
*
* You have to undefine this configuration option on platforms that lack
* the concept of environment variables (and thus don't have the `getenv`
* function), for example Windows CE.
*
* `FREETYPE_PROPERTIES` has the following syntax form (broken here into
* multiple lines for better readability).
*
* ```
* <optional whitespace>
* <module-name1> ':'
* <property-name1> '=' <property-value1>
* <whitespace>
* <module-name2> ':'
* <property-name2> '=' <property-value2>
* ...
* ```
*
* Example:
*
* ```
* FREETYPE_PROPERTIES=truetype:interpreter-version=35 \
* cff:no-stem-darkening=1
* ```
*
*/
#define FT_CONFIG_OPTION_ENVIRONMENT_PROPERTIES
/**************************************************************************
*
* Uncomment the line below if you want to activate LCD rendering
* technology similar to ClearType in this build of the library. This
* technology triples the resolution in the direction color subpixels. To
* mitigate color fringes inherent to this technology, you also need to
* explicitly set up LCD filtering.
*
* When this macro is not defined, FreeType offers alternative LCD
* rendering technology that produces excellent output.
*/
/* #define FT_CONFIG_OPTION_SUBPIXEL_RENDERING */
/**************************************************************************
*
* Many compilers provide a non-ANSI 64-bit data type that can be used by
* FreeType to speed up some computations. However, this will create some
* problems when compiling the library in strict ANSI mode.
*
* For this reason, the use of 64-bit integers is normally disabled when
* the `__STDC__` macro is defined. You can however disable this by
* defining the macro `FT_CONFIG_OPTION_FORCE_INT64` here.
*
* For most compilers, this will only create compilation warnings when
* building the library.
*
* ObNote: The compiler-specific 64-bit integers are detected in the
* file `ftconfig.h` either statically or through the `configure`
* script on supported platforms.
*/
#undef FT_CONFIG_OPTION_FORCE_INT64
/**************************************************************************
*
* If this macro is defined, do not try to use an assembler version of
* performance-critical functions (e.g., @FT_MulFix). You should only do
* that to verify that the assembler function works properly, or to execute
* benchmark tests of the various implementations.
*/
/* #define FT_CONFIG_OPTION_NO_ASSEMBLER */
/**************************************************************************
*
* If this macro is defined, try to use an inlined assembler version of the
* @FT_MulFix function, which is a 'hotspot' when loading and hinting
* glyphs, and which should be executed as fast as possible.
*
* Note that if your compiler or CPU is not supported, this will default to
* the standard and portable implementation found in `ftcalc.c`.
*/
#define FT_CONFIG_OPTION_INLINE_MULFIX
/**************************************************************************
*
* LZW-compressed file support.
*
* FreeType now handles font files that have been compressed with the
* `compress` program. This is mostly used to parse many of the PCF
* files that come with various X11 distributions. The implementation
* uses NetBSD's `zopen` to partially uncompress the file on the fly (see
* `src/lzw/ftgzip.c`).
*
* Define this macro if you want to enable this 'feature'.
*/
#define FT_CONFIG_OPTION_USE_LZW
/**************************************************************************
*
* Gzip-compressed file support.
*
* FreeType now handles font files that have been compressed with the
* `gzip` program. This is mostly used to parse many of the PCF files
* that come with XFree86. The implementation uses 'zlib' to partially
* uncompress the file on the fly (see `src/gzip/ftgzip.c`).
*
* Define this macro if you want to enable this 'feature'. See also the
* macro `FT_CONFIG_OPTION_SYSTEM_ZLIB` below.
*/
#define FT_CONFIG_OPTION_USE_ZLIB
/**************************************************************************
*
* ZLib library selection
*
* This macro is only used when `FT_CONFIG_OPTION_USE_ZLIB` is defined.
* It allows FreeType's 'ftgzip' component to link to the system's
* installation of the ZLib library. This is useful on systems like
* Unix or VMS where it generally is already available.
*
* If you let it undefined, the component will use its own copy of the
* zlib sources instead. These have been modified to be included
* directly within the component and **not** export external function
* names. This allows you to link any program with FreeType _and_ ZLib
* without linking conflicts.
*
* Do not `#undef` this macro here since the build system might define
* it for certain configurations only.
*
* If you use a build system like cmake or the `configure` script,
* options set by those programs have precedence, overwriting the value
* here with the configured one.
*
* If you use the GNU make build system directly (that is, without the
* `configure` script) and you define this macro, you also have to pass
* `SYSTEM_ZLIB=yes` as an argument to make.
*/
/* #define FT_CONFIG_OPTION_SYSTEM_ZLIB */
/**************************************************************************
*
* Bzip2-compressed file support.
*
* FreeType now handles font files that have been compressed with the
* `bzip2` program. This is mostly used to parse many of the PCF files
* that come with XFree86. The implementation uses `libbz2` to partially
* uncompress the file on the fly (see `src/bzip2/ftbzip2.c`). Contrary
* to gzip, bzip2 currently is not included and need to use the system
* available bzip2 implementation.
*
* Define this macro if you want to enable this 'feature'.
*
* If you use a build system like cmake or the `configure` script,
* options set by those programs have precedence, overwriting the value
* here with the configured one.
*/
/* #define FT_CONFIG_OPTION_USE_BZIP2 */
/**************************************************************************
*
* Define to disable the use of file stream functions and types, `FILE`,
* `fopen`, etc. Enables the use of smaller system libraries on embedded
* systems that have multiple system libraries, some with or without file
* stream support, in the cases where file stream support is not necessary
* such as memory loading of font files.
*/
/* #define FT_CONFIG_OPTION_DISABLE_STREAM_SUPPORT */
/**************************************************************************
*
* PNG bitmap support.
*
* FreeType now handles loading color bitmap glyphs in the PNG format.
* This requires help from the external libpng library. Uncompressed
* color bitmaps do not need any external libraries and will be supported
* regardless of this configuration.
*
* Define this macro if you want to enable this 'feature'.
*
* If you use a build system like cmake or the `configure` script,
* options set by those programs have precedence, overwriting the value
* here with the configured one.
*/
#define FT_CONFIG_OPTION_USE_PNG
/**************************************************************************
*
* HarfBuzz support.
*
* FreeType uses the HarfBuzz library to improve auto-hinting of OpenType
* fonts. If available, many glyphs not directly addressable by a font's
* character map will be hinted also.
*
* Define this macro if you want to enable this 'feature'.
*
* If you use a build system like cmake or the `configure` script,
* options set by those programs have precedence, overwriting the value
* here with the configured one.
*/
/* #define FT_CONFIG_OPTION_USE_HARFBUZZ */
/**************************************************************************
*
* Brotli support.
*
* FreeType uses the Brotli library to provide support for decompressing
* WOFF2 streams.
*
* Define this macro if you want to enable this 'feature'.
*
* If you use a build system like cmake or the `configure` script,
* options set by those programs have precedence, overwriting the value
* here with the configured one.
*/
/* #define FT_CONFIG_OPTION_USE_BROTLI */
/**************************************************************************
*
* Glyph Postscript Names handling
*
* By default, FreeType 2 is compiled with the 'psnames' module. This
* module is in charge of converting a glyph name string into a Unicode
* value, or return a Macintosh standard glyph name for the use with the
* TrueType 'post' table.
*
* Undefine this macro if you do not want 'psnames' compiled in your
* build of FreeType. This has the following effects:
*
* - The TrueType driver will provide its own set of glyph names, if you
* build it to support postscript names in the TrueType 'post' table,
* but will not synthesize a missing Unicode charmap.
*
* - The Type~1 driver will not be able to synthesize a Unicode charmap
* out of the glyphs found in the fonts.
*
* You would normally undefine this configuration macro when building a
* version of FreeType that doesn't contain a Type~1 or CFF driver.
*/
#define FT_CONFIG_OPTION_POSTSCRIPT_NAMES
/**************************************************************************
*
* Postscript Names to Unicode Values support
*
* By default, FreeType~2 is built with the 'psnames' module compiled in.
* Among other things, the module is used to convert a glyph name into a
* Unicode value. This is especially useful in order to synthesize on
* the fly a Unicode charmap from the CFF/Type~1 driver through a big
* table named the 'Adobe Glyph List' (AGL).
*
* Undefine this macro if you do not want the Adobe Glyph List compiled
* in your 'psnames' module. The Type~1 driver will not be able to
* synthesize a Unicode charmap out of the glyphs found in the fonts.
*/
#define FT_CONFIG_OPTION_ADOBE_GLYPH_LIST
/**************************************************************************
*
* Support for Mac fonts
*
* Define this macro if you want support for outline fonts in Mac format
* (mac dfont, mac resource, macbinary containing a mac resource) on
* non-Mac platforms.
*
* Note that the 'FOND' resource isn't checked.
*/
/* #define FT_CONFIG_OPTION_MAC_FONTS */
/**************************************************************************
*
* Guessing methods to access embedded resource forks
*
* Enable extra Mac fonts support on non-Mac platforms (e.g., GNU/Linux).
*
* Resource forks which include fonts data are stored sometimes in
* locations which users or developers don't expected. In some cases,
* resource forks start with some offset from the head of a file. In
* other cases, the actual resource fork is stored in file different from
* what the user specifies. If this option is activated, FreeType tries
* to guess whether such offsets or different file names must be used.
*
* Note that normal, direct access of resource forks is controlled via
* the `FT_CONFIG_OPTION_MAC_FONTS` option.
*/
#ifdef FT_CONFIG_OPTION_MAC_FONTS
#define FT_CONFIG_OPTION_GUESSING_EMBEDDED_RFORK
#endif
/**************************************************************************
*
* Allow the use of `FT_Incremental_Interface` to load typefaces that
* contain no glyph data, but supply it via a callback function. This is
* required by clients supporting document formats which supply font data
* incrementally as the document is parsed, such as the Ghostscript
* interpreter for the PostScript language.
*/
#define FT_CONFIG_OPTION_INCREMENTAL
/**************************************************************************
*
* The size in bytes of the render pool used by the scan-line converter to
* do all of its work.
*/
#define FT_RENDER_POOL_SIZE 16384L
/**************************************************************************
*
* FT_MAX_MODULES
*
* The maximum number of modules that can be registered in a single
* FreeType library object. 32~is the default.
*/
#define FT_MAX_MODULES 32
/**************************************************************************
*
* Debug level
*
* FreeType can be compiled in debug or trace mode. In debug mode,
* errors are reported through the 'ftdebug' component. In trace mode,
* additional messages are sent to the standard output during execution.
*
* Define `FT_DEBUG_LEVEL_ERROR` to build the library in debug mode.
* Define `FT_DEBUG_LEVEL_TRACE` to build it in trace mode.
*
* Don't define any of these macros to compile in 'release' mode!
*
* Do not `#undef` these macros here since the build system might define
* them for certain configurations only.
*/
/* #define FT_DEBUG_LEVEL_ERROR */
/* #define FT_DEBUG_LEVEL_TRACE */
/**************************************************************************
*
* Logging
*
* Compiling FreeType in debug or trace mode makes FreeType write error
* and trace log messages to `stderr`. Enabling this macro
* automatically forces the `FT_DEBUG_LEVEL_ERROR` and
* `FT_DEBUG_LEVEL_TRACE` macros and allows FreeType to write error and
* trace log messages to a file instead of `stderr`. For writing logs
* to a file, FreeType uses an the external `dlg` library (the source
* code is in `src/dlg`).
*
* This option needs a C99 compiler.
*/
/* #define FT_DEBUG_LOGGING */
/**************************************************************************
*
* Autofitter debugging
*
* If `FT_DEBUG_AUTOFIT` is defined, FreeType provides some means to
* control the autofitter behaviour for debugging purposes with global
* boolean variables (consequently, you should **never** enable this
* while compiling in 'release' mode):
*
* ```
* _af_debug_disable_horz_hints
* _af_debug_disable_vert_hints
* _af_debug_disable_blue_hints
* ```
*
* Additionally, the following functions provide dumps of various
* internal autofit structures to stdout (using `printf`):
*
* ```
* af_glyph_hints_dump_points
* af_glyph_hints_dump_segments
* af_glyph_hints_dump_edges
* af_glyph_hints_get_num_segments
* af_glyph_hints_get_segment_offset
* ```
*
* As an argument, they use another global variable:
*
* ```
* _af_debug_hints
* ```
*
* Please have a look at the `ftgrid` demo program to see how those
* variables and macros should be used.
*
* Do not `#undef` these macros here since the build system might define
* them for certain configurations only.
*/
/* #define FT_DEBUG_AUTOFIT */
/**************************************************************************
*
* Memory Debugging
*
* FreeType now comes with an integrated memory debugger that is capable
* of detecting simple errors like memory leaks or double deletes. To
* compile it within your build of the library, you should define
* `FT_DEBUG_MEMORY` here.
*
* Note that the memory debugger is only activated at runtime when when
* the _environment_ variable `FT2_DEBUG_MEMORY` is defined also!
*
* Do not `#undef` this macro here since the build system might define it
* for certain configurations only.
*/
/* #define FT_DEBUG_MEMORY */
/**************************************************************************
*
* Module errors
*
* If this macro is set (which is _not_ the default), the higher byte of
* an error code gives the module in which the error has occurred, while
* the lower byte is the real error code.
*
* Setting this macro makes sense for debugging purposes only, since it
* would break source compatibility of certain programs that use
* FreeType~2.
*
* More details can be found in the files `ftmoderr.h` and `fterrors.h`.
*/
#undef FT_CONFIG_OPTION_USE_MODULE_ERRORS
/**************************************************************************
*
* OpenType SVG Glyph Support
*
* Setting this macro enables support for OpenType SVG glyphs. By
* default, FreeType can only fetch SVG documents. However, it can also
* render them if external rendering hook functions are plugged in at
* runtime.
*
* More details on the hooks can be found in file `otsvg.h`.
*/
#define FT_CONFIG_OPTION_SVG
/**************************************************************************
*
* Error Strings
*
* If this macro is set, `FT_Error_String` will return meaningful
* descriptions. This is not enabled by default to reduce the overall
* size of FreeType.
*
* More details can be found in the file `fterrors.h`.
*/
#define FT_CONFIG_OPTION_ERROR_STRINGS
/*************************************************************************/
/*************************************************************************/
/**** ****/
/**** S F N T D R I V E R C O N F I G U R A T I O N ****/
/**** ****/
/*************************************************************************/
/*************************************************************************/
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_EMBEDDED_BITMAPS` if you want to support
* embedded bitmaps in all formats using the 'sfnt' module (namely
* TrueType~& OpenType).
*/
#define TT_CONFIG_OPTION_EMBEDDED_BITMAPS
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_COLOR_LAYERS` if you want to support colored
* outlines (from the 'COLR'/'CPAL' tables) in all formats using the 'sfnt'
* module (namely TrueType~& OpenType).
*/
#define TT_CONFIG_OPTION_COLOR_LAYERS
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_POSTSCRIPT_NAMES` if you want to be able to
* load and enumerate the glyph Postscript names in a TrueType or OpenType
* file.
*
* Note that when you do not compile the 'psnames' module by undefining the
* above `FT_CONFIG_OPTION_POSTSCRIPT_NAMES`, the 'sfnt' module will
* contain additional code used to read the PS Names table from a font.
*
* (By default, the module uses 'psnames' to extract glyph names.)
*/
#define TT_CONFIG_OPTION_POSTSCRIPT_NAMES
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_SFNT_NAMES` if your applications need to access
* the internal name table in a SFNT-based format like TrueType or
* OpenType. The name table contains various strings used to describe the
* font, like family name, copyright, version, etc. It does not contain
* any glyph name though.
*
* Accessing SFNT names is done through the functions declared in
* `ftsnames.h`.
*/
#define TT_CONFIG_OPTION_SFNT_NAMES
/**************************************************************************
*
* TrueType CMap support
*
* Here you can fine-tune which TrueType CMap table format shall be
* supported.
*/
#define TT_CONFIG_CMAP_FORMAT_0
#define TT_CONFIG_CMAP_FORMAT_2
#define TT_CONFIG_CMAP_FORMAT_4
#define TT_CONFIG_CMAP_FORMAT_6
#define TT_CONFIG_CMAP_FORMAT_8
#define TT_CONFIG_CMAP_FORMAT_10
#define TT_CONFIG_CMAP_FORMAT_12
#define TT_CONFIG_CMAP_FORMAT_13
#define TT_CONFIG_CMAP_FORMAT_14
/*************************************************************************/
/*************************************************************************/
/**** ****/
/**** T R U E T Y P E D R I V E R C O N F I G U R A T I O N ****/
/**** ****/
/*************************************************************************/
/*************************************************************************/
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_BYTECODE_INTERPRETER` if you want to compile a
* bytecode interpreter in the TrueType driver.
*
* By undefining this, you will only compile the code necessary to load
* TrueType glyphs without hinting.
*
* Do not `#undef` this macro here, since the build system might define it
* for certain configurations only.
*/
#define TT_CONFIG_OPTION_BYTECODE_INTERPRETER
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_SUBPIXEL_HINTING` if you want to compile
* subpixel hinting support into the TrueType driver. This modifies the
* TrueType hinting mechanism when anything but `FT_RENDER_MODE_MONO` is
* requested.
*
* In particular, it modifies the bytecode interpreter to interpret (or
* not) instructions in a certain way so that all TrueType fonts look like
* they do in a Windows ClearType (DirectWrite) environment. See [1] for a
* technical overview on what this means. See `ttinterp.h` for more
* details on the LEAN option.
*
* There are three possible values.
*
* Value 1:
* This value is associated with the 'Infinality' moniker, contributed by
* an individual nicknamed Infinality with the goal of making TrueType
* fonts render better than on Windows. A high amount of configurability
* and flexibility, down to rules for single glyphs in fonts, but also
* very slow. Its experimental and slow nature and the original
* developer losing interest meant that this option was never enabled in
* default builds.
*
* The corresponding interpreter version is v38.
*
* Value 2:
* The new default mode for the TrueType driver. The Infinality code
* base was stripped to the bare minimum and all configurability removed
* in the name of speed and simplicity. The configurability was mainly
* aimed at legacy fonts like 'Arial', 'Times New Roman', or 'Courier'.
* Legacy fonts are fonts that modify vertical stems to achieve clean
* black-and-white bitmaps. The new mode focuses on applying a minimal
* set of rules to all fonts indiscriminately so that modern and web
* fonts render well while legacy fonts render okay.
*
* The corresponding interpreter version is v40.
*
* Value 3:
* Compile both, making both v38 and v40 available (the latter is the
* default).
*
* By undefining these, you get rendering behavior like on Windows without
* ClearType, i.e., Windows XP without ClearType enabled and Win9x
* (interpreter version v35). Or not, depending on how much hinting blood
* and testing tears the font designer put into a given font. If you
* define one or both subpixel hinting options, you can switch between
* between v35 and the ones you define (using `FT_Property_Set`).
*
* This option requires `TT_CONFIG_OPTION_BYTECODE_INTERPRETER` to be
* defined.
*
* [1]
* https://www.microsoft.com/typography/cleartype/truetypecleartype.aspx
*/
/* #define TT_CONFIG_OPTION_SUBPIXEL_HINTING 1 */
#define TT_CONFIG_OPTION_SUBPIXEL_HINTING 2
/* #define TT_CONFIG_OPTION_SUBPIXEL_HINTING ( 1 | 2 ) */
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_COMPONENT_OFFSET_SCALED` to compile the
* TrueType glyph loader to use Apple's definition of how to handle
* component offsets in composite glyphs.
*
* Apple and MS disagree on the default behavior of component offsets in
* composites. Apple says that they should be scaled by the scaling
* factors in the transformation matrix (roughly, it's more complex) while
* MS says they should not. OpenType defines two bits in the composite
* flags array which can be used to disambiguate, but old fonts will not
* have them.
*
* https://www.microsoft.com/typography/otspec/glyf.htm
* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6glyf.html
*/
#undef TT_CONFIG_OPTION_COMPONENT_OFFSET_SCALED
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_GX_VAR_SUPPORT` if you want to include support
* for Apple's distortable font technology ('fvar', 'gvar', 'cvar', and
* 'avar' tables). Tagged 'Font Variations', this is now part of OpenType
* also. This has many similarities to Type~1 Multiple Masters support.
*/
#define TT_CONFIG_OPTION_GX_VAR_SUPPORT
/**************************************************************************
*
* Define `TT_CONFIG_OPTION_BDF` if you want to include support for an
* embedded 'BDF~' table within SFNT-based bitmap formats.
*/
#define TT_CONFIG_OPTION_BDF
/**************************************************************************
*
* Option `TT_CONFIG_OPTION_MAX_RUNNABLE_OPCODES` controls the maximum
* number of bytecode instructions executed for a single run of the
* bytecode interpreter, needed to prevent infinite loops. You don't want
* to change this except for very special situations (e.g., making a
* library fuzzer spend less time to handle broken fonts).
*
* It is not expected that this value is ever modified by a configuring
* script; instead, it gets surrounded with `#ifndef ... #endif` so that
* the value can be set as a preprocessor option on the compiler's command
* line.
*/
#ifndef TT_CONFIG_OPTION_MAX_RUNNABLE_OPCODES
#define TT_CONFIG_OPTION_MAX_RUNNABLE_OPCODES 1000000L
#endif
/*************************************************************************/
/*************************************************************************/
/**** ****/
/**** T Y P E 1 D R I V E R C O N F I G U R A T I O N ****/
/**** ****/
/*************************************************************************/
/*************************************************************************/
/**************************************************************************
*
* `T1_MAX_DICT_DEPTH` is the maximum depth of nest dictionaries and arrays
* in the Type~1 stream (see `t1load.c`). A minimum of~4 is required.
*/
#define T1_MAX_DICT_DEPTH 5
/**************************************************************************
*
* `T1_MAX_SUBRS_CALLS` details the maximum number of nested sub-routine
* calls during glyph loading.
*/
#define T1_MAX_SUBRS_CALLS 16
/**************************************************************************
*
* `T1_MAX_CHARSTRING_OPERANDS` is the charstring stack's capacity. A
* minimum of~16 is required.
*
* The Chinese font 'MingTiEG-Medium' (covering the CNS 11643 character
* set) needs 256.
*/
#define T1_MAX_CHARSTRINGS_OPERANDS 256
/**************************************************************************
*
* Define this configuration macro if you want to prevent the compilation
* of the 't1afm' module, which is in charge of reading Type~1 AFM files
* into an existing face. Note that if set, the Type~1 driver will be
* unable to produce kerning distances.
*/
#undef T1_CONFIG_OPTION_NO_AFM
/**************************************************************************
*
* Define this configuration macro if you want to prevent the compilation
* of the Multiple Masters font support in the Type~1 driver.
*/
#undef T1_CONFIG_OPTION_NO_MM_SUPPORT
/**************************************************************************
*
* `T1_CONFIG_OPTION_OLD_ENGINE` controls whether the pre-Adobe Type~1
* engine gets compiled into FreeType. If defined, it is possible to
* switch between the two engines using the `hinting-engine` property of
* the 'type1' driver module.
*/
/* #define T1_CONFIG_OPTION_OLD_ENGINE */
/*************************************************************************/
/*************************************************************************/
/**** ****/
/**** C F F D R I V E R C O N F I G U R A T I O N ****/
/**** ****/
/*************************************************************************/
/*************************************************************************/
/**************************************************************************
*
* Using `CFF_CONFIG_OPTION_DARKENING_PARAMETER_{X,Y}{1,2,3,4}` it is
* possible to set up the default values of the four control points that
* define the stem darkening behaviour of the (new) CFF engine. For more
* details please read the documentation of the `darkening-parameters`
* property (file `ftdriver.h`), which allows the control at run-time.
*
* Do **not** undefine these macros!
*/
#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_X1 500
#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y1 400
#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_X2 1000
#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y2 275
#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_X3 1667
#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y3 275
#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_X4 2333
#define CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y4 0
/**************************************************************************
*
* `CFF_CONFIG_OPTION_OLD_ENGINE` controls whether the pre-Adobe CFF engine
* gets compiled into FreeType. If defined, it is possible to switch
* between the two engines using the `hinting-engine` property of the 'cff'
* driver module.
*/
/* #define CFF_CONFIG_OPTION_OLD_ENGINE */
/*************************************************************************/
/*************************************************************************/
/**** ****/
/**** P C F D R I V E R C O N F I G U R A T I O N ****/
/**** ****/
/*************************************************************************/
/*************************************************************************/
/**************************************************************************
*
* There are many PCF fonts just called 'Fixed' which look completely
* different, and which have nothing to do with each other. When selecting
* 'Fixed' in KDE or Gnome one gets results that appear rather random, the
* style changes often if one changes the size and one cannot select some
* fonts at all. This option makes the 'pcf' module prepend the foundry
* name (plus a space) to the family name.
*
* We also check whether we have 'wide' characters; all put together, we
* get family names like 'Sony Fixed' or 'Misc Fixed Wide'.
*
* If this option is activated, it can be controlled with the
* `no-long-family-names` property of the 'pcf' driver module.
*/
/* #define PCF_CONFIG_OPTION_LONG_FAMILY_NAMES */
/*************************************************************************/
/*************************************************************************/
/**** ****/
/**** A U T O F I T M O D U L E C O N F I G U R A T I O N ****/
/**** ****/
/*************************************************************************/
/*************************************************************************/
/**************************************************************************
*
* Compile 'autofit' module with CJK (Chinese, Japanese, Korean) script
* support.
*/
#define AF_CONFIG_OPTION_CJK
/**************************************************************************
*
* Compile 'autofit' module with fallback Indic script support, covering
* some scripts that the 'latin' submodule of the 'autofit' module doesn't
* (yet) handle. Currently, this needs option `AF_CONFIG_OPTION_CJK`.
*/
#ifdef AF_CONFIG_OPTION_CJK
#define AF_CONFIG_OPTION_INDIC
#endif
/**************************************************************************
*
* Use TrueType-like size metrics for 'light' auto-hinting.
*
* It is strongly recommended to avoid this option, which exists only to
* help some legacy applications retain its appearance and behaviour with
* respect to auto-hinted TrueType fonts.
*
* The very reason this option exists at all are GNU/Linux distributions
* like Fedora that did not un-patch the following change (which was
* present in FreeType between versions 2.4.6 and 2.7.1, inclusive).
*
* ```
* 2011-07-16 Steven Chu <steven.f.chu@gmail.com>
*
* [truetype] Fix metrics on size request for scalable fonts.
* ```
*
* This problematic commit is now reverted (more or less).
*/
/* #define AF_CONFIG_OPTION_TT_SIZE_METRICS */
/* */
/*
* This macro is obsolete. Support has been removed in FreeType version
* 2.5.
*/
/* #define FT_CONFIG_OPTION_OLD_INTERNALS */
/*
* The next three macros are defined if native TrueType hinting is
* requested by the definitions above. Don't change this.
*/
#ifdef TT_CONFIG_OPTION_BYTECODE_INTERPRETER
#define TT_USE_BYTECODE_INTERPRETER
#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING
#if TT_CONFIG_OPTION_SUBPIXEL_HINTING & 1
#define TT_SUPPORT_SUBPIXEL_HINTING_INFINALITY
#endif
#if TT_CONFIG_OPTION_SUBPIXEL_HINTING & 2
#define TT_SUPPORT_SUBPIXEL_HINTING_MINIMAL
#endif
#endif
#endif
/*
* The TT_SUPPORT_COLRV1 macro is defined to indicate to clients that this
* version of FreeType has support for 'COLR' v1 API. This definition is
* useful to FreeType clients that want to build in support for 'COLR' v1
* depending on a tip-of-tree checkout before it is officially released in
* FreeType, and while the feature cannot yet be tested against using
* version macros. Don't change this macro. This may be removed once the
* feature is in a FreeType release version and version macros can be used
* to test for availability.
*/
#ifdef TT_CONFIG_OPTION_COLOR_LAYERS
#define TT_SUPPORT_COLRV1
#endif
/*
* Check CFF darkening parameters. The checks are the same as in function
* `cff_property_set` in file `cffdrivr.c`.
*/
#if CFF_CONFIG_OPTION_DARKENING_PARAMETER_X1 < 0 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_X2 < 0 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_X3 < 0 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_X4 < 0 || \
\
CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y1 < 0 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y2 < 0 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y3 < 0 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y4 < 0 || \
\
CFF_CONFIG_OPTION_DARKENING_PARAMETER_X1 > \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_X2 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_X2 > \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_X3 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_X3 > \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_X4 || \
\
CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y1 > 500 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y2 > 500 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y3 > 500 || \
CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y4 > 500
#error "Invalid CFF darkening parameters!"
#endif
FT_END_HEADER
#endif /* FTOPTION_H_ */
/* END */

View File

@@ -0,0 +1,496 @@
/**
* @file lv_freetype.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_freetype_private.h"
#if LV_USE_FREETYPE
#include "../../misc/lv_fs_private.h"
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#define ft_ctx LV_GLOBAL_DEFAULT()->ft_context
#define LV_FREETYPE_OUTLINE_REF_SIZE_DEF 128
/**< This value is from the FreeType's function `FT_GlyphSlot_Oblique` in `ftsynth.c` */
#define LV_FREETYPE_OBLIQUE_SLANT_DEF 0x0366A
#if LV_FREETYPE_CACHE_FT_GLYPH_CNT <= 0
#error "LV_FREETYPE_CACHE_FT_GLYPH_CNT must be greater than 0"
#endif
/**********************
* TYPEDEFS
**********************/
/* Use the pointer storing pathname as the unique request ID of the face */
typedef struct {
char * pathname;
int ref_cnt;
} face_id_node_t;
/**********************
* STATIC PROTOTYPES
**********************/
static void lv_freetype_cleanup(lv_freetype_context_t * ctx);
static FTC_FaceID lv_freetype_req_face_id(lv_freetype_context_t * ctx, const char * pathname);
static void lv_freetype_drop_face_id(lv_freetype_context_t * ctx, FTC_FaceID face_id);
static bool freetype_on_font_create(lv_freetype_font_dsc_t * dsc, uint32_t max_glyph_cnt);
static void freetype_on_font_set_cbs(lv_freetype_font_dsc_t * dsc);
static bool cache_node_cache_create_cb(lv_freetype_cache_node_t * node, void * user_data);
static void cache_node_cache_free_cb(lv_freetype_cache_node_t * node, void * user_data);
static lv_cache_compare_res_t cache_node_cache_compare_cb(const lv_freetype_cache_node_t * lhs,
const lv_freetype_cache_node_t * rhs);
static lv_font_t * freetype_font_create_cb(const lv_font_info_t * info, const void * src);
static void freetype_font_delete_cb(lv_font_t * font);
static void * freetype_font_dup_src_cb(const void * src);
static void freetype_font_free_src_cb(void * src);
/**********************
* STATIC VARIABLES
**********************/
const lv_font_class_t lv_freetype_font_class = {
.create_cb = freetype_font_create_cb,
.delete_cb = freetype_font_delete_cb,
.dup_src_cb = freetype_font_dup_src_cb,
.free_src_cb = freetype_font_free_src_cb,
};
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_result_t lv_freetype_init(uint32_t max_glyph_cnt)
{
if(ft_ctx) {
LV_LOG_WARN("freetype already initialized");
return LV_RESULT_INVALID;
}
ft_ctx = lv_malloc_zeroed(sizeof(lv_freetype_context_t));
LV_ASSERT_MALLOC(ft_ctx);
if(!ft_ctx) {
LV_LOG_ERROR("malloc failed for lv_freetype_context_t");
return LV_RESULT_INVALID;
}
lv_freetype_context_t * ctx = lv_freetype_get_context();
ctx->max_glyph_cnt = max_glyph_cnt;
FT_Error error;
error = FT_Init_FreeType(&ctx->library);
if(error) {
FT_ERROR_MSG("FT_Init_FreeType", error);
return LV_RESULT_INVALID;
}
lv_ll_init(&ctx->face_id_ll, sizeof(face_id_node_t));
lv_cache_ops_t ops = {
.compare_cb = (lv_cache_compare_cb_t)cache_node_cache_compare_cb,
.create_cb = (lv_cache_create_cb_t)cache_node_cache_create_cb,
.free_cb = (lv_cache_free_cb_t)cache_node_cache_free_cb,
};
ctx->cache_node_cache = lv_cache_create(&lv_cache_class_lru_rb_count, sizeof(lv_freetype_cache_node_t), INT32_MAX, ops);
lv_cache_set_name(ctx->cache_node_cache, "FREETYPE_CACHE_NODE");
return LV_RESULT_OK;
}
void lv_freetype_uninit(void)
{
lv_freetype_context_t * ctx = lv_freetype_get_context();
if(!ctx) {
return;
}
lv_freetype_cleanup(ctx);
lv_free(ft_ctx);
ft_ctx = NULL;
}
void lv_freetype_init_font_info(lv_font_info_t * font_info)
{
LV_ASSERT_NULL(font_info);
lv_memzero(font_info, sizeof(lv_font_info_t));
font_info->class_p = &lv_freetype_font_class;
font_info->render_mode = LV_FREETYPE_FONT_RENDER_MODE_BITMAP;
font_info->style = LV_FREETYPE_FONT_STYLE_NORMAL;
font_info->kerning = LV_FONT_KERNING_NONE;
}
lv_font_t * lv_freetype_font_create_with_info(const lv_font_info_t * font_info)
{
LV_ASSERT_NULL(font_info);
if(font_info->size == 0) {
LV_LOG_ERROR("font size can't be zero");
return NULL;
}
const char * pathname = font_info->name;
size_t pathname_len = pathname ? lv_strlen(pathname) : 0;
if(pathname_len == 0) {
LV_LOG_ERROR("font pathname can't be empty");
return NULL;
}
lv_freetype_context_t * ctx = lv_freetype_get_context();
lv_freetype_cache_node_t search_key = {
.pathname = lv_freetype_req_face_id(ctx, pathname),
.style = font_info->style,
.render_mode = font_info->render_mode,
};
bool cache_hitting = true;
lv_cache_entry_t * cache_node_entry = lv_cache_acquire(ctx->cache_node_cache, &search_key, NULL);
if(cache_node_entry == NULL) {
cache_hitting = false;
cache_node_entry = lv_cache_acquire_or_create(ctx->cache_node_cache, &search_key, NULL);
if(cache_node_entry == NULL) {
lv_freetype_drop_face_id(ctx, (FTC_FaceID)search_key.pathname);
LV_LOG_ERROR("cache node creating failed");
return NULL;
}
}
lv_freetype_font_dsc_t * dsc = lv_malloc_zeroed(sizeof(lv_freetype_font_dsc_t));
LV_ASSERT_MALLOC(dsc);
dsc->face_id = (FTC_FaceID)search_key.pathname;
dsc->render_mode = font_info->render_mode;
dsc->context = ctx;
dsc->size = font_info->size;
dsc->style = font_info->style;
dsc->kerning = font_info->kerning;
dsc->magic_num = LV_FREETYPE_FONT_DSC_MAGIC_NUM;
dsc->cache_node = lv_cache_entry_get_data(cache_node_entry);
dsc->cache_node_entry = cache_node_entry;
if(cache_hitting == false && freetype_on_font_create(dsc, ctx->max_glyph_cnt) == false) {
lv_cache_release(ctx->cache_node_cache, dsc->cache_node_entry, NULL);
lv_freetype_drop_face_id(ctx, dsc->face_id);
lv_free(dsc);
return NULL;
}
freetype_on_font_set_cbs(dsc);
FT_Face face = dsc->cache_node->face;
FT_Error error;
if(FT_IS_SCALABLE(face)) {
error = FT_Set_Pixel_Sizes(face, 0, font_info->size);
}
else {
LV_LOG_WARN("font is not scalable, selecting available size");
error = FT_Select_Size(face, 0);
}
if(error) {
FT_ERROR_MSG("FT_Set_Pixel_Sizes", error);
return NULL;
}
if(dsc->kerning != LV_FONT_KERNING_NONE && !dsc->cache_node->face_has_kerning) {
LV_LOG_WARN("font: '%s' doesn't have kerning info", pathname);
}
lv_font_t * font = &dsc->font;
font->dsc = dsc;
font->subpx = LV_FONT_SUBPX_NONE;
font->line_height = FT_F26DOT6_TO_INT(face->size->metrics.height);
font->base_line = -FT_F26DOT6_TO_INT(face->size->metrics.descender);
FT_Fixed scale = face->size->metrics.y_scale;
int8_t thickness = FT_F26DOT6_TO_INT(FT_MulFix(scale, face->underline_thickness));
font->underline_position = FT_F26DOT6_TO_INT(FT_MulFix(scale, face->underline_position));
font->underline_thickness = thickness < 1 ? 1 : thickness;
return font;
}
lv_font_t * lv_freetype_font_create(const char * pathname, lv_freetype_font_render_mode_t render_mode, uint32_t size,
lv_freetype_font_style_t style)
{
lv_font_info_t font_info;
lv_freetype_init_font_info(&font_info);
font_info.name = pathname;
font_info.size = size;
font_info.render_mode = render_mode;
font_info.style = style;
return lv_freetype_font_create_with_info(&font_info);
}
void lv_freetype_font_delete(lv_font_t * font)
{
LV_ASSERT_NULL(font);
lv_freetype_context_t * ctx = lv_freetype_get_context();
if(!ctx) {
/* Freetype already torn down (e.g. static destruction order). Nothing to release. */
return;
}
lv_freetype_font_dsc_t * dsc = (lv_freetype_font_dsc_t *)(font->dsc);
LV_ASSERT_FREETYPE_FONT_DSC(dsc);
lv_cache_release(ctx->cache_node_cache, dsc->cache_node_entry, NULL);
if(lv_cache_entry_get_ref(dsc->cache_node_entry) == 0) {
lv_cache_drop(ctx->cache_node_cache, dsc->cache_node, NULL);
}
lv_freetype_drop_face_id(dsc->context, dsc->face_id);
/* invalidate magic number */
lv_memzero(dsc, sizeof(lv_freetype_font_dsc_t));
lv_free(dsc);
}
lv_freetype_context_t * lv_freetype_get_context(void)
{
return LV_GLOBAL_DEFAULT()->ft_context;
}
void lv_freetype_italic_transform(FT_Face face)
{
LV_ASSERT_NULL(face);
FT_Matrix matrix;
matrix.xx = FT_INT_TO_F16DOT16(1);
matrix.xy = LV_FREETYPE_OBLIQUE_SLANT_DEF;
matrix.yx = 0;
matrix.yy = FT_INT_TO_F16DOT16(1);
FT_Set_Transform(face, &matrix, NULL);
}
int32_t lv_freetype_italic_transform_on_pos(lv_point_t point)
{
return point.x + FT_F16DOT16_TO_INT(point.y * LV_FREETYPE_OBLIQUE_SLANT_DEF);
}
/**********************
* STATIC FUNCTIONS
**********************/
static bool freetype_on_font_create(lv_freetype_font_dsc_t * dsc, uint32_t max_glyph_cnt)
{
/*
* Glyph info uses a small amount of memory, and uses glyph info more frequently,
* so it plans to use twice the maximum number of caches here to
* get a better info acquisition performance.*/
lv_cache_t * glyph_cache = lv_freetype_create_glyph_cache(max_glyph_cnt * 2);
if(glyph_cache == NULL) {
LV_LOG_ERROR("glyph cache creating failed");
return false;
}
dsc->cache_node->glyph_cache = glyph_cache;
lv_cache_t * draw_data_cache = NULL;
if(dsc->render_mode == LV_FREETYPE_FONT_RENDER_MODE_BITMAP) {
draw_data_cache = lv_freetype_create_draw_data_image(max_glyph_cnt);
}
else if(dsc->render_mode == LV_FREETYPE_FONT_RENDER_MODE_OUTLINE) {
draw_data_cache = lv_freetype_create_draw_data_outline(max_glyph_cnt);
}
else {
LV_LOG_ERROR("unknown render mode");
return false;
}
if(draw_data_cache == NULL) {
LV_LOG_ERROR("draw data cache creating failed");
return false;
}
dsc->cache_node->draw_data_cache = draw_data_cache;
return true;
}
static void freetype_on_font_set_cbs(lv_freetype_font_dsc_t * dsc)
{
lv_freetype_set_cbs_glyph(dsc);
if(dsc->render_mode == LV_FREETYPE_FONT_RENDER_MODE_BITMAP) {
lv_freetype_set_cbs_image_font(dsc);
}
else if(dsc->render_mode == LV_FREETYPE_FONT_RENDER_MODE_OUTLINE) {
lv_freetype_set_cbs_outline_font(dsc);
}
}
static void lv_freetype_cleanup(lv_freetype_context_t * ctx)
{
LV_ASSERT_NULL(ctx);
if(ctx->cache_node_cache) {
lv_cache_destroy(ctx->cache_node_cache, NULL);
ctx->cache_node_cache = NULL;
}
if(ctx->library) {
FT_Done_FreeType(ctx->library);
ctx->library = NULL;
}
}
static FTC_FaceID lv_freetype_req_face_id(lv_freetype_context_t * ctx, const char * pathname)
{
size_t len = lv_strlen(pathname);
LV_ASSERT(len > 0);
lv_ll_t * ll_p = &ctx->face_id_ll;
face_id_node_t * node;
/* search cache */
LV_LL_READ(ll_p, node) {
if(strcmp(node->pathname, pathname) == 0) {
node->ref_cnt++;
LV_LOG_INFO("reuse face_id: %s, ref_cnt = %d", node->pathname, node->ref_cnt);
return node->pathname;
}
}
/* insert new cache */
node = lv_ll_ins_tail(ll_p);
LV_ASSERT_MALLOC(node);
#if LV_USE_FS_MEMFS
if(pathname[0] == LV_FS_MEMFS_LETTER) {
#if !LV_FREETYPE_USE_LVGL_PORT
LV_LOG_WARN("LV_FREETYPE_USE_LVGL_PORT is not enabled");
#endif
node->pathname = lv_malloc(sizeof(lv_fs_path_ex_t));
LV_ASSERT_MALLOC(node->pathname);
lv_memcpy(node->pathname, pathname, sizeof(lv_fs_path_ex_t));
}
else
#endif
{
node->pathname = lv_strdup(pathname);
LV_ASSERT_NULL(node->pathname);
}
LV_LOG_INFO("add face_id: %s", node->pathname);
node->ref_cnt = 1;
return node->pathname;
}
static void lv_freetype_drop_face_id(lv_freetype_context_t * ctx, FTC_FaceID face_id)
{
lv_ll_t * ll_p = &ctx->face_id_ll;
face_id_node_t * node;
LV_LL_READ(ll_p, node) {
if(face_id == node->pathname) {
LV_LOG_INFO("found face_id: %s, ref_cnt = %d", node->pathname, node->ref_cnt);
node->ref_cnt--;
if(node->ref_cnt == 0) {
LV_LOG_INFO("drop face_id: %s", node->pathname);
lv_ll_remove(ll_p, node);
lv_free(node->pathname);
lv_free(node);
}
return;
}
}
LV_ASSERT_MSG(false, "face_id not found");
}
/*-----------------
* Cache Node Cache Callbacks
*----------------*/
static bool cache_node_cache_create_cb(lv_freetype_cache_node_t * node, void * user_data)
{
LV_UNUSED(user_data);
lv_freetype_context_t * ctx = lv_freetype_get_context();
/* Cache miss, load face */
FT_Face face;
FT_Error error = FT_New_Face(ctx->library, node->pathname, 0, &face);
if(error) {
FT_ERROR_MSG("FT_New_Face", error);
return false;
}
node->ref_size = LV_FREETYPE_OUTLINE_REF_SIZE_DEF;
if(node->style & LV_FREETYPE_FONT_STYLE_ITALIC) {
lv_freetype_italic_transform(face);
}
node->face = face;
node->face_has_kerning = FT_HAS_KERNING(face);
lv_mutex_init(&node->face_lock);
return true;
}
static void cache_node_cache_free_cb(lv_freetype_cache_node_t * node, void * user_data)
{
FT_Done_Face(node->face);
lv_mutex_delete(&node->face_lock);
if(node->glyph_cache) {
lv_cache_destroy(node->glyph_cache, user_data);
node->glyph_cache = NULL;
}
if(node->draw_data_cache) {
lv_cache_destroy(node->draw_data_cache, user_data);
node->draw_data_cache = NULL;
}
}
static lv_cache_compare_res_t cache_node_cache_compare_cb(const lv_freetype_cache_node_t * lhs,
const lv_freetype_cache_node_t * rhs)
{
if(lhs->render_mode != rhs->render_mode) {
return lhs->render_mode > rhs->render_mode ? 1 : -1;
}
if(lhs->style != rhs->style) {
return lhs->style > rhs->style ? 1 : -1;
}
int32_t cmp_res = lv_strcmp(lhs->pathname, rhs->pathname);
if(cmp_res != 0) {
return cmp_res > 0 ? 1 : -1;
}
return 0;
}
static lv_font_t * freetype_font_create_cb(const lv_font_info_t * info, const void * src)
{
lv_font_info_t font_info = *info;
font_info.name = src;
return lv_freetype_font_create_with_info(&font_info);
}
static void freetype_font_delete_cb(lv_font_t * font)
{
lv_freetype_font_delete(font);
}
static void * freetype_font_dup_src_cb(const void * src)
{
return lv_strdup(src);
}
static void freetype_font_free_src_cb(void * src)
{
lv_free(src);
}
#endif /*LV_USE_FREETYPE*/

View File

@@ -0,0 +1,149 @@
/**
* @file lv_freetype.h
*
*/
#ifndef LV_FREETYPE_H
#define LV_FREETYPE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
#if LV_USE_FREETYPE
#include "../../misc/lv_types.h"
#include "../../misc/lv_event.h"
#include "../../misc/lv_color.h"
#include LV_STDBOOL_INCLUDE
/*********************
* DEFINES
*********************/
#define LV_FREETYPE_F26DOT6_TO_INT(x) ((x) >> 6)
#define LV_FREETYPE_F26DOT6_TO_FLOAT(x) ((float)(x) / 64)
#define FT_FONT_STYLE_NORMAL LV_FREETYPE_FONT_STYLE_NORMAL
#define FT_FONT_STYLE_ITALIC LV_FREETYPE_FONT_STYLE_ITALIC
#define FT_FONT_STYLE_BOLD LV_FREETYPE_FONT_STYLE_BOLD
/**********************
* TYPEDEFS
**********************/
typedef enum {
LV_FREETYPE_FONT_STYLE_NORMAL = 0,
LV_FREETYPE_FONT_STYLE_ITALIC = 1 << 0,
LV_FREETYPE_FONT_STYLE_BOLD = 1 << 1,
} lv_freetype_font_style_t;
typedef lv_freetype_font_style_t LV_FT_FONT_STYLE;
typedef enum {
LV_FREETYPE_FONT_RENDER_MODE_BITMAP = 0,
LV_FREETYPE_FONT_RENDER_MODE_OUTLINE = 1,
} lv_freetype_font_render_mode_t;
typedef void * lv_freetype_outline_t;
typedef enum {
LV_FREETYPE_OUTLINE_END,
LV_FREETYPE_OUTLINE_MOVE_TO,
LV_FREETYPE_OUTLINE_LINE_TO,
LV_FREETYPE_OUTLINE_CUBIC_TO,
LV_FREETYPE_OUTLINE_CONIC_TO,
LV_FREETYPE_OUTLINE_BORDER_START, /* When line width > 0 the border glyph is drawn after the regular glyph */
} lv_freetype_outline_type_t;
/* Only path string is required */
typedef const char lv_freetype_font_src_t;
LV_ATTRIBUTE_EXTERN_DATA extern const lv_font_class_t lv_freetype_font_class;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Initialize the freetype library.
* @return LV_RESULT_OK on success, otherwise LV_RESULT_INVALID.
*/
lv_result_t lv_freetype_init(uint32_t max_glyph_cnt);
/**
* Uninitialize the freetype library
*/
void lv_freetype_uninit(void);
/**
* Initialize a font info structure.
* @param font_info font info structure to be initialized.
*/
void lv_freetype_init_font_info(lv_font_info_t * font_info);
/**
* Create a freetype font with a font info structure.
* @param font_info font info structure.
* @return Created font, or NULL on failure.
*/
lv_font_t * lv_freetype_font_create_with_info(const lv_font_info_t * font_info);
/**
* Create a freetype font.
* @param pathname font file path.
* @param render_mode font render mode(see @lv_freetype_font_render_mode_t for details).
* @param size font size.
* @param style font style(see lv_freetype_font_style_t for details).
* @return Created font, or NULL on failure.
*/
lv_font_t * lv_freetype_font_create(const char * pathname, lv_freetype_font_render_mode_t render_mode, uint32_t size,
lv_freetype_font_style_t style);
/**
* Delete a freetype font.
* @param font freetype font to be deleted.
*/
void lv_freetype_font_delete(lv_font_t * font);
/**
* Register a callback function to generate outlines for FreeType fonts.
*
* @param cb The callback function to be registered.
* @param user_data User data to be passed to the callback function.
* @return The ID of the registered callback function, or a negative value on failure.
*/
void lv_freetype_outline_add_event(lv_event_cb_t event_cb, lv_event_code_t filter, void * user_data);
/**
* Get the scale of a FreeType font.
*
* @param font The FreeType font to get the scale of.
* @return The scale of the FreeType font.
*/
uint32_t lv_freetype_outline_get_scale(const lv_font_t * font);
/**
* Check if the font is an outline font.
*
* @param font The FreeType font.
* @return Is outline font on success, otherwise false.
*/
bool lv_freetype_is_outline_font(const lv_font_t * font);
/**********************
* MACROS
**********************/
#endif /*LV_USE_FREETYPE*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* LV_FREETYPE_H */

View File

@@ -0,0 +1,248 @@
/**
* @file lv_freetype_glyph.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../lvgl.h"
#include "lv_freetype_private.h"
#if LV_USE_FREETYPE
/*********************
* DEFINES
*********************/
#define CACHE_NAME "FREETYPE_GLYPH"
/**********************
* TYPEDEFS
**********************/
typedef struct _lv_freetype_glyph_cache_data_t {
uint32_t unicode;
uint32_t size;
lv_font_glyph_dsc_t glyph_dsc;
} lv_freetype_glyph_cache_data_t;
/**********************
* STATIC PROTOTYPES
**********************/
static bool freetype_get_glyph_dsc_cb(const lv_font_t * font, lv_font_glyph_dsc_t * g_dsc, uint32_t unicode_letter,
uint32_t unicode_letter_next);
static bool freetype_glyph_create_cb(lv_freetype_glyph_cache_data_t * data, void * user_data);
static void freetype_glyph_free_cb(lv_freetype_glyph_cache_data_t * data, void * user_data);
static lv_cache_compare_res_t freetype_glyph_compare_cb(const lv_freetype_glyph_cache_data_t * lhs,
const lv_freetype_glyph_cache_data_t * rhs);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_cache_t * lv_freetype_create_glyph_cache(uint32_t cache_size)
{
lv_cache_ops_t ops = {
.create_cb = (lv_cache_create_cb_t)freetype_glyph_create_cb,
.free_cb = (lv_cache_free_cb_t)freetype_glyph_free_cb,
.compare_cb = (lv_cache_compare_cb_t)freetype_glyph_compare_cb,
};
lv_cache_t * glyph_cache = lv_cache_create(&lv_cache_class_lru_rb_count, sizeof(lv_freetype_glyph_cache_data_t),
cache_size, ops);
lv_cache_set_name(glyph_cache, CACHE_NAME);
return glyph_cache;
}
void lv_freetype_set_cbs_glyph(lv_freetype_font_dsc_t * dsc)
{
LV_ASSERT_FREETYPE_FONT_DSC(dsc);
dsc->font.get_glyph_dsc = freetype_get_glyph_dsc_cb;
}
/**********************
* STATIC FUNCTIONS
**********************/
static bool freetype_get_glyph_dsc_cb(const lv_font_t * font, lv_font_glyph_dsc_t * g_dsc, uint32_t unicode_letter,
uint32_t unicode_letter_next)
{
LV_ASSERT_NULL(font);
LV_ASSERT_NULL(g_dsc);
LV_PROFILER_FONT_BEGIN;
if(unicode_letter < 0x20) {
g_dsc->adv_w = 0;
g_dsc->box_h = 0;
g_dsc->box_w = 0;
g_dsc->ofs_x = 0;
g_dsc->ofs_y = 0;
g_dsc->format = LV_FONT_GLYPH_FORMAT_NONE;
LV_PROFILER_FONT_END;
return true;
}
lv_freetype_font_dsc_t * dsc = (lv_freetype_font_dsc_t *)font->dsc;
LV_ASSERT_FREETYPE_FONT_DSC(dsc);
lv_freetype_glyph_cache_data_t search_key = {
.unicode = unicode_letter,
.size = dsc->size,
};
lv_cache_t * glyph_cache = dsc->cache_node->glyph_cache;
lv_cache_entry_t * entry = lv_cache_acquire_or_create(glyph_cache, &search_key, dsc);
if(entry == NULL) {
LV_LOG_ERROR("glyph lookup failed for unicode = 0x%" LV_PRIx32, unicode_letter);
LV_PROFILER_FONT_END;
return false;
}
lv_freetype_glyph_cache_data_t * data = lv_cache_entry_get_data(entry);
*g_dsc = data->glyph_dsc;
if((dsc->style & LV_FREETYPE_FONT_STYLE_ITALIC) && (unicode_letter_next == '\0')) {
g_dsc->adv_w = g_dsc->box_w + g_dsc->ofs_x;
}
if(dsc->kerning == LV_FONT_KERNING_NORMAL && dsc->cache_node->face_has_kerning && unicode_letter_next != '\0') {
lv_mutex_lock(&dsc->cache_node->face_lock);
FT_Face face = dsc->cache_node->face;
if(FT_IS_SCALABLE(face)) {
FT_Error set_size_error = FT_Set_Pixel_Sizes(face, 0, dsc->size);
if(set_size_error) {
FT_ERROR_MSG("FT_Set_Pixel_Sizes", set_size_error);
}
}
FT_UInt glyph_index_next = FT_Get_Char_Index(face, unicode_letter_next);
FT_Vector kerning;
FT_Error error = FT_Get_Kerning(face, g_dsc->gid.index, glyph_index_next, FT_KERNING_DEFAULT, &kerning);
if(!error) {
g_dsc->adv_w += LV_FREETYPE_F26DOT6_TO_INT(kerning.x);
}
else {
FT_ERROR_MSG("FT_Get_Kerning", error);
}
lv_mutex_unlock(&dsc->cache_node->face_lock);
}
g_dsc->entry = NULL;
lv_cache_release(glyph_cache, entry, NULL);
LV_PROFILER_FONT_END;
return true;
}
/*-----------------
* Cache Callbacks
*----------------*/
static bool freetype_glyph_create_cb(lv_freetype_glyph_cache_data_t * data, void * user_data)
{
LV_PROFILER_FONT_BEGIN;
FT_Error error;
lv_freetype_font_dsc_t * dsc = (lv_freetype_font_dsc_t *)user_data;
lv_font_glyph_dsc_t * dsc_out = &data->glyph_dsc;
lv_mutex_lock(&dsc->cache_node->face_lock);
FT_Face face = dsc->cache_node->face;
FT_UInt glyph_index = FT_Get_Char_Index(face, data->unicode);
if(FT_IS_SCALABLE(face)) {
error = FT_Set_Pixel_Sizes(face, 0, dsc->size);
}
else {
error = FT_Select_Size(face, 0);
}
if(error) {
FT_ERROR_MSG("FT_Set_Pixel_Sizes", error);
lv_mutex_unlock(&dsc->cache_node->face_lock);
return false;
}
if(dsc->render_mode == LV_FREETYPE_FONT_RENDER_MODE_OUTLINE) {
error = FT_Load_Glyph(face, glyph_index, FT_LOAD_COMPUTE_METRICS | FT_LOAD_NO_BITMAP | FT_LOAD_NO_AUTOHINT);
}
else if(dsc->render_mode == LV_FREETYPE_FONT_RENDER_MODE_BITMAP) {
error = FT_Load_Glyph(face, glyph_index, FT_LOAD_COMPUTE_METRICS | FT_LOAD_NO_AUTOHINT);
}
if(error) {
FT_ERROR_MSG("FT_Load_Glyph", error);
lv_mutex_unlock(&dsc->cache_node->face_lock);
LV_PROFILER_FONT_END;
return false;
}
FT_GlyphSlot glyph = face->glyph;
if(dsc->render_mode == LV_FREETYPE_FONT_RENDER_MODE_OUTLINE) {
dsc_out->adv_w = FT_F26DOT6_TO_INT(glyph->metrics.horiAdvance);
dsc_out->box_h = FT_F26DOT6_TO_INT(glyph->metrics.height); /*Height of the bitmap in [px]*/
dsc_out->box_w = FT_F26DOT6_TO_INT(glyph->metrics.width); /*Width of the bitmap in [px]*/
dsc_out->ofs_x = FT_F26DOT6_TO_INT(glyph->metrics.horiBearingX); /*X offset of the bitmap in [pf]*/
dsc_out->ofs_y = FT_F26DOT6_TO_INT(glyph->metrics.horiBearingY -
glyph->metrics.height); /*Y offset of the bitmap measured from the as line*/
dsc_out->format = LV_FONT_GLYPH_FORMAT_VECTOR;
/*Transform the glyph to italic if required */
if(dsc->style & LV_FREETYPE_FONT_STYLE_ITALIC) {
dsc_out->box_w = lv_freetype_italic_transform_on_pos((lv_point_t) {
dsc_out->box_w, dsc_out->box_h
});
}
}
else if(dsc->render_mode == LV_FREETYPE_FONT_RENDER_MODE_BITMAP) {
FT_Bitmap * glyph_bitmap = &face->glyph->bitmap;
dsc_out->adv_w = FT_F26DOT6_TO_INT(glyph->advance.x); /*Width of the glyph in [pf]*/
dsc_out->box_h = glyph_bitmap->rows; /*Height of the bitmap in [px]*/
dsc_out->box_w = glyph_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 -
dsc_out->box_h; /*Y offset of the bitmap measured from the as line*/
if(glyph->format == FT_GLYPH_FORMAT_BITMAP)
dsc_out->format = LV_FONT_GLYPH_FORMAT_IMAGE;
else
dsc_out->format = LV_FONT_GLYPH_FORMAT_A8;
}
dsc_out->is_placeholder = glyph_index == 0;
dsc_out->gid.index = (uint32_t)glyph_index;
lv_mutex_unlock(&dsc->cache_node->face_lock);
LV_PROFILER_FONT_END;
return true;
}
static void freetype_glyph_free_cb(lv_freetype_glyph_cache_data_t * data, void * user_data)
{
LV_UNUSED(data);
LV_UNUSED(user_data);
}
static lv_cache_compare_res_t freetype_glyph_compare_cb(const lv_freetype_glyph_cache_data_t * lhs,
const lv_freetype_glyph_cache_data_t * rhs)
{
if(lhs->unicode != rhs->unicode) {
return lhs->unicode > rhs->unicode ? 1 : -1;
}
if(lhs->size != rhs->size) {
return lhs->size > rhs->size ? 1 : -1;
}
return 0;
}
#endif /*LV_USE_FREETYPE*/

View File

@@ -0,0 +1,226 @@
/**
* @file lv_freetype_image.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../lvgl.h"
#include "lv_freetype_private.h"
#if LV_USE_FREETYPE
#include "../../core/lv_global.h"
#define font_draw_buf_handlers &(LV_GLOBAL_DEFAULT()->font_draw_buf_handlers)
/*********************
* DEFINES
*********************/
#define CACHE_NAME "FREETYPE_IMAGE"
/**********************
* TYPEDEFS
**********************/
typedef struct _lv_freetype_image_cache_data_t {
FT_UInt glyph_index;
uint32_t size;
lv_draw_buf_t * draw_buf;
} lv_freetype_image_cache_data_t;
/**********************
* STATIC PROTOTYPES
**********************/
static const void * freetype_get_glyph_bitmap_cb(lv_font_glyph_dsc_t * g_dsc, lv_draw_buf_t * draw_buf);
static bool freetype_image_create_cb(lv_freetype_image_cache_data_t * data, void * user_data);
static void freetype_image_free_cb(lv_freetype_image_cache_data_t * node, void * user_data);
static lv_cache_compare_res_t freetype_image_compare_cb(const lv_freetype_image_cache_data_t * lhs,
const lv_freetype_image_cache_data_t * rhs);
static void freetype_image_release_cb(const lv_font_t * font, lv_font_glyph_dsc_t * g_dsc);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_cache_t * lv_freetype_create_draw_data_image(uint32_t cache_size)
{
lv_cache_ops_t ops = {
.compare_cb = (lv_cache_compare_cb_t)freetype_image_compare_cb,
.create_cb = (lv_cache_create_cb_t)freetype_image_create_cb,
.free_cb = (lv_cache_free_cb_t)freetype_image_free_cb,
};
lv_cache_t * draw_data_cache = lv_cache_create(&lv_cache_class_lru_rb_count, sizeof(lv_freetype_image_cache_data_t),
cache_size, ops);
lv_cache_set_name(draw_data_cache, CACHE_NAME);
return draw_data_cache;
}
void lv_freetype_set_cbs_image_font(lv_freetype_font_dsc_t * dsc)
{
LV_ASSERT_FREETYPE_FONT_DSC(dsc);
dsc->font.get_glyph_bitmap = freetype_get_glyph_bitmap_cb;
dsc->font.release_glyph = freetype_image_release_cb;
}
/**********************
* STATIC FUNCTIONS
**********************/
static const void * freetype_get_glyph_bitmap_cb(lv_font_glyph_dsc_t * g_dsc, lv_draw_buf_t * draw_buf)
{
LV_UNUSED(draw_buf);
LV_PROFILER_FONT_BEGIN;
const lv_font_t * font = g_dsc->resolved_font;
lv_freetype_font_dsc_t * dsc = (lv_freetype_font_dsc_t *)font->dsc;
LV_ASSERT_FREETYPE_FONT_DSC(dsc);
FT_UInt glyph_index = (FT_UInt)g_dsc->gid.index;
lv_cache_t * cache = dsc->cache_node->draw_data_cache;
lv_freetype_image_cache_data_t search_key = {
.glyph_index = glyph_index,
.size = dsc->size,
};
lv_cache_entry_t * entry = lv_cache_acquire_or_create(cache, &search_key, dsc);
if(entry == NULL) {
LV_LOG_ERROR("glyph bitmap lookup failed for glyph_index = 0x%" LV_PRIx32, (uint32_t)glyph_index);
LV_PROFILER_FONT_END;
return NULL;
}
g_dsc->entry = entry;
lv_freetype_image_cache_data_t * cache_node = lv_cache_entry_get_data(entry);
LV_PROFILER_FONT_END;
return cache_node->draw_buf;
}
static void freetype_image_release_cb(const lv_font_t * font, lv_font_glyph_dsc_t * g_dsc)
{
LV_ASSERT_NULL(font);
lv_freetype_font_dsc_t * dsc = (lv_freetype_font_dsc_t *)font->dsc;
lv_cache_release(dsc->cache_node->draw_data_cache, g_dsc->entry, NULL);
g_dsc->entry = NULL;
}
/*-----------------
* Cache Callbacks
*----------------*/
static bool freetype_image_create_cb(lv_freetype_image_cache_data_t * data, void * user_data)
{
LV_PROFILER_FONT_BEGIN;
lv_freetype_font_dsc_t * dsc = (lv_freetype_font_dsc_t *)user_data;
FT_Error error;
lv_mutex_lock(&dsc->cache_node->face_lock);
FT_Face face = dsc->cache_node->face;
if(FT_IS_SCALABLE(face)) {
error = FT_Set_Pixel_Sizes(face, 0, dsc->size);
}
else {
error = FT_Select_Size(face, 0);
}
if(error) {
FT_ERROR_MSG("FT_Set_Pixel_Sizes", error);
lv_mutex_unlock(&dsc->cache_node->face_lock);
return false;
}
error = FT_Load_Glyph(face, data->glyph_index,
FT_LOAD_COLOR | FT_LOAD_RENDER | FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_AUTOHINT);
if(error) {
FT_ERROR_MSG("FT_Load_Glyph", error);
lv_mutex_unlock(&dsc->cache_node->face_lock);
LV_PROFILER_FONT_END;
return false;
}
error = FT_Render_Glyph(face->glyph, FT_RENDER_MODE_NORMAL);
if(error) {
FT_ERROR_MSG("FT_Render_Glyph", error);
lv_mutex_unlock(&dsc->cache_node->face_lock);
LV_PROFILER_FONT_END;
return false;
}
FT_Glyph glyph;
error = FT_Get_Glyph(face->glyph, &glyph);
if(error) {
FT_ERROR_MSG("FT_Get_Glyph", error);
lv_mutex_unlock(&dsc->cache_node->face_lock);
LV_PROFILER_FONT_END;
return false;
}
FT_BitmapGlyph glyph_bitmap = (FT_BitmapGlyph)glyph;
uint16_t box_h = glyph_bitmap->bitmap.rows; /*Height of the bitmap in [px]*/
uint16_t box_w = glyph_bitmap->bitmap.width; /*Width of the bitmap in [px]*/
lv_color_format_t col_format;
if(glyph_bitmap->bitmap.pixel_mode == FT_PIXEL_MODE_BGRA) {
col_format = LV_COLOR_FORMAT_ARGB8888;
}
else {
col_format = LV_COLOR_FORMAT_A8;
}
uint32_t pitch = glyph_bitmap->bitmap.pitch;
uint32_t stride = lv_draw_buf_width_to_stride(box_w, col_format);
data->draw_buf = lv_draw_buf_create_ex(font_draw_buf_handlers, box_w, box_h, col_format, stride);
if(!data->draw_buf) {
LV_LOG_WARN("Could not create draw buffer");
FT_Done_Glyph(glyph);
LV_PROFILER_FONT_END;
return false;
}
lv_draw_buf_clear(data->draw_buf, NULL);
for(int y = 0; y < box_h; ++y) {
lv_memcpy((uint8_t *)(data->draw_buf->data) + y * stride, glyph_bitmap->bitmap.buffer + y * pitch,
pitch);
}
lv_draw_buf_flush_cache(data->draw_buf, NULL);
FT_Done_Glyph(glyph);
lv_mutex_unlock(&dsc->cache_node->face_lock);
LV_PROFILER_FONT_END;
return true;
}
static void freetype_image_free_cb(lv_freetype_image_cache_data_t * data, void * user_data)
{
LV_UNUSED(user_data);
lv_draw_buf_destroy(data->draw_buf);
}
static lv_cache_compare_res_t freetype_image_compare_cb(const lv_freetype_image_cache_data_t * lhs,
const lv_freetype_image_cache_data_t * rhs)
{
if(lhs->glyph_index != rhs->glyph_index) {
return lhs->glyph_index > rhs->glyph_index ? 1 : -1;
}
if(lhs->size != rhs->size) {
return lhs->size > rhs->size ? 1 : -1;
}
return 0;
}
#endif /*LV_USE_FREETYPE*/

View File

@@ -0,0 +1,481 @@
/**
* @file lv_freetype_outline.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../misc/lv_event_private.h"
#include "../../lvgl.h"
#include "lv_freetype_private.h"
#if LV_USE_FREETYPE
/*********************
* DEFINES
*********************/
#define CACHE_NAME "FREETYPE_OUTLINE"
/**********************
* TYPEDEFS
**********************/
typedef struct _lv_freetype_outline_node_t {
FT_UInt glyph_index;
lv_freetype_outline_t outline;
} lv_freetype_outline_node_t;
/**********************
* STATIC PROTOTYPES
**********************/
static lv_freetype_outline_t outline_create(lv_freetype_context_t * ctx, FT_Face face, FT_UInt glyph_index,
uint32_t size, uint32_t strength, uint32_t border_width);
static lv_result_t outline_delete(lv_freetype_context_t * ctx, lv_freetype_outline_t outline);
static const void * freetype_get_glyph_bitmap_cb(lv_font_glyph_dsc_t * g_dsc, lv_draw_buf_t * draw_buf);
static void freetype_release_glyph_cb(const lv_font_t * font, lv_font_glyph_dsc_t * g_dsc);
static lv_cache_entry_t * lv_freetype_outline_lookup(lv_freetype_font_dsc_t * dsc, FT_UInt glyph_index);
/*glyph dsc cache lru callbacks*/
static bool freetype_glyph_outline_create_cb(lv_freetype_outline_node_t * node, lv_freetype_font_dsc_t * dsc);
static void freetype_glyph_outline_free_cb(lv_freetype_outline_node_t * node, lv_freetype_font_dsc_t * dsc);
static lv_cache_compare_res_t freetype_glyph_outline_cmp_cb(const lv_freetype_outline_node_t * node_a,
const lv_freetype_outline_node_t * node_b);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_cache_t * lv_freetype_create_draw_data_outline(uint32_t cache_size)
{
lv_cache_ops_t glyph_outline_cache_ops = {
.create_cb = (lv_cache_create_cb_t)freetype_glyph_outline_create_cb,
.free_cb = (lv_cache_free_cb_t)freetype_glyph_outline_free_cb,
.compare_cb = (lv_cache_compare_cb_t)freetype_glyph_outline_cmp_cb,
};
lv_cache_t * draw_data_cache = lv_cache_create(&lv_cache_class_lru_rb_count, sizeof(lv_freetype_outline_node_t),
cache_size,
glyph_outline_cache_ops);
lv_cache_set_name(draw_data_cache, CACHE_NAME);
return draw_data_cache;
}
void lv_freetype_set_cbs_outline_font(lv_freetype_font_dsc_t * dsc)
{
LV_ASSERT_FREETYPE_FONT_DSC(dsc);
dsc->font.get_glyph_bitmap = freetype_get_glyph_bitmap_cb;
dsc->font.release_glyph = freetype_release_glyph_cb;
}
void lv_freetype_outline_add_event(lv_event_cb_t event_cb, lv_event_code_t filter, void * user_data)
{
LV_UNUSED(user_data);
lv_freetype_context_t * ctx = lv_freetype_get_context();
LV_UNUSED(filter);
ctx->event_cb = event_cb;
}
uint32_t lv_freetype_outline_get_scale(const lv_font_t * font)
{
LV_ASSERT_NULL(font);
const lv_freetype_font_dsc_t * dsc = font->dsc;
LV_ASSERT_FREETYPE_FONT_DSC(dsc);
return FT_INT_TO_F26DOT6(dsc->size) / dsc->cache_node->ref_size;
}
bool lv_freetype_is_outline_font(const lv_font_t * font)
{
LV_ASSERT_NULL(font);
const lv_freetype_font_dsc_t * dsc = font->dsc;
if(!LV_FREETYPE_FONT_DSC_HAS_MAGIC_NUM(dsc)) {
return false;
}
return dsc->render_mode == LV_FREETYPE_FONT_RENDER_MODE_OUTLINE;
}
/**********************
* STATIC FUNCTIONS
**********************/
/*-------------------
* OUTLINE CACHE
*------------------*/
static bool freetype_glyph_outline_create_cb(lv_freetype_outline_node_t * node, lv_freetype_font_dsc_t * dsc)
{
LV_PROFILER_FONT_BEGIN;
lv_freetype_outline_t outline;
lv_mutex_lock(&dsc->cache_node->face_lock);
outline = outline_create(dsc->context,
dsc->cache_node->face,
node->glyph_index,
dsc->cache_node->ref_size,
dsc->style & LV_FREETYPE_FONT_STYLE_BOLD ? 1 : 0,
dsc->outline_stroke_width);
lv_mutex_unlock(&dsc->cache_node->face_lock);
if(!outline) {
LV_PROFILER_FONT_END;
return false;
}
LV_LOG_INFO("glyph_index = 0x%" LV_PRIx32, (uint32_t)node->glyph_index);
node->outline = outline;
LV_PROFILER_FONT_END;
return true;
}
static void freetype_glyph_outline_free_cb(lv_freetype_outline_node_t * node, lv_freetype_font_dsc_t * dsc)
{
LV_UNUSED(dsc);
lv_freetype_outline_t outline = node->outline;
lv_freetype_context_t * ctx = lv_freetype_get_context();
outline_delete(ctx, outline);
}
static lv_cache_compare_res_t freetype_glyph_outline_cmp_cb(const lv_freetype_outline_node_t * node_a,
const lv_freetype_outline_node_t * node_b)
{
if(node_a->glyph_index == node_b->glyph_index) {
return 0;
}
return node_a->glyph_index > node_b->glyph_index ? 1 : -1;
}
static const void * freetype_get_glyph_bitmap_cb(lv_font_glyph_dsc_t * g_dsc, lv_draw_buf_t * draw_buf)
{
LV_UNUSED(draw_buf);
const lv_font_t * font = g_dsc->resolved_font;
lv_freetype_font_dsc_t * dsc = (lv_freetype_font_dsc_t *)font->dsc;
LV_ASSERT_FREETYPE_FONT_DSC(dsc);
dsc->outline_stroke_width = g_dsc->outline_stroke_width;
lv_cache_entry_t * entry = lv_freetype_outline_lookup(dsc, (FT_UInt)g_dsc->gid.index);
if(entry == NULL) {
return NULL;
}
lv_freetype_outline_node_t * node = lv_cache_entry_get_data(entry);
g_dsc->entry = entry;
return node ? node->outline : NULL;
}
static void freetype_release_glyph_cb(const lv_font_t * font, lv_font_glyph_dsc_t * g_dsc)
{
LV_ASSERT_NULL(font);
lv_freetype_font_dsc_t * dsc = (lv_freetype_font_dsc_t *)font->dsc;
if(g_dsc->entry == NULL) {
return;
}
lv_cache_release(dsc->cache_node->draw_data_cache, g_dsc->entry, NULL);
g_dsc->entry = NULL;
}
static lv_cache_entry_t * lv_freetype_outline_lookup(lv_freetype_font_dsc_t * dsc, FT_UInt glyph_index)
{
LV_PROFILER_FONT_BEGIN;
lv_freetype_cache_node_t * cache_node = dsc->cache_node;
lv_freetype_outline_node_t tmp_node;
tmp_node.glyph_index = glyph_index;
lv_cache_entry_t * entry = lv_cache_acquire_or_create(cache_node->draw_data_cache, &tmp_node, dsc);
if(!entry) {
LV_LOG_ERROR("glyph outline lookup failed for glyph_index = 0x%" LV_PRIx32, (uint32_t)glyph_index);
LV_PROFILER_FONT_END;
return NULL;
}
LV_PROFILER_FONT_END;
return entry;
}
static void ft_vector_to_lv_vector(lv_freetype_outline_vector_t * dest, const FT_Vector * src)
{
dest->x = src ? src->x : 0;
dest->y = src ? src->y : 0;
}
static lv_result_t outline_send_event(lv_freetype_context_t * ctx, lv_event_code_t code,
lv_freetype_outline_event_param_t * param)
{
if(!ctx->event_cb) {
LV_LOG_ERROR("event_cb is not set");
return LV_RESULT_INVALID;
}
lv_event_t e;
lv_memzero(&e, sizeof(e));
e.code = code;
e.param = param;
e.user_data = NULL;
ctx->event_cb(&e);
return LV_RESULT_OK;
}
static lv_result_t outline_push_point(
lv_freetype_outline_t outline,
lv_freetype_outline_type_t type,
const FT_Vector * control1,
const FT_Vector * control2,
const FT_Vector * to)
{
LV_PROFILER_FONT_BEGIN;
lv_freetype_context_t * ctx = lv_freetype_get_context();
lv_freetype_outline_event_param_t param;
lv_memzero(&param, sizeof(param));
param.outline = outline;
param.type = type;
ft_vector_to_lv_vector(&param.control1, control1);
ft_vector_to_lv_vector(&param.control2, control2);
ft_vector_to_lv_vector(&param.to, to);
LV_PROFILER_FONT_END;
return outline_send_event(ctx, LV_EVENT_INSERT, &param);
}
static int outline_move_to_cb(
const FT_Vector * to,
void * user)
{
lv_freetype_outline_t outline = user;
outline_push_point(outline, LV_FREETYPE_OUTLINE_MOVE_TO, NULL, NULL, to);
return FT_Err_Ok;
}
static int outline_line_to_cb(
const FT_Vector * to,
void * user)
{
lv_freetype_outline_t outline = user;
outline_push_point(outline, LV_FREETYPE_OUTLINE_LINE_TO, NULL, NULL, to);
return FT_Err_Ok;
}
static int outline_conic_to_cb(
const FT_Vector * control,
const FT_Vector * to,
void * user)
{
lv_freetype_outline_t outline = user;
outline_push_point(outline, LV_FREETYPE_OUTLINE_CONIC_TO, control, NULL, to);
return FT_Err_Ok;
}
static int outline_cubic_to_cb(
const FT_Vector * control1,
const FT_Vector * control2,
const FT_Vector * to,
void * user)
{
lv_freetype_outline_t outline = user;
outline_push_point(outline, LV_FREETYPE_OUTLINE_CUBIC_TO, control1, control2, to);
return FT_Err_Ok;
}
static lv_freetype_outline_t outline_create(
lv_freetype_context_t * ctx,
FT_Face face,
FT_UInt glyph_index,
uint32_t size,
uint32_t strength,
uint32_t border_width)
{
LV_PROFILER_FONT_BEGIN;
LV_ASSERT_NULL(ctx);
FT_Error error;
FT_Glyph glyph;
FT_Stroker stroker;
error = FT_Set_Pixel_Sizes(face, 0, size);
if(error) {
FT_ERROR_MSG("FT_Set_Char_Size", error);
LV_PROFILER_FONT_END;
return NULL;
}
/**
* Disable AUTOHINT(https://freetype.org/autohinting/hinter.html) to avoid display clipping
* caused by inconsistent glyph measurement and outline.
*/
error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT | FT_LOAD_NO_BITMAP | FT_LOAD_NO_AUTOHINT);
if(error) {
FT_ERROR_MSG("FT_Load_Glyph", error);
LV_PROFILER_FONT_END;
return NULL;
}
if(strength > 0) {
error = FT_Outline_Embolden(&face->glyph->outline, FT_INT_TO_F26DOT6(strength));
if(error) {
FT_ERROR_MSG("FT_Outline_Embolden", error);
}
}
FT_Outline_Funcs outline_funcs = {
.move_to = outline_move_to_cb,
.line_to = outline_line_to_cb,
.conic_to = outline_conic_to_cb,
.cubic_to = outline_cubic_to_cb,
.shift = 0,
.delta = 0
};
lv_result_t res;
lv_freetype_outline_event_param_t param;
lv_memzero(&param, sizeof(param));
lv_freetype_outline_t outline;
res = outline_send_event(ctx, LV_EVENT_CREATE, &param);
outline = param.outline;
if(res != LV_RESULT_OK || !outline) {
LV_LOG_ERROR("Outline object create failed");
LV_PROFILER_FONT_END;
return NULL;
}
/* 1 iteration if there is no border */
/* 2 iterations if there is a a border and the glyph itsef */
for(int i = 0; i < (border_width > 0 ? 2 : 1); i++) {
FT_Outline glyph_outline;
if(i == 1) {
/* decompose the border glyph */
FT_Stroker_New(ctx->library, &stroker);
FT_Stroker_Set(stroker, border_width * 64,
FT_STROKER_LINECAP_ROUND,
FT_STROKER_LINEJOIN_ROUND,
0);
FT_Get_Glyph(face->glyph, &glyph);
FT_Glyph_StrokeBorder(&glyph, stroker, 0, true);
FT_OutlineGlyph g = (FT_OutlineGlyph) glyph;
FT_Stroker_Done(stroker);
glyph_outline = g->outline;
}
else {
/* decompose glyph */
glyph_outline = face->glyph->outline;
}
/*Calculate Total Segments Before decompose */
int32_t tag_size = glyph_outline.n_points;
int32_t segments = 0;
int32_t vectors = 0;
for(int j = 0; j < tag_size; j++) {
#if 0
if(j == 0 && (glyph_outline.tags[j] & 0x1) == 0) {
/* TODO handle the case where the first point is 'off curve' */
https://stackoverflow.com/questions/3465809/how-to-interpret-a-freetype-glyph-outline-when-the-first-point-on-the-contour-is
}
#endif
if((glyph_outline.tags[j] & 0x1) == 0x1) {
segments++;
vectors++;
}
else {
int jj = j + 1 < tag_size ? j + 1 : 0;
if(glyph_outline.tags[jj] & 0x1) {
vectors++;
}
else {
segments++;
vectors += 2;
}
}
}
/*Also for every contour we may have a line for close*/
segments += glyph_outline.n_contours;
vectors += glyph_outline.n_contours;
param.sizes.data_size = vectors * 2;
param.sizes.segments_size = segments;
/* Run outline decompose again to fill outline data */
error = FT_Outline_Decompose(&glyph_outline, &outline_funcs, outline);
if(error) {
FT_ERROR_MSG("FT_Outline_Decompose", error);
outline_delete(ctx, outline);
LV_PROFILER_FONT_END;
return NULL;
}
if(i == 0 && border_width > 0) {
/* Close the border glyph before decomposing the inside glyph */
res = outline_push_point(outline, LV_FREETYPE_OUTLINE_BORDER_START, NULL, NULL, NULL);
if(res != LV_RESULT_OK) {
LV_LOG_ERROR("Outline object close failed");
outline_delete(ctx, outline);
LV_PROFILER_FONT_END;
return NULL;
}
}
else if(i == 0 || (i == 1 && border_width > 0)) {
/* Close the border glyph or the regular glyph */
res = outline_push_point(outline, LV_FREETYPE_OUTLINE_END, NULL, NULL, NULL);
if(res != LV_RESULT_OK) {
LV_LOG_ERROR("Outline object close failed");
outline_delete(ctx, outline);
LV_PROFILER_FONT_END;
return NULL;
}
}
}
LV_PROFILER_FONT_END;
return outline;
}
static lv_result_t outline_delete(lv_freetype_context_t * ctx, lv_freetype_outline_t outline)
{
lv_freetype_outline_event_param_t param;
lv_memzero(&param, sizeof(param));
param.outline = outline;
return outline_send_event(ctx, LV_EVENT_DELETE, &param);
}
#endif /*LV_USE_FREETYPE*/

View File

@@ -0,0 +1,161 @@
/**
* @file lv_freetype_private.h
*
*/
#ifndef LV_FREETYPE_PRIVATE_H
#define LV_FREETYPE_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "lv_freetype.h"
#if LV_USE_FREETYPE
#include "../../misc/cache/lv_cache.h"
#include "../../misc/lv_ll.h"
#include "../../font/lv_font.h"
#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
#include FT_STROKER_H
/*********************
* DEFINES
*********************/
#ifdef FT_CONFIG_OPTION_ERROR_STRINGS
#define FT_ERROR_MSG(msg, error_code) \
LV_LOG_ERROR(msg " error(0x%x): %s", (int)error_code, FT_Error_String(error_code))
#else
#define FT_ERROR_MSG(msg, error_code) \
LV_LOG_ERROR(msg " error(0x%x)", (int)error_code)
#endif
#define LV_FREETYPE_FONT_DSC_MAGIC_NUM 0x5F5F4654 /* '__FT' */
#define LV_FREETYPE_FONT_DSC_HAS_MAGIC_NUM(dsc) ((dsc)->magic_num == LV_FREETYPE_FONT_DSC_MAGIC_NUM)
#define LV_ASSERT_FREETYPE_FONT_DSC(dsc) \
do { \
LV_ASSERT_NULL(dsc); \
LV_ASSERT_FORMAT_MSG(LV_FREETYPE_FONT_DSC_HAS_MAGIC_NUM(dsc), \
"Invalid font descriptor: 0x%" LV_PRIx32, (dsc)->magic_num); \
} while (0)
#define FT_INT_TO_F26DOT6(x) ((x) << 6)
#define FT_F26DOT6_TO_INT(x) ((x) >> 6)
#define FT_INT_TO_F16DOT16(x) ((x) << 16)
#define FT_F16DOT16_TO_INT(x) ((x) >> 16)
/**********************
* TYPEDEFS
**********************/
struct _lv_freetype_outline_vector_t {
int32_t x;
int32_t y;
};
typedef struct {
int32_t segments_size;
int32_t data_size;
} lv_freetype_outline_sizes_t;
struct _lv_freetype_outline_event_param_t {
lv_freetype_outline_t outline;
lv_freetype_outline_type_t type;
lv_freetype_outline_vector_t to;
lv_freetype_outline_vector_t control1;
lv_freetype_outline_vector_t control2;
lv_freetype_outline_sizes_t sizes;
};
typedef struct _lv_freetype_cache_node_t lv_freetype_cache_node_t;
struct _lv_freetype_cache_node_t {
const char * pathname;
lv_freetype_font_style_t style;
lv_freetype_font_render_mode_t render_mode;
uint32_t ref_size; /**< Reference size for calculating outline glyph's real size.*/
FT_Face face;
lv_mutex_t face_lock;
bool face_has_kerning;
/*glyph cache*/
lv_cache_t * glyph_cache;
/*draw data cache*/
lv_cache_t * draw_data_cache;
};
typedef struct _lv_freetype_context_t {
FT_Library library;
lv_ll_t face_id_ll;
lv_event_cb_t event_cb;
uint32_t max_glyph_cnt;
lv_cache_t * cache_node_cache;
} lv_freetype_context_t;
typedef struct _lv_freetype_font_dsc_t {
uint32_t magic_num;
lv_font_t font;
uint32_t size;
lv_freetype_font_style_t style;
lv_freetype_font_render_mode_t render_mode;
lv_freetype_context_t * context;
lv_freetype_cache_node_t * cache_node;
lv_cache_entry_t * cache_node_entry;
FTC_FaceID face_id;
uint32_t outline_stroke_width;
lv_font_kerning_t kerning;
} lv_freetype_font_dsc_t;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Get the FreeType context.
*
* @return A pointer to the FreeType context used by LittlevGL.
*/
lv_freetype_context_t * lv_freetype_get_context(void);
void lv_freetype_italic_transform(FT_Face face);
int32_t lv_freetype_italic_transform_on_pos(lv_point_t point);
lv_cache_t * lv_freetype_create_glyph_cache(uint32_t cache_size);
void lv_freetype_set_cbs_glyph(lv_freetype_font_dsc_t * dsc);
lv_cache_t * lv_freetype_create_draw_data_image(uint32_t cache_size);
void lv_freetype_set_cbs_image_font(lv_freetype_font_dsc_t * dsc);
lv_cache_t * lv_freetype_create_draw_data_outline(uint32_t cache_size);
void lv_freetype_set_cbs_outline_font(lv_freetype_font_dsc_t * dsc);
/**********************
* MACROS
**********************/
#endif /*LV_USE_FREETYPE*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_FREETYPE_PRIVATE_H*/

View File

@@ -0,0 +1,291 @@
/**
* @file lv_ftsystem.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../../lvgl.h"
#if LV_USE_FREETYPE && LV_FREETYPE_USE_LVGL_PORT
#include <ft2build.h>
#include FT_CONFIG_CONFIG_H
#include <freetype/internal/ftdebug.h>
#include <freetype/internal/ftstream.h>
#include <freetype/ftsystem.h>
#include <freetype/fterrors.h>
#include <freetype/fttypes.h>
/*********************
* DEFINES
*********************/
/* The macro FT_COMPONENT is used in trace mode. It is an implicit
* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log
* messages during execution.
*/
#undef FT_COMPONENT
#define FT_COMPONENT io
/* We use the macro STREAM_FILE for convenience to extract the */
/* system-specific stream handle from a given FreeType stream object */
#define STREAM_FILE( stream ) ( (lv_fs_file_t*)stream->descriptor.pointer )
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
FT_CALLBACK_DEF(unsigned long)
ft_lv_fs_stream_io(FT_Stream stream,
unsigned long offset,
unsigned char * buffer,
unsigned long count);
FT_CALLBACK_DEF(void)
ft_lv_fs_stream_close(FT_Stream stream);
FT_CALLBACK_DEF(void *)
ft_alloc(FT_Memory memory,
long size);
FT_CALLBACK_DEF(void *)
ft_realloc(FT_Memory memory,
long cur_size,
long new_size,
void * block);
FT_CALLBACK_DEF(void)
ft_free(FT_Memory memory,
void * block);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
#ifdef FT_DEBUG_MEMORY
extern FT_Int
ft_mem_debug_init(FT_Memory memory);
extern void
ft_mem_debug_done(FT_Memory memory);
#endif
/**********************
* GLOBAL FUNCTIONS
**********************/
#ifndef FT_CONFIG_OPTION_DISABLE_STREAM_SUPPORT
/* documentation is in ftstream.h */
FT_BASE_DEF(FT_Error)
FT_Stream_Open(FT_Stream stream,
const char * filepathname)
{
lv_fs_file_t file;
if(!stream)
return FT_THROW(Invalid_Stream_Handle);
stream->descriptor.pointer = NULL;
stream->pathname.pointer = (char *)filepathname;
stream->base = NULL;
stream->pos = 0;
stream->read = NULL;
stream->close = NULL;
lv_fs_res_t res = lv_fs_open(&file, filepathname, LV_FS_MODE_RD);
if(res != LV_FS_RES_OK) {
FT_ERROR(("FT_Stream_Open:"
" could not open `%s'\n", filepathname));
return FT_THROW(Cannot_Open_Resource);
}
lv_fs_seek(&file, 0, LV_FS_SEEK_END);
uint32_t pos;
res = lv_fs_tell(&file, &pos);
if(res != LV_FS_RES_OK) {
FT_ERROR(("FT_Stream_Open:"));
FT_ERROR((" opened `%s' but zero-sized\n", filepathname));
lv_fs_close(&file);
return FT_THROW(Cannot_Open_Stream);
}
stream->size = pos;
lv_fs_seek(&file, 0, LV_FS_SEEK_SET);
lv_fs_file_t * file_p = lv_malloc(sizeof(lv_fs_file_t));
LV_ASSERT_MALLOC(file_p);
if(!file_p) {
FT_ERROR(("FT_Stream_Open: malloc failed for file_p"));
lv_fs_close(&file);
return FT_THROW(Cannot_Open_Stream);
}
*file_p = file;
stream->descriptor.pointer = file_p;
stream->read = ft_lv_fs_stream_io;
stream->close = ft_lv_fs_stream_close;
FT_TRACE1(("FT_Stream_Open:"));
FT_TRACE1((" opened `%s' (%ld bytes) successfully\n",
filepathname, stream->size));
return FT_Err_Ok;
}
#endif /* !FT_CONFIG_OPTION_DISABLE_STREAM_SUPPORT */
/* documentation is in ftobjs.h */
FT_BASE_DEF(FT_Memory)
FT_New_Memory(void)
{
FT_Memory memory;
memory = (FT_Memory)lv_malloc(sizeof(*memory));
if(memory) {
memory->user = NULL;
memory->alloc = ft_alloc;
memory->realloc = ft_realloc;
memory->free = ft_free;
#ifdef FT_DEBUG_MEMORY
ft_mem_debug_init(memory);
#endif
}
return memory;
}
/* documentation is in ftobjs.h */
FT_BASE_DEF(void)
FT_Done_Memory(FT_Memory memory)
{
#ifdef FT_DEBUG_MEMORY
ft_mem_debug_done(memory);
#endif
lv_free(memory);
}
/**********************
* STATIC FUNCTIONS
**********************/
/**
* The memory allocation function.
* @param memory A pointer to the memory object.
* @param size The requested size in bytes.
* @return The address of newly allocated block.
*/
FT_CALLBACK_DEF(void *)
ft_alloc(FT_Memory memory,
long size)
{
FT_UNUSED(memory);
return lv_malloc((size_t)size);
}
/**
* The memory reallocation function.
* @param memory A pointer to the memory object.
* @param cur_size The current size of the allocated memory block.
* @param new_size The newly requested size in bytes.
* @param block The current address of the block in memory.
* @return The address of the reallocated memory block.
*/
FT_CALLBACK_DEF(void *)
ft_realloc(FT_Memory memory,
long cur_size,
long new_size,
void * block)
{
FT_UNUSED(memory);
FT_UNUSED(cur_size);
return lv_realloc(block, (size_t)new_size);
}
/**
* The memory release function.
* @param memory A pointer to the memory object.
* @param block The address of block in memory to be freed.
*/
FT_CALLBACK_DEF(void)
ft_free(FT_Memory memory,
void * block)
{
FT_UNUSED(memory);
lv_free(block);
}
#ifndef FT_CONFIG_OPTION_DISABLE_STREAM_SUPPORT
/**
* The function to close a stream.
* @param stream A pointer to the stream object.
*/
FT_CALLBACK_DEF(void)
ft_lv_fs_stream_close(FT_Stream stream)
{
lv_fs_file_t * file_p = STREAM_FILE(stream);
lv_fs_close(file_p);
lv_free(file_p);
stream->descriptor.pointer = NULL;
stream->size = 0;
stream->base = NULL;
}
/**
* The function to open a stream.
* @param stream A pointer to the stream object.
* @param offset The position in the data stream to start reading.
* @param buffer The address of buffer to store the read data.
* @param count The number of bytes to read from the stream.
* @return The number of bytes actually read. If `count' is zero (this is,
* the function is used for seeking), a non-zero return value
* indicates an error.
*/
FT_CALLBACK_DEF(unsigned long)
ft_lv_fs_stream_io(FT_Stream stream,
unsigned long offset,
unsigned char * buffer,
unsigned long count)
{
lv_fs_file_t * file_p;
if(!count && offset > stream->size)
return 1;
file_p = STREAM_FILE(stream);
if(stream->pos != offset)
lv_fs_seek(file_p, (long)offset, LV_FS_SEEK_SET);
if(count == 0)
return 0;
uint32_t br;
lv_fs_res_t res = lv_fs_read(file_p, buffer, count, &br);
return res == LV_FS_RES_OK ? br : 0;
}
#endif /* !FT_CONFIG_OPTION_DISABLE_STREAM_SUPPORT */
#endif/*LV_FREETYPE_USE_LV_FTSYSTEM*/

View File

@@ -0,0 +1,362 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.

View File

@@ -0,0 +1,260 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "../../../../lv_conf_internal.h"
#include LV_STDDEF_INCLUDE
#include LV_STDINT_INCLUDE
#include "frogfs_types.h"
/**
* \brief Magic number used in the frogfs file header
*/
#define FROGFS_MAGIC 0x474F5246 /** FROG */
/**
* \brief Major version this source distribution supports
*/
#define FROGFS_VER_MAJOR 1
/**
* \brief Minor version this source distribution supports
*/
#define FROGFS_VER_MINOR 0
/**
* \brief Flag for \a frogfs_open to open any file as raw. Useful to
* pass compressed data over a transport such as HTTP.
*/
#define FROGFS_OPEN_RAW (1 << 0)
/**
* \brief Enum of frogfs entry types
*/
typedef enum frogfs_entry_type_t {
FROGFS_ENTRY_TYPE_DIR,
FROGFS_ENTRY_TYPE_FILE,
} frogfs_entry_type_t;
/**
* \brief Compression algorithm ids
*/
typedef enum frogfs_comp_algo_t {
FROGFS_COMP_ALGO_NONE,
FROGFS_COMP_ALGO_ZLIB,
FROGFS_COMP_ALGO_HEATSHRINK,
FROGFS_COMP_ALGO_GZIP,
} frogfs_comp_algo_t;
/**
* \brief Configuration for the \a frogfs_init function
*/
typedef struct frogfs_config_t {
const void *addr; /**< address of an frogfs filesystem in memory */
} frogfs_config_t;
/**
* \brief A frogfs filesystem handle
*/
typedef struct frogfs_fs_t frogfs_fs_t;
/**
* \brief Structure filled by the \a frogfs_stat function
*/
typedef struct frogfs_stat_t {
frogfs_entry_type_t type; /**< entry type */
size_t size; /**< uncompressed file size */
frogfs_comp_algo_t compression; /**< compression type */
size_t compressed_sz; /**< compressed file size */
} frogfs_stat_t;
/**
* \brief Fiilesystem entry pointer
*/
typedef struct frogfs_entry_t frogfs_entry_t;
typedef struct frogfs_dh_t frogfs_dh_t;
typedef struct frogfs_fh_t frogfs_fh_t;
#if !defined(FROGFS_PRIVATE_STRUCTS)
/**
* \brief A frogfs directory handle
*/
struct frogfs_dh_t {
const frogfs_fs_t *fs; /**< filesystem handle */
frogfs_entry_t *entry; /**< directory entry */
};
/**
* \brief A frogfs file handle
*/
struct frogfs_fh_t {
const frogfs_fs_t *fs; /**< filesystem handle */
frogfs_entry_t *entry; /**< file entry */
};
#endif
/**
* \brief Initialize and return a \a frogfs_fs_t instance
* \param[in] config frogfs configuration
* \return \a frogfs_fs_t pointer or \a NULL on error
*/
frogfs_fs_t *frogfs_init(const frogfs_config_t *conf);
/**
* \brief Tear down a \a frogfs_fs_t instance
* \param[in] fs \a frogfs_fs_t pointer
*/
void frogfs_deinit(frogfs_fs_t *fs);
/**
* \brief Get frogfs entry for path
* \param[in] fs \a frogfs_fs_t pointer
* \param[in] path path string
* \return \a frogfs_entry_t pointer or \a NULL if path was not
* found
*/
const frogfs_entry_t *frogfs_get_entry(const frogfs_fs_t *fs,
const char *path);
/**
* \brief Get name for frogfs entry
* \param[in] entry \a frogfs_entry_t pointer
* \return name string, caller is expected to free
*/
char *frogfs_get_name(const frogfs_entry_t *entry);
/**
* \brief Get full path for frogfs entry
* \param[in] fs \a frogfs_fs_t pointer
* \param[in] entry \a frogfs_entry_t pointer
* \return full path string or \a NULL if entry is NULL, caller is
* expected to free
*/
char *frogfs_get_path(const frogfs_fs_t *fs, const frogfs_entry_t *entry);
/**
* \brief Return if entry is a directory
* \param[in] entry \a frogfs_entry_t pointer
* \return 1 if directory, 0 otherwise
*/
int frogfs_is_dir(const frogfs_entry_t *entry);
/**
* \brief Return if entry is a file
* \param[in] entry \a frogfs_entry_t pointer
* \return 1 if file, 0 otherwise
*/
int frogfs_is_file(const frogfs_entry_t *entry);
/**
* \brief Get information about a frogfs entry
* \param[in] fs \a frogfs_fs_t pointer
* \param[in] entry \a frogfs_entry_t pointer
* \param[out] st \a frogfs_stat_t structure
*/
void frogfs_stat(const frogfs_fs_t *fs, const frogfs_entry_t *entry,
frogfs_stat_t *st);
/**
* \brief Open a frogfs entry as a file from a \a frogfs_fs_t instance
* \param[in] fs \a frogfs_fs_t poitner
* \param[in] entry \a frogfs_entry_t pointer
* \param[in] flags open flags
* \return \a frogfs_fh_t or \a NULL if not found
*/
frogfs_fh_t *frogfs_open(const frogfs_fs_t *fs, const frogfs_entry_t *entry,
unsigned int flags);
/**
* \brief Close an open file entry
* \param[in] f \a frogfs_fh_t pointer
*/
void frogfs_close(frogfs_fh_t *fh);
/**
* \brief Determine if file handle is opened raw.
* \param[in] f \a frogfs_fh_t pointer
* \return 1 if file is open raw, 0 otherwise
*/
int frogfs_is_raw(frogfs_fh_t *fh);
/**
* \brief Read data from an open file entry
* \param[in] f \a frogfs_fh_t pointer
* \param[out] buf buffer to read into
* \param[in] len maximum number of bytes to read
* \return actual number of bytes read, zero if end of file
* reached
*/
ssize_t frogfs_read(frogfs_fh_t *fh, void *buf, size_t len);
/**
* \brief Seek to a position within an open file entry
* \param[in] f \a frogfs_fh_t pointer
* \param[in] offset file position (relative or absolute)
* \param[in] mode \a SEEK_SET, \a SEEK_CUR, or \a SEEK_END
* \return current position in file or < 0 upon error
*/
ssize_t frogfs_seek(frogfs_fh_t *fh, long offset, int mode);
/**
* \brief Get the current position in an open file entry
* \param[in] f \a frogfs_fh_t pointer
* \return current position in file or < 0 upon error
*/
size_t frogfs_tell(frogfs_fh_t *fh);
/**
* \brief Get raw memory for raw file entry
* \param[in] f \a frogfs_fh_t pointer
* \param[out] buf pointer pointer to buf
* \return length of raw data
*/
size_t frogfs_access(frogfs_fh_t *fh, const void **buf);
/**
* \brief Open a directory for reading child entrys
* \param[in] fs \a frogfs_fs_t pointer
* \param[in] entry \a frogfs_entry_t pointer to root director
* \return \a frogfs_dh_t pointer or \a NULL if invalid
*/
frogfs_dh_t *frogfs_opendir(frogfs_fs_t *fs, const frogfs_entry_t *entry);
/**
* \brief Close a directory
* \param[in] d \a frogfs_dh_t pointer
*/
void frogfs_closedir(frogfs_dh_t *dh);
/**
* \brief Get the next child entry in directory
* \param[in] d \a frogfs_dh_t pointer
* \return \a frogfs_entry_t pointer or \a NULL if end has been
* reached
*/
const frogfs_entry_t *frogfs_readdir(frogfs_dh_t *dh);
/**
* \brief Set dir entry index to a value returned by \a frogfs_telldir
* for the current \a frogfs_dh_t pointer
* \param[in] d \a frogfs_dh_t pointer
* \param[in] loc entry index
*/
void frogfs_seekdir(frogfs_dh_t *dh, long loc);
/**
* \brief Return the current entry index for a directory
* \param[in] d \a frogfs_dh_t pointer
* \return entry index
*/
long frogfs_telldir(frogfs_dh_t *dh);
#ifdef __cplusplus
} /* extern "C" */
#endif

View File

@@ -0,0 +1,6 @@
#pragma once
#include "../../../../lv_conf_internal.h"
#include LV_STDINT_INCLUDE
typedef intptr_t ssize_t;

View File

@@ -0,0 +1,77 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "../../../lv_conf_internal.h"
#include LV_STDDEF_INCLUDE
#include LV_STDINT_INCLUDE
#include "../../../stdlib/lv_string.h"
#include "../../../misc/lv_fs.h"
#include "frogfs_priv.h"
#include "frogfs_format.h"
#include "../include/frogfs/frogfs.h"
static ssize_t read_raw(frogfs_fh_t *f, void *buf, size_t len)
{
size_t remaining = f->data_sz - ((char *)f->data_ptr - (char *)f->data_start);
if (len > remaining) {
len = remaining;
}
if (buf) {
lv_memcpy(buf, f->data_ptr, len);
}
f->data_ptr = (char *)f->data_ptr + len;
return len;
}
static ssize_t seek_raw(frogfs_fh_t *f, long offset, int mode)
{
ssize_t new_pos = (char *)f->data_ptr - (char *)f->data_start;
if (mode == LV_FS_SEEK_SET) {
if (offset < 0) {
return -1;
}
if ((size_t)offset > f->data_sz) {
offset = f->data_sz;
}
new_pos = offset;
} else if (mode == LV_FS_SEEK_CUR) {
if (new_pos + offset < 0) {
new_pos = 0;
} else if ((size_t)new_pos > f->data_sz) {
new_pos = f->data_sz;
} else {
new_pos += offset;
}
} else if (mode == LV_FS_SEEK_END) {
if (offset > 0) {
return -1;
}
if (offset < -(ssize_t) f->data_sz) {
offset = 0;
}
new_pos = f->data_sz + offset;
} else {
return -1;
}
f->data_ptr = (char *)f->data_start + new_pos;
return new_pos;
}
static size_t tell_raw(frogfs_fh_t *f)
{
return (char *)f->data_ptr - (char *)f->data_start;
}
const frogfs_decomp_funcs_t frogfs_decomp_raw = {
.read = read_raw,
.seek = seek_raw,
.tell = tell_raw,
};

View File

@@ -0,0 +1,449 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This is a read-only filesystem that uses a sorted hash table to locate
* entries in a monolithic binary. The binary is generated by the mkfrogfs
* tool that comes with this source distribution.
*/
#include "../../../lv_conf_internal.h"
#if LV_USE_FS_FROGFS
#include LV_INTTYPES_INCLUDE
#include LV_LIMITS_INCLUDE
#include LV_STDINT_INCLUDE
#include "../../../stdlib/lv_string.h"
#include "../../../stdlib/lv_mem.h"
#include "../../../misc/lv_log.h"
#include "../../../misc/lv_assert.h"
#include "../../../misc/lv_fs.h"
#include "frogfs_priv.h"
#include "frogfs_format.h"
#include "../include/frogfs/frogfs.h"
typedef struct frogfs_fs_t {
const frogfs_head_t *head; /**< fs header pointer */
const frogfs_hash_t *hash; /**< hash table pointer */
const frogfs_dir_t *root; /**< root directory entry */
int num_entries; /**< total number of file system entries */
} frogfs_fs_t;
// Returns the current or next highest multiple of 4.
static inline size_t align(size_t n)
{
return ((n + 4 - 1) / 4) * 4;
}
// String hashing function.
static inline uint32_t djb2_hash(const char *s)
{
unsigned long hash = 5381;
while (*s) {
/* hash = hash * 33 ^ c */
hash = ((hash << 5) + hash) ^ *s++;
}
return hash;
}
static const char *get_name(const frogfs_entry_t *entry)
{
if (FROGFS_IS_DIR(entry)) {
return (const void *) entry + 8 + (entry->u.child_count * 4);
} else if (FROGFS_IS_FILE(entry) && !FROGFS_IS_COMP(entry)) {
return (const void *) entry + 16;
} else {
return (const void *) entry + 20;
}
}
frogfs_fs_t *frogfs_init(const frogfs_config_t *conf)
{
frogfs_fs_t *fs = lv_calloc(1, sizeof(frogfs_fs_t));
if (fs == NULL) {
LV_LOG_ERROR("calloc failed");
return NULL;
}
LV_LOG_TRACE("%p", fs);
fs->head = (const void *) conf->addr;
if (fs->head == NULL) {
LV_LOG_ERROR("flash mmap not enabled and addr is NULL");
goto err_out;
}
if (fs->head->magic != FROGFS_MAGIC) {
LV_LOG_ERROR("frogfs magic not found");
goto err_out;
}
if (fs->head->ver_major != FROGFS_VER_MAJOR) {
LV_LOG_ERROR("frogfs major version mismatch. filesystem is v%d.%d and this "
"library is v%d.%d", fs->head->ver_major, fs->head->ver_minor,
FROGFS_VER_MAJOR, FROGFS_VER_MINOR);
goto err_out;
}
fs->num_entries = fs->head->num_entries;
fs->hash = (const void *) fs->head + sizeof(frogfs_head_t);
fs->root = (const void *) fs->hash + (sizeof(frogfs_hash_t) * fs->num_entries);
return fs;
err_out:
frogfs_deinit(fs);
return NULL;
}
void frogfs_deinit(frogfs_fs_t *fs)
{
LV_LOG_TRACE("%p", fs);
lv_free(fs);
}
const frogfs_entry_t *frogfs_get_entry(const frogfs_fs_t *fs, const char *path)
{
LV_ASSERT_NULL(fs);
LV_ASSERT_NULL(path);
while (*path == '/') {
path++;
}
LV_LOG_TRACE("'%s'", path);
uint32_t hash = djb2_hash(path);
LV_LOG_TRACE("hash %08"PRIx32, hash);
int first = 0;
int last = fs->num_entries - 1;
int middle;
const frogfs_hash_t *e;
while (first <= last) {
middle = first + (last - first) / 2;
e = &fs->hash[middle];
if (e->hash == hash) {
break;
} else if (e->hash < hash) {
first = middle + 1;
} else {
last = middle - 1;
}
}
if (first > last) {
LV_LOG_TRACE("no match");
return NULL;
}
/* move e to the first match */
while (middle > 0) {
e = fs->hash + middle;
if ((e - 1)->hash != hash) {
break;
}
middle--;
}
/* walk through canidates and look for a match */
const frogfs_entry_t *entry;
do {
entry = (const void *) fs->head + e->offs;
char *match = frogfs_get_path(fs, entry);
if (lv_strcmp(path, match) == 0) {
lv_free(match);
LV_LOG_TRACE("entry %d", middle);
return entry;
}
lv_free(match);
entry++;
middle++;
} while ((middle < last) && (e->hash == hash));
LV_LOG_WARN("unable to find entry");
return NULL;
}
char *frogfs_get_name(const frogfs_entry_t *entry)
{
char *name = lv_malloc(entry->seg_sz + 1);
lv_memcpy(name, get_name(entry), entry->seg_sz);
name[entry->seg_sz] = '\0';
return name;
}
char *frogfs_get_path(const frogfs_fs_t *fs, const frogfs_entry_t *entry)
{
LV_ASSERT_NULL(entry);
char *path = lv_calloc(LV_FS_MAX_PATH_LENGTH, 1);
size_t len = 0;
if (!path) {
return NULL;
}
if (entry->parent == 0) {
return path;
}
while (entry->parent != 0 && len + entry->seg_sz + 1 < LV_FS_MAX_PATH_LENGTH - 1) {
const frogfs_entry_t *parent = (const void *) fs->head + entry->parent;
if ((const void *) parent == (const void *) fs->root) {
lv_memmove(path + entry->seg_sz, path, len);
lv_memcpy(path, get_name(entry), entry->seg_sz);
len += entry->seg_sz;
break;
} else {
lv_memmove(path + entry->seg_sz + 1, path, len + 1);
path[0] = '/';
lv_memcpy(path + 1, get_name(entry), entry->seg_sz);
len += entry->seg_sz + 1;
}
entry = parent;
}
return path;
}
int frogfs_is_dir(const frogfs_entry_t *entry)
{
return FROGFS_IS_DIR(entry);
}
int frogfs_is_file(const frogfs_entry_t *entry)
{
return FROGFS_IS_FILE(entry);
}
void frogfs_stat(const frogfs_fs_t *fs, const frogfs_entry_t *entry,
frogfs_stat_t *st)
{
LV_ASSERT_NULL(fs);
LV_ASSERT_NULL(entry);
lv_memset(st, 0, sizeof(*st));
if (FROGFS_IS_DIR(entry)) {
st->type = FROGFS_ENTRY_TYPE_DIR;
} else {
st->type = FROGFS_ENTRY_TYPE_FILE;
const frogfs_file_t *file = (const void *) entry;
st->compression = entry->u.compression;
if (st->compression) {
const frogfs_comp_t *comp = (const void *) entry;
st->compressed_sz = comp->data_sz;
st->size = comp->real_sz;
} else {
st->compressed_sz = file->data_sz;
st->size = file->data_sz;
}
}
}
frogfs_fh_t *frogfs_open(const frogfs_fs_t *fs, const frogfs_entry_t *entry,
unsigned int flags)
{
LV_ASSERT_NULL(fs);
LV_ASSERT_NULL(entry);
if (FROGFS_IS_DIR(entry)) {
return NULL;
}
const frogfs_file_t *file = (const void *) entry;
frogfs_fh_t *fh = lv_calloc(1, sizeof(frogfs_fh_t));
if (fh == NULL) {
LV_LOG_ERROR("calloc failed");
goto err_out;
}
LV_LOG_TRACE("%p", fh);
fh->fs = fs;
fh->file = file;
fh->data_start = (const void *) fs->head + file->data_offs;
fh->data_ptr = fh->data_start;
fh->data_sz = file->data_sz;
fh->flags = flags;
if (entry->u.compression == 0 || flags & FROGFS_OPEN_RAW) {
fh->real_sz = file->data_sz;
fh->decomp_funcs = &frogfs_decomp_raw;
}
#if CONFIG_FROGFS_USE_MINIZ == 1
else if ((entry->u.compression == FROGFS_COMP_ALGO_GZIP) ||
(entry->u.compression == FROGFS_COMP_ALGO_ZLIB)) {
fh->real_sz = ((frogfs_comp_t *) file)->real_sz;
fh->decomp_funcs = &frogfs_decomp_miniz;
}
#endif
#if CONFIG_FROGFS_USE_ZLIB == 1
else if ((entry->u.compression == FROGFS_COMP_ALGO_GZIP) ||
(entry->u.compression == FROGFS_COMP_ALGO_ZLIB)) {
fh->real_sz = ((frogfs_comp_t *) file)->real_sz;
fh->decomp_funcs = &frogfs_decomp_zlib;
}
#endif
#if CONFIG_FROGFS_USE_HEATSHRINK == 1
else if (entry->u.compression == FROGFS_COMP_ALGO_HEATSHRINK) {
fh->real_sz = ((frogfs_comp_t *) file)->real_sz;
fh->decomp_funcs = &frogfs_decomp_heatshrink;
}
#endif
else {
LV_LOG_ERROR("unsupported compression type %d", entry->u.compression);
goto err_out;
}
if (fh->decomp_funcs->open) {
if (fh->decomp_funcs->open(fh, flags) < 0) {
LV_LOG_ERROR("decomp_funcs->fopen");
goto err_out;
}
}
return fh;
err_out:
frogfs_close(fh);
return NULL;
}
void frogfs_close(frogfs_fh_t *fh)
{
if (fh == NULL) {
/* do nothing */
return;
}
if (fh->decomp_funcs && fh->decomp_funcs->close) {
fh->decomp_funcs->close(fh);
}
LV_LOG_TRACE("%p", fh);
lv_free(fh);
}
int frogfs_is_raw(frogfs_fh_t *fh)
{
return !!(fh->flags & FROGFS_OPEN_RAW);
}
ssize_t frogfs_read(frogfs_fh_t *fh, void *buf, size_t len)
{
LV_ASSERT_NULL(fh);
if (fh->decomp_funcs->read) {
return fh->decomp_funcs->read(fh, buf, len);
}
return -1;
}
ssize_t frogfs_seek(frogfs_fh_t *fh, long offset, int mode)
{
LV_ASSERT_NULL(fh);
if (fh->decomp_funcs->seek) {
return fh->decomp_funcs->seek(fh, offset, mode);
}
return -1;
}
size_t frogfs_tell(frogfs_fh_t *fh)
{
LV_ASSERT_NULL(fh);
if (fh->decomp_funcs->tell) {
return fh->decomp_funcs->tell(fh);
}
return -1;
}
size_t frogfs_access(frogfs_fh_t *fh, const void **buf)
{
LV_ASSERT_NULL(fh);
*buf = fh->data_start;
return fh->data_sz;
}
frogfs_dh_t *frogfs_opendir(frogfs_fs_t *fs, const frogfs_entry_t *entry)
{
LV_ASSERT_NULL(fs);
if (entry != NULL && FROGFS_IS_FILE(entry)) {
return NULL;
}
frogfs_dh_t *dh = lv_calloc(1, sizeof(frogfs_dh_t));
if (dh == NULL) {
LV_LOG_ERROR("calloc failed");
return NULL;
}
dh->fs = fs;
if (entry == NULL) {
dh->dir = fs->root;
} else {
dh->dir = (const void *) entry;
}
return dh;
}
void frogfs_closedir(frogfs_dh_t *dh)
{
if (dh == NULL) {
return;
}
lv_free(dh);
}
const frogfs_entry_t *frogfs_readdir(frogfs_dh_t *dh)
{
const frogfs_entry_t *entry = NULL;
if (dh->dir == NULL) {
return NULL;
}
if (dh->index < dh->dir->entry.u.child_count) {
entry = (const void *) dh->fs->head + dh->dir->children[dh->index];
dh->index++;
}
return entry;
}
void frogfs_seekdir(frogfs_dh_t *dh, long loc)
{
LV_ASSERT_NULL(dh);
LV_ASSERT(loc >= 0);
if (loc < dh->dir->entry.u.child_count) {
dh->index = loc;
} else {
dh->index = dh->dir->entry.u.child_count;
}
}
long frogfs_telldir(frogfs_dh_t *dh)
{
LV_ASSERT_NULL(dh);
return dh->index;
}
#endif /*LV_USE_FS_FROGFS*/

View File

@@ -0,0 +1,90 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#include "../../../lv_conf_internal.h"
#include LV_STDINT_INCLUDE
/**
* \brief Is entry a directory?
*/
#define FROGFS_IS_DIR(e) (e->u.child_count < 0xFF00)
/**
* \brief Is entry a file?
*/
#define FROGFS_IS_FILE(e) (e->u.child_count >= 0xFF00)
/**
* \brief Is entry a compressed file?
*/
#define FROGFS_IS_COMP(e) (e->u.child_count > 0xFF00)
/**
* \brief Filesystem header
*/
typedef struct frogfs_head_t {
uint32_t magic; /**< filesystem magic */
uint8_t ver_major; /**< major version */
uint8_t ver_minor; /**< minor version */
uint16_t num_entries; /** entry count */
uint32_t bin_sz; /**< binary length */
} frogfs_head_t;
/**
* \brief Hash table entry
*/
typedef struct frogfs_hash_t {
uint32_t hash; /**< path hash */
uint32_t offs; /**< object offset */
} frogfs_hash_t;
/**
* \brief Entry header
*/
struct frogfs_entry_t {
uint32_t parent; /**< parent entry offset */
union {
uint16_t child_count; /**< child entry count */
uint8_t compression; /**< compression algorithm */
} u;
uint8_t seg_sz; /**< path segment size (before alignment) */
uint8_t opts; /**< compression opts */
};
/**
* \brief Directory object header
*/
typedef struct frogfs_dir_t {
const frogfs_entry_t entry;
uint32_t children[];
} frogfs_dir_t;
/**
* \brief File object header
*/
typedef struct frogfs_file_t {
const frogfs_entry_t entry;
uint32_t data_offs;
uint32_t data_sz;
} frogfs_file_t;
/**
* \brief Compressed file object header
*/
typedef struct frogfs_comp_t {
const frogfs_entry_t entry;
uint32_t data_offs;
uint32_t data_sz; /**< data size (before alignment) */
uint32_t real_sz; /**< expanded size */
} frogfs_comp_t;
/**
* \brief Filesystem footer
*/
typedef struct frogfs_foot_t {
uint32_t crc32; /**< crc32 of entire file without this field */
} frogfs_foot_t;

View File

@@ -0,0 +1,71 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#include "../include/frogfs/frogfs_types.h"
#define FROGFS_PRIVATE_STRUCTS
#include "../include/frogfs/frogfs.h"
#include "frogfs_format.h"
typedef struct frogfs_decomp_funcs_t frogfs_decomp_funcs_t;
/**
* \brief Structure describing a frogfs file entry
*/
struct frogfs_fh_t {
const frogfs_fs_t *fs; /**< frogfs fs pointer */
const frogfs_file_t *file; /**< file header pointer */
const void *data_start; /**< data start pointer */
const void *data_ptr; /**< current data pointer */
size_t data_sz; /**< data size */
size_t real_sz; /**< real (expanded) size */
unsigned int flags; /** open flags */
const frogfs_decomp_funcs_t *decomp_funcs; /**< decompresor funcs */
void *decomp_priv; /**< decompressor private data */
};
/**
* \brief Structure describing a frogfs directory entry
*/
struct frogfs_dh_t {
const frogfs_fs_t *fs; /**< frogfs fs pointer */
const frogfs_dir_t *dir; /**< frogfs entry */
long index; /**< current index */
};
/**
* \brief Structure of function pointers that describe a decompressor
*/
struct frogfs_decomp_funcs_t {
int (*open)(frogfs_fh_t *f, unsigned int flags);
void (*close)(frogfs_fh_t *f);
ssize_t (*read)(frogfs_fh_t *f, void *buf, size_t len);
ssize_t (*seek)(frogfs_fh_t *f, long offset, int mode);
size_t (*tell)(frogfs_fh_t *f);
};
/**
* \brief Raw decompressor functions
*/
extern const frogfs_decomp_funcs_t frogfs_decomp_raw;
/**
* \brief Heatshrink decompressor functions
*/
extern const frogfs_decomp_funcs_t frogfs_decomp_heatshrink;
/**
* \brief Miniz decompressor functions
*/
extern const frogfs_decomp_funcs_t frogfs_decomp_miniz;
/**
* \brief Zlib decompressor functions
*/
extern const frogfs_decomp_funcs_t frogfs_decomp_zlib;
#include "../include/frogfs/frogfs.h"

View File

@@ -0,0 +1,194 @@
#include "../../../lvgl.h"
#if LV_USE_FS_ARDUINO_ESP_LITTLEFS
#include "../../core/lv_global.h"
#include "LittleFS.h"
#if !LV_FS_IS_VALID_LETTER(LV_FS_ARDUINO_ESP_LITTLEFS_LETTER)
#error "Invalid drive letter"
#endif
typedef struct ArduinoEspLittleFile {
File file;
} ArduinoEspLittleFile;
/**********************
* 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);
/**
* Register a driver for the LittleFS File System interface
*/
extern "C" void lv_fs_arduino_esp_littlefs_init(void)
{
fs_init();
lv_fs_drv_t * fs_drv = &(LV_GLOBAL_DEFAULT()->arduino_esp_littlefs_fs_drv);
lv_fs_drv_init(fs_drv);
fs_drv->letter = LV_FS_ARDUINO_ESP_LITTLEFS_LETTER;
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 = NULL;
fs_drv->dir_open_cb = NULL;
fs_drv->dir_read_cb = NULL;
lv_fs_drv_register(fs_drv);
}
/**********************
* STATIC FUNCTIONS
**********************/
/*Initialize your Storage device and File system.*/
static void fs_init(void)
{
LittleFS.begin();
}
/**
* 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 descriptor or NULL on error
*/
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 = FILE_WRITE;
else if(mode == LV_FS_MODE_RD)
flags = FILE_READ;
else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD))
flags = FILE_WRITE;
char buf[LV_FS_MAX_PATH_LEN];
lv_snprintf(buf, sizeof(buf), LV_FS_ARDUINO_ESP_LITTLEFS_PATH "%s", path);
File file = LittleFS.open(buf, flags);
if(!file) {
return NULL;
}
ArduinoEspLittleFile * lf = new ArduinoEspLittleFile{file};
return (void *)lf;
}
/**
* Close an opened file
* @param drv pointer to a driver where this function belongs
* @param file_p pointer to a file_t variable. (opened with fs_open)
* @return LV_FS_RES_OK: no error or 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);
ArduinoEspLittleFile * lf = (ArduinoEspLittleFile *)file_p;
lf->file.close();
delete lf;
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_t 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 or 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);
ArduinoEspLittleFile * lf = (ArduinoEspLittleFile *)file_p;
*br = lf->file.read((uint8_t *)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 pointer to a file_t 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)
* @return LV_FS_RES_OK: no error 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);
ArduinoEspLittleFile * lf = (ArduinoEspLittleFile *)file_p;
*bw = lf->file.write((uint8_t *)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 pointer to a file_t variable. (opened with fs_open )
* @param pos the new position of read write pointer
* @param whence tells from where to interpret the `pos`. See @lv_fs_whence_t
* @return LV_FS_RES_OK: no error or 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);
SeekMode mode;
if(whence == LV_FS_SEEK_SET)
mode = SeekSet;
else if(whence == LV_FS_SEEK_CUR)
mode = SeekCur;
else if(whence == LV_FS_SEEK_END)
mode = SeekEnd;
ArduinoEspLittleFile * lf = (ArduinoEspLittleFile *)file_p;
int rc = lf->file.seek(pos, mode);
return rc < 0 ? LV_FS_RES_UNKNOWN : 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_p variable
* @param pos_p pointer to store the result
* @return LV_FS_RES_OK: no error or 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);
ArduinoEspLittleFile * lf = (ArduinoEspLittleFile *)file_p;
*pos_p = lf->file.position();
return (int32_t)(*pos_p) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK;
}
#else /*LV_USE_FS_ARDUINO_ESP_LITTLEFS == 0*/
#if defined(LV_FS_ARDUINO_ESP_LITTLEFS_LETTER) && LV_FS_ARDUINO_ESP_LITTLEFS_LETTER != '\0'
#warning "LV_USE_FS_ARDUINO_ESP_LITTLEFS is not enabled but LV_FS_ARDUINO_ESP_LITTLEFS_LETTER is set"
#endif
#endif /*LV_USE_FS_ARDUINO_ESP_LITTLEFS*/

View File

@@ -0,0 +1,186 @@
#include "../../../lvgl.h"
#if LV_USE_FS_ARDUINO_SD
#include "../../core/lv_global.h"
#include <SPI.h>
#include "SD.h"
#if !LV_FS_IS_VALID_LETTER(LV_FS_ARDUINO_SD_LETTER)
#error "Invalid drive letter"
#endif
typedef struct SdFile {
File file;
} SdFile;
/**********************
* 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);
/**
* Register a driver for the SD File System interface
*/
extern "C" void lv_fs_arduino_sd_init(void)
{
lv_fs_drv_t * fs_drv = &(LV_GLOBAL_DEFAULT()->arduino_sd_fs_drv);
lv_fs_drv_init(fs_drv);
fs_drv->letter = LV_FS_ARDUINO_SD_LETTER;
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 = NULL;
fs_drv->dir_open_cb = NULL;
fs_drv->dir_read_cb = NULL;
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 descriptor or NULL on error
*/
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 = FILE_WRITE;
else if(mode == LV_FS_MODE_RD)
flags = FILE_READ;
else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD))
flags = FILE_WRITE;
char buf[LV_FS_MAX_PATH_LEN];
lv_snprintf(buf, sizeof(buf), LV_FS_ARDUINO_SD_PATH "%s", path);
File file = SD.open(buf, flags);
if(!file) {
return NULL;
}
SdFile * lf = new SdFile{file};
return (void *)lf;
}
/**
* Close an opened file
* @param drv pointer to a driver where this function belongs
* @param file_p pointer to a file_t variable. (opened with fs_open)
* @return LV_FS_RES_OK: no error or 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);
SdFile * lf = (SdFile *)file_p;
lf->file.close();
delete lf;
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_t 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 or 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);
SdFile * lf = (SdFile *)file_p;
*br = lf->file.read((uint8_t *)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 pointer to a file_t 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)
* @return LV_FS_RES_OK: no error 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);
SdFile * lf = (SdFile *)file_p;
*bw = lf->file.write((uint8_t *)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 pointer to a file_t variable. (opened with fs_open )
* @param pos the new position of read write pointer
* @param whence tells from where to interpret the `pos`. See @lv_fs_whence_t
* @return LV_FS_RES_OK: no error or 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);
SeekMode mode;
if(whence == LV_FS_SEEK_SET)
mode = SeekSet;
else if(whence == LV_FS_SEEK_CUR)
mode = SeekCur;
else if(whence == LV_FS_SEEK_END)
mode = SeekEnd;
SdFile * lf = (SdFile *)file_p;
int rc = lf->file.seek(pos, mode);
return rc < 0 ? LV_FS_RES_UNKNOWN : 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_p variable
* @param pos_p pointer to store the result
* @return LV_FS_RES_OK: no error or 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);
SdFile * lf = (SdFile *)file_p;
*pos_p = lf->file.position();
return (int32_t)(*pos_p) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK;
}
#else /*LV_USE_FS_ARDUINO_SD == 0*/
#if defined(LV_FS_ARDUINO_SD_LETTER) && LV_FS_ARDUINO_SD_LETTER != '\0'
#warning "LV_USE_FS_ARDUINO_SD is not enabled but LV_FS_ARDUINO_SD_LETTER is set"
#endif
#endif /*LV_USE_FS_ARDUINO_SD*/

View File

@@ -0,0 +1,10 @@
// this file should not exist
#ifdef __GNUC__
#define IS_NOT_USED __attribute__ ((unused))
#else
#define IS_NOT_USED
#endif
IS_NOT_USED static void nothing(void)
{
// do nothing
}

View File

@@ -0,0 +1,303 @@
/**
* @file lv_fs_fatfs.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../../lvgl.h"
#if LV_USE_FS_FATFS
#include "ff.h"
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#ifdef ESP_PLATFORM
#define DIR FF_DIR /* ESP IDF typedefs `DIR` as `FF_DIR` in its version of ff.h. Use `FF_DIR` in LVGL too */
#endif
#if !LV_FS_IS_VALID_LETTER(LV_FS_FATFS_LETTER)
#error "Invalid drive 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, uint32_t fn_len);
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
*--------------------------------------------------*/
lv_fs_drv_t * fs_drv_p = &(LV_GLOBAL_DEFAULT()->fatfs_fs_drv);
lv_fs_drv_init(fs_drv_p);
/*Set up fields...*/
fs_drv_p->letter = LV_FS_FATFS_LETTER;
fs_drv_p->cache_size = LV_FS_FATFS_CACHE_SIZE;
fs_drv_p->open_cb = fs_open;
fs_drv_p->close_cb = fs_close;
fs_drv_p->read_cb = fs_read;
fs_drv_p->write_cb = fs_write;
fs_drv_p->seek_cb = fs_seek;
fs_drv_p->tell_cb = fs_tell;
fs_drv_p->dir_close_cb = fs_dir_close;
fs_drv_p->dir_open_cb = fs_dir_open;
fs_drv_p->dir_read_cb = fs_dir_read;
lv_fs_drv_register(fs_drv_p);
}
/**********************
* 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_malloc(sizeof(FIL));
if(f == NULL) return NULL;
char buf[LV_FS_MAX_PATH_LEN];
lv_snprintf(buf, sizeof(buf), LV_FS_FATFS_PATH "%s", path);
FRESULT res = f_open(f, buf, flags);
if(res == FR_OK) {
return f;
}
else {
lv_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_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 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_malloc(sizeof(DIR));
if(d == NULL) return NULL;
char buf[LV_FS_MAX_PATH_LEN];
lv_snprintf(buf, sizeof(buf), LV_FS_FATFS_PATH "%s", path);
FRESULT res = f_opendir(d, buf);
if(res != FR_OK) {
lv_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
* @param fn_len length of the 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, uint32_t fn_len)
{
LV_UNUSED(drv);
if(fn_len == 0) return LV_FS_RES_INV_PARAM;
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.fname[0] == 0) break; /* End of the directory */
if(fno.fattrib & AM_DIR) {
lv_snprintf(fn, fn_len, "/%s", fno.fname);
}
else lv_strlcpy(fn, fno.fname, fn_len);
} while(lv_strcmp(fn, "/.") == 0 || lv_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_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_FATFS*/

View File

@@ -0,0 +1,341 @@
/**
* @file lv_fs_frogfs.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../../lvgl.h"
#if LV_USE_FS_FROGFS
#include "../frogfs/include/frogfs/frogfs.h"
#include "../../core/lv_global.h"
#include "../../misc/lv_ll.h"
#if !LV_FS_IS_VALID_LETTER(LV_FS_FROGFS_LETTER)
#error "Invalid drive letter"
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
typedef struct {
lv_ll_t blob_ll;
} fs_drv_data_t;
typedef struct {
char * path_prefix;
frogfs_fs_t * blob_fs;
} blob_t;
/**********************
* STATIC PROTOTYPES
**********************/
static bool get_blob_and_entry(const char * path, blob_t ** blob_dst, const frogfs_entry_t ** entry_dst);
static void destroy_blob(blob_t * blob);
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_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, uint32_t fn_len);
static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
#define frogfs_fs_drv (&(LV_GLOBAL_DEFAULT()->frogfs_fs_drv))
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_fs_frogfs_init(void)
{
fs_drv_data_t * data = lv_malloc(sizeof(*data));
LV_ASSERT_MALLOC(data);
lv_ll_init(&data->blob_ll, sizeof(blob_t));
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
lv_fs_drv_init(fs_drv_p);
fs_drv_p->letter = LV_FS_FROGFS_LETTER;
fs_drv_p->open_cb = fs_open;
fs_drv_p->close_cb = fs_close;
fs_drv_p->read_cb = fs_read;
fs_drv_p->seek_cb = fs_seek;
fs_drv_p->tell_cb = fs_tell;
fs_drv_p->dir_close_cb = fs_dir_close;
fs_drv_p->dir_open_cb = fs_dir_open;
fs_drv_p->dir_read_cb = fs_dir_read;
fs_drv_p->user_data = data;
lv_fs_drv_register(fs_drv_p);
}
void lv_fs_frogfs_deinit(void)
{
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
fs_drv_data_t * data = fs_drv_p->user_data;
lv_ll_clear_custom(&data->blob_ll, (void (*)(void *)) destroy_blob);
lv_free(data);
}
lv_result_t lv_fs_frogfs_register_blob(const void * blob, const char * path_prefix)
{
if(path_prefix[0] == '\0') {
LV_LOG_WARN("path prefix should not be zero-length");
return LV_RESULT_INVALID;
}
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
fs_drv_data_t * data = fs_drv_p->user_data;
frogfs_config_t frogfs_config = {
.addr = blob,
};
frogfs_fs_t * blob_fs = frogfs_init(&frogfs_config);
if(blob_fs == NULL) {
LV_LOG_WARN("Could not register frogfs blob 0x%p", blob);
return LV_RESULT_INVALID;
}
blob_t * hdl = lv_ll_ins_head(&data->blob_ll);
LV_ASSERT_MALLOC(hdl);
hdl->path_prefix = lv_strdup(path_prefix);
LV_ASSERT_MALLOC(hdl->path_prefix);
hdl->blob_fs = blob_fs;
return LV_RESULT_OK;
}
void lv_fs_frogfs_unregister_blob(const char * path_prefix)
{
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
fs_drv_data_t * data = fs_drv_p->user_data;
blob_t * blob_handle;
LV_LL_READ(&data->blob_ll, blob_handle) {
if(lv_streq(path_prefix, blob_handle->path_prefix)) {
break;
}
}
if(blob_handle == NULL) {
LV_LOG_WARN("No frogfs blob with path prefix '%s' to unregister", path_prefix);
return;
}
destroy_blob(blob_handle);
}
/**********************
* STATIC FUNCTIONS
**********************/
static bool get_blob_and_entry(const char * path, blob_t ** blob_dst, const frogfs_entry_t ** entry_dst)
{
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
fs_drv_data_t * data = fs_drv_p->user_data;
blob_t * blob;
size_t path_prefix_length;
LV_LL_READ(&data->blob_ll, blob) {
path_prefix_length = lv_strlen(blob->path_prefix);
if(0 == lv_strncmp(path, blob->path_prefix, path_prefix_length)
&& (blob->path_prefix[path_prefix_length - 1] == '/'
|| blob->path_prefix[path_prefix_length - 1] == '\\'
|| path[path_prefix_length] == '\0'
|| path[path_prefix_length] == '/'
|| path[path_prefix_length] == '\\')) {
break;
}
}
if(blob == NULL) {
LV_LOG_WARN("Path '%s' does not have a prefix that matches any of the registered frogfs blobs", path);
return false;
}
path += path_prefix_length;
if(path[0] == '/' || path[0] == '\\') path++;
const frogfs_entry_t * entry = frogfs_get_entry(blob->blob_fs, path);
if(entry == NULL) {
LV_LOG_WARN("No entry '%s' in frogfs blob registered under '%s'", path, blob->path_prefix);
return false;
}
*blob_dst = blob;
*entry_dst = entry;
return true;
}
static void destroy_blob(blob_t * blob)
{
lv_fs_drv_t * fs_drv_p = frogfs_fs_drv;
fs_drv_data_t * data = fs_drv_p->user_data;
lv_free(blob->path_prefix);
frogfs_deinit(blob->blob_fs);
lv_ll_remove(&data->blob_ll, blob);
lv_free(blob);
}
static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode)
{
LV_UNUSED(drv);
if(mode & LV_FS_MODE_WR) {
LV_LOG_WARN("Cannot open files for writing with frogfs");
return NULL;
}
blob_t * blob;
const frogfs_entry_t * entry;
if(!get_blob_and_entry(path, &blob, &entry)) {
return NULL;
}
if(frogfs_is_dir(entry)) {
LV_LOG_WARN("Cannot open directory as file with frogfs");
return NULL;
}
frogfs_fh_t * fh = frogfs_open(blob->blob_fs, entry, 0);
if(fh == NULL) {
LV_LOG_WARN("Could not open '%s' even though the entry exists in frogfs", path);
return NULL;
}
return fh;
}
static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p)
{
LV_UNUSED(drv);
frogfs_close(file_p);
return LV_FS_RES_OK;
}
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);
ssize_t res = frogfs_read(file_p, buf, btr);
if(res < 0) {
LV_LOG_WARN("Error reading frogfs file");
*br = 0;
return LV_FS_RES_UNKNOWN;
}
*br = res;
return LV_FS_RES_OK;
}
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);
ssize_t res = frogfs_seek(file_p, pos, whence);
if(res < 0) {
LV_LOG_WARN("Error `seek`ing frogfs file");
return LV_FS_RES_UNKNOWN;
}
return LV_FS_RES_OK;
}
static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{
LV_UNUSED(drv);
size_t res = frogfs_tell(file_p);
if(res == (size_t) -1) {
LV_LOG_WARN("Error `tell`ing frogfs file");
return LV_FS_RES_UNKNOWN;
}
*pos_p = res;
return LV_FS_RES_OK;
}
static void * fs_dir_open(lv_fs_drv_t * drv, const char * path)
{
LV_UNUSED(drv);
blob_t * blob;
const frogfs_entry_t * entry;
if(!get_blob_and_entry(path, &blob, &entry)) {
return NULL;
}
if(!frogfs_is_dir(entry)) {
LV_LOG_WARN("Cannot open non-directory as directory with frogfs");
return NULL;
}
frogfs_dh_t * dh = frogfs_opendir(blob->blob_fs, entry);
if(dh == NULL) {
LV_LOG_WARN("Could not open directory '%s' even though the entry exists in frogfs", path);
return NULL;
}
return dh;
}
static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn, uint32_t fn_len)
{
LV_UNUSED(drv);
if(fn_len == 0) return LV_FS_RES_INV_PARAM;
const frogfs_entry_t * entry = frogfs_readdir(dir_p);
if(entry == NULL) {
fn[0] = '\0';
return LV_FS_RES_OK;
}
char * name = frogfs_get_name(entry);
if(frogfs_is_dir(entry)) {
lv_snprintf(fn, fn_len, "/%s", name);
}
else {
lv_strlcpy(fn, name, fn_len);
}
lv_free(name); /* frogfs `malloc`d it */
return LV_FS_RES_OK;
}
static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p)
{
LV_UNUSED(drv);
frogfs_closedir(dir_p);
return LV_FS_RES_OK;
}
#endif /*LV_USE_FS_FROGFS*/

View File

@@ -0,0 +1,332 @@
#include "../../../lvgl.h"
#if LV_USE_FS_LITTLEFS
#include "lfs.h"
#include "../../core/lv_global.h"
#if !LV_FS_IS_VALID_LETTER(LV_FS_LITTLEFS_LETTER)
#error "Invalid drive letter"
#endif
typedef struct LittleFile {
lfs_file_t file;
} LittleFile;
typedef struct LittleDirectory {
lfs_dir_t dir;
} LittleDirectory;
/**********************
* STATIC PROTOTYPES
**********************/
static void fs_remove(lv_fs_drv_t * drv);
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_close(lv_fs_drv_t * drv, void * dir_p);
static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn, uint32_t fn_len);
void lv_littlefs_set_handler(lfs_t * lfs)
{
lv_fs_drv_t * drv = lv_fs_get_drv(LV_FS_LITTLEFS_LETTER);
drv->user_data = lfs;
}
void lv_fs_littlefs_init(void)
{
lv_fs_drv_t * fs_drv = &(LV_GLOBAL_DEFAULT()->littlefs_fs_drv);
lv_fs_drv_init(fs_drv);
fs_drv->letter = LV_FS_LITTLEFS_LETTER;
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_open_cb = fs_dir_open;
fs_drv->dir_close_cb = fs_dir_close;
fs_drv->dir_read_cb = fs_dir_read;
lv_fs_drv_register(fs_drv);
}
lv_fs_res_t lv_fs_littlefs_register_drive(lfs_t * lfs, char letter)
{
if(lfs == NULL) {
return LV_FS_RES_INV_PARAM; /*Invalid LittleFS handle*/
}
if(LV_FS_IS_VALID_LETTER(letter) == false) {
return LV_FS_RES_INV_PARAM; /*Invalid letter*/
}
if(lv_fs_get_drv(letter) != NULL) {
return LV_FS_RES_DRIVE_LETTER_ALREADY_USED; /*Already registered*/
}
lv_fs_drv_t * fs_drv = lv_malloc(sizeof(lv_fs_drv_t));
LV_ASSERT_MALLOC(fs_drv);
lv_fs_drv_init(fs_drv);
fs_drv->letter = letter;
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_open_cb = fs_dir_open;
fs_drv->dir_close_cb = fs_dir_close;
fs_drv->dir_read_cb = fs_dir_read;
fs_drv->remove_cb = fs_remove; /*Optional*/
fs_drv->user_data = lfs;
lv_fs_drv_register(fs_drv);
return LV_FS_RES_OK;
}
/**********************
* STATIC FUNCTIONS
**********************/
/**
* free the driver handle
* @param drv pointer to a driver where this function belongs
*/
static void fs_remove(lv_fs_drv_t * drv)
{
lv_free(drv);
}
/**
* 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 descriptor or NULL on error
*/
static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode)
{
int flags = 0;
if(mode == LV_FS_MODE_WR)
flags = LFS_O_WRONLY;
else if(mode == LV_FS_MODE_RD)
flags = LFS_O_RDONLY;
else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD))
flags = LFS_O_RDWR;
LittleFile * lf = lv_malloc(sizeof(LittleFile));
LV_ASSERT_MALLOC(lf);
char buf[LV_FS_MAX_PATH_LEN];
lv_snprintf(buf, sizeof(buf), LV_FS_LITTLEFS_PATH "%s", path);
lfs_t * lfs = drv->user_data;
int err = lfs_file_open(lfs, &lf->file, buf, flags);
if(err) {
lv_free(lf);
return NULL;
}
return (void *)lf;
}
/**
* Close an opened file
* @param drv pointer to a driver where this function belongs
* @param file_p pointer to a file_t variable. (opened with fs_open)
* @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum
*/
static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p)
{
LittleFile * lf = file_p;
lfs_t * lfs = drv->user_data;
lfs_file_close(lfs, &lf->file);
lv_free(lf);
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_t 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 or 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)
{
LittleFile * lf = file_p;
lfs_t * lfs = drv->user_data;
*br = lfs_file_read(lfs, &lf->file, (uint8_t *)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 pointer to a file_t 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)
* @return LV_FS_RES_OK: no error 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)
{
LittleFile * lf = file_p;
lfs_t * lfs = drv->user_data;
*bw = lfs_file_write(lfs, &lf->file, (uint8_t *)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 pointer to a file_t variable. (opened with fs_open)
* @param pos the new position of read write pointer
* @param whence tells from where to interpret the `pos`. See @lv_fs_whence_t
* @return LV_FS_RES_OK: no error or 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)
{
int mode = 0;
if(whence == LV_FS_SEEK_SET)
mode = LFS_SEEK_SET;
else if(whence == LV_FS_SEEK_CUR)
mode = LFS_SEEK_CUR;
else if(whence == LV_FS_SEEK_END)
mode = LFS_SEEK_END;
LittleFile * lf = file_p;
lfs_t * lfs = drv->user_data;
int rc = lfs_file_seek(lfs, &lf->file, pos, mode);
return rc < 0 ? LV_FS_RES_UNKNOWN : 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_p variable
* @param pos_p pointer to store the result
* @return LV_FS_RES_OK: no error or 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)
{
LittleFile * lf = file_p;
lfs_t * lfs = drv->user_data;
*pos_p = lfs_file_tell(lfs, &lf->file);
return (int32_t)(*pos_p) < 0 ? LV_FS_RES_UNKNOWN : LV_FS_RES_OK;
}
/**
* Open a directory
* @param drv pointer to a driver where this function belongs
* @param path path to the directory beginning with the driver letter (e.g. S:/folder)
* @return a directory descriptor or NULL on error
*/
static void * fs_dir_open(lv_fs_drv_t * drv, const char * path)
{
LittleDirectory * ld = lv_malloc(sizeof(LittleDirectory));
LV_ASSERT_MALLOC(ld);
char buf[LV_FS_MAX_PATH_LEN];
lv_snprintf(buf, sizeof(buf), LV_FS_LITTLEFS_PATH "%s", path);
lfs_t * lfs = drv->user_data;
int err = lfs_dir_open(lfs, &ld->dir, buf);
if(err != LFS_ERR_OK) {
lv_free(ld);
return NULL;
}
return (void *)ld;
}
/**
* Close an opened directory
* @param drv pointer to a driver where this function belongs
* @param dir_p pointer to a dir_p variable. (opened with fs_dir_open)
* @return LV_FS_RES_OK: no error 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)
{
LittleDirectory * ld = dir_p;
lfs_t * lfs = drv->user_data;
int rc = lfs_dir_close(lfs, &ld->dir);
if(rc < 0) return LV_FS_RES_UNKNOWN;
lv_free(ld);
return LV_FS_RES_OK;
}
/**
* Read data from an opened directory
* @param drv pointer to a driver where this function belongs
* @param dir_p pointer to a file_t variable.
* @param fn pointer to a buffer to store the filename
* @param fn_len length of the buffer to store the filename
* @return LV_FS_RES_OK: no error 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, uint32_t fn_len)
{
if(fn_len == 0) return LV_FS_RES_INV_PARAM;
LittleDirectory * lf = dir_p;
lfs_t * lfs = drv->user_data;
fn[0] = '\0';
do {
struct lfs_info info;
int res = lfs_dir_read(lfs, &lf->dir, &info);
if(res < 0) return LV_FS_RES_UNKNOWN;
if(res == 0) { /* End of the directory */
fn[0] = '\0';
break;
}
if(info.type != LFS_TYPE_DIR) {
lv_strlcpy(fn, info.name, fn_len);
}
else {
lv_snprintf(fn, fn_len, "/%s", info.name);
}
} while(lv_strcmp(fn, "/.") == 0 || lv_strcmp(fn, "/..") == 0);
return LV_FS_RES_OK;
}
#else /*LV_USE_FS_LITTLEFS == 0*/
#if defined(LV_FS_LITTLEFS_LETTER) && LV_FS_LITTLEFS_LETTER != '\0'
#warning "LV_USE_FS_LITTLEFS is not enabled but LV_FS_LITTLEFS_LETTER is set"
#endif
#endif /*LV_USE_FS_LITTLEFS*/

View File

@@ -0,0 +1,219 @@
/**
* @file lv_fs_memfs.c
*
* File System Interface driver for memory-mapped files
*
* This driver allows using a memory area as a file that can be read by normal file operations. It can
* be used, e.g., to store font files in slow flash memory, and load them into RAM on demand.
*
* You can enable it in lv_conf.h:
*
* #define LV_USE_FS_MEMFS 1
*
* The actual implementation uses the built-in cache mechanism of the file system interface.
*
* Since this is not an actual file system, file write and directories are not supported.
*
* The default drive letter is 'M', but this can be changed in lv_conf.h:
*
* #define LV_FS_MEMFS_LETTER 'M'
*
* To use it seamlessly with the file system interface a new extended path object has been introduced:
*
* lv_fs_path_ex_t mempath;
*
* This structure can be initialized with the helper function:
*
* lv_fs_make_path_ex(&mempath, (const uint8_t *) & my_mem_buffer, sizeof(my_mem_buffer));
*
* Then the "file" can be opened with:
*
* lv_fs_file_t file;
* lv_fs_res_t res = lv_fs_open(&file, (const char *) & mempath, LV_FS_MODE_RD);
*
* The path object can be used at any place where a file path is required, e.g.:
*
* lv_font_t* my_font = lv_binfont_create((const char *) & mempath);
*
*/
/*********************
* INCLUDES
*********************/
#include "../../misc/lv_fs_private.h"
#include "../../../lvgl.h"
#if LV_USE_FS_MEMFS
/*********************
* DEFINES
*********************/
#if !LV_FS_IS_VALID_LETTER(LV_FS_MEMFS_LETTER)
#error "Invalid drive 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_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 VARIABLES
**********************/
static lv_fs_drv_t fs_drv; /*A driver descriptor*/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* Register a driver for the File system interface
*/
void lv_fs_memfs_init(void)
{
/*---------------------------------------------------
* Register the file system interface in LVGL
*--------------------------------------------------*/
lv_fs_drv_init(&fs_drv);
/*Set up fields...*/
fs_drv.letter = LV_FS_MEMFS_LETTER;
fs_drv.cache_size = LV_FS_CACHE_FROM_BUFFER;
fs_drv.open_cb = fs_open;
fs_drv.close_cb = fs_close;
fs_drv.read_cb = fs_read;
fs_drv.write_cb = NULL;
fs_drv.seek_cb = fs_seek;
fs_drv.tell_cb = fs_tell;
fs_drv.dir_close_cb = NULL;
fs_drv.dir_open_cb = NULL;
fs_drv.dir_read_cb = NULL;
lv_fs_drv_register(&fs_drv);
}
/**********************
* STATIC FUNCTIONS
**********************/
/**
* Open a file
* @param drv pointer to a driver where this function belongs
* @param path pointer to an extended path object containing the memory buffer address and size
* @param mode read: FS_MODE_RD (currently only reading from the buffer is supported)
* @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);
LV_UNUSED(mode);
return (void *)path;
}
/**
* 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);
LV_UNUSED(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);
LV_UNUSED(file_p);
LV_UNUSED(buf);
LV_UNUSED(btr);
*br = 0;
return LV_FS_RES_OK;
}
/**
* Set the read pointer.
* @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 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)
{
/* NOTE: this function is only called to determine the end of the buffer when LV_FS_SEEK_END was given to lv_fs_seek() */
LV_UNUSED(drv);
lv_fs_file_t * fp = (lv_fs_file_t *)file_p;
switch(whence) {
case LV_FS_SEEK_SET: {
fp->cache->file_position = pos;
break;
}
case LV_FS_SEEK_CUR: {
fp->cache->file_position += pos;
break;
}
case LV_FS_SEEK_END: {
fp->cache->file_position = fp->cache->end - pos;
break;
}
}
if(fp->cache->file_position < fp->cache->start)
fp->cache->file_position = fp->cache->start;
else if(fp->cache->file_position > fp->cache->end)
fp->cache->file_position = fp->cache->end;
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 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 = ((lv_fs_file_t *)file_p)->cache->file_position;
return LV_FS_RES_OK;
}
#else /*LV_USE_FS_MEMFS == 0*/
#if defined(LV_FS_MEMFS_LETTER) && LV_FS_MEMFS_LETTER != '\0'
#warning "LV_USE_FS_MEMFS is not enabled but LV_FS_MEMFS_LETTER is set"
#endif
#endif /*LV_USE_FS_MEMFS*/

View File

@@ -0,0 +1,385 @@
/**
* @file lv_fs_posix.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../../lvgl.h"
#if LV_USE_FS_POSIX
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <errno.h>
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#if !LV_FS_IS_VALID_LETTER(LV_FS_POSIX_LETTER)
#error "Invalid drive letter"
#endif
/** The reason for 'fd + 1' is because open() may return a legal fd with a value of 0,
* preventing it from being judged as NULL when converted to a pointer type.
*/
#define FILEP2FD(file_p) ((lv_uintptr_t)file_p - 1)
#define FD2FILEP(fd) ((void *)(lv_uintptr_t)(fd + 1))
/**********************
* 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, uint32_t fn_len);
static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p);
static lv_fs_res_t fs_errno_to_res(int errno_val);
/**********************
* 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
*--------------------------------------------------*/
lv_fs_drv_t * fs_drv_p = &(LV_GLOBAL_DEFAULT()->posix_fs_drv);
lv_fs_drv_init(fs_drv_p);
/*Set up fields...*/
fs_drv_p->letter = LV_FS_POSIX_LETTER;
fs_drv_p->cache_size = LV_FS_POSIX_CACHE_SIZE;
fs_drv_p->open_cb = fs_open;
fs_drv_p->close_cb = fs_close;
fs_drv_p->read_cb = fs_read;
fs_drv_p->write_cb = fs_write;
fs_drv_p->seek_cb = fs_seek;
fs_drv_p->tell_cb = fs_tell;
fs_drv_p->dir_close_cb = fs_dir_close;
fs_drv_p->dir_open_cb = fs_dir_open;
fs_drv_p->dir_read_cb = fs_dir_read;
lv_fs_drv_register(fs_drv_p);
}
/**********************
* 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);
int flags = 0;
if(mode == LV_FS_MODE_WR) flags = O_WRONLY | O_CREAT;
else if(mode == LV_FS_MODE_RD) flags = O_RDONLY;
else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = O_RDWR | O_CREAT;
/*Make the path relative to the current directory (the projects root folder)*/
char buf[LV_FS_MAX_PATH_LEN];
lv_snprintf(buf, sizeof(buf), LV_FS_POSIX_PATH "%s", path);
int fd = open(buf, flags, 0666);
if(fd < 0) {
LV_LOG_WARN("Could not open file: %s, flags: 0x%x, errno: %d", buf, flags, errno);
return NULL;
}
return FD2FILEP(fd);
}
/**
* 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);
int fd = FILEP2FD(file_p);
int ret = close(fd);
if(ret < 0) {
LV_LOG_WARN("Could not close file: %d, errno: %d", fd, errno);
return fs_errno_to_res(errno);
}
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);
int fd = FILEP2FD(file_p);
ssize_t ret = read(fd, buf, btr);
if(ret < 0) {
LV_LOG_WARN("Could not read file: %d, errno: %d", fd, errno);
return fs_errno_to_res(errno);
}
*br = (uint32_t)ret;
return 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);
int fd = FILEP2FD(file_p);
ssize_t ret = write(fd, buf, btw);
if(ret < 0) {
LV_LOG_WARN("Could not write file: %d, errno: %d", fd, errno);
return fs_errno_to_res(errno);
}
*bw = (uint32_t)ret;
return 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);
int w;
switch(whence) {
case LV_FS_SEEK_SET:
w = SEEK_SET;
break;
case LV_FS_SEEK_CUR:
w = SEEK_CUR;
break;
case LV_FS_SEEK_END:
w = SEEK_END;
break;
default:
return LV_FS_RES_INV_PARAM;
}
int fd = FILEP2FD(file_p);
off_t offset = lseek(fd, pos, w);
if(offset < 0) {
LV_LOG_WARN("Could not seek file: %d, errno: %d", fd, errno);
return fs_errno_to_res(errno);
}
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 a file handle variable
* @param pos_p pointer 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);
int fd = FILEP2FD(file_p);
off_t offset = lseek(fd, 0, SEEK_CUR);
if(offset < 0) {
LV_LOG_WARN("Could not get position of file: %d, errno: %d", fd, errno);
return fs_errno_to_res(errno);
}
*pos_p = (uint32_t)offset;
return LV_FS_RES_OK;
}
/**
* 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);
/*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);
void * dir = opendir(buf);
if(!dir) {
LV_LOG_WARN("Could not open directory: %s, errno: %d", buf, errno);
return NULL;
}
return dir;
}
/**
* 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
* @param fn_len length of the 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, uint32_t fn_len)
{
LV_UNUSED(drv);
if(fn_len == 0) return LV_FS_RES_INV_PARAM;
struct dirent * entry;
do {
entry = readdir(dir_p);
if(entry) {
if(entry->d_type == DT_DIR) lv_snprintf(fn, fn_len, "/%s", entry->d_name);
else lv_strlcpy(fn, entry->d_name, fn_len);
}
else {
lv_strlcpy(fn, "", fn_len);
}
} while(lv_strcmp(fn, "/.") == 0 || lv_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' 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);
int ret = closedir(dir_p);
if(ret < 0) {
LV_LOG_WARN("Could not close directory: errno: %d", errno);
return fs_errno_to_res(errno);
}
return LV_FS_RES_OK;
}
/**
* Convert an errno value to a lv_fs_res_t value
* @param errno_val an errno value
* @return a corresponding lv_fs_res_t value
*/
static lv_fs_res_t fs_errno_to_res(int errno_val)
{
switch(errno_val) {
case 0:
return LV_FS_RES_OK;
case EIO: /* I/O error */
return LV_FS_RES_HW_ERR;
case EFAULT: /* Bad address */
return LV_FS_RES_FS_ERR;
case ENOENT: /* No such file or directory */
return LV_FS_RES_NOT_EX;
case ENOSPC: /* No space left on device */
return LV_FS_RES_FULL;
case EALREADY: /* Operation already in progress */
return LV_FS_RES_LOCKED;
case EACCES: /* Permission denied */
return LV_FS_RES_DENIED;
case EBUSY: /* Device or resource busy */
return LV_FS_RES_BUSY;
case ETIMEDOUT: /* Connection timed out */
return LV_FS_RES_TOUT;
case ENOSYS: /* Invalid system call number */
return LV_FS_RES_NOT_IMP;
case ENOMEM: /* Out of memory */
return LV_FS_RES_OUT_OF_MEM;
case EINVAL: /* "Invalid argument" */
return LV_FS_RES_INV_PARAM;
default:
break;
}
return LV_FS_RES_UNKNOWN;
}
#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*/

View File

@@ -0,0 +1,349 @@
/**
* @file lv_fs_stdio.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../../lvgl.h"
#if LV_USE_FS_STDIO
#include <stdio.h>
#ifndef WIN32
#include <dirent.h>
#include <unistd.h>
#else
#include <windows.h>
#endif
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#if !LV_FS_IS_VALID_LETTER(LV_FS_STDIO_LETTER)
#error "Invalid drive letter"
#endif
/**********************
* TYPEDEFS
**********************/
typedef struct {
#ifdef _WIN32
HANDLE dir_p;
char next_fn[LV_FS_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, uint32_t fn_len);
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
*--------------------------------------------------*/
lv_fs_drv_t * fs_drv_p = &(LV_GLOBAL_DEFAULT()->stdio_fs_drv);
lv_fs_drv_init(fs_drv_p);
/*Set up fields...*/
fs_drv_p->letter = LV_FS_STDIO_LETTER;
fs_drv_p->cache_size = LV_FS_STDIO_CACHE_SIZE;
fs_drv_p->open_cb = fs_open;
fs_drv_p->close_cb = fs_close;
fs_drv_p->read_cb = fs_read;
fs_drv_p->write_cb = fs_write;
fs_drv_p->seek_cb = fs_seek;
fs_drv_p->tell_cb = fs_tell;
fs_drv_p->dir_close_cb = fs_dir_close;
fs_drv_p->dir_open_cb = fs_dir_open;
fs_drv_p->dir_read_cb = fs_dir_read;
lv_fs_drv_register(fs_drv_p);
}
/**********************
* 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[LV_FS_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);
int w;
switch(whence) {
case LV_FS_SEEK_SET:
w = SEEK_SET;
break;
case LV_FS_SEEK_CUR:
w = SEEK_CUR;
break;
case LV_FS_SEEK_END:
w = SEEK_END;
break;
default:
return LV_FS_RES_INV_PARAM;
}
fseek(file_p, pos, w);
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 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_malloc(sizeof(dir_handle_t));
#ifndef WIN32
/*Make the path relative to the current directory (the projects root folder)*/
char buf[LV_FS_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_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[LV_FS_MAX_PATH_LEN];
lv_snprintf(buf, sizeof(buf), LV_FS_STDIO_PATH "%s\\*", path);
lv_strcpy(handle->next_fn, "");
handle->dir_p = FindFirstFileA(buf, &fdata);
do {
if(lv_strcmp(fdata.cFileName, ".") == 0 || lv_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_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
* @param fn_len length of the 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, uint32_t fn_len)
{
LV_UNUSED(drv);
if(fn_len == 0) return LV_FS_RES_INV_PARAM;
dir_handle_t * handle = (dir_handle_t *)dir_p;
#ifndef WIN32
struct dirent * entry;
do {
entry = readdir(handle->dir_p);
if(entry) {
/*Note, DT_DIR is not defined in C99*/
if(entry->d_type == DT_DIR) lv_snprintf(fn, fn_len, "/%s", entry->d_name);
else lv_strlcpy(fn, entry->d_name, fn_len);
}
else {
lv_strlcpy(fn, "", fn_len);
}
} while(lv_strcmp(fn, "/.") == 0 || lv_strcmp(fn, "/..") == 0);
#else
lv_strlcpy(fn, handle->next_fn, fn_len);
lv_strcpy(handle->next_fn, "");
WIN32_FIND_DATAA fdata;
if(FindNextFileA(handle->dir_p, &fdata) == false) return LV_FS_RES_OK;
do {
if(lv_strcmp(fdata.cFileName, ".") == 0 || lv_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_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*/

View File

@@ -0,0 +1,607 @@
/**
* @file lv_fs_uefi.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../../lvgl.h"
#if LV_USE_FS_UEFI && LV_USE_UEFI
#include "../../drivers/uefi/lv_uefi_private.h"
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#if LV_FS_UEFI_LETTER == '\0'
#error "LV_FS_UEFI_LETTER must be set to a valid value"
#else
#if (LV_FS_UEFI_LETTER < 'A') || (LV_FS_UEFI_LETTER > 'Z')
#if LV_FS_DEFAULT_DRIVE_LETTER != '\0' /* When using default driver-identifier letter, strict format (X:) is mandatory. */
#error "LV_FS_UEFI_LETTER must be an upper case ASCII letter"
#else /*Lean rules for backward compatibility*/
#warning LV_FS_UEFI_LETTER should be an upper case ASCII letter. \
Using a slash symbol as driver - identifier letter should be replaced with LV_FS_DEFAULT_DRIVE_LETTER mechanism.
#endif
#endif
#endif
/**********************
* TYPEDEFS
**********************/
typedef struct _lv_uefi_fs_file_context_t {
EFI_FILE_PROTOCOL * interface;
} lv_uefi_fs_file_context_t;
/**********************
* STATIC PROTOTYPES
**********************/
static void lv_fs_drv_uefi_init(lv_fs_drv_t * drv, char fs_drive_letter, EFI_HANDLE fs_handle);
static void lv_fs_drv_uefi_deinit(lv_fs_drv_t * drv);
static void lv_fs_uefi_lvgl_path_to_uefi_path(CHAR16 * path);
static void lv_fs_uefi_uefi_path_to_lvgl_path(CHAR16 * path);
static bool lv_fs_uefi_is_dot_path(CONST CHAR16 * path);
static bool lv_fs_uefi_is_dir(EFI_FILE_PROTOCOL * dir);
static bool lv_fs_uefi_is_file(EFI_FILE_PROTOCOL * file);
static EFI_FILE_INFO * lv_fs_uefi_get_info(EFI_FILE_PROTOCOL * file);
static bool lv_fs_uefi_ready_cb(lv_fs_drv_t * drv);
static void * lv_fs_uefi_open_cb(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode);
static lv_fs_res_t lv_fs_uefi_close_cb(lv_fs_drv_t * drv, void * file_p);
static lv_fs_res_t lv_fs_uefi_read_cb(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br);
static lv_fs_res_t lv_fs_uefi_write_cb(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw);
static lv_fs_res_t lv_fs_uefi_seek_cb(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence);
static lv_fs_res_t lv_fs_uefi_tell_cb(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p);
static void * lv_fs_uefi_dir_open_cb(lv_fs_drv_t * drv, const char * path);
static lv_fs_res_t lv_fs_uefi_dir_read_cb(lv_fs_drv_t * drv, void * rddir_p, char * fn, uint32_t fn_len);
static lv_fs_res_t lv_fs_uefi_dir_close_cb(lv_fs_drv_t * drv, void * rddir_p);
/**********************
* STATIC VARIABLES
**********************/
static EFI_GUID _uefi_guid_simple_file_system = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID;
static EFI_GUID _uefi_guid_loaded_image = EFI_LOADED_IMAGE_PROTOCOL_GUID;
static EFI_GUID _uefi_guid_file_info = EFI_FILE_INFO_ID;
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* Register a driver for the File system interface
*/
void lv_fs_uefi_init(void)
{
EFI_LOADED_IMAGE_PROTOCOL * interface_loaded_image = NULL;
EFI_HANDLE fs_handle = NULL;
/*---------------------------------------------------
* Register the file system interface in LVGL
*--------------------------------------------------*/
interface_loaded_image = lv_uefi_protocol_open(gLvEfiImageHandle, &_uefi_guid_loaded_image);
LV_ASSERT_NULL(interface_loaded_image);
fs_handle = interface_loaded_image->DeviceHandle;
if(fs_handle == NULL) return;
lv_uefi_protocol_close(gLvEfiImageHandle, &_uefi_guid_loaded_image);
/*Add a simple driver to open images*/
lv_fs_drv_t * fs_drv_p = &(LV_GLOBAL_DEFAULT()->uefi_fs_drv);
lv_fs_drv_uefi_init(fs_drv_p, LV_FS_UEFI_LETTER, fs_handle);
lv_fs_drv_register(fs_drv_p);
}
/**********************
* STATIC FUNCTIONS
**********************/
static bool lv_fs_uefi_ready_cb(lv_fs_drv_t * drv)
{
EFI_HANDLE fs_handle = (EFI_HANDLE)drv->user_data;
return fs_handle != NULL;
}
static void * lv_fs_uefi_open_cb(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode)
{
EFI_STATUS status;
EFI_FILE_PROTOCOL * fs_root = NULL;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL * fs_interface = NULL;
CHAR16 path_ucs2[LV_FS_MAX_PATH_LENGTH + 1];
lv_uefi_fs_file_context_t * file_ctx = NULL;
UINT64 uefi_mode = 0;
EFI_HANDLE fs_handle = (EFI_HANDLE)drv->user_data;
fs_interface = lv_uefi_protocol_open(fs_handle, &_uefi_guid_simple_file_system);
if(fs_interface == NULL) {
LV_LOG_WARN("[lv_uefi] Unable to open file system protocol.");
goto error;
}
status = fs_interface->OpenVolume(fs_interface, &fs_root);
if(status != EFI_SUCCESS) {
LV_LOG_WARN("[lv_uefi] Unable to open file system root.");
goto error;
}
if(lv_uefi_ascii_to_ucs2(path, path_ucs2, LV_FS_MAX_PATH_LENGTH + 1) == 0) {
LV_LOG_WARN("[lv_uefi] Unable to convert the ASCII path into an UCS-2 path.");
goto error;
}
lv_fs_uefi_lvgl_path_to_uefi_path(path_ucs2);
file_ctx = lv_calloc(1, sizeof(lv_uefi_fs_file_context_t));
LV_ASSERT_MALLOC(file_ctx);
if(mode == LV_FS_MODE_WR) {
uefi_mode = EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE;
}
else {
uefi_mode = EFI_FILE_MODE_READ;
}
status = fs_root->Open(
fs_root,
&file_ctx->interface,
path_ucs2,
uefi_mode,
0);
if(status != EFI_SUCCESS) {
LV_LOG_WARN("[lv_uefi] Unable to open file '%s'.", path);
goto error;
}
if(!lv_fs_uefi_is_file(file_ctx->interface)) {
goto error;
}
goto finish;
error:
if(file_ctx != NULL) {
if(file_ctx->interface != NULL) file_ctx->interface->Close(file_ctx->interface);
lv_free(file_ctx);
file_ctx = NULL;
}
finish:
if(fs_interface != NULL) lv_uefi_protocol_close(fs_handle, &_uefi_guid_simple_file_system);
if(fs_root != NULL) fs_root->Close(fs_root);
return file_ctx;
}
static lv_fs_res_t lv_fs_uefi_close_cb(lv_fs_drv_t * drv, void * file_p)
{
EFI_STATUS status;
lv_uefi_fs_file_context_t * file_ctx = (lv_uefi_fs_file_context_t *)file_p;
if(file_ctx == NULL || file_ctx->interface == NULL) return LV_FS_RES_INV_PARAM;
status = file_ctx->interface->Close(file_ctx->interface);
if(status != EFI_SUCCESS) return LV_FS_RES_HW_ERR;
lv_free(file_ctx);
return LV_FS_RES_OK;
}
static lv_fs_res_t lv_fs_uefi_read_cb(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br)
{
EFI_STATUS status;
lv_uefi_fs_file_context_t * file_ctx = (lv_uefi_fs_file_context_t *)file_p;
UINTN buf_size = btr;
if(file_ctx == NULL || file_ctx->interface == NULL) return LV_FS_RES_INV_PARAM;
status = file_ctx->interface->Read(
file_ctx->interface,
&buf_size,
buf);
if(status != EFI_SUCCESS) return LV_FS_RES_HW_ERR;
*br = (uint32_t) buf_size;
return LV_FS_RES_OK;
}
static lv_fs_res_t lv_fs_uefi_write_cb(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw)
{
EFI_STATUS status;
lv_uefi_fs_file_context_t * file_ctx = (lv_uefi_fs_file_context_t *)file_p;
UINTN buf_size = btw;
if(file_ctx == NULL || file_ctx->interface == NULL) return LV_FS_RES_INV_PARAM;
status = file_ctx->interface->Write(
file_ctx->interface,
&buf_size,
(VOID *)buf);
if(status != EFI_SUCCESS) return LV_FS_RES_HW_ERR;
file_ctx->interface->Flush(file_ctx->interface);
*bw = (uint32_t) buf_size;
return LV_FS_RES_OK;
}
static lv_fs_res_t lv_fs_uefi_seek_cb(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence)
{
EFI_STATUS status;
lv_uefi_fs_file_context_t * file_ctx = (lv_uefi_fs_file_context_t *)file_p;
UINT64 new_pos;
if(file_ctx == NULL || file_ctx->interface == NULL) return LV_FS_RES_INV_PARAM;
if(whence == LV_FS_SEEK_END) {
status = file_ctx->interface->SetPosition(
file_ctx->interface,
UINT64_MAX);
if(status != EFI_SUCCESS) return LV_FS_RES_HW_ERR;
status = file_ctx->interface->GetPosition(
file_ctx->interface,
&new_pos);
if(status != EFI_SUCCESS) return LV_FS_RES_HW_ERR;
if(new_pos < pos) {
new_pos = 0;
}
else {
new_pos -= pos;
}
}
else if(whence == LV_FS_SEEK_SET) {
new_pos = pos;
}
else if(whence == LV_FS_SEEK_CUR) {
status = file_ctx->interface->GetPosition(
file_ctx->interface,
&new_pos);
if(status != EFI_SUCCESS) return LV_FS_RES_HW_ERR;
new_pos += pos;
}
else {
return LV_FS_RES_INV_PARAM;
}
status = file_ctx->interface->SetPosition(
file_ctx->interface,
new_pos);
if(status != EFI_SUCCESS) return LV_FS_RES_HW_ERR;
return LV_FS_RES_OK;
}
static lv_fs_res_t lv_fs_uefi_tell_cb(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p)
{
EFI_STATUS status;
lv_uefi_fs_file_context_t * file_ctx = (lv_uefi_fs_file_context_t *)file_p;
UINT64 pos;
if(file_ctx == NULL || file_ctx->interface == NULL) return LV_FS_RES_INV_PARAM;
status = file_ctx->interface->GetPosition(
file_ctx->interface,
&pos);
if(status != EFI_SUCCESS) return LV_FS_RES_HW_ERR;
if(pos > UINT32_MAX) return LV_FS_RES_UNKNOWN;
*pos_p = (uint32_t) pos;
return LV_FS_RES_OK;
}
static void * lv_fs_uefi_dir_open_cb(lv_fs_drv_t * drv, const char * path)
{
EFI_STATUS status;
EFI_FILE_PROTOCOL * fs_root = NULL;
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL * fs_interface = NULL;
CHAR16 path_ucs2[LV_FS_MAX_PATH_LENGTH + 1];
lv_uefi_fs_file_context_t * file_ctx = NULL;
UINT64 mode = 0;
UINT64 attributes = 0;
EFI_HANDLE fs_handle = (EFI_HANDLE)drv->user_data;
fs_interface = lv_uefi_protocol_open(fs_handle, &_uefi_guid_simple_file_system);
if(fs_interface == NULL) {
LV_LOG_WARN("[lv_uefi] Unable to open file system protocol.");
goto error;
}
status = fs_interface->OpenVolume(fs_interface, &fs_root);
if(status != EFI_SUCCESS) {
LV_LOG_WARN("[lv_uefi] Unable to open file system root.");
goto error;
}
if(path != NULL && path[0] != '\0') {
if(lv_uefi_ascii_to_ucs2(path, path_ucs2, LV_FS_MAX_PATH_LENGTH + 1) == 0) {
LV_LOG_WARN("[lv_uefi] Unable to convert the ASCII path into an UCS-2 path.");
goto error;
}
}
else {
path_ucs2[0] = '\\';
path_ucs2[1] = '\0';
}
lv_fs_uefi_lvgl_path_to_uefi_path(path_ucs2);
file_ctx = lv_calloc(1, sizeof(lv_uefi_fs_file_context_t));
LV_ASSERT_MALLOC(file_ctx);
mode = EFI_FILE_MODE_CREATE | EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE;
attributes = EFI_FILE_DIRECTORY;
status = fs_root->Open(
fs_root,
&file_ctx->interface,
path_ucs2,
mode,
attributes);
if(status != EFI_SUCCESS) {
LV_LOG_WARN("[lv_uefi] Unable to open directory '%s'.", path);
goto error;
}
if(!lv_fs_uefi_is_dir(file_ctx->interface)) {
goto error;
}
goto finish;
error:
if(file_ctx != NULL) {
if(file_ctx->interface != NULL) {
file_ctx->interface->Close(file_ctx->interface);
}
lv_free(file_ctx);
file_ctx = NULL;
}
finish:
if(fs_interface != NULL) lv_uefi_protocol_close(fs_handle, &_uefi_guid_simple_file_system);
if(fs_root != NULL) fs_root->Close(fs_root);
return file_ctx;
}
static lv_fs_res_t lv_fs_uefi_dir_read_cb(lv_fs_drv_t * drv, void * rddir_p, char * fn, uint32_t fn_len)
{
lv_fs_res_t return_code;
EFI_STATUS status;
lv_uefi_fs_file_context_t * file_ctx = (lv_uefi_fs_file_context_t *)rddir_p;
EFI_FILE_INFO * info = NULL;
UINTN size;
CONST CHAR16 * fn_ucs2;
if(fn == NULL || fn_len == 0) return LV_FS_RES_INV_PARAM;
if(file_ctx == NULL || file_ctx->interface == NULL) return LV_FS_RES_INV_PARAM;
// skip . and ..
do {
if(info != NULL) lv_free(info);
info = NULL;
size = 0;
status = file_ctx->interface->Read(
file_ctx->interface,
&size,
info);
if(status == EFI_SUCCESS && size == 0) {
return_code = LV_FS_RES_OK;
*fn = '\0';
goto finish;
}
else if(status != EFI_BUFFER_TOO_SMALL) {
return_code = LV_FS_RES_NOT_EX;
goto error;
}
info = lv_calloc(1, size);
LV_ASSERT_MALLOC(info);
status = file_ctx->interface->Read(
file_ctx->interface,
&size,
info);
if(status != EFI_SUCCESS) {
return_code = LV_FS_RES_HW_ERR;
goto error;
}
} while(lv_fs_uefi_is_dot_path(info->FileName));
lv_fs_uefi_uefi_path_to_lvgl_path(info->FileName);
// skip leading \ and /
for(fn_ucs2 = info->FileName; *fn_ucs2 != L'\0'; fn_ucs2++) {
if(*fn_ucs2 != L'\\' && *fn_ucs2 != L'/') {
break;
}
}
if((info->Attribute & EFI_FILE_DIRECTORY) != 0) {
if(fn_len == 0) {
return_code = LV_FS_RES_UNKNOWN;
goto error;
}
fn[0] = '/';
fn++;
fn_len--;
}
if(lv_uefi_ucs2_to_ascii(fn_ucs2, fn, fn_len) == 0) {
LV_LOG_WARN("[lv_uefi] Unable to convert the UCS-2 path into an ascii path.");
return_code = LV_FS_RES_UNKNOWN;
goto error;
}
return_code = LV_FS_RES_OK;
goto finish;
error:
finish:
if(info) lv_free(info);
return return_code;
}
static lv_fs_res_t lv_fs_uefi_dir_close_cb(lv_fs_drv_t * drv, void * rddir_p)
{
EFI_STATUS status;
lv_uefi_fs_file_context_t * file_ctx = (lv_uefi_fs_file_context_t *)rddir_p;
if(file_ctx == NULL || file_ctx->interface == NULL) return LV_FS_RES_INV_PARAM;
status = file_ctx->interface->Close(file_ctx->interface);
if(status != EFI_SUCCESS) return LV_FS_RES_HW_ERR;
lv_free(file_ctx);
return LV_FS_RES_OK;
}
static void lv_fs_drv_uefi_init(lv_fs_drv_t * drv, char fs_drive_letter, EFI_HANDLE fs_handle)
{
LV_ASSERT_NULL(drv);
LV_ASSERT_NULL(fs_handle);
lv_fs_drv_init(drv);
drv->letter = fs_drive_letter;
drv->cache_size = 0;
drv->ready_cb = lv_fs_uefi_ready_cb;
drv->open_cb = lv_fs_uefi_open_cb;
drv->close_cb = lv_fs_uefi_close_cb;
drv->read_cb = lv_fs_uefi_read_cb;
drv->write_cb = lv_fs_uefi_write_cb;
drv->seek_cb = lv_fs_uefi_seek_cb;
drv->tell_cb = lv_fs_uefi_tell_cb;
drv->dir_open_cb = lv_fs_uefi_dir_open_cb;
drv->dir_read_cb = lv_fs_uefi_dir_read_cb;
drv->dir_close_cb = lv_fs_uefi_dir_close_cb;
drv->user_data = (void *) fs_handle;
}
static void lv_fs_drv_uefi_deinit(lv_fs_drv_t * drv)
{
LV_ASSERT_NULL(drv);
drv->user_data = NULL;
}
static void lv_fs_uefi_lvgl_path_to_uefi_path(CHAR16 * path)
{
if(path == NULL) return;
for(; *path != '\0'; path++) {
if(*path == L'/') *path = L'\\';
}
}
static void lv_fs_uefi_uefi_path_to_lvgl_path(CHAR16 * path)
{
if(path == NULL) return;
for(; *path != '\0'; path++) {
if(*path == L'\\') *path = L'/';
}
}
static bool lv_fs_uefi_is_dot_path(CONST CHAR16 * path)
{
if(path == NULL) return FALSE;
if(path[0] == L'.' && path[1] == L'\0') return TRUE;
if(path[0] == L'.' && path[1] == L'.' && path[2] == L'\0') return TRUE;
return FALSE;
}
static bool lv_fs_uefi_is_dir(EFI_FILE_PROTOCOL * dir)
{
UINT64 attributes;
if(dir == NULL) return FALSE;
EFI_FILE_INFO * info = lv_fs_uefi_get_info(dir);
if(info == NULL) return FALSE;
attributes = info->Attribute;
lv_free(info);
return (attributes & EFI_FILE_DIRECTORY) != 0;
}
static bool lv_fs_uefi_is_file(EFI_FILE_PROTOCOL * file)
{
UINT64 attributes;
if(file == NULL) return FALSE;
EFI_FILE_INFO * info = lv_fs_uefi_get_info(file);
if(info == NULL) return FALSE;
attributes = info->Attribute;
lv_free(info);
return (attributes & EFI_FILE_DIRECTORY) == 0;
}
static EFI_FILE_INFO * lv_fs_uefi_get_info(EFI_FILE_PROTOCOL * file)
{
EFI_STATUS status;
EFI_FILE_INFO * info = NULL;
UINTN size = 0;
status = file->GetInfo(file, &_uefi_guid_file_info, &size, info);
if(status != EFI_BUFFER_TOO_SMALL) return NULL;
info = lv_calloc(1, size);
LV_ASSERT_MALLOC(info);
status = file->GetInfo(file, &_uefi_guid_file_info, &size, info);
if(status != EFI_SUCCESS) {
lv_free(info);
return NULL;
}
return info;
}
#else /* LV_FS_UEFI_LETTER == 0*/
#if defined(LV_FS_UEFI_LETTER) && LV_FS_UEFI_LETTER != '\0'
#warning "LV_FS_UEFI is not enabled but LV_FS_UEFI_LETTER is set"
#endif
#endif

View File

@@ -0,0 +1,472 @@
/**
* @file lv_fs_win32.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../../lvgl.h"
#if LV_USE_FS_WIN32
#include <windows.h>
#include <stdio.h>
#include <limits.h>
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#if !LV_FS_IS_VALID_LETTER(LV_FS_WIN32_LETTER)
#error "Invalid drive letter"
#endif
/**********************
* TYPEDEFS
**********************/
typedef struct {
HANDLE dir_p;
char next_fn[LV_FS_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, uint32_t fn_len);
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*/
lv_fs_drv_t * fs_drv_p = &(LV_GLOBAL_DEFAULT()->win32_fs_drv);
lv_fs_drv_init(fs_drv_p);
/*Set up fields...*/
fs_drv_p->letter = LV_FS_WIN32_LETTER;
fs_drv_p->cache_size = LV_FS_WIN32_CACHE_SIZE;
fs_drv_p->open_cb = fs_open;
fs_drv_p->close_cb = fs_close;
fs_drv_p->read_cb = fs_read;
fs_drv_p->write_cb = fs_write;
fs_drv_p->seek_cb = fs_seek;
fs_drv_p->tell_cb = fs_tell;
fs_drv_p->dir_close_cb = fs_dir_close;
fs_drv_p->dir_open_cb = fs_dir_open;
fs_drv_p->dir_read_cb = fs_dir_read;
lv_fs_drv_register(fs_drv_p);
}
/**********************
* 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 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_malloc(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[LV_FS_MAX_PATH_LEN];
lv_snprintf(buf, sizeof(buf), LV_FS_WIN32_PATH "%s\\*", path);
lv_strcpy(handle->next_fn, "");
handle->dir_p = FindFirstFileA(buf, &fdata);
if(handle->dir_p != INVALID_HANDLE_VALUE) {
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_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
* @param fn_len length of the 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, uint32_t fn_len)
{
LV_UNUSED(drv);
if(fn_len == 0) return LV_FS_RES_INV_PARAM;
dir_handle_t * handle = (dir_handle_t *)dir_p;
lv_strlcpy(fn, handle->next_fn, fn_len);
lv_fs_res_t current_error = handle->next_error;
lv_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_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

View File

@@ -0,0 +1,119 @@
/**
* @file lv_fsdrv.h
*
*/
#ifndef LV_FSDRV_H
#define LV_FSDRV_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
/*********************
* DEFINES
*********************/
#define LV_FS_MAX_PATH_LEN 256
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
#if LV_USE_FS_FATFS
void lv_fs_fatfs_init(void);
#endif
#if LV_USE_FS_STDIO
void lv_fs_stdio_init(void);
#endif
#if LV_USE_FS_POSIX
void lv_fs_posix_init(void);
#endif
#if LV_USE_FS_WIN32
void lv_fs_win32_init(void);
#endif
#if LV_USE_FS_MEMFS
void lv_fs_memfs_init(void);
#endif
#if LV_USE_FS_LITTLEFS
#include "lfs.h"
struct lfs;
/**
* Set the default LittleFS handler to be used by LVGL
* @param lfs pointer to an initialized LittleFS filesystem structure
*/
void lv_littlefs_set_handler(struct lfs * lfs);
/**
* Initialize LittleFS file system driver
*/
void lv_fs_littlefs_init(void);
/**
* Register a LittleFS drive with LVGL
* @param lfs pointer to an initialized LittleFS filesystem structure
* @param letter driver letter to register (e.g. 'A')
* @return LV_FS_RES_OK: success, LV_FS_RES_INV_PARAM: lfs is NULL or letter not in range A-Z,
* LV_FS_RES_DRIVE_LETTER_ALREADY_USED: A drive with this letter is already registered
*/
lv_fs_res_t lv_fs_littlefs_register_drive(lfs_t * lfs, char letter);
#endif
#if LV_USE_FS_ARDUINO_ESP_LITTLEFS
void lv_fs_arduino_esp_littlefs_init(void);
#endif
#if LV_USE_FS_ARDUINO_SD
void lv_fs_arduino_sd_init(void);
#endif
#if LV_USE_FS_UEFI
void lv_fs_uefi_init(void);
#endif
#if LV_USE_FS_FROGFS
void lv_fs_frogfs_init(void);
void lv_fs_frogfs_deinit(void);
/**
* Mount a frogfs blob at the path prefix. If there is a file "foo.txt"
* in the blob and the blob is registered with `path_prefix` as "my_blob",
* it can be opened later at path "my_blob/foo.txt".
* @param blob a frogfs blob/image from mkfrogfs.py
* @param path_prefix a prefix that will be used to refer to this blob when accessing it.
* @return LV_RESULT_OK or LV_RESULT_INVALID if there was an issue with the blob
*/
lv_result_t lv_fs_frogfs_register_blob(const void * blob, const char * path_prefix);
/**
* Unmount a frogfs blob that was previously mounted by `lv_fs_frogfs_register_blob`.
* All files and dirs should be closed before calling this.
* @param path_prefix the path prefix that the blob was registered with
*/
void lv_fs_frogfs_unregister_blob(const char * path_prefix);
#endif /*LV_USE_FS_FROGFS*/
/**********************
* MACROS
**********************/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /*LV_FSDRV_H*/

View File

@@ -0,0 +1,229 @@
// Copyright 2020 BitBank Software, Inc. All Rights Reserved.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//===========================================================================
#ifndef __ANIMATEDGIF__
#define __ANIMATEDGIF__
#include "../../lv_conf_internal.h"
#if LV_USE_GIF
#include "../../misc/lv_fs.h"
//
// GIF Animator
// Written by Larry Bank
// Copyright (c) 2020 BitBank Software, Inc.
// bitbank@pobox.com
//
// Designed to decode images up to 480x320 on MCUs
// using less than 22K of RAM
// ...and decode any sized image when more RAM is available
//
// ** NEW **
// Turbo mode added Feb 18, 2024. This option decodes images
// up to 30x faster if there is enough RAM (48K + full framebuffer)
//
/* GIF Defines and variables */
#define MAX_CHUNK_SIZE 255
//
// These 2 macros can be changed to limit the amount of RAM
// required by the decoder. For example, decoding 1-bit images to
// a 128x32 display will not need a max code size of 12 nor a palette
// with 256 entries
//
#define TURBO_BUFFER_SIZE 0x6100
// If you intend to decode generic GIFs, you want this value to be 12. If you are using GIFs solely for animations in
// your own project, and you control the GIFs you intend to play, then you can save additional RAM here:
// the decoder must reserve a minimum of 4 byte * (1<<MAX_CODE_SIZE) for the dictionary, but based on implementation
// actually reserves 5 byte * (1<<MAX_CODE_SIZE). Small or low colour GIFs may inherently not require a large
// dictionary. For larger GIFs, the en(!)coder can "voluntarily" choose not to utilize the entire dictionary. I.e.,
// by preparing (specially encoding) the GIFs, you can save >10kB RAM, but you will not be able to decode arbitrary
// images anymore. One application to craft such GIFs can be found here (use option -d)
// https://create.stephan-brumme.com/flexigif-lossless-gif-lzw-optimization/
#define MAX_CODE_SIZE 12
#define MAX_COLORS 256
#define MAX_WIDTH 480
#define LZW_BUF_SIZE (6*MAX_CHUNK_SIZE)
#define LZW_HIGHWATER (4*MAX_CHUNK_SIZE)
// This buffer is used to store the pixel sequence in reverse order
// it needs to be large enough to hold the longest possible
// sequence (1<<MAX_CODE_SIZE)
#define FILE_BUF_SIZE (1<<MAX_CODE_SIZE)
#define PIXEL_FIRST 0
#define PIXEL_LAST (1<<MAX_CODE_SIZE)
#define LINK_UNUSED 5911 // 0x1717 to use memset
#define LINK_END 5912
#define MAX_HASH 5003
// expanded LZW buffer for Turbo mode
#define LZW_BUF_SIZE_TURBO (LZW_BUF_SIZE + (2<<MAX_CODE_SIZE) + (PIXEL_LAST*2) + MAX_WIDTH)
#define LZW_HIGHWATER_TURBO ((LZW_BUF_SIZE_TURBO * 14) / 16)
//
// Pixel types
//
enum {
GIF_PALETTE_RGB565_LE = 0, // little endian (default)
GIF_PALETTE_RGB565_BE, // big endian
GIF_PALETTE_RGB888, // original 24-bpp entries
GIF_PALETTE_RGB8888, // 32-bit (alpha = 0xff or 0x00)
GIF_PALETTE_1BPP, // 1-bit per pixel (horizontal, MSB on left)
GIF_PALETTE_1BPP_OLED // 1-bit per pixel (vertical, LSB on top)
};
// for compatibility with older code
#define LITTLE_ENDIAN_PIXELS GIF_PALETTE_RGB565_LE
#define BIG_ENDIAN_PIXELS GIF_PALETTE_RGB565_BE
//
// Draw types
//
// RAW = 8-bit palettized pixels requiring transparent pixel handling and conversion through the palette.
// Each line is sent to the GIFDraw callback as 8-bit pixels. If a framebuffer exists, the lines will be
// written there too. The GIFDraw callback is optional if there is a framebuffer allocated.
//
// COOKED = 16/24/32-bpp fully rendered pixels ready for display. This requires a full frame buffer with extra
// room for the fully rendered pixels at the end of the 8-bit pixel buffer. For example, a 160x120
// canvas size with 24-bit output would require (160*120 + 3*160) bytes.
// Each prepared line is sent to the GIFDraw callback as a row of 16/24/32-bit pixels.
//
enum {
GIF_DRAW_RAW = 0,
GIF_DRAW_COOKED
};
enum {
GIF_SUCCESS = 0,
GIF_DECODE_ERROR,
GIF_TOO_WIDE,
GIF_INVALID_PARAMETER,
GIF_UNSUPPORTED_FEATURE,
GIF_FILE_NOT_OPEN,
GIF_EARLY_EOF,
GIF_EMPTY_FRAME,
GIF_BAD_FILE,
GIF_ERROR_MEMORY
};
typedef struct gif_file_tag
{
int32_t iPos; // current file position
int32_t iSize; // file size
uint8_t *pData; // memory file pointer
lv_fs_file_t fHandle; // class pointer to File/SdFat or whatever you want
} GIFFILE;
typedef struct gif_info_tag
{
int32_t iFrameCount; // total frames in file
int32_t iDuration; // duration of animation in milliseconds
int32_t iMaxDelay; // maximum frame delay
int32_t iMinDelay; // minimum frame delay
} GIFINFO;
typedef struct gif_draw_tag
{
int iX, iY; // Corner offset of this frame on the canvas
int y; // current line being drawn (0 = top line of image)
int iWidth, iHeight; // size of this frame
int iCanvasWidth; // need this to know where to place output in a fully cooked bitmap
void *pUser; // user supplied pointer
uint8_t *pPixels; // 8-bit source pixels for this line
uint16_t *pPalette; // little or big-endian RGB565 palette entries (default)
uint8_t *pPalette24; // RGB888 palette (optional)
uint8_t ucTransparent; // transparent color
uint8_t ucHasTransparency; // flag indicating the transparent color is in use
uint8_t ucDisposalMethod; // frame disposal method
uint8_t ucBackground; // background color
uint8_t ucPaletteType; // type of palette entries
uint8_t ucIsGlobalPalette; // Flag to indicate that a global palette, rather than a local palette is being used
} GIFDRAW;
// Callback function prototypes
typedef int32_t (GIF_READ_CALLBACK)(GIFFILE *pFile, uint8_t *pBuf, int32_t iLen);
typedef int32_t (GIF_SEEK_CALLBACK)(GIFFILE *pFile, int32_t iPosition);
typedef void (GIF_DRAW_CALLBACK)(GIFDRAW *pDraw);
typedef void * (GIF_OPEN_CALLBACK)(const char *szFilename, int32_t *pFileSize);
typedef void (GIF_CLOSE_CALLBACK)(lv_fs_file_t *pHandle);
typedef void * (GIF_ALLOC_CALLBACK)(uint32_t iSize);
typedef void (GIF_FREE_CALLBACK)(void *buffer);
//
// our private structure to hold a GIF image decode state
//
typedef struct gif_image_tag
{
uint16_t iWidth, iHeight, iCanvasWidth, iCanvasHeight;
uint16_t iX, iY; // GIF corner offset
uint16_t iBpp;
int16_t iError; // last error
uint16_t iFrameDelay; // delay in milliseconds for this frame
int16_t iRepeatCount; // NETSCAPE animation repeat count. 0=forever
uint16_t iXCount, iYCount; // decoding position in image (countdown values)
int iLZWOff; // current LZW data offset
int iLZWSize; // current quantity of data in the LZW buffer
int iCommentPos; // file offset of start of comment data
short sCommentLen; // length of comment
unsigned char bEndOfFrame;
unsigned char ucGIFBits, ucBackground, ucTransparent, ucCodeStart, ucMap, bUseLocalPalette;
unsigned char ucPaletteType; // RGB565 or RGB888
unsigned char ucDrawType; // RAW or COOKED
GIF_READ_CALLBACK *pfnRead;
GIF_SEEK_CALLBACK *pfnSeek;
GIF_DRAW_CALLBACK *pfnDraw;
GIF_OPEN_CALLBACK *pfnOpen;
GIF_CLOSE_CALLBACK *pfnClose;
GIFFILE GIFFile;
void *pUser;
unsigned char *pFrameBuffer;
unsigned char *pTurboBuffer;
unsigned char *pPixels, *pOldPixels;
unsigned char ucFileBuf[FILE_BUF_SIZE]; // holds temp data and pixel stack
unsigned short pPalette[(MAX_COLORS * 3)/2]; // can hold RGB565 or RGB888 - set in begin()
unsigned short pLocalPalette[(MAX_COLORS * 3)/2]; // color palettes for GIF images
unsigned char ucLZW[LZW_BUF_SIZE]; // holds de-chunked LZW data
// These next 3 are used in Turbo mode to have a larger ucLZW buffer
unsigned short usGIFTable[1<<MAX_CODE_SIZE];
unsigned char ucGIFPixels[(PIXEL_LAST*2)];
unsigned char ucLineBuf[MAX_WIDTH]; // current line
} GIFIMAGE;
// C interface
int GIF_openRAM(GIFIMAGE *pGIF, uint8_t *pData, int iDataSize, GIF_DRAW_CALLBACK *pfnDraw);
int GIF_openFile(GIFIMAGE *pGIF, const char *szFilename, GIF_DRAW_CALLBACK *pfnDraw);
void GIF_close(GIFIMAGE *pGIF);
void GIF_begin(GIFIMAGE *pGIF, unsigned char ucPaletteType);
void GIF_reset(GIFIMAGE *pGIF);
int GIF_playFrame(GIFIMAGE *pGIF, int *delayMilliseconds, void *pUser);
int GIF_getCanvasWidth(GIFIMAGE *pGIF);
int GIF_getCanvasHeight(GIFIMAGE *pGIF);
int GIF_getComment(GIFIMAGE *pGIF, char *destBuffer);
int GIF_getInfo(GIFIMAGE *pGIF, GIFINFO *pInfo);
int GIF_getLastError(GIFIMAGE *pGIF);
int GIF_getLoopCount(GIFIMAGE *pGIF);
#define REGISTER_WIDTH 32
#ifdef ALLOWS_UNALIGNED
#define INTELSHORT(p) (*(uint16_t *)p)
#define INTELLONG(p) (*(uint32_t *)p)
#else
// Due to unaligned memory causing an exception, we have to do these macros the slow way
#define INTELSHORT(p) ((*p) + (*(p+1)<<8))
#define INTELLONG(p) ((*p) + (*(p+1)<<8) + (*(p+2)<<16) + (*(p+3)<<24))
#endif // ALLOWS_UNALIGNED
#define BIGINT int32_t
#define BIGUINT uint32_t
#endif // LV_USE_GIF
#endif // __ANIMATEDGIF__

View File

@@ -0,0 +1,204 @@
Copyright 2020 BitBank Software, Inc. All rights reserved.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1523
inc/lvgl/src/libs/gif/gif.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,189 @@
/**
* @file lv_fastgltf.hpp
*
*/
/*********************
* INCLUDES
*********************/
#include "../../../lv_conf_internal.h"
#if LV_USE_GLTF
#include <fastgltf/math.hpp>
#include <fastgltf/util.hpp>
#include <fastgltf/types.hpp>
#include <fastgltf/tools.hpp>
#include <functional>
#include <string>
#include "../gltf_data/lv_gltf_data_internal.hpp"
namespace fastgltf
{
/**
* Computes the transform matrix for a given node a different way with less total operations
*/
FASTGLTF_EXPORT inline auto getFastLocalTransformMatrix(const Node & node)
{
return visit_exhaustive(visitor {
[&](const math::fmat4x4 & matrix)
{
return matrix;
},
[&](const TRS & trs)
{
math::fmat4x4 matrix = math::fmat4x4();
float sx = trs.scale[0], sy = trs.scale[1], sz = trs.scale[2];
float qx = trs.rotation[0], qy = trs.rotation[1], qz = trs.rotation[2], qw = trs.rotation[3];
float x2 = qx + qx, y2 = qy + qy, z2 = qz + qz;
float xx = qx * x2, xy = qx * y2, xz = qx * z2;
float yy = qy * y2, yz = qy * z2, zz = qz * z2;
float wx = qw * x2, wy = qw * y2, wz = qw * z2;
matrix[0][0] = (1 - (yy + zz)) * sx;
matrix[0][1] = (xy + wz) * sx;
matrix[0][2] = (xz - wy) * sx;
matrix[1][0] = (xy - wz) * sy;
matrix[1][1] = (1 - (xx + zz)) * sy;
matrix[1][2] = (yz + wx) * sy;
matrix[2][0] = (xz + wy) * sz;
matrix[2][1] = (yz - wx) * sz;
matrix[2][2] = (1 - (xx + yy)) * sz;
matrix[3][0] = trs.translation[0];
matrix[3][1] = trs.translation[1];
matrix[3][2] = trs.translation[2];
matrix[0][3] = 0.f;
matrix[1][3] = 0.f;
matrix[2][3] = 0.f;
matrix[3][3] = 1.f;
return matrix;
}
}, node.transform);
}
/**
* Attempts to remove the scale component of a 4x4 matrix transform. Will silently fail if
* any of the component scales is 0 or near zero (which they should never be).
*/
FASTGLTF_EXPORT inline void removeScale(fastgltf::math::fmat4x4 & matrix)
{
auto scale = math::fvec3(length(matrix.col(0)), length(matrix.col(1)), length(matrix.col(2)));
if((fabs(scale.x()) > 0.00001f) && (fabs(scale.y()) > 0.00001f) && (fabs(scale.z()) > 0.00001f)) {
matrix.col(0) /= scale.x();
matrix.col(1) /= scale.y();
matrix.col(2) /= scale.z();
}
}
FASTGLTF_EXPORT template <typename AssetType, typename Callback>
#if FASTGLTF_HAS_CONCEPTS
requires std::same_as<std::remove_cvref_t<AssetType>, Asset> &&
std::is_invocable_v<Callback, fastgltf::Node &, std::string &,
std::string &, std::size_t, std::size_t>
#endif
void namegen_iterate_scene_nodes(AssetType &&asset,
std::size_t sceneIndex,
Callback callback)
{
auto & scene = asset.scenes[sceneIndex];
std::string _id = std::string("");
std::string _ip = std::string("");
if(asset.scenes.size() > 1) {
_id = "scene_" + std::to_string(sceneIndex);
_ip = std::to_string(sceneIndex);
}
auto function = [&](std::size_t nodeIndex, std::string & parentId,
std::string & parentIp, std::size_t __child_index,
auto & self) -> void {
assert(asset.nodes.size() > nodeIndex);
auto & node = asset.nodes[nodeIndex];
std::string _nodeId =
parentId + std::string("/") + std::string(node.name);
std::string _nodeIp = parentIp + std::string(".") +
std::to_string(__child_index);
std::invoke(callback, node, _nodeId, _nodeIp, nodeIndex,
__child_index);
std::size_t ____child_index = 0;
for(auto & child : node.children) {
self(child, _nodeId, _nodeIp, ____child_index, self);
____child_index += 1;
}
};
std::size_t child_index = 0;
for(auto & sceneNode : scene.nodeIndices) {
function(sceneNode, _id, _ip, child_index, function);
child_index += 1;
}
}
FASTGLTF_EXPORT template <typename AssetType, typename Callback>
#if FASTGLTF_HAS_CONCEPTS
requires std::same_as<std::remove_cvref_t<AssetType>, Asset> &&
std::is_invocable_v<Callback, fastgltf::Node &, fastgltf::math::fmat4x4 &, fastgltf::math::fmat4x4 &>
#endif
void
findlight_iterate_scene_nodes(AssetType &&asset, std::size_t sceneIndex,
math::fmat4x4 * initial, Callback callback)
{
auto & scene = asset.scenes[sceneIndex];
auto function = [&](std::size_t nodeIndex,
math::fmat4x4 & parentWorldMatrix,
auto & self) -> void {
assert(asset.nodes.size() > nodeIndex);
auto & node = asset.nodes[nodeIndex];
auto _localMat = getFastLocalTransformMatrix(node);
std::invoke(callback, node, parentWorldMatrix, _localMat);
for(auto & child : node.children) {
math::fmat4x4 _parentWorldTemp =
parentWorldMatrix * _localMat;
self(child, _parentWorldTemp, self);
}
};
for(auto & sceneNode : scene.nodeIndices) {
auto tmat2 = fastgltf::math::fmat4x4(*initial);
function(sceneNode, tmat2, function);
}
}
FASTGLTF_EXPORT template <typename Callback>
inline void custom_iterate_scene_nodes(lv_gltf_model_t * model, std::size_t sceneIndex, math::fmat4x4 * initial,
Callback callback)
{
auto & scene = model->asset.scenes[sceneIndex];
auto invoke_cb = [&](std::size_t node_index, math::fmat4x4 & parent_world_matrix,
auto & self) -> void {
lv_gltf_model_node_t * node = (lv_gltf_model_node_t *)lv_array_at(&model->nodes, node_index);
fastgltf::Node & fastgltf_node = *node->fastgltf_node;
LV_ASSERT(node->fastgltf_node == &fastgltf_node);
auto local_matrix = getFastLocalTransformMatrix(fastgltf_node);
std::invoke(callback, node, parent_world_matrix, local_matrix);
uint32_t num_children = fastgltf_node.children.size();
if(num_children == 0) {
return;
}
math::fmat4x4 new_parent_world_matrix = parent_world_matrix * local_matrix;
if(num_children == 1)
{
self(fastgltf_node.children[0], new_parent_world_matrix, self);
return;
}
math::fmat4x4 parent_world_matrix_tmp_copy = math::fmat4x4(new_parent_world_matrix);
for(auto & child : fastgltf_node.children)
{
self(child, parent_world_matrix_tmp_copy, self);
}
};
for(size_t i = 0 ; i < scene.nodeIndices.size(); ++i) {
invoke_cb(scene.nodeIndices[i], *initial, invoke_cb);
}
}
}
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,289 @@
/**
* @file lv_gltf_data.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
#include <fastgltf/tools.hpp>
#include "../../../misc/lv_assert.h"
#include "../../../core/lv_obj_pos.h"
#include "../../../misc/lv_timer.h"
#include "../gltf_view/lv_gltf_view_internal.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static void update_animation_cb(lv_timer_t * timer);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_gltf_model_t * lv_gltf_data_create_internal(const char * gltf_path,
fastgltf::Asset asset)
{
lv_gltf_model_t * data = (lv_gltf_model_t *)lv_zalloc(sizeof(*data));
LV_ASSERT_MALLOC(data);
new(data) lv_gltf_model_t;
new(&data->asset) fastgltf::Asset(std::move(asset));
data->filename = gltf_path;
data->last_camera_index = -5;
data->last_anim_num = -5;
data->current_animation_max_time = 0;
data->local_timestamp = 0.0f;
data->last_material_index = 99999;
data->last_frame_was_antialiased = false;
data->last_frame_no_motion = false;
data->_last_frame_no_motion = false;
data->animation_update_timer = lv_timer_create(update_animation_cb, LV_DEF_REFR_PERIOD, data);
lv_timer_pause(data->animation_update_timer);
LV_ASSERT_NULL(data->animation_update_timer);
new(&data->node_transform_cache) NodeTransformMap();
new(&data->opaque_nodes_by_material_index) MaterialIndexMap();
new(&data->blended_nodes_by_material_index) MaterialIndexMap();
new(&data->validated_skins) LongVector();
new(&data->skin_tex) IntVector();
new(&data->local_mesh_to_center_points_by_primitive)
NodePrimCenterMap();
new(&data->node_by_light_index) NodeVector();
new(&data->meshes) std::vector<lv_gltf_mesh_data_t>();
new(&data->textures) std::vector<GLuint>();
lv_array_init(&data->compiled_shaders, 1, sizeof(lv_gltf_compiled_shader_t));
return data;
}
void lv_gltf_data_delete(lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
lv_timer_delete(data->animation_update_timer);
data->animation_update_timer = NULL;
lv_gltf_data_delete_textures(data);
uint32_t node_count = lv_array_size(&data->nodes);
for(uint32_t i = 0; i < node_count; ++i) {
lv_gltf_model_node_t * node = (lv_gltf_model_node_t *) lv_array_at(&data->nodes, i);
lv_gltf_model_node_deinit(node);
}
lv_array_deinit(&data->nodes);
lv_array_deinit(&data->compiled_shaders);
/* Explicitly call destructors for C++ objects initialized with placement new */
data->textures.~vector();
data->meshes.~vector();
data->node_by_light_index.~vector();
data->local_mesh_to_center_points_by_primitive.~map();
data->skin_tex.~vector();
data->validated_skins.~vector();
data->blended_nodes_by_material_index.~map();
data->opaque_nodes_by_material_index.~map();
data->node_transform_cache.~map();
data->channel_set_cache.~map();
data->asset.~Asset();
lv_free(data);
}
const char * lv_gltf_get_filename(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->filename;
}
size_t lv_gltf_model_get_image_count(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->asset.images.size();
}
size_t lv_gltf_model_get_texture_count(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->asset.textures.size();
}
GLuint lv_gltf_data_get_texture(lv_gltf_model_t * data, size_t index)
{
LV_ASSERT_NULL(data);
LV_ASSERT(index < data->textures.size());
return data->textures[index];
}
size_t lv_gltf_model_get_material_count(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->asset.materials.size();
}
size_t lv_gltf_model_get_camera_count(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->asset.cameras.size();
}
size_t lv_gltf_model_get_node_count(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->asset.nodes.size();
}
size_t lv_gltf_model_get_mesh_count(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->asset.meshes.size();
}
size_t lv_gltf_model_get_scene_count(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->asset.scenes.size();
}
size_t lv_gltf_model_get_animation_count(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->asset.animations.size();
}
lv_result_t lv_gltf_model_play_animation(lv_gltf_model_t * model, size_t index)
{
LV_ASSERT_NULL(model);
if(index >= model->asset.animations.size()) {
return LV_RESULT_INVALID;
}
if(lv_timer_get_paused(model->animation_update_timer)) {
model->last_tick = lv_tick_get();
lv_timer_resume(model->animation_update_timer);
}
model->current_animation_max_time = lv_gltf_data_get_animation_total_time(model, index);
model->current_animation = index;
model->is_animation_enabled = true;
return LV_RESULT_OK;
}
void lv_gltf_model_pause_animation(lv_gltf_model_t * model)
{
LV_ASSERT_NULL(model);
model->is_animation_enabled = false;
lv_timer_pause(model->animation_update_timer);
}
bool lv_gltf_model_is_animation_paused(lv_gltf_model_t * model)
{
LV_ASSERT_NULL(model);
return !model->is_animation_enabled;
}
size_t lv_gltf_model_get_animation(lv_gltf_model_t * model)
{
LV_ASSERT_NULL(model);
return model->current_animation;
}
lv_gltf_model_t *
lv_gltf_data_load_from_file(const char * file_path,
lv_opengl_shader_manager_t * shader_manager)
{
return lv_gltf_data_load_internal(file_path, 0, shader_manager);
}
lv_gltf_model_t *
lv_gltf_data_load_from_bytes(const uint8_t * data, size_t data_size,
lv_opengl_shader_manager_t * shader_manager)
{
return lv_gltf_data_load_internal(data, data_size, shader_manager);
}
fastgltf::Asset * lv_gltf_data_get_asset(lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return &data->asset;
}
double lv_gltf_data_get_radius(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->bound_radius;
}
fastgltf::math::fvec3 lv_gltf_data_get_center(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->vertex_cen;
}
fastgltf::math::fvec3 lv_gltf_data_get_bounds_min(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->vertex_min;
}
fastgltf::math::fvec3 lv_gltf_data_get_bounds_max(const lv_gltf_model_t * data)
{
LV_ASSERT_NULL(data);
return data->vertex_max;
}
void lv_gltf_data_copy_bounds_info(lv_gltf_model_t * to, lv_gltf_model_t * from)
{
{
to->vertex_min[0] = from->vertex_min[0];
to->vertex_min[1] = from->vertex_min[1];
to->vertex_min[2] = from->vertex_min[2];
}
{
to->vertex_max[0] = from->vertex_max[0];
to->vertex_max[1] = from->vertex_max[1];
to->vertex_max[2] = from->vertex_max[2];
}
{
to->vertex_cen[0] = from->vertex_cen[0];
to->vertex_cen[1] = from->vertex_cen[1];
to->vertex_cen[2] = from->vertex_cen[2];
}
to->bound_radius = from->bound_radius;
}
/**********************
* STATIC FUNCTIONS
**********************/
static void update_animation_cb(lv_timer_t * timer)
{
lv_gltf_model_t * model = (lv_gltf_model_t *)lv_timer_get_user_data(timer);
const uint32_t current_tick = lv_tick_get();
const uint32_t delta = lv_tick_diff(current_tick, model->last_tick);
model->last_tick = current_tick;
model->local_timestamp += (delta * model->viewer->desc.animation_speed_ratio) / 1000;
if(model->local_timestamp >= model->current_animation_max_time) {
model->local_timestamp = 50;
}
lv_obj_invalidate((lv_obj_t *)model->viewer);
}
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,278 @@
/**
* @file lv_gltf_data_animations.cpp
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
#include <fastgltf/tools.hpp>
/*********************
* DEFINES
*********************/
#define TIME_LOC_PREPASS_COUNT 16
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
#include "fastgltf/math.hpp"
#include "lv_gltf_data_internal.hpp"
static fastgltf::math::fvec3 animation_get_vec3_at_timestamp(lv_gltf_model_t * data,
fastgltf::AnimationSampler * sampler,
float seconds);
static fastgltf::math::fquat animation_get_quat_at_timestamp(lv_gltf_model_t * data,
fastgltf::AnimationSampler * sampler,
float _seconds);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
uint32_t lv_gltf_data_get_animation_total_time(lv_gltf_model_t * data, uint32_t index)
{
LV_ASSERT(data->asset.animations.size() > index);
auto & animation = data->asset.animations[index];
float max_time = -1.0f;
for(uint64_t i = 0; i < animation.channels.size(); i++) {
auto & accessor = data->asset.accessors[animation.samplers[i].inputAccessor];
max_time = std::max(max_time, fastgltf::getAccessorElement<float>(data->asset, accessor, accessor.count - 1));
}
return (uint32_t)(max_time * 1000);
}
std::vector<uint32_t> * lv_gltf_data_animation_get_channel_set(std::size_t anim_num, lv_gltf_model_t * data,
fastgltf::Node * node)
{
const auto & asset = lv_gltf_data_get_asset(data);
size_t animation_count = lv_gltf_model_get_animation_count(data);
if(data->channel_set_cache.find(node) == data->channel_set_cache.end()) {
std::vector<uint32_t> new_cache = std::vector<uint32_t>();
if(animation_count > anim_num) {
auto & anim = asset->animations[anim_num];
for(uint64_t c = 0; c < anim.channels.size(); c++) {
auto & channel = anim.channels[c];
if(&(asset->nodes[channel.nodeIndex.value()]) == node) {
new_cache.push_back(c);
}
}
}
data->channel_set_cache[node] = new_cache;
}
return &data->channel_set_cache[node];
}
void lv_gltf_data_animation_matrix_apply(float timestamp, std::size_t anim_num, lv_gltf_model_t * gltf_data,
fastgltf::Node * node,
fastgltf::math::fmat4x4 & matrix)
{
const auto & asset = lv_gltf_data_get_asset(gltf_data);
size_t animation_count = lv_gltf_model_get_animation_count(gltf_data);
auto _channel_set = lv_gltf_data_animation_get_channel_set(anim_num, gltf_data, node);
if(_channel_set->size() == 0) {
return;
}
if(animation_count > anim_num) {
auto & anim = asset->animations[anim_num];
bool need_rot_recalc = false;
int32_t translation_comp_index = -1;
int32_t rotation_comp_index = -1;
int32_t scale_comp_index = -1;
for(const auto & c : (*_channel_set)) {
switch(anim.channels[c].path) {
case fastgltf::AnimationPath::Translation:
translation_comp_index = c;
break;
case fastgltf::AnimationPath::Rotation:
rotation_comp_index = c;
need_rot_recalc = true;
break;
case fastgltf::AnimationPath::Scale:
scale_comp_index = c;
need_rot_recalc = true;
break;
case fastgltf::AnimationPath::Weights:
LV_LOG_WARN("Unhandled weights animation");
break;
}
}
if(need_rot_recalc) {
fastgltf::math::fvec3 new_scale;
fastgltf::math::fquat new_quat;
if(!((scale_comp_index > -1) && (rotation_comp_index > -1))) {
fastgltf::math::fvec3 unused;
fastgltf::math::decomposeTransformMatrix(matrix, new_scale, new_quat, unused);
}
if(scale_comp_index > -1) new_scale = animation_get_vec3_at_timestamp(gltf_data, &anim.samplers[scale_comp_index],
timestamp);
if(rotation_comp_index > -1) new_quat = animation_get_quat_at_timestamp(gltf_data, &anim.samplers[rotation_comp_index],
timestamp);
float sx = new_scale[0], sy = new_scale[1], sz = new_scale[2];
float qx = new_quat[0], qy = new_quat[1], qz = new_quat[2], qw = new_quat[3];
float x2 = qx + qx, y2 = qy + qy, z2 = qz + qz;
float xx = qx * x2, xy = qx * y2, xz = qx * z2;
float yy = qy * y2, yz = qy * z2, zz = qz * z2;
float wx = qw * x2, wy = qw * y2, wz = qw * z2;
matrix[0][0] = (1 - (yy + zz)) * sx;
matrix[0][1] = (xy + wz) * sx;
matrix[0][2] = (xz - wy) * sx;
matrix[1][0] = (xy - wz) * sy;
matrix[1][1] = (1 - (xx + zz)) * sy;
matrix[1][2] = (yz + wx) * sy;
matrix[2][0] = (xz + wy) * sz;
matrix[2][1] = (yz - wx) * sz;
matrix[2][2] = (1 - (xx + yy)) * sz;
/* These entries should not be necessary */
//matrix[0][3] = 0.f;
//matrix[1][3] = 0.f;
//matrix[2][3] = 0.f;
}
if(translation_comp_index > -1) {
fastgltf::math::fvec3 new_translation = animation_get_vec3_at_timestamp(gltf_data,
&anim.samplers[translation_comp_index], timestamp);
matrix[3][0] = new_translation[0];
matrix[3][1] = new_translation[1];
matrix[3][2] = new_translation[2];
}
matrix[3][3] = 1.f;
}
}
/**********************
* STATIC FUNCTIONS
**********************/
static fastgltf::math::fquat animation_get_quat_at_timestamp(lv_gltf_model_t * data,
fastgltf::AnimationSampler * sampler,
float _seconds)
{
const auto & asset = lv_gltf_data_get_asset(data);
auto & _inAcc = asset->accessors[sampler->inputAccessor];
auto & _outAcc = asset->accessors[sampler->outputAccessor];
std::size_t _inAccCount = _inAcc.count;
float _maxTime = fastgltf::getAccessorElement<float>(*asset, _inAcc, _inAccCount - 1);
std::size_t _lowerIndex = 0;
float _lowerTimestamp = 0.0f;
if(_seconds < 0.001f) {
_lowerIndex = 0;
}
else {
std::size_t _firstCheckOffset = 0;
std::size_t _lastCheckOffset = _inAccCount;
std::size_t _prepassLeft = TIME_LOC_PREPASS_COUNT;
while(_prepassLeft > 0) {
_prepassLeft -= 1;
if(_seconds >= fastgltf::getAccessorElement<float>(*asset, _inAcc, (_firstCheckOffset + _lastCheckOffset) >> 1)) {
_firstCheckOffset = (_firstCheckOffset + _lastCheckOffset) >> 1;
}
else {
_lastCheckOffset = (_firstCheckOffset + _lastCheckOffset) >> 1;
if(_lastCheckOffset <= _firstCheckOffset + 1) {
_prepassLeft = 0;
}
}
}
for(uint64_t ii = _firstCheckOffset; ii < _inAccCount; ii++) {
float _stampTime = fastgltf::getAccessorElement<float>(*asset, _inAcc, ii);
if(_stampTime > _seconds) {
_lowerIndex = ii - 1;
break;
}
_lowerTimestamp = _stampTime;
}
}
fastgltf::math::fquat _lowerValue = fastgltf::getAccessorElement<fastgltf::math::fquat>(*asset, _outAcc, _lowerIndex);
if(_seconds >= _maxTime || _seconds <= 0.0f) {
return _lowerValue;
}
std::size_t _upperIndex = _lowerIndex + 1;
float _linDist = fastgltf::getAccessorElement<float>(*asset, _inAcc, _upperIndex) - _lowerTimestamp;
return fastgltf::math::slerp(_lowerValue, fastgltf::getAccessorElement<fastgltf::math::fquat>(*asset, _outAcc,
_upperIndex), (_seconds - _lowerTimestamp) / _linDist);
}
fastgltf::math::fvec3 animation_get_vec3_at_timestamp(lv_gltf_model_t * data, fastgltf::AnimationSampler * sampler,
float _seconds)
{
const auto & asset = lv_gltf_data_get_asset(data);
auto & _inAcc = asset->accessors[sampler->inputAccessor];
auto & _outAcc = asset->accessors[sampler->outputAccessor];
std::size_t _inAccCount = _inAcc.count;
float _maxTime = fastgltf::getAccessorElement<float>(*asset, _inAcc, _inAccCount - 1);
std::size_t _lowerIndex = 0;
float _lowerTimestamp = 0.0f;
if(_seconds < 0.001f) {
_lowerIndex = 0;
}
else {
std::size_t _firstCheckOffset = 0;
std::size_t _lastCheckOffset = _inAccCount;
std::size_t _prepassLeft = TIME_LOC_PREPASS_COUNT;
while(_prepassLeft > 0) {
_prepassLeft -= 1;
if(_seconds >= fastgltf::getAccessorElement<float>(*asset, _inAcc, (_firstCheckOffset + _lastCheckOffset) >> 1)) {
_firstCheckOffset = (_firstCheckOffset + _lastCheckOffset) >> 1;
}
else {
_lastCheckOffset = (_firstCheckOffset + _lastCheckOffset) >> 1;
if(_lastCheckOffset <= _firstCheckOffset + 1) {
_prepassLeft = 0;
}
}
}
for(uint64_t ii = _firstCheckOffset; ii < _inAccCount; ii++) {
float _stampTime = fastgltf::getAccessorElement<float>(*asset, _inAcc, ii);
if(_stampTime > _seconds) {
_lowerIndex = ii - 1;
break;
}
_lowerTimestamp = _stampTime;
}
}
fastgltf::math::fvec3 _lowerValue = fastgltf::getAccessorElement<fastgltf::math::fvec3>(*asset, _outAcc, _lowerIndex);
if(_seconds >= _maxTime || _seconds <= 0.0f) {
return _lowerValue;
}
std::size_t _upperIndex = _lowerIndex + 1;
fastgltf::math::fvec3 _upperValue = fastgltf::getAccessorElement<fastgltf::math::fvec3>(*asset, _outAcc, _upperIndex);
float _upperTimestamp = fastgltf::getAccessorElement<float>(*asset, _inAcc, _upperIndex);
return fastgltf::math::lerp(_lowerValue, _upperValue,
(_seconds - _lowerTimestamp) / (_upperTimestamp - _lowerTimestamp));
}
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,103 @@
/**
* @file lv_gltf_data_cache.cpp
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
fastgltf::math::fmat4x4 lv_gltf_data_get_cached_transform(lv_gltf_model_t * data,
fastgltf::Node * node)
{
return data->node_transform_cache[node];
}
bool lv_gltf_data_has_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node)
{
return (data->node_transform_cache.find(node) !=
data->node_transform_cache.end());
}
void lv_gltf_data_set_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node,
fastgltf::math::fmat4x4 M)
{
data->node_transform_cache[node] = M;
}
void lv_gltf_data_clear_transform_cache(lv_gltf_model_t * data)
{
data->node_transform_cache.clear();
}
bool lv_gltf_data_transform_cache_is_empty(lv_gltf_model_t * data)
{
return data->node_transform_cache.size() == 0;
}
void recache_centerpoint(lv_gltf_model_t * data, size_t index_mesh, int32_t primitive)
{
data->local_mesh_to_center_points_by_primitive[index_mesh][primitive] =
lv_gltf_get_primitive_centerpoint(data, data->asset.meshes[index_mesh],
primitive);
}
fastgltf::math::fvec3 lv_gltf_data_get_centerpoint(lv_gltf_model_t * gltf_data,
fastgltf::math::fmat4x4 matrix,
size_t mesh_index, int32_t elem)
{
if(!lv_gltf_data_centerpoint_cache_contains(gltf_data, mesh_index, elem)) {
recache_centerpoint(gltf_data, mesh_index, elem);
}
return get_cached_centerpoint(gltf_data, mesh_index, elem, matrix);
}
bool lv_gltf_data_centerpoint_cache_contains(lv_gltf_model_t * data, size_t index, int32_t element)
{
return data->local_mesh_to_center_points_by_primitive.find(index) !=
data->local_mesh_to_center_points_by_primitive.end() &&
data->local_mesh_to_center_points_by_primitive[index].find(element) !=
data->local_mesh_to_center_points_by_primitive[index].end();
}
fastgltf::math::fvec3 get_cached_centerpoint(lv_gltf_model_t * data, size_t index,
int32_t element,
fastgltf::math::fmat4x4 matrix)
{
fastgltf::math::fvec4 tv = fastgltf::math::fvec4(
data->local_mesh_to_center_points_by_primitive[index][element]);
tv[3] = 1.f;
tv = matrix * tv;
return fastgltf::math::fvec3(tv[0], tv[1], tv[2]);
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,875 @@
/**
* @file lv_gltf_data_injest.cpp
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
#include <fastgltf/core.hpp>
#include <fastgltf/math.hpp>
#include <fastgltf/tools.hpp>
#include <fastgltf/types.hpp>
#include "../fastgltf/lv_fastgltf.hpp"
#include "../../../misc/lv_assert.h"
#include "../../../misc/lv_log.h"
#include "../../../misc/lv_math.h"
#include "../../../stdlib/lv_sprintf.h"
#include "../../../misc/lv_fs.h"
#include "../stb_image/stb_image.h"
#include <webp/decode.h>
/*********************
* DEFINES
*********************/
constexpr auto SUPPORTED_EXTENSIONS =
//fastgltf::Extensions::KHR_draco_mesh_compression |
//fastgltf::Extensions::EXT_meshopt_compression |
fastgltf::Extensions::KHR_mesh_quantization | fastgltf::Extensions::KHR_texture_transform |
fastgltf::Extensions::KHR_lights_punctual | fastgltf::Extensions::KHR_materials_anisotropy |
fastgltf::Extensions::KHR_materials_clearcoat | fastgltf::Extensions::KHR_materials_dispersion |
fastgltf::Extensions::KHR_materials_emissive_strength | fastgltf::Extensions::KHR_materials_ior |
fastgltf::Extensions::KHR_materials_iridescence | fastgltf::Extensions::KHR_materials_sheen |
fastgltf::Extensions::KHR_materials_specular |
fastgltf::Extensions::
KHR_materials_pbrSpecularGlossiness
| // Depreciated, to enable support make sure to define FASTGLTF_ENABLE_DEPRECATED_EXT
fastgltf::Extensions::KHR_materials_transmission |
fastgltf::Extensions::KHR_materials_volume | fastgltf::Extensions::KHR_materials_unlit |
fastgltf::Extensions::EXT_texture_webp |
//fastgltf::Extensions::KHR_materials_diffuse_transmission |
fastgltf::Extensions::KHR_materials_variants;
constexpr auto GLTF_OPTIONS = fastgltf::Options::DontRequireValidAssetMember | fastgltf::Options::AllowDouble |
fastgltf::Options::LoadExternalBuffers | fastgltf::Options::LoadExternalImages |
fastgltf::Options::GenerateMeshIndices;
/**********************
* TYPEDEFS
**********************/
typedef struct {
fastgltf::math::fvec3 position;
fastgltf::math::fvec3 normal;
fastgltf::math::fvec4 tangent;
fastgltf::math::fvec2 uv;
fastgltf::math::fvec2 uv2;
fastgltf::math::fvec4 joints;
fastgltf::math::fvec4 joints2;
fastgltf::math::fvec4 weights;
fastgltf::math::fvec4 weights2;
} vertex_t;
/**********************
* STATIC PROTOTYPES
**********************/
static void set_bounds_info(lv_gltf_model_t * data, fastgltf::math::fvec3 v_min, fastgltf::math::fvec3 v_max,
fastgltf::math::fvec3 v_cen, float radius);
static lv_gltf_model_t * create_data_from_bytes(const uint8_t * bytes, size_t data_size);
static lv_gltf_model_t * create_data_from_file(const char * path);
static void injest_grow_bounds_to_include(lv_gltf_model_t * data, const fastgltf::math::fmat4x4 & matrix,
const fastgltf::Mesh & mesh);
static void injest_set_initial_bounds(lv_gltf_model_t * data, const fastgltf::math::fmat4x4 & matrix,
const fastgltf::Mesh & mesh);
static bool injest_image(lv_opengl_shader_manager_t * shader_manager, lv_gltf_model_t * data, fastgltf::Image & image,
uint32_t index);
static bool injest_image_from_buffer_view(lv_gltf_model_t * data, fastgltf::sources::BufferView & view,
GLuint texture_id);
static void injest_light(lv_gltf_model_t * data, size_t light_index, fastgltf::Light & light, size_t scene_index);
static bool injest_mesh(lv_gltf_model_t * data, fastgltf::Mesh & mesh);
static void make_small_magenta_texture(uint32_t new_magenta_tex);
template <typename T, typename Func>
static size_t injest_vec_attribute(uint8_t vec_size, int32_t current_attrib_index, lv_gltf_model_t * data,
const fastgltf::Primitive * prim, const char * attrib_id, GLuint primitive_vertex_buffer,
size_t offset, Func &&functor);
static int32_t injest_get_any_image_index(fastgltf::Optional<fastgltf::Texture> tex);
static bool injest_check_any_image_index_valid(fastgltf::Optional<fastgltf::Texture> tex);
static inline GLsizei get_level_count(int32_t width, int32_t height)
{
return static_cast<GLsizei>(1 + floor(log2(width > height ? width : height)));
}
/**
* @brief Allocate immutable texture storage with fallback for GLES2
*
* glTexStorage2D (GL_EXT_texture_storage) may not be available on all GLES2 drivers.
* This function falls back to glTexImage2D when the extension is not available.
*/
static inline void tex_storage_2d_compat(GLenum target, GLsizei levels, GLenum internalformat,
GLsizei width, GLsizei height)
{
#ifdef glTexStorage2D
if(glad_glTexStorage2DEXT) {
glTexStorage2D(target, levels, internalformat, width, height);
return;
}
#endif
/* Fallback: use glTexImage2D for each mipmap level */
GLenum format = GL_RGBA;
if(internalformat == GL_RGB8) {
format = GL_RGB;
}
for(GLsizei level = 0; level < levels; level++) {
glTexImage2D(target, level, internalformat, width, height, 0, format, GL_UNSIGNED_BYTE, NULL);
width = (width > 1) ? (width / 2) : 1;
height = (height > 1) ? (height / 2) : 1;
}
}
static void load_mesh_texture_impl(lv_gltf_model_t * data, const fastgltf::TextureInfo & material_prop,
GLuint * primitive_tex_prop,
GLint * primitive_tex_uv_id);
static void load_mesh_texture(lv_gltf_model_t * data, const fastgltf::Optional<fastgltf::TextureInfo> & material_prop,
GLuint * primitive_tex_prop, GLint * primitive_tex_uv_id);
static void load_mesh_texture(lv_gltf_model_t * data,
const fastgltf::Optional<fastgltf::NormalTextureInfo> & material_prop,
GLuint * primitive_tex_prop, GLint * primitive_tex_uv_id);
static void load_mesh_texture(lv_gltf_model_t * data,
const fastgltf::Optional<fastgltf::OcclusionTextureInfo> & material_prop,
GLuint * primitive_tex_prop, GLint * primitive_tex_uv_id);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_gltf_model_t * lv_gltf_data_load_internal(const void * data_source, size_t data_size,
lv_opengl_shader_manager_t * shaders)
{
lv_gltf_model_t * model = NULL;
if(data_size > 0) {
model = create_data_from_bytes((const uint8_t *)data_source, data_size);
}
else {
model = create_data_from_file((const char *)data_source);
}
LV_ASSERT_MSG(model, "Failed to create gltf data");
if(!model) {
return NULL;
}
// Parse the visible node structure to get a world transform matrix for each mesh component
// instance per node, and apply that matrix to the min/max of the untransformed mesh, then
// grow a bounding volume to include those transformed points
int32_t scene_index = 0;
bool first_visible_mesh = true;
fastgltf::iterateSceneNodes(
model->asset, scene_index, fastgltf::math::fmat4x4(), [&](fastgltf::Node & node, fastgltf::math::fmat4x4 matrix) {
if(!node.meshIndex.has_value()) {
return;
}
if(first_visible_mesh) {
injest_set_initial_bounds(model, matrix, model->asset.meshes[node.meshIndex.value()]);
}
else {
injest_grow_bounds_to_include(model, matrix, model->asset.meshes[node.meshIndex.value()]);
}
first_visible_mesh = false;
});
/* Reserve enough space for model nodes */
lv_array_init(&model->nodes, model->asset.nodes.size(), sizeof(lv_gltf_model_node_t));
/*Virtually set size so that lv_array_assign will work*/
model->nodes.size = model->asset.nodes.size();
fastgltf::namegen_iterate_scene_nodes(model->asset, scene_index,
[&](fastgltf::Node & node, const std::string & node_path, const std::string & node_num_path,
size_t node_index, std::size_t child_index) {
LV_UNUSED(child_index);
lv_gltf_model_node_t model_node;
lv_gltf_model_node_init(model, &model_node, &node, node_path.c_str(), node_num_path.c_str());
/* Store the nodes in the same order as fastgltf
* This is a workaround as we can't assign any type of user data to fastgltf's types*/
lv_array_assign(&model->nodes, node_index, & model_node);
});
{
uint32_t i = 0;
for(auto & image : model->asset.images) {
injest_image(shaders, model, image, i);
i++;
}
}
uint16_t lightnum = 0;
for(auto & light : model->asset.lights) {
injest_light(model, lightnum, light, 0);
lightnum += 1;
}
for(auto & mesh : model->asset.meshes) {
injest_mesh(model, mesh);
}
if(model->asset.defaultScene.has_value()) {
LV_LOG_INFO("Default scene = #%d", data->asset.defaultScene.value());
}
return model;
}
/**********************
* STATIC FUNCTIONS
**********************/
static lv_gltf_model_t * create_data_from_file(const char * path)
{
lv_fs_file_t file;
lv_fs_res_t res = lv_fs_open(&file, path, LV_FS_MODE_RD);
if(res != LV_FS_RES_OK) {
LV_LOG_ERROR("Failed to open file '%s': %d", path, res);
return NULL;
}
res = lv_fs_seek(&file, 0, LV_FS_SEEK_END);
if(res != LV_FS_RES_OK) {
LV_LOG_ERROR("Failed to seek end of file '%s': %d", path, res);
return NULL;
}
uint32_t file_size;
res = lv_fs_tell(&file, &file_size);
if(res != LV_FS_RES_OK) {
LV_LOG_ERROR("Failed to get file count size '%s': %d", path, res);
return NULL;
}
res = lv_fs_seek(&file, 0, LV_FS_SEEK_SET);
if(res != LV_FS_RES_OK) {
LV_LOG_ERROR("Failed to seek start of file '%s': %d", path, res);
return NULL;
}
uint8_t * bytes = (uint8_t *) lv_malloc(file_size);
uint32_t bytes_read;
res = lv_fs_read(&file, bytes, file_size, &bytes_read);
if(res != LV_FS_RES_OK) {
LV_LOG_ERROR("Failed to seek start of file '%s': %d", path, res);
return NULL;
}
if(bytes_read != file_size) {
LV_LOG_ERROR("Failed to read the entire gltf file '%s': %d", path, res);
return NULL;
}
lv_fs_close(&file);
lv_gltf_model_t * model = create_data_from_bytes(bytes, file_size);
lv_free(bytes);
model->filename = path;
return model;
}
static lv_gltf_model_t * create_data_from_bytes(const uint8_t * bytes, size_t data_size)
{
fastgltf::Parser parser(SUPPORTED_EXTENSIONS);
auto gltf_buffer = fastgltf::GltfDataBuffer::FromBytes(reinterpret_cast<const std::byte *>(bytes), data_size);
if(!gltf_buffer) {
LV_LOG_ERROR("Failed to create glTF buffer from bytes: %s",
std::string(fastgltf::getErrorMessage(gltf_buffer.error())).c_str());
return NULL;
}
auto asset = parser.loadGltf(gltf_buffer.get(), ".", GLTF_OPTIONS);
if(!asset) {
LV_LOG_ERROR("Failed to decode glTF bytes: %s", std::string(fastgltf::getErrorMessage(asset.error())).c_str());
return NULL;
}
return lv_gltf_data_create_internal("from_bytes", std::move(asset.get()));
}
static void make_small_magenta_texture(uint32_t new_magenta_tex)
{
GL_CALL(glBindTexture(GL_TEXTURE_2D, new_magenta_tex));
unsigned char clearBytes[4] = { 255, 0, 255, 255 }; // RGBA format
GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, clearBytes));
// Set texture parameters (optional but recommended)
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
GL_CALL(glGenerateMipmap(GL_TEXTURE_2D));
GL_CALL(glBindTexture(GL_TEXTURE_2D, 0));
return;
}
static void load_mesh_texture_impl(lv_gltf_model_t * data, const fastgltf::TextureInfo & material_prop,
GLuint * primitive_tex_prop,
GLint * primitive_tex_uv_id)
{
const auto & texture = data->asset.textures[material_prop.textureIndex];
if(!injest_check_any_image_index_valid(texture)) {
return;
}
*primitive_tex_prop = data->textures[injest_get_any_image_index(texture)];
if(material_prop.transform && material_prop.transform->texCoordIndex.has_value()) {
*primitive_tex_uv_id = material_prop.transform->texCoordIndex.value();
}
else {
*primitive_tex_uv_id = material_prop.texCoordIndex;
}
LV_LOG_TRACE("Prim tex prop: %d Prim tex uv id %d", *primitive_tex_prop, *primitive_tex_uv_id);
}
static void load_mesh_texture(lv_gltf_model_t * data,
const fastgltf::Optional<fastgltf::NormalTextureInfo> & material_prop,
GLuint * primitive_tex_prop, GLint * primitive_tex_uv_id)
{
if(!material_prop) {
return;
}
load_mesh_texture_impl(data, material_prop.value(), primitive_tex_prop, primitive_tex_uv_id);
}
static void load_mesh_texture(lv_gltf_model_t * data, const fastgltf::Optional<fastgltf::TextureInfo> & material_prop,
GLuint * primitive_tex_prop, GLint * primitive_tex_uv_id)
{
if(!material_prop) {
return;
}
load_mesh_texture_impl(data, material_prop.value(), primitive_tex_prop, primitive_tex_uv_id);
}
static void load_mesh_texture(lv_gltf_model_t * data,
const fastgltf::Optional<fastgltf::OcclusionTextureInfo> & material_prop,
GLuint * primitive_tex_prop, GLint * primitive_tex_uv_id)
{
if(!material_prop) {
return;
}
load_mesh_texture_impl(data, material_prop.value(), primitive_tex_prop, primitive_tex_uv_id);
}
static int32_t injest_get_any_image_index(fastgltf::Optional<fastgltf::Texture> tex)
{
if(tex->imageIndex.has_value()) {
return tex->imageIndex.value();
}
if(tex->webpImageIndex.has_value()) {
return tex->webpImageIndex.value();
}
return 0;
}
static bool injest_check_any_image_index_valid(fastgltf::Optional<fastgltf::Texture> tex)
{
if(tex->imageIndex.has_value())
return true;
if(tex->webpImageIndex.has_value())
return true;
return false;
}
static void injest_grow_bounds_to_include(lv_gltf_model_t * data, const fastgltf::math::fmat4x4 & matrix,
const fastgltf::Mesh & mesh)
{
/* Grow the bounds to include the specified mesh. */
fastgltf::math::fvec3 v_min{ data->vertex_min[0], data->vertex_min[1], data->vertex_min[2] };
fastgltf::math::fvec3 v_max{
data->vertex_max[0],
data->vertex_max[1],
data->vertex_max[2],
};
fastgltf::math::fvec3 v_cen{ data->vertex_cen[0], data->vertex_cen[1], data->vertex_cen[2] };
float new_bound_radius = data->bound_radius;
if(mesh.primitives.size() > 0) {
set_bounds_info(data, v_min, v_max, v_cen, new_bound_radius);
return;
}
size_t accessor_index = mesh.primitives[0].findAttribute("POSITION")->accessorIndex;
const auto & accessor = data->asset.accessors[accessor_index];
if(!accessor.bufferViewIndex.has_value() || !(accessor.min.has_value() && accessor.max.has_value())) {
set_bounds_info(data, v_min, v_max, v_cen, new_bound_radius);
return;
}
fastgltf::math::fvec4 t_min{ (float)(accessor.min.value().get<double>(0)), (float)(accessor.min.value().get<double>(1)),
(float)(accessor.min.value().get<double>(2)), 1.f };
fastgltf::math::fvec4 t_max{ (float)(accessor.max.value().get<double>(0)), (float)(accessor.max.value().get<double>(1)),
(float)(accessor.max.value().get<double>(2)), 1.f };
t_min = matrix * t_min;
t_max = matrix * t_max;
v_max[0] = LV_MAX(LV_MAX(v_max[0], t_min.x()), t_max.x());
v_max[1] = LV_MAX(LV_MAX(v_max[1], t_min.y()), t_max.y());
v_max[2] = LV_MAX(LV_MAX(v_max[2], t_min.z()), t_max.z());
v_min[0] = LV_MIN(LV_MIN(v_min[0], t_min.x()), t_max.x());
v_min[1] = LV_MIN(LV_MIN(v_min[1], t_min.y()), t_max.y());
v_min[2] = LV_MIN(LV_MIN(v_min[2], t_min.z()), t_max.z());
v_cen[0] = (v_max[0] + v_min[0]) / 2.0f;
v_cen[1] = (v_max[1] + v_min[1]) / 2.0f;
v_cen[2] = (v_max[2] + v_min[2]) / 2.0f;
float size_x = v_max[0] - v_min[0];
float size_y = v_max[1] - v_min[1];
float size_z = v_max[2] - v_min[2];
new_bound_radius = std::sqrt((size_x * size_x) + (size_y * size_y) + (size_z * size_z)) / 2.0f;
set_bounds_info(data, v_min, v_max, v_cen, new_bound_radius);
}
static void injest_set_initial_bounds(lv_gltf_model_t * data, const fastgltf::math::fmat4x4 & matrix,
const fastgltf::Mesh & mesh)
{
fastgltf::math::fvec3 v_min, v_max, v_cen;
float radius = 0.f;
if(mesh.primitives.size() == 0) {
set_bounds_info(data, v_min, v_max, v_cen, radius);
return;
}
size_t accessor_index = mesh.primitives[0].findAttribute("POSITION")->accessorIndex;
const auto & accessor = data->asset.accessors[accessor_index];
if(!accessor.bufferViewIndex.has_value() || !(accessor.min.has_value() && accessor.max.has_value())) {
set_bounds_info(data, v_min, v_max, v_cen, radius);
return;
}
fastgltf::math::fvec4 t_min{ (float)(accessor.min.value().get<double>(0)), (float)(accessor.min.value().get<double>(1)),
(float)(accessor.min.value().get<double>(2)), 1.f };
fastgltf::math::fvec4 t_max{ (float)(accessor.max.value().get<double>(0)), (float)(accessor.max.value().get<double>(1)),
(float)(accessor.max.value().get<double>(2)), 1.f };
t_min = matrix * t_min;
t_max = matrix * t_max;
v_max[0] = LV_MAX(t_min.x(), t_max.x());
v_max[1] = LV_MAX(t_min.y(), t_max.y());
v_max[2] = LV_MAX(t_min.z(), t_max.z());
v_min[0] = LV_MIN(t_min.x(), t_max.x());
v_min[1] = LV_MIN(t_min.y(), t_max.y());
v_min[2] = LV_MIN(t_min.z(), t_max.z());
v_cen[0] = (v_max[0] + v_min[0]) / 2.0f;
v_cen[1] = (v_max[1] + v_min[1]) / 2.0f;
v_cen[2] = (v_max[2] + v_min[2]) / 2.0f;
const float size_x = v_max[0] - v_min[0];
const float size_y = v_max[1] - v_min[1];
const float size_z = v_max[2] - v_min[2];
radius = std::sqrt((size_x * size_x) + (size_y * size_y) + (size_z * size_z)) / 2.0f;
set_bounds_info(data, v_min, v_max, v_cen, radius);
}
bool injest_image(lv_opengl_shader_manager_t * shader_manager, lv_gltf_model_t * data, fastgltf::Image & image,
uint32_t index)
{
std::string _tex_id = std::string(lv_gltf_get_filename(data)) + "_IMG" + std::to_string(index);
char tmp[512];
lv_snprintf(tmp, sizeof(tmp), "%s_img_%u", data->filename, index);
const uint32_t hash = lv_opengl_shader_hash(tmp);
GLuint texture_id = lv_opengl_shader_manager_get_texture(shader_manager, hash);
if(texture_id != GL_NONE) {
LV_LOG_TRACE("Emplacing back already cached texture from previous injest iteration %u", texture_id);
data->textures.emplace_back(texture_id);
return true;
}
LV_LOG_TRACE("Image (%s) [%d] [%u]", image.name.c_str(), texture_id, hash);
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 20));
GL_CALL(glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
bool image_invalidated = false;
std::visit(fastgltf::visitor{
[](auto & arg)
{
LV_UNUSED(arg);
LV_LOG_ERROR("Unexpected image source");
},
[&](fastgltf::sources::URI & file_path)
{
LV_ASSERT_MSG(file_path.fileByteOffset == 0, "Offsets aren't supported with stbi");
LV_ASSERT_MSG(file_path.uri.isLocalPath(), "We're only capable of loading local files.");
int32_t width, height, nrChannels;
LV_LOG_TRACE("Loading image: %s", image.name.c_str());
const std::string path(file_path.uri.path().begin(), file_path.uri.path().end());
unsigned char * data = stbi_load(path.c_str(), &width, &height, &nrChannels, 4);
tex_storage_2d_compat(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
stbi_image_free(data);
},
[&](fastgltf::sources::Array & vector)
{
int32_t width, height, nrChannels;
LV_LOG_TRACE("Unpacking image data: %s", image.name.c_str());
unsigned char * data = stbi_load_from_memory(
reinterpret_cast<const stbi_uc *>(vector.bytes.data()),
static_cast<int32_t>(vector.bytes.size()), &width, &height, &nrChannels, 4);
tex_storage_2d_compat(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
stbi_image_free(data);
},
[&](fastgltf::sources::BufferView & view)
{
LV_LOG_TRACE("Injesting image from bufferview: %s", image.name.c_str());
image_invalidated |= injest_image_from_buffer_view(data, view, texture_id);
},
}, image.data);
if(!image_invalidated) {
glGenerateMipmap(GL_TEXTURE_2D);
}
else {
LV_LOG_ERROR("Failed to load image %s", image.name.c_str());
}
LV_LOG_TRACE("Storing texture with hash: %u %u", hash, texture_id);
lv_opengl_shader_manager_store_texture(shader_manager, hash, texture_id);
GL_CALL(glBindTexture(GL_TEXTURE_2D, 0));
data->textures.emplace_back(texture_id);
return true;
}
static bool injest_image_from_buffer_view(lv_gltf_model_t * data, fastgltf::sources::BufferView & view,
GLuint texture_id)
{
/* Yes, we've already loaded every buffer into some GL buffer. However, with GL it's simpler
to just copy the buffer data again for the texture. Besides, this is just an example. */
auto & buffer_view = data->asset.bufferViews[view.bufferViewIndex];
auto & buffer = data->asset.buffers[buffer_view.bufferIndex];
LV_LOG_INFO("Unpacking image bufferView: %s from %d bytes", image.name, bufferView.byteLenght);
return std::visit(
fastgltf::visitor{
// We only care about VectorWithMime here, because we specify LoadExternalBuffers, meaning
// all buffers are already loaded into a vector.
[](auto & arg)
{
LV_UNUSED(arg);
LV_LOG_ERROR("Unexpected image source");
return false;
},
[&](fastgltf::sources::Array & vector)
{
LV_LOG_TRACE("[WEBP] width: %d height: %d", width, height);
int32_t width, height, nrChannels;
int32_t webpRes = WebPGetInfo(
reinterpret_cast<const uint8_t *>(vector.bytes.data() + buffer_view.byteOffset),
static_cast<std::size_t>(buffer_view.byteLength), &width, &height);
if(!webpRes) {
unsigned char * data = stbi_load_from_memory(
reinterpret_cast<const stbi_uc *>(vector.bytes.data() + buffer_view.byteOffset),
static_cast<int32_t>(buffer_view.byteLength), &width, &height, &nrChannels, 4);
if((width <= 0) || (height <= 0)) {
LV_LOG_ERROR("Failed to load image from memory");
make_small_magenta_texture(texture_id);
return true;
}
LV_LOG_TRACE("[WEBP] width: %d height: %d", width, height);
tex_storage_2d_compat(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
stbi_image_free(data);
return false;
}
WebPBitstreamFeatures features = WebPBitstreamFeatures();
auto status_code = WebPGetFeatures(
reinterpret_cast<const uint8_t *>(vector.bytes.data() + buffer_view.byteOffset),
static_cast<std::size_t>(buffer_view.byteLength), &features);
if(status_code != VP8_STATUS_OK) {
LV_LOG_ERROR("Failed to load webp image %d", status_code);
make_small_magenta_texture(texture_id);
return true;
}
if(features.has_alpha) {
uint8_t * unpacked = WebPDecodeRGBA(
reinterpret_cast<const uint8_t *>(vector.bytes.data() + buffer_view.byteOffset),
static_cast<std::size_t>(buffer_view.byteLength), &width, &height);
tex_storage_2d_compat(GL_TEXTURE_2D, get_level_count(width, height), GL_RGBA8, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE,
unpacked);
WebPFree(unpacked);
}
else {
uint8_t * unpacked = WebPDecodeRGB(
reinterpret_cast<const uint8_t *>(vector.bytes.data() + buffer_view.byteOffset),
static_cast<std::size_t>(buffer_view.byteLength), &width, &height);
tex_storage_2d_compat(GL_TEXTURE_2D, get_level_count(width, height), GL_RGB8, width, height);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE,
unpacked);
WebPFree(unpacked);
}
return false;
} },
buffer.data);
}
static void injest_light(lv_gltf_model_t * data, size_t light_index, fastgltf::Light & light, size_t scene_index)
{
fastgltf::math::fmat4x4 tmat;
// It would seem like we'd need this info but not really, just the index will do at the loading phase, the rest is pulled during frame updates.
LV_UNUSED(light);
fastgltf::findlight_iterate_scene_nodes(data->asset, scene_index, &tmat,
[&](fastgltf::Node & node, const fastgltf::math::fmat4x4 & parentworldmatrix,
const fastgltf::math::fmat4x4 & localmatrix) {
LV_UNUSED(parentworldmatrix);
LV_UNUSED(localmatrix);
if(!node.lightIndex.has_value() ||
node.lightIndex.value() != light_index) {
return;
}
LV_LOG_INFO("SCENE LIGHT BEING ADDED #%d\n", light_index);
data->node_by_light_index.push_back(&node);
});
}
static bool injest_mesh(lv_gltf_model_t * data, fastgltf::Mesh & mesh)
{
/*const auto &asset = GET_ASSET(data);*/
const auto & outMesh = lv_gltf_get_new_meshdata(data);
outMesh->primitives.resize(mesh.primitives.size());
for(auto it = mesh.primitives.begin(); it != mesh.primitives.end(); ++it) {
if(it->dracoCompression) {
LV_LOG_WARN("Unhandled draco compression");
}
auto * positionIt = it->findAttribute("POSITION");
// A mesh primitive is required to hold the POSITION attribute.
//
assert(positionIt != it->attributes.end());
assert(it->indicesAccessor.has_value()); // We specify GenerateMeshIndices, so we should always have indices
auto index = std::distance(mesh.primitives.begin(), it);
auto & primitive = outMesh->primitives[index];
// Generate the VAO
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
primitive.primitiveType = fastgltf::to_underlying(it->type);
primitive.vertexArray = vao;
if(it->materialIndex.has_value()) {
// Adjust for default material
primitive.materialUniformsIndex = it->materialIndex.value() + 1;
auto & material = data->asset.materials[it->materialIndex.value()];
load_mesh_texture(data, material.pbrData.baseColorTexture, &primitive.albedoTexture,
&primitive.baseColorTexcoordIndex);
load_mesh_texture(data, material.pbrData.metallicRoughnessTexture, &primitive.metalRoughTexture,
&primitive.metallicRoughnessTexcoordIndex);
load_mesh_texture(data, material.normalTexture, &primitive.normalTexture, &primitive.normalTexcoordIndex);
load_mesh_texture(data, material.occlusionTexture, &primitive.occlusionTexture,
&primitive.occlusionTexcoordIndex);
load_mesh_texture(data, material.emissiveTexture, &primitive.emissiveTexture,
&primitive.emissiveTexcoordIndex);
if(material.volume)
load_mesh_texture(data, material.volume->thicknessTexture, &primitive.thicknessTexture,
&primitive.thicknessTexcoordIndex);
if(material.transmission)
load_mesh_texture(data, material.transmission->transmissionTexture,
&primitive.transmissionTexture, (GLint *)&primitive.transmissionTexcoordIndex);
if(material.clearcoat && material.clearcoat->clearcoatFactor > 0.0f) {
load_mesh_texture(data, material.clearcoat->clearcoatTexture,
(GLuint *)&primitive.clearcoatTexture, &primitive.clearcoatTexcoordIndex);
load_mesh_texture(data, material.clearcoat->clearcoatRoughnessTexture,
(GLuint *)&primitive.clearcoatRoughnessTexture,
&primitive.clearcoatRoughnessTexcoordIndex);
load_mesh_texture(data, material.clearcoat->clearcoatNormalTexture,
(GLuint *)&primitive.clearcoatNormalTexture,
&primitive.clearcoatNormalTexcoordIndex);
}
if(material.diffuseTransmission && material.diffuseTransmission->diffuseTransmissionFactor > 0.0f) {
load_mesh_texture(data, material.diffuseTransmission->diffuseTransmissionTexture,
&primitive.diffuseTransmissionTexture,
&primitive.diffuseTransmissionTexcoordIndex);
load_mesh_texture(data, material.diffuseTransmission->diffuseTransmissionColorTexture,
&primitive.diffuseTransmissionColorTexture,
&primitive.diffuseTransmissionColorTexcoordIndex);
}
}
else {
primitive.materialUniformsIndex = 0;
}
auto & positionAccessor = data->asset.accessors[positionIt->accessorIndex];
if(!positionAccessor.bufferViewIndex.has_value()) {
continue;
}
// Create the vertex buffer for this primitive, and use the accessor tools to copy directly into the mapped buffer.
GL_CALL(glGenBuffers(1, &primitive.vertexBuffer));
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, primitive.vertexBuffer));
std::vector<vertex_t> vertices_vec(positionAccessor.count);
vertex_t * vertices = vertices_vec.data();
glBufferData(GL_ARRAY_BUFFER, positionAccessor.count * sizeof(*vertices), nullptr, GL_STATIC_DRAW);
{
int32_t attr_index = 0;
attr_index = injest_vec_attribute<fastgltf::math::fvec3>(
3, attr_index, data, &(*it), "POSITION", primitive.vertexBuffer, offsetof(vertex_t, position),
[&](fastgltf::math::fvec3 vec, size_t idx) {
vertices[idx].position = vec;
});
attr_index = injest_vec_attribute<fastgltf::math::fvec4>(
4, attr_index, data, &(*it), "JOINTS_0", primitive.vertexBuffer, offsetof(vertex_t, joints),
[&](fastgltf::math::fvec4 vec, size_t idx) {
vertices[idx].joints = vec;
});
attr_index = injest_vec_attribute<fastgltf::math::fvec4>(
4, attr_index, data, &(*it), "JOINTS_1", primitive.vertexBuffer, offsetof(vertex_t, joints2),
[&](fastgltf::math::fvec4 vec, std::size_t idx) {
vertices[idx].joints2 = vec;
});
attr_index = injest_vec_attribute<fastgltf::math::fvec4>(
4, attr_index, data, &(*it), "WEIGHTS_0", primitive.vertexBuffer, offsetof(vertex_t, weights),
[&](fastgltf::math::fvec4 vec, std::size_t idx) {
vertices[idx].weights = vec;
});
attr_index = injest_vec_attribute<fastgltf::math::fvec4>(
4, attr_index, data, &(*it), "WEIGHTS_1", primitive.vertexBuffer, offsetof(vertex_t, weights2),
[&](fastgltf::math::fvec4 vec, size_t idx) {
vertices[idx].weights2 = vec;
});
attr_index = injest_vec_attribute<fastgltf::math::fvec3>(
3, attr_index, data, &(*it), "NORMAL", primitive.vertexBuffer, offsetof(vertex_t, normal),
[&](fastgltf::math::fvec3 vec, std::size_t idx) {
vertices[idx].normal = vec;
});
attr_index = injest_vec_attribute<fastgltf::math::fvec4>(
4, attr_index, data, &(*it), "TANGENT", primitive.vertexBuffer, offsetof(vertex_t, tangent),
[&](fastgltf::math::fvec4 vec, size_t idx) {
vertices[idx].tangent = vec;
});
attr_index = injest_vec_attribute<fastgltf::math::fvec2>(
2, attr_index, data, &(*it), "TEXCOORD_0", primitive.vertexBuffer, offsetof(vertex_t, uv),
[&](fastgltf::math::fvec2 vec, size_t idx) {
vertices[idx].uv = vec;
});
attr_index = injest_vec_attribute<fastgltf::math::fvec2>(
2, attr_index, data, &(*it), "TEXCOORD_1", primitive.vertexBuffer, offsetof(vertex_t, uv2),
[&](fastgltf::math::fvec2 vec, size_t idx) {
vertices[idx].uv2 = vec;
});
}
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, primitive.vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, positionAccessor.count * sizeof(vertex_t), vertices_vec.data(), GL_STATIC_DRAW);
// Generate the indirect draw command
auto & draw = primitive.draw;
draw.instanceCount = 1;
draw.baseInstance = 0;
draw.baseVertex = 0;
draw.firstIndex = 0;
auto & indexAccessor = data->asset.accessors[it->indicesAccessor.value()];
if(!indexAccessor.bufferViewIndex.has_value())
return false;
draw.count = static_cast<std::uint32_t>(indexAccessor.count);
// Create the index buffer and copy the indices into it.
glGenBuffers(1, &primitive.indexBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, primitive.indexBuffer);
if(indexAccessor.componentType == fastgltf::ComponentType::UnsignedByte ||
indexAccessor.componentType == fastgltf::ComponentType::UnsignedShort) {
primitive.indexType = GL_UNSIGNED_SHORT;
std::uint16_t * tempIndices = new std::uint16_t[indexAccessor.count];
fastgltf::copyFromAccessor<std::uint16_t>(data->asset, indexAccessor, tempIndices);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
static_cast<GLsizeiptr>(indexAccessor.count * sizeof(std::uint16_t)), tempIndices,
GL_STATIC_DRAW);
delete[] tempIndices;
}
else {
primitive.indexType = GL_UNSIGNED_INT;
//std::uint32_t tempIndices[indexAccessor.count];
std::uint32_t * tempIndices = new std::uint32_t[indexAccessor.count];
fastgltf::copyFromAccessor<std::uint32_t>(data->asset, indexAccessor, tempIndices);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
static_cast<GLsizeiptr>(indexAccessor.count * sizeof(std::uint32_t)), tempIndices,
GL_STATIC_DRAW);
delete[] tempIndices;
}
}
glBindVertexArray(0);
return true;
}
template <typename T, typename Func>
static size_t injest_vec_attribute(uint8_t vec_size, int32_t current_attrib_index, lv_gltf_model_t * data,
const fastgltf::Primitive * prim, const char * attrib_id, GLuint primitive_vertex_buffer,
size_t offset, Func &&functor
)
{
const auto & asset = data->asset;
if(const auto * _attrib = prim->findAttribute(std::string(attrib_id)); _attrib != prim->attributes.end()) {
auto & accessor = asset.accessors[_attrib->accessorIndex];
if(accessor.bufferViewIndex.has_value()) {
glBindBuffer(GL_ARRAY_BUFFER, primitive_vertex_buffer);
fastgltf::iterateAccessorWithIndex<T>(asset, accessor, functor);
// Specify the layout of the vertex data
glVertexAttribPointer(current_attrib_index, // Attribute index
vec_size, // Number of components per vertex
GL_FLOAT, // Data type
GL_FALSE, // Normalized
sizeof(vertex_t), // Stride (size of one vertex)
(void *)offset); // Offset in the buffer
glEnableVertexAttribArray(current_attrib_index);
}
else {
glDisableVertexAttribArray(current_attrib_index);
}
current_attrib_index++;
}
return current_attrib_index;
}
static void set_bounds_info(lv_gltf_model_t * data, fastgltf::math::fvec3 v_min, fastgltf::math::fvec3 v_max,
fastgltf::math::fvec3 v_cen, float radius)
{
{
auto _d = v_min.data();
data->vertex_min[0] = _d[0];
data->vertex_min[1] = _d[1];
data->vertex_min[2] = _d[2];
}
{
auto _d = v_max.data();
data->vertex_max[0] = _d[0];
data->vertex_max[1] = _d[1];
data->vertex_max[2] = _d[2];
}
{
auto _d = v_cen.data();
data->vertex_cen[0] = _d[0];
data->vertex_cen[1] = _d[1];
data->vertex_cen[2] = _d[2];
}
data->bound_radius = radius;
}
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,256 @@
#ifndef LV_GLTFDATA_PRIVATE_H
#define LV_GLTFDATA_PRIVATE_H
#include "../../../lv_conf_internal.h"
#ifdef __cplusplus
extern "C" {
#endif
#if LV_USE_GLTF
#include "../../../drivers/opengles/opengl_shader/lv_opengl_shader_internal.h"
#include "../../../draw/lv_image_dsc.h"
#include "../../../misc/lv_types.h"
#include "../../../misc/lv_array.h"
typedef struct {
GLuint count;
GLuint instanceCount;
GLuint firstIndex;
GLint baseVertex;
GLuint baseInstance;
} IndirectDrawCommand;
typedef struct {
IndirectDrawCommand draw;
GLenum primitiveType;
GLenum indexType;
GLuint vertexArray;
GLuint vertexBuffer;
GLuint indexBuffer;
GLuint materialUniformsIndex;
GLuint albedoTexture;
GLuint emissiveTexture;
GLuint metalRoughTexture;
GLuint occlusionTexture;
GLuint normalTexture;
GLuint diffuseTransmissionTexture;
GLuint diffuseTransmissionColorTexture;
GLuint transmissionTexture;
GLuint transmissionTexcoordIndex;
GLint baseColorTexcoordIndex;
GLint emissiveTexcoordIndex;
GLint metallicRoughnessTexcoordIndex;
GLint occlusionTexcoordIndex;
GLint normalTexcoordIndex;
GLint diffuseTransmissionTexcoordIndex;
GLint diffuseTransmissionColorTexcoordIndex;
GLint clearcoatTexture;
GLint clearcoatRoughnessTexture;
GLint clearcoatNormalTexture;
GLint clearcoatTexcoordIndex;
GLint clearcoatRoughnessTexcoordIndex;
GLint clearcoatNormalTexcoordIndex;
GLuint thicknessTexture;
GLint thicknessTexcoordIndex;
GLuint diffuseTexture;
GLint diffuseTexcoordIndex;
GLuint specularGlossinessTexture;
GLint specularGlossinessTexcoordIndex;
} lv_gltf_primitive_t;
typedef struct {
GLint camera;
GLint view_projection_matrix;
GLint model_matrix;
GLint view_matrix;
GLint projection_matrix;
GLint env_intensity;
GLint env_diffuse_sampler;
GLint env_specular_sampler;
GLint env_sheen_sampler;
GLint env_ggx_lut_sampler;
GLint env_charlie_lut_sampler;
GLint env_mip_count;
GLint exposure;
GLint roughness_factor;
GLint base_color_factor;
GLint base_color_sampler;
GLint base_color_uv_set;
GLint base_color_uv_transform;
GLint emissive_factor;
GLint emissive_sampler;
GLint emissive_uv_set;
GLint emissive_uv_transform;
GLint emissive_strength;
GLint metallic_factor;
GLint metallic_roughness_sampler;
GLint metallic_roughness_uv_set;
GLint metallic_roughness_uv_transform;
GLint occlusion_strength;
GLint occlusion_sampler;
GLint occlusion_uv_set;
GLint occlusion_uv_transform;
GLint normal_scale;
GLint normal_sampler;
GLint normal_uv_set;
GLint normal_uv_transform;
GLint clearcoat_factor;
GLint clearcoat_roughness_factor;
GLint clearcoat_sampler;
GLint clearcoat_uv_set;
GLint clearcoat_uv_transform;
GLint clearcoat_roughness_sampler;
GLint clearcoat_roughness_uv_set;
GLint clearcoat_roughness_uv_transform;
GLint clearcoat_normal_scale;
GLint clearcoat_normal_sampler;
GLint clearcoat_normal_uv_set;
GLint clearcoat_normal_uv_transform;
GLint thickness;
GLint thickness_sampler;
GLint thickness_uv_set;
GLint thickness_uv_transform;
GLint diffuse_transmission_sampler;
GLint diffuse_transmission_uv_set;
GLint diffuse_transmission_uv_transform;
GLint diffuse_transmission_color_sampler;
GLint diffuse_transmission_color_uv_set;
GLint diffuse_transmission_color_uv_transform;
GLint sheen_color_factor;
GLint sheen_roughness_factor;
GLint specular_color_factor;
GLint specular_factor;
GLint diffuse_transmission_color_factor;
GLint diffuse_transmission_factor;
GLint ior;
GLint alpha_cutoff;
GLint dispersion;
GLint screen_size;
GLint transmission_factor;
GLint transmission_sampler;
GLint transmission_uv_set;
GLint transmission_uv_transform;
GLint transmission_framebuffer_sampler;
GLint transmission_framebuffer_size;
GLint attenuation_distance;
GLint attenuation_color;
GLint joints_sampler;
GLint diffuse_factor;
GLint glossiness_factor;
GLint diffuse_sampler;
GLint diffuse_uv_set;
GLint diffuse_uv_transform;
GLint specular_glossiness_sampler;
GLint specular_glossiness_uv_set;
GLint specular_glossiness_uv_transform;
} lv_gltf_uniform_locations_t;
lv_gltf_uniform_locations_t lv_gltf_uniform_locations_create(GLuint program);
typedef struct {
lv_gltf_uniform_locations_t uniforms;
GLuint program;
} lv_gltf_compiled_shader_t;
void lv_gltf_store_compiled_shader(lv_gltf_model_t * data, size_t identifier, lv_gltf_compiled_shader_t * shader);
lv_gltf_compiled_shader_t * lv_gltf_get_compiled_shader(lv_gltf_model_t * data, size_t identifier);
/**
* @brief Load the gltf file at the specified filepath
*
* @param gltf_path The gltf filename
* @param ret_data Pointer to the data container that will be populated.
* @param shaders Pointer to the shader cache object this file uses.
*/
lv_gltf_model_t *
lv_gltf_data_load_from_file(const char * file_path,
lv_opengl_shader_manager_t * shader_manager);
/**
* @brief Load the gltf file encoded within the supplied byte array
*
* @param gltf_path The gltf filename
* @param gltf_data_size if gltf_path is instead a byte array, pass the size of that array in through this variable (or 0 if it's a file path).
* @param ret_data Pointer to the data container that will be populated.
* @param shaders Pointer to the shader cache object this file uses.
*/
lv_gltf_model_t *
lv_gltf_data_load_from_bytes(const uint8_t * data, size_t data_size,
lv_opengl_shader_manager_t * shader_manager);
/**
* @brief Retrieve the radius of the GLTF data object.
*
* @param D Pointer to the lv_gltf_data_t object from which to get the radius.
* @return The radius of the GLTF data object.
*/
double lv_gltf_data_get_radius(const lv_gltf_model_t * model);
/**
* @brief Destroy a GLTF data object and free associated resources.
*
* @param _data Pointer to the lv_gltf_data_t object to be destroyed.
*/
void lv_gltf_data_delete(lv_gltf_model_t * _data);
/**
* @brief Copy the bounds information from one GLTF data object to another.
*
* @param to Pointer to the destination lv_gltf_data_t object.
* @param from Pointer to the source lv_gltf_data_t object.
*/
void lv_gltf_data_copy_bounds_info(lv_gltf_model_t * to, lv_gltf_model_t * from);
/**
* @brief Swap the red and blue channels in a pixel buffer.
*
* @param pixel_buffer Pointer to the pixel buffer containing the image data.
* @param byte_total_count The total number of bytes in the pixel buffer.
* @param has_alpha Flag indicating whether the pixel buffer includes an alpha channel.
*/
void lv_gltf_data_rgb_to_bgr(uint8_t * pixel_buffer,
size_t byte_total_count,
bool has_alpha);
#endif /*LV_USE_GLTF*/
#ifdef __cplusplus
}
#endif
#endif /* LV_GLTFDATA_PRIVATE_H */

View File

@@ -0,0 +1,412 @@
#ifndef LV_GLTFDATA_HPP
#define LV_GLTFDATA_HPP
#include "../../../lv_conf_internal.h"
#if LV_USE_GLTF
#include "../gltf_view/lv_gltf.h"
#include "lv_gltf_data_internal.h"
#include "../../../misc/lv_array.h"
#include "../../../drivers/opengles/lv_opengles_private.h"
#include "../../../misc/lv_types.h"
#include "../../../misc/lv_ll.h"
#include "../../../misc/lv_event.h"
#ifdef __cplusplus
#include <fastgltf/math.hpp>
#include <string>
#include <vector>
#include <map>
#include <fastgltf/types.hpp>
// Vector of int32_t's
using UintVector = std::vector<uint32_t>;
// Vector of int32_t's
using IntVector = std::vector<int32_t>;
// Vector of int64_t's
using LongVector = std::vector<int64_t>;
// Pointer to fastgltf::Node
using NodePtr = fastgltf::Node *;
// A standard 4x4 transform matrix
using Transform = fastgltf::math::fmat4x4;
// Pair of Node pointer and int32_t
using NodeIndexPair = std::pair<NodePtr, size_t>;
// Pair of float and Node/Index pair
using NodeIndexDistancePair = std::pair<float, NodeIndexPair>;
// Vector of NodeIndexPair
using NodePairVector = std::vector<NodeIndexPair>;
// Vector of NodeIndexDistancePair
using NodeDistanceVector = std::vector<NodeIndexDistancePair>;
// Map of uint32_t to NodePairVector
using MaterialIndexMap = std::map<uint32_t, NodePairVector>;
// Map of Node Pointers to Transforms
using NodeTransformMap = std::map<NodePtr, Transform>;
// Map of Nodes by string (name)
using StringNodeMap = std::map<std::string, NodePtr>;
// Map of Nodes by string (name)
using NodeIntMap = std::map<NodePtr, uint32_t>;
// Map of Nodes by string (name)
using NodeVector = std::vector<NodePtr>;
// Map of Node Index to Map of Prim Index to CenterXYZ+RadiusW Vec4
using NodePrimCenterMap = std::map<uint32_t, std::map<uint32_t, fastgltf::math::fvec4> >;
#define LV_GLTF_NODE_CHANNEL_X 0
#define LV_GLTF_NODE_CHANNEL_Y 1
#define LV_GLTF_NODE_CHANNEL_Z 2
#define LV_GLTF_NODE_CHANNEL_W 4
typedef enum {
LV_GLTF_NODE_PROP_POSITION,
LV_GLTF_NODE_PROP_ROTATION,
LV_GLTF_NODE_PROP_SCALE,
} lv_gltf_node_prop_t;
typedef struct {
lv_3dpoint_t local_position;
lv_3dpoint_t world_position;
lv_3dpoint_t scale;
lv_3dpoint_t rotation;
} lv_gltf_model_node_data_t;
typedef struct {
lv_gltf_node_prop_t prop;
uint8_t channel;
float value;
} lv_gltf_write_op_t;
typedef struct {
GLuint drawsBuffer;
std::vector<lv_gltf_primitive_t> primitives;
} lv_gltf_mesh_data_t;
typedef struct {
lv_event_list_t event_list;
lv_gltf_model_node_data_t node_data;
bool read_world_position;
bool value_changed;
} lv_gltf_model_node_attr_t;
struct _lv_gltf_model_node_t {
lv_gltf_model_t * model;
const char * numeric_path;
const char * path;
fastgltf::Node * fastgltf_node;
lv_gltf_model_node_attr_t * read_attrs;
lv_array_t write_ops;
};
struct _lv_gltf_model_t {
const char * filename;
fastgltf::Asset asset;
lv_array_t nodes;
NodeVector node_by_light_index;
NodeTransformMap node_transform_cache;
MaterialIndexMap opaque_nodes_by_material_index;
MaterialIndexMap blended_nodes_by_material_index;
std::vector<size_t> validated_skins;
std::vector<GLuint> skin_tex;
NodePrimCenterMap local_mesh_to_center_points_by_primitive;
lv_gltf_t * viewer;
std::vector<lv_gltf_mesh_data_t> meshes;
std::vector<GLuint> textures;
lv_array_t compiled_shaders;
std::map<fastgltf::Node *, std::vector<uint32_t> > channel_set_cache;
fastgltf::math::fmat4x4 view_mat;
fastgltf::math::fvec3 view_pos;
fastgltf::math::fvec3 vertex_max;
fastgltf::math::fvec3 vertex_min;
fastgltf::math::fvec3 vertex_cen;
lv_timer_t * animation_update_timer;
size_t current_animation;
size_t last_material_index;
uint32_t last_camera_index;
int32_t last_anim_num;
float bound_radius;
uint32_t current_animation_max_time;
uint32_t local_timestamp;
uint32_t last_tick;
uint32_t camera;
bool is_animation_enabled;
bool last_pass_was_transmission;
bool last_frame_was_antialiased;
bool last_frame_no_motion;
bool _last_frame_no_motion;
bool write_ops_pending;
bool write_ops_flushed;
struct _lv_gltf_model_t * linked_view_source;
};
/**
* @brief Retrieve a specific texture from the GLTF model data.
*
* @param data Pointer to the lv_gltf_data_t object containing the model data.
* @param index The index of the texture to retrieve.
* @return Pointer to the texture object.
*/
GLuint lv_gltf_data_get_texture(lv_gltf_model_t * data, size_t index);
/**
* @brief Retrieve the minimum bounds (X/Y/Z) of the model from the GLTF data.
*
* @param data Pointer to the lv_gltf_data_t object containing the model data.
* @return Pointer to a 3-element float array representing the minimum bounds.
*/
fastgltf::math::fvec3 lv_gltf_data_get_bounds_min(const lv_gltf_model_t * data);
/**
* @brief Retrieve the maximum bounds (X/Y/Z) of the model from the GLTF data.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @return Pointer to a 3-element float array representing the maximum bounds.
*/
fastgltf::math::fvec3 lv_gltf_data_get_bounds_max(const lv_gltf_model_t * data);
/**
* @brief Retrieve the center coordinates of the GLTF data object.
*
* @param D Pointer to the lv_gltf_data_t object from which to get the center.
* @return Pointer to an array containing the center coordinates (x, y, z).
*/
fastgltf::math::fvec3 lv_gltf_data_get_center(const lv_gltf_model_t * data);
/**
* @brief Retrieve the filename of the GLTF model.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @return Pointer to a constant character string representing the filename.
*/
const char * lv_gltf_get_filename(const lv_gltf_model_t * data);
/**
* @brief Check if the centerpoint cache contains a specific entry.
*
* @param data Pointer to the lv_gltf_data_t object containing the model data.
* @param index The index of the entry to check.
* @param element The specific parameter to check within the cache.
* @return True if the cache contains the entry, false otherwise.
*/
bool lv_gltf_data_centerpoint_cache_contains(lv_gltf_model_t * data, size_t index, int32_t element);
/**
* @brief Retrieve a specific primitive from a mesh.
*
* @param M Pointer to the MeshData structure containing the mesh data.
* @param I The index of the primitive to retrieve.
* @return Pointer to the primitive data.
*/
lv_gltf_primitive_t * lv_gltf_data_get_primitive_from_mesh(lv_gltf_mesh_data_t * M, size_t I);
/**
* @brief Retrieve the asset associated with the GLTF model data.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @return Pointer to the asset data.
*/
fastgltf::Asset * lv_gltf_data_get_asset(lv_gltf_model_t * data);
/**
* @brief Retrieve mesh data for a specific index from the GLTF model data.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @param I The index of the mesh data to retrieve.
* @return Pointer to the MeshData structure containing the mesh data.
*/
lv_gltf_mesh_data_t * lv_gltf_data_get_mesh(lv_gltf_model_t * data, size_t index);
/**
* @brief Retrieve the skin texture index for a specific entry in the GLTF model data.
*
* @param data Pointer to the lv_gltf_data_t object containing the model data.
* @param index The index of the entry for which to retrieve the skin texture index.
* @return The skin texture index.
*/
GLuint lv_gltf_data_get_skin_texture_at(lv_gltf_model_t * data, size_t index);
/**
* @brief Check if the validated skins contain a specific entry.
*
* @param data Pointer to the lv_gltf_data_t object containing the model data.
* @param index The index of the skin to check.
* @return True if the validated skins contain the entry, false otherwise.
*/
bool lv_gltf_data_validated_skins_contains(lv_gltf_model_t * data, size_t index);
/**
* @brief Validate a specific skin in the GLTF model data.
*
* @param data Pointer to the lv_gltf_data_t object containing the model data.
* @param index The index of the skin to validate.
*/
void lv_gltf_data_validate_skin(lv_gltf_model_t * data, size_t index);
/**
* @brief Add an opaque node primitive to the GLTF model data.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @param I The index of the primitive to add.
* @param N Pointer to the NodePtr representing the node to add.
* @param P The specific parameter associated with the primitive.
*/
void lv_gltf_data_add_opaque_node_primitive(lv_gltf_model_t * data, size_t index, fastgltf::Node * node,
size_t primitive_index);
/**
* @brief Add a blended node primitive to the GLTF model data.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @param I The index of the primitive to add.
* @param N Pointer to the NodePtr representing the node to add.
* @param P The specific parameter associated with the primitive.
*/
void lv_gltf_data_add_blended_node_primitive(lv_gltf_model_t * data, size_t mesh_index, fastgltf::Node * node,
size_t primitive_index);
/**
* @brief Set the cached transformation matrix for a specific node in the GLTF model data.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @param N Pointer to the NodePtr representing the node for which to set the transformation.
* @param M The transformation matrix to cache.
*/
void lv_gltf_data_set_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node, fastgltf::math::fmat4x4 M);
/**
* @brief Clear the transformation cache for the GLTF model data.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
*/
void lv_gltf_data_clear_transform_cache(lv_gltf_model_t * data);
/**
* @brief Retrieve the cached transformation matrix for a specific node in the GLTF model data.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @param N Pointer to the NodePtr representing the node for which to retrieve the transformation.
* @return The cached transformation matrix.
*/
fastgltf::math::fmat4x4 lv_gltf_data_get_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node);
/**
* @brief Check if a cached transformation matrix exists for a given node.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @param N Pointer to the NodePtr representing the node for which to retrieve the transformation.
* @return true if a cache item exists, false otherwise
int32_t*/
bool lv_gltf_data_has_cached_transform(lv_gltf_model_t * data, fastgltf::Node * node);
/**
* @brief Check if the transformation cache is empty.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @return True if the transformation cache is empty, false otherwise.
*/
bool lv_gltf_data_transform_cache_is_empty(lv_gltf_model_t * data);
/**
* @brief Retrieve the size of the skins in the GLTF model data.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @return The size of the skins.
*/
size_t lv_gltf_data_get_skins_size(lv_gltf_model_t * data);
/**
* @brief Retrieve a specific skin from the GLTF model data.
*
* @param D Pointer to the lv_gltf_data_t object containing the model data.
* @param I The index of the skin to retrieve.
* @return The skin index.
*/
size_t lv_gltf_data_get_skin(lv_gltf_model_t * data, size_t index);
/**
* @brief Ingest and discover defines for a specific node and primitive in the GLTF model data.
*
* @param data_obj Pointer to the lv_gltf_data_t object containing the model data.
* @param node Pointer to the node for which to ingest defines.
* @param prim Pointer to the primitive for which to ingest defines.
*/
void lv_gltf_data_injest_discover_defines(lv_gltf_model_t * data, fastgltf::Node * node, fastgltf::Primitive * prim);
/**
* @brief Retrieve the center point of a specific mesh element from the GLTF model data.
*
* @param gltf_data Pointer to the lv_gltf_data_t object containing the model data.
* @param matrix The transformation matrix to apply when calculating the center point.
* @param meshIndex The index of the mesh from which to retrieve the center point.
* @param elem The specific element index within the mesh.
* @return The center point as a fastgltf::math::fvec3 structure.
*/
fastgltf::math::fvec3 lv_gltf_data_get_centerpoint(lv_gltf_model_t * gltf_data, fastgltf::math::fmat4x4 matrix,
size_t mesh_index,
int32_t elem);
lv_gltf_mesh_data_t * lv_gltf_get_new_meshdata(lv_gltf_model_t * _data);
lv_gltf_model_t * lv_gltf_data_create_internal(const char * gltf_path, fastgltf::Asset);
lv_gltf_model_t * lv_gltf_data_load_internal(const void * data_source, size_t data_size,
lv_opengl_shader_manager_t * shaders);
fastgltf::math::fvec4 lv_gltf_get_primitive_centerpoint(lv_gltf_model_t * data, fastgltf::Mesh & mesh,
uint32_t prim_num);
fastgltf::math::fvec3 get_cached_centerpoint(lv_gltf_model_t * data, size_t index, int32_t element,
fastgltf::math::fmat4x4 matrix);
void lv_gltf_data_delete_textures(lv_gltf_model_t * data);
GLuint lv_gltf_data_create_texture(lv_gltf_model_t * data);
void lv_gltf_model_node_init(lv_gltf_model_t * model, lv_gltf_model_node_t * node, fastgltf::Node * fastgltf_node,
const char * path,
const char * num_path);
void lv_gltf_model_node_deinit(lv_gltf_model_node_t * node);
/**
* @brief Retrieve the pixel data for a specific texture in a GLTF model.
*
* @param pixels Pointer to the memory where the pixel data will be stored.
* @param data_obj Pointer to the lv_gltf_data_t object containing the model data.
* @param model_texture_index The index of the texture in the model.
* @param mipmapnum The mipmap level to retrieve pixel data for.
* @param width The width of the texture.
* @param height The height of the texture.
* @param has_alpha Flag indicating whether the texture includes an alpha channel.
* @return True if the pixel data was successfully retrieved, false otherwise.
*/
bool lv_gltf_data_get_texture_pixels(void * pixels, lv_gltf_model_t * data_obj, uint32_t model_texture_index,
uint32_t mipmapnum,
uint32_t width, uint32_t height, bool has_alpha);
uint32_t lv_gltf_data_get_animation_total_time(lv_gltf_model_t * data, uint32_t index);
std::vector<uint32_t> * lv_gltf_data_animation_get_channel_set(std::size_t anim_num, lv_gltf_model_t * data,
fastgltf::Node * node);
void lv_gltf_data_animation_matrix_apply(float timestamp, std::size_t anim_num, lv_gltf_model_t * gltf_data,
fastgltf::Node * node,
fastgltf::math::fmat4x4 & matrix);
lv_gltf_model_node_t * lv_gltf_model_node_get_by_internal_node(lv_gltf_model_t * model,
const fastgltf::Node * fastgltf_node);
void lv_gltf_model_send_new_values(lv_gltf_model_t * model);
#endif
#endif /*LV_USE_GLTF*/
#endif /*LV_GLTFVIEW_H*/

View File

@@ -0,0 +1,52 @@
/**
* @file lv_gltf_data_mesh.cpp
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_gltf_mesh_data_t * lv_gltf_get_new_meshdata(lv_gltf_model_t * data)
{
data->meshes.emplace_back(lv_gltf_mesh_data_t {});
return &(data->meshes[data->meshes.size() - 1]);
}
lv_gltf_mesh_data_t * lv_gltf_data_get_mesh(lv_gltf_model_t * data, size_t index)
{
return &data->meshes[index];
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,126 @@
/**
* @file lv_gltf_data_primitive.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
#include "../../../misc/lv_math.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_gltf_primitive_t * lv_gltf_data_get_primitive_from_mesh(lv_gltf_mesh_data_t * mesh, size_t index)
{
return &(mesh->primitives[index]);
}
void lv_gltf_data_add_opaque_node_primitive(lv_gltf_model_t * data, size_t index,
fastgltf::Node * node, size_t primitive_index)
{
data->opaque_nodes_by_material_index[index].emplace_back(
std::make_pair(node, primitive_index));
}
void lv_gltf_data_add_blended_node_primitive(lv_gltf_model_t * data, size_t index,
fastgltf::Node * node, size_t primitive_index)
{
data->blended_nodes_by_material_index[index].push_back(
std::make_pair(node, primitive_index));
}
fastgltf::math::fvec4 lv_gltf_get_primitive_centerpoint(lv_gltf_model_t * data,
fastgltf::Mesh & mesh,
uint32_t prim_num)
{
fastgltf::math::fvec4 result{ 0.f };
fastgltf::math::fvec3 v_min{ 999999999.f };
fastgltf::math::fvec3 v_max{ -999999999.f };
fastgltf::math::fvec3 v_cen{ 0.f };
float radius = 0.f;
if(mesh.primitives.size() <= prim_num) {
return result;
}
const auto & it = mesh.primitives[prim_num];
const auto & asset = data->asset;
const auto * positionIt = it.findAttribute("POSITION");
const auto & positionAccessor =
asset.accessors[positionIt->accessorIndex];
if(!positionAccessor.bufferViewIndex.has_value()) {
return result;
}
if(!(positionAccessor.min.has_value() &&
positionAccessor.max.has_value())) {
LV_LOG_ERROR(
"Could not get primitive center point. Missing min/max values");
return result;
}
fastgltf::math::fvec4 t_min{
(float)(positionAccessor.min.value().get<double>((size_t)0)),
(float)(positionAccessor.min.value().get<double>((size_t)1)),
(float)(positionAccessor.min.value().get<double>((size_t)2)),
0.f
};
fastgltf::math::fvec4 t_max{
(float)(positionAccessor.max.value().get<double>((size_t)0)),
(float)(positionAccessor.max.value().get<double>((size_t)1)),
(float)(positionAccessor.max.value().get<double>((size_t)2)),
0.f
};
v_max[0] = LV_MAX(t_min.x(), t_max.x());
v_max[1] = LV_MAX(t_min.y(), t_max.y());
v_max[2] = LV_MAX(t_min.z(), t_max.z());
v_min[0] = LV_MIN(t_min.x(), t_max.x());
v_min[1] = LV_MIN(t_min.y(), t_max.y());
v_min[2] = LV_MIN(t_min.z(), t_max.z());
v_cen[0] = (v_max[0] + v_min[0]) / 2.0f;
v_cen[1] = (v_max[1] + v_min[1]) / 2.0f;
v_cen[2] = (v_max[2] + v_min[2]) / 2.0f;
float size_x = v_max[0] - v_min[0];
float size_y = v_max[1] - v_min[1];
float size_z = v_max[2] - v_min[2];
radius = std::sqrt((size_x * size_x) + (size_y * size_y) +
(size_z * size_z)) /
2.0f;
result[0] = v_cen[0];
result[1] = v_cen[1];
result[2] = v_cen[2];
result[3] = radius;
return result;
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,60 @@
/**
* @file lv_gltf_data_shader.cpp
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
#include "../../../misc/lv_array.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_gltf_store_compiled_shader(lv_gltf_model_t * data, size_t identifier, lv_gltf_compiled_shader_t * shader)
{
const size_t index = identifier - 1;
bool has_to_resize = index >= lv_array_size(&data->compiled_shaders);
if(!has_to_resize) {
lv_array_assign(&data->compiled_shaders, index, shader);
}
while(index >= lv_array_size(&data->compiled_shaders)) {
lv_array_push_back(&data->compiled_shaders, shader);
}
}
lv_gltf_compiled_shader_t * lv_gltf_get_compiled_shader(lv_gltf_model_t * data, size_t identifier)
{
const size_t index = identifier - 1;
LV_ASSERT(index < lv_array_size(&data->compiled_shaders));
return (lv_gltf_compiled_shader_t *)lv_array_at(&data->compiled_shaders, index);
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,68 @@
/**
* @file lv_gltf_data_skin.cpp
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
#include <algorithm>
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
GLuint lv_gltf_data_get_skin_texture_at(lv_gltf_model_t * data, size_t index)
{
return data->skin_tex[index];
}
bool lv_gltf_data_validated_skins_contains(lv_gltf_model_t * data, size_t index)
{
return ((std::find(data->validated_skins.begin(),
data->validated_skins.end(),
index) != data->validated_skins.end()));
}
void lv_gltf_data_validate_skin(lv_gltf_model_t * data, size_t index)
{
data->validated_skins.push_back(index);
}
size_t lv_gltf_data_get_skins_size(lv_gltf_model_t * data)
{
return data->validated_skins.size();
}
size_t lv_gltf_data_get_skin(lv_gltf_model_t * data, size_t index)
{
return data->validated_skins[index];
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,87 @@
/**
* @file lv_gltf_data_texture.cpp
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
#include <cstdint>
#include "../../../misc/lv_color.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_gltf_data_delete_textures(lv_gltf_model_t * data)
{
glDeleteTextures(data->skin_tex.size(), data->skin_tex.data());
data->skin_tex.clear();
}
GLuint lv_gltf_data_create_texture(lv_gltf_model_t * data)
{
GLuint texture;
GL_CALL(glGenTextures(1, &texture));
data->skin_tex.push_back(texture);
return texture;
}
void lv_gltf_data_rgb_to_bgr(uint8_t * pixels, size_t byte_total_count, bool has_alpha)
{
size_t bytes_per_pixel = has_alpha ? 4 : 3;
size_t pixel_count = (byte_total_count / bytes_per_pixel);
if(bytes_per_pixel == 4) {
for(size_t p = 0; p < pixel_count; p++) {
size_t index = p << 2;
uint8_t r = pixels[index + 0];
uint8_t g = pixels[index + 1];
uint8_t b = pixels[index + 2];
uint8_t a = pixels[index + 3];
pixels[index + 0] = b;
pixels[index + 1] = g;
pixels[index + 2] = r;
pixels[index + 3] = a;
}
}
else {
for(size_t p = 0; p < pixel_count; p++) {
size_t index = p * 3;
uint8_t r = pixels[index + 0];
uint8_t g = pixels[index + 1];
uint8_t b = pixels[index + 2];
pixels[index + 0] = b;
pixels[index + 1] = g;
pixels[index + 2] = r;
}
}
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,127 @@
#ifndef LV_GLTF_MODEL_H
#define LV_GLTF_MODEL_H
#include "../../../lv_conf_internal.h"
#if LV_USE_GLTF
#include "lv_gltf_model_node.h"
#include "../../../misc/lv_types.h"
#include "../../../misc/lv_event.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Get the number of images in the glTF model
*
* Images in glTF are used as sources for textures and can be stored either as external files
* or embedded as base64-encoded model within the glTF file.
*
* @param model Pointer to the glTF model data structure
* @return Number of images in the model
*/
size_t lv_gltf_model_get_image_count(const lv_gltf_model_t * model);
/**
* @brief Get the number of textures in the glTF model
*
* Textures define how images are sampled and applied to materials. Each texture references
* an image and may specify sampling parameters like filtering and wrapping modes.
*
* @param model Pointer to the glTF model data structure
* @return Number of textures in the model
*/
size_t lv_gltf_model_get_texture_count(const lv_gltf_model_t * model);
/**
* @brief Get the number of materials in the glTF model
*
* Materials define the visual appearance of mesh primitives, including properties like
* base color, metallic/roughness values, normal maps, and other surface characteristics.
*
* @param model Pointer to the glTF model data structure
* @return Number of materials in the model
*/
size_t lv_gltf_model_get_material_count(const lv_gltf_model_t * model);
/**
* @brief Get the number of cameras in the glTF model
*
* Cameras define viewpoints within the 3D scene and can be either perspective or
* orthographic. They are typically attached to nodes in the scene graph.
*
* @param model Pointer to the glTF model data structure
* @return Number of cameras in the model
*/
size_t lv_gltf_model_get_camera_count(const lv_gltf_model_t * model);
/**
* @brief Get the number of meshes in the glTF model
*
* Meshes contain the geometric model for 3D objects, including vertex positions, normals,
* texture coordinates, and indices. Each mesh can have multiple primitives with different materials.
*
* @param model Pointer to the glTF model data structure
* @return Number of meshes in the model
*/
size_t lv_gltf_model_get_mesh_count(const lv_gltf_model_t * model);
/**
* @brief Get the number of scenes in the glTF model
*
* Scenes define the root nodes of the scene graph. A glTF file can contain multiple scenes,
* though typically only one is designated as the default scene to be displayed.
*
* @param model Pointer to the glTF model data structure
* @return Number of scenes in the model
*/
size_t lv_gltf_model_get_scene_count(const lv_gltf_model_t * model);
/**
* @brief Get the number of animations in the glTF model
*
* Animations define keyframe-based motion for nodes in the scene, including transformations
* like translation, rotation, and scaling over time.
*
* @param model Pointer to the glTF model data structure
* @return Number of animations in the model
*/
size_t lv_gltf_model_get_animation_count(const lv_gltf_model_t * model);
/**
* @brief Select and start playing an animation
*
* @param model Pointer to the glTF model structure
* @param index Animation number to start playing
* @return LV_RESULT_OK if the animation was started else LV_RESULT_INVALID
*/
lv_result_t lv_gltf_model_play_animation(lv_gltf_model_t * model, size_t index);
/**
* @brief Pause the current animation
*
* @param model Pointer to the glTF model structure
*/
void lv_gltf_model_pause_animation(lv_gltf_model_t * model);
/**
* @brief Check if an animation is currently being played
*
* @param model Pointer to the glTF model structure
*/
bool lv_gltf_model_is_animation_paused(lv_gltf_model_t * model);
/**
* @brief Get the current selected animation. To see if it's playing see `lv_gltf_model_is_animation_paused`
*
* @param model Pointer to the glTF model structure
*/
size_t lv_gltf_model_get_animation(lv_gltf_model_t * model);
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_USE_GLTF*/
#endif /*LV_GLTF_MODEL_H*/

View File

@@ -0,0 +1,375 @@
/**
* @file lv_gltf_model_node.cpp
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
#include <float.h>
#include "fastgltf/types.hpp"
#include "lv_gltf_model.h"
#include "../../../misc/lv_array.h"
#include "../../../misc/lv_assert.h"
#include "../../../misc/lv_types.h"
#include "../../../misc/lv_event_private.h"
#include "../../../stdlib/lv_string.h"
#include "../../../stdlib/lv_sprintf.h"
#include "../../../core/lv_obj_pos.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
static lv_result_t add_write_op(lv_gltf_model_node_t * node, lv_gltf_node_prop_t prop, uint8_t channel, float value);
static void invalidate_model(lv_gltf_model_t * model);
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void lv_gltf_model_node_init(lv_gltf_model_t * model, lv_gltf_model_node_t * node, fastgltf::Node * fastgltf_node,
const char * path,
const char * numeric_path)
{
LV_ASSERT_NULL(node);
lv_memset(node, 0, sizeof(*node));
node->model = model;
node->fastgltf_node = fastgltf_node;
node->path = lv_strdup(path);
node->numeric_path = lv_strdup(numeric_path);
LV_ASSERT_MALLOC(node->path);
LV_ASSERT_MALLOC(node->numeric_path);
lv_array_init(&node->write_ops, 0, sizeof(lv_gltf_write_op_t));
}
void lv_gltf_model_node_deinit(lv_gltf_model_node_t * node)
{
LV_ASSERT_NULL(node);
lv_free((void *)node->path);
lv_free((void *)node->numeric_path);
lv_array_deinit(&node->write_ops);
if(node->read_attrs) {
lv_event_remove_all(&node->read_attrs->event_list);
lv_free((void *)node->read_attrs);
}
}
lv_gltf_model_node_t * lv_gltf_model_node_get_by_index(lv_gltf_model_t * model, size_t index)
{
if(!model) {
LV_LOG_WARN("Failed to get node from NULL model");
return nullptr;
}
const uint32_t count = lv_array_size(&model->nodes);
if(index >= count) {
LV_LOG_WARN("Invalid index %zu. Max should be %" LV_PRIu32, index, count - 1);
return nullptr;
}
return (lv_gltf_model_node_t *)lv_array_at(&model->nodes, index);
}
lv_gltf_model_node_t * lv_gltf_model_node_get_by_numeric_path(lv_gltf_model_t * model, const char * num_path)
{
const uint32_t node_count = lv_array_size(&model->nodes);
for(uint32_t i = 0; i < node_count; ++i) {
lv_gltf_model_node_t * entry = (lv_gltf_model_node_t *) lv_array_at(&model->nodes, i);
if(lv_streq(entry->numeric_path, num_path)) {
return entry;
}
}
return nullptr;
}
lv_gltf_model_node_t * lv_gltf_model_node_get_by_path(lv_gltf_model_t * model, const char * path)
{
if(!model) {
LV_LOG_WARN("Can't get node from NULL model");
return nullptr;
}
const uint32_t node_count = lv_array_size(&model->nodes);
for(uint32_t i = 0; i < node_count; ++i) {
lv_gltf_model_node_t * entry = (lv_gltf_model_node_t *) lv_array_at(&model->nodes, i);
if(lv_streq(entry->path, path)) {
return entry;
}
}
return nullptr;
}
lv_gltf_model_node_t * lv_gltf_model_node_get_by_internal_node(lv_gltf_model_t * model,
const fastgltf::Node * fastgltf_node)
{
LV_ASSERT_NULL(model);
const uint32_t node_count = lv_array_size(&model->nodes);
for(uint32_t i = 0; i < node_count; ++i) {
lv_gltf_model_node_t * entry = (lv_gltf_model_node_t *) lv_array_at(&model->nodes, i);
if(entry->fastgltf_node == fastgltf_node) {
return entry;
}
}
return nullptr;
}
const char * lv_gltf_model_node_get_path(lv_gltf_model_node_t * node)
{
if(!node) {
LV_LOG_WARN("Can't get the path of a null node");
return nullptr;
}
return node->path;
}
const char * lv_gltf_model_node_get_ip(lv_gltf_model_node_t * node)
{
if(!node) {
LV_LOG_WARN("Can't get the ip of a null node");
return nullptr;
}
return node->numeric_path;
}
void lv_gltf_model_send_new_values(lv_gltf_model_t * model)
{
LV_ASSERT_NULL(model);
if(!model->write_ops_flushed) {
return;
}
const uint32_t node_count = lv_array_size(&model->nodes);
for(uint32_t i = 0; i < node_count; ++i) {
lv_gltf_model_node_t * node = (lv_gltf_model_node_t *) lv_array_at(&model->nodes, i);
if(!node->read_attrs || !node->read_attrs->value_changed) {
continue;
}
lv_event_push_and_send(&node->read_attrs->event_list, LV_EVENT_VALUE_CHANGED, node, &node->read_attrs->node_data);
node->read_attrs->value_changed = false;
}
model->write_ops_flushed = false;
}
lv_event_dsc_t * lv_gltf_model_node_add_event_cb(lv_gltf_model_node_t * node, lv_event_cb_t cb,
lv_event_code_t filter_list,
void * user_data)
{
if(!node->read_attrs) {
node->read_attrs = (lv_gltf_model_node_attr_t *) lv_zalloc(sizeof(*node->read_attrs));
LV_ASSERT_MALLOC(node->read_attrs);
if(!node->read_attrs) {
LV_LOG_WARN("Failed to allocate memory for read attributes");
return nullptr;
}
lv_array_init(&node->read_attrs->event_list.array, 1, sizeof(lv_event_dsc_t *));
}
return lv_event_add(&node->read_attrs->event_list, cb, filter_list, user_data);
}
lv_event_dsc_t * lv_gltf_model_node_add_event_cb_with_world_position(lv_gltf_model_node_t * node, lv_event_cb_t cb,
lv_event_code_t filter_list,
void * user_data)
{
lv_event_dsc_t * dsc = lv_gltf_model_node_add_event_cb(node, cb, filter_list, user_data);
if(!dsc) {
return nullptr;
}
LV_ASSERT_NULL(node->read_attrs);
node->read_attrs->read_world_position = true;
return dsc;
}
lv_result_t lv_gltf_model_node_set_position_x(lv_gltf_model_node_t * node, float x)
{
return add_write_op(node, LV_GLTF_NODE_PROP_POSITION, LV_GLTF_NODE_CHANNEL_X, x);
}
lv_result_t lv_gltf_model_node_set_position_y(lv_gltf_model_node_t * node, float y)
{
return add_write_op(node, LV_GLTF_NODE_PROP_POSITION, LV_GLTF_NODE_CHANNEL_Y, y);
}
lv_result_t lv_gltf_model_node_set_position_z(lv_gltf_model_node_t * node, float z)
{
return add_write_op(node, LV_GLTF_NODE_PROP_POSITION, LV_GLTF_NODE_CHANNEL_Z, z);
}
lv_result_t lv_gltf_model_node_set_rotation_x(lv_gltf_model_node_t * node, float x)
{
return add_write_op(node, LV_GLTF_NODE_PROP_ROTATION, LV_GLTF_NODE_CHANNEL_X, x);
}
lv_result_t lv_gltf_model_node_set_rotation_y(lv_gltf_model_node_t * node, float y)
{
return add_write_op(node, LV_GLTF_NODE_PROP_ROTATION, LV_GLTF_NODE_CHANNEL_Y, y);
}
lv_result_t lv_gltf_model_node_set_rotation_z(lv_gltf_model_node_t * node, float z)
{
return add_write_op(node, LV_GLTF_NODE_PROP_ROTATION, LV_GLTF_NODE_CHANNEL_Z, z);
}
lv_result_t lv_gltf_model_node_set_scale_x(lv_gltf_model_node_t * node, float x)
{
return add_write_op(node, LV_GLTF_NODE_PROP_SCALE, LV_GLTF_NODE_CHANNEL_X, x);
}
lv_result_t lv_gltf_model_node_set_scale_y(lv_gltf_model_node_t * node, float y)
{
return add_write_op(node, LV_GLTF_NODE_PROP_SCALE, LV_GLTF_NODE_CHANNEL_Y, y);
}
lv_result_t lv_gltf_model_node_set_scale_z(lv_gltf_model_node_t * node, float z)
{
return add_write_op(node, LV_GLTF_NODE_PROP_SCALE, LV_GLTF_NODE_CHANNEL_Z, z);
}
lv_result_t lv_gltf_model_node_get_local_position(lv_event_t * e, lv_3dpoint_t * result)
{
if(!e || e->code != LV_EVENT_VALUE_CHANGED) {
LV_LOG_WARN("Invalid event");
return LV_RESULT_INVALID;
}
lv_gltf_model_node_t * node = (lv_gltf_model_node_t *)lv_event_get_target(e);
if(!node) {
LV_LOG_WARN("Cannot get property from a NULL node");
return LV_RESULT_INVALID;
}
if(!node->read_attrs) {
LV_LOG_WARN("No read event set for this node");
return LV_RESULT_INVALID;
}
*result = node->read_attrs->node_data.local_position;
return LV_RESULT_OK;
}
lv_result_t lv_gltf_model_node_get_world_position(lv_event_t * e, lv_3dpoint_t * result)
{
if(!e || e->code != LV_EVENT_VALUE_CHANGED) {
LV_LOG_WARN("Invalid event");
return LV_RESULT_INVALID;
}
lv_gltf_model_node_t * node = (lv_gltf_model_node_t *)lv_event_get_target(e);
if(!node) {
LV_LOG_WARN("Cannot get property from a NULL node");
return LV_RESULT_INVALID;
}
if(!node->read_attrs) {
LV_LOG_WARN("No read event set for this node");
return LV_RESULT_INVALID;
}
if(!node->read_attrs->read_world_position) {
LV_LOG_WARN("World position is not read by this node");
return LV_RESULT_INVALID;
}
*result = node->read_attrs->node_data.world_position;
return LV_RESULT_OK;
}
lv_result_t lv_gltf_model_node_get_scale(lv_event_t * e, lv_3dpoint_t * result)
{
if(!e || e->code != LV_EVENT_VALUE_CHANGED) {
LV_LOG_WARN("Invalid event");
return LV_RESULT_INVALID;
}
lv_gltf_model_node_t * node = (lv_gltf_model_node_t *)lv_event_get_target(e);
if(!node) {
LV_LOG_WARN("Cannot get property from a NULL node");
return LV_RESULT_INVALID;
}
if(!node->read_attrs) {
LV_LOG_WARN("No read event set for this node");
return LV_RESULT_INVALID;
}
*result = node->read_attrs->node_data.scale;
return LV_RESULT_OK;
}
lv_result_t lv_gltf_model_node_get_euler_rotation(lv_event_t * e, lv_3dpoint_t * result)
{
if(!e || e->code != LV_EVENT_VALUE_CHANGED) {
LV_LOG_WARN("Invalid event");
return LV_RESULT_INVALID;
}
lv_gltf_model_node_t * node = (lv_gltf_model_node_t *)lv_event_get_target(e);
if(!node) {
LV_LOG_WARN("Cannot get property from a NULL node");
return LV_RESULT_INVALID;
}
if(!node->read_attrs) {
LV_LOG_WARN("No read event set for this node");
return LV_RESULT_INVALID;
}
*result = node->read_attrs->node_data.rotation;
return LV_RESULT_OK;
}
/**********************
* STATIC FUNCTIONS
**********************/
static void invalidate_model(lv_gltf_model_t * model)
{
lv_obj_invalidate((lv_obj_t *)model->viewer);
model->write_ops_pending = true;
}
static lv_result_t add_write_op(lv_gltf_model_node_t * node, lv_gltf_node_prop_t prop, uint8_t channel, float value)
{
if(!node) {
LV_LOG_WARN("Can't queue queue write operation for NULL node");
return LV_RESULT_INVALID;
}
/* Try to find if a write operation for this property + channel combination exists*/
/* Doing this is ok for now because the array will be of max size 9 (3 properties x 3 channels)
* In case we start adding more properties we need to look into other approaches*/
const uint32_t write_ops_count = lv_array_size(&node->write_ops);
for(uint32_t i = 0; i < write_ops_count; ++i) {
lv_gltf_write_op_t * write_op = (lv_gltf_write_op_t *)lv_array_at(&node->write_ops, i);
if(write_op->prop != prop || write_op->channel != channel) {
continue;
}
if(LV_ABS(write_op->value - value) > FLT_EPSILON) {
write_op->value = value;
invalidate_model(node->model);
}
return LV_RESULT_OK;
}
/* Else create a new one */
lv_gltf_write_op_t write_op {prop, channel, value};
lv_result_t res = lv_array_push_back(&node->write_ops, &write_op);
if(res != LV_RESULT_OK) {
return res;
}
invalidate_model(node->model);
return LV_RESULT_OK;
}
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,264 @@
/**
* @file lv_gltf_model_node.h
*
*/
#ifndef LV_GLTF_MODEL_NODE_H
#define LV_GLTF_MODEL_NODE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../../lv_conf_internal.h"
#if LV_USE_GLTF
#include "../../../misc/lv_types.h"
#include "../math/lv_3dmath.h"
#include "../../../misc/lv_event.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* @brief Get a glTF model node by its index
*
* @param data Pointer to the glTF model structure
* @param index The index of the node to retrieve
* @return Pointer to the glTF model node, or NULL if not found
*/
lv_gltf_model_node_t * lv_gltf_model_node_get_by_index(lv_gltf_model_t * data, size_t index);
/**
* @brief Get a glTF model node by its numeric path
*
* @param data Pointer to the glTF model structure
* @param num_path The numeric path string of the node to retrieve (eg. ".0")
* @return Pointer to the glTF model node, or NULL if not found
*/
lv_gltf_model_node_t * lv_gltf_model_node_get_by_numeric_path(lv_gltf_model_t * data, const char * num_path);
/**
* @brief Get a glTF model node by its path
*
* @param data Pointer to the glTF model structure
* @param path The path string of the node to retrieve
* @return Pointer to the glTF model node, or NULL if not found
*/
lv_gltf_model_node_t * lv_gltf_model_node_get_by_path(lv_gltf_model_t * data, const char * path);
/**
* @brief Get the path of a glTF model node
*
* @param node Pointer to the glTF model node structure
* @return The path string of the node, or NULL if node is invalid
*/
const char * lv_gltf_model_node_get_path(lv_gltf_model_node_t * node);
/**
* @brief Get the IP (internal pointer/identifier) of a glTF model node
*
* @param node Pointer to the glTF model node structure
* @return The IP string of the node, or NULL if node is invalid
*/
const char * lv_gltf_model_node_get_ip(lv_gltf_model_node_t * node);
/**
* @brief Add an event callback to a glTF model node
*
* @param node Pointer to the glTF model node structure
* @param cb The event callback function to add. Use lv_event_get_param() to retrieve lv_gltf_node_data_t with the node's data.
* @param filter_list Event code filter for the callback
* @param user_data User data to pass to the callback
* @return Pointer to the event descriptor, or NULL if allocation failed
*/
lv_event_dsc_t * lv_gltf_model_node_add_event_cb(lv_gltf_model_node_t * node, lv_event_cb_t cb,
lv_event_code_t filter_list,
void * user_data);
/**
* @brief Add an event callback to a glTF model node with world position computation enabled. Use this only when world position is needed, as computing it is an expensive operation.
*
* @param node Pointer to the glTF model node structure
* @param cb The event callback function to add. Use lv_event_get_param() to retrieve lv_gltf_node_data_t with the node's data.
* @param filter_list Event code filter for the callback
* @param user_data User data to pass to the callback
* @return Pointer to the event descriptor, or NULL if allocation failed
*/
lv_event_dsc_t * lv_gltf_model_node_add_event_cb_with_world_position(lv_gltf_model_node_t * node, lv_event_cb_t cb,
lv_event_code_t filter_list,
void * user_data);
/**
* @brief Get the number of nodes in the glTF model
*
* Nodes form the scene graph hierarchy and can contain transformations, meshes, cameras,
* or other nodes as children. They define the spatial relationships between objects in the scene.
*
* @param model Pointer to the glTF model data structure
* @return Number of nodes in the model
*/
size_t lv_gltf_model_get_node_count(const lv_gltf_model_t * model);
/**
* @brief Set the X position of a glTF model node. The operation is queued and applied on the next rendering phase.
*
* @param node Pointer to the glTF model node structure
* @param x The X position value
* @return LV_RESULT_OK if the operation is queued successfully, LV_RESULT_INVALID if node is null or no more memory to queue the operation
*/
lv_result_t lv_gltf_model_node_set_position_x(lv_gltf_model_node_t * node, float x);
/**
* @brief Set the Y position of a glTF model node. The operation is queued and applied on the next rendering phase.
*
* @param node Pointer to the glTF model node structure
* @param y The Y position value
* @return LV_RESULT_OK if the operation is queued successfully, LV_RESULT_INVALID if node is null or no more memory to queue the operation
*/
lv_result_t lv_gltf_model_node_set_position_y(lv_gltf_model_node_t * node, float y);
/**
* @brief Set the Z position of a glTF model node. The operation is queued and applied on the next rendering phase.
*
* @param node Pointer to the glTF model node structure
* @param z The Z position value
* @return LV_RESULT_OK if the operation is queued successfully, LV_RESULT_INVALID if node is null or no more memory to queue the operation
*/
lv_result_t lv_gltf_model_node_set_position_z(lv_gltf_model_node_t * node, float z);
/**
* @brief Set the X component of a glTF model node's rotation quaternion. The operation is queued and applied on the next rendering phase.
*
* @param node Pointer to the glTF model node structure
* @param x The X rotation component value
* @return LV_RESULT_OK if the operation is queued successfully, LV_RESULT_INVALID if node is null or no more memory to queue the operation
*/
lv_result_t lv_gltf_model_node_set_rotation_x(lv_gltf_model_node_t * node, float x);
/**
* @brief Set the Y component of a glTF model node's rotation quaternion. The operation is queued and applied on the next rendering phase.
*
* @param node Pointer to the glTF model node structure
* @param y The Y rotation component value
* @return LV_RESULT_OK if the operation is queued successfully, LV_RESULT_INVALID if node is null or no more memory to queue the operation
*/
lv_result_t lv_gltf_model_node_set_rotation_y(lv_gltf_model_node_t * node, float y);
/**
* @brief Set the Z component of a glTF model node's rotation quaternion. The operation is queued and applied on the next rendering phase.
*
* @param node Pointer to the glTF model node structure
* @param z The Z rotation component value
* @return LV_RESULT_OK if the operation is queued successfully, LV_RESULT_INVALID if node is null or no more memory to queue the operation
*/
lv_result_t lv_gltf_model_node_set_rotation_z(lv_gltf_model_node_t * node, float z);
/**
* @brief Set the X scale of a glTF model node. The operation is queued and applied on the next rendering phase.
*
* @param node Pointer to the glTF model node structure
* @param x The X scale value
* @return LV_RESULT_OK if the operation is queued successfully, LV_RESULT_INVALID if node is null or no more memory to queue the operation
*/
lv_result_t lv_gltf_model_node_set_scale_x(lv_gltf_model_node_t * node, float x);
/**
* @brief Set the Y scale of a glTF model node. The operation is queued and applied on the next rendering phase.
*
* @param node Pointer to the glTF model node structure
* @param y The Y scale value
* @return LV_RESULT_OK if the operation is queued successfully, LV_RESULT_INVALID if node is null or no more memory to queue the operation
*/
lv_result_t lv_gltf_model_node_set_scale_y(lv_gltf_model_node_t * node, float y);
/**
* @brief Set the Z scale of a glTF model node. The operation is queued and applied on the next rendering phase.
*
* @param node Pointer to the glTF model node structure
* @param z The Z scale value
* @return LV_RESULT_OK if the operation is queued successfully, LV_RESULT_INVALID if node is null or no more memory to queue the operation
*/
lv_result_t lv_gltf_model_node_set_scale_z(lv_gltf_model_node_t * node, float z);
/**
* @brief Get the local position of a glTF model node. Must be called from within an LV_EVENT_VALUE_CHANGED callback.
*
* Local position is relative to the node's parent.
*
* This function is only valid when called from an event callback registered.
* See `lv_gltf_model_node_add_event_cb()` and `lv_gltf_model_node_add_event_cb_with_world_position()`
*
* @param e Pointer to the event structure from the callback
* @param result Pointer to lv_3dpoint_t structure to store the position (x, y, z)
* @return LV_RESULT_OK if successful, LV_RESULT_INVALID if called outside event callback or if parameters are null
*/
lv_result_t lv_gltf_model_node_get_local_position(lv_event_t * e, lv_3dpoint_t * result);
/**
* @brief Get the world position of a glTF model node. Must be called from within an LV_EVENT_VALUE_CHANGED callback
* registered with world position enabled.
*
* World position is the absolute position in global scene coordinates.
*
* This function requires the event callback to be registered with lv_gltf_model_node_add_event_cb_with_world_position()
* as it involves complex matrix calculations that are computed on-demand.
*
* @param e Pointer to the event structure from the callback
* @param result Pointer to lv_3dpoint_t structure to store the position (x, y, z)
* @return LV_RESULT_OK if successful, LV_RESULT_INVALID if called outside event callback, world position not enabled, or if parameters are null
*/
lv_result_t lv_gltf_model_node_get_world_position(lv_event_t * e, lv_3dpoint_t * result);
/**
* @brief Get the scale of a glTF model node. Must be called from within an LV_EVENT_VALUE_CHANGED callback.
*
* Returns the scale factors for each axis.
*
* This function is only valid when called from an event callback registered.
* See `lv_gltf_model_node_add_event_cb()` and `lv_gltf_model_node_add_event_cb_with_world_position()`
*
* @param e Pointer to the event structure from the callback
* @param result Pointer to lv_3dpoint_t structure to store the scale (x, y, z)
* @return LV_RESULT_OK if successful, LV_RESULT_INVALID if called outside event callback or if parameters are null
*/
lv_result_t lv_gltf_model_node_get_scale(lv_event_t * e, lv_3dpoint_t * result);
/**
* @brief Get the Euler rotation of a glTF model node. Must be called from within an LV_EVENT_VALUE_CHANGED callback.
*
* Returns rotation as Euler angles in radians (x, y, z).
*
* This function is only valid when called from an event callback registered.
* See `lv_gltf_model_node_add_event_cb()` and `lv_gltf_model_node_add_event_cb_with_world_position()`
*
* @param e Pointer to the event structure from the callback
* @param result Pointer to lv_3dpoint_t structure to store the rotation in radians (x, y, z)
* @return LV_RESULT_OK if successful, LV_RESULT_INVALID if called outside event callback or if parameters are null
*/
lv_result_t lv_gltf_model_node_get_euler_rotation(lv_event_t * e, lv_3dpoint_t * result);
/**********************
* MACROS
**********************/
#endif /*LV_USE_GLTF*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_GLTF_MODEL_NODE_H*/

View File

@@ -0,0 +1,115 @@
#include "lv_gltf_data_internal.hpp"
#if LV_USE_GLTF
lv_gltf_uniform_locations_t lv_gltf_uniform_locations_create(GLuint program)
{
lv_gltf_uniform_locations_t uniforms;
lv_memset(&uniforms, 0, sizeof(uniforms));
// *** IMAGE QUALITY UNIFORMS ***********************************************************************
uniforms.exposure = glGetUniformLocation(program, "u_Exposure");
// *** CAMERA/VIEW/PROJECTION/MODEL MATRIX UNIFORMS *************************************************
uniforms.camera = glGetUniformLocation(program, "u_Camera");
uniforms.model_matrix = glGetUniformLocation(program, "u_ModelMatrix");
uniforms.view_projection_matrix = glGetUniformLocation(program, "u_ViewProjectionMatrix");
uniforms.view_matrix = glGetUniformLocation(program, "u_ViewMatrix");
uniforms.projection_matrix = glGetUniformLocation(program, "u_ProjectionMatrix");
// *** IMAGE BASED LIGHTING (IBL) UNIFORMS **********************************************************
uniforms.env_intensity = glGetUniformLocation(program, "u_EnvIntensity");
uniforms.env_diffuse_sampler = glGetUniformLocation(program, "u_LambertianEnvSampler");
uniforms.env_specular_sampler = glGetUniformLocation(program, "u_GGXEnvSampler");
uniforms.env_sheen_sampler = glGetUniformLocation(program, "u_CharlieEnvSampler");
uniforms.env_ggx_lut_sampler = glGetUniformLocation(program, "u_GGXLUT");
uniforms.env_charlie_lut_sampler = glGetUniformLocation(program, "u_CharlieLUT");
uniforms.env_mip_count = glGetUniformLocation(program, "u_MipCount");
// *** BASE COLOR / TEXTURE UNIFORMS ****************************************************************
uniforms.base_color_factor = glGetUniformLocation(program, "u_BaseColorFactor");
uniforms.base_color_sampler = glGetUniformLocation(program, "u_BaseColorSampler");
uniforms.base_color_uv_set = glGetUniformLocation(program, "u_BaseColorUVSet");
uniforms.base_color_uv_transform = glGetUniformLocation(program, "u_BaseColorUVTransform");
// *** CUTOFF / IOR / DISPERSION UNIFORMS ***********************************************************
uniforms.alpha_cutoff = glGetUniformLocation(program, "u_AlphaCutoff");
uniforms.ior = glGetUniformLocation(program, "u_Ior");
uniforms.dispersion = glGetUniformLocation(program, "u_Dispersion");
// *** METALLIC / ROUGHNESS UNIFORMS ****************************************************************
uniforms.metallic_factor = glGetUniformLocation(program, "u_MetallicFactor");
uniforms.roughness_factor = glGetUniformLocation(program, "u_RoughnessFactor");
uniforms.metallic_roughness_sampler = glGetUniformLocation(program, "u_MetallicRoughnessSampler");
uniforms.metallic_roughness_uv_set = glGetUniformLocation(program, "u_MetallicRoughnessUVSet");
uniforms.metallic_roughness_uv_transform = glGetUniformLocation(program, "u_MetallicRoughnessUVTransform");
// *** EMISSION UNIFORMS ****************************************************************************
uniforms.emissive_factor = glGetUniformLocation(program, "u_EmissiveFactor");
uniforms.emissive_sampler = glGetUniformLocation(program, "u_EmissiveSampler");
uniforms.emissive_uv_set = glGetUniformLocation(program, "u_EmissiveUVSet");
uniforms.emissive_uv_transform = glGetUniformLocation(program, "u_EmissiveUVTransform");
uniforms.emissive_strength = glGetUniformLocation(program, "u_EmissiveStrength");
// *** OCCLUSION UNIFORMS ***************************************************************************
uniforms.occlusion_strength = glGetUniformLocation(program, "u_OcclusionStrength");
uniforms.occlusion_sampler = glGetUniformLocation(program, "u_OcclusionSampler");
uniforms.occlusion_uv_set = glGetUniformLocation(program, "u_OcclusionUVSet");
uniforms.occlusion_uv_transform = glGetUniformLocation(program, "u_OcclusionUVTransform");
// *** NORMAL MAP UNIFORMS **************************************************************************
uniforms.normal_sampler = glGetUniformLocation(program, "u_NormalSampler");
uniforms.normal_scale = glGetUniformLocation(program, "u_NormalScale");
uniforms.normal_uv_set = glGetUniformLocation(program, "u_NormalUVSet");
uniforms.normal_uv_transform = glGetUniformLocation(program, "u_NormalUVTransform");
// *** VOLUME / TRANSMISSION UNIFORMS ***************************************************************
uniforms.attenuation_distance = glGetUniformLocation(program, "u_AttenuationDistance");
uniforms.attenuation_color = glGetUniformLocation(program, "u_AttenuationColor");
uniforms.transmission_factor = glGetUniformLocation(program, "u_TransmissionFactor");
uniforms.transmission_sampler = glGetUniformLocation(program, "u_TransmissionSampler");
uniforms.transmission_uv_set = glGetUniformLocation(program, "u_TransmissionUVSet");
uniforms.transmission_uv_transform = glGetUniformLocation(program, "u_TransmissionUVTransform");
uniforms.transmission_framebuffer_sampler = glGetUniformLocation(program, "u_TransmissionFramebufferSampler");
uniforms.transmission_framebuffer_size = glGetUniformLocation(program, "u_TransmissionFramebufferSize");
uniforms.screen_size = glGetUniformLocation(program, "u_ScreenSize");
uniforms.thickness = glGetUniformLocation(program, "u_ThicknessFactor");
uniforms.thickness_sampler = glGetUniformLocation(program, "u_ThicknessSampler");
uniforms.thickness_uv_set = glGetUniformLocation(program, "u_ThicknessUVSet");
uniforms.thickness_uv_transform = glGetUniformLocation(program, "u_ThicknessUVTransform");
// *** CLEARCOAT UNIFORMS ***************************************************************************
uniforms.clearcoat_factor = glGetUniformLocation(program, "u_ClearcoatFactor");
uniforms.clearcoat_roughness_factor = glGetUniformLocation(program, "u_ClearcoatRoughnessFactor");
uniforms.clearcoat_sampler = glGetUniformLocation(program, "u_ClearcoatSampler");
uniforms.clearcoat_uv_set = glGetUniformLocation(program, "u_ClearcoatUVSet");
uniforms.clearcoat_uv_transform = glGetUniformLocation(program, "u_ClearcoatUVTransform");
uniforms.clearcoat_roughness_sampler = glGetUniformLocation(program, "u_ClearcoatRoughnessSampler");
uniforms.clearcoat_roughness_uv_set = glGetUniformLocation(program, "u_ClearcoatRoughnessUVSet");
uniforms.clearcoat_roughness_uv_transform = glGetUniformLocation(program, "u_ClearcoatRoughnessUVTransform");
uniforms.clearcoat_normal_scale = glGetUniformLocation(program, "u_ClearcoatNormalScale");
uniforms.clearcoat_normal_sampler = glGetUniformLocation(program, "u_ClearcoatNormalSampler");
uniforms.clearcoat_normal_uv_set = glGetUniformLocation(program, "u_ClearcoatNormalUVSet");
uniforms.clearcoat_normal_uv_transform = glGetUniformLocation(program, "u_ClearcoatNormalUVTransform");
// *** DIFFUSE TRANSMISSION UNIFORMS ****************************************************************
uniforms.diffuse_transmission_factor = glGetUniformLocation(program, "u_DiffuseTransmissionFactor");
uniforms.diffuse_transmission_sampler = glGetUniformLocation(program, "u_DiffuseTransmissionSampler");
uniforms.diffuse_transmission_uv_set = glGetUniformLocation(program, "u_DiffuseTransmissionUVSet");
uniforms.diffuse_transmission_uv_transform = glGetUniformLocation(program, "u_DiffuseTransmissionUVTransform");
uniforms.diffuse_transmission_color_factor = glGetUniformLocation(program, "u_DiffuseTransmissionColorFactor");
uniforms.diffuse_transmission_color_sampler = glGetUniformLocation(program, "u_DiffuseTransmissionColorSampler");
uniforms.diffuse_transmission_color_uv_set = glGetUniformLocation(program, "u_DiffuseTransmissionColorUVSet");
uniforms.diffuse_transmission_color_uv_transform = glGetUniformLocation(program,
"u_DiffuseTransmissionColorUVTransform");
// *** LEGACY SUPPORT - PBR_SPECULARGLOSS ***********************************************************
uniforms.diffuse_factor = glGetUniformLocation(program, "u_DiffuseFactor");
uniforms.specular_factor = glGetUniformLocation(program, "u_SpecularFactor");
uniforms.glossiness_factor = glGetUniformLocation(program, "u_GlossinessFactor");
uniforms.diffuse_sampler = glGetUniformLocation(program, "u_DiffuseSampler");
uniforms.diffuse_uv_set = glGetUniformLocation(program, "u_DiffuseUVSet");
uniforms.diffuse_uv_transform = glGetUniformLocation(program, "u_DiffuseUVTransform");
uniforms.specular_glossiness_sampler = glGetUniformLocation(program, "u_SpecularGlossinessSampler");
uniforms.specular_glossiness_uv_set = glGetUniformLocation(program, "u_SpecularGlossinessUVSet");
uniforms.specular_glossiness_uv_transform = glGetUniformLocation(program, "u_SpecularGlossinessUVTransform");
// *** [PARTIALLY SUPPORTED / IN DEVELOPMENT] UNIFORMS **********************************************
uniforms.sheen_color_factor = glGetUniformLocation(program, "u_SheenColorFactor");
uniforms.sheen_roughness_factor = glGetUniformLocation(program, "u_SheenRoughnessFactor");
//
uniforms.specular_color_factor = glGetUniformLocation(program, "u_KHR_materials_specular_specularColorFactor");
uniforms.specular_factor = glGetUniformLocation(program, "u_KHR_materials_specular_specularFactor");
//
uniforms.joints_sampler = glGetUniformLocation(program, "u_jointsSampler");
return uniforms;
}
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,92 @@
/**
* @file lv_gltf_environment.h
*
*/
#ifndef LV_GLTF_ENVIRONMENT_H
#define LV_GLTF_ENVIRONMENT_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../../lv_conf_internal.h"
#if LV_USE_GLTF
#include "../../../misc/lv_types.h"
/*********************
* DEFINES
*********************/
#define LV_GLTF_DEFAULT_CUBE_MAP_RESOLUTION 128
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Create an IBL sampler for processing environment images
* @return pointer to the created sampler, or NULL on failure
* @note Can be safely deleted after environments are created
*/
lv_gltf_ibl_sampler_t * lv_gltf_ibl_sampler_create(void);
/**
* Set the resolution for each cubemap face
* @param pointer to a sampler
* @param resolution of each cube map face in pixels (recommended: 64-512 for embedded)
*/
void lv_gltf_ibl_sampler_set_cube_map_pixel_resolution(lv_gltf_ibl_sampler_t * sampler, uint32_t resolution);
/**
* Delete an IBL sampler
* @param sampler pointer to the sampler to delete
*/
void lv_gltf_ibl_sampler_delete(lv_gltf_ibl_sampler_t * sampler);
/**
* Create an environment from an HDR or JPEG panoramic image for IBL rendering
* @param sampler IBL sampler defining output resolution (can be deleted after this call)
* @param file_path path to equirectangular environment image, or NULL to use default embedded image
* @return pointer to the created environment, or NULL on failure
*
* @note The source image will be downsampled to the sampler's texture_size
* @note The environment can be shared across multiple glTF objects
*/
lv_gltf_environment_t * lv_gltf_environment_create(lv_gltf_ibl_sampler_t * sampler, const char * file_path);
/**
* Set the rotation angle of the environment map
* @param env pointer to the environment
* @param angle rotation angle in degrees
*/
void lv_gltf_environment_set_angle(lv_gltf_environment_t * env, float angle);
/**
* Delete an environment
* @param environment pointer to the environment to delete
*/
void lv_gltf_environment_delete(lv_gltf_environment_t * environment);
/**********************
* MACROS
**********************/
#endif /*LV_USE_GLTF*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_GLTF_ENVIRONMENT_H*/

View File

@@ -0,0 +1,106 @@
/**
* @file lv_gltf_environment_private.h
*
*/
#ifndef LV_GLTF_ENVIRONMENT_PRIVATE_H
#define LV_GLTF_ENVIRONMENT_PRIVATE_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../../lv_conf_internal.h"
#if LV_USE_GLTF
#include "lv_gltf_environment.h"
#include "../../../misc/lv_types.h"
#include "../../../drivers/opengles/opengl_shader/lv_opengl_shader_internal.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
struct _lv_gltf_ibl_sampler {
uint32_t cube_map_resolution;
float lod_bias;
uint32_t lowest_mip_level;
uint32_t input_texture_id;
uint32_t cube_map_texture_id;
uint32_t framebuffer;
uint32_t mipmap_count;
uint32_t lambertian_texture_id;
uint32_t lambertian_sample_count;
uint32_t ggx_sample_count;
uint32_t ggx_texture_id;
uint32_t sheen_texture_id;
uint32_t sheen_sample_count;
uint32_t ggxlut_texture_id;
uint32_t lut_sample_count;
uint32_t lut_resolution;
uint32_t charlielut_texture_id;
float scale_value;
uint32_t mipmap_levels;
lv_opengl_shader_manager_t shader_manager;
uint32_t fullscreen_vertex_buffer;
uint32_t fullscreen_tex_coord_buffer;
};
typedef struct {
uint8_t * data;
uint32_t internal_format;
uint32_t format;
uint32_t type;
} lv_gltf_ibl_texture_t;
typedef struct {
float * data;
size_t data_len;
uint32_t width;
uint32_t height;
} lv_gltf_ibl_image_t;
struct _lv_gltf_environment {
uint32_t diffuse;
uint32_t specular;
uint32_t sheen;
uint32_t ggxLut;
uint32_t charlie_lut;
uint32_t mip_count;
float ibl_intensity_scale;
float angle;
};
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
#endif /*LV_USE_GLTF*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_GLTF_ENVIRONMENT_PRIVATE_H*/

View File

@@ -0,0 +1,655 @@
/**
* @file lv_gltf_ibl_sampler.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_environment_private.h"
#if LV_USE_GLTF
#include "../../../misc/lv_math.h"
#include "../../../misc/lv_log.h"
#include "../../../stdlib/lv_string.h"
#include "../../../drivers/opengles/lv_opengles_private.h"
#include "../../../drivers/opengles/lv_opengles_debug.h"
#include "../../../drivers/opengles/opengl_shader/lv_opengl_shader_internal.h"
#include "../gltf_view/assets/lv_gltf_view_shader.h"
#define STB_IMAGE_IMPLEMENTATION
#include "../stb_image/stb_image.h"
/*********************
* DEFINES
*********************/
#define INTERNAL_FORMAT GL_RGBA8
#define TEXTURE_TARGET_TYPE GL_UNSIGNED_BYTE
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static lv_result_t ibl_sampler_load(lv_gltf_ibl_sampler_t * sampler, const char * path);
static void ibl_sampler_filter(lv_gltf_ibl_sampler_t * sampler);
static void ibl_sampler_destroy(lv_gltf_ibl_sampler_t * sampler);
static bool ibl_gl_has_extension(const char * extension);
static void ibl_texture_from_image(lv_gltf_ibl_sampler_t * sampler, lv_gltf_ibl_texture_t * texture,
const lv_gltf_ibl_image_t * image);
static GLuint ibl_load_texture_hdr(lv_gltf_ibl_sampler_t * sampler, const lv_gltf_ibl_image_t * image);
static GLuint ibl_create_cube_map_texture(const lv_gltf_ibl_sampler_t * sampler, bool with_mipmaps);
static uint32_t ibl_create_lut_texture(const lv_gltf_ibl_sampler_t * sampler);
static void ibl_panorama_to_cubemap(lv_gltf_ibl_sampler_t * sampler);
static void ibl_apply_filter(lv_gltf_ibl_sampler_t * sampler, uint32_t distribution, float roughness,
uint32_t target_mip_level, GLuint target_texture, uint32_t sample_count, float lod_bias);
static void ibl_cube_map_to_lambertian(lv_gltf_ibl_sampler_t * sampler);
static void ibl_cube_map_to_ggx(lv_gltf_ibl_sampler_t * sampler);
static void ibl_cube_map_to_sheen(lv_gltf_ibl_sampler_t * sampler);
static void ibl_sample_lut(lv_gltf_ibl_sampler_t * sampler, uint32_t distribution, uint32_t targetTexture,
uint32_t currentTextureSize);
static void ibl_sample_ggx_lut(lv_gltf_ibl_sampler_t * sampler);
static void ibl_sample_charlie_lut(lv_gltf_ibl_sampler_t * sampler);
static int ibl_count_bits(int value);
static void init_fullscreen_quad(lv_gltf_ibl_sampler_t * sampler);
static void draw_fullscreen_quad(lv_gltf_ibl_sampler_t * sampler, GLuint program_id);
/**********************
* STATIC VARIABLES
**********************/
static const lv_opengl_glsl_version_t GLSL_VERSIONS[] = {
LV_OPENGL_GLSL_VERSION_300ES,
LV_OPENGL_GLSL_VERSION_330,
};
static const size_t GLSL_VERSION_COUNT = sizeof(GLSL_VERSIONS) / sizeof(GLSL_VERSIONS[0]);
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_gltf_ibl_sampler_t * lv_gltf_ibl_sampler_create(void)
{
lv_gltf_ibl_sampler_t * sampler = lv_zalloc(sizeof(*sampler));
LV_ASSERT_MALLOC(sampler)
if(!sampler) {
LV_LOG_WARN("Failed to create sampler");
return NULL;
}
sampler->cube_map_resolution = LV_GLTF_DEFAULT_CUBE_MAP_RESOLUTION;
sampler->ggx_sample_count = 128;
sampler->lambertian_sample_count = 256;
sampler->sheen_sample_count = 32;
sampler->lod_bias = 0.0;
sampler->lowest_mip_level = 3;
sampler->lut_resolution = 1024;
sampler->lut_sample_count = 64;
sampler->scale_value = 1.0;
lv_opengl_shader_portions_t env_shader_portions;
lv_gltf_view_shader_get_env(&env_shader_portions);
lv_opengl_shader_manager_init(&sampler->shader_manager, env_shader_portions.all, env_shader_portions.count, NULL,
NULL);
init_fullscreen_quad(sampler);
return sampler;
}
void lv_gltf_ibl_sampler_set_cube_map_pixel_resolution(lv_gltf_ibl_sampler_t * sampler, uint32_t resolution)
{
if(!sampler) {
LV_LOG_WARN("Can't set cube map resolution on a NULL sampler");
return;
}
if(resolution == 0) {
LV_LOG_WARN("Cube map resolution should be > 0");
return;
}
sampler->cube_map_resolution = resolution;
}
void lv_gltf_ibl_sampler_delete(lv_gltf_ibl_sampler_t * sampler)
{
if(!sampler) {
LV_LOG_WARN("Can't delete a NULL sampler");
return;
}
ibl_sampler_destroy(sampler);
lv_free(sampler);
}
void lv_gltf_environment_set_angle(lv_gltf_environment_t * env, float angle)
{
if(!env) {
LV_LOG_WARN("Can't set angle on a NULL environment");
return;
}
env->angle = angle;
}
lv_gltf_environment_t * lv_gltf_environment_create(lv_gltf_ibl_sampler_t * sampler, const char * file_path)
{
if(!sampler) {
LV_LOG_WARN("Can't create an environment with a NULL sampler");
return NULL;
}
lv_gltf_environment_t * env = lv_zalloc(sizeof(*env));
LV_ASSERT_MALLOC(env);
if(!env) {
LV_LOG_WARN("Failed to create environment");
return NULL;
}
if(ibl_sampler_load(sampler, file_path) != LV_RESULT_OK) {
LV_LOG_WARN("Failed to initialize ibl sampler");
lv_free(env);
return NULL;
}
ibl_sampler_filter(sampler);
env->diffuse = sampler->lambertian_texture_id;
env->specular = sampler->ggx_texture_id;
env->sheen = sampler->sheen_texture_id;
env->ggxLut = sampler->ggxlut_texture_id;
env->charlie_lut = sampler->charlielut_texture_id;
env->mip_count = sampler->mipmap_levels;
env->ibl_intensity_scale = sampler->scale_value;
return env;
}
void lv_gltf_environment_delete(lv_gltf_environment_t * env)
{
const unsigned int d[3] = { env->diffuse, env->specular, env->sheen };
GL_CALL(glDeleteTextures(3, d));
lv_free(env);
}
/**********************
* STATIC FUNCTIONS
**********************/
static lv_result_t ibl_sampler_load(lv_gltf_ibl_sampler_t * sampler, const char * path)
{
// vv -- WebGL Naming
if(ibl_gl_has_extension("GL_NV_float") && ibl_gl_has_extension("GL_ARB_color_buffer_float")) {
LV_LOG_INFO("Device supports float format textures");
}
// Native naming #2
if(ibl_gl_has_extension("GL_ARB_color_buffer_float") || ibl_gl_has_extension("GL_NV_half_float")) {
LV_LOG_INFO("Device supports half_float format textures");
}
int32_t src_width, src_height, src_nrChannels;
float * data = NULL;
if(path) {
data = stbi_loadf(path, &src_width, &src_height, &src_nrChannels, 3);
}
if(!data) {
if(path) {
LV_LOG_WARN("Failed to load environment image. Falling back to default");
}
extern unsigned char chromatic_jpg[];
extern unsigned int chromatic_jpg_len;
data = stbi_loadf_from_memory(chromatic_jpg, chromatic_jpg_len, &src_width, &src_height, &src_nrChannels, 3);
if(!data) {
LV_LOG_ERROR("Failed to load fallback env image");
return LV_RESULT_INVALID;
}
}
{
lv_gltf_ibl_image_t panorama_image = {
.data = data,
.data_len = src_width * src_height * 3,
.width = src_width,
.height = src_height,
};
sampler->input_texture_id = ibl_load_texture_hdr(sampler, &panorama_image);
stbi_image_free(data);
}
GL_CALL(glGenFramebuffers(1, &sampler->framebuffer));
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, sampler->framebuffer));
sampler->cube_map_texture_id = ibl_create_cube_map_texture(sampler, true);
sampler->lambertian_texture_id = ibl_create_cube_map_texture(sampler, false);
sampler->ggx_texture_id = ibl_create_cube_map_texture(sampler, true);
sampler->sheen_texture_id = ibl_create_cube_map_texture(sampler, true);
GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, sampler->ggx_texture_id));
GL_CALL(glGenerateMipmap(GL_TEXTURE_CUBE_MAP));
GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, sampler->sheen_texture_id));
GL_CALL(glGenerateMipmap(GL_TEXTURE_CUBE_MAP));
sampler->mipmap_levels = ibl_count_bits(sampler->cube_map_resolution) + 1 - sampler->lowest_mip_level;
return LV_RESULT_OK;
}
static void ibl_sampler_filter(lv_gltf_ibl_sampler_t * sampler)
{
GLint prev_framebuffer;
GLint prev_viewport[4];
GLint prev_program;
GLint prev_texture_2d;
GLint prev_texture_cube;
GLint prev_active_texture;
GL_CALL(glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_framebuffer));
GL_CALL(glGetIntegerv(GL_VIEWPORT, prev_viewport));
GL_CALL(glGetIntegerv(GL_CURRENT_PROGRAM, &prev_program));
GL_CALL(glGetIntegerv(GL_ACTIVE_TEXTURE, &prev_active_texture));
GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &prev_texture_2d));
GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_CUBE_MAP, &prev_texture_cube));
ibl_panorama_to_cubemap(sampler);
ibl_cube_map_to_lambertian(sampler);
ibl_cube_map_to_ggx(sampler);
ibl_cube_map_to_sheen(sampler);
ibl_sample_ggx_lut(sampler);
ibl_sample_charlie_lut(sampler);
// Restore all GL state
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, prev_framebuffer));
GL_CALL(glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]));
GL_CALL(glUseProgram(prev_program));
GL_CALL(glActiveTexture(prev_active_texture));
GL_CALL(glBindTexture(GL_TEXTURE_2D, prev_texture_2d));
GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, prev_texture_cube));
}
static void ibl_sampler_destroy(lv_gltf_ibl_sampler_t * sampler)
{
if(sampler->framebuffer != 0) {
GL_CALL(glDeleteFramebuffers(1, &sampler->framebuffer));
sampler->framebuffer = 0;
}
if(sampler->input_texture_id != 0) {
GL_CALL(glDeleteTextures(1, &sampler->input_texture_id));
sampler->input_texture_id = 0;
}
if(sampler->cube_map_texture_id != 0) {
GL_CALL(glDeleteTextures(1, &sampler->cube_map_texture_id));
sampler->cube_map_texture_id = 0;
}
GL_CALL(glDeleteBuffers(1, &sampler->fullscreen_vertex_buffer));
GL_CALL(glDeleteBuffers(1, &sampler->fullscreen_tex_coord_buffer));
lv_opengl_shader_manager_deinit(&sampler->shader_manager);
}
static void ibl_texture_from_image(lv_gltf_ibl_sampler_t * sampler, lv_gltf_ibl_texture_t * texture,
const lv_gltf_ibl_image_t * image)
{
const size_t src_format_bpp = 3;
const size_t dst_format_bpp = 4;
texture->internal_format = INTERNAL_FORMAT;
texture->format = GL_RGBA;
texture->type = TEXTURE_TARGET_TYPE;
size_t pixel_num = image->data_len / src_format_bpp;
texture->data = (uint8_t *)lv_malloc(pixel_num * 4);
LV_ASSERT_MALLOC(texture->data);
float max_value = 0.0;
float clamped_sum = 0.0;
float diff_sum = 0.0;
size_t src = 0;
size_t dst = 0;
for(size_t i = 0; i < pixel_num; i++) {
const float r = image->data[src + 0];
const float g = image->data[src + 1];
const float b = image->data[src + 2];
const float max_component = LV_MAX(LV_MAX(r, g), b);
if(max_component > 1.0) {
diff_sum += max_component - 1.0;
}
clamped_sum += LV_MIN(max_component, 1.0f);
max_value = LV_MAX(max_component, max_value);
texture->data[dst + 0] = LV_MIN(r * 255, 255);
texture->data[dst + 1] = LV_MIN(g * 255, 255);
texture->data[dst + 2] = LV_MIN(b * 255, 255);
texture->data[dst + 3] = 0xFF;
src += src_format_bpp;
dst += dst_format_bpp;
}
float scale_factor = 1.0;
if(clamped_sum > 1.0) {
// Apply global scale factor to compensate for intensity lost when clamping
scale_factor = (clamped_sum + diff_sum) / clamped_sum;
LV_LOG_INFO("HDR Intensity Scale %f\n", scale_factor);
}
sampler->scale_value = scale_factor;
}
static uint32_t ibl_load_texture_hdr(lv_gltf_ibl_sampler_t * sampler, const lv_gltf_ibl_image_t * image)
{
lv_gltf_ibl_texture_t texture;
ibl_texture_from_image(sampler, &texture, image);
GLuint texture_id;
GL_CALL(glGenTextures(1, &texture_id));
GL_CALL(glBindTexture(GL_TEXTURE_2D, texture_id));
GL_CALL(glTexImage2D(GL_TEXTURE_2D, // target
0, // level
texture.internal_format, image->width, image->height,
0, // border
texture.format, // format of the pixel data
texture.type, // type of the pixel data
texture.data));
lv_free(texture.data);
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
return texture_id;
}
static GLuint ibl_create_cube_map_texture(const lv_gltf_ibl_sampler_t * sampler, bool with_mipmaps)
{
uint32_t targetTexture;
GL_CALL(glGenTextures(1, &targetTexture));
GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, targetTexture));
for(int32_t i = 0; i < 6; ++i) {
GL_CALL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, INTERNAL_FORMAT, sampler->cube_map_resolution,
sampler->cube_map_resolution, 0, GL_RGBA, TEXTURE_TARGET_TYPE, NULL));
}
if(with_mipmaps) {
GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
}
else {
GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
}
GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
GL_CALL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
return targetTexture;
}
static GLuint ibl_create_lut_texture(const lv_gltf_ibl_sampler_t * sampler)
{
GLuint texture;
GL_CALL(glGenTextures(1, &texture));
GL_CALL(glBindTexture(GL_TEXTURE_2D, texture));
GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, INTERNAL_FORMAT, sampler->lut_resolution, sampler->lut_resolution, 0, GL_RGBA,
TEXTURE_TARGET_TYPE, NULL));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
return texture;
}
static void ibl_panorama_to_cubemap(lv_gltf_ibl_sampler_t * sampler)
{
for(int32_t i = 0; i < 6; ++i) {
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, sampler->framebuffer));
GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
sampler->cube_map_texture_id, 0));
GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, sampler->cube_map_texture_id));
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
while(status != GL_FRAMEBUFFER_COMPLETE) {
status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
LV_LOG_ERROR("Environnement render error not complete. Expected %d. Got %d", GL_FRAMEBUFFER_COMPLETE,
status);
}
GL_CALL(glViewport(0, 0, sampler->cube_map_resolution, sampler->cube_map_resolution));
GL_CALL(glClearColor(1.0, 0.0, 0.0, 0.0));
GL_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
lv_opengl_shader_params_t frag_shader = {.name = "panorama_to_cubemap.frag"};
lv_opengl_shader_params_t vert_shader = {.name = "fullscreen.vert"};
lv_opengl_shader_program_t * program = lv_opengl_shader_manager_compile_program_best_version(&sampler->shader_manager,
&frag_shader, &vert_shader,
GLSL_VERSIONS,
GLSL_VERSION_COUNT);
LV_ASSERT_MSG(program != NULL,
"Failed to link program. This probably means your platform doesn't support a required GLSL version");
GLuint program_id = lv_opengl_shader_program_get_id(program);
GL_CALL(glUseProgram(program_id));
GL_CALL(glActiveTexture(GL_TEXTURE0 + 0));
// Bind texture ID to active texture
GL_CALL(glBindTexture(GL_TEXTURE_2D, sampler->input_texture_id));
// map shader uniform to texture unit (TEXTURE0)
GLuint location;
GL_CALL(location = glGetUniformLocation(program_id, "u_panorama"));
GL_CALL(glUniform1i(location, 0));
program->update_uniform_1i(program, "u_currentFace", i);
//fullscreen triangle
draw_fullscreen_quad(sampler, program_id);
}
GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, sampler->cube_map_texture_id));
GL_CALL(glGenerateMipmap(GL_TEXTURE_CUBE_MAP));
}
static void ibl_apply_filter(lv_gltf_ibl_sampler_t * sampler, uint32_t distribution, float roughness,
uint32_t target_mip_level, GLuint target_texture, uint32_t sample_count, float lod_bias)
{
uint32_t current_texture_size = sampler->cube_map_resolution >> target_mip_level;
for(uint32_t i = 0; i < 6; ++i) {
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, sampler->framebuffer));
GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
target_texture, target_mip_level));
GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, target_texture));
GL_CALL(glViewport(0, 0, current_texture_size, current_texture_size));
GL_CALL(glClearColor(0.0, 1.0, 0.0, 0.0));
GL_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
lv_opengl_shader_params_t frag_shader = {.name = "ibl_filtering.frag"};
lv_opengl_shader_params_t vert_shader = {.name = "fullscreen.vert"};
lv_opengl_shader_program_t * program =
lv_opengl_shader_manager_compile_program_best_version(&sampler->shader_manager, &frag_shader, &vert_shader,
GLSL_VERSIONS,
GLSL_VERSION_COUNT);
LV_ASSERT_MSG(program != NULL,
"Failed to link program. This probably means your platform doesn't support a required GLSL version");
GLuint program_id = lv_opengl_shader_program_get_id(program);
GL_CALL(glUseProgram(program_id));
GL_CALL(glActiveTexture(GL_TEXTURE0));
// Bind texture ID to active texture
GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, sampler->cube_map_texture_id));
// map shader uniform to texture unit (TEXTURE0)
uint32_t location = glGetUniformLocation(program_id, "u_cubemapTexture");
GL_CALL(glUniform1i(location, 0)); // texture unit 0
program->update_uniform_1f(program, "u_roughness", roughness);
program->update_uniform_1i(program, "u_sampleCount", sample_count);
/* Software rendered mode looks better with this and horrible with below */
/*program->update_uniform_1i(program, "u_width", current_texture_size); */
/* Standard mode looks best with this and somewhat worse with above */
program->update_uniform_1i(program, "u_width", sampler->cube_map_resolution);
program->update_uniform_1f(program, "u_lodBias", lod_bias);
program->update_uniform_1i(program, "u_distribution", distribution);
program->update_uniform_1i(program, "u_currentFace", i);
program->update_uniform_1i(program, "u_isGeneratingLUT", 0);
program->update_uniform_1i(program, "u_floatTexture", 0);
program->update_uniform_1f(program, "u_intensityScale", sampler->scale_value);
//fullscreen triangle
draw_fullscreen_quad(sampler, program_id);
}
}
static void ibl_cube_map_to_lambertian(lv_gltf_ibl_sampler_t * sampler)
{
ibl_apply_filter(sampler, 0, 0.0, 0, sampler->lambertian_texture_id, sampler->lambertian_sample_count, 0.0);
}
static void ibl_cube_map_to_ggx(lv_gltf_ibl_sampler_t * sampler)
{
LV_ASSERT(sampler->mipmap_levels != 1);
for(uint32_t current_mip_level = 0; current_mip_level <= sampler->mipmap_levels; ++current_mip_level) {
float roughness = (current_mip_level) / (float)(sampler->mipmap_levels - 1);
ibl_apply_filter(sampler, 1, roughness, current_mip_level, sampler->ggx_texture_id, sampler->ggx_sample_count,
0.0);
}
}
static void ibl_cube_map_to_sheen(lv_gltf_ibl_sampler_t * sampler)
{
LV_ASSERT(sampler->mipmap_levels != 1);
for(uint32_t current_mip_level = 0; current_mip_level <= sampler->mipmap_levels; ++current_mip_level) {
float roughness = (current_mip_level) / (float)(sampler->mipmap_levels - 1);
ibl_apply_filter(sampler, 2, roughness, current_mip_level, sampler->sheen_texture_id,
sampler->sheen_sample_count, 0.0);
}
}
static void ibl_sample_lut(lv_gltf_ibl_sampler_t * sampler, uint32_t distribution, uint32_t targetTexture,
uint32_t currentTextureSize)
{
GL_CALL(glBindFramebuffer(GL_FRAMEBUFFER, sampler->framebuffer));
GL_CALL(glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, targetTexture, 0));
GL_CALL(glBindTexture(GL_TEXTURE_2D, targetTexture));
GL_CALL(glViewport(0, 0, currentTextureSize, currentTextureSize));
GL_CALL(glClearColor(0.0, 1.0, 1.0, 0.0));
GL_CALL(glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
lv_opengl_shader_params_t frag_shader = {.name = "ibl_filtering.frag"};
lv_opengl_shader_params_t vert_shader = {.name = "fullscreen.vert"};
lv_opengl_shader_program_t * program =
lv_opengl_shader_manager_compile_program_best_version(&sampler->shader_manager, &frag_shader, &vert_shader,
GLSL_VERSIONS,
GLSL_VERSION_COUNT);
LV_ASSERT_MSG(program != NULL,
"Failed to link program. This probably means your platform doesn't support a required GLSL version");
GLuint program_id = lv_opengl_shader_program_get_id(program);
GL_CALL(glUseProgram(program_id));
// TEXTURE0 = active.
GL_CALL(glActiveTexture(GL_TEXTURE0 + 0));
// Bind texture ID to active texture
GL_CALL(glBindTexture(GL_TEXTURE_CUBE_MAP, sampler->cube_map_texture_id));
// map shader uniform to texture unit (TEXTURE0)
uint32_t location = glGetUniformLocation(program_id, "u_cubemapTexture");
GL_CALL(glUniform1i(location, 0)); // texture unit 0
program->update_uniform_1f(program, "u_roughness", 0.0);
program->update_uniform_1i(program, "u_sampleCount", sampler->lut_sample_count);
//shader->update_uniform_1i( shader, "u_sampleCount", 512);
program->update_uniform_1i(program, "u_width", 0.0);
program->update_uniform_1f(program, "u_lodBias", 0.0);
program->update_uniform_1i(program, "u_distribution", distribution);
program->update_uniform_1i(program, "u_currentFace", 0);
program->update_uniform_1i(program, "u_isGeneratingLUT", 1);
//fullscreen triangle
draw_fullscreen_quad(sampler, program_id);
}
static void ibl_sample_ggx_lut(lv_gltf_ibl_sampler_t * sampler)
{
sampler->ggxlut_texture_id = ibl_create_lut_texture(sampler);
ibl_sample_lut(sampler, 1, sampler->ggxlut_texture_id, sampler->lut_resolution);
}
static void ibl_sample_charlie_lut(lv_gltf_ibl_sampler_t * sampler)
{
sampler->charlielut_texture_id = ibl_create_lut_texture(sampler);
ibl_sample_lut(sampler, 2, sampler->charlielut_texture_id, sampler->lut_resolution);
}
static bool ibl_gl_has_extension(const char * extension)
{
const GLubyte * extensions = glGetString(GL_EXTENSIONS);
if(!extensions) {
return false;
}
const char * ext_str = (const char *)extensions;
const char * current = ext_str;
const char * next;
while(*current) {
/* Find the next space or end of string */
next = strchr(current, ' ');
if(next) {
size_t length = next - current;
if(length == strlen(extension) && strncmp(current, extension, length) == 0) {
return true;
}
current = next + 1;
}
else {
/* Last extension (no space found) */
if(strcmp(current, extension) == 0) {
return true;
}
break;
}
}
return false;
}
static int ibl_count_bits(int value)
{
int count = 0;
while(value > 1) {
value >>= 1;
count++;
}
return count;
}
static void init_fullscreen_quad(lv_gltf_ibl_sampler_t * sampler)
{
/* Vertices go from -1 -1 (left bottom) to 1 1 (right top)*/
GLfloat vertices[] = {
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
};
/* Texture coords go from 0 0 (left botton) to 1 1 (right top)*/
GLfloat texCoords[] = {
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
};
GL_CALL(glGenBuffers(1, &sampler->fullscreen_vertex_buffer));
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, sampler->fullscreen_vertex_buffer));
GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW));
GL_CALL(glGenBuffers(1, &sampler->fullscreen_tex_coord_buffer));
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, sampler->fullscreen_tex_coord_buffer));
GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(texCoords), texCoords, GL_STATIC_DRAW));
}
static void draw_fullscreen_quad(lv_gltf_ibl_sampler_t * sampler, GLuint program_id)
{
GLuint positionAttrib = glGetAttribLocation(program_id, "aPosition");
GL_CALL(glEnableVertexAttribArray(positionAttrib));
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, sampler->fullscreen_vertex_buffer));
GL_CALL(glVertexAttribPointer(positionAttrib, 2, GL_FLOAT, GL_FALSE, 0, (void *)0));
GLuint texCoordAttrib = glGetAttribLocation(program_id, "aTexCoord");
GL_CALL(glEnableVertexAttribArray(texCoordAttrib));
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, sampler->fullscreen_tex_coord_buffer));
GL_CALL(glVertexAttribPointer(texCoordAttrib, 2, GL_FLOAT, GL_FALSE, 0, (void *)0));
GL_CALL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
GL_CALL(glDisableVertexAttribArray(positionAttrib));
GL_CALL(glDisableVertexAttribArray(texCoordAttrib));
}
#endif /*LV_USE_GLTF*/

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,39 @@
/**
* @file lv_gltf_view_shader.h
*
*/
#ifndef LV_GLTF_VIEW_SHADER_H
#define LV_GLTF_VIEW_SHADER_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../../../drivers/opengles/opengl_shader/lv_opengl_shader_internal.h"
#if LV_USE_GLTF
/**********************
* GLOBAL PROTOTYPES
**********************/
char *lv_gltf_view_shader_get_vertex(void);
char *lv_gltf_view_shader_get_fragment(void);
void lv_gltf_view_shader_get_src(lv_opengl_shader_portions_t *shaders);
void lv_gltf_view_shader_get_env(lv_opengl_shader_portions_t *shaders);
/**********************
* MACROS
**********************/
#endif /*LV_USE_GLTF*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_GLTF_VIEW_SHADER_H*/

View File

@@ -0,0 +1,421 @@
/**
* @file lv_gltf.h
*
*/
#ifndef LV_GLTF_H
#define LV_GLTF_H
/*********************
* INCLUDES
*********************/
#include "../../../lv_conf_internal.h"
#if LV_USE_GLTF
#include "../math/lv_3dmath.h"
#include "../../../misc/lv_types.h"
#include "../../../misc/lv_area.h"
#include "../gltf_data/lv_gltf_model.h"
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* DEFINES
*********************/
#define LV_GLTF_ANIM_SPEED_TENTH 100
#define LV_GLTF_ANIM_SPEED_QUARTER 250
#define LV_GLTF_ANIM_SPEED_HALF 500
#define LV_GLTF_ANIM_SPEED_NORMAL 1000
#define LV_GLTF_ANIM_SPEED_2X 2000
#define LV_GLTF_ANIM_SPEED_3X 3000
#define LV_GLTF_ANIM_SPEED_4X 4000
#define LV_GLTF_DEFAULT_CAMERA 0
/**********************
* TYPEDEFS
**********************/
typedef enum {
LV_GLTF_AA_MODE_OFF = 0, /** Anti aliasing off*/
LV_GLTF_AA_MODE_ON = 1, /** Anti aliasing on*/
LV_GLTF_AA_MODE_DYNAMIC = 2, /** Anti aliasing on only when frame has no movement*/
} lv_gltf_aa_mode_t;
typedef enum {
LV_GLTF_BG_MODE_SOLID = 0, /** Solid background. Use `lv_obj_set_style_bg_color` to set the background color*/
LV_GLTF_BG_MODE_ENVIRONMENT = 1, /** Environnement background*/
} lv_gltf_bg_mode_t;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Create a glTF object
* @param parent pointer to the parent object
* @return pointer to the created glTF object
*/
lv_obj_t * lv_gltf_create(lv_obj_t * parent);
/**
* Assign an environment to a glTF object for IBL rendering
* @param obj pointer to a glTF viewer object
* @param environment pointer to the environment to use
* @note The environment can be shared across multiple glTF objects
* @note If no environment is set before attempting to load a file,
* a default one will be created for you
*/
void lv_gltf_set_environment(lv_obj_t * obj, lv_gltf_environment_t * environment);
/**
* Load a glTF model from a file into the viewer
* @param obj pointer to a glTF viewer object
* @param path file path to the glTF model to load
* @return pointer to the loaded glTF model, or NULL on failure
*/
lv_gltf_model_t * lv_gltf_load_model_from_file(lv_obj_t * obj, const char * path);
/**
* Load a glTF model from a byte array into the viewer
* @param obj pointer to a glTF viewer object
* @param bytes glTF raw data
* @param len glTF raw data length in bytes
* @return pointer to the loaded glTF model, or NULL on failure
*/
lv_gltf_model_t * lv_gltf_load_model_from_bytes(lv_obj_t * obj, const uint8_t * bytes, size_t len);
/**
* Get the number of models loaded in the glTF viewer
* @param obj pointer to a glTF viewer object
* @return the total number of models in the viewer
*/
size_t lv_gltf_get_model_count(lv_obj_t * obj);
/**
* Get a specific model by its index
* @param obj pointer to a glTF viewer object
* @param id index of the model to retrieve (0-based)
* @return pointer to the model at the specified index, or NULL if index is invalid
*/
lv_gltf_model_t * lv_gltf_get_model_by_index(lv_obj_t * obj, size_t id);
/**
* Get the primary model from the glTF viewer
* The primary model is the first model added to the viewer and can be used
* for camera selection and other primary operations
* @param obj pointer to a glTF viewer object
* @return pointer to the primary model, or NULL if no models are loaded
*/
lv_gltf_model_t * lv_gltf_get_primary_model(lv_obj_t * obj);
/**
* Set the yaw (horizontal rotation) of the camera
* @param obj pointer to a glTF viewer object
* @param yaw yaw angle in degrees
*/
void lv_gltf_set_yaw(lv_obj_t * obj, float yaw);
/**
* Get the yaw (horizontal rotation) of the camera
* @param obj pointer to a glTF viewer object
* @return yaw angle in degrees
*/
float lv_gltf_get_yaw(const lv_obj_t * obj);
/**
* Set the pitch (vertical rotation) of the camera
* @param obj pointer to a glTF viewer object
* @param pitch pitch angle in degrees
*/
void lv_gltf_set_pitch(lv_obj_t * obj, float pitch);
/**
* Get the pitch (vertical rotation) of the camera
* @param obj pointer to a glTF viewer object
* @return pitch angle in degrees
*/
float lv_gltf_get_pitch(const lv_obj_t * obj);
/**
* Set the camera distance from the focal point
* @param obj pointer to a glTF viewer object
* @param value distance value
*/
void lv_gltf_set_distance(lv_obj_t * obj, float value);
/**
* Get the camera distance scale factor from the focal point
* @param obj pointer to a glTF viewer object
* @return distance scaling factor value
*/
float lv_gltf_get_distance(const lv_obj_t * obj);
/**
* Get the camera distance from the focal point in world units
* @param obj pointer to a GLTF viewer object
* @return world unit distance value
*/
float lv_gltf_get_world_distance(const lv_obj_t * obj);
/**********************
* Viewport Functions
**********************/
/**
* Set the field of view
* @param obj pointer to a glTF viewer object
* @param value vertical FOV in degrees. If zero, the view will be orthographic (non-perspective)
*/
void lv_gltf_set_fov(lv_obj_t * obj, float value);
/**
* Get the field of view
* @param obj pointer to a glTF viewer object
* @return vertical FOV in degrees
*/
float lv_gltf_get_fov(const lv_obj_t * obj);
/**********************
* Focal Point Functions
**********************/
/**
* Set the X coordinate of the camera focal point
* @param obj pointer to a glTF viewer object
* @param value X coordinate
*/
void lv_gltf_set_focal_x(lv_obj_t * obj, float value);
/**
* Get the X coordinate of the camera focal point
* @param obj pointer to a glTF viewer object
* @return X coordinate
*/
float lv_gltf_get_focal_x(const lv_obj_t * obj);
/**
* Set the Y coordinate of the camera focal point
* @param obj pointer to a glTF viewer object
* @param value Y coordinate
*/
void lv_gltf_set_focal_y(lv_obj_t * obj, float value);
/**
* Get the Y coordinate of the camera focal point
* @param obj pointer to a glTF viewer object
* @return Y coordinate
*/
float lv_gltf_get_focal_y(const lv_obj_t * obj);
/**
* Set the Z coordinate of the camera focal point
* @param obj pointer to a glTF viewer object
* @param value Z coordinate
*/
void lv_gltf_set_focal_z(lv_obj_t * obj, float value);
/**
* Get the Z coordinate of the camera focal point
* @param obj pointer to a glTF viewer object
* @return Z coordinate
*/
float lv_gltf_get_focal_z(const lv_obj_t * obj);
/**
* Set the focal coordinates to the center point of the model object
* @param obj pointer to a glTF viewer object
* @param model a model attached to this viewer or NULL for the first model
*/
void lv_gltf_recenter(lv_obj_t * obj, lv_gltf_model_t * model);
/**********************
* Scene Control Functions
**********************/
/**
* Set the active camera index
* The camera is selected from the first glTF model added to the viewer
*
* @param obj pointer to a glTF viewer object
* @param value camera index (0 for default camera, 1+ for scene camera index)
* @note Values higher than the scene's camera count will be clamped to the maximum available camera index
*/
void lv_gltf_set_camera(lv_obj_t * obj, uint32_t value);
/**
* Get the active camera index
* @param obj pointer to a glTF viewer object
* @return active camera index
*/
uint32_t lv_gltf_get_camera(const lv_obj_t * obj);
/**
* Get the number of cameras in the first glTF model added to the viewer
* This count represents the valid range for the camera index parameter
* used with lv_gltf_set_camera()
*
* To get the camera count of other models, call
* lv_gltf_model_get_camera_count(model) directly with the specific model
*
* @param obj pointer to a glTF viewer object
* @return number of available cameras
*/
uint32_t lv_gltf_get_camera_count(const lv_obj_t * obj);
/**
* Set the animation speed ratio
*
* The actual ratio is the value parameter / LV_GLTF_ANIM_SPEED_NORMAL
* Values greater than LV_GLTF_ANIM_SPEED_NORMAL will speed-up the animation
* Values less than LV_GLTF_ANIM_SPEED_NORMAL will slow down the animation
*
* @param obj pointer to a glTF viewer object
* @param value speed-up ratio of the animation
*/
void lv_gltf_set_animation_speed(lv_obj_t * obj, uint32_t value);
/**
* Get the animation speed ratio
*
* The actual ratio is the return value / LV_GLTF_ANIM_SPEED_NORMAL
*
* @param obj pointer to a glTF viewer object
*/
uint32_t lv_gltf_get_animation_speed(const lv_obj_t * obj);
/**********************
* Visual Settings Functions
**********************/
/**
* Set the background mode
* @param obj pointer to a glTF viewer object
* @param value background mode
*/
void lv_gltf_set_background_mode(lv_obj_t * obj, lv_gltf_bg_mode_t value);
/**
* Get the background mode
* @param obj pointer to a glTF viewer object
* @return background mode
*/
lv_gltf_bg_mode_t lv_gltf_get_background_mode(const lv_obj_t * obj);
/**
* Set the background blur amount
* @param obj pointer to a glTF viewer object
* @param value blur amount between 0 and 100
*/
void lv_gltf_set_background_blur(lv_obj_t * obj, uint32_t value);
/**
* Get the background blur amount
* @param obj pointer to a glTF viewer object
* @return blur amount between 0 and 100
*/
uint32_t lv_gltf_get_background_blur(const lv_obj_t * obj);
/**
* Set the environmental brightness/power
* @param obj pointer to a glTF viewer object
* @param value brightness multiplier
*/
void lv_gltf_set_env_brightness(lv_obj_t * obj, uint32_t value);
/**
* Get the environmental brightness/power
* @param obj pointer to a glTF viewer object
* @return brightness multiplier
*/
uint32_t lv_gltf_get_env_brightness(const lv_obj_t * obj);
/**
* Set the image exposure level
* @param obj pointer to a glTF viewer object
* @param value exposure level (1.0 is default)
*/
void lv_gltf_set_image_exposure(lv_obj_t * obj, float value);
/**
* Get the image exposure level
* @param obj pointer to a glTF viewer object
* @return exposure level
*/
float lv_gltf_get_image_exposure(const lv_obj_t * obj);
/**********************
* Rendering Functions
**********************/
/**
* Set the anti-aliasing mode
* @param obj pointer to a glTF viewer object
* @param value anti-aliasing mode
*/
void lv_gltf_set_antialiasing_mode(lv_obj_t * obj, lv_gltf_aa_mode_t value);
/**
* Get the anti-aliasing mode
* @param obj pointer to a glTF viewer object
* @return anti-aliasing mode
*/
lv_gltf_aa_mode_t lv_gltf_get_antialiasing_mode(const lv_obj_t * obj);
/***********************
* Raycasting Functions
***********************/
/**
* Get the point that a given ray intersects with a specified plane at, if any
* @param ray the intersection test ray
* @param screen_y the plane to test ray intersection with
* @param collision_point output lv_3dpoint_t holder, values are only valid if true is the return value
* @return LV_RESULT_OK if intersection, LV_RESULT_INVALID if no intersection
*/
lv_result_t lv_intersect_ray_with_plane(const lv_3dray_t * ray, const lv_3dplane_t * plane,
lv_3dpoint_t * collision_point);
/**
* Get a plane that faces the current view camera, centered some units in front of it
* @param obj pointer to a GLTF viewer object
* @param distance distance in front of the camera to set the plane, in world units. see lv_gltf_get_world_distance to get the auto-distance
* @return camera facing plane
*/
lv_3dplane_t lv_gltf_get_current_view_plane(lv_obj_t * obj, float distance);
/**
* Calculates a ray originating from the camera and passing through the specified mouse position on the screen.
* @param obj pointer to a GLTF viewer object
* @param screen_pos screen co-ordinate, in pixels
* @return mouse point ray
*/
lv_3dray_t lv_gltf_get_ray_from_2d_coordinate(lv_obj_t * obj, const lv_point_t * screen_pos);
/**
* Get the screen position of a 3d point
* @param obj pointer to a GLTF viewer object
* @param world_pos world position to convert
* @param lv_point_t the resulting point, in pixels. only valid if return value is true
* @return LV_RESULT_OK if conversion valid, LV_RESULT_INVALID if no valid conversion
*/
lv_result_t lv_gltf_world_to_screen(lv_obj_t * obj, const lv_3dpoint_t world_pos, lv_point_t * screen_pos);
/**********************
* MACROS
**********************/
#ifdef __cplusplus
}
#endif
#endif /*LV_USE_GLTF*/
#endif /*LV_GLTF_H*/

View File

@@ -0,0 +1,863 @@
/**
* @file lv_gltf_view.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_view_internal.h"
#if LV_USE_GLTF
#include "../gltf_data/lv_gltf_model.h"
#include "../gltf_data/lv_gltf_data_internal.hpp"
#include "../../../draw/lv_draw_3d.h"
#include "../fastgltf/lv_fastgltf.hpp"
#include "../../../core/lv_obj_class_private.h"
#include "../../../misc/lv_types.h"
#include "../../../widgets/3dtexture/lv_3dtexture.h"
#include "../gltf_environment/lv_gltf_environment.h"
#include "assets/lv_gltf_view_shader.h"
#include <fastgltf/math.hpp>
#include <fastgltf/tools.hpp>
/*********************
* DEFINES
*********************/
#define MY_CLASS (&lv_gltf_class)
#ifndef LV_GLTF_INITIAL_MODEL_CAPACITY
#define LV_GLTF_INITIAL_MODEL_CAPACITY 1
#endif /*LV_GLTF_INITIAL_MODEL_CAPACITY*/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static lv_gltf_model_t * lv_gltf_add_model(lv_gltf_t * viewer, lv_gltf_model_t * model);
static void lv_gltf_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_gltf_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_gltf_event(const lv_obj_class_t * class_p, lv_event_t * e);
static void lv_gltf_view_state_init(lv_gltf_t * state);
static void lv_gltf_view_desc_init(lv_gltf_view_desc_t * state);
static void lv_gltf_parse_model(lv_gltf_t * viewer, lv_gltf_model_t * model);
static void setup_compile_and_load_bg_shader(lv_opengl_shader_manager_t * manager);
static void setup_background_environment(GLuint program, GLuint * vao, GLuint * indexBuffer, GLuint * vertexBuffer);
static lv_result_t create_default_environment(lv_gltf_t * gltf);
static void display_refr_end_event_cb(lv_event_t * e);
const lv_obj_class_t lv_gltf_class {
&lv_3dtexture_class,
lv_gltf_constructor,
lv_gltf_destructor,
lv_gltf_event,
#if LV_USE_OBJ_PROPERTY
0,
0,
NULL,
0,
NULL,
0,
#endif
NULL,
"lv_gltf",
LV_DPI_DEF * 2,
LV_DPI_DEF / 10,
LV_OBJ_CLASS_EDITABLE_INHERIT,
LV_OBJ_CLASS_GROUP_DEF_INHERIT,
sizeof(lv_gltf_t),
LV_OBJ_CLASS_THEME_INHERITABLE_FALSE
};
/**********************
* STATIC VARIABLES
**********************/
static const lv_opengl_glsl_version_t GLSL_VERSIONS[] {
LV_OPENGL_GLSL_VERSION_300ES,
LV_OPENGL_GLSL_VERSION_330,
};
static const size_t GLSL_VERSION_COUNT = sizeof(GLSL_VERSIONS) / sizeof(GLSL_VERSIONS[0]);
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_obj_t * lv_gltf_create(lv_obj_t * parent)
{
lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
lv_obj_class_init_obj(obj);
lv_display_t * disp = lv_obj_get_display(obj);
LV_ASSERT_NULL(disp);
lv_display_add_event_cb(disp, display_refr_end_event_cb, LV_EVENT_REFR_READY, obj);
return obj;
}
lv_gltf_model_t * lv_gltf_load_model_from_file(lv_obj_t * obj, const char * path)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
if(!viewer->environment) {
lv_result_t res = create_default_environment(viewer);
if(res != LV_RESULT_OK) {
return NULL;
}
}
lv_gltf_model_t * model = lv_gltf_data_load_from_file(path, &viewer->shader_manager);
return lv_gltf_add_model(viewer, model);
}
lv_gltf_model_t * lv_gltf_load_model_from_bytes(lv_obj_t * obj, const uint8_t * bytes, size_t len)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
if(!viewer->environment) {
lv_result_t res = create_default_environment(viewer);
if(res != LV_RESULT_OK) {
return NULL;
}
}
lv_gltf_model_t * model = lv_gltf_data_load_from_bytes(bytes, len, &viewer->shader_manager);
return lv_gltf_add_model(viewer, model);
}
void lv_gltf_set_environment(lv_obj_t * obj, lv_gltf_environment_t * env)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * gltf = (lv_gltf_t *)obj;
if(env == NULL) {
LV_LOG_WARN("Refusing to assign a NULL environment to the glTF object");
return;
}
if(gltf->environment && gltf->owns_environment) {
lv_gltf_environment_delete(gltf->environment);
gltf->environment = NULL;
}
gltf->environment = env;
gltf->owns_environment = false;
}
size_t lv_gltf_get_model_count(lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
return lv_array_size(&((lv_gltf_t *)obj)->models);
}
lv_gltf_model_t * lv_gltf_get_model_by_index(lv_obj_t * obj, size_t id)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *) obj;
if(id >= lv_array_size(&viewer->models)) {
return NULL;
}
return *(lv_gltf_model_t **)lv_array_at(&((lv_gltf_t *)obj)->models, id);
}
lv_gltf_model_t * lv_gltf_get_primary_model(lv_obj_t * obj)
{
return lv_gltf_get_model_by_index(obj, 0);
}
void lv_gltf_set_yaw(lv_obj_t * obj, float yaw)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.yaw = yaw;
lv_obj_invalidate(obj);
}
float lv_gltf_get_yaw(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.yaw;
}
void lv_gltf_set_pitch(lv_obj_t * obj, float pitch)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.pitch = pitch;
lv_obj_invalidate(obj);
}
float lv_gltf_get_pitch(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.pitch;
}
void lv_gltf_set_fov(lv_obj_t * obj, float value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.fov = value;
lv_obj_invalidate(obj);
}
float lv_gltf_get_fov(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.fov;
}
void lv_gltf_set_distance(lv_obj_t * obj, float value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.distance = value;
lv_obj_invalidate(obj);
}
float lv_gltf_get_distance(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.distance;
}
float lv_gltf_get_world_distance(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
lv_gltf_view_desc_t * view_desc = &viewer->desc;
if(viewer->models.size == 0) {
return 0.0f;
}
lv_gltf_model_t * model = *(lv_gltf_model_t **)lv_array_at(&viewer->models, 0);
return (lv_gltf_data_get_radius(model) * LV_GLTF_DISTANCE_SCALE_FACTOR) * view_desc->distance;
}
void lv_gltf_set_animation_speed(lv_obj_t * obj, uint32_t value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.animation_speed_ratio = value;
lv_obj_invalidate(obj);
}
uint32_t lv_gltf_get_animation_speed(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.animation_speed_ratio;
}
void lv_gltf_set_focal_x(lv_obj_t * obj, float value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.focal_x = value;
lv_obj_invalidate(obj);
}
float lv_gltf_get_focal_x(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.focal_x;
}
void lv_gltf_set_focal_y(lv_obj_t * obj, float value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.focal_y = value;
lv_obj_invalidate(obj);
}
float lv_gltf_get_focal_y(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.focal_y;
}
void lv_gltf_set_focal_z(lv_obj_t * obj, float value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.focal_z = value;
lv_obj_invalidate(obj);
}
float lv_gltf_get_focal_z(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.focal_z;
}
void lv_gltf_set_camera(lv_obj_t * obj, uint32_t value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
if(lv_array_is_empty(&viewer->models)) {
return;
}
lv_gltf_model_t * model = *(lv_gltf_model_t **) lv_array_at(&viewer->models, 0);
if(value > model->asset.cameras.size()) {
return;
}
model->camera = value;
lv_obj_invalidate(obj);
}
uint32_t lv_gltf_get_camera(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
if(lv_array_is_empty(&viewer->models)) {
return 0;
}
const lv_gltf_model_t * model = *(const lv_gltf_model_t **)lv_array_at(&viewer->models, 0);
return model->camera;
}
uint32_t lv_gltf_get_camera_count(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
if(lv_array_is_empty(&viewer->models)) {
return 0;
}
const lv_gltf_model_t * model = *(const lv_gltf_model_t **) lv_array_at(&viewer->models, 0);
return lv_gltf_model_get_camera_count(model);
}
void lv_gltf_set_antialiasing_mode(lv_obj_t * obj, lv_gltf_aa_mode_t value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.aa_mode = value;
lv_obj_invalidate(obj);
}
lv_gltf_aa_mode_t lv_gltf_get_antialiasing_mode(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.aa_mode;
}
void lv_gltf_set_background_mode(lv_obj_t * obj, lv_gltf_bg_mode_t value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.bg_mode = value;
lv_obj_invalidate(obj);
}
lv_gltf_bg_mode_t lv_gltf_get_background_mode(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.bg_mode;
}
void lv_gltf_set_background_blur(lv_obj_t * obj, uint32_t value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
if(value > 100) {
value = 100;
}
viewer->desc.blur_bg = value / 100.f;
lv_obj_invalidate(obj);
}
uint32_t lv_gltf_get_background_blur(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.blur_bg * 100;
}
void lv_gltf_set_env_brightness(lv_obj_t * obj, uint32_t value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.env_pow = value / 100.;
lv_obj_invalidate(obj);
}
uint32_t lv_gltf_get_env_brightness(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.env_pow * 100;
}
void lv_gltf_set_image_exposure(lv_obj_t * obj, float value)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
viewer->desc.exposure = value;
lv_obj_invalidate(obj);
}
float lv_gltf_get_image_exposure(const lv_obj_t * obj)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
return viewer->desc.exposure;
}
void lv_gltf_recenter(lv_obj_t * obj, lv_gltf_model_t * model)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
if(model == NULL) {
LV_ASSERT(lv_array_size(&viewer->models) > 0);
model = *(lv_gltf_model_t **)lv_array_at(&viewer->models, 0);
}
const auto & center_position = lv_gltf_data_get_center(model);
viewer->desc.focal_x = center_position[0];
viewer->desc.focal_y = center_position[1];
viewer->desc.focal_z = center_position[2];
}
lv_3dray_t lv_gltf_get_ray_from_2d_coordinate(lv_obj_t * obj, const lv_point_t * screen_pos)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
float norm_mouse_x = (float)screen_pos->x / (float)(lv_obj_get_width(obj));
float norm_mouse_y = (float)screen_pos->y / (float)(lv_obj_get_height(obj));
lv_3dray_t outray {{0, 0, 0}, {0, 0, 0}};
fastgltf::math::fmat4x4 proj_mat = fastgltf::math::inverse(fastgltf::math::fmat4x4(viewer->projection_matrix));
/* Convert mouse coordinates to NDC */
float x = norm_mouse_x * 2.0f - 1.0f;
float y = 1.0f - (norm_mouse_y * 2.0f);
float z = -1.0f; /* Clip space z */
fastgltf::math::fvec4 clip_space_pos = fastgltf::math::fvec4(x, y, z, 1.f);
auto ray_eye = (proj_mat) * clip_space_pos;
ray_eye[2] = -1.0f;
ray_eye[3] = 0.0f;
/* Calculate ray world direction */
fastgltf::math::fvec4 ray_world = fastgltf::math::inverse(viewer->view_matrix) * ray_eye;
auto ray_direction = fastgltf::math::normalize(fastgltf::math::fvec3(ray_world[0], ray_world[1], ray_world[2]));
outray.direction = {ray_direction[0], ray_direction[1], ray_direction[2]};
outray.origin = {viewer->camera_pos[0], viewer->camera_pos[1], viewer->camera_pos[2]};
return outray;
}
lv_result_t lv_intersect_ray_with_plane(const lv_3dray_t * ray, const lv_3dplane_t * plane,
lv_3dpoint_t * collision_point)
{
fastgltf::math::fvec3 plane_center = fastgltf::math::fvec3(plane->origin.x, plane->origin.y, plane->origin.z);
fastgltf::math::fvec3 plane_normal = fastgltf::math::fvec3(plane->direction.x, plane->direction.y, plane->direction.z);
fastgltf::math::fvec3 ray_start = fastgltf::math::fvec3(ray->origin.x, ray->origin.y, ray->origin.z);
fastgltf::math::fvec3 ray_direction = fastgltf::math::fvec3(ray->direction.x, ray->direction.y, ray->direction.z);
float denom = fastgltf::math::dot(plane_normal, ray_direction);
if(fabs(denom) > 1e-6) { /* Check if the ray is not parallel to the plane */
fastgltf::math::fvec3 diff = plane_center - ray_start;
float t = fastgltf::math::dot(diff, plane_normal) / denom;
if(t >= 0) { /* Intersection occurs ahead of the ray origin */
/* Calculate the collision point */
(*collision_point).x = ray_start[0] + t * ray_direction[0];
(*collision_point).y = ray_start[1] + t * ray_direction[1];
(*collision_point).z = ray_start[2] + t * ray_direction[2];
return LV_RESULT_OK; /* Collision point found */
}
}
return LV_RESULT_INVALID; /* No intersection */
}
lv_3dplane_t lv_gltf_get_current_view_plane(lv_obj_t * obj, float distance)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
lv_3dplane_t outplane = {{0, 0, 0}, {0, 0, 0}};
/* Forward vector is the third column of the matrix */
auto forward = fastgltf::math::fvec3(viewer->view_matrix[0][2], viewer->view_matrix[1][2], viewer->view_matrix[2][2]);
forward = fastgltf::math::normalize(forward);
/* Calculate the plane center */
const auto & camera_pos = viewer->camera_pos;
auto plane_pos = fastgltf::math::fvec3(camera_pos[0], camera_pos[1], camera_pos[2]) - forward * distance;
outplane.origin = {plane_pos[0], plane_pos[1], plane_pos[2]};
outplane.direction = {-forward[0], -forward[1], -forward[2]};
return outplane;
}
lv_result_t lv_gltf_world_to_screen(lv_obj_t * obj, const lv_3dpoint_t world_pos, lv_point_t * screen_pos)
{
LV_ASSERT_NULL(obj);
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
fastgltf::math::fvec4 world_position_h = fastgltf::math::fvec4(world_pos.x, world_pos.y, world_pos.z, 1.0f);
fastgltf::math::fvec4 clip_space_pos = viewer->projection_matrix * viewer->view_matrix * world_position_h;
/* Check for perspective division (w must not be zero) */
if(clip_space_pos[3] == 0.0f) {
screen_pos->x = -1;
screen_pos->y = -1;
return LV_RESULT_INVALID; /* Position is not valid for screen mapping */
}
clip_space_pos /= clip_space_pos[3];
float norm_screen_x = clip_space_pos[0] * 0.5f + 0.5f;
float norm_screen_y = 0.5f - (clip_space_pos[1] * 0.5f);
int32_t win_width = lv_obj_get_width(obj);
int32_t win_height = lv_obj_get_height(obj);
screen_pos->x = norm_screen_x * win_width;
screen_pos->y = norm_screen_y * win_height;
return LV_RESULT_OK;
}
/**********************
* STATIC FUNCTIONS
**********************/
static lv_gltf_model_t * lv_gltf_add_model(lv_gltf_t * viewer, lv_gltf_model_t * model)
{
if(!model) {
return NULL;
}
if(lv_array_push_back(&viewer->models, &model) == LV_RESULT_INVALID) {
lv_gltf_data_delete(model);
return NULL;
}
model->viewer = viewer;
lv_gltf_parse_model(viewer, model);
if(lv_array_size(&viewer->models) == 1) {
lv_gltf_recenter((lv_obj_t *)viewer, model);
}
return model;
}
static lv_result_t create_default_environment(lv_gltf_t * gltf)
{
lv_gltf_ibl_sampler_t * sampler = lv_gltf_ibl_sampler_create();
gltf->environment = lv_gltf_environment_create(sampler, NULL);
lv_gltf_ibl_sampler_delete(sampler);
if(!gltf->environment) {
LV_LOG_WARN("Failed to create default gltf environment");
return LV_RESULT_INVALID;
}
gltf->owns_environment = true;
return LV_RESULT_OK;
}
static void lv_gltf_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
lv_gltf_t * view = (lv_gltf_t *)obj;
lv_gltf_view_state_init(view);
lv_gltf_view_desc_init(&view->desc);
view->view_matrix = fastgltf::math::fmat4x4(1.0f);
view->projection_matrix = fastgltf::math::fmat4x4(1.0f);
view->view_projection_matrix = fastgltf::math::fmat4x4(1.0f);
view->camera_pos = fastgltf::math::fvec3(0.0f);
view->texture.h_flip = false;
view->texture.v_flip = true;
new(&view->ibm_by_skin_then_node) std::map<int32_t, std::map<fastgltf::Node *, fastgltf::math::fmat4x4>>;
lv_opengl_shader_portions_t portions;
lv_gltf_view_shader_get_src(&portions);
char * vertex_shader = lv_gltf_view_shader_get_vertex();
char * frag_shader = lv_gltf_view_shader_get_fragment();
lv_opengl_shader_manager_init(&view->shader_manager, portions.all, portions.count, vertex_shader, frag_shader);
lv_free(vertex_shader);
lv_free(frag_shader);
lv_array_init(&view->models, LV_GLTF_INITIAL_MODEL_CAPACITY, sizeof(lv_gltf_model_t *));
LV_TRACE_OBJ_CREATE("end");
}
static void lv_gltf_event(const lv_obj_class_t * class_p, lv_event_t * e)
{
LV_UNUSED(class_p);
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = (lv_obj_t *)lv_event_get_current_target(e);
lv_gltf_t * viewer = (lv_gltf_t *)obj;
if(code == LV_EVENT_DRAW_MAIN) {
GLuint texture_id = lv_gltf_view_render(viewer);
lv_3dtexture_set_src((lv_obj_t *)&viewer->texture, (lv_3dtexture_id_t)texture_id);
}
lv_result_t res;
/*Call the ancestor's event handler*/
res = lv_obj_event_base(MY_CLASS, e);
if(res != LV_RESULT_OK) {
return;
}
}
static void lv_gltf_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
lv_gltf_t * view = (lv_gltf_t *)obj;
lv_opengl_shader_manager_deinit(&view->shader_manager);
using IbmBySkinThenNodeMap = std::map<int32_t, std::map<fastgltf::Node *, fastgltf::math::fmat4x4>>;
view->ibm_by_skin_then_node.~IbmBySkinThenNodeMap();
const size_t n = lv_array_size(&view->models);
for(size_t i = 0; i < n; ++i) {
lv_gltf_data_delete(*(lv_gltf_model_t **)lv_array_at(&view->models, i));
}
lv_array_deinit(&view->models);
if(view->environment && view->owns_environment) {
lv_gltf_environment_delete(view->environment);
}
lv_display_t * disp = lv_obj_get_display(obj);
LV_ASSERT_NULL(disp);
lv_display_remove_event_cb_with_user_data(disp, display_refr_end_event_cb, obj);
}
static void lv_gltf_view_state_init(lv_gltf_t * view)
{
lv_memset(&view->state, 0, sizeof(view->state));
view->state.opaque_frame_buffer_width = LV_GLTF_TRANSMISSION_PASS_SIZE;
view->state.opaque_frame_buffer_height = LV_GLTF_TRANSMISSION_PASS_SIZE;
view->state.material_variant = 0;
view->state.render_state_ready = false;
view->state.render_opaque_buffer = false;
}
static void lv_gltf_view_desc_init(lv_gltf_view_desc_t * desc)
{
lv_memset(desc, 0, sizeof(*desc));
desc->distance = 2.f;
desc->exposure = 1.0f;
desc->env_pow = 1.8f;
desc->blur_bg = 0.5f;
desc->bg_mode = LV_GLTF_BG_MODE_ENVIRONMENT;
desc->aa_mode = LV_GLTF_AA_MODE_OFF;
desc->fov = 45.f;
desc->animation_speed_ratio = LV_GLTF_ANIM_SPEED_NORMAL;
desc->frame_was_antialiased = false;
}
static void lv_gltf_parse_model(lv_gltf_t * viewer, lv_gltf_model_t * model)
{
const auto & iterate_callback = [&](fastgltf::Node & node, const fastgltf::math::fmat4x4 & matrix) {
LV_UNUSED(matrix);
if(!node.meshIndex) {
return;
}
auto & mesh_index = node.meshIndex.value();
if(node.skinIndex) {
auto skin_index = node.skinIndex.value();
if(!lv_gltf_data_validated_skins_contains(model, skin_index)) {
lv_gltf_data_validate_skin(model, skin_index);
auto skin = model->asset.skins[skin_index];
if(skin.inverseBindMatrices) {
auto & ibm_value = skin.inverseBindMatrices.value();
auto & ibm_accessor = model->asset.accessors[ibm_value];
if(ibm_accessor.bufferViewIndex) {
fastgltf::iterateAccessorWithIndex<fastgltf::math::fmat4x4>(
model->asset, ibm_accessor,
[&](fastgltf::math::fmat4x4 _matrix, std::size_t idx) {
auto & joint_node = model->asset.nodes[skin.joints[idx]];
viewer->ibm_by_skin_then_node[skin_index][&joint_node] = _matrix;
});
}
}
}
}
for(size_t mp = 0; mp < model->asset.meshes[mesh_index].primitives.size(); mp++) {
auto & model_primitive = model->asset.meshes[mesh_index].primitives[mp];
const auto & mappings = model_primitive.mappings;
ssize_t material_index =
(!mappings.empty() && mappings[viewer->state.material_variant]) ?
mappings[viewer->state.material_variant].value() + 1 :
((model_primitive.materialIndex) ? (model_primitive.materialIndex.value() + 1) : 0);
if(material_index < 0) {
lv_gltf_data_add_opaque_node_primitive(model, 0, &node, mp);
continue;
}
const fastgltf::Material & material = model->asset.materials[material_index - 1];
viewer->state.render_opaque_buffer |= material.transmission != NULL;
if(material.alphaMode == fastgltf::AlphaMode::Blend || material.transmission != NULL) {
lv_gltf_data_add_blended_node_primitive(model, material_index + 1, &node, mp);
}
else {
lv_gltf_data_add_opaque_node_primitive(model, material_index + 1, &node, mp);
}
lv_array_t defines;
lv_array_init(&defines, 64, sizeof(lv_opengl_shader_define_t));
lv_result_t result =
lv_gltf_view_shader_injest_discover_defines(&defines, model, &node, &model_primitive);
LV_ASSERT_MSG(result == LV_RESULT_OK, "Couldn't injest shader defines");
lv_opengl_shader_params_t frag_shader {"__MAIN__.frag", (lv_opengl_shader_define_t *) defines.data, lv_array_size(&defines) };
lv_opengl_shader_params_t vert_shader {"__MAIN__.vert", (lv_opengl_shader_define_t *) defines.data, lv_array_size(&defines) };
lv_opengl_shader_program_t * program = lv_opengl_shader_manager_compile_program_best_version(&viewer->shader_manager,
&frag_shader,
&vert_shader, GLSL_VERSIONS, GLSL_VERSION_COUNT);
LV_ASSERT_MSG(program != NULL,
"Failed to link program. This probably means your platform doesn't support a required GLSL version");
GLuint program_id = lv_opengl_shader_program_get_id(program);
GL_CALL(glUseProgram(program_id));
lv_gltf_compiled_shader_t compiled_shader { lv_gltf_uniform_locations_create(program_id), program_id, };
lv_gltf_store_compiled_shader(model, material_index, &compiled_shader);
const size_t n = lv_array_size(&defines);
for(size_t i = 0; i < n; ++i) {
lv_opengl_shader_define_t * define = (lv_opengl_shader_define_t *) lv_array_at(&defines, i);
if(define->value_allocated) {
lv_free((void *)define->value);
}
}
lv_array_deinit(&defines);
}
};
setup_compile_and_load_bg_shader(&viewer->shader_manager);
fastgltf::iterateSceneNodes(model->asset, 0, fastgltf::math::fmat4x4(), iterate_callback);
}
static void setup_compile_and_load_bg_shader(lv_opengl_shader_manager_t * manager)
{
lv_opengl_shader_define_t frag_defs[1] { { "TONEMAP_KHR_PBR_NEUTRAL", NULL, false} };
uint32_t frag_shader_hash ;
uint32_t vert_shader_hash;
lv_result_t res = lv_opengl_shader_manager_select_shader(manager, "cubemap.frag", frag_defs, 1,
LV_OPENGL_GLSL_VERSION_300ES,
&frag_shader_hash);
LV_ASSERT(res == LV_RESULT_OK);
res = lv_opengl_shader_manager_select_shader(manager, "cubemap.vert", nullptr, 0, LV_OPENGL_GLSL_VERSION_300ES,
&vert_shader_hash);
LV_ASSERT(res == LV_RESULT_OK);
lv_opengl_shader_program_t * program = lv_opengl_shader_manager_get_program(manager, frag_shader_hash,
vert_shader_hash);
manager->bg_program = lv_opengl_shader_program_get_id(program);
setup_background_environment(manager->bg_program, &manager->bg_vao, &manager->bg_index_buf, &manager->bg_vertex_buf);
}
static void setup_background_environment(GLuint program, GLuint * vao, GLuint * indexBuffer, GLuint * vertexBuffer)
{
int32_t indices[] { 1, 2, 0, 2, 3, 0, 6, 2, 1, 1, 5, 6, 6, 5, 4, 4, 7, 6,
6, 3, 2, 7, 3, 6, 3, 7, 0, 7, 4, 0, 5, 1, 0, 4, 5, 0
};
float verts[] { -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f,
-1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f
};
GL_CALL(glUseProgram(program));
GL_CALL(glGenVertexArrays(1, vao));
GL_CALL(glBindVertexArray(*vao));
GL_CALL(glGenBuffers(1, indexBuffer));
GL_CALL(glGenBuffers(1, vertexBuffer));
GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, *vertexBuffer));
GL_CALL(glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW));
GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, *indexBuffer));
GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW));
GLint positionAttributeLocation = glGetAttribLocation(program, "a_position");
// Specify the layout of the vertex data
glVertexAttribPointer(positionAttributeLocation, 3, GL_FLOAT, GL_FALSE, 0, (void *)0);
glEnableVertexAttribArray(positionAttributeLocation);
GL_CALL(glBindVertexArray(0));
GL_CALL(glUseProgram(0));
}
static void display_refr_end_event_cb(lv_event_t * e)
{
lv_gltf_t * viewer = (lv_gltf_t *) lv_event_get_user_data(e);
uint32_t model_count = lv_array_size(&viewer->models);
for(uint32_t i = 0; i < model_count; ++i) {
lv_gltf_model_t * model = *(lv_gltf_model_t **)lv_array_at(&viewer->models, i);
lv_gltf_model_send_new_values(model);
}
}
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,192 @@
/**
* @file lv_gltf_view_internal.h
*
*/
#ifndef LV_GLTF_VIEW_INTERNAL_H
#define LV_GLTF_VIEW_INTERNAL_H
/*********************
* INCLUDES
*********************/
#include "../../../lv_conf_internal.h"
#if LV_USE_GLTF
#include "lv_gltf.h"
#include "../../../misc/lv_types.h"
#include "../../../drivers/opengles/opengl_shader/lv_opengl_shader_internal.h"
#include "../../../widgets/3dtexture/lv_3dtexture_private.h"
#include "../gltf_data/lv_gltf_data_internal.h"
/*********************
* DEFINES
*********************/
/* ::Gamma Presets::
* Standard Gamma value is 2.2
* Values range from 0.5 to 3.5, roughly speaking, with
* reasonable results between the 1.5 and 2.8 levels.
* The value must be enclosed with quotes, as a string literal.
*/
#define LV_GLTF_GAMMA_BRIGHTEST "3.5"
#define LV_GLTF_GAMMA_BRIGHTER "3.0"
#define LV_GLTF_GAMMA_BRIGHT "2.6"
#define LV_GLTF_GAMMA_STANDARD "2.2"
#define LV_GLTF_GAMMA_DARK "1.8"
#define LV_GLTF_GAMMA_DARKER "1.3"
#define LV_GLTF_GAMMA_DARKEST "0.8"
#define LV_GLTF_DISTANCE_SCALE_FACTOR 2.5f
#define LV_GLTF_TRANSMISSION_PASS_SIZE 256
/* Apply defaults below if not set explicitly */
/* Tone-mapping is not applied if linear output is enabled.
* Linear output is the default.
*/
#ifndef LV_GLTF_LINEAR_OUTPUT
#define LV_GLTF_LINEAR_OUTPUT 1
#endif
/* If tone-mapping is applied, this adjusts the brightness
* and color range of the output. Use stringified values.
*/
#ifndef LV_GLTF_TONEMAP_GAMMA
#define LV_GLTF_TONEMAP_GAMMA LV_GLTF_GAMMA_STANDARD
#endif
/**********************
* TYPEDEFS
**********************/
#ifdef __cplusplus
extern "C" {
#endif/* __cplusplus*/
typedef struct {
uint32_t texture;
uint32_t renderbuffer;
unsigned framebuffer;
} lv_gltf_renwin_state_t;
typedef struct {
lv_gltf_renwin_state_t render_state;
lv_gltf_renwin_state_t opaque_render_state;
uint64_t opaque_frame_buffer_width;
uint64_t opaque_frame_buffer_height;
uint32_t material_variant;
bool render_state_ready;
bool render_opaque_buffer;
} lv_gltf_view_state_t;
typedef struct {
float pitch;
float yaw;
float distance;
float fov; // The vertical FOV, in degrees. If this is zero, the view will be orthographic (non-perspective)
int32_t render_width; // If anti-aliasing is not applied this frame, these are the same as width/height, if antialiasing
int32_t render_height; // is enabled, these are width/height * antialias upscale power (currently 2.0)
float focal_x;
float focal_y;
float focal_z;
bool frame_was_antialiased;
int32_t animation_speed_ratio;
lv_gltf_aa_mode_t aa_mode;
lv_gltf_bg_mode_t bg_mode;
float blur_bg; /** How much to blur the environment background, between 0.0 and 1.0 */
float env_pow; /** Environmental brightness, 1.8 by default */
float exposure; /** Image exposure level, 1.0 default */
} lv_gltf_view_desc_t;
typedef struct {
/* Blend state */
GLboolean blend_enabled;
GLint blend_src;
GLint blend_dst;
GLint blend_equation;
/* Depth state */
GLboolean depth_test_enabled;
GLboolean depth_mask;
GLint depth_func;
/* Face culling state */
GLboolean cull_face_enabled;
GLint cull_face_mode;
GLint front_face;
/* Stencil state */
GLboolean stencil_test_enabled;
GLuint stencil_mask;
GLint stencil_func;
GLint stencil_ref;
GLuint stencil_value_mask;
/* Buffer bindings */
GLuint current_vao;
GLuint current_vbo;
GLuint current_ibo;
GLuint current_program;
/* Texture state */
GLint active_texture;
GLuint bound_texture_2d;
/* Viewport and scissor */
GLint viewport[4];
GLboolean scissor_test_enabled;
GLint scissor_box[4];
/* Clear values */
GLfloat clear_depth;
GLfloat clear_color[4];
} lv_opengl_state_t;
#ifdef __cplusplus
}
#include <fastgltf/math.hpp>
#include <fastgltf/types.hpp>
#include <map>
struct _lv_gltf_t {
lv_3dtexture_t texture;
lv_array_t models;
lv_gltf_view_state_t state;
lv_gltf_view_desc_t desc;
lv_gltf_view_desc_t last_desc;
lv_opengl_shader_manager_t shader_manager;
lv_gltf_environment_t * environment;
fastgltf::math::fmat4x4 view_matrix;
fastgltf::math::fmat4x4 projection_matrix;
fastgltf::math::fmat4x4 view_projection_matrix;
fastgltf::math::fvec3 camera_pos;
std::map<int32_t, std::map<fastgltf::Node *, fastgltf::math::fmat4x4>> ibm_by_skin_then_node;
bool owns_environment;
};
/**********************
* GLOBAL PROTOTYPES
**********************/
GLuint lv_gltf_view_render(lv_gltf_t * viewer);
lv_result_t lv_gltf_view_shader_injest_discover_defines(lv_array_t * result, lv_gltf_model_t * data,
fastgltf::Node * node,
fastgltf::Primitive * prim);
/**********************
* MACROS
**********************/
#endif/* __cplusplus*/
#endif /*LV_USE_GLTF*/
#endif /*LV_GLTF_VIEW_INTERNAL_H*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,379 @@
/**
* @file lv_gltf_view_shader.cpp
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_view_internal.h"
#if LV_USE_GLTF
#include "fastgltf/types.hpp"
#include "../gltf_data/lv_gltf_data_internal.hpp"
#include "../gltf_data/lv_gltf_data_internal.h"
#include "../../../drivers/opengles/opengl_shader/lv_opengl_shader_internal.h"
#include "../../../misc/lv_array.h"
#include "../../../misc/lv_assert.h"
#include "../../../misc/lv_types.h"
#include "../../../stdlib/lv_sprintf.h"
#include "../../../stdlib/lv_string.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static lv_result_t add_define(lv_array_t * array, const char * defsymbol, const char * value, bool value_allocated);
static lv_result_t add_define_if_primitive_attribute_exists(lv_array_t * array, const fastgltf::Asset & asset,
const fastgltf::Primitive * primitive, const char * attribute,
const char * define);
static lv_result_t add_texture_defines_impl(lv_array_t * array, const fastgltf::TextureInfo & material_prop,
const char * define,
const char * uv_define);
static lv_result_t add_texture_defines(lv_array_t * array,
const fastgltf::Optional<fastgltf::TextureInfo> & material_prop,
const char * define, const char * uv_define);
static lv_result_t add_texture_defines(lv_array_t * array,
const fastgltf::Optional<fastgltf::NormalTextureInfo> & material_prop,
const char * define, const char * uv_define);
static lv_result_t add_texture_defines(lv_array_t * array,
const fastgltf::Optional<fastgltf::OcclusionTextureInfo> & material_prop,
const char * define, const char * uv_define);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_result_t lv_gltf_view_shader_injest_discover_defines(lv_array_t * result, lv_gltf_model_t * data,
fastgltf::Node * node,
fastgltf::Primitive * prim)
{
const auto & asset = data->asset;
if(add_define(result, "_OPAQUE", "0", false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define(result, "_MASK", "1", false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define(result, "_BLEND", "2", false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
LV_ASSERT_MSG(prim->findAttribute("POSITION") != prim->attributes.end(),
"A mesh primitive is required to hold the POSITION attribute");
LV_ASSERT_MSG(prim->indicesAccessor.has_value(),
"We specify fastgltf::Options::GenerateMeshIndices, so we should always have indices");
if(!prim->materialIndex.has_value()) {
if(add_define(result, "ALPHAMODE", "_OPAQUE", false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
else {
const auto & material = asset.materials[prim->materialIndex.value()];
if(add_define(result, "TONEMAP_KHR_PBR_NEUTRAL", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(material.unlit) {
if(add_define(result, "MATERIAL_UNLIT", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
else {
if(material.pbrData.baseColorFactor.x() == 0.0f
&& material.pbrData.baseColorFactor.y() == 0.0f
&& material.pbrData.baseColorFactor.z() == 0.0f
&& material.pbrData.metallicFactor == 1.0f
&& material.pbrData.roughnessFactor == 1.0f
&& material.emissiveStrength > 0.0f) {
/* Special case where settings preclude IBL's ability to have visible effect, so disable it entirely */
LV_LOG_TRACE("Special case identified, disabling IBL and enabling UNLIT\n");
if(add_define(result, "MATERIAL_UNLIT", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
else {
if(add_define(result, "MATERIAL_METALLICROUGHNESS", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define(result, "USE_IBL", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
const size_t light_count = data->node_by_light_index.size();
if(light_count > 10) {
LV_LOG_ERROR("Too many scene lights, max is 10");
}
else if(light_count > 0) {
if(add_define(result, "USE_PUNCTUAL", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
char * count = (char *) lv_zalloc(5);
lv_snprintf(count, 5, "%zu", light_count);
if(add_define(result, "LIGHT_COUNT", count, true) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
else {
if(add_define(result, "LIGHT_COUNT", "0", false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
}
#if LV_GLTF_LINEAR_OUTPUT
if(add_define(result, "LINEAR_OUTPUT", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
#endif
// only set cutoff value for mask material
if(material.alphaMode == fastgltf::AlphaMode::Mask) {
if(add_define(result, "ALPHAMODE", "_MASK", false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
else if(material.alphaMode == fastgltf::AlphaMode::Opaque) {
if(add_define(result, "ALPHAMODE", "_OPAQUE", false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
else {
if(add_define(result, "ALPHAMODE", "_BLEND", false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
if(add_texture_defines(result, material.pbrData.baseColorTexture, "HAS_BASE_COLOR_MAP",
"HAS_BASECOLOR_UV_TRANSFORM") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_texture_defines(result, material.pbrData.metallicRoughnessTexture, "HAS_METALLIC_ROUGHNESS_MAP",
"HAS_METALLICROUGHNESS_UV_TRANSFORM") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_texture_defines(result, material.occlusionTexture, "HAS_OCCLUSION_MAP", "HAS_OCCLUSION_UV_TRANSFORM") ==
LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_texture_defines(result, material.normalTexture, "HAS_NORMAL_MAP", "HAS_NORMAL_UV_TRANSFORM") ==
LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_texture_defines(result, material.emissiveTexture, "HAS_EMISSIVE_MAP", "HAS_EMISSIVE_UV_TRANSFORM") ==
LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define(result, "MATERIAL_EMISSIVE_STRENGTH", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(material.sheen)
if(add_define(result, "MATERIAL_SHEEN", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(material.specular)
if(add_define(result, "MATERIAL_SPECULAR", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(material.specularGlossiness) {
if(add_define(result, "MATERIAL_SPECULARGLOSSINESS", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_texture_defines(result, material.specularGlossiness->diffuseTexture, "HAS_DIFFUSE_MAP",
"HAS_DIFFUSE_UV_TRANSFORM") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_texture_defines(result, material.specularGlossiness->specularGlossinessTexture,
"HAS_SPECULARGLOSSINESS_MAP", "HAS_SPECULARGLOSSINESS_UV_TRANSFORM") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
if(material.transmission) {
if(add_define(result, "MATERIAL_TRANSMISSION", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
#if 0 /* Material dispersion is being revisited.*/
if(add_define(result, "MATERIAL_DISPERSION", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
#endif
if(add_define(result, "MATERIAL_VOLUME", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(material.transmission->transmissionTexture.has_value())
if(add_define(result, "HAS_TRANSMISSION_MAP", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(material.volume) {
add_texture_defines(result, material.volume->thicknessTexture, "HAS_THICKNESS_MAP",
"HAS_THICKNESS_UV_TRANSFORM");
}
}
if(material.clearcoat && material.clearcoat->clearcoatFactor > 0.0f) {
if(add_define(result, "MATERIAL_CLEARCOAT", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_texture_defines(result, material.clearcoat->clearcoatTexture, "HAS_CLEARCOAT_MAP",
"HAS_CLEARCOAT_UV_TRANSFORM") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_texture_defines(result, material.clearcoat->clearcoatRoughnessTexture,
"HAS_CLEARCOAT_ROUGHNESS_MAP",
"HAS_CLEARCOATROUGHNESS_UV_TRANSFORM") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_texture_defines(result, material.clearcoat->clearcoatNormalTexture, "HAS_CLEARCOAT_NORMAL_MAP",
"HAS_CLEARCOATNORMAL_UV_TRANSFORM") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
if(material.diffuseTransmission && material.diffuseTransmission->diffuseTransmissionFactor > 0.0f) {
if(add_define(result, "MATERIAL_DIFFUSE_TRANSMISSION", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(material.diffuseTransmission->diffuseTransmissionTexture.has_value()) {
if(add_define(result, "HAS_DIFFUSE_TRANSMISSION_MAP", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
if(material.diffuseTransmission->diffuseTransmissionColorTexture.has_value()) {
if(add_define(result, "HAS_DIFFUSE_TRANSMISSION_COLOR_MAP", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
}
}
if(add_define_if_primitive_attribute_exists(result, asset, prim, "NORMAL", "HAS_NORMAL_VEC3") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define_if_primitive_attribute_exists(result, asset, prim, "TANGENT", "HAS_TANGENT_VEC4") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define_if_primitive_attribute_exists(result, asset, prim, "TEXCOORD_0", "HAS_TEXCOORD_0_VEC2") ==
LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define_if_primitive_attribute_exists(result, asset, prim, "TEXCOORD_1", "HAS_TEXCOORD_1_VEC2") ==
LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define_if_primitive_attribute_exists(result, asset, prim, "JOINTS_0",
"HAS_JOINTS_0_VEC4") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define_if_primitive_attribute_exists(result, asset, prim, "JOINTS_1",
"HAS_JOINTS_1_VEC4") == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define_if_primitive_attribute_exists(result, asset, prim, "WEIGHTS_0", "HAS_WEIGHTS_0_VEC4") ==
LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(add_define_if_primitive_attribute_exists(result, asset, prim, "WEIGHTS_1", "HAS_WEIGHTS_1_VEC4") ==
LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
const auto * joints0it = prim->findAttribute("JOINTS_0");
const auto * weights0it = prim->findAttribute("WEIGHTS_0");
if((node->skinIndex.has_value()) && (joints0it != prim->attributes.end()) && (weights0it != prim->attributes.end())) {
if(add_define(result, "USE_SKINNING", NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
}
return LV_RESULT_OK;
}
/**********************
* STATIC FUNCTIONS
**********************/
static lv_result_t add_define(lv_array_t * array, const char * name, const char * value, bool value_allocated)
{
const size_t n = lv_array_size(array);
for(size_t i = 0; i < n; ++i) {
lv_opengl_shader_define_t * define = (lv_opengl_shader_define_t *)lv_array_at(array, i);
if(lv_streq(define->name, name)) {
return LV_RESULT_OK;
}
}
lv_opengl_shader_define_t entry = { name, value, value_allocated };
return lv_array_push_back(array, &entry);
}
static lv_result_t add_define_if_primitive_attribute_exists(lv_array_t * array, const fastgltf::Asset & asset,
const fastgltf::Primitive * primitive, const char * attribute,
const char * define)
{
const auto & it = primitive->findAttribute(attribute);
if(it == primitive->attributes.end() || !asset.accessors[it->accessorIndex].bufferViewIndex.has_value()) {
return LV_RESULT_OK;
}
return add_define(array, define, NULL, false);
}
static lv_result_t add_texture_defines_impl(lv_array_t * array, const fastgltf::TextureInfo & material_prop,
const char * define,
const char * uv_define)
{
if(add_define(array, define, NULL, false) == LV_RESULT_INVALID) {
return LV_RESULT_INVALID;
}
if(!material_prop.transform) {
return LV_RESULT_OK;
}
return add_define(array, uv_define, NULL, false);
}
static lv_result_t add_texture_defines(lv_array_t * array,
const fastgltf::Optional<fastgltf::TextureInfo> & material_prop,
const char * define, const char * uv_define)
{
if(!material_prop.has_value()) {
return LV_RESULT_OK;
}
return add_texture_defines_impl(array, material_prop.value(), define, uv_define);
}
static lv_result_t add_texture_defines(lv_array_t * array,
const fastgltf::Optional<fastgltf::NormalTextureInfo> & material_prop,
const char * define, const char * uv_define)
{
if(!material_prop.has_value()) {
return LV_RESULT_OK;
}
return add_texture_defines_impl(array, material_prop.value(), define, uv_define);
}
static lv_result_t add_texture_defines(lv_array_t * array,
const fastgltf::Optional<fastgltf::OcclusionTextureInfo> & material_prop,
const char * define, const char * uv_define)
{
if(!material_prop.has_value()) {
return LV_RESULT_OK;
}
return add_texture_defines_impl(array, material_prop.value(), define, uv_define);
}
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,50 @@
/**
* @file lv_3dmath.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_3dmath.h"
#if LV_USE_GLTF
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_3dplane_t lv_get_ground_plane(float elevation)
{
return (lv_3dplane_t) {
.origin = {0.f, elevation, 0.f},
.direction = {0.f, 1.f, 0.f}
};
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,72 @@
/**
* @file lv_3dmath.h
*
*/
#ifndef LV_3DMATH_H
#define LV_3DMATH_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../../lv_conf_internal.h"
#if LV_USE_GLTF
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
typedef struct {
float x;
float y;
float z;
} lv_3dpoint_t;
typedef struct {
float x;
float y;
float z;
float w;
} lv_quaternion_t;
typedef struct {
lv_3dpoint_t origin;
lv_3dpoint_t direction;
} lv_3dplane_t;
typedef lv_3dplane_t lv_3dray_t;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Get a plane that faces upward, centered at a given height
* @param elevation elevation of the ground plane, in world units. this is usually zero
* @return ground plane
*/
lv_3dplane_t lv_get_ground_plane(float elevation);
/**********************
* MACROS
**********************/
#endif /*LV_USE_GLTF*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_3DMATH_H*/

View File

@@ -0,0 +1,78 @@
/**
* @file lv_gltf_math.cpp
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gltf_math.hpp"
#if LV_USE_GLTF
#include <fastgltf/math.hpp>
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
/** Creates a right-handed view matrix */
fastgltf::math::fmat4x4 lv_gltf_math_look_at_rh(const fastgltf::math::fvec3 & eye, const fastgltf::math::fvec3 & center,
const fastgltf::math::fvec3 & up) noexcept
{
auto dir = normalize(center - eye);
auto lft = normalize(cross(dir, up));
auto rup = cross(lft, dir);
fastgltf::math::fmat4x4 ret(1.f);
ret.col(0) = { lft.x(), rup.x(), -dir.x(), 0.f };
ret.col(1) = { lft.y(), rup.y(), -dir.y(), 0.f };
ret.col(2) = { lft.z(), rup.z(), -dir.z(), 0.f };
ret.col(3) = { -dot(lft, eye), -dot(rup, eye), dot(dir, eye), 1.f };
return ret;
}
/**
* Creates a right-handed perspective matrix, with the near and far clips at -1 and +1, respectively.
* @param fov The FOV in radians
*/
[[nodiscard]] fastgltf::math::fmat4x4 lv_gltf_math_perspective_rh(float fov, float ratio, float z_near,
float z_far) noexcept
{
fastgltf::math::fmat4x4 ret(0.f);
auto tanHalfFov = std::tan(fov / 2.f);
ret.col(0).x() = 1.f / (ratio * tanHalfFov);
ret.col(1).y() = 1.f / tanHalfFov;
ret.col(2).z() = -(z_far + z_near) / (z_far - z_near);
ret.col(2).w() = -1.f;
ret.col(3).z() = -(2.f * z_far * z_near) / (z_far - z_near);
return ret;
}
/**********************
* STATIC FUNCTIONS
**********************/
#endif /*LV_USE_GLTF*/

View File

@@ -0,0 +1,84 @@
/**
* @file lv_gltf_math.hpp
* @brief GLTF math utilities and helper functions
*/
#ifndef LV_GLTF_MATH_HPP
#define LV_GLTF_MATH_HPP
/*********************
* INCLUDES
*********************/
#include "../../../lv_conf_internal.h"
#if LV_USE_GLTF
#include <fastgltf/math.hpp>
/*********************
* DEFINES
*********************/
#ifndef M_PI
#define M_PI 3.14159265358979323846264338327950288
#endif
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
fastgltf::math::fmat4x4 lv_gltf_math_look_at_rh(const fastgltf::math::fvec3 & eye, const fastgltf::math::fvec3 & center, const fastgltf::math::fvec3 & up) noexcept;
fastgltf::math::fmat4x4 lv_gltf_math_perspective_rh(float fov, float ratio, float z_near, float z_far) noexcept;
template <typename T>
[[nodiscard]] fastgltf::math::quat<T> lv_gltf_math_euler_to_quaternion(T P, T Y, T R)
{
// Convert degrees to radians if necessary
// roll = roll * (M_PI / 180.0);
// pitch = pitch * (M_PI / 180.0);
// yaw = yaw * (M_PI / 180.0);
T H = T(0.5);
Y *= H;
P *= H;
R *= H;
T cy = cos(Y), sy = sin(Y), cp = cos(P), sp = sin(P), cr = cos(R), sr = sin(R);
T cr_cp = cr * cp, sp_sy = sp * sy, sr_cp = sr * cp, sp_cy = sp * cy;
return fastgltf::math::quat<T>(
sr_cp * cy - cr * sp_sy, // X
sr_cp * sy + cr * sp_cy, // Y
cr_cp * sy - sr * sp_cy, // Z
cr_cp * cy + sr * sp_sy // W
);
}
template <typename T>
[[nodiscard]] fastgltf::math::vec<T, 3> lv_gltf_math_quaternion_to_euler(fastgltf::math::quat<T> q)
{
T Q11 = q[1] * q[1];
// Roll (Z)
T sinr_cosp = T(2.0) * (q[3] * q[0] + q[1] * q[2]);
T cosr_cosp = T(1.0) - T(2.0) * (q[0] * q[0] + Q11);
// Pitch (X)
T sinp = T(2.0) * (q[3] * q[1] - q[2] * q[0]);
// Yaw (Y)
T siny_cosp = T(2.0) * (q[3] * q[2] + q[0] * q[1]);
T cosy_cosp = T(1.0) - T(2.0) * (Q11 + q[2] * q[2]);
return fastgltf::math::vec<T, 3>(
(std::abs(sinp) >= T(1)) ? std::copysign(T(M_PI) / T(2), sinp) : std::asin(sinp),
std::atan2(siny_cosp, cosy_cosp),
std::atan2(sinr_cosp, cosr_cosp)
);
}
/**********************
* MACROS
**********************/
#endif /*LV_USE_GLTF*/
#endif /*LV_GLTF_MATH_HPP*/

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,685 @@
/**
* @file lv_gstreamer.c
*
*/
/*********************
* INCLUDES
*********************/
#include "lv_gstreamer_internal.h"
#if LV_USE_GSTREAMER
#include <glib.h>
#include <gst/gstelementfactory.h>
#include "../../core/lv_obj_class_private.h"
#include "../../misc/lv_event_private.h"
/*********************
* DEFINES
*********************/
#define MY_CLASS (&lv_gstreamer_class)
/**********************
* TYPEDEFS
**********************/
typedef struct {
const char * factory;
const char * name;
GstElement ** store;
} lv_gstreamer_pipeline_element_t;
/**********************
* STATIC PROTOTYPES
**********************/
static void lv_gstreamer_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void lv_gstreamer_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj);
static void on_decode_pad_added(GstElement * element, GstPad * pad, gpointer user_data);
static GstFlowReturn on_new_sample(GstElement * sink, gpointer user_data);
static void gstreamer_timer_cb(lv_timer_t * timer);
static lv_result_t gstreamer_poll_bus(lv_gstreamer_t * streamer);
static void gstreamer_update_frame(lv_gstreamer_t * streamer);
static lv_result_t gstreamer_make_and_add_to_pipeline(lv_gstreamer_t * streamer,
const lv_gstreamer_pipeline_element_t * elements, size_t element_count);
static lv_result_t gstreamer_send_state_changed(lv_gstreamer_t * streamer, lv_gstreamer_stream_state_t state);
/**********************
* STATIC VARIABLES
**********************/
const lv_obj_class_t lv_gstreamer_class = {
.constructor_cb = lv_gstreamer_constructor,
.destructor_cb = lv_gstreamer_destructor,
.width_def = LV_SIZE_CONTENT,
.height_def = LV_SIZE_CONTENT,
.instance_size = sizeof(lv_gstreamer_t),
.base_class = &lv_image_class,
.name = "lv_gstreamer",
};
/**********************
* MACROS
**********************/
#if LV_COLOR_DEPTH == 16
#define GST_FORMAT "RGB16"
#define IMAGE_FORMAT LV_COLOR_FORMAT_RGB565
#elif LV_COLOR_DEPTH == 24
#define GST_FORMAT "BGR"
#define IMAGE_FORMAT LV_COLOR_FORMAT_RGB888
#elif LV_COLOR_DEPTH == 32
#define GST_FORMAT "BGRA"
#define IMAGE_FORMAT LV_COLOR_FORMAT_ARGB8888
#else
#error Unsupported LV_COLOR_DEPTH
#endif
/**********************
* GLOBAL FUNCTIONS
**********************/
lv_obj_t * lv_gstreamer_create(lv_obj_t * parent)
{
if(!gst_is_initialized()) {
gst_init(0, NULL);
}
LV_TRACE_OBJ_CREATE("begin");
lv_obj_t * obj = lv_obj_class_create_obj(MY_CLASS, parent);
lv_obj_class_init_obj(obj);
LV_TRACE_OBJ_CREATE("end");
return obj;
}
lv_result_t lv_gstreamer_set_src(lv_obj_t * obj, const char * factory_name, const char * property, const char * source)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
LV_ASSERT_NULL(factory_name);
if(!obj || !factory_name) {
LV_LOG_WARN("Refusing to set source with invalid params. Obj: %p Factory Name: %s", (void *)obj, factory_name);
return LV_RESULT_INVALID;
}
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(streamer->pipeline) {
LV_LOG_WARN("LVGL doesn't allow modifying the GStreamer source. Create a new widget with a new src instead");
return LV_RESULT_INVALID;
}
GstElement * pipeline = gst_pipeline_new("lv_gstreamer_pipeline");
if(!pipeline) {
LV_LOG_ERROR("Failed to create gstreamer pipeline");
return LV_RESULT_INVALID;
}
GstElement * head = gst_element_factory_make(factory_name, "lv_gstreamer_source");
if(!head) {
gst_object_unref(pipeline);
LV_LOG_ERROR("Failed to create source from factory '%s'", factory_name);
return LV_RESULT_INVALID;
}
if(!gst_bin_add(GST_BIN(pipeline), head)) {
gst_object_unref(head);
gst_object_unref(pipeline);
LV_LOG_ERROR("Failed to add source element to pipeline");
return LV_RESULT_INVALID;
}
if(property != NULL && source != NULL) {
g_object_set(G_OBJECT(head), property, source, NULL);
}
/* The uri decode source element will automatically handle parsing and decoding for us
* for other source types, we need to add a parser and a decoder ourselves element*/
if(!lv_streq(LV_GSTREAMER_FACTORY_URI_DECODE, factory_name)) {
GstElement * decodebin = gst_element_factory_make("decodebin", "lv_gstreamer_decodebin");
if(!decodebin) {
gst_object_unref(pipeline);
LV_LOG_ERROR("Failed to create decodebin element");
return LV_RESULT_INVALID;
}
if(!gst_bin_add(GST_BIN(pipeline), decodebin)) {
gst_object_unref(decodebin);
gst_object_unref(pipeline);
LV_LOG_ERROR("Failed to add decodebin element to pipeline");
return LV_RESULT_INVALID;
}
if(!gst_element_link(head, decodebin)) {
gst_object_unref(pipeline);
LV_LOG_ERROR("Failed to link source with parsebin elements");
return LV_RESULT_INVALID;
}
head = decodebin;
}
/* At this point we don't yet know the input format
* Once the source starts receiving the data, it will create the necessary pads,
* i.e one pad for audio and one for video
* We add a callback so that we automatically connect to the data once it's figured out*/
g_signal_connect(head, "pad-added", G_CALLBACK(on_decode_pad_added), streamer);
streamer->pipeline = pipeline;
return LV_RESULT_OK;
}
void lv_gstreamer_play(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
if(!obj) {
return;
}
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(!streamer->pipeline) {
LV_LOG_WARN("Cannot play: GStreamer pipeline not initialized");
return;
}
GstStateChangeReturn ret = gst_element_set_state(streamer->pipeline, GST_STATE_PLAYING);
if(ret == GST_STATE_CHANGE_FAILURE) {
LV_LOG_ERROR("Unable to play pipeline");
return;
}
gstreamer_send_state_changed(streamer, LV_GSTREAMER_STREAM_STATE_PLAY);
}
void lv_gstreamer_pause(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
if(!obj) {
return;
}
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(!streamer->pipeline) {
LV_LOG_WARN("Cannot pause: GStreamer pipeline not initialized");
return;
}
GstStateChangeReturn ret = gst_element_set_state(streamer->pipeline, GST_STATE_PAUSED);
if(ret == GST_STATE_CHANGE_FAILURE) {
LV_LOG_ERROR("Unable to pause pipeline");
return;
}
gstreamer_send_state_changed(streamer, LV_GSTREAMER_STREAM_STATE_PAUSE);
}
void lv_gstreamer_stop(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
if(!obj) {
return;
}
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(!streamer->pipeline) {
LV_LOG_WARN("Cannot stop: GStreamer pipeline not initialized");
return;
}
GstStateChangeReturn ret = gst_element_set_state(streamer->pipeline, GST_STATE_READY);
if(ret == GST_STATE_CHANGE_FAILURE) {
LV_LOG_ERROR("Unable to stop pipeline");
return;
}
gstreamer_send_state_changed(streamer, LV_GSTREAMER_STREAM_STATE_STOP);
}
void lv_gstreamer_set_position(lv_obj_t * obj, uint32_t position)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
if(!obj) {
return;
}
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(!streamer->pipeline) {
LV_LOG_WARN("Cannot set position: GStreamer pipeline not initialized");
return;
}
gint64 seek_position = (gint64)position * GST_MSECOND;
if(!gst_element_seek_simple(streamer->pipeline, GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
seek_position)) {
LV_LOG_WARN("Seek operation failed");
}
}
uint32_t lv_gstreamer_get_duration(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(!streamer->pipeline) {
return 0;
}
gint64 duration;
if(gst_element_query_duration(streamer->pipeline, GST_FORMAT_TIME, &duration)) {
return (uint32_t)(duration / GST_MSECOND);
}
return 0;
}
uint32_t lv_gstreamer_get_position(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(!streamer->pipeline) {
return 0;
}
gint64 position;
if(gst_element_query_position(streamer->pipeline, GST_FORMAT_TIME, &position)) {
return (uint32_t)(position / GST_MSECOND);
}
return 0;
}
lv_gstreamer_state_t lv_gstreamer_get_state(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(!streamer->pipeline) {
return LV_GSTREAMER_STATE_NULL;
}
GstState state, pending;
GstStateChangeReturn ret = gst_element_get_state(streamer->pipeline, &state, &pending, 0);
if(ret == GST_STATE_CHANGE_FAILURE) {
return LV_GSTREAMER_STATE_NULL;
}
switch(state) {
case GST_STATE_NULL:
return LV_GSTREAMER_STATE_NULL;
case GST_STATE_READY:
return LV_GSTREAMER_STATE_READY;
case GST_STATE_PAUSED:
return LV_GSTREAMER_STATE_PAUSED;
case GST_STATE_PLAYING:
return LV_GSTREAMER_STATE_PLAYING;
default:
return LV_GSTREAMER_STATE_NULL;
}
}
void lv_gstreamer_set_volume(lv_obj_t * obj, uint8_t volume)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(!streamer->audio_volume) {
return;
}
g_object_set(streamer->audio_volume, "volume", volume / 100.f, NULL);
}
uint8_t lv_gstreamer_get_volume(lv_obj_t * obj)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(!streamer->audio_volume) {
return 0;
}
gdouble volume;
g_object_get(streamer->audio_volume, "volume", &volume, NULL);
return (uint8_t)(volume * 100.f);
}
void lv_gstreamer_set_rate(lv_obj_t * obj, uint32_t rate)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(!streamer->pipeline) {
return;
}
gdouble gst_rate = (gdouble)rate / 256.0;
gint64 current_pos;
if(!gst_element_query_position(streamer->pipeline, GST_FORMAT_TIME, &current_pos)) {
LV_LOG_WARN("Failed to query current position which is required to set the stream rate");
return;
}
/* Perform the seek with new rate from the current position */
if(!gst_element_seek(streamer->pipeline, gst_rate,
GST_FORMAT_TIME,
GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE,
GST_SEEK_TYPE_SET, current_pos,
GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE)) {
LV_LOG_WARN("Failed to change stream rate");
}
}
lv_gstreamer_stream_state_t lv_gstreamer_get_stream_state(lv_event_t * e)
{
if(!e || e->code != LV_EVENT_STATE_CHANGED) {
LV_LOG_WARN("Invalid event");
return -1;
}
return *(lv_gstreamer_stream_state_t *)lv_event_get_param(e);
}
/**********************
* STATIC FUNCTIONS
**********************/
static void lv_gstreamer_constructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
LV_TRACE_OBJ_CREATE("begin");
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
lv_memzero(&streamer->frame, sizeof(streamer->frame));
streamer->gstreamer_timer = lv_timer_create(gstreamer_timer_cb, LV_DEF_REFR_PERIOD / 5, streamer);
LV_ASSERT_NULL(streamer->gstreamer_timer);
streamer->frame_queue = g_async_queue_new();
LV_ASSERT_NULL(streamer->frame_queue);
streamer->last_sample = NULL;
LV_TRACE_OBJ_CREATE("finished");
}
static lv_result_t gstreamer_poll_bus(lv_gstreamer_t * streamer)
{
GstBus * bus = gst_element_get_bus(streamer->pipeline);
GstMessage * msg;
while((msg = gst_bus_pop(bus)) != NULL) {
const GstMessageType message_type = GST_MESSAGE_TYPE(msg);
switch(message_type) {
case GST_MESSAGE_ERROR: {
GError * err;
gchar * debug;
gst_message_parse_error(msg, &err, &debug);
LV_LOG_ERROR("GStreamer error: %s", err->message);
g_error_free(err);
g_free(debug);
break;
}
case GST_MESSAGE_EOS:
if(gstreamer_send_state_changed(streamer, LV_GSTREAMER_STREAM_STATE_END) == LV_RESULT_INVALID) {
/* Object deleted inside event handler */
gst_object_unref(bus);
gst_message_unref(msg);
return LV_RESULT_INVALID;
}
break;
case GST_MESSAGE_STATE_CHANGED: {
GstState old_state, new_state;
gst_message_parse_state_changed(msg, &old_state, &new_state, NULL);
LV_LOG_INFO("State changed: %s -> %s", gst_element_state_get_name(old_state),
gst_element_state_get_name(new_state));
break;
}
default:
LV_LOG_TRACE("Received message %d", message_type);
break;
}
gst_message_unref(msg);
}
gst_object_unref(bus);
return LV_RESULT_OK;
}
static void gstreamer_update_frame(lv_gstreamer_t * streamer)
{
GstSample * sample = g_async_queue_try_pop(streamer->frame_queue);
if(!sample) {
return;
}
const bool first_frame = !streamer->is_video_info_valid;
if(first_frame) {
GstCaps * caps = gst_sample_get_caps(sample);
if(!caps || !gst_video_info_from_caps(&streamer->video_info, caps)) {
LV_LOG_ERROR("Failed to get video info from caps");
gst_sample_unref(sample);
return;
}
streamer->is_video_info_valid = true;
}
GstBuffer * buffer = gst_sample_get_buffer(sample);
GstMapInfo map;
if(buffer && gst_buffer_map(buffer, &map, GST_MAP_READ)) {
if(streamer->last_buffer) {
gst_buffer_unmap(streamer->last_buffer, &streamer->last_map_info);
}
if(streamer->last_sample) {
gst_sample_unref(streamer->last_sample);
}
streamer->last_buffer = buffer;
streamer->last_map_info = map;
streamer->last_sample = sample;
streamer->frame = (lv_image_dsc_t) {
.data = map.data,
.data_size = map.size,
.header = {
.magic = LV_IMAGE_HEADER_MAGIC,
.cf = IMAGE_FORMAT,
.flags = LV_IMAGE_FLAGS_MODIFIABLE,
.h = GST_VIDEO_INFO_HEIGHT(&streamer->video_info),
.w = GST_VIDEO_INFO_WIDTH(&streamer->video_info),
.stride = GST_VIDEO_INFO_PLANE_STRIDE(&streamer->video_info, 0),
}
};
lv_image_set_src((lv_obj_t *)streamer, &streamer->frame);
}
/* We send the event AFTER setting the image source so that users can query the
* resolution on this specific event callback */
if(first_frame) {
if(gstreamer_send_state_changed(streamer, LV_GSTREAMER_STREAM_STATE_START) == LV_RESULT_INVALID) {
/* Object deleted inside event handler */
return;
}
/*Send READY event for backwards compatibility with v9.4*/
lv_obj_send_event((lv_obj_t *)streamer, LV_EVENT_READY, streamer);
}
}
static void gstreamer_timer_cb(lv_timer_t * timer)
{
lv_gstreamer_t * streamer = lv_timer_get_user_data(timer);
if(!streamer->pipeline) {
return;
}
if(gstreamer_poll_bus(streamer) == LV_RESULT_INVALID) {
return;
}
gstreamer_update_frame(streamer);
}
static void lv_gstreamer_destructor(const lv_obj_class_t * class_p, lv_obj_t * obj)
{
LV_UNUSED(class_p);
lv_gstreamer_t * streamer = (lv_gstreamer_t *)obj;
if(streamer->pipeline) {
gst_element_set_state(streamer->pipeline, GST_STATE_NULL);
gst_object_unref(streamer->pipeline);
}
if(streamer->last_buffer) {
gst_buffer_unmap(streamer->last_buffer, &streamer->last_map_info);
}
if(streamer->last_sample) {
gst_sample_unref(streamer->last_sample);
}
if(streamer->frame_queue) {
GstSample * sample;
while((sample = g_async_queue_try_pop(streamer->frame_queue)) != NULL) {
gst_sample_unref(sample);
}
g_async_queue_unref(streamer->frame_queue);
streamer->frame_queue = NULL;
}
lv_timer_delete(streamer->gstreamer_timer);
}
static lv_result_t gstreamer_make_and_add_to_pipeline(lv_gstreamer_t * streamer,
const lv_gstreamer_pipeline_element_t * elements, size_t element_count)
{
for(size_t i = 0; i < element_count; ++i) {
GstElement * el = gst_element_factory_make(elements[i].factory, elements[i].name);
if(!el) {
/* The previous elements were added to the pipeline so we don't need to unref them explicitly
* Unrefing the pipeline is enough and is done by caller*/
LV_LOG_ERROR("Failed to create %s element", elements[i].name);
return LV_RESULT_INVALID;
}
*(elements[i].store) = el;
if(!gst_bin_add(GST_BIN(streamer->pipeline), el)) {
gst_object_unref(el);
LV_LOG_ERROR("Failed to add %s element to pipeline", elements[i].name);
return LV_RESULT_INVALID;
}
}
return LV_RESULT_OK;
}
static void on_decode_pad_added(GstElement * element, GstPad * pad, gpointer user_data)
{
LV_UNUSED(element);
lv_gstreamer_t * streamer = (lv_gstreamer_t *)user_data;
GstCaps * caps = gst_pad_get_current_caps(pad);
GstStructure * structure = gst_caps_get_structure(caps, 0);
const gchar * name = gst_structure_get_name(structure);
LV_LOG_TRACE("Pad discovered %s", name);
if(g_str_has_prefix(name, "video/")) {
if(!streamer->video_convert) {
GstElement * video_app_sink;
GstElement * video_rate;
GstElement * video_queue;
const lv_gstreamer_pipeline_element_t elements[] = {
{"videoconvert", "lv_gstreamer_video_convert", &streamer->video_convert},
{"videorate", "lv_gstreamer_video_rate", &video_rate},
{"queue", "lv_gstreamer_video_queue", &video_queue},
{"appsink", "lv_gstreamer_video_sink", &video_app_sink},
};
const size_t element_count = sizeof(elements) / sizeof(elements[0]);
if(gstreamer_make_and_add_to_pipeline(streamer, elements, element_count) != LV_RESULT_OK) {
goto exit;
}
/* Here we set the fps we want the pipeline to produce and the color format
* This is achieved by the video_convert and video_rate elements that will automatically throttle and
* convert the image to the format we desire*/
uint32_t target_fps = 1000 / LV_DEF_REFR_PERIOD;
char caps_str[128];
lv_snprintf(caps_str, sizeof(caps_str), "video/x-raw,format=%s,framerate=%" LV_PRIu32 "/1", GST_FORMAT, target_fps);
GstCaps * appsink_caps = gst_caps_from_string(caps_str);
g_object_set(G_OBJECT(video_app_sink), "emit-signals", TRUE, "sync", TRUE, "max-buffers", 1, "drop", TRUE, "caps",
appsink_caps, NULL);
gst_caps_unref(appsink_caps);
if(!gst_element_link_many(streamer->video_convert, video_rate, video_queue, video_app_sink, NULL)) {
LV_LOG_ERROR("Failed to link video convert to sink");
goto exit;
}
for(size_t i = 0; i < element_count; ++i) {
gst_element_sync_state_with_parent(*elements[i].store);
}
g_signal_connect(video_app_sink, "new-sample", G_CALLBACK(on_new_sample), streamer);
}
GstPad * video_convert_sink_pad = gst_element_get_static_pad(streamer->video_convert, "sink");
if(gst_pad_is_linked(video_convert_sink_pad)) {
LV_LOG_WARN("Received another video pad '%s' but our video pipeline is already linked - Ignoring", name);
}
else if(gst_pad_link(pad, video_convert_sink_pad) != GST_PAD_LINK_OK) {
LV_LOG_ERROR("Failed to link discovered pad '%s' to videoconvert", name);
}
gst_object_unref(video_convert_sink_pad);
}
else if(g_str_has_prefix(name, "audio/")) {
if(!streamer->audio_convert) {
GstElement * audio_resample;
GstElement * audio_sink;
const lv_gstreamer_pipeline_element_t elements[] = {
{"audioconvert", "lv_gstreamer_audio_convert", &streamer->audio_convert},
{"volume", "lv_gstreamer_audio_volume", &streamer->audio_volume},
{"audioresample", "lv_gstreamer_audio_resample", &audio_resample},
{"autoaudiosink", "lv_gstreamer_audio_sink", &audio_sink},
};
const size_t element_count = sizeof(elements) / sizeof(elements[0]);
if(gstreamer_make_and_add_to_pipeline(streamer, elements, element_count) != LV_RESULT_OK) {
goto exit;
}
if(!gst_element_link_many(streamer->audio_convert, audio_resample, streamer->audio_volume, audio_sink, NULL)) {
LV_LOG_ERROR("Failed to link audio convert to sink");
goto exit;
}
for(size_t i = 0; i < element_count; ++i) {
gst_element_sync_state_with_parent(*elements[i].store);
}
}
GstPad * audio_convert_sink_pad = gst_element_get_static_pad(streamer->audio_convert, "sink");
if(!gst_pad_is_linked(audio_convert_sink_pad)) {
if(gst_pad_link(pad, audio_convert_sink_pad) != GST_PAD_LINK_OK) {
LV_LOG_ERROR("Failed to link discovered pad '%s' to audioconvert", name);
}
}
else {
LV_LOG_WARN("Received another audio pad '%s' but our audio pipeline is already linked - Ignoring", name);
}
gst_object_unref(audio_convert_sink_pad);
}
exit:
gst_caps_unref(caps);
}
static GstFlowReturn on_new_sample(GstElement * sink, gpointer user_data)
{
/* This function is called from a thread other than the main one so we can't call anything related to LVGL here
* Instead, we acquire the new sample (the new frame) and push it to the queue so that we can retrieve it from an LVGL timer
* Note that the pipeline spits out a new frame every LV_DEF_REFR_PERIOD as per the way it's set up so we shouldn't ever lose any
* frames with this method*/
lv_gstreamer_t * streamer = (lv_gstreamer_t *)user_data;
GstSample * sample;
g_signal_emit_by_name(sink, "pull-sample", &sample);
if(!sample) {
return GST_FLOW_OK;
}
g_async_queue_push(streamer->frame_queue, sample);
return GST_FLOW_OK;
}
static lv_result_t gstreamer_send_state_changed(lv_gstreamer_t * streamer, lv_gstreamer_stream_state_t state)
{
return lv_obj_send_event((lv_obj_t *)streamer, LV_EVENT_STATE_CHANGED, &state);
}
#endif

View File

@@ -0,0 +1,199 @@
/**
* @file lv_gstreamer.h
*
*/
#ifndef LV_GSTREAMER_H
#define LV_GSTREAMER_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
#if LV_USE_GSTREAMER
#include "../../core/lv_obj.h"
/*********************
* DEFINES
*********************/
/* Using the `URI` "factory", we can specify various URI schemes as media sources including
* - local files (file://)
* - web streams (http://, https://)
* - RTSP streams (rtsp://)
* - UDP streams (udp://)
* and many others.
* GStreamer's uridecodebin automatically selects the appropriate
* source element and decoder based on the URI scheme and media format. */
#define LV_GSTREAMER_FACTORY_URI_DECODE "uridecodebin"
#define LV_GSTREAMER_PROPERTY_URI_DECODE "uri"
#define LV_GSTREAMER_FACTORY_FILE "filesrc"
#define LV_GSTREAMER_PROPERTY_FILE "location"
#define LV_GSTREAMER_FACTORY_HTTP "souphttpsrc"
#define LV_GSTREAMER_PROPERTY_HTTP "location"
#define LV_GSTREAMER_FACTORY_HTTPS "souphttpsrc"
#define LV_GSTREAMER_PROPERTY_HTTPS "location"
#define LV_GSTREAMER_FACTORY_V4L2_CAMERA "v4l2src"
#define LV_GSTREAMER_PROPERTY_V4L2_CAMERA "device"
#define LV_GSTREAMER_FACTORY_ALSA_AUDIO "alsasrc"
#define LV_GSTREAMER_PROPERTY_ALSA_AUDIO "device"
#define LV_GSTREAMER_FACTORY_PULSE_AUDIO "pulsesrc"
#define LV_GSTREAMER_PROPERTY_PULSE_AUDIO "device"
#define LV_GSTREAMER_FACTORY_TEST_AUDIO "audiotestsrc"
#define LV_GSTREAMER_PROPERTY_TEST_AUDIO NULL
#define LV_GSTREAMER_FACTORY_TEST_VIDEO "videotestsrc"
#define LV_GSTREAMER_PROPERTY_TEST_VIDEO NULL
#define LV_GSTREAMER_FACTORY_APP "appsrc"
#define LV_GSTREAMER_PROPERTY_APP NULL
/**********************
* TYPEDEFS
**********************/
typedef enum {
LV_GSTREAMER_STATE_NULL,
LV_GSTREAMER_STATE_READY,
LV_GSTREAMER_STATE_PAUSED,
LV_GSTREAMER_STATE_PLAYING
} lv_gstreamer_state_t;
typedef enum {
LV_GSTREAMER_STREAM_STATE_START,
LV_GSTREAMER_STREAM_STATE_PLAY,
LV_GSTREAMER_STREAM_STATE_PAUSE,
LV_GSTREAMER_STREAM_STATE_STOP,
LV_GSTREAMER_STREAM_STATE_END
} lv_gstreamer_stream_state_t;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Create a gstreamer object
* @param parent pointer to an object, it will be the parent of the new gstreamer
* @return pointer to the created gstreamer
*/
lv_obj_t * lv_gstreamer_create(lv_obj_t * parent);
/**
* Add a source to this gstreamer object
* @param gstreamer pointer to a gstreamer object
* @param factory_name the factory name for the source of this gstreamer object.
* for common factory names, check `LV_GSTREAMER_FACTORY_XXX` defines
* @param property the property name for the gstreamer source object
* for common properties, see `LV_GSTREAMER_PROPERTY_XXX` defines
* Passing NULL will create the source object but not set its source
* @param source the property value for the gstreamer source object
* Passing NULL will create the source object but not set its source
* @return LV_RESULT_OK if the source was correctly set else LV_RESULT_INVALID
*/
lv_result_t lv_gstreamer_set_src(lv_obj_t * gstreamer, const char * factory_name, const char * property,
const char * source);
/**
* Play this gstreamer
* @param gstreamer pointer to a gstreamer object
*/
void lv_gstreamer_play(lv_obj_t * gstreamer);
/**
* Pause this gstreamer
* @param gstreamer pointer to a gstreamer object
*/
void lv_gstreamer_pause(lv_obj_t * gstreamer);
/**
* Stop this gstreamer
* @param gstreamer pointer to a gstreamer object
*/
void lv_gstreamer_stop(lv_obj_t * gstreamer);
/**
* Seek a position in this gstreamer
* @param gstreamer pointer to a gstreamer object
* @param position position to seek to
*/
void lv_gstreamer_set_position(lv_obj_t * gstreamer, uint32_t position);
/**
* Get the duration of this gstreamer
* @param gstreamer pointer to a gstreamer object
* @return the duration (in ms) of the gstreamer object
*/
uint32_t lv_gstreamer_get_duration(lv_obj_t * gstreamer);
/**
* Get the position of this gstreamer
* @param gstreamer pointer to a gstreamer object
* @return the position (in ms) of the gstreamer object
*/
uint32_t lv_gstreamer_get_position(lv_obj_t * gstreamer);
/**
* Get the state of this gstreamer
* @param gstreamer pointer to a gstreamer object
*/
lv_gstreamer_state_t lv_gstreamer_get_state(lv_obj_t * gstreamer);
/**
* Set the volume of this gstreamer
* @param gstreamer pointer to a gstreamer object
* @param volume the value to set in the range [0..100]. Higher values are clamped
*/
void lv_gstreamer_set_volume(lv_obj_t * gstreamer, uint8_t volume);
/**
* Get the volume of this gstreamer
* @param gstreamer pointer to a gstreamer object
* @return the volume for this gstreamer
*/
uint8_t lv_gstreamer_get_volume(lv_obj_t * gstreamer);
/**
* Set the speed rate of this gstreamer
* @param gstreamer pointer to a gstreamer object
* @param rate the rate factor. Example values:
* - 256: 1x
* - <256: slow down
* - >256: speed up
* - 128: 0.5x
* - 512: 2x
*/
void lv_gstreamer_set_rate(lv_obj_t * gstreamer, uint32_t rate);
/**
* Retrieve the stream state from a STATE_CHANGED event callback
* @param e pointer to the event
* @return the stream state or -1 if `e` is invalid (i.e. NULL or does not match expected event)
*/
lv_gstreamer_stream_state_t lv_gstreamer_get_stream_state(lv_event_t * e);
/**********************
* MACROS
**********************/
#endif /*LV_USE_GSTREAMER*/
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_GSTREAMER_H*/

View File

@@ -0,0 +1,78 @@
/**
* @file lv_gstreamer_internal.h
*
*/
#ifndef LV_GSTREAMER_INTERNAL_H
#define LV_GSTREAMER_INTERNAL_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
#if LV_USE_GSTREAMER
#include <gst/gst.h>
#include <gst/video/video.h>
#include "../../widgets/image/lv_image_private.h"
#include "lv_gstreamer.h"
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
struct _lv_gstreamer_t {
lv_image_t image;
lv_image_dsc_t frame;
GstVideoInfo video_info;
GstMapInfo last_map_info;
GstBuffer * last_buffer;
GstSample * last_sample;
GstElement * pipeline;
GstElement * audio_convert;
GstElement * video_convert;
GstElement * audio_volume;
lv_timer_t * gstreamer_timer;
GAsyncQueue * frame_queue;
bool is_video_info_valid;
};
typedef struct {
uint8_t * frame_data;
uint32_t width;
uint32_t height;
uint32_t stride;
size_t data_size;
} frame_data_t;
typedef struct _lv_gstreamer_t lv_gstreamer_t;
LV_ATTRIBUTE_EXTERN_DATA extern const lv_obj_class_t lv_gstreamer_class;
/**********************
* GLOBAL PROTOTYPES
**********************/
/**********************
* MACROS
**********************/
#endif /* LV_USE_GSTREAMER != 0 */
#ifdef __cplusplus
} /*extern "C"*/
#endif
#endif /*LV_GSTREAMER_INTERNAL_H*/

View File

@@ -0,0 +1,644 @@
/**
* @file lv_libjpeg_turbo.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../draw/lv_image_decoder_private.h"
#include "../../../lvgl.h"
#if LV_USE_LIBJPEG_TURBO
#include "lv_libjpeg_turbo.h"
#include <stdio.h>
#include <jpeglib.h>
#include <jpegint.h>
#include <setjmp.h>
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#define DECODER_NAME "JPEG_TURBO"
#define image_cache_draw_buf_handlers &(LV_GLOBAL_DEFAULT()->image_cache_draw_buf_handlers)
#define JPEG_PIXEL_SIZE 3 /* RGB888 */
#define JPEG_SIGNATURE 0xFFD8FF
#define IS_JPEG_SIGNATURE(x) (((x) & 0x00FFFFFF) == JPEG_SIGNATURE)
#define ORIENTATION_TAG 0x112 /* Exif tag for orientation */
#define APP1_MARKER JPEG_APP0 + 1 /* APP1 Marker code https://www.media.mit.edu/pia/Research/deepview/exif.html */
#define MARKER_DATA_LIMIT 0xFFFF /* APP1 Marker limit */
/**********************
* TYPEDEFS
**********************/
/**
* according to the Exif specification(http://www.cipa.jp/std/documents/e/DC-008-Translation-2019-E.pdf)
* Relationship between image data and orientation on a display screen according to an orientation tag
*/
typedef enum {
/* Orientation = 0 is created when the image data in the Exif is not rotated */
IMAGE_CLOCKWISE_NONE = 0,
/* Orientation = 1 is created when Oth row of the coded image data stored in the Exif image file
* and the visual top of the display screen, and Oth column and visual left, will each be matched for display
*/
IMAGE_CLOCKWISE_0 = 1,
/* Orientation = 2 is equivalent to an arrangement that is reversed Orientation = 1 horizontally */
IMAGE_FLIP_HOR = 2,
/* Orientation = 3 is equivalent to an arrangement that is turned Orientation = 6 90 degrees clockwise */
IMAGE_CLOCKWISE_180 = 3,
/* Orientation = 4 is equivalent to an arrangement that is reversed Orientation = 3 horizontally */
IMAGE_FLIP_VER = 4,
/* Orientation = 5 is equivalent to an arrangement that is reversed Orientation = 6 horizontally */
IMAGE_TRANSPOSE = 5,
/* Orientation = 6 is equivalent to an arrangement that is turned Orientation = 1 90 degrees clockwise */
IMAGE_CLOCKWISE_90 = 6,
/* Orientation = 7 is equivalent to an arrangement that is reversed Orientation = 8 horizontally */
IMAGE_TRANSVERSE = 7,
/* Orientation = 8 is equivalent to an arrangement that is turned Orientation = 3 90 degrees clockwise */
IMAGE_CLOCKWISE_270 = 8,
} image_orientation_t;
typedef struct error_mgr_s {
struct jpeg_error_mgr pub;
jmp_buf jb;
} error_mgr_t;
/**********************
* STATIC PROTOTYPES
**********************/
static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc, lv_image_header_t * header);
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
static void convert_size_with_orientation(image_orientation_t image_orientation, uint32_t * width, uint32_t * height);
static lv_draw_buf_t * decode_jpeg_file(const char * filename);
static bool get_jpeg_head_info(const char * filename, uint32_t * width, uint32_t * height);
static bool get_jpeg_size(uint8_t * data, uint32_t data_size, uint32_t * width, uint32_t * height);
static image_orientation_t get_jpeg_direction(uint8_t * data, uint32_t data_size);
static inline void process_buffer_orientation(image_orientation_t op, uint8_t * cur_pos, lv_image_header_t * header,
uint32_t line_index, uint8_t * buffer, uint32_t buf_stride);
static image_orientation_t jpeg_markers_reader(struct jpeg_decompress_struct * cinfo);
static void jpeg_cmyk_to_bgrx(uint8_t * cmyk_data, uint32_t pixel_count);
static void error_exit(j_common_ptr cinfo);
/**********************
* STATIC VARIABLES
**********************/
const int JPEG_EXIF = 0x45786966; /* Exif data structure tag */
const int JPEG_BIG_ENDIAN_TAG = 0x4d4d;
const int JPEG_LITTLE_ENDIAN_TAG = 0x4949;
/**********************
* MACROS
**********************/
#define TRANS_32_VALUE(big_endian, data) big_endian ? \
((*(data) << 24) | (*((data) + 1) << 16) | (*((data) + 2) << 8) | *((data) + 3)) : \
(*(data) | (*((data) + 1) << 8) | (*((data) + 2) << 16) | (*((data) + 3) << 24))
#define TRANS_16_VALUE(big_endian, data) big_endian ? \
((*(data) << 8) | *((data) + 1)) : (*(data) | (*((data) + 1) << 8))
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* Register the JPEG decoder functions in LVGL
*/
void lv_libjpeg_turbo_init(void)
{
lv_image_decoder_t * dec = lv_image_decoder_create();
lv_image_decoder_set_info_cb(dec, decoder_info);
lv_image_decoder_set_open_cb(dec, decoder_open);
lv_image_decoder_set_close_cb(dec, decoder_close);
dec->name = DECODER_NAME;
}
void lv_libjpeg_turbo_deinit(void)
{
lv_image_decoder_t * dec = NULL;
while((dec = lv_image_decoder_get_next(dec)) != NULL) {
if(dec->info_cb == decoder_info) {
lv_image_decoder_delete(dec);
break;
}
}
}
/**********************
* STATIC FUNCTIONS
**********************/
/**
* Get info about a JPEG image
* @param dsc image descriptor containing the source and type of the image and other info.
* @param header store the info here
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't get the info
*/
static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc, lv_image_header_t * header)
{
LV_UNUSED(decoder); /*Unused*/
lv_image_src_t src_type = dsc->src_type; /*Get the source type*/
/*If it's a JPEG file...*/
if(src_type == LV_IMAGE_SRC_FILE) {
const char * src = dsc->src;
uint32_t jpg_signature = 0;
uint32_t rn;
lv_fs_read(&dsc->file, &jpg_signature, sizeof(jpg_signature), &rn);
if(rn != sizeof(jpg_signature)) {
LV_LOG_WARN("file: %s signature len = %" LV_PRIu32 " error", src, rn);
return LV_RESULT_INVALID;
}
const char * ext = lv_fs_get_ext(src);
bool is_jpeg_ext = (lv_strcmp(ext, "jpg") == 0)
|| (lv_strcmp(ext, "jpeg") == 0);
if(!IS_JPEG_SIGNATURE(jpg_signature)) {
if(is_jpeg_ext) {
LV_LOG_WARN("file: %s signature = 0X%" LV_PRIX32 " error", src, jpg_signature);
}
return LV_RESULT_INVALID;
}
uint32_t width;
uint32_t height;
if(!get_jpeg_head_info(src, &width, &height)) {
return LV_RESULT_INVALID;
}
/*Save the data in the header*/
header->cf = LV_COLOR_FORMAT_RGB888;
header->w = width;
header->h = height;
return LV_RESULT_OK;
}
return LV_RESULT_INVALID; /*If didn't succeeded earlier then it's an error*/
}
/**
* Open a JPEG image and return the decided image
* @param decoder pointer to the decoder
* @param dsc pointer to the decoder descriptor
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't open the image
*/
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
LV_UNUSED(decoder); /*Unused*/
/*If it's a JPEG file...*/
if(dsc->src_type == LV_IMAGE_SRC_FILE) {
const char * fn = dsc->src;
lv_draw_buf_t * decoded = decode_jpeg_file(fn);
if(decoded == NULL) {
LV_LOG_WARN("decode jpeg file failed");
return LV_RESULT_INVALID;
}
dsc->decoded = decoded;
if(dsc->args.no_cache) return LV_RESULT_OK;
/*If the image cache is disabled, just return the decoded image*/
if(!lv_image_cache_is_enabled()) return LV_RESULT_OK;
/*Add the decoded image to the cache*/
lv_image_cache_data_t search_key;
search_key.src_type = dsc->src_type;
search_key.src = dsc->src;
search_key.slot.size = decoded->data_size;
lv_cache_entry_t * entry = lv_image_decoder_add_to_cache(decoder, &search_key, decoded, NULL);
if(entry == NULL) {
lv_draw_buf_destroy(decoded);
return LV_RESULT_INVALID;
}
dsc->cache_entry = entry;
return LV_RESULT_OK; /*If not returned earlier then it failed*/
}
return LV_RESULT_INVALID; /*If not returned earlier then it failed*/
}
/**
* Free the allocated resources
*/
static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
LV_UNUSED(decoder); /*Unused*/
if(dsc->args.no_cache ||
!lv_image_cache_is_enabled()) lv_draw_buf_destroy((lv_draw_buf_t *)dsc->decoded);
}
static void convert_size_with_orientation(image_orientation_t image_orientation, uint32_t * width, uint32_t * height)
{
if(image_orientation == IMAGE_CLOCKWISE_NONE || image_orientation == IMAGE_CLOCKWISE_0
|| image_orientation == IMAGE_CLOCKWISE_180 || image_orientation == IMAGE_FLIP_VER ||
image_orientation == IMAGE_FLIP_HOR) {
return;
}
uint32_t tmp = *width;
*width = *height;
*height = tmp;
}
static lv_draw_buf_t * decode_jpeg_file(const char * filename)
{
/* This struct contains the JPEG decompression parameters and pointers to
* working space (which is allocated as needed by the JPEG library).
*/
struct jpeg_decompress_struct cinfo;
/* We use our private extension JPEG error handler.
* Note that this struct must live as long as the main JPEG parameter
* struct, to avoid dangling-pointer problems.
*/
error_mgr_t jerr;
/* More stuff */
JSAMPARRAY buffer; /* Output row buffer */
uint32_t row_stride; /* physical row width in output buffer */
lv_draw_buf_t * decoded = NULL;
/* In this example we want to open the input file before doing anything else,
* so that the setjmp() error recovery below can assume the file is open.
* VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
* requires it in order to read binary files.
*/
uint32_t data_size;
uint8_t * data = lv_fs_load_with_alloc(filename, &data_size);
if(data == NULL) {
LV_LOG_WARN("can't load file %s", filename);
return NULL;
}
/* read jpeg exif orientation */
image_orientation_t image_orientation = get_jpeg_direction(data, data_size);
/* allocate and initialize JPEG decompression object */
/* We set up the normal JPEG error routines, then override error_exit. */
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = error_exit;
/* Establish the setjmp return context for my_error_exit to use. */
if(setjmp(jerr.jb)) {
LV_LOG_WARN("decoding error");
if(decoded) {
lv_draw_buf_destroy(decoded);
}
/* If we get here, the JPEG code has signaled an error.
* We need to clean up the JPEG object, close the input file, and return.
*/
jpeg_destroy_decompress(&cinfo);
lv_free(data);
return NULL;
}
/* Now we can initialize the JPEG decompression object. */
jpeg_create_decompress(&cinfo);
/* specify data source (eg, a file or buffer) */
jpeg_mem_src(&cinfo, data, data_size);
/* read file parameters with jpeg_read_header() */
jpeg_read_header(&cinfo, TRUE);
/* We can ignore the return value from jpeg_read_header since
* (a) suspension is not possible with the stdio data source, and
* (b) we passed TRUE to reject a tables-only JPEG file as an error.
* See libjpeg.doc for more info.
*/
/* set parameters for decompression */
if(cinfo.jpeg_color_space == JCS_CMYK || cinfo.jpeg_color_space == JCS_YCCK) {
cinfo.out_color_space = JCS_CMYK;
}
else {
cinfo.out_color_space = JCS_EXT_BGR;
}
/* In this example, we don't need to change any of the defaults set by
* jpeg_read_header(), so we do nothing here.
*/
/* Start decompressor */
jpeg_start_decompress(&cinfo);
/* We can ignore the return value since suspension is not possible
* with the stdio data source.
*/
/* We may need to do some setup of our own at this point before reading
* the data. After jpeg_start_decompress() we have the correct scaled
* output image dimensions available, as well as the output colormap
* if we asked for color quantization.
* In this example, we need to make an output work buffer of the right size.
*/
/* JSAMPLEs per row in output buffer */
row_stride = cinfo.output_width * cinfo.output_components;
/* Make a one-row-high sample array that will go away when done with image */
buffer = (*cinfo.mem->alloc_sarray)
((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
uint32_t width = cinfo.output_width;
uint32_t height = cinfo.output_height;
convert_size_with_orientation(image_orientation, &width, &height);
lv_image_header_t image_header = { 0 };
image_header.w = width;
image_header.h = height;
image_header.stride = row_stride;
lv_color_format_t fm = LV_COLOR_FORMAT_RGB888;
if(cinfo.out_color_space == JCS_CMYK) {
fm = LV_COLOR_FORMAT_XRGB8888;
}
/* Allocate the decoded draw buffer */
decoded = lv_draw_buf_create_ex(image_cache_draw_buf_handlers, width, height, fm, LV_STRIDE_AUTO);
if(decoded != NULL) {
uint32_t line_index = 0;
/* while (scan lines remain to be read) */
/* jpeg_read_scanlines(...); */
/* Here we use the library's state variable cinfo.output_scanline as the
* loop counter, so that we don't have to keep track ourselves.
*/
while(cinfo.output_scanline < cinfo.output_height) {
/* jpeg_read_scanlines expects an array of pointers to scanlines.
* Here the array is only one element long, but you could ask for
* more than one scanline at a time if that's more convenient.
*/
jpeg_read_scanlines(&cinfo, buffer, 1);
if(cinfo.out_color_space == JCS_CMYK) {
jpeg_cmyk_to_bgrx(buffer[0], decoded->header.w);
}
/* Assume put_scanline_someplace wants a pointer and sample count. */
process_buffer_orientation(image_orientation, decoded->data, &image_header, line_index, buffer[0],
decoded->header.stride);
line_index++;
}
}
/* Finish decompression */
jpeg_finish_decompress(&cinfo);
/* We can ignore the return value since suspension is not possible
* with the stdio data source.
*/
/* Release JPEG decompression object */
/* This is an important step since it will release a good deal of memory. */
jpeg_destroy_decompress(&cinfo);
/* After finish_decompress, we can close the input file.
* Here we postpone it until after no more JPEG errors are possible,
* so as to simplify the setjmp error logic above. (Actually, I don't
* think that jpeg_destroy can do an error exit, but why assume anything...)
*/
lv_free(data);
return decoded;
}
static bool get_jpeg_head_info(const char * filename, uint32_t * width, uint32_t * height)
{
uint8_t * data = NULL;
uint32_t data_size;
data = lv_fs_load_with_alloc(filename, &data_size);
if(data == NULL) {
return false;
}
if(!get_jpeg_size(data, data_size, width, height)) {
LV_LOG_WARN("read jpeg size failed.");
}
lv_free(data);
return JPEG_HEADER_OK;
}
static void jpeg_decompress_prepare(struct jpeg_decompress_struct * cinfo, uint8_t * data, uint32_t data_size)
{
jpeg_create_decompress(cinfo);
jpeg_mem_src(cinfo, data, data_size);
jpeg_save_markers(cinfo, APP1_MARKER, MARKER_DATA_LIMIT);
}
static bool get_jpeg_size(uint8_t * data, uint32_t data_size, uint32_t * width, uint32_t * height)
{
struct jpeg_decompress_struct cinfo;
error_mgr_t jerr;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = error_exit;
if(setjmp(jerr.jb)) {
LV_LOG_WARN("read jpeg head failed");
jpeg_destroy_decompress(&cinfo);
return false;
}
/* jpeg_create_decompress */
jpeg_decompress_prepare(&cinfo, data, data_size);
int ret = jpeg_read_header(&cinfo, TRUE);
if(ret != JPEG_HEADER_OK) {
LV_LOG_WARN("read jpeg header failed: %d", ret);
jpeg_destroy_decompress(&cinfo);
return false;
}
/* read file exif orientation */
image_orientation_t op = jpeg_markers_reader(&cinfo);
*width = cinfo.image_width;
*height = cinfo.image_height;
convert_size_with_orientation(op, width, height);
jpeg_destroy_decompress(&cinfo);
return true;
}
static image_orientation_t get_jpeg_direction(uint8_t * data, uint32_t data_size)
{
image_orientation_t res = IMAGE_CLOCKWISE_NONE;
struct jpeg_decompress_struct cinfo;
error_mgr_t jerr;
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = error_exit;
if(setjmp(jerr.jb)) {
LV_LOG_WARN("read jpeg orientation failed");
jpeg_destroy_decompress(&cinfo);
return res;
}
/* jpeg_create_decompress */
jpeg_decompress_prepare(&cinfo, data, data_size);
/* read file exif orientation */
res = jpeg_markers_reader(&cinfo);
jpeg_destroy_decompress(&cinfo);
LV_LOG_INFO("read jpeg orientation data : %d", res);
return res;
}
static inline void process_buffer_orientation(image_orientation_t op, uint8_t * cur_pos, lv_image_header_t * header,
uint32_t line_index, uint8_t * buffer, uint32_t buf_stride)
{
uint32_t dst_index = 0;
switch(op) {
case IMAGE_CLOCKWISE_NONE:
case IMAGE_CLOCKWISE_0:
lv_memcpy(cur_pos + line_index * buf_stride, buffer, header->stride);
break;
case IMAGE_CLOCKWISE_90:
for(uint32_t x = 0; x < header->h; x++) {
dst_index = x * buf_stride + (header->w - line_index - 1) * JPEG_PIXEL_SIZE;
lv_memcpy(cur_pos + dst_index, buffer + x * JPEG_PIXEL_SIZE, JPEG_PIXEL_SIZE);
}
break;
case IMAGE_CLOCKWISE_180:
for(uint32_t x = 0; x < header->w; x++) {
dst_index = (header->h - line_index - 1) * buf_stride + x * JPEG_PIXEL_SIZE;
lv_memcpy(cur_pos + dst_index, buffer + (header->w - x - 1) * JPEG_PIXEL_SIZE, JPEG_PIXEL_SIZE);
}
break;
case IMAGE_CLOCKWISE_270:
for(uint32_t x = 0; x < header->h; x++) {
dst_index = (header->h - x - 1) * buf_stride + line_index * JPEG_PIXEL_SIZE;
lv_memcpy(cur_pos + dst_index, buffer + x * JPEG_PIXEL_SIZE, JPEG_PIXEL_SIZE);
}
break;
case IMAGE_FLIP_VER:
dst_index = (header->h - line_index - 1) * buf_stride;
lv_memcpy(cur_pos + dst_index, buffer, header->stride);
break;
case IMAGE_FLIP_HOR:
for(uint32_t x = 0; x < header->w; x++) {
dst_index = line_index * buf_stride + (header->w - x - 1) * JPEG_PIXEL_SIZE;
lv_memcpy(cur_pos + dst_index, buffer + x * JPEG_PIXEL_SIZE, JPEG_PIXEL_SIZE);
}
break;
case IMAGE_TRANSPOSE:
for(uint32_t x = 0; x < header->h; x++) {
dst_index = x * buf_stride + line_index * JPEG_PIXEL_SIZE;
lv_memcpy(cur_pos + dst_index, buffer + x * JPEG_PIXEL_SIZE, JPEG_PIXEL_SIZE);
}
break;
case IMAGE_TRANSVERSE:
for(uint32_t x = 0; x < header->h; x++) {
dst_index = (header->h - x - 1) * buf_stride + (header->w - line_index - 1) * JPEG_PIXEL_SIZE;
lv_memcpy(cur_pos + dst_index, buffer + x * JPEG_PIXEL_SIZE, JPEG_PIXEL_SIZE);
}
break;
default:
lv_memcpy(cur_pos + line_index * buf_stride, buffer, header->stride);
break;
}
}
static image_orientation_t jpeg_markers_reader(struct jpeg_decompress_struct * cinfo)
{
image_orientation_t res = IMAGE_CLOCKWISE_NONE;
if(cinfo == NULL || cinfo->marker == NULL) {
return res;
}
cinfo->marker->read_markers(cinfo);
jpeg_saved_marker_ptr marker = cinfo->marker_list;
while(marker != NULL) {
if(marker->marker == APP1_MARKER) {
JOCTET FAR * app1_data = marker->data;
if(TRANS_32_VALUE(true, app1_data) == JPEG_EXIF) {
uint16_t endian_tag = TRANS_16_VALUE(true, app1_data + 4 + 2);
if(!(endian_tag == JPEG_LITTLE_ENDIAN_TAG || endian_tag == JPEG_BIG_ENDIAN_TAG)) {
return res;
}
bool is_big_endian = endian_tag == JPEG_BIG_ENDIAN_TAG;
/* first ifd offset addr : 4bytes(Exif) + 2bytes(0x00) + 2bytes(align) + 2bytes(tag mark) */
unsigned int offset = TRANS_32_VALUE(is_big_endian, app1_data + 8 + 2);
/* ifd base : 4bytes(Exif) + 2bytes(0x00) */
unsigned char * ifd = 0;
do {
/* ifd start: 4bytes(Exif) + 2bytes(0x00) + offset value(2bytes(align) + 2bytes(tag mark) + 4bytes(offset size)) */
unsigned int entry_offset = 4 + 2 + offset + 2;
if(entry_offset >= marker->data_length) {
return res;
}
ifd = app1_data + entry_offset;
unsigned short num_entries = TRANS_16_VALUE(is_big_endian, ifd - 2);
if(entry_offset + num_entries * 12 >= marker->data_length) {
return res;
}
for(int i = 0; i < num_entries; i++) {
unsigned short tag = TRANS_16_VALUE(is_big_endian, ifd);
if(tag == ORIENTATION_TAG) {
/* ifd entry: 12bytes = 2bytes(tag number) + 2bytes(kind of data) + 4bytes(number of components) + 4bytes(data)
* orientation kind(0x03) of data is unsigned short */
uint32_t dirc = TRANS_16_VALUE(is_big_endian, ifd + 2 + 2 + 4);
res = (dirc >= IMAGE_CLOCKWISE_0 && dirc <= IMAGE_CLOCKWISE_270) ? (image_orientation_t)dirc : IMAGE_CLOCKWISE_NONE;
}
ifd += 12;
}
offset = TRANS_32_VALUE(is_big_endian, ifd);
} while(offset != 0);
}
break;
}
marker = marker->next;
}
return res;
}
static void jpeg_cmyk_to_bgrx(uint8_t * cmyk_data, uint32_t pixel_count)
{
uint8_t * ptr = cmyk_data;
for(uint32_t i = 0; i < pixel_count; i++) {
uint8_t c = 255 - ptr[0];
uint8_t m = 255 - ptr[1];
uint8_t y = 255 - ptr[2];
uint8_t k = 255 - ptr[3];
uint16_t inv_k = 255 - k;
ptr[0] = (uint8_t)(((inv_k * (255 - y)) + 128) / 255);
ptr[1] = (uint8_t)(((inv_k * (255 - m)) + 128) / 255);
ptr[2] = (uint8_t)(((inv_k * (255 - c)) + 128) / 255);
ptr[3] = 255;
ptr += 4;
}
}
static void error_exit(j_common_ptr cinfo)
{
error_mgr_t * myerr = (error_mgr_t *)cinfo->err;
(*cinfo->err->output_message)(cinfo);
longjmp(myerr->jb, 1);
}
#endif /*LV_USE_LIBJPEG_TURBO*/

View File

@@ -0,0 +1,48 @@
/**
* @file lv_libjpeg_turbo.h
*
*/
#ifndef LV_LIBJPEG_TURBO_H
#define LV_LIBJPEG_TURBO_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
#if LV_USE_LIBJPEG_TURBO
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Register the JPEG-Turbo decoder functions in LVGL
*/
void lv_libjpeg_turbo_init(void);
void lv_libjpeg_turbo_deinit(void);
/**********************
* MACROS
**********************/
#endif /*LV_USE_LIBJPEG_TURBO*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /*LV_LIBJPEG_TURBO_H*/

View File

@@ -0,0 +1,294 @@
/**
* @file lv_libpng.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../draw/lv_image_decoder_private.h"
#include "../../../lvgl.h"
#if LV_USE_LIBPNG
#include "lv_libpng.h"
#include <png.h>
#include <string.h>
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#define DECODER_NAME "PNG"
#define image_cache_draw_buf_handlers &(LV_GLOBAL_DEFAULT()->image_cache_draw_buf_handlers)
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * src, lv_image_header_t * header);
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
static lv_draw_buf_t * decode_png(lv_image_decoder_dsc_t * dsc);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* Register the PNG decoder functions in LVGL
*/
void lv_libpng_init(void)
{
lv_image_decoder_t * dec = lv_image_decoder_create();
lv_image_decoder_set_info_cb(dec, decoder_info);
lv_image_decoder_set_open_cb(dec, decoder_open);
lv_image_decoder_set_close_cb(dec, decoder_close);
dec->name = DECODER_NAME;
}
void lv_libpng_deinit(void)
{
lv_image_decoder_t * dec = NULL;
while((dec = lv_image_decoder_get_next(dec)) != NULL) {
if(dec->info_cb == decoder_info) {
lv_image_decoder_delete(dec);
break;
}
}
}
/**********************
* STATIC FUNCTIONS
**********************/
/**
* Get info about a PNG image
* @param dsc can be file name or pointer to a C array
* @param header store the info here
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't get the info
*/
static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc, lv_image_header_t * header)
{
LV_UNUSED(decoder); /*Unused*/
lv_image_src_t src_type = dsc->src_type; /*Get the source type*/
if(src_type == LV_IMAGE_SRC_FILE || src_type == LV_IMAGE_SRC_VARIABLE) {
uint32_t * size;
static const uint8_t magic[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a};
uint8_t buf[24];
/*If it's a PNG file...*/
if(src_type == LV_IMAGE_SRC_FILE) {
/* Read the width and height from the file. They have a constant location:
* [16..19]: width
* [20..23]: height
*/
uint32_t rn;
lv_fs_read(&dsc->file, buf, sizeof(buf), &rn);
if(rn != sizeof(buf)) return LV_RESULT_INVALID;
if(lv_memcmp(buf, magic, sizeof(magic)) != 0) return LV_RESULT_INVALID;
size = (uint32_t *)&buf[16];
}
/*If it's a PNG file in a C array...*/
else {
const lv_image_dsc_t * img_dsc = dsc->src;
const uint32_t data_size = img_dsc->data_size;
size = ((uint32_t *)img_dsc->data) + 4;
if(data_size < sizeof(magic)) return LV_RESULT_INVALID;
if(lv_memcmp(img_dsc->data, magic, sizeof(magic)) != 0) return LV_RESULT_INVALID;
}
/*Save the data in the header*/
header->cf = LV_COLOR_FORMAT_ARGB8888;
/*The width and height are stored in Big endian format so convert them to little endian*/
header->w = (int32_t)((size[0] & 0xff000000) >> 24) + ((size[0] & 0x00ff0000) >> 8);
header->h = (int32_t)((size[1] & 0xff000000) >> 24) + ((size[1] & 0x00ff0000) >> 8);
return LV_RESULT_OK;
}
return LV_RESULT_INVALID; /*If didn't succeeded earlier then it's an error*/
}
/**
* Open a PNG image and return the decoded image
* @param decoder pointer to the decoder
* @param dsc pointer to the decoder descriptor
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't open the image
*/
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
LV_UNUSED(decoder); /*Unused*/
lv_draw_buf_t * decoded;
decoded = decode_png(dsc);
if(decoded == NULL) {
return LV_RESULT_INVALID;
}
lv_draw_buf_t * adjusted = lv_image_decoder_post_process(dsc, decoded);
if(adjusted == NULL) {
lv_draw_buf_destroy(decoded);
return LV_RESULT_INVALID;
}
/*The adjusted draw buffer is newly allocated.*/
if(adjusted != decoded) {
lv_draw_buf_destroy(decoded);
decoded = adjusted;
}
dsc->decoded = decoded;
if(dsc->args.no_cache) {
return LV_RESULT_OK;
}
/*If the image cache is disabled, just return the decoded image*/
if(!lv_image_cache_is_enabled()) {
return LV_RESULT_OK;
}
/*Add the decoded image to the cache*/
lv_image_cache_data_t search_key;
search_key.src_type = dsc->src_type;
search_key.src = dsc->src;
search_key.slot.size = decoded->data_size;
lv_cache_entry_t * entry = lv_image_decoder_add_to_cache(decoder, &search_key, decoded, NULL);
if(entry == NULL) {
lv_draw_buf_destroy(decoded);
return LV_RESULT_INVALID;
}
dsc->cache_entry = entry;
return LV_RESULT_OK; /*The image is fully decoded. Return with its pointer*/
}
/**
* Free the allocated resources
*/
static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
LV_UNUSED(decoder); /*Unused*/
if(dsc->args.no_cache ||
!lv_image_cache_is_enabled()) lv_draw_buf_destroy((lv_draw_buf_t *)dsc->decoded);
}
static lv_draw_buf_t * decode_png(lv_image_decoder_dsc_t * dsc)
{
LV_PROFILER_DECODER_BEGIN;
int ret;
uint8_t * png_data;
uint32_t png_data_size;
/*Prepare png_image*/
png_image image;
lv_memzero(&image, sizeof(image));
image.version = PNG_IMAGE_VERSION;
if(dsc->src_type == LV_IMAGE_SRC_FILE) {
png_data = lv_fs_load_with_alloc((const char *)dsc->src, &png_data_size);
if(png_data == NULL) {
LV_LOG_WARN("can't load file: %s", (const char *)dsc->src);
LV_PROFILER_DECODER_END;
return NULL;
}
}
else if(dsc->src_type == LV_IMAGE_SRC_VARIABLE) {
const lv_image_dsc_t * img_dsc = dsc->src;
png_data = (uint8_t *)img_dsc->data;
png_data_size = img_dsc->data_size;
}
else {
LV_PROFILER_DECODER_END;
return NULL;
}
/*Ready to read file*/
LV_PROFILER_DECODER_BEGIN_TAG("png_image_begin_read_from_memory");
ret = png_image_begin_read_from_memory(&image, png_data, png_data_size);
LV_PROFILER_DECODER_END_TAG("png_image_begin_read_from_memory");
if(!ret) {
LV_LOG_ERROR("png read failed: %d", ret);
if(dsc->src_type == LV_IMAGE_SRC_FILE)
lv_free(png_data);
LV_PROFILER_DECODER_END;
return NULL;
}
lv_color_format_t cf;
if(dsc->args.use_indexed && (image.format & PNG_FORMAT_FLAG_COLORMAP)) {
cf = LV_COLOR_FORMAT_I8;
image.format = PNG_FORMAT_BGRA_COLORMAP;
}
else {
cf = LV_COLOR_FORMAT_ARGB8888;
image.format = PNG_FORMAT_BGRA;
}
/*Alloc image buffer*/
lv_draw_buf_t * decoded;
decoded = lv_draw_buf_create_ex(image_cache_draw_buf_handlers, image.width, image.height, cf, LV_STRIDE_AUTO);
if(decoded == NULL) {
if(dsc->src_type == LV_IMAGE_SRC_FILE) {
LV_LOG_ERROR("alloc PNG_IMAGE_SIZE(%" LV_PRIu32 ") failed: %s", (uint32_t)PNG_IMAGE_SIZE(image),
(const char *)dsc->src);
lv_free(png_data);
}
else if(dsc->src_type == LV_IMAGE_SRC_VARIABLE)
LV_LOG_ERROR("alloc PNG_IMAGE_SIZE(%" LV_PRIu32 ")", (uint32_t)PNG_IMAGE_SIZE(image));
LV_PROFILER_DECODER_BEGIN_TAG("png_image_free");
png_image_free(&image);
LV_PROFILER_DECODER_END_TAG("png_image_free");
LV_PROFILER_DECODER_END;
return NULL;
}
void * palette = decoded->data;
void * map = decoded->data + LV_COLOR_INDEXED_PALETTE_SIZE(cf) * sizeof(lv_color32_t);
/*Start decoding*/
LV_PROFILER_DECODER_BEGIN_TAG("png_image_finish_read");
ret = png_image_finish_read(&image, NULL, map, decoded->header.stride, palette);
LV_PROFILER_DECODER_END_TAG("png_image_finish_read");
LV_PROFILER_DECODER_BEGIN_TAG("png_image_free");
png_image_free(&image);
LV_PROFILER_DECODER_END_TAG("png_image_free");
if(dsc->src_type == LV_IMAGE_SRC_FILE)
lv_free(png_data);
if(!ret) {
LV_LOG_ERROR("png decode failed: %s", image.message);
lv_draw_buf_destroy(decoded);
LV_PROFILER_DECODER_END;
return NULL;
}
LV_PROFILER_DECODER_END;
return decoded;
}
#endif /*LV_USE_LIBPNG*/

View File

@@ -0,0 +1,48 @@
/**
* @file lv_libpng.h
*
*/
#ifndef LV_LIBPNG_H
#define LV_LIBPNG_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
#if LV_USE_LIBPNG
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Register the PNG decoder functions in LVGL
*/
void lv_libpng_init(void);
void lv_libpng_deinit(void);
/**********************
* MACROS
**********************/
#endif /*LV_USE_LIBPNG*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /*LV_LIBPNG_H*/

View File

@@ -0,0 +1,240 @@
/**
* @file lv_libwebp.c
*
*/
/*********************
* INCLUDES
*********************/
#include "../../draw/lv_image_decoder_private.h"
#include "../../../lvgl.h"
#if LV_USE_LIBWEBP
#include "lv_libwebp.h"
#include <webp/decode.h>
#include "../../core/lv_global.h"
/*********************
* DEFINES
*********************/
#define DECODER_NAME "LIBWEBP"
#define image_cache_draw_buf_handlers &(LV_GLOBAL_DEFAULT()->image_cache_draw_buf_handlers)
#define WEBP_HEADER_SIZE 64
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc, lv_image_header_t * header);
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc);
static lv_draw_buf_t * decode_webp_file(lv_image_decoder_dsc_t * dsc, const char * filename);
/**********************
* STATIC VARIABLES
**********************/
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* Register the WEBP decoder functions in LVGL
*/
void lv_libwebp_init(void)
{
lv_image_decoder_t * dec = lv_image_decoder_create();
lv_image_decoder_set_info_cb(dec, decoder_info);
lv_image_decoder_set_open_cb(dec, decoder_open);
lv_image_decoder_set_close_cb(dec, decoder_close);
dec->name = DECODER_NAME;
}
void lv_libwebp_deinit(void)
{
lv_image_decoder_t * dec = NULL;
while((dec = lv_image_decoder_get_next(dec)) != NULL) {
if(dec->info_cb == decoder_info) {
lv_image_decoder_delete(dec);
break;
}
}
}
/**********************
* STATIC FUNCTIONS
**********************/
/**
* Get info about a WEBP image
* @param dsc can be file name or pointer to a C array
* @param header store the info here
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't get the info
*/
static lv_result_t decoder_info(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc, lv_image_header_t * header)
{
LV_UNUSED(decoder); /*Unused*/
lv_image_src_t src_type = dsc->src_type; /*Get the source type*/
/*If it's a webp file...*/
if(src_type == LV_IMAGE_SRC_FILE) {
uint8_t buf[WEBP_HEADER_SIZE];
int width;
int height;
uint32_t rn;
lv_fs_res_t res = lv_fs_read(&dsc->file, buf, sizeof(buf), &rn);
/* The max header size = RIFF + VP8X + (optional chunks) + VP8(L), 64 bytes is enough to get the width and height.
* If the file is smaller than 64 bytes, it's maybe a valid webp file,
* so we don't check the result length here.
* VP8X : RIFF(12) + VP8X(18) = 30bytes;
* VP8(L): RIFF(12) + VP8(L) chunk header(8) + VP8(L) frame header(5) = 23bytes;
* VP8: RIFF(12) + VP8(L) chunk header(8) + VP8(L) frame header(10) = 28bytes;
*/
if(res != LV_FS_RES_OK) return LV_RESULT_INVALID;
if(WebPGetInfo(buf, rn, &width, &height) == 0) {
return LV_RESULT_INVALID;
}
/*Default decoder color format is ARGB8888*/
header->cf = LV_COLOR_FORMAT_ARGB8888;
header->w = width;
header->h = height;
header->stride = width * sizeof(lv_color32_t);
return LV_RESULT_OK;
}
return LV_RESULT_INVALID; /*If it didn't succeed earlier then it's an error*/
}
/**
* Open a WEBP image and return the decoded image
* @param decoder pointer to the decoder
* @param dsc pointer to the decoder descriptor
* @return LV_RESULT_OK: no error; LV_RESULT_INVALID: can't open the image
*/
static lv_result_t decoder_open(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
LV_UNUSED(decoder); /*Unused*/
/*If it's a webp file...*/
if(dsc->src_type == LV_IMAGE_SRC_FILE) {
const char * fn = dsc->src;
lv_draw_buf_t * decoded = decode_webp_file(dsc, fn);
if(decoded == NULL) {
return LV_RESULT_INVALID;
}
lv_draw_buf_t * adjusted = lv_image_decoder_post_process(dsc, decoded);
if(adjusted == NULL) {
lv_draw_buf_destroy(decoded);
return LV_RESULT_INVALID;
}
/*The adjusted draw buffer is newly allocated.*/
if(adjusted != decoded) {
lv_draw_buf_destroy(decoded);
decoded = adjusted;
}
dsc->decoded = decoded;
if(dsc->args.no_cache) {
return LV_RESULT_OK;
}
/*If the image cache is disabled, just return the decoded image*/
if(!lv_image_cache_is_enabled()) {
return LV_RESULT_OK;
}
lv_image_cache_data_t search_key;
search_key.src_type = dsc->src_type;
search_key.src = dsc->src;
search_key.slot.size = decoded->data_size;
lv_cache_entry_t * entry = lv_image_decoder_add_to_cache(decoder, &search_key, decoded, NULL);
if(entry == NULL) {
lv_draw_buf_destroy(decoded);
return LV_RESULT_INVALID;
}
dsc->cache_entry = entry;
return LV_RESULT_OK; /*The image is fully decoded. Return with its pointer*/
}
return LV_RESULT_INVALID; /*If not returned earlier then it failed*/
}
/**
* Free the allocated resources
*/
static void decoder_close(lv_image_decoder_t * decoder, lv_image_decoder_dsc_t * dsc)
{
LV_UNUSED(decoder); /*Unused*/
if(dsc->args.no_cache ||
!lv_image_cache_is_enabled()) lv_draw_buf_destroy((lv_draw_buf_t *)dsc->decoded);
}
static lv_draw_buf_t * decode_webp_file(lv_image_decoder_dsc_t * dsc, const char * filename)
{
LV_PROFILER_DECODER_BEGIN;
uint32_t data_size;
uint8_t * data = lv_fs_load_with_alloc(filename, &data_size);
if(data == NULL) {
LV_LOG_WARN("can't load file: %s", filename);
LV_PROFILER_DECODER_END;
return NULL;
}
/*Alloc image buffer*/
lv_draw_buf_t * decoded;
decoded = lv_draw_buf_create_ex(image_cache_draw_buf_handlers, dsc->header.w, dsc->header.h, dsc->header.cf,
LV_STRIDE_AUTO);
if(decoded == NULL) {
LV_LOG_ERROR("alloc draw buffer failed: %s", filename);
lv_free(data);
LV_PROFILER_DECODER_END;
return NULL;
}
WebPDecoderConfig config;
WebPInitDecoderConfig(&config);
config.output.colorspace = MODE_BGRA;
config.output.u.RGBA.rgba = (uint8_t *) decoded->data;
config.output.u.RGBA.stride = decoded->header.stride;
config.output.u.RGBA.size = decoded->data_size;
config.output.is_external_memory = 1;
LV_PROFILER_DECODER_BEGIN_TAG("WebPDecode");
int status = WebPDecode(data, data_size, &config);
LV_PROFILER_DECODER_END_TAG("WebPDecode");
if(status != VP8_STATUS_OK) {
LV_LOG_ERROR("decode webp failed: %s, status: %d", filename, status);
lv_draw_buf_destroy(decoded);
decoded = NULL;
}
lv_free(data);
LV_PROFILER_DECODER_END;
return decoded;
}
#endif /*LV_USE_LIBWEBP*/

View File

@@ -0,0 +1,48 @@
/**
* @file lv_libwebp.h
*
*/
#ifndef LV_LIBWEBP_H
#define LV_LIBWEBP_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#include "../../lv_conf_internal.h"
#if LV_USE_LIBWEBP
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Register the WEBP decoder functions in LVGL
*/
void lv_libwebp_init(void);
void lv_libwebp_deinit(void);
/**********************
* MACROS
**********************/
#endif /*LV_USE_LIBWEBP*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /*LV_LIBWEBP_H*/

View File

@@ -0,0 +1,21 @@
Copyright (c) 2005-2018 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.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More