Compare commits
2 Commits
0d451e8863
...
fe2dcd22eb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe2dcd22eb | ||
|
|
7ea8c346b0 |
@ -36,7 +36,7 @@ CONFIG_LV_USE_FS_STDIO=y
|
||||
CONFIG_LV_FS_STDIO_LETTER=65
|
||||
CONFIG_LV_FS_STDIO_PATH=""
|
||||
CONFIG_LV_FS_STDIO_CACHE_SIZE=4096
|
||||
CONFIG_LV_USE_LODEPNG=y
|
||||
CONFIG_LV_USE_LODEPNG=n
|
||||
CONFIG_LV_USE_BUILTIN_MALLOC=n
|
||||
# Routes lv_malloc/realloc/free through Modules/lvgl-module/source/lv_mem_custom.c, which prefers
|
||||
# PSRAM (falls back to internal RAM automatically) instead of a fixed-size pool or plain malloc.
|
||||
@ -170,6 +170,8 @@ if (NOT DEFINED ENV{ESP_IDF_VERSION})
|
||||
add_subdirectory(Libraries/minmea)
|
||||
add_subdirectory(Modules/lvgl-module)
|
||||
add_subdirectory(Modules/audio-decoder-module)
|
||||
add_subdirectory(Modules/binfont-module)
|
||||
add_subdirectory(Modules/lodepng-module)
|
||||
add_subdirectory(Modules/c-symbols-module)
|
||||
add_subdirectory(Modules/cjson-module)
|
||||
add_subdirectory(Modules/cpp-symbols-module)
|
||||
|
||||
BIN
Data/system/app/Boot/assets/logo_large.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
Data/system/app/Boot/assets/logo_usb_large.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
Data/system/app/Boot/assets/logo_usb_small.png
Normal file
|
After Width: | Height: | Size: 685 B |
69
Data/system/fonts/MaterialSymbolsRounded.codepoints
Normal file
@ -0,0 +1,69 @@
|
||||
add e145
|
||||
app_registration ef40
|
||||
apps e5c3
|
||||
area_chart e770
|
||||
battery_android_frame_1 f257
|
||||
battery_android_frame_2 f256
|
||||
battery_android_frame_3 f255
|
||||
battery_android_frame_4 f254
|
||||
battery_android_frame_5 f253
|
||||
battery_android_frame_6 f252
|
||||
battery_android_frame_bolt f250
|
||||
battery_android_frame_full f24f
|
||||
bluetooth e1a7
|
||||
bluetooth_connected e1a8
|
||||
bluetooth_disabled e1a9
|
||||
bluetooth_searching e60f
|
||||
cable efe6
|
||||
calendar_month ebcc
|
||||
camera e3af
|
||||
check e668
|
||||
circle ef4a
|
||||
close e5cd
|
||||
cloud f15c
|
||||
delete e92e
|
||||
deployed_code f720
|
||||
devices e326
|
||||
display_settings eb97
|
||||
download f090
|
||||
edit_note e745
|
||||
electric_bolt ec1c
|
||||
folder e2c7
|
||||
forum e8af
|
||||
gamepad e30f
|
||||
help e8fd
|
||||
hub e9f4
|
||||
image e3f4
|
||||
keyboard_alt f028
|
||||
keyboard_arrow_up e316
|
||||
language ea07
|
||||
lightbulb e90f
|
||||
lightstrip fff75
|
||||
lists e9b9
|
||||
location_on f1db
|
||||
logo_dev ead6
|
||||
mail e159
|
||||
memory e322
|
||||
menu e5d2
|
||||
mop e28d
|
||||
more_vert e5d4
|
||||
music_note e405
|
||||
navigation e55d
|
||||
network_wifi_1_bar ebe4
|
||||
network_wifi_2_bar ebd6
|
||||
network_wifi_3_bar ebe1
|
||||
note_add e89c
|
||||
power_settings_new f8c7
|
||||
refresh e5d5
|
||||
sd_card e623
|
||||
sd_card_alert f057
|
||||
search ef7a
|
||||
settings e8b8
|
||||
signal_wifi_0_bar f0b0
|
||||
signal_wifi_4_bar f065
|
||||
signal_wifi_bad f064
|
||||
signal_wifi_off e1da
|
||||
terminal eb8e
|
||||
toolbar e9f7
|
||||
usb e1e0
|
||||
wifi e63e
|
||||
BIN
Data/system/fonts/MaterialSymbolsRounded.ttf
Normal file
@ -1,306 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="512"
|
||||
height="512"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg11"
|
||||
sodipodi:docname="Desktop Icons.svg"
|
||||
xml:space="preserve"
|
||||
inkscape:export-filename="../assets/app_icon_i2c.png"
|
||||
inkscape:export-xdpi="3"
|
||||
inkscape:export-ydpi="3"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><style
|
||||
id="style1"><![CDATA[
|
||||
#keylines circle, #keylines rect {
|
||||
vector-effect: non-scaling-stroke;
|
||||
fill: none;
|
||||
stroke: #0000FF;
|
||||
}
|
||||
#mask path, #mask rect, #mask circle, #hatching path {
|
||||
vector-effect: non-scaling-stroke;
|
||||
fill: none;
|
||||
stroke: #FF0000;
|
||||
}
|
||||
#keylines, #mask {
|
||||
opacity: 0.5;
|
||||
}
|
||||
]]></style><inkscape:_templateinfo><inkscape:_name>Android O icon template</inkscape:_name><inkscape:author>Venceslas DUET</inkscape:author><inkscape:_shortdesc>A collection of keylines and guides used to make adaptive icons for android O</inkscape:_shortdesc><inkscape:date>2021-02-14</inkscape:date><inkscape:_keywords>Android Icon Adaptive Keylines</inkscape:_keywords></inkscape:_templateinfo><defs
|
||||
id="defs1"><inkscape:path-effect
|
||||
effect="bspline"
|
||||
id="path-effect3"
|
||||
is_visible="true"
|
||||
lpeversion="1.3"
|
||||
weight="33.333333"
|
||||
steps="2"
|
||||
helper_size="0"
|
||||
apply_no_weight="true"
|
||||
apply_with_weight="true"
|
||||
only_selected="false"
|
||||
uniform="false" /><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipHatching"><rect
|
||||
width="8"
|
||||
height="8"
|
||||
x="0"
|
||||
y="0"
|
||||
id="rect1" /></clipPath><pattern
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="8"
|
||||
height="8"
|
||||
id="hatching"><path
|
||||
d="M 1,-1 -1,1 M 9,7 7,9 M 9,-1 -1,9"
|
||||
clip-path="url(#clipHatching)"
|
||||
id="path1" /></pattern><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipHatching-2"><rect
|
||||
width="8"
|
||||
height="8"
|
||||
x="0"
|
||||
y="0"
|
||||
id="rect1-7" /></clipPath><pattern
|
||||
patternUnits="userSpaceOnUse"
|
||||
width="8"
|
||||
height="8"
|
||||
id="hatching-0"><path
|
||||
d="M 1,-1 -1,1 M 9,7 7,9 M 9,-1 -1,9"
|
||||
clip-path="url(#clipHatching-2)"
|
||||
id="path1-9" /></pattern><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath17"><rect
|
||||
width="512"
|
||||
height="512"
|
||||
x="0.0081919599"
|
||||
y="0.01246433"
|
||||
ry="128"
|
||||
id="rect17"
|
||||
style="display:inline" /></clipPath><filter
|
||||
style="color-interpolation-filters:sRGB;"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter41"
|
||||
x="-0.04317569"
|
||||
y="-0.040927102"
|
||||
width="1.1013429"
|
||||
height="1.096065"><feFlood
|
||||
result="flood"
|
||||
in="SourceGraphic"
|
||||
flood-opacity="0.29915108267716534"
|
||||
flood-color="rgb(0,0,0)"
|
||||
id="feFlood40" /><feGaussianBlur
|
||||
result="blur"
|
||||
in="SourceGraphic"
|
||||
stdDeviation="6"
|
||||
id="feGaussianBlur40" /><feOffset
|
||||
result="offset"
|
||||
in="blur"
|
||||
dx="5"
|
||||
dy="5"
|
||||
id="feOffset40" /><feComposite
|
||||
result="comp1"
|
||||
operator="in"
|
||||
in="flood"
|
||||
in2="offset"
|
||||
id="feComposite40" /><feComposite
|
||||
result="comp2"
|
||||
operator="over"
|
||||
in="SourceGraphic"
|
||||
in2="comp1"
|
||||
id="feComposite41" /></filter></defs><sodipodi:namedview
|
||||
id="base"
|
||||
inkscape:current-layer="g6"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
showguides="true"
|
||||
inkscape:guide-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:cx="211.14908"
|
||||
inkscape:cy="264.49672"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.1153257"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"><inkscape:grid
|
||||
type="xygrid"
|
||||
empspacing="8"
|
||||
id="grid1"
|
||||
visible="true"
|
||||
originx="0"
|
||||
originy="0"
|
||||
spacingy="1"
|
||||
spacingx="1"
|
||||
units="px" /><sodipodi:guide
|
||||
position="256,256"
|
||||
orientation="0,1"
|
||||
inkscape:locked="false"
|
||||
id="guide1" /><sodipodi:guide
|
||||
position="256,200"
|
||||
orientation="0,1"
|
||||
inkscape:locked="false"
|
||||
id="guide2" /><sodipodi:guide
|
||||
position="256,312"
|
||||
orientation="0,1"
|
||||
inkscape:locked="false"
|
||||
id="guide3" /><sodipodi:guide
|
||||
position="256,256"
|
||||
orientation="1,0"
|
||||
inkscape:locked="false"
|
||||
id="guide4" /><sodipodi:guide
|
||||
position="200,256"
|
||||
orientation="1,0"
|
||||
inkscape:locked="false"
|
||||
id="guide5" /><sodipodi:guide
|
||||
position="312,256"
|
||||
orientation="1,0"
|
||||
inkscape:locked="false"
|
||||
id="guide6" /><sodipodi:guide
|
||||
position="256,256"
|
||||
orientation="1,1"
|
||||
inkscape:locked="false"
|
||||
id="guide7" /><sodipodi:guide
|
||||
position="256,256"
|
||||
orientation="1,-1"
|
||||
inkscape:locked="false"
|
||||
id="guide8" /></sodipodi:namedview><rect
|
||||
style="display:none;opacity:1;fill:#9e9e9e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||
id="Background"
|
||||
width="512"
|
||||
height="512"
|
||||
x="0.02173974"
|
||||
y="0.030385898"
|
||||
clip-path="url(#clipPath17)" /><g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer4"
|
||||
inkscape:label="Icons"><path
|
||||
d="m 212.48429,510.5 h 84.3951 l 18.4617,-74.4906 c 12.30779,-4.1402 23.95604,-9.07563 34.9447,-14.80628 10.98912,-5.73277 21.31888,-13.00885 30.98932,-21.82812 l 72.527,21.16643 L 496,347.78125 441.93403,296.82377 c 1.75966,-11.88934 3.07831,-25.11001 3.95602,-39.66212 0.8777,-14.55209 -0.003,-27.56091 -2.63946,-39.02647 l 52.74749,-50.2704 -42.19787,-72.760837 -71.20854,19.843727 C 372.92125,106.1284 362.3718,98.631997 350.94345,92.458553 339.51504,86.284646 327.64696,80.99297 315.33917,76.583523 L 298.19662,2.5 H 212.4824 l -15.82412,74.083523 c -12.30736,4.409447 -24.17545,9.701123 -35.60426,15.87503 -11.42839,6.173444 -21.97781,13.669847 -31.64825,22.489117 L 58.197238,93.781248 16,167.86478 l 54.0653,50.2704 c -2.637344,12.34739 -3.956016,25.35618 -3.956016,39.02647 0,13.66984 1.318672,26.67847 3.956016,39.02583 L 16,347.78125 58.197238,421.86476 130.72486,399.375 c 9.67003,7.93749 19.9996,14.99297 30.9887,21.16643 10.98911,6.17392 22.63736,11.46556 34.94472,15.87501 z m 43.2372,-158.75 c -26.18744,0 -48.51191,-9.35395 -66.97336,-28.06191 -18.46149,-18.70839 -27.69222,-41.19798 -27.69222,-67.46879 0,-26.27119 9.32399,-48.66724 27.97199,-67.18805 18.64883,-18.52082 41.06634,-27.78125 67.25251,-27.78125 26.18701,0 48.51126,9.35395 66.97272,28.0619 18.4615,18.70836 27.69223,41.19795 27.69223,67.46876 0,26.27122 -9.32399,48.66722 -27.97198,67.18809 C 304.32494,342.48958 281.90766,351.75 255.72149,351.75 Z"
|
||||
id="Settings"
|
||||
style="display:none;fill:#333333;fill-opacity:1;stroke:none;stroke-width:1.3208;stroke-opacity:1"
|
||||
sodipodi:nodetypes="ccccccccsccccccccccccccscccccccscscscscs" /><path
|
||||
d="M 51.047467,512 Q 28.952485,512 14.476063,497.52393 0,483.0475 0,460.95253 V 51.047462 Q 0,28.190328 14.476063,14.095334 28.952485,0 51.047467,0 H 460.95253 Q 483.80967,0 497.90466,14.095334 512,28.190328 512,51.047462 V 460.95253 q 0,22.09497 -14.09534,36.5714 Q 483.80967,512 460.95253,512 Z M 54.857141,127.99998 H 457.14285 V 54.857136 H 54.857141 Z M 457.14285,182.85714 H 54.857141 V 457.14285 H 457.14285 Z M 54.857141,127.99998 v 54.85716 z m 0,0 V 54.857136 Z m 0,54.85716 v 274.28571 z"
|
||||
id="Fallback"
|
||||
style="display:none;fill:#333333;fill-opacity:1;stroke-width:0.761904" /><g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer5"
|
||||
inkscape:label="System Info"
|
||||
style="display:none"><path
|
||||
d="m 207.3461,368.61149 c 4.24475,0 8.2964,-1.10767 12.15495,-3.323 3.85898,-2.21507 6.79308,-5.34536 8.80231,-9.39085 l 74.74094,-136.81458 26.5827,49.17833 c 1.7715,4.4304 4.54058,7.64247 8.30725,9.63617 3.76584,1.9937 8.08555,2.99055 12.95914,2.99055 H 510.3904 V 233.03904 H 365.51384 L 324.31067,157.9424 c -2.21531,-4.43039 -5.25245,-7.64244 -9.11143,-9.63616 -3.85853,-1.99263 -7.91018,-2.98895 -12.15494,-2.98895 -4.24475,0 -8.2964,0.99632 -12.15496,2.98895 -3.85897,1.99266 -6.8961,5.20471 -9.11139,9.63616 L 207.3461,294.84409 180.49221,245.81347 c -2.03515,-4.0859 -4.9819,-7.23639 -8.84026,-9.45148 -3.85856,-2.2153 -7.9102,-3.32295 -12.15494,-3.32295 H 0 v 47.84907 h 144.87656 l 41.5177,75.09183 c 2.00541,3.99063 4.93771,7.09348 8.7969,9.30855 3.85853,2.21533 7.91018,3.323 12.15494,3.323 z"
|
||||
style="fill:#0d00be;fill-opacity:1;stroke-width:0.664569"
|
||||
id="path1-8" /><path
|
||||
d="m 0.48281402,192.83596 v -79.74848 c 0,-14.177585 4.99842408,-25.696778 14.99527098,-34.557575 C 25.47472,69.668868 37.492176,65.238358 51.530467,65.238358 H 459.91073 c 14.03785,0 26.04106,4.43051 36.00962,13.291547 9.96858,8.860797 14.95286,20.37999 14.95286,34.557575 v 79.74848 H 463.02408 V 113.08748 H 48.331914 v 79.74848 z M 51.445288,448.03116 c -14.037857,0 -26.041069,-4.43051 -36.009631,-13.29154 -9.9685626,-8.8608 -14.95284298,-20.38 -14.95284298,-34.55758 V 320.43356 H 48.331914 v 79.74848 H 463.02408 v -79.74848 h 47.84913 v 79.74848 c 0,14.17758 -4.99842,25.69678 -14.99528,34.55758 -9.99707,8.86103 -22.01453,13.29154 -36.0524,13.29154 z"
|
||||
id="System_Info"
|
||||
style="display:inline;fill:#333333;fill-opacity:1;stroke-width:0.664569"
|
||||
sodipodi:nodetypes="cscsscsccccccscsccccccssss" /></g><g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
inkscape:label="Power_Settings"
|
||||
style="display:none"><rect
|
||||
style="display:inline;fill:#000000;fill-opacity:1;stroke-width:0.8862;paint-order:stroke fill markers"
|
||||
id="rect4"
|
||||
width="346.68829"
|
||||
height="197.63705"
|
||||
x="65.055527"
|
||||
y="164.69754"
|
||||
inkscape:label="Power_at_100" /><rect
|
||||
style="display:none;fill:#000000;fill-opacity:1;stroke-width:0.8862;paint-order:stroke fill markers"
|
||||
id="rect16"
|
||||
width="277"
|
||||
height="197.63705"
|
||||
x="65.055527"
|
||||
y="164.69754"
|
||||
inkscape:label="Power_at_080" /><rect
|
||||
style="display:none;fill:#000000;fill-opacity:1;stroke-width:0.8862;paint-order:stroke fill markers"
|
||||
id="rect18"
|
||||
width="208"
|
||||
height="197.63705"
|
||||
x="65.055527"
|
||||
y="164.69754"
|
||||
inkscape:label="Power_at_060" /><rect
|
||||
style="display:none;fill:#000000;fill-opacity:1;stroke-width:0.8862;paint-order:stroke fill markers"
|
||||
id="rect19"
|
||||
width="139"
|
||||
height="197.63705"
|
||||
x="65.055527"
|
||||
y="164.69754"
|
||||
inkscape:label="Power_at_040" /><rect
|
||||
style="display:none;fill:#000000;fill-opacity:1;stroke-width:0.8862;paint-order:stroke fill markers"
|
||||
id="rect20"
|
||||
width="70"
|
||||
height="197.63705"
|
||||
x="65.055527"
|
||||
y="164.69754"
|
||||
inkscape:label="Power_at_020" /><g
|
||||
id="g3"
|
||||
inkscape:label="Shared"
|
||||
transform="translate(0.41209841,0.82986943)"><path
|
||||
id="rect15"
|
||||
style="fill:#000000;paint-order:stroke fill markers"
|
||||
d="M 1.6464844,104.58203 V 417.50781 H 475.15234 V 104.58203 Z M 23.880859,125.99414 H 451.27148 v 269.2793 H 23.880859 Z"
|
||||
transform="translate(-0.41209841,-0.82986943)" /><rect
|
||||
style="display:inline;fill:#000000;paint-order:stroke fill markers"
|
||||
id="rect2-7"
|
||||
width="39.527409"
|
||||
height="113.6413"
|
||||
x="471.03497"
|
||||
y="201.28403" /></g></g><path
|
||||
d="m 128.42457,319.70188 h 31.96224 V 223.81515 H 128.42457 V 255.7774 H 96.462329 v 31.96225 h 31.962241 z m 63.92449,-31.96223 H 416.08475 V 255.7774 H 192.34906 Z m 159.81121,-63.9245 h 31.96224 v -31.96223 h 31.96224 V 159.89067 H 384.12251 V 127.92843 H 352.16027 Z M 96.462329,191.85292 H 320.19802 V 159.89067 H 96.462329 Z m 63.924481,287.66019 v -63.9245 H 48.518961 q -19.776638,0 -33.860161,-14.09119 Q 0.575596,387.40591 0.575596,367.61871 V 79.796797 q 0,-19.788137 14.083204,-33.77162 Q 28.742323,32.041695 48.518961,32.041695 H 464.02812 q 19.77663,0 33.85984,14.091514 14.08352,14.091196 14.08352,33.878376 V 367.83383 q 0,19.78783 -14.08352,33.7713 -14.08321,13.98348 -33.85984,13.98348 H 352.16027 v 63.9245 z M 48.518961,367.64525 H 464.02812 V 79.98506 H 48.518961 Z m 0,0 V 79.98506 Z"
|
||||
id="Display_Settings"
|
||||
style="display:none;fill:#333333;fill-opacity:1;stroke-width:0.665879" /><path
|
||||
d="m 47.867683,447.55649 q -19.326766,0 -33.654939,-14.32849 Q -0.1157482,418.89983 -0.1157482,399.57306 V 111.67248 q 0,-19.793166 14.3284922,-33.888136 Q 28.540917,63.689052 47.867683,63.689052 H 191.81797 l 63.97791,63.977908 h 207.9282 q 19.79316,0 33.88814,14.09529 14.09529,14.09497 14.09529,33.88814 v 223.92267 q 0,19.32677 -14.09529,33.65494 -14.09498,14.32849 -33.88814,14.32849 z m 0,-47.98343 H 463.72408 V 175.65039 h -227.9213 l -63.9779,-63.97791 H 47.867683 Z m 0,0 V 111.67248 Z"
|
||||
id="Files"
|
||||
style="display:none;fill:#333333;fill-opacity:1;stroke-width:0.666435" /><g
|
||||
id="g6"
|
||||
inkscape:label="I2C"><path
|
||||
style="fill:#333333;stroke-width:1.10895;paint-order:stroke fill markers"
|
||||
d="M 1.1034012,386.61813 V 121.14457 H 512.16486 V 388.6067 H 406.77087 v -75.56551 h 27.83993 v 44.74274 h 51.70271 V 146.99592 H 28.943326 V 360.76677 H 82.634608 V 313.04119 H 106.4974 v 75.56551 z"
|
||||
id="path4"
|
||||
inkscape:label="housing" /><circle
|
||||
style="fill:#1a1a1a;stroke-width:1.68907;paint-order:stroke fill markers"
|
||||
id="path5"
|
||||
cx="416.15726"
|
||||
cy="233.15318"
|
||||
inkscape:label="pin4"
|
||||
r="36.346325" /><circle
|
||||
style="fill:#800000;stroke-width:1.68909;paint-order:stroke fill markers"
|
||||
id="path5-62"
|
||||
cx="311.80624"
|
||||
cy="232.68082"
|
||||
inkscape:label="pin3"
|
||||
r="36.346325" /><circle
|
||||
style="fill:#aa8800;stroke-width:1.68907;paint-order:stroke fill markers"
|
||||
id="path5-6"
|
||||
cx="201.49115"
|
||||
cy="232.88933"
|
||||
inkscape:label="pin2"
|
||||
r="36.346325" /><circle
|
||||
style="fill:#000080;stroke-width:1.68907;paint-order:stroke fill markers"
|
||||
id="path5-9"
|
||||
cx="94.05365"
|
||||
cy="233.18108"
|
||||
inkscape:label="pin1"
|
||||
r="36.346325" /></g></g><style
|
||||
id="style1-9"><![CDATA[
|
||||
#keylines circle, #keylines rect {
|
||||
vector-effect: non-scaling-stroke;
|
||||
fill: none;
|
||||
stroke: #0000FF;
|
||||
}
|
||||
#mask path, #mask rect, #mask circle, #hatching path {
|
||||
vector-effect: non-scaling-stroke;
|
||||
fill: none;
|
||||
stroke: #FF0000;
|
||||
}
|
||||
#keylines, #mask {
|
||||
opacity: 0.5;
|
||||
}
|
||||
]]></style></svg>
|
||||
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 446 B |
|
Before Width: | Height: | Size: 522 B |
|
Before Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 240 B |
|
Before Width: | Height: | Size: 286 B |
|
Before Width: | Height: | Size: 403 B |
|
Before Width: | Height: | Size: 429 B |
|
Before Width: | Height: | Size: 491 B |
|
Before Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 535 B |
|
Before Width: | Height: | Size: 436 B |
|
Before Width: | Height: | Size: 392 B |
@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="apps.svg"
|
||||
inkscape:export-filename="icon_apps.png"
|
||||
inkscape:export-xdpi="160"
|
||||
inkscape:export-ydpi="160"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="17.088414"
|
||||
inkscape:cx="14.600536"
|
||||
inkscape:cy="18.960215"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="933"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m 122.37644,-3.1685927 q -49.173234,0 -84.190541,-35.0173063 -35.0173063,-35.017307 -35.0173063,-84.190541 0,-49.17324 35.0173063,-84.19055 35.017307,-35.01731 84.190541,-35.01731 49.17324,0 84.19055,35.01731 35.01731,35.01731 35.01731,84.19055 0,49.173234 -35.01731,84.190541 -35.01731,35.0173063 -84.19055,35.0173063 z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173063 -35.0173,-35.017307 -35.0173,-84.190541 0,-49.17324 35.0173,-84.19055 35.01731,-35.01731 84.19055,-35.01731 49.17324,0 84.19055,35.01731 35.0173,35.01731 35.0173,84.19055 0,49.173234 -35.0173,84.190541 Q 529.17324,-3.1685927 480,-3.1685927 Z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173063 -35.01731,-35.017307 -35.01731,-84.190541 0,-49.17324 35.01731,-84.19055 35.01731,-35.01731 84.19055,-35.01731 49.17323,0 84.19054,35.01731 35.01731,35.01731 35.01731,84.19055 0,49.173234 -35.01731,84.190541 -35.01731,35.0173063 -84.19054,35.0173063 z M 122.37644,-360.79215 q -49.173234,0 -84.190541,-35.0173 Q 3.1685927,-430.82676 3.1685927,-480 q 0,-49.17324 35.0173063,-84.19055 35.017307,-35.0173 84.190541,-35.0173 49.17324,0 84.19055,35.0173 35.01731,35.01731 35.01731,84.19055 0,49.17324 -35.01731,84.19055 -35.01731,35.0173 -84.19055,35.0173 z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173 -35.0173,-35.01731 -35.0173,-84.19055 0,-49.17324 35.0173,-84.19055 35.01731,-35.0173 84.19055,-35.0173 49.17324,0 84.19055,35.0173 35.0173,35.01731 35.0173,84.19055 0,49.17324 -35.0173,84.19055 -35.01731,35.0173 -84.19055,35.0173 z m 357.62356,0 q -49.17324,0 -84.19055,-35.0173 Q 718.4157,-430.82676 718.4157,-480 q 0,-49.17324 35.01731,-84.19055 35.01731,-35.0173 84.19055,-35.0173 49.17323,0 84.19054,35.0173 35.01731,35.01731 35.01731,84.19055 0,49.17324 -35.01731,84.19055 -35.01731,35.0173 -84.19054,35.0173 z M 122.37644,-718.4157 q -49.173234,0 -84.190541,-35.01731 -35.0173063,-35.01731 -35.0173063,-84.19055 0,-49.17323 35.0173063,-84.19054 35.017307,-35.01731 84.190541,-35.01731 49.17324,0 84.19055,35.01731 35.01731,35.01731 35.01731,84.19054 0,49.17324 -35.01731,84.19055 -35.01731,35.01731 -84.19055,35.01731 z m 357.62356,0 q -49.17324,0 -84.19055,-35.01731 -35.0173,-35.01731 -35.0173,-84.19055 0,-49.17323 35.0173,-84.19054 35.01731,-35.01731 84.19055,-35.01731 49.17324,0 84.19055,35.01731 35.0173,35.01731 35.0173,84.19054 0,49.17324 -35.0173,84.19055 Q 529.17324,-718.4157 480,-718.4157 Z m 357.62356,0 q -49.17324,0 -84.19055,-35.01731 -35.01731,-35.01731 -35.01731,-84.19055 0,-49.17323 35.01731,-84.19054 35.01731,-35.01731 84.19055,-35.01731 49.17323,0 84.19054,35.01731 35.01731,35.01731 35.01731,84.19054 0,49.17324 -35.01731,84.19055 -35.01731,35.01731 -84.19054,35.01731 z"
|
||||
id="path1"
|
||||
style="stroke-width:1.4901" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.8 KiB |
@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="folder.svg"
|
||||
inkscape:export-filename="icon_files.png"
|
||||
inkscape:export-xdpi="160"
|
||||
inkscape:export-ydpi="160"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="48.333333"
|
||||
inkscape:cx="11.989655"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="933"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m 96.441379,-96.441379 q -39.554482,0 -67.722069,-28.167591 -28.16758586,-28.16758 -28.16758586,-67.72206 v -575.33794 q 0,-39.55448 28.16758586,-67.72206 28.167587,-28.16759 67.722069,-28.16759 H 384.11034 L 480,-767.66897 h 383.55862 q 39.55448,0 67.72207,28.16759 28.16759,28.16759 28.16759,67.72207 v 479.44828 q 0,39.55448 -28.16759,67.72206 -28.16759,28.167591 -67.72207,28.167591 z m 0,-95.889651 H 863.55862 v -479.44828 h -423.1131 l -95.88966,-95.88966 H 96.441379 Z m 0,0 v -575.33794 z"
|
||||
id="path1"
|
||||
style="stroke-width:1.19862" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="settings.svg"
|
||||
inkscape:export-filename="icon_settings.png"
|
||||
inkscape:export-xdpi="160"
|
||||
inkscape:export-ydpi="160"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="48.333333"
|
||||
inkscape:cx="11.989655"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="933"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M 347.80751,0.69994853 328.57952,-153.12403 q -15.62275,-6.00875 -29.44288,-14.421 -13.82012,-8.41225 -27.03937,-18.02625 L 129.08904,-125.48379 -3.1034483,-353.81626 120.67679,-447.55275 q -1.20175,-8.41225 -1.20175,-16.22363 v -32.44724 q 0,-7.81138 1.20175,-16.22363 L -3.1034483,-606.18374 129.08904,-834.51621 l 143.00823,60.08749 q 13.21925,-9.614 27.64025,-18.02625 14.421,-8.41225 28.842,-14.421 l 19.22799,-153.82398 h 264.38498 l 19.22799,153.82398 q 15.62275,6.00875 29.44288,14.421 13.82012,8.41225 27.03937,18.02625 l 143.00823,-60.08749 132.19249,228.33247 -123.78024,93.73649 q 1.20175,8.41225 1.20175,16.22363 v 32.44724 q 0,7.81138 -2.4035,16.22363 l 123.78024,93.73649 -132.19249,228.33247 -141.80648,-60.08749 q -13.21925,9.614 -27.64025,18.02625 -14.421,8.41225 -28.842,14.421 L 612.19249,0.69994853 Z m 84.1225,-96.13998953 h 94.93823 l 16.8245,-127.385489 q 37.25425,-9.614 69.10062,-28.24112 31.84637,-18.62712 58.28487,-45.06562 l 118.97324,49.27175 46.86824,-81.719 -103.35049,-78.11374 q 6.00875,-16.8245 8.41225,-35.45162 2.4035,-18.62712 2.4035,-37.85512 0,-19.228 -2.4035,-37.85512 -2.4035,-18.62712 -8.41225,-35.45162 l 103.35049,-78.11374 -46.86824,-81.719 -118.97324,50.4735 q -26.4385,-27.64025 -58.28487,-46.26737 -31.84637,-18.62712 -69.10062,-28.24112 l -15.62275,-127.38549 h -94.93823 l -16.8245,127.38549 q -37.25425,9.614 -69.10062,28.24112 -31.84637,18.62712 -58.28487,45.06562 l -118.97324,-49.27175 -46.86824,81.719 103.35049,76.91199 q -6.00875,18.02625 -8.41225,36.05249 -2.4035,18.02625 -2.4035,38.456 0,19.228 2.4035,37.25425 2.4035,18.02624 8.41225,36.05249 l -103.35049,78.11374 46.86824,81.719 118.97324,-50.4735 q 26.4385,27.64025 58.28487,46.26737 31.84637,18.62712 69.10062,28.24112 z M 482.4035,-311.75502 q 69.70149,0 118.97324,-49.27174 49.27174,-49.27175 49.27174,-118.97324 0,-69.70149 -49.27174,-118.97324 -49.27175,-49.27174 -118.97324,-49.27174 -70.90324,0 -119.57411,49.27174 -48.67087,49.27175 -48.67087,118.97324 0,69.70149 48.67087,118.97324 48.67087,49.27174 119.57411,49.27174 z M 480,-480 Z"
|
||||
id="path1"
|
||||
style="stroke-width:1.20175" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.2 KiB |
@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="search.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
inkscape:export-filename="search.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="47.5"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="10.989474"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1368"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M 884.20117,0.42105034 548.79064,-334.98948 q -39.92982,31.94386 -91.8386,50.57778 -51.90877,18.63392 -110.47251,18.63392 -145.07836,0 -245.56842,-100.49006 Q 0.42105263,-466.7579 0.42105263,-611.83626 q 0,-145.07836 100.49005737,-245.56842 100.49006,-100.49006 245.56842,-100.49006 145.07836,0 245.56842,100.49006 100.49006,100.49006 100.49006,245.56842 0,58.56374 -18.63392,110.47251 -18.63392,51.90878 -50.57778,91.8386 L 958.73684,-74.114622 Z M 346.47953,-372.25731 q 99.82456,0 169.70175,-69.8772 69.8772,-69.87719 69.8772,-169.70175 0,-99.82456 -69.8772,-169.70175 -69.87719,-69.8772 -169.70175,-69.8772 -99.82456,0 -169.70176,69.8772 -69.87718,69.87719 -69.87718,169.70175 0,99.82456 69.87718,169.70175 69.8772,69.8772 169.70176,69.8772 z"
|
||||
id="path1"
|
||||
style="stroke-width:1.33099" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.9 KiB |
@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="wifi_lock_black.svg"
|
||||
inkscape:export-filename="wifi_lock_black.png"
|
||||
inkscape:export-xdpi="64"
|
||||
inkscape:export-ydpi="64"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="27.5"
|
||||
inkscape:cx="12.054545"
|
||||
inkscape:cy="12.072727"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M240-80q-33 0-56.5-23.5T160-160v-400q0-33 23.5-56.5T240-640h40v-80q0-83 58.5-141.5T480-920q83 0 141.5 58.5T680-720v80h40q33 0 56.5 23.5T800-560v400q0 33-23.5 56.5T720-80H240Zm0-80h480v-400H240v400Zm240-120q33 0 56.5-23.5T560-360q0-33-23.5-56.5T480-440q-33 0-56.5 23.5T400-360q0 33 23.5 56.5T480-280ZM360-640h240v-80q0-50-35-85t-85-35q-50 0-85 35t-35 85v80ZM240-160v-400 400Z"
|
||||
id="path1"
|
||||
style="fill:#000000" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="wifi_signal_medium_black.svg"
|
||||
inkscape:export-filename="../assets/wifi_signal_medium_black.png"
|
||||
inkscape:export-xdpi="64"
|
||||
inkscape:export-ydpi="64"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="27.5"
|
||||
inkscape:cx="10.563636"
|
||||
inkscape:cy="12.072727"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m 480,-120 c -28,0 -51.66667,-9.66667 -71,-29 -19.33333,-19.33333 -29,-43 -29,-71 0,-28 9.66667,-51.66667 29,-71 19.33333,-19.33333 43,-29 71,-29 28,0 51.66667,9.66667 71,29 19.33333,19.33333 29,43 29,71 0,28 -9.66667,51.66667 -29,71 -19.33333,19.33333 -43,29 -71,29 z"
|
||||
id="path1"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
d="m 254,-346 -84,-86 c 39.33333,-39.33333 85.5,-70.5 138.5,-93.5 53,-23 110.16667,-34.5 171.5,-34.5 61.33333,0 118.5,11.66667 171.5,35 53,23.33333 99.16667,55 138.5,95 l -84,84 c -29.33333,-29.33333 -63.33333,-52.33333 -102,-69 -38.66667,-16.66667 -80,-25 -124,-25 -44,0 -85.33333,8.33333 -124,25 -38.66667,16.66667 -72.66667,39.66667 -102,69 z"
|
||||
id="path2"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
d="M 84,-516 0,-600 c 61.333333,-62.66667 133,-111.66667 215,-147 82,-35.33333 170.33333,-53 265,-53 94.66667,0 183,17.66667 265,53 82,35.33333 153.66667,84.33333 215,147 l -84,84 C 824.66667,-567.33333 765.16667,-607.5 697.5,-636.5 629.83333,-665.5 557.33333,-680 480,-680 c -77.33333,0 -149.83333,14.5 -217.5,43.5 -67.66667,29 -127.16667,69.16667 -178.5,120.5 z"
|
||||
id="path3"
|
||||
style="fill:#b3b3b3" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="wifi_signal_strong_black.svg"
|
||||
inkscape:export-filename="wifi_signal_full_black.png"
|
||||
inkscape:export-xdpi="64"
|
||||
inkscape:export-ydpi="64"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="27.5"
|
||||
inkscape:cx="10.563636"
|
||||
inkscape:cy="12.072727"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m 480,-120 c -28,0 -51.66667,-9.66667 -71,-29 -19.33333,-19.33333 -29,-43 -29,-71 0,-28 9.66667,-51.66667 29,-71 19.33333,-19.33333 43,-29 71,-29 28,0 51.66667,9.66667 71,29 19.33333,19.33333 29,43 29,71 0,28 -9.66667,51.66667 -29,71 -19.33333,19.33333 -43,29 -71,29 z"
|
||||
id="path1"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
d="m 254,-346 -84,-86 c 39.33333,-39.33333 85.5,-70.5 138.5,-93.5 53,-23 110.16667,-34.5 171.5,-34.5 61.33333,0 118.5,11.66667 171.5,35 53,23.33333 99.16667,55 138.5,95 l -84,84 c -29.33333,-29.33333 -63.33333,-52.33333 -102,-69 -38.66667,-16.66667 -80,-25 -124,-25 -44,0 -85.33333,8.33333 -124,25 -38.66667,16.66667 -72.66667,39.66667 -102,69 z"
|
||||
id="path2"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
d="M 84,-516 0,-600 c 61.333333,-62.66667 133,-111.66667 215,-147 82,-35.33333 170.33333,-53 265,-53 94.66667,0 183,17.66667 265,53 82,35.33333 153.66667,84.33333 215,147 l -84,84 C 824.66667,-567.33333 765.16667,-607.5 697.5,-636.5 629.83333,-665.5 557.33333,-680 480,-680 c -77.33333,0 -149.83333,14.5 -217.5,43.5 -67.66667,29 -127.16667,69.16667 -178.5,120.5 z"
|
||||
id="path3"
|
||||
style="fill:#000000" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="wifi_signal_weak_black.svg"
|
||||
inkscape:export-filename="../assets/wifi_signal_weak_black.png"
|
||||
inkscape:export-xdpi="64"
|
||||
inkscape:export-ydpi="64"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="27.5"
|
||||
inkscape:cx="10.563636"
|
||||
inkscape:cy="12.072727"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m 480,-120 c -28,0 -51.66667,-9.66667 -71,-29 -19.33333,-19.33333 -29,-43 -29,-71 0,-28 9.66667,-51.66667 29,-71 19.33333,-19.33333 43,-29 71,-29 28,0 51.66667,9.66667 71,29 19.33333,19.33333 29,43 29,71 0,28 -9.66667,51.66667 -29,71 -19.33333,19.33333 -43,29 -71,29 z"
|
||||
id="path1"
|
||||
style="fill:#000000" />
|
||||
<path
|
||||
d="m 254,-346 -84,-86 c 39.33333,-39.33333 85.5,-70.5 138.5,-93.5 53,-23 110.16667,-34.5 171.5,-34.5 61.33333,0 118.5,11.66667 171.5,35 53,23.33333 99.16667,55 138.5,95 l -84,84 c -29.33333,-29.33333 -63.33333,-52.33333 -102,-69 -38.66667,-16.66667 -80,-25 -124,-25 -44,0 -85.33333,8.33333 -124,25 -38.66667,16.66667 -72.66667,39.66667 -102,69 z"
|
||||
id="path2"
|
||||
style="fill:#b3b3b3" />
|
||||
<path
|
||||
d="M 84,-516 0,-600 c 61.333333,-62.66667 133,-111.66667 215,-147 82,-35.33333 170.33333,-53 265,-53 94.66667,0 183,17.66667 265,53 82,35.33333 153.66667,84.33333 215,147 l -84,84 C 824.66667,-567.33333 765.16667,-607.5 697.5,-636.5 629.83333,-665.5 557.33333,-680 480,-680 c -77.33333,0 -149.83333,14.5 -217.5,43.5 -67.66667,29 -127.16667,69.16667 -178.5,120.5 z"
|
||||
id="path3"
|
||||
style="fill:#b3b3b3" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- License: Apache. Made by Iconscout: https://github.com/Iconscout/unicons -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" data-name="Layer 1"><path d="M12.71,17.29a1,1,0,0,0-.16-.12.56.56,0,0,0-.17-.09.6.6,0,0,0-.19-.06.93.93,0,0,0-.57.06.9.9,0,0,0-.54.54A.84.84,0,0,0,11,18a1,1,0,0,0,.07.38,1.46,1.46,0,0,0,.22.33A1,1,0,0,0,12,19a.84.84,0,0,0,.38-.08,1.15,1.15,0,0,0,.33-.21A1,1,0,0,0,13,18a1,1,0,0,0-.08-.38A1,1,0,0,0,12.71,17.29ZM8.55,13.17a.56.56,0,0,0-.17-.09A.6.6,0,0,0,8.19,13a.86.86,0,0,0-.39,0l-.18.06-.18.09-.15.12A1.05,1.05,0,0,0,7,14a1,1,0,0,0,.29.71,1.15,1.15,0,0,0,.33.21A1,1,0,0,0,9,14a1.05,1.05,0,0,0-.29-.71Zm.16,4.12a1,1,0,0,0-.33-.21A1,1,0,0,0,7.8,17l-.18.06a.76.76,0,0,0-.18.09,1.58,1.58,0,0,0-.15.12,1,1,0,0,0-.21.33.94.94,0,0,0,0,.76,1.15,1.15,0,0,0,.21.33A1,1,0,0,0,8,19a.84.84,0,0,0,.38-.08,1.15,1.15,0,0,0,.33-.21,1.15,1.15,0,0,0,.21-.33.94.94,0,0,0,0-.76A1,1,0,0,0,8.71,17.29Zm2.91-4.21a1,1,0,0,0-.33.21A1.05,1.05,0,0,0,11,14a1,1,0,0,0,1.38.92,1.15,1.15,0,0,0,.33-.21A1,1,0,0,0,13,14a1.05,1.05,0,0,0-.29-.71A1,1,0,0,0,11.62,13.08Zm5.09,4.21a1.15,1.15,0,0,0-.33-.21,1,1,0,0,0-1.09.21,1,1,0,0,0-.21.33.94.94,0,0,0,0,.76,1.15,1.15,0,0,0,.21.33A1,1,0,0,0,16,19a.84.84,0,0,0,.38-.08,1.15,1.15,0,0,0,.33-.21,1,1,0,0,0,.21-1.09A1,1,0,0,0,16.71,17.29ZM16,5H8A1,1,0,0,0,7,6v4a1,1,0,0,0,1,1h8a1,1,0,0,0,1-1V6A1,1,0,0,0,16,5ZM15,9H9V7h6Zm3-8H6A3,3,0,0,0,3,4V20a3,3,0,0,0,3,3H18a3,3,0,0,0,3-3V4A3,3,0,0,0,18,1Zm1,19a1,1,0,0,1-1,1H6a1,1,0,0,1-1-1V4A1,1,0,0,1,6,3H18a1,1,0,0,1,1,1Zm-2.45-6.83a.56.56,0,0,0-.17-.09.6.6,0,0,0-.19-.06.86.86,0,0,0-.39,0l-.18.06-.18.09-.15.12A1.05,1.05,0,0,0,15,14a1,1,0,0,0,1.38.92,1.15,1.15,0,0,0,.33-.21A1,1,0,0,0,17,14a1.05,1.05,0,0,0-.29-.71Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- License: PD. Made by Mary Akveo: https://maryakveo.com/ -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" id="chat" data-name="Line Color" xmlns="http://www.w3.org/2000/svg" class="icon line-color"><path id="primary" d="M18.81,16.23,20,21l-4.95-2.48A9.84,9.84,0,0,1,12,19c-5,0-9-3.58-9-8s4-8,9-8,9,3.58,9,8A7.49,7.49,0,0,1,18.81,16.23Z" style="fill: none; stroke: #000000; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path></svg>
|
||||
|
Before Width: | Height: | Size: 518 B |
@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- License: PD. Made by Mary Akveo: https://maryakveo.com/ -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 24 24" id="chip" data-name="Flat Line" xmlns="http://www.w3.org/2000/svg" class="icon flat-line"><rect id="secondary" x="6" y="6" width="12" height="12" rx="1" style="fill: none; stroke-width: 2;"></rect><path id="primary" d="M12,6V3m4,3V4M8,6V4m10,8h3m-3,4h2M18,8h2M12,18v3M8,18v2m8-2v2M6,12H3M6,8H4m2,8H4m14,1V7a1,1,0,0,0-1-1H7A1,1,0,0,0,6,7V17a1,1,0,0,0,1,1H17A1,1,0,0,0,18,17Z" style="fill: none; stroke: #333333; stroke-linecap: round; stroke-linejoin: round; stroke-width: 2;"></path></svg>
|
||||
|
Before Width: | Height: | Size: 662 B |
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- License: MIT. Made by vmware: https://github.com/vmware/clarity-assets -->
|
||||
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 36 36" version="1.1" preserveAspectRatio="xMidYMid meet" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<title>note-line</title>
|
||||
<path d="M28,30H6V8H19.22l2-2H6A2,2,0,0,0,4,8V30a2,2,0,0,0,2,2H28a2,2,0,0,0,2-2V15l-2,2Z" class="clr-i-outline clr-i-outline-path-1"></path><path d="M33.53,5.84,30.16,2.47a1.61,1.61,0,0,0-2.28,0L14.17,16.26l-1.11,4.81A1.61,1.61,0,0,0,14.63,23,1.69,1.69,0,0,0,15,23l4.85-1.07L33.53,8.12A1.61,1.61,0,0,0,33.53,5.84ZM18.81,20.08l-3.66.81L16,17.26,26.32,6.87l2.82,2.82ZM30.27,8.56,27.45,5.74,29,4.16,31.84,7Z" class="clr-i-outline clr-i-outline-path-2"></path>
|
||||
<rect x="0" y="0" width="36" height="36" fill-opacity="0"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 878 B |
@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
viewBox="0 -960 640 640"
|
||||
width="16"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="calendar_clock_24dp_E8EAED_FILL0_wght400_GRAD0_opsz24.svg"
|
||||
inkscape:export-filename="calendar_clock_24dp_E8EAED_FILL0_wght400_GRAD0_opsz24.png"
|
||||
inkscape:export-xdpi="96.18351"
|
||||
inkscape:export-ydpi="96.18351"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="47.5"
|
||||
inkscape:cx="10.842105"
|
||||
inkscape:cy="10.821053"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1368"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M 61.257143,-775.80752 H 487.10977 v -60.83609 H 61.257143 Z m 0,0 v -60.83609 z m 0,425.85263 q -25.094888,0 -42.965489,-17.8706 -17.870601,-17.8706 -17.870601,-42.96549 v -425.85263 q 0,-25.09489 17.870601,-42.96549 17.870601,-17.8706 42.965489,-17.8706 H 91.67518 v -60.83609 h 60.83609 v 60.83609 h 243.34437 v -60.83609 h 60.83609 v 60.83609 h 30.41804 q 25.09489,0 42.96549,17.8706 17.8706,17.8706 17.8706,42.96549 v 172.62241 q -14.44857,-6.84406 -29.6576,-11.40677 -15.20901,-4.56271 -31.17849,-6.84406 v -32.6994 H 61.257143 v 304.18045 H 252.89083 q 5.32315,16.72993 12.54744,31.93895 7.22428,15.20902 17.11015,28.89714 z m 395.434587,30.41805 q -63.11745,0 -107.60384,-44.48639 -44.48639,-44.4864 -44.48639,-107.60384 0,-63.11744 44.48639,-107.60384 44.48639,-44.48638 107.60384,-44.48638 63.11744,0 107.60383,44.48638 44.48639,44.4864 44.48639,107.60384 0,63.11744 -44.48639,107.60384 -44.48639,44.48639 -107.60383,44.48639 z m 50.95022,-79.84737 21.29264,-21.29263 -57.03384,-57.03384 V -562.8812 H 441.4827 v 97.33774 z"
|
||||
id="path1"
|
||||
style="stroke-width:0.760454;fill:#000000" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
@ -1,42 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
viewBox="0 -960 640 640"
|
||||
width="16"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="location.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
inkscape:export-filename="location.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="47.5"
|
||||
inkscape:cx="12"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1368"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M 347.36841,-361.6842 259.36842,-579.36842 41.684211,-667.36841 V -710.5965 L 597.47368,-917.47368 390.59649,-361.6842 Z m 20.07018,-114.24563 125.05263,-336.5614 -336.5614,125.05264 151.29825,60.21052 z m -60.21052,-151.29824 z"
|
||||
id="path1"
|
||||
style="stroke-width:0.771924" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.4 KiB |
@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="20"
|
||||
viewBox="0 -960 960 960"
|
||||
width="20"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="wifi_off_white.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
inkscape:export-filename="../assets/wifi_off_white.png"
|
||||
inkscape:export-xdpi="76.800003"
|
||||
inkscape:export-ydpi="76.800003"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="33"
|
||||
inkscape:cx="10.045455"
|
||||
inkscape:cy="10"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m699-363-51-51 206-205q-81-61-176-93t-198-32q-36.667 0-73.333 4.5Q370-735 335-727l-59-59q48-15 99.241-22.5T480-816q140 0 263 51t217 141L699-363ZM480-246l66-66-359-359q-21 11-41 24.5T106-619l374 373ZM813-45 597-261 480-144 0-624q30-29 63.533-54.224Q97.065-703.447 134-724l-89-89 51-51L864-96l-51 51ZM492-571Zm-125 79Z"
|
||||
id="path1"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,41 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="20"
|
||||
viewBox="0 -960 960 960"
|
||||
width="20"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="wifi_scan_white.svg"
|
||||
inkscape:export-filename="../assets/wifi_scan_white.png"
|
||||
inkscape:export-xdpi="76.800003"
|
||||
inkscape:export-ydpi="76.800003"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="33"
|
||||
inkscape:cx="10.015152"
|
||||
inkscape:cy="10"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="M480-144 0-624q97-94 221-143t259-49q135 0 259 49t221 143l-51 52q-86-85-197-128.5T480-744q-102 0-197.5 32T106-619l427 422-53 53Zm381-48L745-308q-17 10-35 15t-38 5q-60 0-102-42t-42-102q0-60 42-102t102-42q60 0 102 42t42 102q0 20-4.5 38.5T796-359l116 116-51 51ZM672-360q30 0 51-21t21-51q0-30-21-51t-51-21q-30 0-51 21t-21 51q0 30 21 51t51 21ZM480-196Z"
|
||||
id="path1"
|
||||
style="fill:#ffffff" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB |
@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="wifi_signal_medium_white.svg"
|
||||
inkscape:export-filename="../assets/wifi_signal_medium_white.png"
|
||||
inkscape:export-xdpi="64"
|
||||
inkscape:export-ydpi="64"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="27.5"
|
||||
inkscape:cx="10.563636"
|
||||
inkscape:cy="12.072727"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m 480,-120 c -28,0 -51.66667,-9.66667 -71,-29 -19.33333,-19.33333 -29,-43 -29,-71 0,-28 9.66667,-51.66667 29,-71 19.33333,-19.33333 43,-29 71,-29 28,0 51.66667,9.66667 71,29 19.33333,19.33333 29,43 29,71 0,28 -9.66667,51.66667 -29,71 -19.33333,19.33333 -43,29 -71,29 z"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 254,-346 -84,-86 c 39.33333,-39.33333 85.5,-70.5 138.5,-93.5 53,-23 110.16667,-34.5 171.5,-34.5 61.33333,0 118.5,11.66667 171.5,35 53,23.33333 99.16667,55 138.5,95 l -84,84 c -29.33333,-29.33333 -63.33333,-52.33333 -102,-69 -38.66667,-16.66667 -80,-25 -124,-25 -44,0 -85.33333,8.33333 -124,25 -38.66667,16.66667 -72.66667,39.66667 -102,69 z"
|
||||
id="path2" />
|
||||
<path
|
||||
d="M 84,-516 0,-600 c 61.333333,-62.66667 133,-111.66667 215,-147 82,-35.33333 170.33333,-53 265,-53 94.66667,0 183,17.66667 265,53 82,35.33333 153.66667,84.33333 215,147 l -84,84 C 824.66667,-567.33333 765.16667,-607.5 697.5,-636.5 629.83333,-665.5 557.33333,-680 480,-680 c -77.33333,0 -149.83333,14.5 -217.5,43.5 -67.66667,29 -127.16667,69.16667 -178.5,120.5 z"
|
||||
id="path3"
|
||||
style="fill:#666666" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@ -1,47 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="wifi_signal_strong_white.svg"
|
||||
inkscape:export-filename="../assets/wifi_signal_strong_white.png"
|
||||
inkscape:export-xdpi="64"
|
||||
inkscape:export-ydpi="64"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="27.5"
|
||||
inkscape:cx="10.563636"
|
||||
inkscape:cy="12.036364"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m 480,-120 c -28,0 -51.66667,-9.66667 -71,-29 -19.33333,-19.33333 -29,-43 -29,-71 0,-28 9.66667,-51.66667 29,-71 19.33333,-19.33333 43,-29 71,-29 28,0 51.66667,9.66667 71,29 19.33333,19.33333 29,43 29,71 0,28 -9.66667,51.66667 -29,71 -19.33333,19.33333 -43,29 -71,29 z"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 254,-346 -84,-86 c 39.33333,-39.33333 85.5,-70.5 138.5,-93.5 53,-23 110.16667,-34.5 171.5,-34.5 61.33333,0 118.5,11.66667 171.5,35 53,23.33333 99.16667,55 138.5,95 l -84,84 c -29.33333,-29.33333 -63.33333,-52.33333 -102,-69 -38.66667,-16.66667 -80,-25 -124,-25 -44,0 -85.33333,8.33333 -124,25 -38.66667,16.66667 -72.66667,39.66667 -102,69 z"
|
||||
id="path2" />
|
||||
<path
|
||||
d="M 84,-516 0,-600 c 61.333333,-62.66667 133,-111.66667 215,-147 82,-35.33333 170.33333,-53 265,-53 94.66667,0 183,17.66667 265,53 82,35.33333 153.66667,84.33333 215,147 l -84,84 C 824.66667,-567.33333 765.16667,-607.5 697.5,-636.5 629.83333,-665.5 557.33333,-680 480,-680 c -77.33333,0 -149.83333,14.5 -217.5,43.5 -67.66667,29 -127.16667,69.16667 -178.5,120.5 z"
|
||||
id="path3" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.2 KiB |
@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="24px"
|
||||
viewBox="0 -960 960 960"
|
||||
width="24px"
|
||||
fill="#e8eaed"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="wifi_signal_weak_white.svg"
|
||||
inkscape:export-filename="../assets/wifi_signal_weak_white.png"
|
||||
inkscape:export-xdpi="64"
|
||||
inkscape:export-ydpi="64"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:showpageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="27.5"
|
||||
inkscape:cx="10.563636"
|
||||
inkscape:cy="12.072727"
|
||||
inkscape:window-width="1503"
|
||||
inkscape:window-height="930"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
d="m 480,-120 c -28,0 -51.66667,-9.66667 -71,-29 -19.33333,-19.33333 -29,-43 -29,-71 0,-28 9.66667,-51.66667 29,-71 19.33333,-19.33333 43,-29 71,-29 28,0 51.66667,9.66667 71,29 19.33333,19.33333 29,43 29,71 0,28 -9.66667,51.66667 -29,71 -19.33333,19.33333 -43,29 -71,29 z"
|
||||
id="path1" />
|
||||
<path
|
||||
d="m 254,-346 -84,-86 c 39.33333,-39.33333 85.5,-70.5 138.5,-93.5 53,-23 110.16667,-34.5 171.5,-34.5 61.33333,0 118.5,11.66667 171.5,35 53,23.33333 99.16667,55 138.5,95 l -84,84 c -29.33333,-29.33333 -63.33333,-52.33333 -102,-69 -38.66667,-16.66667 -80,-25 -124,-25 -44,0 -85.33333,8.33333 -124,25 -38.66667,16.66667 -72.66667,39.66667 -102,69 z"
|
||||
id="path2"
|
||||
style="fill:#666666" />
|
||||
<path
|
||||
d="M 84,-516 0,-600 c 61.333333,-62.66667 133,-111.66667 215,-147 82,-35.33333 170.33333,-53 265,-53 94.66667,0 183,17.66667 265,53 82,35.33333 153.66667,84.33333 215,147 l -84,84 C 824.66667,-567.33333 765.16667,-607.5 697.5,-636.5 629.83333,-665.5 557.33333,-680 480,-680 c -77.33333,0 -149.83333,14.5 -217.5,43.5 -67.66667,29 -127.16667,69.16667 -178.5,120.5 z"
|
||||
id="path3"
|
||||
style="fill:#666666" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB |
195
Devices/lilygo-t5-epd47-s3/LICENSE-Apache-2.0.md
Normal file
@ -0,0 +1,195 @@
|
||||
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.
|
||||
|
||||
26
Devices/lilygo-t5-epd47-s3/device.properties
Normal file
@ -0,0 +1,26 @@
|
||||
general.vendor=LilyGO
|
||||
general.name=T5 4.7 Inch E-Paper S3
|
||||
general.incubating=true
|
||||
|
||||
apps.launcherAppId=tactility.launcher
|
||||
|
||||
hardware.target=ESP32S3
|
||||
hardware.flashSize=16MB
|
||||
hardware.spiRam=true
|
||||
hardware.spiRamMode=OCT
|
||||
hardware.spiRamSpeed=80M
|
||||
hardware.esptoolFlashFreq=80M
|
||||
hardware.bluetooth=true
|
||||
|
||||
storage.userDataLocation=Internal
|
||||
|
||||
display.size=4.7"
|
||||
display.shape=rectangle
|
||||
display.dpi=235
|
||||
|
||||
lvgl.colorDepth=8
|
||||
lvgl.fontSize=24
|
||||
lvgl.theme=Mono
|
||||
lvgl.statusbarColorsInverted=true
|
||||
|
||||
sdkconfig.CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=0
|
||||
92
Devices/lilygo-t5-epd47-s3/lilygo,t5-epd47-s3.dts
Normal file
@ -0,0 +1,92 @@
|
||||
/dts-v1/;
|
||||
|
||||
#include <tactility/bindings/root.h>
|
||||
#include <tactility/bindings/esp32_ble.h>
|
||||
#include <tactility/bindings/esp32_wifi_pinned.h>
|
||||
#include <tactility/bindings/esp32_gpio.h>
|
||||
#include <tactility/bindings/esp32_i2c_master.h>
|
||||
#include <tactility/bindings/esp32_spi.h>
|
||||
#include <tactility/bindings/esp32_adc_oneshot.h>
|
||||
#include <tactility/bindings/battery_sense.h>
|
||||
#include <bindings/bm8563.h>
|
||||
#include <bindings/gt911.h>
|
||||
#include <tactility/bindings/esp32_sdspi.h>
|
||||
#include <bindings/t5s3_display.h>
|
||||
|
||||
/ {
|
||||
compatible = "root";
|
||||
model = "LILYGO T5 4.7 Inch E-Paper S3";
|
||||
|
||||
wifi0 {
|
||||
compatible = "espressif,esp32-wifi-pinned";
|
||||
};
|
||||
|
||||
ble0 {
|
||||
compatible = "espressif,esp32-ble";
|
||||
};
|
||||
|
||||
gpio0 {
|
||||
compatible = "espressif,esp32-gpio";
|
||||
gpio-count = <49>;
|
||||
};
|
||||
|
||||
// Listed before the I2C bus on purpose, the touch controller was only tested with the display started first
|
||||
display {
|
||||
compatible = "lilygo,t5s3-display";
|
||||
};
|
||||
|
||||
i2c_internal {
|
||||
compatible = "espressif,esp32-i2c-master";
|
||||
port = <I2C_NUM_0>;
|
||||
clock-frequency = <400000>;
|
||||
// The board relies on the internal pull-ups, as the Arduino Wire library enables them
|
||||
pin-sda = <&gpio0 18 GPIO_FLAG_PULL_UP>;
|
||||
pin-scl = <&gpio0 17 GPIO_FLAG_PULL_UP>;
|
||||
|
||||
// PCF8563, register-compatible with the BM8563
|
||||
bm8563 {
|
||||
compatible = "belling,bm8563";
|
||||
reg = <0x51>;
|
||||
};
|
||||
|
||||
// The controller reports 540x960 (portrait) and the panel runs in landscape
|
||||
touch {
|
||||
compatible = "goodix,gt911";
|
||||
reg = <0x5D>;
|
||||
x-max = <540>;
|
||||
y-max = <960>;
|
||||
swap-xy;
|
||||
mirror-x;
|
||||
pin-interrupt = <&gpio0 47 GPIO_FLAG_NONE>;
|
||||
};
|
||||
};
|
||||
|
||||
spi0 {
|
||||
compatible = "espressif,esp32-spi";
|
||||
host = <SPI2_HOST>;
|
||||
cs-gpios = <&gpio0 42 GPIO_FLAG_NONE>;
|
||||
pin-mosi = <&gpio0 15 GPIO_FLAG_NONE>;
|
||||
pin-miso = <&gpio0 16 GPIO_FLAG_NONE>;
|
||||
pin-sclk = <&gpio0 11 GPIO_FLAG_NONE>;
|
||||
max-transfer-size = <4096>;
|
||||
|
||||
sdcard@0 {
|
||||
compatible = "espressif,esp32-sdspi";
|
||||
frequency-khz = <20000>;
|
||||
};
|
||||
};
|
||||
|
||||
// Battery voltage behind a 1:2 divider on GPIO14 (ADC2 channel 3), not yet calibrated
|
||||
adc0 {
|
||||
compatible = "espressif,esp32-adc-oneshot";
|
||||
unit-id = <ADC_UNIT_2>;
|
||||
channels = <ADC_CHANNEL_3 ADC_ATTEN_DB_12 ADC_BITWIDTH_DEFAULT>;
|
||||
};
|
||||
|
||||
battery-sense {
|
||||
compatible = "battery-sense";
|
||||
io-channel = <&adc0 0>;
|
||||
reference-voltage-mv = <3300>;
|
||||
multiplier = <2000>;
|
||||
};
|
||||
};
|
||||
7
Devices/lilygo-t5-epd47-s3/module.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
dependencies:
|
||||
- Platforms/platform-esp32
|
||||
- Drivers/bm8563-module
|
||||
- Drivers/gt911-module
|
||||
- Drivers/lilygo-t5s3-epd-module
|
||||
dts: lilygo,t5-epd47-s3.dts
|
||||
dts-only: true
|
||||
@ -67,7 +67,7 @@
|
||||
i2c_internal: i2c0 {
|
||||
compatible = "espressif,esp32-i2c-master";
|
||||
port = <I2C_NUM_0>;
|
||||
clock-frequency = <100000>;
|
||||
clock-frequency = <400000>;
|
||||
pin-sda = <&gpio0 31 GPIO_FLAG_PULL_UP>;
|
||||
pin-scl = <&gpio0 32 GPIO_FLAG_PULL_UP>;
|
||||
|
||||
|
||||
@ -32,7 +32,7 @@
|
||||
- Improve Setup: Show "Step done" screen
|
||||
- Make it more clear to end-users that an SD card is required to run Tactility
|
||||
- Warn if file operations are done from prohibited tasks (e.g. lvgl task)
|
||||
- stopAppFromToolbar() in Tactility.cpp stops the top-most app. Change it so the toolbar knows for which app id it is created, so it can rely on that.
|
||||
- stopAppFromToolbar() in lvgl/Lvgl.cpp stops the top-most app. Change it so the toolbar knows for which app id it is created, so it can rely on that.
|
||||
- Consider not unpacking `.app` files and executing them directly. Might want to cache file offsets. Cache file must be pinned to app version.
|
||||
- lvgl-module's spinner relies on hard-coded spinner asset from Tactility main project.
|
||||
- esp_lvgl_port settings has a large stack size (~9kB) to fix stackoverflow when LVGL events (e.g. button click) do actions like file operations do actions like file operations. Can we reduce the callstack?
|
||||
|
||||
18
Drivers/lilygo-t5s3-epd-module/CMakeLists.txt
Normal file
@ -0,0 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
||||
|
||||
tactility_add_module(lilygo-t5s3-epd-module
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS include/
|
||||
REQUIRES TactilityKernel esp_lcd esp_driver_gpio esp_driver_rmt esp_timer esp_rom hal soc heap freertos
|
||||
)
|
||||
|
||||
# The pixel loops of the display are too slow with the debug optimization level of the project
|
||||
set_source_files_properties(
|
||||
source/t5s3_epd.cpp
|
||||
source/t5s3_display.cpp
|
||||
PROPERTIES COMPILE_OPTIONS -O2
|
||||
)
|
||||
195
Drivers/lilygo-t5s3-epd-module/LICENSE-Apache-2.0.md
Normal file
@ -0,0 +1,195 @@
|
||||
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.
|
||||
|
||||
165
Drivers/lilygo-t5s3-epd-module/LICENSE-LGPL-3.0.md
Normal file
@ -0,0 +1,165 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
28
Drivers/lilygo-t5s3-epd-module/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
# lilygo-t5s3-epd-module
|
||||
|
||||
A kernel driver for the 4.7" 960x540 `ED047TC1` e-paper panel of the LILYGO T5 4.7 Inch E-Paper S3 (V2.3/V2.4).
|
||||
|
||||
The driver only works on this board. The pins of the panel, the 74HCT4094 shift register that carries the panel supplies and the sequence of the panel are fixed in the code, so the devicetree node only has the pixel clock as property.
|
||||
|
||||
- `t5s3_display.cpp`: the Tactility display driver. It decides between fast and quality updates.
|
||||
- `t5s3_epd.cpp`: the engine. It controls the shift register, the CKV gate clock (RMT peripheral), the row transfer (LCD peripheral in i80 mode), the update loop and the clear sequence.
|
||||
- `t5s3_epd_rows.h`: the conversion of framebuffer rows into bus data. It has no hardware access.
|
||||
- `t5s3_epd_waveform.h`: the 30 phase drive table of the quality updates.
|
||||
|
||||
There are three update modes. Fast draws black and white only. Quality draws 16 levels and leaves white pixels that stay white alone, which is used for partial updates. Full also flashes those white pixels, which is used for the clear and the refresh.
|
||||
|
||||
## License
|
||||
|
||||
The engine and its tables are derived from [epdiy](https://github.com/vroland/epdiy) and are licensed under the
|
||||
[LGPL v3.0 or later](LICENSE-LGPL-3.0.md) (the LGPL supplements the [GPL v3.0](../../Documentation/LICENSE-GPL-3.0.md)):
|
||||
|
||||
- `source/t5s3_epd.cpp`
|
||||
- `source/t5s3_epd.h`
|
||||
- `source/t5s3_epd_rows.h`
|
||||
- `source/t5s3_epd_waveform.h`
|
||||
|
||||
These files follow the sequences and timings of epdiy and contain its ED047TC1 waveform data. Each of them has an `SPDX-License-Identifier: LGPL-3.0-or-later` header.
|
||||
|
||||
The rest of the module (`source/t5s3_display.cpp`, `source/module.cpp`, the bindings and the headers in `include/`) is licensed under [Apache License v2.0](LICENSE-Apache-2.0.md).
|
||||
|
||||
A project that contains this module has to follow the LGPL for the four files above.
|
||||
@ -0,0 +1,11 @@
|
||||
description: >
|
||||
LILYGO T5 4.7 Inch E-Paper S3 (V2.3/V2.4) display with an ED047TC1 panel. The pins of the panel
|
||||
are fixed in the driver, so this node only has the pixel clock as property.
|
||||
|
||||
compatible: "lilygo,t5s3-display"
|
||||
|
||||
properties:
|
||||
pixel-clock-hz:
|
||||
type: int
|
||||
default: 10000000
|
||||
description: Clock of the bus that sends the pixel data of a row. Faster speeds up updates if the panel accepts it.
|
||||
@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <tactility/bindings/bindings.h>
|
||||
#include <drivers/t5s3_display.h>
|
||||
|
||||
DEFINE_DEVICETREE(t5s3_display, struct T5s3DisplayConfig)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct T5s3DisplayConfig {
|
||||
uint32_t pixel_clock_hz;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/module.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern struct Module lilygo_t5s3_epd_module;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
3
Drivers/lilygo-t5s3-epd-module/module.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
- TactilityKernel
|
||||
bindings: bindings
|
||||
23
Drivers/lilygo-t5s3-epd-module/source/module.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/module.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
extern Driver t5s3_display_driver;
|
||||
|
||||
static Driver* const lilygo_t5s3_epd_drivers[] = {
|
||||
&t5s3_display_driver,
|
||||
nullptr
|
||||
};
|
||||
|
||||
Module lilygo_t5s3_epd_module = {
|
||||
.name = "lilygo-t5s3-epd",
|
||||
.start = nullptr,
|
||||
.stop = nullptr,
|
||||
.drivers = lilygo_t5s3_epd_drivers,
|
||||
.symbols = nullptr,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
}
|
||||
309
Drivers/lilygo-t5s3-epd-module/source/t5s3_display.cpp
Normal file
@ -0,0 +1,309 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <drivers/t5s3_display.h>
|
||||
#include <lilygo_t5s3_epd_module.h>
|
||||
|
||||
#include <tactility/device.h>
|
||||
#include <tactility/driver.h>
|
||||
#include <tactility/drivers/display.h>
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/log.h>
|
||||
#include <tactility/module.h>
|
||||
#include <tactility/time.h>
|
||||
|
||||
#include "t5s3_epd.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "T5s3Display"
|
||||
#define GET_CONFIG(device) (static_cast<const T5s3DisplayConfig*>((device)->config))
|
||||
|
||||
// An update covering at least this fraction of the panel is a full-screen change and gets a quality refresh
|
||||
static constexpr float FULL_AREA_QUALITY_THRESHOLD = 0.6f;
|
||||
|
||||
// Fast updates allowed before a quality refresh limits ghosting. A full-screen redraw takes about 10 updates, so this must be higher
|
||||
static constexpr uint32_t QUALITY_REFRESH_PARTIAL_COUNT = 20;
|
||||
|
||||
// Updates within this time after a quality update also use quality mode, so the tiles of one redraw stay consistent.
|
||||
// It must stay well below the duration of a quality update.
|
||||
static constexpr uint32_t QUALITY_HOLD_MS = 50;
|
||||
|
||||
// Maximum time that consecutive quality updates keep extending the hold window
|
||||
static constexpr uint32_t QUALITY_HOLD_SESSION_MAX_MS = 200;
|
||||
|
||||
// 4x4 ordered (Bayer) dither thresholds, spread evenly across a 0-15 nibble range.
|
||||
static constexpr uint8_t BAYER_4X4[4][4] = {
|
||||
{ 0, 8, 2, 10 },
|
||||
{ 12, 4, 14, 6 },
|
||||
{ 3, 11, 1, 9 },
|
||||
{ 15, 7, 13, 5 },
|
||||
};
|
||||
|
||||
// Dithers an 8-bit luminance (0x00 black to 0xFF white) to a 4-bit level (0x0 black to 0xF white)
|
||||
static inline uint8_t dither_to_nibble(uint8_t luminance, int32_t x, int32_t y) {
|
||||
// Scaling by 17 spreads the thresholds over one full quantization step
|
||||
const uint32_t threshold = BAYER_4X4[y & 3][x & 3] * 17U;
|
||||
const uint32_t level = (static_cast<uint32_t>(luminance) * 15U + threshold) / 255U;
|
||||
return static_cast<uint8_t>(level > 15U ? 15U : level);
|
||||
}
|
||||
|
||||
// Binary variant for fast updates, which only draw black and white
|
||||
static inline uint8_t dither_to_bw_nibble(uint8_t luminance, int32_t x, int32_t y) {
|
||||
// Scaling by 16 keeps the highest threshold at 240, so pure white (0xFF) is never classified as black
|
||||
const uint32_t threshold = BAYER_4X4[y & 3][x & 3] * 16U;
|
||||
return luminance > threshold ? 0xF : 0x0;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct T5s3DisplayInternal {
|
||||
uint8_t* framebuffer;
|
||||
bool powered;
|
||||
// Fast updates since the last quality refresh
|
||||
uint32_t partial_count_since_quality;
|
||||
// Until this tick every update uses quality mode
|
||||
TickType_t quality_hold_until_tick;
|
||||
// Tick at which the current hold window started
|
||||
TickType_t quality_hold_session_start_tick;
|
||||
};
|
||||
|
||||
static void power_on(T5s3DisplayInternal* internal) {
|
||||
if (!internal->powered) {
|
||||
t5s3_epd_power_on();
|
||||
internal->powered = true;
|
||||
}
|
||||
}
|
||||
|
||||
// region DisplayApi
|
||||
|
||||
static error_t t5s3_display_reset(Device* device) {
|
||||
auto* internal = static_cast<T5s3DisplayInternal*>(device_get_driver_data(device));
|
||||
// The panel has no reset line, so a power cycle is the closest equivalent
|
||||
t5s3_epd_power_off();
|
||||
internal->powered = false;
|
||||
power_on(internal);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
/** Initialization is done in start() */
|
||||
static error_t t5s3_display_init(Device*) {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
/** Fills the panel white with one full update, without the flashing cycles of the initial clear */
|
||||
static error_t t5s3_display_clear(Device* device) {
|
||||
auto* internal = static_cast<T5s3DisplayInternal*>(device_get_driver_data(device));
|
||||
power_on(internal);
|
||||
t5s3_epd_fill_white();
|
||||
const bool success = t5s3_epd_update(T5s3EpdMode::Full, 0, T5S3_EPD_HEIGHT);
|
||||
if (success) {
|
||||
internal->partial_count_since_quality = 0;
|
||||
internal->quality_hold_until_tick = 0;
|
||||
}
|
||||
return success ? ERROR_NONE : ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redraws the current content with a quality update to remove ghosting.
|
||||
* The update skips pixels that did not change, so every pixel is invalidated first.
|
||||
*/
|
||||
static error_t t5s3_display_refresh(Device* device) {
|
||||
auto* internal = static_cast<T5s3DisplayInternal*>(device_get_driver_data(device));
|
||||
power_on(internal);
|
||||
t5s3_epd_invalidate();
|
||||
const bool success = t5s3_epd_update(T5s3EpdMode::Full, 0, T5S3_EPD_HEIGHT);
|
||||
if (success) {
|
||||
internal->partial_count_since_quality = 0;
|
||||
internal->quality_hold_until_tick = 0;
|
||||
}
|
||||
return success ? ERROR_NONE : ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
// Decides between a quality refresh and a fast update, *out_within_hold tells if the hold window applied
|
||||
static bool should_use_quality_mode(T5s3DisplayInternal* internal, int32_t width, int32_t height, bool* out_within_hold) {
|
||||
const TickType_t now = get_ticks();
|
||||
|
||||
const uint32_t area = static_cast<uint32_t>(width) * static_cast<uint32_t>(height);
|
||||
const bool is_full_screen_change = area >= static_cast<uint32_t>(
|
||||
static_cast<float>(T5S3_EPD_WIDTH * T5S3_EPD_HEIGHT) * FULL_AREA_QUALITY_THRESHOLD
|
||||
);
|
||||
const bool partial_count_exceeded = internal->partial_count_since_quality >= QUALITY_REFRESH_PARTIAL_COUNT;
|
||||
// Signed difference stays correct across the tick counter wrap, 0 means no hold
|
||||
const TickType_t hold_until = internal->quality_hold_until_tick;
|
||||
const bool within_hold = hold_until != 0 && static_cast<int32_t>(hold_until - now) > 0;
|
||||
*out_within_hold = within_hold;
|
||||
|
||||
return is_full_screen_change || partial_count_exceeded || within_hold;
|
||||
}
|
||||
|
||||
// Updates the counters after an update. A failed quality update leaves them unchanged and a failed fast update still counts.
|
||||
// A quality update starts or extends the hold window, up to QUALITY_HOLD_SESSION_MAX_MS.
|
||||
static void commit_quality_mode_decision(T5s3DisplayInternal* internal, bool used_quality, bool draw_succeeded, bool was_within_hold) {
|
||||
if (used_quality) {
|
||||
if (draw_succeeded) {
|
||||
internal->partial_count_since_quality = 0;
|
||||
const TickType_t now = get_ticks();
|
||||
if (!was_within_hold) {
|
||||
internal->quality_hold_session_start_tick = now;
|
||||
}
|
||||
const TickType_t session_elapsed = now - internal->quality_hold_session_start_tick;
|
||||
if (session_elapsed < millis_to_ticks(QUALITY_HOLD_SESSION_MAX_MS)) {
|
||||
internal->quality_hold_until_tick = now + millis_to_ticks(QUALITY_HOLD_MS);
|
||||
} else {
|
||||
internal->quality_hold_until_tick = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
internal->partial_count_since_quality++;
|
||||
}
|
||||
}
|
||||
|
||||
// Reports GRAYSCALE8 so that draw_bitmap() is called per changed tile
|
||||
static error_t t5s3_display_draw_bitmap(Device* device, int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end, const void* color_data) {
|
||||
auto* internal = static_cast<T5s3DisplayInternal*>(device_get_driver_data(device));
|
||||
|
||||
const int32_t width = x_end - x_start;
|
||||
const int32_t height = y_end - y_start;
|
||||
bool within_hold = false;
|
||||
const bool use_quality = should_use_quality_mode(internal, width, height, &within_hold);
|
||||
|
||||
// color_data is 8-bit luminance (0x00 black to 0xFF white) and the panel wants 4-bit levels (0x0 black to 0xF white)
|
||||
// Dithering uses 16 levels for quality updates and 2 levels for fast updates
|
||||
const auto* src = static_cast<const uint8_t*>(color_data);
|
||||
const size_t src_stride = static_cast<size_t>(width);
|
||||
|
||||
for (int32_t row = 0; row < height; row++) {
|
||||
const uint8_t* src_row = src + static_cast<size_t>(row) * src_stride;
|
||||
const int32_t display_y = y_start + row;
|
||||
|
||||
for (int32_t col = 0; col < width; col++) {
|
||||
const int32_t display_x = x_start + col;
|
||||
const uint8_t nibble = use_quality
|
||||
? dither_to_nibble(src_row[col], display_x, display_y)
|
||||
: dither_to_bw_nibble(src_row[col], display_x, display_y);
|
||||
t5s3_epd_set_pixel(internal->framebuffer, display_x, display_y, nibble);
|
||||
}
|
||||
}
|
||||
|
||||
power_on(internal);
|
||||
const bool success = t5s3_epd_update(use_quality ? T5s3EpdMode::Quality : T5s3EpdMode::Fast, y_start, y_end);
|
||||
|
||||
commit_quality_mode_decision(internal, use_quality, success, within_hold);
|
||||
return success ? ERROR_NONE : ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
static error_t t5s3_display_disp_on_off(Device* device, bool on_off) {
|
||||
auto* internal = static_cast<T5s3DisplayInternal*>(device_get_driver_data(device));
|
||||
if (on_off) {
|
||||
power_on(internal);
|
||||
} else if (internal->powered) {
|
||||
t5s3_epd_power_off();
|
||||
internal->powered = false;
|
||||
}
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static DisplayColorFormat t5s3_display_get_color_format(Device*) {
|
||||
return DISPLAY_COLOR_FORMAT_GRAYSCALE8;
|
||||
}
|
||||
|
||||
static uint16_t t5s3_display_get_resolution_x(Device*) {
|
||||
return T5S3_EPD_WIDTH;
|
||||
}
|
||||
|
||||
static uint16_t t5s3_display_get_resolution_y(Device*) {
|
||||
return T5S3_EPD_HEIGHT;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
static const DisplayApi t5s3_display_api = {
|
||||
// The driver converts into its own framebuffer, so the LVGL draw buffers may live in external RAM
|
||||
.capabilities = DISPLAY_CAPABILITY_ON_OFF | DISPLAY_CAPABILITY_SLOW_REFRESH | DISPLAY_CAPABILITY_PREFER_EXTERNAL_RAM,
|
||||
.reset = t5s3_display_reset,
|
||||
.init = t5s3_display_init,
|
||||
.draw_bitmap = t5s3_display_draw_bitmap,
|
||||
.clear = t5s3_display_clear,
|
||||
.refresh = t5s3_display_refresh,
|
||||
.mirror = nullptr,
|
||||
.swap_xy = nullptr,
|
||||
.get_swap_xy = nullptr,
|
||||
.get_mirror_x = nullptr,
|
||||
.get_mirror_y = nullptr,
|
||||
.set_gap = nullptr,
|
||||
.get_gap_x = nullptr,
|
||||
.get_gap_y = nullptr,
|
||||
.invert_color = nullptr,
|
||||
.disp_on_off = t5s3_display_disp_on_off,
|
||||
.disp_sleep = nullptr,
|
||||
.get_color_format = t5s3_display_get_color_format,
|
||||
.get_resolution_x = t5s3_display_get_resolution_x,
|
||||
.get_resolution_y = t5s3_display_get_resolution_y,
|
||||
// The framebuffer is 4bpp and not in the reported GRAYSCALE8 format
|
||||
.get_frame_buffer = nullptr,
|
||||
.get_frame_buffer_count = nullptr,
|
||||
.get_backlight = nullptr,
|
||||
.has_capability = nullptr,
|
||||
};
|
||||
|
||||
// region Driver lifecycle
|
||||
|
||||
static error_t start(Device* device) {
|
||||
const auto* config = GET_CONFIG(device);
|
||||
|
||||
auto* internal = static_cast<T5s3DisplayInternal*>(malloc(sizeof(T5s3DisplayInternal)));
|
||||
if (internal == nullptr) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
internal->powered = false;
|
||||
|
||||
if (!t5s3_epd_init(config->pixel_clock_hz)) {
|
||||
free(internal);
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
internal->framebuffer = t5s3_epd_framebuffer();
|
||||
|
||||
internal->partial_count_since_quality = 0;
|
||||
internal->quality_hold_until_tick = 0;
|
||||
internal->quality_hold_session_start_tick = 0;
|
||||
|
||||
device_set_driver_data(device, internal);
|
||||
|
||||
// The boot splash leaves ghosting, so clear the panel with the full flash cycle before LVGL draws
|
||||
power_on(internal);
|
||||
t5s3_epd_clear();
|
||||
|
||||
LOG_I(TAG, "Initialized (%dx%d)", T5S3_EPD_WIDTH, T5S3_EPD_HEIGHT);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
static error_t stop(Device* device) {
|
||||
auto* internal = static_cast<T5s3DisplayInternal*>(device_get_driver_data(device));
|
||||
|
||||
if (internal->powered) {
|
||||
t5s3_epd_power_off();
|
||||
internal->powered = false;
|
||||
}
|
||||
|
||||
t5s3_epd_deinit();
|
||||
|
||||
free(internal);
|
||||
device_set_driver_data(device, nullptr);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
Driver t5s3_display_driver = {
|
||||
.name = "t5s3-display",
|
||||
.compatible = (const char*[]) { "lilygo,t5s3-display", nullptr },
|
||||
.start_device = start,
|
||||
.stop_device = stop,
|
||||
.probe = nullptr,
|
||||
.api = &t5s3_display_api,
|
||||
.device_type = &DISPLAY_TYPE,
|
||||
.owner = &lilygo_t5s3_epd_module,
|
||||
.internal = nullptr
|
||||
};
|
||||
|
||||
}
|
||||
596
Drivers/lilygo-t5s3-epd-module/source/t5s3_epd.cpp
Normal file
@ -0,0 +1,596 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Minimal driver for the ED047TC1 panel of the LILYGO T5 4.7 Inch E-Paper S3.
|
||||
// The sequences and timings follow the epdiy project (https://github.com/vroland/epdiy).
|
||||
//
|
||||
// The panel is controlled by a 74HCT4094 shift register (latch, output enable, mode, start of frame and the
|
||||
// panel supplies), the CKV gate clock (RMT peripheral) and an 8 bit bus (LCD peripheral in i80 mode) that
|
||||
// carries the 2 bit actions of 4 pixels per byte. Rows are sent one at a time. The data of a row is applied by the
|
||||
// CKV pulse after the one that was running during its transfer.
|
||||
|
||||
#include "t5s3_epd.h"
|
||||
|
||||
#include <tactility/log.h>
|
||||
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/rmt_encoder.h>
|
||||
#include <driver/rmt_tx.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <esp_lcd_io_i80.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_rom_sys.h>
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <hal/gpio_ll.h>
|
||||
#include <soc/gpio_struct.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "T5s3Epd"
|
||||
|
||||
static constexpr gpio_num_t PIN_REGISTER_DATA = GPIO_NUM_13;
|
||||
static constexpr gpio_num_t PIN_REGISTER_CLOCK = GPIO_NUM_12;
|
||||
static constexpr gpio_num_t PIN_REGISTER_STROBE = GPIO_NUM_0;
|
||||
static constexpr gpio_num_t PIN_CKV = GPIO_NUM_38;
|
||||
// Start pulse of a row, driven as the data/command line of the bus
|
||||
static constexpr gpio_num_t PIN_STH = GPIO_NUM_40;
|
||||
// Pixel clock, driven as the write line of the bus
|
||||
static constexpr gpio_num_t PIN_CKH = GPIO_NUM_41;
|
||||
static constexpr gpio_num_t BUS_PINS[8] = {GPIO_NUM_6, GPIO_NUM_7, GPIO_NUM_4, GPIO_NUM_5, GPIO_NUM_2, GPIO_NUM_3, GPIO_NUM_8, GPIO_NUM_1};
|
||||
|
||||
// A row has headroom after the pixel data for the timing of the source driver
|
||||
static constexpr int BUS_TRANSFER_BYTES = ((T5S3_EPD_WIDTH + 32) / 4 + 3) & ~3;
|
||||
|
||||
// Time base of the CKV pulses, 10 MHz
|
||||
static constexpr uint32_t PULSE_RESOLUTION_HZ = 10000000;
|
||||
// Low time after the pulse of a row in ticks
|
||||
static constexpr uint32_t ROW_LOW_TICKS = 50;
|
||||
// More phases drive the pixels further into black or white, which leaves less ghosting but makes fast updates slower
|
||||
static constexpr uint32_t FAST_PHASES = 10;
|
||||
// Hold time of a phase of the fast update in ticks
|
||||
static constexpr uint32_t FAST_HOLD_TICKS = 1000;
|
||||
// Hold time of the frames that flash the panel to white
|
||||
static constexpr uint32_t CLEAR_HOLD_TICKS = 120;
|
||||
static constexpr int CLEAR_CYCLES = 3;
|
||||
static constexpr int CLEAR_DARK_FRAMES = 10;
|
||||
static constexpr int CLEAR_LIGHT_FRAMES = 10;
|
||||
static constexpr int CLEAR_REST_FRAMES = 2;
|
||||
static constexpr uint8_t ALL_DARK = 0x55;
|
||||
static constexpr uint8_t ALL_LIGHT = 0xAA;
|
||||
|
||||
// Limit for waiting on a flag that an interrupt handler sets, so that a lost interrupt fails the update instead of spinning forever
|
||||
static constexpr uint32_t WAIT_SPIN_LIMIT = 20000000;
|
||||
|
||||
struct Engine {
|
||||
uint8_t* front = nullptr;
|
||||
uint8_t* back = nullptr;
|
||||
uint8_t* row_buffer[2] = {nullptr, nullptr};
|
||||
int current = 0;
|
||||
esp_lcd_i80_bus_handle_t bus = nullptr;
|
||||
esp_lcd_panel_io_handle_t io = nullptr;
|
||||
rmt_channel_handle_t pulse_channel = nullptr;
|
||||
rmt_encoder_handle_t pulse_encoder = nullptr;
|
||||
volatile bool transfer_done = true;
|
||||
volatile bool pulse_done = true;
|
||||
bool failed = false;
|
||||
// Shift register
|
||||
bool latch = false;
|
||||
bool output_enable = false;
|
||||
bool mode = false;
|
||||
bool start_of_frame = true;
|
||||
bool power_disable = true;
|
||||
bool positive_power = false;
|
||||
bool negative_power = false;
|
||||
// Rows written since the last row that carried data
|
||||
int rows_skipped = 0;
|
||||
bool row_dirty[T5S3_EPD_HEIGHT];
|
||||
alignas(4) uint8_t changes[T5S3_EPD_FB_ROW_BYTES];
|
||||
alignas(4) uint8_t line_mask[T5S3_EPD_BUS_ROW_BYTES];
|
||||
};
|
||||
|
||||
static Engine engine;
|
||||
static rmt_symbol_word_t pulse_symbol;
|
||||
|
||||
static inline void IRAM_ATTR pin_set(gpio_num_t pin, uint32_t level) {
|
||||
gpio_ll_set_level(GPIO_LL_GET_HW(GPIO_PORT_0), pin, level);
|
||||
}
|
||||
|
||||
static bool IRAM_ATTR on_pulse_done(rmt_channel_handle_t, const rmt_tx_done_event_data_t*, void*) {
|
||||
engine.pulse_done = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool IRAM_ATTR on_transfer_done(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void*) {
|
||||
engine.transfer_done = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// After a failure nothing is waited for anymore, so the rest of the update ends quickly
|
||||
static void IRAM_ATTR wait_for(volatile bool& flag) {
|
||||
uint32_t spins = 0;
|
||||
while (!flag) {
|
||||
if (engine.failed || ++spins > WAIT_SPIN_LIMIT) {
|
||||
engine.failed = true;
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// region Shift register
|
||||
|
||||
static void IRAM_ATTR push_register_bit(bool bit) {
|
||||
pin_set(PIN_REGISTER_CLOCK, 0);
|
||||
pin_set(PIN_REGISTER_DATA, bit ? 1 : 0);
|
||||
pin_set(PIN_REGISTER_CLOCK, 1);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR push_register() {
|
||||
pin_set(PIN_REGISTER_STROBE, 0);
|
||||
push_register_bit(engine.output_enable);
|
||||
push_register_bit(engine.mode);
|
||||
// Always set
|
||||
push_register_bit(true);
|
||||
push_register_bit(engine.start_of_frame);
|
||||
push_register_bit(engine.negative_power);
|
||||
push_register_bit(engine.positive_power);
|
||||
push_register_bit(engine.power_disable);
|
||||
push_register_bit(engine.latch);
|
||||
pin_set(PIN_REGISTER_STROBE, 1);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region CKV pulses and row transfer
|
||||
|
||||
// Pulses the gate clock for high_ticks and keeps it low for low_ticks, repeated for the given number of pulses.
|
||||
// Without a high time the line is high for low_ticks.
|
||||
static void IRAM_ATTR pulse_ckv(uint32_t high_ticks, uint32_t low_ticks, bool wait, int count = 1) {
|
||||
wait_for(engine.pulse_done);
|
||||
if (high_ticks > 0) {
|
||||
pulse_symbol.duration0 = high_ticks;
|
||||
pulse_symbol.level0 = 1;
|
||||
pulse_symbol.duration1 = low_ticks;
|
||||
pulse_symbol.level1 = 0;
|
||||
} else {
|
||||
pulse_symbol.duration0 = low_ticks;
|
||||
pulse_symbol.level0 = 1;
|
||||
pulse_symbol.duration1 = 0;
|
||||
pulse_symbol.level1 = 0;
|
||||
}
|
||||
engine.pulse_done = false;
|
||||
rmt_transmit_config_t config = {};
|
||||
config.loop_count = count;
|
||||
if (rmt_transmit(engine.pulse_channel, engine.pulse_encoder, &pulse_symbol, sizeof(pulse_symbol), &config) != ESP_OK) {
|
||||
engine.pulse_done = true;
|
||||
engine.failed = true;
|
||||
return;
|
||||
}
|
||||
if (wait) {
|
||||
wait_for(engine.pulse_done);
|
||||
}
|
||||
}
|
||||
|
||||
static void IRAM_ATTR pulse_ckv_us(uint32_t high_us, uint32_t low_us, bool wait) {
|
||||
pulse_ckv(high_us * 10, low_us * 10, wait);
|
||||
}
|
||||
|
||||
static void IRAM_ATTR start_transfer() {
|
||||
engine.transfer_done = false;
|
||||
if (esp_lcd_panel_io_tx_color(engine.io, 0, engine.row_buffer[engine.current], BUS_TRANSFER_BYTES) != ESP_OK) {
|
||||
engine.transfer_done = true;
|
||||
engine.failed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Applies the data that was sent last and starts the transfer of the current row buffer while the CKV pulse runs
|
||||
static void IRAM_ATTR output_row(uint32_t hold_ticks) {
|
||||
wait_for(engine.transfer_done);
|
||||
wait_for(engine.pulse_done);
|
||||
engine.latch = true;
|
||||
push_register();
|
||||
engine.latch = false;
|
||||
push_register();
|
||||
pulse_ckv(hold_ticks, ROW_LOW_TICKS, false);
|
||||
start_transfer();
|
||||
engine.current ^= 1;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR write_row(uint32_t hold_ticks) {
|
||||
output_row(hold_ticks);
|
||||
engine.rows_skipped = 0;
|
||||
}
|
||||
|
||||
// Skips a row after rows with data. The first two skipped rows push blank data through the pipeline.
|
||||
static void IRAM_ATTR skip_row(uint32_t hold_ticks) {
|
||||
memset(engine.row_buffer[engine.current], 0, T5S3_EPD_BUS_ROW_BYTES);
|
||||
output_row(hold_ticks);
|
||||
engine.rows_skipped++;
|
||||
}
|
||||
|
||||
// Clocks the gate driver through rows that carry no data with one train of pulses
|
||||
static void IRAM_ATTR skip_rows(int count) {
|
||||
pulse_ckv(45, 5, false, count);
|
||||
engine.rows_skipped += count;
|
||||
}
|
||||
|
||||
static void IRAM_ATTR start_frame() {
|
||||
wait_for(engine.transfer_done);
|
||||
wait_for(engine.pulse_done);
|
||||
engine.rows_skipped = 0;
|
||||
|
||||
engine.mode = true;
|
||||
push_register();
|
||||
pulse_ckv_us(1, 1, true);
|
||||
|
||||
// The start of frame line must go low and high again while the long pulse runs
|
||||
engine.start_of_frame = false;
|
||||
push_register();
|
||||
pulse_ckv_us(1000, 100, false);
|
||||
engine.start_of_frame = true;
|
||||
push_register();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
pulse_ckv_us(1, 1, true);
|
||||
}
|
||||
|
||||
engine.output_enable = true;
|
||||
push_register();
|
||||
}
|
||||
|
||||
static void IRAM_ATTR end_frame() {
|
||||
engine.start_of_frame = false;
|
||||
push_register();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
pulse_ckv_us(1, 1, true);
|
||||
}
|
||||
engine.mode = false;
|
||||
push_register();
|
||||
pulse_ckv_us(0, 10, true);
|
||||
engine.output_enable = false;
|
||||
push_register();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
pulse_ckv_us(1, 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Frames
|
||||
|
||||
static void IRAM_ATTR draw_frame(const uint8_t* table, uint32_t hold_ticks) {
|
||||
start_frame();
|
||||
int y = 0;
|
||||
bool ends_with_train = false;
|
||||
while (y < T5S3_EPD_HEIGHT && !engine.failed) {
|
||||
ends_with_train = false;
|
||||
if (engine.row_dirty[y]) {
|
||||
t5s3_epd_prepare_row(
|
||||
engine.row_buffer[engine.current],
|
||||
engine.front + y * T5S3_EPD_FB_ROW_BYTES,
|
||||
engine.back + y * T5S3_EPD_FB_ROW_BYTES,
|
||||
table,
|
||||
engine.line_mask
|
||||
);
|
||||
write_row(hold_ticks);
|
||||
y++;
|
||||
} else if (engine.rows_skipped < 2) {
|
||||
skip_row(hold_ticks);
|
||||
y++;
|
||||
} else {
|
||||
int run = 1;
|
||||
while (y + run < T5S3_EPD_HEIGHT && !engine.row_dirty[y + run]) {
|
||||
run++;
|
||||
}
|
||||
skip_rows(run);
|
||||
ends_with_train = true;
|
||||
y += run;
|
||||
}
|
||||
}
|
||||
// The last row is applied by one more pulse
|
||||
if (engine.rows_skipped == 0 && !engine.failed) {
|
||||
write_row(hold_ticks);
|
||||
} else if (ends_with_train) {
|
||||
// The end of the frame must not overlap the train of pulses of the skipped rows
|
||||
wait_for(engine.pulse_done);
|
||||
}
|
||||
end_frame();
|
||||
}
|
||||
|
||||
static void IRAM_ATTR draw_uniform_frame(uint8_t pattern, uint32_t hold_ticks) {
|
||||
memset(engine.row_buffer[0], pattern, T5S3_EPD_BUS_ROW_BYTES);
|
||||
memset(engine.row_buffer[1], pattern, T5S3_EPD_BUS_ROW_BYTES);
|
||||
start_frame();
|
||||
for (int y = 0; y < T5S3_EPD_HEIGHT && !engine.failed; y++) {
|
||||
write_row(hold_ticks);
|
||||
}
|
||||
if (!engine.failed) {
|
||||
write_row(hold_ticks);
|
||||
}
|
||||
end_frame();
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Setup
|
||||
|
||||
static bool setup_register_pins() {
|
||||
gpio_config_t config = {};
|
||||
config.pin_bit_mask = (1ULL << PIN_REGISTER_DATA) | (1ULL << PIN_REGISTER_CLOCK) | (1ULL << PIN_REGISTER_STROBE);
|
||||
config.mode = GPIO_MODE_OUTPUT;
|
||||
if (gpio_config(&config) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
pin_set(PIN_REGISTER_STROBE, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool setup_bus(uint32_t pixel_clock_hz) {
|
||||
esp_lcd_i80_bus_config_t bus_config = {};
|
||||
bus_config.dc_gpio_num = PIN_STH;
|
||||
bus_config.wr_gpio_num = PIN_CKH;
|
||||
bus_config.clk_src = LCD_CLK_SRC_DEFAULT;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
bus_config.data_gpio_nums[i] = BUS_PINS[i];
|
||||
}
|
||||
bus_config.bus_width = 8;
|
||||
bus_config.max_transfer_bytes = BUS_TRANSFER_BYTES;
|
||||
if (esp_lcd_new_i80_bus(&bus_config, &engine.bus) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The data/command line is low when idle, high during a dummy command phase and low during the data of a row
|
||||
esp_lcd_panel_io_i80_config_t io_config = {};
|
||||
io_config.cs_gpio_num = GPIO_NUM_NC;
|
||||
io_config.pclk_hz = pixel_clock_hz;
|
||||
io_config.trans_queue_depth = 4;
|
||||
io_config.on_color_trans_done = on_transfer_done;
|
||||
io_config.lcd_cmd_bits = 10;
|
||||
io_config.lcd_param_bits = 0;
|
||||
io_config.dc_levels.dc_idle_level = 0;
|
||||
io_config.dc_levels.dc_cmd_level = 1;
|
||||
io_config.dc_levels.dc_dummy_level = 0;
|
||||
io_config.dc_levels.dc_data_level = 0;
|
||||
return esp_lcd_new_panel_io_i80(engine.bus, &io_config, &engine.io) == ESP_OK;
|
||||
}
|
||||
|
||||
static bool setup_pulse() {
|
||||
rmt_tx_channel_config_t channel_config = {};
|
||||
channel_config.gpio_num = PIN_CKV;
|
||||
channel_config.clk_src = RMT_CLK_SRC_DEFAULT;
|
||||
channel_config.resolution_hz = PULSE_RESOLUTION_HZ;
|
||||
channel_config.mem_block_symbols = 48;
|
||||
channel_config.trans_queue_depth = 1;
|
||||
if (rmt_new_tx_channel(&channel_config, &engine.pulse_channel) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
rmt_copy_encoder_config_t encoder_config = {};
|
||||
if (rmt_new_copy_encoder(&encoder_config, &engine.pulse_encoder) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
rmt_tx_event_callbacks_t callbacks = {};
|
||||
callbacks.on_trans_done = on_pulse_done;
|
||||
if (rmt_tx_register_event_callbacks(engine.pulse_channel, &callbacks, nullptr) != ESP_OK) {
|
||||
return false;
|
||||
}
|
||||
return rmt_enable(engine.pulse_channel) == ESP_OK;
|
||||
}
|
||||
|
||||
bool t5s3_epd_init(uint32_t pixel_clock_hz) {
|
||||
if (engine.front != nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
engine.front = static_cast<uint8_t*>(heap_caps_aligned_alloc(16, T5S3_EPD_FB_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
||||
engine.back = static_cast<uint8_t*>(heap_caps_aligned_alloc(16, T5S3_EPD_FB_BYTES, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT));
|
||||
for (auto& buffer: engine.row_buffer) {
|
||||
buffer = static_cast<uint8_t*>(heap_caps_aligned_calloc(16, 1, BUS_TRANSFER_BYTES, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT));
|
||||
}
|
||||
if (engine.front == nullptr || engine.back == nullptr || engine.row_buffer[0] == nullptr || engine.row_buffer[1] == nullptr) {
|
||||
LOG_E(TAG, "Out of memory");
|
||||
t5s3_epd_deinit();
|
||||
return false;
|
||||
}
|
||||
memset(engine.front, 0xFF, T5S3_EPD_FB_BYTES);
|
||||
memset(engine.back, 0xFF, T5S3_EPD_FB_BYTES);
|
||||
|
||||
if (!setup_register_pins() || !setup_bus(pixel_clock_hz) || !setup_pulse()) {
|
||||
LOG_E(TAG, "Failed to set up the pins and peripherals");
|
||||
t5s3_epd_deinit();
|
||||
return false;
|
||||
}
|
||||
|
||||
engine.latch = false;
|
||||
engine.output_enable = false;
|
||||
engine.mode = false;
|
||||
engine.start_of_frame = true;
|
||||
engine.power_disable = true;
|
||||
engine.positive_power = false;
|
||||
engine.negative_power = false;
|
||||
engine.current = 0;
|
||||
engine.transfer_done = true;
|
||||
engine.pulse_done = true;
|
||||
push_register();
|
||||
return true;
|
||||
}
|
||||
|
||||
void t5s3_epd_deinit() {
|
||||
if (engine.pulse_channel != nullptr) {
|
||||
// Both calls fail harmlessly when the channel was not enabled
|
||||
rmt_disable(engine.pulse_channel);
|
||||
rmt_del_channel(engine.pulse_channel);
|
||||
engine.pulse_channel = nullptr;
|
||||
}
|
||||
if (engine.pulse_encoder != nullptr) {
|
||||
rmt_del_encoder(engine.pulse_encoder);
|
||||
engine.pulse_encoder = nullptr;
|
||||
}
|
||||
if (engine.io != nullptr) {
|
||||
esp_lcd_panel_io_del(engine.io);
|
||||
engine.io = nullptr;
|
||||
}
|
||||
if (engine.bus != nullptr) {
|
||||
esp_lcd_del_i80_bus(engine.bus);
|
||||
engine.bus = nullptr;
|
||||
}
|
||||
heap_caps_free(engine.front);
|
||||
heap_caps_free(engine.back);
|
||||
heap_caps_free(engine.row_buffer[0]);
|
||||
heap_caps_free(engine.row_buffer[1]);
|
||||
engine.front = nullptr;
|
||||
engine.back = nullptr;
|
||||
engine.row_buffer[0] = nullptr;
|
||||
engine.row_buffer[1] = nullptr;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Power
|
||||
|
||||
void t5s3_epd_power_on() {
|
||||
engine.power_disable = false;
|
||||
push_register();
|
||||
esp_rom_delay_us(100);
|
||||
engine.negative_power = true;
|
||||
push_register();
|
||||
esp_rom_delay_us(500);
|
||||
engine.positive_power = true;
|
||||
push_register();
|
||||
esp_rom_delay_us(100);
|
||||
engine.start_of_frame = true;
|
||||
push_register();
|
||||
}
|
||||
|
||||
void t5s3_epd_power_off() {
|
||||
engine.positive_power = false;
|
||||
push_register();
|
||||
esp_rom_delay_us(10);
|
||||
engine.negative_power = false;
|
||||
push_register();
|
||||
esp_rom_delay_us(100);
|
||||
engine.start_of_frame = false;
|
||||
engine.output_enable = false;
|
||||
engine.mode = false;
|
||||
engine.power_disable = true;
|
||||
push_register();
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Updates
|
||||
|
||||
uint8_t* t5s3_epd_framebuffer() {
|
||||
return engine.front;
|
||||
}
|
||||
|
||||
void t5s3_epd_fill_white() {
|
||||
memset(engine.front, 0xFF, T5S3_EPD_FB_BYTES);
|
||||
}
|
||||
|
||||
void t5s3_epd_invalidate() {
|
||||
auto* words = reinterpret_cast<uint32_t*>(engine.back);
|
||||
for (int i = 0; i < T5S3_EPD_FB_BYTES / 4; i++) {
|
||||
words[i] = ~words[i];
|
||||
}
|
||||
}
|
||||
|
||||
bool t5s3_epd_update(T5s3EpdMode mode, int32_t y_start, int32_t y_end) {
|
||||
if (y_start < 0) {
|
||||
y_start = 0;
|
||||
}
|
||||
if (y_end > T5S3_EPD_HEIGHT) {
|
||||
y_end = T5S3_EPD_HEIGHT;
|
||||
}
|
||||
if (y_start >= y_end) {
|
||||
return true;
|
||||
}
|
||||
const int64_t started = esp_timer_get_time();
|
||||
|
||||
// Find the rows and columns that changed
|
||||
memset(engine.row_dirty, 0, sizeof(engine.row_dirty));
|
||||
memset(engine.changes, 0, sizeof(engine.changes));
|
||||
auto* changes = reinterpret_cast<uint32_t*>(engine.changes);
|
||||
bool any_changes = false;
|
||||
for (int y = y_start; y < y_end; y++) {
|
||||
const auto* to_row = reinterpret_cast<const uint32_t*>(engine.front + y * T5S3_EPD_FB_ROW_BYTES);
|
||||
const auto* from_row = reinterpret_cast<const uint32_t*>(engine.back + y * T5S3_EPD_FB_ROW_BYTES);
|
||||
uint32_t row_changes = 0;
|
||||
for (int i = 0; i < T5S3_EPD_FB_ROW_BYTES / 4; i++) {
|
||||
const uint32_t difference = to_row[i] ^ from_row[i];
|
||||
changes[i] |= difference;
|
||||
row_changes |= difference;
|
||||
}
|
||||
engine.row_dirty[y] = row_changes != 0;
|
||||
any_changes = any_changes || row_changes != 0;
|
||||
}
|
||||
if (!any_changes) {
|
||||
return true;
|
||||
}
|
||||
t5s3_epd_build_line_mask(engine.line_mask, engine.changes);
|
||||
|
||||
engine.failed = false;
|
||||
uint8_t table[256];
|
||||
if (mode == T5s3EpdMode::Fast) {
|
||||
t5s3_epd_build_fast_table(table);
|
||||
for (uint32_t phase = 0; phase < FAST_PHASES && !engine.failed; phase++) {
|
||||
draw_frame(table, FAST_HOLD_TICKS);
|
||||
taskYIELD();
|
||||
}
|
||||
} else {
|
||||
// Long updates sleep between frames so that the idle task can feed the watchdog
|
||||
for (int phase = 0; phase < T5S3_EPD_QUALITY_PHASES && !engine.failed; phase++) {
|
||||
t5s3_epd_build_quality_table(table, phase, mode == T5s3EpdMode::Full);
|
||||
draw_frame(table, T5S3_EPD_QUALITY_HOLD[phase]);
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
// A failed update leaves the back buffer alone, so the next update draws the same rows again
|
||||
if (engine.failed) {
|
||||
LOG_E(TAG, "Update failed, a peripheral did not respond");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int y = y_start; y < y_end; y++) {
|
||||
if (engine.row_dirty[y]) {
|
||||
memcpy(engine.back + y * T5S3_EPD_FB_ROW_BYTES, engine.front + y * T5S3_EPD_FB_ROW_BYTES, T5S3_EPD_FB_ROW_BYTES);
|
||||
}
|
||||
}
|
||||
|
||||
const int elapsed_ms = static_cast<int>((esp_timer_get_time() - started) / 1000);
|
||||
// Quality and slow updates are logged at info level, frequent fast updates would flood the log
|
||||
if (mode != T5s3EpdMode::Fast || elapsed_ms > 150) {
|
||||
LOG_I(TAG, "Updated rows %d to %d in %d ms", static_cast<int>(y_start), static_cast<int>(y_end), elapsed_ms);
|
||||
} else {
|
||||
LOG_D(TAG, "Updated rows %d to %d in %d ms", static_cast<int>(y_start), static_cast<int>(y_end), elapsed_ms);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void draw_clear_frames(uint8_t pattern, int count) {
|
||||
for (int i = 0; i < count && !engine.failed; i++) {
|
||||
draw_uniform_frame(pattern, CLEAR_HOLD_TICKS);
|
||||
vTaskDelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
bool t5s3_epd_clear() {
|
||||
const int64_t started = esp_timer_get_time();
|
||||
t5s3_epd_fill_white();
|
||||
if (!t5s3_epd_update(T5s3EpdMode::Full, 0, T5S3_EPD_HEIGHT)) {
|
||||
return false;
|
||||
}
|
||||
engine.failed = false;
|
||||
for (int cycle = 0; cycle < CLEAR_CYCLES; cycle++) {
|
||||
draw_clear_frames(ALL_DARK, CLEAR_DARK_FRAMES);
|
||||
draw_clear_frames(ALL_LIGHT, CLEAR_LIGHT_FRAMES);
|
||||
draw_clear_frames(0, CLEAR_REST_FRAMES);
|
||||
}
|
||||
if (engine.failed) {
|
||||
LOG_E(TAG, "Clear failed, a peripheral did not respond");
|
||||
return false;
|
||||
}
|
||||
LOG_I(TAG, "Cleared in %d ms", static_cast<int>((esp_timer_get_time() - started) / 1000));
|
||||
return true;
|
||||
}
|
||||
|
||||
// endregion
|
||||
54
Drivers/lilygo-t5s3-epd-module/source/t5s3_epd.h
Normal file
@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Minimal driver for the ED047TC1 panel of the LILYGO T5 4.7 Inch E-Paper S3.
|
||||
// The calls must be serialized by the user, the driver has no locking.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "t5s3_epd_rows.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class T5s3EpdMode {
|
||||
// Black and white only
|
||||
Fast,
|
||||
// 16 levels in 30 phases, white pixels that stay white are not touched
|
||||
Quality,
|
||||
// 16 levels in 30 phases, white pixels that stay white are flashed too, which clears ghosting
|
||||
Full
|
||||
};
|
||||
|
||||
// Allocates the frame buffers and sets up the pins and peripherals. The panel stays unpowered.
|
||||
bool t5s3_epd_init(uint32_t pixel_clock_hz);
|
||||
|
||||
void t5s3_epd_deinit();
|
||||
|
||||
// Frame buffer to draw in, 4 bits per pixel. The panel shows it after t5s3_epd_update().
|
||||
uint8_t* t5s3_epd_framebuffer();
|
||||
|
||||
void t5s3_epd_power_on();
|
||||
|
||||
void t5s3_epd_power_off();
|
||||
|
||||
// Drives all pixels that changed in the rows [y_start, y_end) since the last update. Needs the panel to be powered.
|
||||
bool t5s3_epd_update(T5s3EpdMode mode, int32_t y_start, int32_t y_end);
|
||||
|
||||
void t5s3_epd_fill_white();
|
||||
|
||||
// Makes the next update drive every pixel
|
||||
void t5s3_epd_invalidate();
|
||||
|
||||
// Flashes the whole panel to white. Needs the panel to be powered.
|
||||
bool t5s3_epd_clear();
|
||||
|
||||
inline void t5s3_epd_set_pixel(uint8_t* framebuffer, int32_t x, int32_t y, uint8_t level) {
|
||||
if (x < 0 || x >= T5S3_EPD_WIDTH || y < 0 || y >= T5S3_EPD_HEIGHT) {
|
||||
return;
|
||||
}
|
||||
uint8_t* byte = &framebuffer[y * T5S3_EPD_FB_ROW_BYTES + x / 2];
|
||||
if (x % 2) {
|
||||
*byte = (*byte & 0x0F) | static_cast<uint8_t>(level << 4);
|
||||
} else {
|
||||
*byte = (*byte & 0xF0) | (level & 0x0F);
|
||||
}
|
||||
}
|
||||
106
Drivers/lilygo-t5s3-epd-module/source/t5s3_epd_rows.h
Normal file
@ -0,0 +1,106 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Conversion of framebuffer rows into the data the source driver of the ED047TC1 panel expects.
|
||||
// This code has no hardware access.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "t5s3_epd_waveform.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <esp_attr.h>
|
||||
#define T5S3_EPD_IRAM IRAM_ATTR
|
||||
#else
|
||||
#define T5S3_EPD_IRAM
|
||||
#endif
|
||||
|
||||
constexpr int T5S3_EPD_WIDTH = 960;
|
||||
constexpr int T5S3_EPD_HEIGHT = 540;
|
||||
|
||||
// Framebuffer rows hold 4 bits per pixel with the even pixel of a pair in the low nibble, 0x0 is black and 0xF is white
|
||||
constexpr int T5S3_EPD_FB_ROW_BYTES = T5S3_EPD_WIDTH / 2;
|
||||
constexpr int T5S3_EPD_FB_BYTES = T5S3_EPD_FB_ROW_BYTES * T5S3_EPD_HEIGHT;
|
||||
|
||||
// Rows on the bus hold 2 bits per pixel with the first of four pixels in the lowest bits
|
||||
constexpr int T5S3_EPD_BUS_ROW_BYTES = T5S3_EPD_WIDTH / 4;
|
||||
|
||||
constexpr uint8_t T5S3_EPD_ACTION_HOLD = 0;
|
||||
constexpr uint8_t T5S3_EPD_ACTION_DARK = 1;
|
||||
constexpr uint8_t T5S3_EPD_ACTION_LIGHT = 2;
|
||||
|
||||
// Tables map (target level << 4 | source level) to the action for one phase of an update.
|
||||
|
||||
// Fast update: pixels that end up black are driven dark and pixels that end up white are driven light
|
||||
inline void t5s3_epd_build_fast_table(uint8_t* table) {
|
||||
for (int to = 0; to < 16; to++) {
|
||||
for (int from = 0; from < 16; from++) {
|
||||
uint8_t action = T5S3_EPD_ACTION_HOLD;
|
||||
if (to == 0 && from != 0) {
|
||||
action = T5S3_EPD_ACTION_DARK;
|
||||
} else if (to == 15 && from != 15) {
|
||||
action = T5S3_EPD_ACTION_LIGHT;
|
||||
}
|
||||
table[(to << 4) | from] = action;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The waveform drives white pixels that stay white with a dark and a light pulse, which flashes them.
|
||||
// Holding them instead leaves the rest of an area alone, so an update of a part of the screen does not leave visible columns.
|
||||
inline void t5s3_epd_build_quality_table(uint8_t* table, int phase, bool flash_white) {
|
||||
const uint8_t* packed = T5S3_EPD_QUALITY_LUT[phase];
|
||||
for (int to = 0; to < 16; to++) {
|
||||
for (int group = 0; group < 4; group++) {
|
||||
const uint8_t actions = *packed++;
|
||||
const int index = (to << 4) | (group * 4);
|
||||
table[index] = (actions >> 6) & 3;
|
||||
table[index + 1] = (actions >> 4) & 3;
|
||||
table[index + 2] = (actions >> 2) & 3;
|
||||
table[index + 3] = actions & 3;
|
||||
}
|
||||
}
|
||||
if (!flash_white) {
|
||||
table[(15 << 4) | 15] = T5S3_EPD_ACTION_HOLD;
|
||||
}
|
||||
}
|
||||
|
||||
// Keeps the pixels of changed columns and holds all others. A framebuffer byte of the changes holds the XOR of two pixels.
|
||||
inline void t5s3_epd_build_line_mask(uint8_t* mask, const uint8_t* changes) {
|
||||
for (int i = 0; i < T5S3_EPD_BUS_ROW_BYTES; i++) {
|
||||
const uint8_t first = changes[2 * i];
|
||||
const uint8_t second = changes[2 * i + 1];
|
||||
uint8_t value = 0;
|
||||
value |= (first & 0x0F) != 0 ? 0x03 : 0x00;
|
||||
value |= (first & 0xF0) != 0 ? 0x0C : 0x00;
|
||||
value |= (second & 0x0F) != 0 ? 0x30 : 0x00;
|
||||
value |= (second & 0xF0) != 0 ? 0xC0 : 0x00;
|
||||
mask[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Converts one row of target levels and one row of source levels into bus data.
|
||||
// All pointers must be 4-byte aligned. Four framebuffer bytes (8 pixels) become two bus bytes per step.
|
||||
inline void T5S3_EPD_IRAM t5s3_epd_prepare_row(
|
||||
uint8_t* out,
|
||||
const uint8_t* to_row,
|
||||
const uint8_t* from_row,
|
||||
const uint8_t* table,
|
||||
const uint8_t* mask
|
||||
) {
|
||||
const auto* to_words = reinterpret_cast<const uint32_t*>(to_row);
|
||||
const auto* from_words = reinterpret_cast<const uint32_t*>(from_row);
|
||||
const auto* mask_words = reinterpret_cast<const uint16_t*>(mask);
|
||||
auto* out_words = reinterpret_cast<uint16_t*>(out);
|
||||
for (int i = 0; i < T5S3_EPD_FB_ROW_BYTES / 4; i++) {
|
||||
const uint32_t to = to_words[i];
|
||||
const uint32_t from = from_words[i];
|
||||
// Table index of a pixel is (target level << 4) | source level, the even pixel of a byte is in its low nibble
|
||||
const uint32_t even = ((to & 0x0F0F0F0F) << 4) | (from & 0x0F0F0F0F);
|
||||
const uint32_t odd = (to & 0xF0F0F0F0) | ((from >> 4) & 0x0F0F0F0F);
|
||||
const uint32_t first = table[even & 0xFF] | (table[odd & 0xFF] << 2) | (table[(even >> 8) & 0xFF] << 4) | (table[(odd >> 8) & 0xFF] << 6);
|
||||
const uint32_t second = table[(even >> 16) & 0xFF] | (table[(odd >> 16) & 0xFF] << 2) | (table[even >> 24] << 4) | (table[odd >> 24] << 6);
|
||||
out_words[i] = static_cast<uint16_t>((first | (second << 8)) & mask_words[i]);
|
||||
}
|
||||
}
|
||||
201
Drivers/lilygo-t5s3-epd-module/source/t5s3_epd_waveform.h
Normal file
@ -0,0 +1,201 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Drive data of the ED047TC1 panel for the 16 level update, taken from the epdiy project
|
||||
// (https://github.com/vroland/epdiy, built-in waveform epdiy_ED047TC1, mode GC16).
|
||||
// This file is under the LGPL v3.0 or later like epdiy itself.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
constexpr int T5S3_EPD_QUALITY_PHASES = 30;
|
||||
|
||||
// Hold time of each phase in units of 0.1 microsecond
|
||||
constexpr uint16_t T5S3_EPD_QUALITY_HOLD[T5S3_EPD_QUALITY_PHASES] = {
|
||||
30, 30, 20, 20, 30, 30, 30, 40, 40, 50, 50, 50, 100, 200, 300, 10, 10, 8, 8, 8, 8, 8, 10, 10, 15, 15, 20, 20, 100, 300
|
||||
};
|
||||
|
||||
// Per phase: 16 target levels with 4 bytes each. A byte holds the 2 bit actions of 4 source levels,
|
||||
// the lowest source level in the top bits. Action 0 holds the pixel, 1 drives it dark and 2 light.
|
||||
constexpr uint8_t T5S3_EPD_QUALITY_LUT[T5S3_EPD_QUALITY_PHASES][64] = {
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05,
|
||||
0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05,
|
||||
0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05,
|
||||
0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15,
|
||||
0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15,
|
||||
0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15,
|
||||
0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x15,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55,
|
||||
0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x55,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55,
|
||||
0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55,
|
||||
0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55,
|
||||
0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00, 0x01, 0x55,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55,
|
||||
0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55,
|
||||
0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55,
|
||||
0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55, 0x00, 0x00, 0x05, 0x55,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55,
|
||||
0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55,
|
||||
0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55,
|
||||
0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55, 0x00, 0x00, 0x15, 0x55,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55,
|
||||
0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55,
|
||||
0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55,
|
||||
0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55, 0x00, 0x00, 0x55, 0x55,
|
||||
},
|
||||
{
|
||||
0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55,
|
||||
0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55,
|
||||
0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55,
|
||||
0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55, 0x00, 0x01, 0x55, 0x55,
|
||||
},
|
||||
{
|
||||
0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55,
|
||||
0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55,
|
||||
0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55,
|
||||
0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55, 0x00, 0x05, 0x55, 0x55,
|
||||
},
|
||||
{
|
||||
0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55,
|
||||
0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55,
|
||||
0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55,
|
||||
0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55, 0x00, 0x15, 0x55, 0x55,
|
||||
},
|
||||
{
|
||||
0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55,
|
||||
0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55,
|
||||
0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55,
|
||||
0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55, 0x00, 0x55, 0x55, 0x55,
|
||||
},
|
||||
{
|
||||
0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55,
|
||||
0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55,
|
||||
0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55,
|
||||
0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55, 0x01, 0x55, 0x55, 0x55,
|
||||
},
|
||||
{
|
||||
0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55,
|
||||
0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55,
|
||||
0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55,
|
||||
0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55, 0x05, 0x55, 0x55, 0x55,
|
||||
},
|
||||
{
|
||||
0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55,
|
||||
0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55,
|
||||
0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55,
|
||||
0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55, 0x15, 0x55, 0x55, 0x55,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
{
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xAA, 0xAA, 0xAA,
|
||||
},
|
||||
};
|
||||
@ -5,7 +5,7 @@ if (DEFINED ENV{ESP_IDF_VERSION})
|
||||
SRC_DIRS "Source/"
|
||||
INCLUDE_DIRS "Source/"
|
||||
PRIV_INCLUDE_DIRS "Private/"
|
||||
REQUIRES lvgl
|
||||
REQUIRES lvgl lodepng-module
|
||||
)
|
||||
|
||||
else()
|
||||
@ -25,6 +25,6 @@ else()
|
||||
)
|
||||
|
||||
target_link_libraries(lv_screenshot
|
||||
PUBLIC lvgl
|
||||
PUBLIC lvgl lodepng-module
|
||||
)
|
||||
endif()
|
||||
@ -1,11 +1,32 @@
|
||||
#include "src/libs/lodepng/lodepng.h"
|
||||
#include "save_png.h"
|
||||
|
||||
#include <lodepng/lodepng.h>
|
||||
#include <lvgl.h>
|
||||
|
||||
bool lv_screenshot_save_png_file(const uint8_t* image, uint32_t w, uint32_t h, uint32_t bpp, const char* filename) {
|
||||
unsigned char* png = NULL;
|
||||
size_t png_size = 0;
|
||||
unsigned error;
|
||||
if (bpp == 32) {
|
||||
return lodepng_encode32_file(filename, image, w, h) == 0;
|
||||
error = lodepng_encode32(&png, &png_size, image, w, h);
|
||||
} else if (bpp == 24) {
|
||||
return lodepng_encode24_file(filename, image, w, h) == 0;
|
||||
error = lodepng_encode24(&png, &png_size, image, w, h);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (error) {
|
||||
lodepng_free(png);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Written through lv_fs, so LVGL drive letter paths ("A:...") work
|
||||
bool success = false;
|
||||
lv_fs_file_t file;
|
||||
if (lv_fs_open(&file, filename, LV_FS_MODE_WR) == LV_FS_RES_OK) {
|
||||
uint32_t written = 0;
|
||||
success = lv_fs_write(&file, png, png_size, &written) == LV_FS_RES_OK && written == png_size;
|
||||
lv_fs_close(&file);
|
||||
}
|
||||
lodepng_free(png);
|
||||
return success;
|
||||
}
|
||||
|
||||
17
Modules/binfont-module/CMakeLists.txt
Normal file
@ -0,0 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
||||
|
||||
list(APPEND REQUIRES_LIST
|
||||
TactilityKernel
|
||||
graphics-module
|
||||
)
|
||||
|
||||
tactility_add_module(binfont-module
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS include/
|
||||
PRIV_INCLUDE_DIRS private/
|
||||
REQUIRES ${REQUIRES_LIST}
|
||||
)
|
||||
195
Modules/binfont-module/LICENSE-Apache-2.0.md
Normal file
@ -0,0 +1,195 @@
|
||||
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.
|
||||
|
||||
14
Modules/binfont-module/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# binfont-module
|
||||
|
||||
Bitmap fonts in the [lv_font_conv](https://github.com/lvgl/lv_font_conv) binary format (`--format bin`), usable with and without LVGL.
|
||||
|
||||
- `<binfont/binfont.h>`: load fonts and decode glyphs
|
||||
- `<binfont/generator.h>`: rasterize a TTF into the binary format on device
|
||||
- `<binfont/render.h>`: draw glyphs and text into a `PixelBuffer`
|
||||
|
||||
For LVGL, see `lvgl_binfont_create()` in lvgl-module's `<lvgl/binfont.h>`.
|
||||
|
||||
## License
|
||||
|
||||
This module is licensed under the [Apache v2.0](LICENSE-Apache-2.0.md) license.
|
||||
Uses [stb_truetype](https://github.com/nothings/stb) (public domain).
|
||||
118
Modules/binfont-module/include/binfont/binfont.h
Normal file
@ -0,0 +1,118 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/error.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* A bitmap font in the lv_font_conv binary format (lv_font_conv --format bin).
|
||||
* Thread-safe: a font can be used from multiple tasks at the same time.
|
||||
*/
|
||||
struct BinFont;
|
||||
|
||||
typedef struct BinFontMetrics {
|
||||
/** Font size in pixels, as passed to the converter */
|
||||
uint16_t size;
|
||||
/** Pixels above the baseline */
|
||||
int16_t ascent;
|
||||
/** Pixels below the baseline (negative) */
|
||||
int16_t descent;
|
||||
/** ascent - descent */
|
||||
uint16_t line_height;
|
||||
/** Distance from the bottom of the line to the baseline (-descent) */
|
||||
int16_t base_line;
|
||||
int16_t underline_position;
|
||||
uint16_t underline_thickness;
|
||||
/** Bits per pixel of the stored bitmaps (1-4) */
|
||||
uint8_t bpp;
|
||||
} BinFontMetrics;
|
||||
|
||||
typedef struct BinFontGlyph {
|
||||
uint32_t glyph_id;
|
||||
/** Horizontal advance in 1/16th pixels */
|
||||
uint32_t advance_x16;
|
||||
/** Bounding box left, relative to the pen position */
|
||||
int16_t x;
|
||||
/** Bounding box bottom, relative to the baseline (positive is up) */
|
||||
int16_t y;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
} BinFontGlyph;
|
||||
|
||||
/**
|
||||
* @brief Opens a font file.
|
||||
* The whole file is loaded into external memory (PSRAM) when available, otherwise into internal memory.
|
||||
* When that allocation fails, only the lookup tables are loaded and glyph data is read from the file
|
||||
* on demand: the file is opened and closed for every glyph lookup, unless it's kept open with
|
||||
* binfont_begin()/binfont_end().
|
||||
* @param[in] path the file path
|
||||
* @param[out] out the opened font
|
||||
* @retval ERROR_NONE on success
|
||||
* @retval ERROR_NOT_FOUND when the file can't be opened
|
||||
* @retval ERROR_NOT_SUPPORTED when the file is not a valid or supported font
|
||||
* @retval ERROR_OUT_OF_MEMORY when memory allocation failed
|
||||
*/
|
||||
error_t binfont_open_file(const char* path, struct BinFont** out);
|
||||
|
||||
/**
|
||||
* @brief Opens a font from memory.
|
||||
* @param[in] data the font file contents, which must stay valid until binfont_close()
|
||||
* @param[in] size the size of data in bytes
|
||||
* @param[in] take_ownership when true, data is released with memory_free() by binfont_close() (also on failure)
|
||||
* @param[out] out the opened font
|
||||
* @retval ERROR_NONE on success
|
||||
* @retval ERROR_NOT_SUPPORTED when the data is not a valid or supported font
|
||||
* @retval ERROR_OUT_OF_MEMORY when memory allocation failed
|
||||
*/
|
||||
error_t binfont_open_memory(const void* data, size_t size, bool take_ownership, struct BinFont** out);
|
||||
|
||||
/** Releases the font and all its resources. */
|
||||
void binfont_close(struct BinFont* font);
|
||||
|
||||
/**
|
||||
* @brief Keeps the font file open until the matching binfont_end(), so rendering many glyphs
|
||||
* doesn't open and close the file for each of them. Calls can be nested and used from multiple tasks.
|
||||
* Does nothing for fonts that are fully loaded in memory.
|
||||
* @retval ERROR_NONE on success
|
||||
* @retval ERROR_NOT_FOUND when the file can't be opened
|
||||
*/
|
||||
error_t binfont_begin(struct BinFont* font);
|
||||
|
||||
/** Ends a binfont_begin() session. The file is closed when no sessions remain. */
|
||||
void binfont_end(struct BinFont* font);
|
||||
|
||||
void binfont_get_metrics(const struct BinFont* font, BinFontMetrics* out);
|
||||
|
||||
/**
|
||||
* @param[in] font the font
|
||||
* @param[in] codepoint the Unicode codepoint
|
||||
* @param[out] out the glyph info
|
||||
* @return false when the font has no glyph for this codepoint
|
||||
*/
|
||||
bool binfont_get_glyph(struct BinFont* font, uint32_t codepoint, BinFontGlyph* out);
|
||||
|
||||
/** @return the kerning adjustment for a glyph pair in 1/16th pixels, or 0 when there is none */
|
||||
int32_t binfont_get_kerning_x16(struct BinFont* font, uint32_t left_glyph_id, uint32_t right_glyph_id);
|
||||
|
||||
/**
|
||||
* @brief Decodes a glyph bitmap as 8-bit alpha (0 = transparent, 255 = opaque).
|
||||
* @param[in] font the font
|
||||
* @param[in] glyph a glyph from binfont_get_glyph()
|
||||
* @param[out] out the destination: glyph->height rows of glyph->width bytes
|
||||
* @param[in] stride bytes per row in out (at least glyph->width)
|
||||
* @retval ERROR_NONE on success
|
||||
* @retval ERROR_INVALID_ARGUMENT when stride is too small
|
||||
* @retval ERROR_RESOURCE when the glyph data can't be read
|
||||
*/
|
||||
error_t binfont_get_glyph_bitmap(struct BinFont* font, const BinFontGlyph* glyph, uint8_t* out, size_t stride);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
40
Modules/binfont-module/include/binfont/generator.h
Normal file
@ -0,0 +1,40 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/error.h>
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct BinFontGeneratorConfig {
|
||||
/** Path to a TrueType (.ttf) file */
|
||||
const char* ttf_path;
|
||||
/** Font size in pixels (em height) */
|
||||
uint16_t size;
|
||||
/** Bits per pixel: 1, 2, 3 or 4 */
|
||||
uint8_t bpp;
|
||||
/** Codepoints to include. Codepoints that the TTF doesn't contain are skipped. */
|
||||
const uint32_t* codepoints;
|
||||
size_t codepoint_count;
|
||||
} BinFontGeneratorConfig;
|
||||
|
||||
/**
|
||||
* @brief Rasterizes a TrueType font into the lv_font_conv binary format.
|
||||
* @param[in] config the generator configuration
|
||||
* @param[out] out_data the generated font file contents, to be released with memory_free()
|
||||
* @param[out] out_size the size of out_data in bytes
|
||||
* @retval ERROR_NONE on success
|
||||
* @retval ERROR_INVALID_ARGUMENT when the configuration is invalid
|
||||
* @retval ERROR_NOT_FOUND when the TTF file can't be read
|
||||
* @retval ERROR_NOT_SUPPORTED when the TTF file can't be parsed or contains none of the codepoints
|
||||
* @retval ERROR_OUT_OF_MEMORY when memory allocation failed
|
||||
*/
|
||||
error_t binfont_generate(const BinFontGeneratorConfig* config, uint8_t** out_data, size_t* out_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
14
Modules/binfont-module/include/binfont/module.h
Normal file
@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/module.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern struct Module binfont_module;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
57
Modules/binfont-module/include/binfont/render.h
Normal file
@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <binfont/binfont.h>
|
||||
#include <graphics/pixel_buffer.h>
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Alpha-blends one glyph over the existing pixels of a PixelBuffer.
|
||||
* @param[out] buffer destination, in any pixel format
|
||||
* @param[in] x pen position x
|
||||
* @param[in] baseline_y pen position y (the baseline)
|
||||
* @param[in] font the font
|
||||
* @param[in] codepoint the Unicode codepoint
|
||||
* @param[in] color glyph colour as RGB565
|
||||
* @param[in] conversion how colours reduce into the buffer's native format (see PixelBufferConversion)
|
||||
* @return the horizontal advance in pixels, or 0 when the font has no glyph for the codepoint
|
||||
*/
|
||||
int binfont_draw_glyph_rgb565(
|
||||
struct PixelBuffer* buffer,
|
||||
int x,
|
||||
int baseline_y,
|
||||
struct BinFont* font,
|
||||
uint32_t codepoint,
|
||||
uint16_t color,
|
||||
enum PixelBufferConversion conversion
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Alpha-blends a UTF-8 string over the existing pixels of a PixelBuffer, applying kerning.
|
||||
* @param[out] buffer destination, in any pixel format
|
||||
* @param[in] x pen position x
|
||||
* @param[in] baseline_y pen position y (the baseline)
|
||||
* @param[in] font the font
|
||||
* @param[in] text UTF-8 encoded, null-terminated text
|
||||
* @param[in] color glyph colour as RGB565
|
||||
* @param[in] conversion how colours reduce into the buffer's native format (see PixelBufferConversion)
|
||||
* @return the total advance in pixels
|
||||
*/
|
||||
int binfont_draw_text_rgb565(
|
||||
struct PixelBuffer* buffer,
|
||||
int x,
|
||||
int baseline_y,
|
||||
struct BinFont* font,
|
||||
const char* text,
|
||||
uint16_t color,
|
||||
enum PixelBufferConversion conversion
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
19
Modules/binfont-module/private/binfont_private.h
Normal file
@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <binfont/binfont.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Opens a font file without loading it into memory: only the lookup tables are loaded and
|
||||
* glyph data is read from the file on demand. binfont_open_file() falls back to this when the file
|
||||
* doesn't fit in memory.
|
||||
*/
|
||||
error_t binfont_open_file_streaming(const char* path, struct BinFont** out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
5079
Modules/binfont-module/private/stb_truetype.h
Normal file
803
Modules/binfont-module/source/binfont.cpp
Normal file
@ -0,0 +1,803 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <binfont/binfont.h>
|
||||
#include <binfont_private.h>
|
||||
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
#include <tactility/memory.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <initializer_list>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t RECORD_HEADER_SIZE = 8;
|
||||
constexpr size_t HEAD_MIN_SIZE = 48;
|
||||
constexpr size_t CMAP_SUBTABLE_HEADER_SIZE = 16;
|
||||
constexpr size_t GLYPH_HEADER_MAX_BYTES = 16;
|
||||
|
||||
enum CmapFormat : uint8_t {
|
||||
CMAP_FORMAT_0 = 0,
|
||||
CMAP_FORMAT_SPARSE = 1,
|
||||
CMAP_FORMAT_0_TINY = 2,
|
||||
CMAP_FORMAT_SPARSE_TINY = 3,
|
||||
};
|
||||
|
||||
enum Compression : uint8_t {
|
||||
COMPRESSION_NONE = 0,
|
||||
COMPRESSION_RLE_PREFILTER = 1,
|
||||
COMPRESSION_RLE = 2,
|
||||
};
|
||||
|
||||
uint16_t read_u16(const uint8_t* p) {
|
||||
return static_cast<uint16_t>(p[0] | (p[1] << 8));
|
||||
}
|
||||
|
||||
int16_t read_i16(const uint8_t* p) {
|
||||
return static_cast<int16_t>(read_u16(p));
|
||||
}
|
||||
|
||||
uint32_t read_u32(const uint8_t* p) {
|
||||
return static_cast<uint32_t>(p[0]) | (static_cast<uint32_t>(p[1]) << 8) | (static_cast<uint32_t>(p[2]) << 16) | (static_cast<uint32_t>(p[3]) << 24);
|
||||
}
|
||||
|
||||
bool has_marker(const uint8_t* record, const char* marker) {
|
||||
return memcmp(record + 4, marker, 4) == 0;
|
||||
}
|
||||
|
||||
/** MSB-first bit reader. Bits past the end read as 0. */
|
||||
class BitReader {
|
||||
const uint8_t* data;
|
||||
size_t sizeBits;
|
||||
size_t position;
|
||||
|
||||
public:
|
||||
BitReader(const uint8_t* data, size_t size, size_t bitOffset = 0) : data(data), sizeBits(size * 8), position(bitOffset) {}
|
||||
|
||||
uint32_t read(uint8_t bits) {
|
||||
uint32_t value = 0;
|
||||
for (uint8_t i = 0; i < bits; i++) {
|
||||
value <<= 1;
|
||||
if (position < sizeBits) {
|
||||
value |= (data[position >> 3] >> (7 - (position & 7))) & 1U;
|
||||
}
|
||||
position++;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int32_t readSigned(uint8_t bits) {
|
||||
if (bits == 0) {
|
||||
return 0;
|
||||
}
|
||||
const uint32_t value = read(bits);
|
||||
if (value & (1U << (bits - 1))) {
|
||||
return static_cast<int32_t>(value | (~0U << bits));
|
||||
}
|
||||
return static_cast<int32_t>(value);
|
||||
}
|
||||
};
|
||||
|
||||
/** Decoder for the modified I3BN RLE stream of lv_font_conv. */
|
||||
class RleReader {
|
||||
enum class State { Single, Repeated, Counter };
|
||||
|
||||
BitReader& bits;
|
||||
uint8_t bpp;
|
||||
State state = State::Single;
|
||||
bool first = true;
|
||||
uint8_t previous = 0;
|
||||
uint32_t count = 0;
|
||||
|
||||
uint8_t readSingle() {
|
||||
previous = static_cast<uint8_t>(bits.read(bpp));
|
||||
state = State::Single;
|
||||
return previous;
|
||||
}
|
||||
|
||||
public:
|
||||
RleReader(BitReader& bits, uint8_t bpp) : bits(bits), bpp(bpp) {}
|
||||
|
||||
uint8_t next() {
|
||||
switch (state) {
|
||||
case State::Single: {
|
||||
const auto value = static_cast<uint8_t>(bits.read(bpp));
|
||||
if (!first && value == previous) {
|
||||
count = 0;
|
||||
state = State::Repeated;
|
||||
}
|
||||
first = false;
|
||||
previous = value;
|
||||
return value;
|
||||
}
|
||||
case State::Repeated: {
|
||||
count++;
|
||||
if (bits.read(1) == 0) {
|
||||
return readSingle();
|
||||
}
|
||||
if (count == 11) {
|
||||
count = bits.read(6);
|
||||
if (count != 0) {
|
||||
state = State::Counter;
|
||||
} else {
|
||||
return readSingle();
|
||||
}
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
case State::Counter:
|
||||
count--;
|
||||
if (count == 0) {
|
||||
return readSingle();
|
||||
}
|
||||
return previous;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct Header {
|
||||
uint16_t fontSize;
|
||||
int16_t ascent;
|
||||
int16_t descent;
|
||||
uint16_t defaultAdvanceWidth;
|
||||
uint16_t kerningScale;
|
||||
uint8_t indexToLocFormat;
|
||||
uint8_t glyphIdFormat;
|
||||
uint8_t advanceWidthFormat;
|
||||
uint8_t bpp;
|
||||
uint8_t xyBits;
|
||||
uint8_t whBits;
|
||||
uint8_t advanceWidthBits;
|
||||
uint8_t compression;
|
||||
uint8_t subpixelMode;
|
||||
int16_t underlinePosition;
|
||||
uint16_t underlineThickness;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct BinFont {
|
||||
Header header;
|
||||
/** The whole file in memory mode, nullptr in stream mode */
|
||||
const uint8_t* data;
|
||||
bool ownsData;
|
||||
/** Stream mode only: the file path, and the file while it's open (guarded by mutex) */
|
||||
char* path;
|
||||
FILE* file;
|
||||
uint32_t sessionCount;
|
||||
Mutex mutex;
|
||||
/** Owned copy of the cmap/loca/kern records in stream mode */
|
||||
uint8_t* tables;
|
||||
/** Each record pointer includes its 8-byte record header */
|
||||
const uint8_t* cmap;
|
||||
size_t cmapSize;
|
||||
const uint8_t* loca;
|
||||
size_t locaSize;
|
||||
uint32_t locaCount;
|
||||
const uint8_t* kern;
|
||||
size_t kernSize;
|
||||
size_t glyfOffset;
|
||||
size_t glyfSize;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
bool read_at(BinFont* font, size_t offset, uint8_t* out, size_t size) {
|
||||
if (fseek(font->file, static_cast<long>(offset), SEEK_SET) != 0) {
|
||||
return false;
|
||||
}
|
||||
return fread(out, 1, size, font->file) == size;
|
||||
}
|
||||
|
||||
bool parse_header(const uint8_t* record, size_t size, Header& header) {
|
||||
if (size < HEAD_MIN_SIZE || !has_marker(record, "head")) {
|
||||
return false;
|
||||
}
|
||||
header.fontSize = read_u16(record + 14);
|
||||
header.ascent = read_i16(record + 16);
|
||||
header.descent = read_i16(record + 18);
|
||||
header.defaultAdvanceWidth = read_u16(record + 30);
|
||||
header.kerningScale = read_u16(record + 32);
|
||||
header.indexToLocFormat = record[34];
|
||||
header.glyphIdFormat = record[35];
|
||||
header.advanceWidthFormat = record[36];
|
||||
header.bpp = record[37];
|
||||
header.xyBits = record[38];
|
||||
header.whBits = record[39];
|
||||
header.advanceWidthBits = record[40];
|
||||
header.compression = record[41];
|
||||
header.subpixelMode = record[42];
|
||||
header.underlinePosition = read_i16(record + 44);
|
||||
header.underlineThickness = read_u16(record + 46);
|
||||
|
||||
return header.bpp >= 1 && header.bpp <= 4 &&
|
||||
header.compression <= COMPRESSION_RLE &&
|
||||
header.subpixelMode == 0 &&
|
||||
header.indexToLocFormat <= 1 &&
|
||||
header.xyBits <= 16 && header.whBits <= 16 && header.advanceWidthBits <= 32;
|
||||
}
|
||||
|
||||
bool validate_cmap(const uint8_t* cmap, size_t size) {
|
||||
if (size < 12) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t count = read_u32(cmap + 8);
|
||||
if (count > (size - 12) / CMAP_SUBTABLE_HEADER_SIZE) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
const uint8_t* subtable = cmap + 12 + i * CMAP_SUBTABLE_HEADER_SIZE;
|
||||
const size_t data_offset = read_u32(subtable);
|
||||
const size_t range_length = read_u16(subtable + 8);
|
||||
const size_t entries = read_u16(subtable + 12);
|
||||
size_t data_size;
|
||||
switch (subtable[14]) {
|
||||
case CMAP_FORMAT_0: data_size = range_length; break;
|
||||
case CMAP_FORMAT_SPARSE: data_size = entries * 4; break;
|
||||
case CMAP_FORMAT_0_TINY: data_size = 0; break;
|
||||
case CMAP_FORMAT_SPARSE_TINY: data_size = entries * 2; break;
|
||||
default: return false;
|
||||
}
|
||||
if (data_size > 0 && (data_offset > size || data_size > size - data_offset)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool validate_kern(const uint8_t* kern, size_t size, uint8_t glyphIdFormat) {
|
||||
if (size < 16) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t format = kern[8];
|
||||
if (format == 0) {
|
||||
const size_t count = read_u32(kern + 12);
|
||||
const size_t entry_size = (glyphIdFormat ? 4 : 2) + 1;
|
||||
return count <= (size - 16) / entry_size;
|
||||
} else if (format == 3) {
|
||||
const size_t glyph_count = read_u16(kern + 12);
|
||||
const size_t left_count = kern[14];
|
||||
const size_t right_count = kern[15];
|
||||
return 16 + glyph_count * 2 + left_count * right_count <= size;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates all tables. In memory mode the table pointers point into font->data, in stream mode
|
||||
* the lookup tables are copied into font->tables.
|
||||
*/
|
||||
error_t parse_layout(BinFont* font, size_t fileSize) {
|
||||
uint8_t record_header[RECORD_HEADER_SIZE];
|
||||
uint8_t head[64];
|
||||
|
||||
auto read = [font](size_t offset, uint8_t* out, size_t size) {
|
||||
if (font->data != nullptr) {
|
||||
memcpy(out, font->data + offset, size);
|
||||
return true;
|
||||
}
|
||||
return read_at(font, offset, out, size);
|
||||
};
|
||||
|
||||
// head
|
||||
if (fileSize < RECORD_HEADER_SIZE || !read(0, record_header, RECORD_HEADER_SIZE)) {
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
const size_t head_size = read_u32(record_header);
|
||||
if (head_size < HEAD_MIN_SIZE || head_size > fileSize) {
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
if (!read(0, head, HEAD_MIN_SIZE) || !parse_header(head, HEAD_MIN_SIZE, font->header)) {
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// cmap, loca, glyf and optionally kern follow each other
|
||||
size_t offsets[4] = {};
|
||||
size_t sizes[4] = {};
|
||||
const char* markers[4] = { "cmap", "loca", "glyf", "kern" };
|
||||
size_t offset = head_size;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (offset > fileSize || fileSize - offset < RECORD_HEADER_SIZE || !read(offset, record_header, RECORD_HEADER_SIZE)) {
|
||||
if (i == 3) {
|
||||
break; // kern is optional
|
||||
}
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
const size_t size = read_u32(record_header);
|
||||
if (!has_marker(record_header, markers[i]) || size < RECORD_HEADER_SIZE || size > fileSize - offset) {
|
||||
if (i == 3) {
|
||||
break;
|
||||
}
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
offsets[i] = offset;
|
||||
sizes[i] = size;
|
||||
offset += size;
|
||||
}
|
||||
|
||||
font->glyfOffset = offsets[2];
|
||||
font->glyfSize = sizes[2];
|
||||
|
||||
if (font->data != nullptr) {
|
||||
font->cmap = font->data + offsets[0];
|
||||
font->loca = font->data + offsets[1];
|
||||
font->kern = sizes[3] ? font->data + offsets[3] : nullptr;
|
||||
} else {
|
||||
font->tables = static_cast<uint8_t*>(memory_alloc(sizes[0] + sizes[1] + sizes[3]));
|
||||
if (font->tables == nullptr) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
uint8_t* cursor = font->tables;
|
||||
for (int i : { 0, 1, 3 }) {
|
||||
if (sizes[i] > 0 && !read_at(font, offsets[i], cursor, sizes[i])) {
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
cursor += sizes[i];
|
||||
}
|
||||
font->cmap = font->tables;
|
||||
font->loca = font->tables + sizes[0];
|
||||
font->kern = sizes[3] ? font->tables + sizes[0] + sizes[1] : nullptr;
|
||||
}
|
||||
font->cmapSize = sizes[0];
|
||||
font->locaSize = sizes[1];
|
||||
font->kernSize = sizes[3];
|
||||
|
||||
if (!validate_cmap(font->cmap, font->cmapSize)) {
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (font->locaSize < 12) {
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
font->locaCount = read_u32(font->loca + 8);
|
||||
const size_t loca_entry_size = font->header.indexToLocFormat ? 4 : 2;
|
||||
if (font->locaCount > (font->locaSize - 12) / loca_entry_size) {
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
if (font->kern != nullptr && !validate_kern(font->kern, font->kernSize, font->header.glyphIdFormat)) {
|
||||
font->kern = nullptr;
|
||||
font->kernSize = 0;
|
||||
}
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
BinFont* create_font() {
|
||||
auto* font = static_cast<BinFont*>(memory_calloc(1, sizeof(BinFont)));
|
||||
if (font != nullptr) {
|
||||
mutex_construct(&font->mutex);
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
uint32_t find_glyph_id(const BinFont* font, uint32_t codepoint) {
|
||||
const uint8_t* cmap = font->cmap;
|
||||
const uint32_t count = read_u32(cmap + 8);
|
||||
|
||||
// Find the last subtable that starts at or before the codepoint
|
||||
uint32_t low = 0;
|
||||
uint32_t high = count;
|
||||
while (low < high) {
|
||||
const uint32_t mid = (low + high) / 2;
|
||||
if (read_u32(cmap + 12 + mid * CMAP_SUBTABLE_HEADER_SIZE + 4) <= codepoint) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
if (low == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint8_t* subtable = cmap + 12 + (low - 1) * CMAP_SUBTABLE_HEADER_SIZE;
|
||||
const uint32_t data_offset = read_u32(subtable);
|
||||
const uint32_t range_start = read_u32(subtable + 4);
|
||||
const uint16_t range_length = read_u16(subtable + 8);
|
||||
const uint16_t glyph_id_start = read_u16(subtable + 10);
|
||||
const uint16_t entries = read_u16(subtable + 12);
|
||||
const uint32_t relative = codepoint - range_start;
|
||||
if (relative >= range_length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (subtable[14]) {
|
||||
case CMAP_FORMAT_0:
|
||||
return glyph_id_start + cmap[data_offset + relative];
|
||||
case CMAP_FORMAT_0_TINY:
|
||||
return glyph_id_start + relative;
|
||||
case CMAP_FORMAT_SPARSE:
|
||||
case CMAP_FORMAT_SPARSE_TINY: {
|
||||
const uint8_t* list = cmap + data_offset;
|
||||
uint32_t list_low = 0;
|
||||
uint32_t list_high = entries;
|
||||
while (list_low < list_high) {
|
||||
const uint32_t mid = (list_low + list_high) / 2;
|
||||
const uint16_t value = read_u16(list + mid * 2);
|
||||
if (value == relative) {
|
||||
if (subtable[14] == CMAP_FORMAT_SPARSE_TINY) {
|
||||
return glyph_id_start + mid;
|
||||
}
|
||||
return glyph_id_start + read_u16(list + entries * 2 + mid * 2);
|
||||
} else if (value < relative) {
|
||||
list_low = mid + 1;
|
||||
} else {
|
||||
list_high = mid;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/** Gets the offset and size of a glyph record, relative to the glyf data (after its record header). */
|
||||
bool get_glyph_location(const BinFont* font, uint32_t glyphId, size_t& offset, size_t& size) {
|
||||
if (glyphId == 0 || glyphId >= font->locaCount) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t* entries = font->loca + 12;
|
||||
auto entry = [&](uint32_t index) -> size_t {
|
||||
return font->header.indexToLocFormat ? read_u32(entries + index * 4) : read_u16(entries + index * 2);
|
||||
};
|
||||
const size_t start = entry(glyphId);
|
||||
const size_t end = (glyphId + 1 < font->locaCount) ? entry(glyphId + 1) : font->glyfSize;
|
||||
if (start < RECORD_HEADER_SIZE || end < start || end > font->glyfSize) {
|
||||
return false;
|
||||
}
|
||||
offset = start;
|
||||
size = end - start;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Reads from the font file in stream mode. Outside of a session, the file is opened for this read only. */
|
||||
bool read_glyph_data(BinFont* font, size_t offset, uint8_t* out, size_t size) {
|
||||
mutex_lock(&font->mutex);
|
||||
const bool temporary = font->file == nullptr;
|
||||
if (temporary) {
|
||||
font->file = fopen(font->path, "rb");
|
||||
}
|
||||
const bool success = font->file != nullptr && read_at(font, offset, out, size);
|
||||
if (temporary && font->file != nullptr) {
|
||||
fclose(font->file);
|
||||
font->file = nullptr;
|
||||
}
|
||||
mutex_unlock(&font->mutex);
|
||||
return success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the record of a glyph. In memory mode it points into the font data, in stream mode it
|
||||
* is read into buffer (bufferSize bytes) or, when that is too small, into an allocation returned
|
||||
* via allocated (to be freed by the caller).
|
||||
*/
|
||||
bool get_glyph_record(BinFont* font, uint32_t glyphId, size_t maxSize, uint8_t* buffer, size_t bufferSize, const uint8_t*& record, size_t& size, uint8_t*& allocated) {
|
||||
size_t offset;
|
||||
allocated = nullptr;
|
||||
if (!get_glyph_location(font, glyphId, offset, size)) {
|
||||
return false;
|
||||
}
|
||||
if (font->data != nullptr) {
|
||||
record = font->data + font->glyfOffset + offset;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (size > maxSize) {
|
||||
size = maxSize;
|
||||
}
|
||||
uint8_t* target = buffer;
|
||||
if (size > bufferSize) {
|
||||
allocated = static_cast<uint8_t*>(memory_alloc(size));
|
||||
if (allocated == nullptr) {
|
||||
return false;
|
||||
}
|
||||
target = allocated;
|
||||
}
|
||||
|
||||
const bool success = read_glyph_data(font, font->glyfOffset + offset, target, size);
|
||||
|
||||
if (!success) {
|
||||
memory_free(allocated);
|
||||
allocated = nullptr;
|
||||
return false;
|
||||
}
|
||||
record = target;
|
||||
return true;
|
||||
}
|
||||
|
||||
void parse_glyph(const Header& header, BitReader& reader, uint32_t glyphId, BinFontGlyph* out) {
|
||||
uint32_t advance = header.advanceWidthBits ? reader.read(header.advanceWidthBits) : header.defaultAdvanceWidth;
|
||||
if (header.advanceWidthFormat == 0) {
|
||||
advance *= 16;
|
||||
}
|
||||
out->glyph_id = glyphId;
|
||||
out->advance_x16 = advance;
|
||||
out->x = static_cast<int16_t>(reader.readSigned(header.xyBits));
|
||||
out->y = static_cast<int16_t>(reader.readSigned(header.xyBits));
|
||||
out->width = static_cast<uint16_t>(reader.read(header.whBits));
|
||||
out->height = static_cast<uint16_t>(reader.read(header.whBits));
|
||||
}
|
||||
|
||||
/** Opens a file for reading and determines its size */
|
||||
error_t open_file(const char* path, FILE*& file, size_t& size) {
|
||||
file = fopen(path, "rb");
|
||||
if (file == nullptr) {
|
||||
return ERROR_NOT_FOUND;
|
||||
}
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
fclose(file);
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
const long file_size = ftell(file);
|
||||
if (file_size <= 0 || fseek(file, 0, SEEK_SET) != 0) {
|
||||
fclose(file);
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
size = static_cast<size_t>(file_size);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
/** Loads the lookup tables from an open file, then closes it. Glyph data is read on demand. */
|
||||
error_t open_streaming(const char* path, FILE* file, size_t size, BinFont** out) {
|
||||
BinFont* font = create_font();
|
||||
if (font == nullptr) {
|
||||
fclose(file);
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
font->file = file;
|
||||
|
||||
const size_t path_size = strlen(path) + 1;
|
||||
font->path = static_cast<char*>(memory_alloc(path_size));
|
||||
if (font->path == nullptr) {
|
||||
binfont_close(font);
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
memcpy(font->path, path, path_size);
|
||||
|
||||
const error_t error = parse_layout(font, size);
|
||||
fclose(font->file);
|
||||
font->file = nullptr;
|
||||
if (error != ERROR_NONE) {
|
||||
binfont_close(font);
|
||||
return error;
|
||||
}
|
||||
*out = font;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
error_t binfont_open_memory(const void* data, size_t size, bool take_ownership, BinFont** out) {
|
||||
BinFont* font = create_font();
|
||||
if (font == nullptr) {
|
||||
if (take_ownership) {
|
||||
memory_free(const_cast<void*>(data));
|
||||
}
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
font->data = static_cast<const uint8_t*>(data);
|
||||
font->ownsData = take_ownership;
|
||||
|
||||
const error_t error = parse_layout(font, size);
|
||||
if (error != ERROR_NONE) {
|
||||
binfont_close(font);
|
||||
return error;
|
||||
}
|
||||
*out = font;
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t binfont_open_file(const char* path, BinFont** out) {
|
||||
FILE* file;
|
||||
size_t size;
|
||||
const error_t open_error = open_file(path, file, size);
|
||||
if (open_error != ERROR_NONE) {
|
||||
return open_error;
|
||||
}
|
||||
|
||||
const uint16_t memory_capability = memory_external_total() > 0 ? MEMORY_CAPABILITY_EXTERNAL : MEMORY_CAPABILITY_INTERNAL;
|
||||
const MemoryPolicy policy = { .required = memory_capability, .desired = 0, .alignment = 0 };
|
||||
auto* data = static_cast<uint8_t*>(memory_alloc_with_policy(size, &policy));
|
||||
if (data == nullptr) {
|
||||
return open_streaming(path, file, size, out);
|
||||
}
|
||||
|
||||
const bool success = fread(data, 1, size, file) == size;
|
||||
fclose(file);
|
||||
if (!success) {
|
||||
memory_free(data);
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
return binfont_open_memory(data, size, true, out);
|
||||
}
|
||||
|
||||
error_t binfont_open_file_streaming(const char* path, BinFont** out) {
|
||||
FILE* file;
|
||||
size_t size;
|
||||
const error_t open_error = open_file(path, file, size);
|
||||
if (open_error != ERROR_NONE) {
|
||||
return open_error;
|
||||
}
|
||||
return open_streaming(path, file, size, out);
|
||||
}
|
||||
|
||||
error_t binfont_begin(BinFont* font) {
|
||||
if (font->data != nullptr) {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
mutex_lock(&font->mutex);
|
||||
if (font->sessionCount == 0) {
|
||||
font->file = fopen(font->path, "rb");
|
||||
if (font->file == nullptr) {
|
||||
mutex_unlock(&font->mutex);
|
||||
return ERROR_NOT_FOUND;
|
||||
}
|
||||
}
|
||||
font->sessionCount++;
|
||||
mutex_unlock(&font->mutex);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
void binfont_end(BinFont* font) {
|
||||
if (font->data != nullptr) {
|
||||
return;
|
||||
}
|
||||
mutex_lock(&font->mutex);
|
||||
if (font->sessionCount > 0) {
|
||||
font->sessionCount--;
|
||||
if (font->sessionCount == 0) {
|
||||
fclose(font->file);
|
||||
font->file = nullptr;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&font->mutex);
|
||||
}
|
||||
|
||||
void binfont_close(BinFont* font) {
|
||||
if (font->ownsData) {
|
||||
memory_free(const_cast<uint8_t*>(font->data));
|
||||
}
|
||||
if (font->file != nullptr) {
|
||||
fclose(font->file);
|
||||
}
|
||||
memory_free(font->path);
|
||||
memory_free(font->tables);
|
||||
mutex_destruct(&font->mutex);
|
||||
memory_free(font);
|
||||
}
|
||||
|
||||
void binfont_get_metrics(const BinFont* font, BinFontMetrics* out) {
|
||||
const Header& header = font->header;
|
||||
out->size = header.fontSize;
|
||||
out->ascent = header.ascent;
|
||||
out->descent = header.descent;
|
||||
out->line_height = static_cast<uint16_t>(header.ascent - header.descent);
|
||||
out->base_line = static_cast<int16_t>(-header.descent);
|
||||
out->underline_position = header.underlinePosition;
|
||||
out->underline_thickness = header.underlineThickness;
|
||||
out->bpp = header.bpp;
|
||||
}
|
||||
|
||||
bool binfont_get_glyph(BinFont* font, uint32_t codepoint, BinFontGlyph* out) {
|
||||
const uint32_t glyph_id = find_glyph_id(font, codepoint);
|
||||
uint8_t buffer[GLYPH_HEADER_MAX_BYTES];
|
||||
const uint8_t* record;
|
||||
size_t size;
|
||||
uint8_t* allocated;
|
||||
if (!get_glyph_record(font, glyph_id, sizeof(buffer), buffer, sizeof(buffer), record, size, allocated)) {
|
||||
return false;
|
||||
}
|
||||
BitReader reader(record, size);
|
||||
parse_glyph(font->header, reader, glyph_id, out);
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t binfont_get_kerning_x16(BinFont* font, uint32_t left_glyph_id, uint32_t right_glyph_id) {
|
||||
const uint8_t* kern = font->kern;
|
||||
if (kern == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int8_t value = 0;
|
||||
if (kern[8] == 0) {
|
||||
const uint32_t count = read_u32(kern + 12);
|
||||
const uint8_t* pairs = kern + 16;
|
||||
const bool wide = font->header.glyphIdFormat != 0;
|
||||
const size_t pair_size = wide ? 4 : 2;
|
||||
const uint32_t key = (left_glyph_id << 16) | right_glyph_id;
|
||||
uint32_t low = 0;
|
||||
uint32_t high = count;
|
||||
while (low < high) {
|
||||
const uint32_t mid = (low + high) / 2;
|
||||
const uint8_t* pair = pairs + mid * pair_size;
|
||||
const uint32_t left = wide ? read_u16(pair) : pair[0];
|
||||
const uint32_t right = wide ? read_u16(pair + 2) : pair[1];
|
||||
const uint32_t mid_key = (left << 16) | right;
|
||||
if (mid_key == key) {
|
||||
value = static_cast<int8_t>(pairs[count * pair_size + mid]);
|
||||
break;
|
||||
} else if (mid_key < key) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
high = mid;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const uint16_t glyph_count = read_u16(kern + 12);
|
||||
const uint8_t right_count = kern[15];
|
||||
if (left_glyph_id >= glyph_count || right_glyph_id >= glyph_count) {
|
||||
return 0;
|
||||
}
|
||||
const uint8_t* left_classes = kern + 16;
|
||||
const uint8_t* right_classes = left_classes + glyph_count;
|
||||
const uint8_t* values = right_classes + glyph_count;
|
||||
const uint8_t left_class = left_classes[left_glyph_id];
|
||||
const uint8_t right_class = right_classes[right_glyph_id];
|
||||
if (left_class == 0 || right_class == 0 || left_class > kern[14] || right_class > right_count) {
|
||||
return 0;
|
||||
}
|
||||
value = static_cast<int8_t>(values[(left_class - 1) * right_count + (right_class - 1)]);
|
||||
}
|
||||
|
||||
return (static_cast<int32_t>(value) * font->header.kerningScale) >> 4;
|
||||
}
|
||||
|
||||
error_t binfont_get_glyph_bitmap(BinFont* font, const BinFontGlyph* glyph, uint8_t* out, size_t stride) {
|
||||
if (stride < glyph->width) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
if (glyph->width == 0 || glyph->height == 0) {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
const uint8_t* record;
|
||||
size_t size;
|
||||
uint8_t* allocated;
|
||||
if (!get_glyph_record(font, glyph->glyph_id, SIZE_MAX, nullptr, 0, record, size, allocated)) {
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
const Header& header = font->header;
|
||||
const uint8_t bpp = header.bpp;
|
||||
const size_t header_bits = header.advanceWidthBits + 2U * header.xyBits + 2U * header.whBits;
|
||||
BitReader reader(record, size, header_bits);
|
||||
|
||||
// Raw pixel values first, so the XOR prefilter can work on the previous line
|
||||
if (header.compression == COMPRESSION_NONE) {
|
||||
for (uint16_t y = 0; y < glyph->height; y++) {
|
||||
uint8_t* line = out + y * stride;
|
||||
for (uint16_t x = 0; x < glyph->width; x++) {
|
||||
line[x] = static_cast<uint8_t>(reader.read(bpp));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
RleReader rle(reader, bpp);
|
||||
const bool prefilter = header.compression == COMPRESSION_RLE_PREFILTER;
|
||||
for (uint16_t y = 0; y < glyph->height; y++) {
|
||||
uint8_t* line = out + y * stride;
|
||||
const uint8_t* previous = line - stride;
|
||||
for (uint16_t x = 0; x < glyph->width; x++) {
|
||||
const uint8_t value = rle.next();
|
||||
line[x] = (prefilter && y > 0) ? (value ^ previous[x]) : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uint32_t max_value = (1U << bpp) - 1;
|
||||
for (uint16_t y = 0; y < glyph->height; y++) {
|
||||
uint8_t* line = out + y * stride;
|
||||
for (uint16_t x = 0; x < glyph->width; x++) {
|
||||
line[x] = static_cast<uint8_t>(line[x] * 255U / max_value);
|
||||
}
|
||||
}
|
||||
|
||||
memory_free(allocated);
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
}
|
||||
443
Modules/binfont-module/source/generator.cpp
Normal file
@ -0,0 +1,443 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <binfont/generator.h>
|
||||
|
||||
#include <tactility/memory.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#define STBTT_STATIC
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#define STBTT_malloc(size, user) ((void)(user), memory_alloc(size))
|
||||
#define STBTT_free(pointer, user) ((void)(user), memory_free(pointer))
|
||||
#include "stb_truetype.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t HEAD_SIZE = 48;
|
||||
constexpr uint8_t CMAP_FORMAT_0_TINY = 2;
|
||||
constexpr uint8_t CMAP_FORMAT_SPARSE_TINY = 3;
|
||||
constexpr uint32_t CMAP_MAX_RANGE = 0xFFFF;
|
||||
|
||||
constexpr MemoryPolicy EXTERNAL_PREFERRED = { .required = 0, .desired = MEMORY_CAPABILITY_EXTERNAL, .alignment = 0 };
|
||||
|
||||
class ByteBuffer {
|
||||
uint8_t* data = nullptr;
|
||||
size_t size = 0;
|
||||
size_t capacity = 0;
|
||||
bool failed = false;
|
||||
|
||||
bool reserve(size_t required) {
|
||||
if (failed) {
|
||||
return false;
|
||||
}
|
||||
if (required <= capacity) {
|
||||
return true;
|
||||
}
|
||||
size_t new_capacity = capacity ? capacity * 2 : 4096;
|
||||
while (new_capacity < required) {
|
||||
new_capacity *= 2;
|
||||
}
|
||||
auto* new_data = static_cast<uint8_t*>(memory_realloc_with_policy(data, new_capacity, &EXTERNAL_PREFERRED));
|
||||
if (new_data == nullptr) {
|
||||
failed = true;
|
||||
return false;
|
||||
}
|
||||
data = new_data;
|
||||
capacity = new_capacity;
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
~ByteBuffer() { memory_free(data); }
|
||||
|
||||
size_t getSize() const { return size; }
|
||||
bool hasFailed() const { return failed; }
|
||||
|
||||
void append(const void* bytes, size_t count) {
|
||||
if (reserve(size + count)) {
|
||||
memcpy(data + size, bytes, count);
|
||||
size += count;
|
||||
}
|
||||
}
|
||||
|
||||
void appendZeros(size_t count) {
|
||||
if (reserve(size + count)) {
|
||||
memset(data + size, 0, count);
|
||||
size += count;
|
||||
}
|
||||
}
|
||||
|
||||
void appendU8(uint8_t value) { append(&value, 1); }
|
||||
|
||||
void appendU16(uint16_t value) {
|
||||
const uint8_t bytes[2] = { static_cast<uint8_t>(value), static_cast<uint8_t>(value >> 8) };
|
||||
append(bytes, 2);
|
||||
}
|
||||
|
||||
void appendU32(uint32_t value) {
|
||||
const uint8_t bytes[4] = { static_cast<uint8_t>(value), static_cast<uint8_t>(value >> 8), static_cast<uint8_t>(value >> 16), static_cast<uint8_t>(value >> 24) };
|
||||
append(bytes, 4);
|
||||
}
|
||||
|
||||
void setU32(size_t offset, uint32_t value) {
|
||||
if (!failed) {
|
||||
data[offset] = static_cast<uint8_t>(value);
|
||||
data[offset + 1] = static_cast<uint8_t>(value >> 8);
|
||||
data[offset + 2] = static_cast<uint8_t>(value >> 16);
|
||||
data[offset + 3] = static_cast<uint8_t>(value >> 24);
|
||||
}
|
||||
}
|
||||
|
||||
void alignTo4() { appendZeros((4 - size % 4) % 4); }
|
||||
|
||||
/** Starts a record and returns its offset, for endRecord(). */
|
||||
size_t beginRecord(const char* marker) {
|
||||
const size_t offset = size;
|
||||
appendU32(0);
|
||||
append(marker, 4);
|
||||
return offset;
|
||||
}
|
||||
|
||||
void endRecord(size_t offset) { setU32(offset, static_cast<uint32_t>(size - offset)); }
|
||||
|
||||
uint8_t* release() {
|
||||
uint8_t* result = data;
|
||||
data = nullptr;
|
||||
size = 0;
|
||||
capacity = 0;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
/** MSB-first bit writer */
|
||||
class BitWriter {
|
||||
ByteBuffer& buffer;
|
||||
uint8_t current = 0;
|
||||
uint8_t used = 0;
|
||||
|
||||
public:
|
||||
explicit BitWriter(ByteBuffer& buffer) : buffer(buffer) {}
|
||||
|
||||
void write(uint32_t value, uint8_t bits) {
|
||||
while (bits > 0) {
|
||||
bits--;
|
||||
current = static_cast<uint8_t>((current << 1) | ((value >> bits) & 1U));
|
||||
used++;
|
||||
if (used == 8) {
|
||||
buffer.appendU8(current);
|
||||
current = 0;
|
||||
used = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void flush() {
|
||||
if (used > 0) {
|
||||
buffer.appendU8(static_cast<uint8_t>(current << (8 - used)));
|
||||
current = 0;
|
||||
used = 0;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct GlyphInfo {
|
||||
uint32_t codepoint;
|
||||
int ttfIndex;
|
||||
uint32_t advanceX16;
|
||||
int x0, y0, x1, y1; // stb_truetype bitmap box: y points down
|
||||
};
|
||||
|
||||
uint8_t bits_for_unsigned(uint32_t value) {
|
||||
uint8_t bits = 1;
|
||||
while (bits < 32 && (value >> bits) != 0) {
|
||||
bits++;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
uint8_t bits_for_signed(int32_t min, int32_t max) {
|
||||
uint8_t bits = 1;
|
||||
while (bits < 32 && (min < -(1 << (bits - 1)) || max > (1 << (bits - 1)) - 1)) {
|
||||
bits++;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
int16_t scale_metric(int value, float scale) {
|
||||
return static_cast<int16_t>(std::lround(static_cast<float>(value) * scale));
|
||||
}
|
||||
|
||||
uint8_t* read_file(const char* path, size_t& size) {
|
||||
FILE* file = fopen(path, "rb");
|
||||
if (file == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
uint8_t* data = nullptr;
|
||||
long file_size = 0;
|
||||
if (fseek(file, 0, SEEK_END) == 0 && (file_size = ftell(file)) > 0 && fseek(file, 0, SEEK_SET) == 0) {
|
||||
data = static_cast<uint8_t*>(memory_alloc_with_policy(static_cast<size_t>(file_size), &EXTERNAL_PREFERRED));
|
||||
if (data != nullptr && fread(data, 1, static_cast<size_t>(file_size), file) != static_cast<size_t>(file_size)) {
|
||||
memory_free(data);
|
||||
data = nullptr;
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
size = static_cast<size_t>(file_size);
|
||||
return data;
|
||||
}
|
||||
|
||||
void write_cmap(ByteBuffer& out, const GlyphInfo* glyphs, size_t count) {
|
||||
// Glyph ids are 1..count in codepoint order, so every subtable can be a "tiny" format
|
||||
struct Range {
|
||||
size_t first;
|
||||
size_t count;
|
||||
bool contiguous;
|
||||
};
|
||||
|
||||
auto next_range = [&](size_t first) {
|
||||
Range range = { first, 1, true };
|
||||
while (first + range.count < count && range.count < CMAP_MAX_RANGE) {
|
||||
const GlyphInfo& candidate = glyphs[first + range.count];
|
||||
if (candidate.codepoint - glyphs[first].codepoint >= CMAP_MAX_RANGE) {
|
||||
break;
|
||||
}
|
||||
if (candidate.codepoint != glyphs[first].codepoint + range.count) {
|
||||
range.contiguous = false;
|
||||
}
|
||||
range.count++;
|
||||
}
|
||||
return range;
|
||||
};
|
||||
|
||||
uint32_t range_count = 0;
|
||||
for (size_t i = 0; i < count; i += next_range(i).count) {
|
||||
range_count++;
|
||||
}
|
||||
|
||||
const size_t record = out.beginRecord("cmap");
|
||||
out.appendU32(range_count);
|
||||
|
||||
size_t data_offset = 12 + range_count * 16;
|
||||
for (size_t i = 0; i < count;) {
|
||||
const Range range = next_range(i);
|
||||
const uint32_t range_start = glyphs[i].codepoint;
|
||||
const uint32_t range_length = glyphs[i + range.count - 1].codepoint - range_start + 1;
|
||||
out.appendU32(range.contiguous ? 0 : static_cast<uint32_t>(data_offset));
|
||||
out.appendU32(range_start);
|
||||
out.appendU16(static_cast<uint16_t>(range_length));
|
||||
out.appendU16(static_cast<uint16_t>(i + 1));
|
||||
out.appendU16(range.contiguous ? 0 : static_cast<uint16_t>(range.count));
|
||||
out.appendU8(range.contiguous ? CMAP_FORMAT_0_TINY : CMAP_FORMAT_SPARSE_TINY);
|
||||
out.appendU8(0);
|
||||
if (!range.contiguous) {
|
||||
data_offset += (range.count * 2 + 3) & ~static_cast<size_t>(3);
|
||||
}
|
||||
i += range.count;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < count;) {
|
||||
const Range range = next_range(i);
|
||||
if (!range.contiguous) {
|
||||
for (size_t j = 0; j < range.count; j++) {
|
||||
out.appendU16(static_cast<uint16_t>(glyphs[i + j].codepoint - glyphs[i].codepoint));
|
||||
}
|
||||
out.alignTo4();
|
||||
}
|
||||
i += range.count;
|
||||
}
|
||||
|
||||
out.endRecord(record);
|
||||
}
|
||||
|
||||
error_t generate(const BinFontGeneratorConfig* config, const stbtt_fontinfo& info, GlyphInfo* glyphs, ByteBuffer& out) {
|
||||
const float scale = stbtt_ScaleForMappingEmToPixels(&info, config->size);
|
||||
|
||||
// Collect glyphs, sorted by codepoint and without duplicates or missing glyphs
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < config->codepoint_count; i++) {
|
||||
const int index = stbtt_FindGlyphIndex(&info, static_cast<int>(config->codepoints[i]));
|
||||
if (index != 0) {
|
||||
glyphs[count].codepoint = config->codepoints[i];
|
||||
glyphs[count].ttfIndex = index;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
std::sort(glyphs, glyphs + count, [](const GlyphInfo& a, const GlyphInfo& b) { return a.codepoint < b.codepoint; });
|
||||
count = std::unique(glyphs, glyphs + count, [](const GlyphInfo& a, const GlyphInfo& b) { return a.codepoint == b.codepoint; }) - glyphs;
|
||||
if (count == 0 || count >= 0xFFFF) {
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
int32_t min_xy = 0;
|
||||
int32_t max_xy = 0;
|
||||
uint32_t max_wh = 0;
|
||||
uint32_t max_advance = 0;
|
||||
// Like lv_font_conv, ascent and descent are the extents of the included glyphs
|
||||
int32_t min_y = INT32_MAX;
|
||||
int32_t max_y = INT32_MIN;
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
GlyphInfo& glyph = glyphs[i];
|
||||
int advance;
|
||||
int left_side_bearing;
|
||||
stbtt_GetGlyphHMetrics(&info, glyph.ttfIndex, &advance, &left_side_bearing);
|
||||
glyph.advanceX16 = static_cast<uint32_t>(std::lround(static_cast<float>(advance) * scale * 16.0f));
|
||||
stbtt_GetGlyphBitmapBox(&info, glyph.ttfIndex, scale, scale, &glyph.x0, &glyph.y0, &glyph.x1, &glyph.y1);
|
||||
|
||||
const int32_t bottom = -glyph.y1;
|
||||
min_xy = std::min({ min_xy, static_cast<int32_t>(glyph.x0), bottom });
|
||||
max_xy = std::max({ max_xy, static_cast<int32_t>(glyph.x0), bottom });
|
||||
max_wh = std::max({ max_wh, static_cast<uint32_t>(glyph.x1 - glyph.x0), static_cast<uint32_t>(glyph.y1 - glyph.y0) });
|
||||
max_advance = std::max(max_advance, glyph.advanceX16);
|
||||
min_y = std::min(min_y, bottom);
|
||||
max_y = std::max(max_y, static_cast<int32_t>(-glyph.y0));
|
||||
}
|
||||
|
||||
const uint8_t xy_bits = bits_for_signed(min_xy, max_xy);
|
||||
const uint8_t wh_bits = bits_for_unsigned(max_wh);
|
||||
const uint8_t advance_bits = bits_for_unsigned(max_advance);
|
||||
const uint32_t glyph_count = static_cast<uint32_t>(count) + 1;
|
||||
|
||||
int typo_ascent = 0;
|
||||
int typo_descent = 0;
|
||||
int typo_line_gap = 0;
|
||||
stbtt_GetFontVMetricsOS2(&info, &typo_ascent, &typo_descent, &typo_line_gap);
|
||||
int underline_position = 0;
|
||||
int underline_thickness = 0;
|
||||
const stbtt_uint32 post = stbtt__find_table(info.data, info.fontstart, "post");
|
||||
if (post != 0) {
|
||||
underline_position = ttSHORT(info.data + post + 8);
|
||||
underline_thickness = ttSHORT(info.data + post + 10);
|
||||
}
|
||||
|
||||
// head
|
||||
const size_t head = out.beginRecord("head");
|
||||
out.appendU32(1); // version
|
||||
out.appendU16(3); // additional tables: cmap, loca, glyf
|
||||
out.appendU16(config->size);
|
||||
out.appendU16(static_cast<uint16_t>(max_y));
|
||||
out.appendU16(static_cast<uint16_t>(min_y));
|
||||
out.appendU16(static_cast<uint16_t>(scale_metric(typo_ascent, scale)));
|
||||
out.appendU16(static_cast<uint16_t>(scale_metric(typo_descent, scale)));
|
||||
out.appendU16(static_cast<uint16_t>(scale_metric(typo_line_gap, scale)));
|
||||
out.appendU16(static_cast<uint16_t>(min_y));
|
||||
out.appendU16(static_cast<uint16_t>(max_y));
|
||||
out.appendU16(0); // default advance width
|
||||
out.appendU16(16); // kerning scale 1.0 in FP12.4
|
||||
out.appendU8(1); // loca: Offset32
|
||||
out.appendU8(glyph_count > 256 ? 1 : 0);
|
||||
out.appendU8(1); // advance width with 4 fractional bits
|
||||
out.appendU8(config->bpp);
|
||||
out.appendU8(xy_bits);
|
||||
out.appendU8(wh_bits);
|
||||
out.appendU8(advance_bits);
|
||||
out.appendU8(0); // no compression
|
||||
out.appendU8(0); // no subpixel rendering
|
||||
out.appendU8(0);
|
||||
out.appendU16(static_cast<uint16_t>(scale_metric(underline_position, scale)));
|
||||
out.appendU16(static_cast<uint16_t>(scale_metric(underline_thickness, scale)));
|
||||
out.endRecord(head);
|
||||
|
||||
write_cmap(out, glyphs, count);
|
||||
|
||||
const size_t loca = out.beginRecord("loca");
|
||||
out.appendU32(glyph_count);
|
||||
const size_t loca_entries = out.getSize();
|
||||
out.appendZeros(glyph_count * 4);
|
||||
out.endRecord(loca);
|
||||
|
||||
const size_t glyf = out.beginRecord("glyf");
|
||||
uint8_t* bitmap = nullptr;
|
||||
size_t bitmap_capacity = 0;
|
||||
const uint32_t max_value = (1U << config->bpp) - 1;
|
||||
for (uint32_t id = 0; id < glyph_count && !out.hasFailed(); id++) {
|
||||
out.setU32(loca_entries + id * 4, static_cast<uint32_t>(out.getSize() - glyf));
|
||||
BitWriter writer(out);
|
||||
if (id == 0) {
|
||||
writer.write(0, advance_bits);
|
||||
writer.write(0, xy_bits);
|
||||
writer.write(0, xy_bits);
|
||||
writer.write(0, wh_bits);
|
||||
writer.write(0, wh_bits);
|
||||
writer.flush();
|
||||
continue;
|
||||
}
|
||||
|
||||
const GlyphInfo& glyph = glyphs[id - 1];
|
||||
const int width = glyph.x1 - glyph.x0;
|
||||
const int height = glyph.y1 - glyph.y0;
|
||||
writer.write(glyph.advanceX16, advance_bits);
|
||||
writer.write(static_cast<uint32_t>(glyph.x0), xy_bits);
|
||||
writer.write(static_cast<uint32_t>(-glyph.y1), xy_bits);
|
||||
writer.write(static_cast<uint32_t>(width), wh_bits);
|
||||
writer.write(static_cast<uint32_t>(height), wh_bits);
|
||||
|
||||
const size_t pixel_count = static_cast<size_t>(width) * height;
|
||||
if (pixel_count > 0) {
|
||||
if (pixel_count > bitmap_capacity) {
|
||||
memory_free(bitmap);
|
||||
bitmap = static_cast<uint8_t*>(memory_alloc_with_policy(pixel_count, &EXTERNAL_PREFERRED));
|
||||
if (bitmap == nullptr) {
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
bitmap_capacity = pixel_count;
|
||||
}
|
||||
stbtt_MakeGlyphBitmap(&info, bitmap, width, height, width, scale, scale, glyph.ttfIndex);
|
||||
for (size_t i = 0; i < pixel_count; i++) {
|
||||
writer.write((bitmap[i] * max_value + 127) / 255, config->bpp);
|
||||
}
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
memory_free(bitmap);
|
||||
out.alignTo4();
|
||||
out.endRecord(glyf);
|
||||
|
||||
return out.hasFailed() ? ERROR_OUT_OF_MEMORY : ERROR_NONE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
error_t binfont_generate(const BinFontGeneratorConfig* config, uint8_t** out_data, size_t* out_size) {
|
||||
if (config->ttf_path == nullptr || config->size == 0 || config->bpp < 1 || config->bpp > 4 || config->codepoint_count == 0) {
|
||||
return ERROR_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
size_t ttf_size = 0;
|
||||
uint8_t* ttf = read_file(config->ttf_path, ttf_size);
|
||||
if (ttf == nullptr) {
|
||||
return ERROR_NOT_FOUND;
|
||||
}
|
||||
|
||||
stbtt_fontinfo info;
|
||||
const int font_offset = stbtt_GetFontOffsetForIndex(ttf, 0);
|
||||
if (font_offset < 0 || stbtt_InitFont(&info, ttf, font_offset) == 0) {
|
||||
memory_free(ttf);
|
||||
return ERROR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
auto* glyphs = static_cast<GlyphInfo*>(memory_alloc(config->codepoint_count * sizeof(GlyphInfo)));
|
||||
if (glyphs == nullptr) {
|
||||
memory_free(ttf);
|
||||
return ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
ByteBuffer out;
|
||||
const error_t error = generate(config, info, glyphs, out);
|
||||
memory_free(glyphs);
|
||||
memory_free(ttf);
|
||||
if (error != ERROR_NONE) {
|
||||
return error;
|
||||
}
|
||||
|
||||
*out_size = out.getSize();
|
||||
*out_data = out.release();
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
}
|
||||
33
Modules/binfont-module/source/module.c
Normal file
@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <binfont/binfont.h>
|
||||
#include <binfont/generator.h>
|
||||
#include <binfont/module.h>
|
||||
#include <binfont/render.h>
|
||||
|
||||
static const struct ModuleSymbol SYMBOLS[] = {
|
||||
// binfont
|
||||
DEFINE_MODULE_SYMBOL(binfont_open_file),
|
||||
DEFINE_MODULE_SYMBOL(binfont_open_memory),
|
||||
DEFINE_MODULE_SYMBOL(binfont_close),
|
||||
DEFINE_MODULE_SYMBOL(binfont_begin),
|
||||
DEFINE_MODULE_SYMBOL(binfont_end),
|
||||
DEFINE_MODULE_SYMBOL(binfont_get_metrics),
|
||||
DEFINE_MODULE_SYMBOL(binfont_get_glyph),
|
||||
DEFINE_MODULE_SYMBOL(binfont_get_kerning_x16),
|
||||
DEFINE_MODULE_SYMBOL(binfont_get_glyph_bitmap),
|
||||
// generator
|
||||
DEFINE_MODULE_SYMBOL(binfont_generate),
|
||||
// render
|
||||
DEFINE_MODULE_SYMBOL(binfont_draw_glyph_rgb565),
|
||||
DEFINE_MODULE_SYMBOL(binfont_draw_text_rgb565),
|
||||
MODULE_SYMBOL_TERMINATOR,
|
||||
};
|
||||
|
||||
struct Module binfont_module = {
|
||||
.name = "binfont",
|
||||
.start = NULL,
|
||||
.stop = NULL,
|
||||
.drivers = NULL,
|
||||
.symbols = SYMBOLS,
|
||||
.internal = NULL,
|
||||
};
|
||||
126
Modules/binfont-module/source/render.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <binfont/render.h>
|
||||
|
||||
#include <tactility/memory.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t STACK_BITMAP_SIZE = 1024;
|
||||
|
||||
uint16_t blend_rgb565(uint16_t foreground, uint16_t background, uint8_t alpha) {
|
||||
const uint32_t inverse = 255U - alpha;
|
||||
const uint32_t r = (((foreground >> 11) & 0x1FU) * alpha + ((background >> 11) & 0x1FU) * inverse + 127U) / 255U;
|
||||
const uint32_t g = (((foreground >> 5) & 0x3FU) * alpha + ((background >> 5) & 0x3FU) * inverse + 127U) / 255U;
|
||||
const uint32_t b = ((foreground & 0x1FU) * alpha + (background & 0x1FU) * inverse + 127U) / 255U;
|
||||
return static_cast<uint16_t>((r << 11) | (g << 5) | b);
|
||||
}
|
||||
|
||||
void draw_glyph(PixelBuffer* buffer, int x, int baselineY, BinFont* font, const BinFontGlyph& glyph, uint16_t color, PixelBufferConversion conversion) {
|
||||
const size_t pixel_count = static_cast<size_t>(glyph.width) * glyph.height;
|
||||
if (pixel_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t stack_bitmap[STACK_BITMAP_SIZE];
|
||||
uint8_t* allocated = nullptr;
|
||||
uint8_t* bitmap = stack_bitmap;
|
||||
if (pixel_count > sizeof(stack_bitmap)) {
|
||||
allocated = static_cast<uint8_t*>(memory_alloc(pixel_count));
|
||||
if (allocated == nullptr) {
|
||||
return;
|
||||
}
|
||||
bitmap = allocated;
|
||||
}
|
||||
|
||||
if (binfont_get_glyph_bitmap(font, &glyph, bitmap, glyph.width) == ERROR_NONE) {
|
||||
const int left = x + glyph.x;
|
||||
const int top = baselineY - glyph.y - glyph.height;
|
||||
for (int row = 0; row < glyph.height; row++) {
|
||||
const uint8_t* line = bitmap + row * glyph.width;
|
||||
for (int column = 0; column < glyph.width; column++) {
|
||||
const uint8_t alpha = line[column];
|
||||
if (alpha == 0) {
|
||||
continue;
|
||||
}
|
||||
const int pixel_x = left + column;
|
||||
const int pixel_y = top + row;
|
||||
const uint16_t pixel = (alpha == 255) ? color : blend_rgb565(color, pixel_buffer_get_pixel_rgb565(buffer, pixel_x, pixel_y), alpha);
|
||||
pixel_buffer_set_pixel_rgb565(buffer, pixel_x, pixel_y, pixel, conversion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
memory_free(allocated);
|
||||
}
|
||||
|
||||
/** Decodes one UTF-8 codepoint and advances text. Invalid sequences decode as U+FFFD. */
|
||||
uint32_t next_codepoint(const char*& text) {
|
||||
const auto* bytes = reinterpret_cast<const uint8_t*>(text);
|
||||
const uint8_t lead = bytes[0];
|
||||
int length;
|
||||
uint32_t codepoint;
|
||||
if (lead < 0x80) {
|
||||
length = 1;
|
||||
codepoint = lead;
|
||||
} else if ((lead & 0xE0) == 0xC0) {
|
||||
length = 2;
|
||||
codepoint = lead & 0x1FU;
|
||||
} else if ((lead & 0xF0) == 0xE0) {
|
||||
length = 3;
|
||||
codepoint = lead & 0x0FU;
|
||||
} else if ((lead & 0xF8) == 0xF0) {
|
||||
length = 4;
|
||||
codepoint = lead & 0x07U;
|
||||
} else {
|
||||
text++;
|
||||
return 0xFFFD;
|
||||
}
|
||||
for (int i = 1; i < length; i++) {
|
||||
if ((bytes[i] & 0xC0) != 0x80) {
|
||||
text += i;
|
||||
return 0xFFFD;
|
||||
}
|
||||
codepoint = (codepoint << 6) | (bytes[i] & 0x3FU);
|
||||
}
|
||||
text += length;
|
||||
return codepoint;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
int binfont_draw_glyph_rgb565(PixelBuffer* buffer, int x, int baseline_y, BinFont* font, uint32_t codepoint, uint16_t color, PixelBufferConversion conversion) {
|
||||
BinFontGlyph glyph;
|
||||
if (!binfont_get_glyph(font, codepoint, &glyph)) {
|
||||
return 0;
|
||||
}
|
||||
draw_glyph(buffer, x, baseline_y, font, glyph, color, conversion);
|
||||
return static_cast<int>((glyph.advance_x16 + 8) >> 4);
|
||||
}
|
||||
|
||||
int binfont_draw_text_rgb565(PixelBuffer* buffer, int x, int baseline_y, BinFont* font, const char* text, uint16_t color, PixelBufferConversion conversion) {
|
||||
// When the file can't be kept open, every glyph lookup opens it by itself
|
||||
const bool session = binfont_begin(font) == ERROR_NONE;
|
||||
int32_t pen_x16 = 0;
|
||||
uint32_t previous_glyph_id = 0;
|
||||
while (*text != '\0') {
|
||||
BinFontGlyph glyph;
|
||||
if (!binfont_get_glyph(font, next_codepoint(text), &glyph)) {
|
||||
previous_glyph_id = 0;
|
||||
continue;
|
||||
}
|
||||
if (previous_glyph_id != 0) {
|
||||
pen_x16 += binfont_get_kerning_x16(font, previous_glyph_id, glyph.glyph_id);
|
||||
}
|
||||
draw_glyph(buffer, x + ((pen_x16 + 8) >> 4), baseline_y, font, glyph, color, conversion);
|
||||
pen_x16 += static_cast<int32_t>(glyph.advance_x16);
|
||||
previous_glyph_id = glyph.glyph_id;
|
||||
}
|
||||
if (session) {
|
||||
binfont_end(font);
|
||||
}
|
||||
return (pen_x16 + 8) >> 4;
|
||||
}
|
||||
|
||||
}
|
||||
23
Modules/binfont-module/tests/CMakeLists.txt
Normal file
@ -0,0 +1,23 @@
|
||||
project(BinFontModuleTests)
|
||||
|
||||
enable_language(C CXX ASM)
|
||||
|
||||
file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/source/*.cpp)
|
||||
add_executable(BinFontModuleTests EXCLUDE_FROM_ALL ${TEST_SOURCES})
|
||||
|
||||
target_include_directories(BinFontModuleTests PRIVATE ${DOCTESTINC} ${PROJECT_SOURCE_DIR}/../private)
|
||||
|
||||
target_compile_definitions(BinFontModuleTests PRIVATE
|
||||
"BINFONT_TEST_FIXTURES_DIR=\"${PROJECT_SOURCE_DIR}/fixtures\""
|
||||
"BINFONT_TEST_TEMP_DIR=\"${CMAKE_CURRENT_BINARY_DIR}\""
|
||||
"BINFONT_TEST_ICON_TTF=\"${CMAKE_SOURCE_DIR}/Data/system/fonts/MaterialSymbolsRounded.ttf\""
|
||||
)
|
||||
|
||||
add_test(NAME BinFontModuleTests COMMAND BinFontModuleTests)
|
||||
|
||||
target_link_libraries(BinFontModuleTests PUBLIC
|
||||
binfont-module
|
||||
graphics-module
|
||||
TactilityKernel
|
||||
freertos_kernel
|
||||
)
|
||||
BIN
Modules/binfont-module/tests/fixtures/icons_raw_2.bin
vendored
Normal file
BIN
Modules/binfont-module/tests/fixtures/icons_raw_4.bin
vendored
Normal file
BIN
Modules/binfont-module/tests/fixtures/icons_rle_2.bin
vendored
Normal file
BIN
Modules/binfont-module/tests/fixtures/icons_rle_4.bin
vendored
Normal file
BIN
Modules/binfont-module/tests/fixtures/icons_rlepf_2.bin
vendored
Normal file
BIN
Modules/binfont-module/tests/fixtures/icons_rlepf_4.bin
vendored
Normal file
BIN
Modules/binfont-module/tests/fixtures/text_kern_classes.bin
vendored
Normal file
BIN
Modules/binfont-module/tests/fixtures/text_kern_pairs.bin
vendored
Normal file
340
Modules/binfont-module/tests/source/binfont_test.cpp
Normal file
@ -0,0 +1,340 @@
|
||||
#include "doctest.h"
|
||||
|
||||
#include <binfont/binfont.h>
|
||||
#include <binfont_private.h>
|
||||
#include <binfont/generator.h>
|
||||
#include <binfont/render.h>
|
||||
#include <graphics/pixel_buffer.h>
|
||||
#include <tactility/memory.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t ICON_CODEPOINTS[] = { 0xE5C3, 0xE8B8, 0xE2C7, 0xE63E, 0xE1A7 };
|
||||
|
||||
std::string fixture(const char* name) {
|
||||
return std::string(BINFONT_TEST_FIXTURES_DIR) + "/" + name;
|
||||
}
|
||||
|
||||
/** Loads a file into memory allocated with memory_alloc() */
|
||||
uint8_t* load_file(const std::string& path, size_t& size) {
|
||||
FILE* file = fopen(path.c_str(), "rb");
|
||||
REQUIRE(file != nullptr);
|
||||
fseek(file, 0, SEEK_END);
|
||||
size = static_cast<size_t>(ftell(file));
|
||||
fseek(file, 0, SEEK_SET);
|
||||
auto* data = static_cast<uint8_t*>(memory_alloc(size));
|
||||
REQUIRE(fread(data, 1, size, file) == size);
|
||||
fclose(file);
|
||||
return data;
|
||||
}
|
||||
|
||||
BinFont* open_fixture_in_memory(const char* name) {
|
||||
size_t size;
|
||||
uint8_t* data = load_file(fixture(name), size);
|
||||
BinFont* font = nullptr;
|
||||
REQUIRE(binfont_open_memory(data, size, true, &font) == ERROR_NONE);
|
||||
return font;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> decode(BinFont* font, const BinFontGlyph& glyph) {
|
||||
std::vector<uint8_t> bitmap(static_cast<size_t>(glyph.width) * glyph.height);
|
||||
REQUIRE(binfont_get_glyph_bitmap(font, &glyph, bitmap.data(), glyph.width) == ERROR_NONE);
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
uint32_t sum(const std::vector<uint8_t>& bitmap) {
|
||||
uint32_t total = 0;
|
||||
for (uint8_t value : bitmap) {
|
||||
total += value;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
void check_same_glyph(const BinFontGlyph& a, const BinFontGlyph& b) {
|
||||
CHECK_EQ(a.glyph_id, b.glyph_id);
|
||||
CHECK_EQ(a.advance_x16, b.advance_x16);
|
||||
CHECK_EQ(a.x, b.x);
|
||||
CHECK_EQ(a.y, b.y);
|
||||
CHECK_EQ(a.width, b.width);
|
||||
CHECK_EQ(a.height, b.height);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("binfont_open_file returns ERROR_NOT_FOUND for a missing file") {
|
||||
BinFont* font = nullptr;
|
||||
CHECK_EQ(binfont_open_file(fixture("missing.bin").c_str(), &font), ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
TEST_CASE("binfont_open_memory rejects data that is not a font") {
|
||||
const uint8_t data[64] = { 1, 2, 3, 4 };
|
||||
BinFont* font = nullptr;
|
||||
CHECK_EQ(binfont_open_memory(data, sizeof(data), false, &font), ERROR_NOT_SUPPORTED);
|
||||
}
|
||||
|
||||
TEST_CASE("binfont reads metrics, glyphs and bitmaps that match lv_font_conv's C output") {
|
||||
BinFont* font = open_fixture_in_memory("text_kern_pairs.bin");
|
||||
|
||||
BinFontMetrics metrics;
|
||||
binfont_get_metrics(font, &metrics);
|
||||
CHECK_EQ(metrics.size, 20);
|
||||
CHECK_EQ(metrics.line_height, 20);
|
||||
CHECK_EQ(metrics.base_line, 5);
|
||||
CHECK_EQ(metrics.ascent, 15);
|
||||
CHECK_EQ(metrics.descent, -5);
|
||||
CHECK_EQ(metrics.underline_position, 1);
|
||||
CHECK_EQ(metrics.underline_thickness, 0);
|
||||
CHECK_EQ(metrics.bpp, 4);
|
||||
|
||||
BinFontGlyph glyph;
|
||||
REQUIRE(binfont_get_glyph(font, 'A', &glyph));
|
||||
CHECK_EQ(glyph.glyph_id, 1);
|
||||
CHECK_EQ(glyph.advance_x16, 195);
|
||||
CHECK_EQ(glyph.x, 0);
|
||||
CHECK_EQ(glyph.y, 0);
|
||||
CHECK_EQ(glyph.width, 13);
|
||||
CHECK_EQ(glyph.height, 14);
|
||||
|
||||
const auto bitmap = decode(font, glyph);
|
||||
const std::vector<uint8_t> first_row = { 0, 0, 0, 0, 0, 238, 255, 17, 0, 0, 0, 0, 0 };
|
||||
const std::vector<uint8_t> last_row = { 221, 255, 0, 0, 0, 0, 0, 0, 0, 0, 238, 238, 0 };
|
||||
CHECK(std::vector<uint8_t>(bitmap.begin(), bitmap.begin() + 13) == first_row);
|
||||
CHECK(std::vector<uint8_t>(bitmap.end() - 13, bitmap.end()) == last_row);
|
||||
CHECK_EQ(sum(bitmap), 13974);
|
||||
|
||||
REQUIRE(binfont_get_glyph(font, 'V', &glyph));
|
||||
CHECK_EQ(glyph.glyph_id, 22);
|
||||
REQUIRE(binfont_get_glyph(font, 'z', &glyph));
|
||||
CHECK_EQ(glyph.glyph_id, 52);
|
||||
|
||||
CHECK_FALSE(binfont_get_glyph(font, '0', &glyph));
|
||||
CHECK_FALSE(binfont_get_glyph(font, 0x5B, &glyph));
|
||||
CHECK_FALSE(binfont_get_glyph(font, 0x10FFFF, &glyph));
|
||||
|
||||
binfont_close(font);
|
||||
}
|
||||
|
||||
TEST_CASE("binfont kerning is the same for the pairs and the classes format") {
|
||||
BinFont* pairs = open_fixture_in_memory("text_kern_pairs.bin");
|
||||
BinFont* classes = open_fixture_in_memory("text_kern_classes.bin");
|
||||
|
||||
CHECK_EQ(binfont_get_kerning_x16(pairs, 1, 22), -10);
|
||||
CHECK_EQ(binfont_get_kerning_x16(classes, 1, 22), -10);
|
||||
|
||||
int non_zero = 0;
|
||||
for (uint32_t left = 1; left <= 52; left++) {
|
||||
for (uint32_t right = 1; right <= 52; right++) {
|
||||
const int32_t value = binfont_get_kerning_x16(pairs, left, right);
|
||||
CHECK_EQ(value, binfont_get_kerning_x16(classes, left, right));
|
||||
if (value != 0) {
|
||||
non_zero++;
|
||||
}
|
||||
}
|
||||
}
|
||||
CHECK(non_zero > 0);
|
||||
CHECK_EQ(binfont_get_kerning_x16(pairs, 1, 1000), 0);
|
||||
CHECK_EQ(binfont_get_kerning_x16(classes, 1, 1000), 0);
|
||||
|
||||
binfont_close(pairs);
|
||||
binfont_close(classes);
|
||||
}
|
||||
|
||||
TEST_CASE("binfont decodes identical bitmaps for raw, RLE and RLE with prefilter") {
|
||||
for (const char* bpp : { "2", "4" }) {
|
||||
CAPTURE(bpp);
|
||||
BinFont* raw = open_fixture_in_memory((std::string("icons_raw_") + bpp + ".bin").c_str());
|
||||
BinFont* rle = open_fixture_in_memory((std::string("icons_rle_") + bpp + ".bin").c_str());
|
||||
BinFont* prefiltered = open_fixture_in_memory((std::string("icons_rlepf_") + bpp + ".bin").c_str());
|
||||
|
||||
for (uint32_t codepoint : ICON_CODEPOINTS) {
|
||||
CAPTURE(codepoint);
|
||||
BinFontGlyph raw_glyph, rle_glyph, prefiltered_glyph;
|
||||
REQUIRE(binfont_get_glyph(raw, codepoint, &raw_glyph));
|
||||
REQUIRE(binfont_get_glyph(rle, codepoint, &rle_glyph));
|
||||
REQUIRE(binfont_get_glyph(prefiltered, codepoint, &prefiltered_glyph));
|
||||
check_same_glyph(raw_glyph, rle_glyph);
|
||||
check_same_glyph(raw_glyph, prefiltered_glyph);
|
||||
|
||||
const auto raw_bitmap = decode(raw, raw_glyph);
|
||||
CHECK(sum(raw_bitmap) > 0);
|
||||
CHECK(decode(rle, rle_glyph) == raw_bitmap);
|
||||
CHECK(decode(prefiltered, prefiltered_glyph) == raw_bitmap);
|
||||
}
|
||||
|
||||
binfont_close(raw);
|
||||
binfont_close(rle);
|
||||
binfont_close(prefiltered);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("binfont gives the same results when streaming from a file") {
|
||||
BinFont* memory_font = open_fixture_in_memory("icons_rlepf_4.bin");
|
||||
BinFont* file_font = nullptr;
|
||||
REQUIRE(binfont_open_file_streaming(fixture("icons_rlepf_4.bin").c_str(), &file_font) == ERROR_NONE);
|
||||
|
||||
for (uint32_t codepoint : ICON_CODEPOINTS) {
|
||||
BinFontGlyph memory_glyph, file_glyph;
|
||||
REQUIRE(binfont_get_glyph(memory_font, codepoint, &memory_glyph));
|
||||
REQUIRE(binfont_get_glyph(file_font, codepoint, &file_glyph));
|
||||
check_same_glyph(memory_glyph, file_glyph);
|
||||
CHECK(decode(memory_font, memory_glyph) == decode(file_font, file_glyph));
|
||||
}
|
||||
|
||||
binfont_close(memory_font);
|
||||
binfont_close(file_font);
|
||||
}
|
||||
|
||||
TEST_CASE("binfont_open_file loads the font into memory") {
|
||||
const std::string path = std::string(BINFONT_TEST_TEMP_DIR) + "/binfont_memory_test.bin";
|
||||
size_t size;
|
||||
uint8_t* data = load_file(fixture("icons_rlepf_4.bin"), size);
|
||||
FILE* copy = fopen(path.c_str(), "wb");
|
||||
REQUIRE(copy != nullptr);
|
||||
REQUIRE(fwrite(data, 1, size, copy) == size);
|
||||
fclose(copy);
|
||||
memory_free(data);
|
||||
|
||||
BinFont* font = nullptr;
|
||||
REQUIRE(binfont_open_file(path.c_str(), &font) == ERROR_NONE);
|
||||
REQUIRE(remove(path.c_str()) == 0);
|
||||
|
||||
BinFontGlyph glyph;
|
||||
CHECK(binfont_get_glyph(font, ICON_CODEPOINTS[0], &glyph));
|
||||
CHECK(sum(decode(font, glyph)) > 0);
|
||||
|
||||
binfont_close(font);
|
||||
}
|
||||
|
||||
TEST_CASE("binfont only keeps a streamed font file open during a session") {
|
||||
// An open file stays readable after it's deleted, so deleting it shows whether it's kept open
|
||||
const std::string path = std::string(BINFONT_TEST_TEMP_DIR) + "/binfont_session_test.bin";
|
||||
size_t size;
|
||||
uint8_t* data = load_file(fixture("icons_rlepf_4.bin"), size);
|
||||
FILE* copy = fopen(path.c_str(), "wb");
|
||||
REQUIRE(copy != nullptr);
|
||||
REQUIRE(fwrite(data, 1, size, copy) == size);
|
||||
fclose(copy);
|
||||
memory_free(data);
|
||||
|
||||
BinFont* font = nullptr;
|
||||
REQUIRE(binfont_open_file_streaming(path.c_str(), &font) == ERROR_NONE);
|
||||
BinFontGlyph glyph;
|
||||
REQUIRE(binfont_get_glyph(font, ICON_CODEPOINTS[0], &glyph));
|
||||
|
||||
REQUIRE(binfont_begin(font) == ERROR_NONE);
|
||||
REQUIRE(binfont_begin(font) == ERROR_NONE);
|
||||
REQUIRE(remove(path.c_str()) == 0);
|
||||
binfont_end(font);
|
||||
// Still open: one session remains
|
||||
CHECK(binfont_get_glyph(font, ICON_CODEPOINTS[1], &glyph));
|
||||
CHECK_FALSE(decode(font, glyph).empty());
|
||||
binfont_end(font);
|
||||
|
||||
// Closed: the deleted file can't be opened again
|
||||
CHECK_FALSE(binfont_get_glyph(font, ICON_CODEPOINTS[2], &glyph));
|
||||
CHECK_EQ(binfont_begin(font), ERROR_NOT_FOUND);
|
||||
|
||||
binfont_close(font);
|
||||
}
|
||||
|
||||
TEST_CASE("binfont_get_glyph_bitmap rejects a stride smaller than the glyph width") {
|
||||
BinFont* font = open_fixture_in_memory("icons_raw_2.bin");
|
||||
BinFontGlyph glyph;
|
||||
REQUIRE(binfont_get_glyph(font, ICON_CODEPOINTS[0], &glyph));
|
||||
std::vector<uint8_t> bitmap(static_cast<size_t>(glyph.width) * glyph.height);
|
||||
CHECK_EQ(binfont_get_glyph_bitmap(font, &glyph, bitmap.data(), glyph.width - 1), ERROR_INVALID_ARGUMENT);
|
||||
binfont_close(font);
|
||||
}
|
||||
|
||||
TEST_CASE("binfont_generate rasterizes a TTF close to lv_font_conv") {
|
||||
uint32_t codepoints[] = { 0xE8B8, 0xE5C3, 0x41, 0xE1A7, 0xE2C7, 0xE63E, 0xE5C3 }; // unsorted, duplicate and missing entries
|
||||
const BinFontGeneratorConfig config = {
|
||||
.ttf_path = BINFONT_TEST_ICON_TTF,
|
||||
.size = 24,
|
||||
.bpp = 4,
|
||||
.codepoints = codepoints,
|
||||
.codepoint_count = sizeof(codepoints) / sizeof(codepoints[0]),
|
||||
};
|
||||
uint8_t* data = nullptr;
|
||||
size_t size = 0;
|
||||
REQUIRE(binfont_generate(&config, &data, &size) == ERROR_NONE);
|
||||
|
||||
BinFont* generated = nullptr;
|
||||
REQUIRE(binfont_open_memory(data, size, true, &generated) == ERROR_NONE);
|
||||
BinFont* reference = open_fixture_in_memory("icons_raw_4.bin");
|
||||
|
||||
BinFontMetrics generated_metrics, reference_metrics;
|
||||
binfont_get_metrics(generated, &generated_metrics);
|
||||
binfont_get_metrics(reference, &reference_metrics);
|
||||
CHECK_EQ(generated_metrics.size, 24);
|
||||
CHECK_EQ(generated_metrics.bpp, 4);
|
||||
CHECK_EQ(generated_metrics.ascent, reference_metrics.ascent);
|
||||
CHECK_EQ(generated_metrics.descent, reference_metrics.descent);
|
||||
|
||||
BinFontGlyph glyph;
|
||||
CHECK_FALSE(binfont_get_glyph(generated, 0x41, &glyph));
|
||||
|
||||
for (uint32_t codepoint : ICON_CODEPOINTS) {
|
||||
CAPTURE(codepoint);
|
||||
BinFontGlyph generated_glyph, reference_glyph;
|
||||
REQUIRE(binfont_get_glyph(generated, codepoint, &generated_glyph));
|
||||
REQUIRE(binfont_get_glyph(reference, codepoint, &reference_glyph));
|
||||
CHECK(std::abs(static_cast<int>(generated_glyph.advance_x16) - static_cast<int>(reference_glyph.advance_x16)) <= 16);
|
||||
CHECK(std::abs(generated_glyph.x - reference_glyph.x) <= 1);
|
||||
CHECK(std::abs(generated_glyph.y - reference_glyph.y) <= 1);
|
||||
CHECK(std::abs(generated_glyph.width - reference_glyph.width) <= 1);
|
||||
CHECK(std::abs(generated_glyph.height - reference_glyph.height) <= 1);
|
||||
|
||||
const auto generated_sum = static_cast<double>(sum(decode(generated, generated_glyph)));
|
||||
const auto reference_sum = static_cast<double>(sum(decode(reference, reference_glyph)));
|
||||
CHECK(generated_sum > reference_sum * 0.9);
|
||||
CHECK(generated_sum < reference_sum * 1.1);
|
||||
}
|
||||
|
||||
binfont_close(generated);
|
||||
binfont_close(reference);
|
||||
}
|
||||
|
||||
TEST_CASE("binfont_generate fails for a missing TTF") {
|
||||
const uint32_t codepoint = 0xE5C3;
|
||||
const BinFontGeneratorConfig config = {
|
||||
.ttf_path = BINFONT_TEST_FIXTURES_DIR "/missing.ttf",
|
||||
.size = 24,
|
||||
.bpp = 2,
|
||||
.codepoints = &codepoint,
|
||||
.codepoint_count = 1,
|
||||
};
|
||||
uint8_t* data = nullptr;
|
||||
size_t size = 0;
|
||||
CHECK_EQ(binfont_generate(&config, &data, &size), ERROR_NOT_FOUND);
|
||||
}
|
||||
|
||||
TEST_CASE("binfont draws glyphs and text into a PixelBuffer") {
|
||||
BinFont* font = open_fixture_in_memory("text_kern_pairs.bin");
|
||||
PixelBuffer* buffer = pixel_buffer_create(DISPLAY_COLOR_FORMAT_RGB565, 64, 24);
|
||||
REQUIRE(buffer != nullptr);
|
||||
pixel_buffer_clear(buffer);
|
||||
|
||||
const int advance = binfont_draw_glyph_rgb565(buffer, 0, 18, font, 'A', 0xFFFF, PIXEL_BUFFER_CONVERSION_EXACT_BLACK);
|
||||
CHECK_EQ(advance, (195 + 8) >> 4);
|
||||
// Top of 'A' (row 0 of the glyph, column 6 is fully opaque) sits 14 rows above the baseline
|
||||
CHECK_EQ(pixel_buffer_get_pixel_rgb565(buffer, 6, 18 - 14), 0xFFFF);
|
||||
CHECK_EQ(pixel_buffer_get_pixel_rgb565(buffer, 0, 0), 0x0000);
|
||||
|
||||
CHECK_EQ(binfont_draw_glyph_rgb565(buffer, 0, 18, font, '0', 0xFFFF, PIXEL_BUFFER_CONVERSION_EXACT_BLACK), 0);
|
||||
|
||||
// "AV" is kerned: 195 - 10 + V's advance
|
||||
BinFontGlyph v;
|
||||
REQUIRE(binfont_get_glyph(font, 'V', &v));
|
||||
pixel_buffer_clear(buffer);
|
||||
const int width = binfont_draw_text_rgb565(buffer, 0, 18, font, "AV", 0xFFFF, PIXEL_BUFFER_CONVERSION_EXACT_BLACK);
|
||||
CHECK_EQ(width, static_cast<int>((195 - 10 + v.advance_x16 + 8) >> 4));
|
||||
|
||||
pixel_buffer_free(buffer);
|
||||
binfont_close(font);
|
||||
}
|
||||
64
Modules/binfont-module/tests/source/main.cpp
Normal file
@ -0,0 +1,64 @@
|
||||
#define DOCTEST_CONFIG_IMPLEMENT
|
||||
#include "doctest.h"
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
|
||||
typedef struct {
|
||||
int argc;
|
||||
char** argv;
|
||||
int result;
|
||||
} TestTaskData;
|
||||
|
||||
void test_task(void* parameter) {
|
||||
auto* data = (TestTaskData*)parameter;
|
||||
|
||||
doctest::Context context;
|
||||
|
||||
context.applyCommandLine(data->argc, data->argv);
|
||||
|
||||
// overrides
|
||||
context.setOption("no-breaks", true); // don't break in the debugger when assertions fail
|
||||
|
||||
data->result = context.run();
|
||||
|
||||
vTaskEndScheduler();
|
||||
|
||||
vTaskDelete(nullptr);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
TestTaskData data = {
|
||||
.argc = argc,
|
||||
.argv = argv,
|
||||
.result = 0
|
||||
};
|
||||
|
||||
BaseType_t task_result = xTaskCreate(
|
||||
test_task,
|
||||
"test_task",
|
||||
8192,
|
||||
&data,
|
||||
1,
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (task_result != pdPASS) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
vTaskStartScheduler();
|
||||
|
||||
return data.result;
|
||||
}
|
||||
|
||||
// NOTE: This is normally provided by the platform kernel module, but that's not loaded for binfont-module
|
||||
extern "C" {
|
||||
// Required for FreeRTOS
|
||||
void vAssertCalled(unsigned long line, const char* const file) {
|
||||
std::fprintf(stderr, "assert failed at %s:%lu\n", file, line);
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
15
Modules/lodepng-module/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/../../Buildscripts/module.cmake")
|
||||
|
||||
file(GLOB_RECURSE SOURCE_FILES "source/*.c*")
|
||||
|
||||
list(APPEND REQUIRES_LIST
|
||||
TactilityKernel
|
||||
)
|
||||
|
||||
tactility_add_module(lodepng-module
|
||||
SRCS ${SOURCE_FILES}
|
||||
INCLUDE_DIRS include/
|
||||
REQUIRES ${REQUIRES_LIST}
|
||||
)
|
||||
195
Modules/lodepng-module/LICENSE-Apache-2.0.md
Normal file
@ -0,0 +1,195 @@
|
||||
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.
|
||||
|
||||
25
Modules/lodepng-module/LICENSE-lodepng.txt
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
LodePNG version 20261001
|
||||
|
||||
Copyright (c) 2005-2026 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.
|
||||
*/
|
||||
|
||||
10
Modules/lodepng-module/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
# lodepng-module
|
||||
|
||||
PNG decoding and encoding with [LodePNG](https://github.com/lvandeve/lodepng), usable with and without LVGL.
|
||||
|
||||
Include `<lodepng/lodepng.h>` and release decoded or encoded data with `lodepng_free()`.
|
||||
|
||||
## License
|
||||
|
||||
This module is licensed under the [Apache v2.0](LICENSE-Apache-2.0.md) license.
|
||||
LodePNG is licensed under the [zlib](LICENSE-lodepng.txt) license.
|
||||
25
Modules/lodepng-module/include/lodepng/lodepng.h
Normal file
@ -0,0 +1,25 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
// The C++ wrapper is not compiled: callers use the C API from C and C++ alike
|
||||
#define LODEPNG_NO_COMPILE_CPP
|
||||
// Allocations go through the kernel's memory API (see lodepng_malloc() below)
|
||||
#define LODEPNG_NO_COMPILE_ALLOCATORS
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <lodepng/lodepng_upstream.h>
|
||||
|
||||
/** Allocates memory, preferring external memory (PSRAM) */
|
||||
void* lodepng_malloc(size_t size);
|
||||
|
||||
void* lodepng_realloc(void* ptr, size_t new_size);
|
||||
|
||||
/** Releases memory from lodepng_malloc(), including images returned by the decode and encode functions */
|
||||
void lodepng_free(void* ptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
2188
Modules/lodepng-module/include/lodepng/lodepng_upstream.h
Normal file
14
Modules/lodepng-module/include/lodepng/module.h
Normal file
@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <tactility/module.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
extern struct Module lodepng_module;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
22
Modules/lodepng-module/source/allocators.c
Normal file
@ -0,0 +1,22 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lodepng/lodepng.h>
|
||||
|
||||
#include <tactility/memory.h>
|
||||
|
||||
static const struct MemoryPolicy EXTERNAL_PREFERRED = {
|
||||
.required = 0,
|
||||
.desired = MEMORY_CAPABILITY_EXTERNAL,
|
||||
.alignment = 0,
|
||||
};
|
||||
|
||||
void* lodepng_malloc(size_t size) {
|
||||
return memory_alloc_with_policy(size, &EXTERNAL_PREFERRED);
|
||||
}
|
||||
|
||||
void* lodepng_realloc(void* ptr, size_t new_size) {
|
||||
return memory_realloc_with_policy(ptr, new_size, &EXTERNAL_PREFERRED);
|
||||
}
|
||||
|
||||
void lodepng_free(void* ptr) {
|
||||
memory_free(ptr);
|
||||
}
|
||||
7249
Modules/lodepng-module/source/lodepng.c
Normal file
37
Modules/lodepng-module/source/module.c
Normal file
@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#include <lodepng/lodepng.h>
|
||||
#include <lodepng/module.h>
|
||||
|
||||
static const struct ModuleSymbol SYMBOLS[] = {
|
||||
// decode
|
||||
DEFINE_MODULE_SYMBOL(lodepng_decode_memory),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_decode32),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_decode24),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_decode_file),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_decode32_file),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_decode24_file),
|
||||
// encode
|
||||
DEFINE_MODULE_SYMBOL(lodepng_encode_memory),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_encode32),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_encode24),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_encode_file),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_encode32_file),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_encode24_file),
|
||||
// utilities
|
||||
DEFINE_MODULE_SYMBOL(lodepng_error_text),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_load_file),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_save_file),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_malloc),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_realloc),
|
||||
DEFINE_MODULE_SYMBOL(lodepng_free),
|
||||
MODULE_SYMBOL_TERMINATOR,
|
||||
};
|
||||
|
||||
struct Module lodepng_module = {
|
||||
.name = "lodepng",
|
||||
.start = NULL,
|
||||
.stop = NULL,
|
||||
.drivers = NULL,
|
||||
.symbols = SYMBOLS,
|
||||
.internal = NULL,
|
||||
};
|
||||
20
Modules/lodepng-module/tests/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
project(LodePngModuleTests)
|
||||
|
||||
enable_language(C CXX ASM)
|
||||
|
||||
file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/source/*.cpp)
|
||||
add_executable(LodePngModuleTests EXCLUDE_FROM_ALL ${TEST_SOURCES})
|
||||
|
||||
target_include_directories(LodePngModuleTests PRIVATE ${DOCTESTINC})
|
||||
|
||||
target_compile_definitions(LodePngModuleTests PRIVATE
|
||||
"LODEPNG_TEST_LOGO=\"${CMAKE_SOURCE_DIR}/Data/system/app/Boot/assets/logo.png\""
|
||||
)
|
||||
|
||||
add_test(NAME LodePngModuleTests COMMAND LodePngModuleTests)
|
||||
|
||||
target_link_libraries(LodePngModuleTests PUBLIC
|
||||
lodepng-module
|
||||
TactilityKernel
|
||||
freertos_kernel
|
||||
)
|
||||
47
Modules/lodepng-module/tests/source/lodepng_test.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "doctest.h"
|
||||
|
||||
#include <lodepng/lodepng.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
TEST_CASE("lodepng decodes a PNG file") {
|
||||
unsigned char* image = nullptr;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
REQUIRE_EQ(lodepng_decode32_file(&image, &width, &height, LODEPNG_TEST_LOGO), 0);
|
||||
CHECK_EQ(width, 80);
|
||||
CHECK_EQ(height, 103);
|
||||
lodepng_free(image);
|
||||
}
|
||||
|
||||
TEST_CASE("lodepng returns an error for a missing file") {
|
||||
unsigned char* image = nullptr;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
CHECK_NE(lodepng_decode32_file(&image, &width, &height, "missing.png"), 0);
|
||||
lodepng_free(image);
|
||||
}
|
||||
|
||||
TEST_CASE("lodepng encodes and decodes an image without changes") {
|
||||
constexpr unsigned width = 3;
|
||||
constexpr unsigned height = 2;
|
||||
const unsigned char pixels[width * height * 4] = {
|
||||
255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255,
|
||||
0, 0, 0, 0, 255, 255, 255, 128, 10, 20, 30, 40,
|
||||
};
|
||||
|
||||
unsigned char* png = nullptr;
|
||||
size_t png_size = 0;
|
||||
REQUIRE_EQ(lodepng_encode32(&png, &png_size, pixels, width, height), 0);
|
||||
|
||||
unsigned char* decoded = nullptr;
|
||||
unsigned decoded_width = 0;
|
||||
unsigned decoded_height = 0;
|
||||
REQUIRE_EQ(lodepng_decode32(&decoded, &decoded_width, &decoded_height, png, png_size), 0);
|
||||
CHECK_EQ(decoded_width, width);
|
||||
CHECK_EQ(decoded_height, height);
|
||||
CHECK_EQ(memcmp(decoded, pixels, sizeof(pixels)), 0);
|
||||
|
||||
lodepng_free(png);
|
||||
lodepng_free(decoded);
|
||||
}
|
||||