Skip to content

Commit dcc671c

Browse files
authored
Merge pull request #26 from tixlegeek/tixlegeek/enhance_swipe_anim
Enhance bitmap animation display. Use frame based switch mechanism instead of time based Add more configuration options Fix conversion script
2 parents 6e1d3ae + 581c20d commit dcc671c

29 files changed

+145
-108
lines changed

401lightMessengerApp/401LightMsg_acc.c

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ static void swipes_init(void* ctx, uint8_t direction) {
149149
}
150150

151151
if(appAcc->direction != direction) {
152+
appAcc->swipes_count ++;
152153
if(appAcc->cyclesAvg != 0)
153154
appAcc->cyclesAvg = (appAcc->cyclesAvg + appAcc->cycles) / 2;
154155
else
@@ -220,11 +221,10 @@ static int32_t app_acc_worker(void* ctx) {
220221

221222
uint32_t render_delay_us = lightmsg_width_value[light_msg_data->width];
222223

223-
uint32_t tick = furi_get_tick();
224224
uint32_t direction_change_count = 0;
225225
uint8_t end_message_count = 0;
226226

227-
uint32_t message_duration_ms = lightmsg_speed_value[light_msg_data->speed];
227+
uint32_t message_duration_swipe = lightmsg_speed_value[light_msg_data->speed];
228228
uint32_t flush_counter = 0;
229229

230230
while(running) {
@@ -247,15 +247,11 @@ static int32_t app_acc_worker(void* ctx) {
247247
direction_change_count++;
248248
}
249249

250-
// Don't start the timer if we have changed direction more than 3 times
251-
if(direction_change_count < 3) {
252-
tick = furi_get_tick();
253-
}
254250

255251
// Beginning of the swipe
256252
if(appAcc->cycles == 1) {
257253
// Change the bitmap if we have exceeded the message duration
258-
if((furi_get_tick() - tick) > message_duration_ms) {
254+
if(!(appAcc->swipes_count % message_duration_swipe)) {
259255
if(bitmapMatrix->next_bitmap) {
260256
bitmapMatrix = bitmapMatrix->next_bitmap;
261257
} else {
@@ -265,9 +261,6 @@ static int32_t app_acc_worker(void* ctx) {
265261
bitmapMatrix = bitmapMatrix->next_bitmap;
266262
}
267263
}
268-
269-
// Reset the timer
270-
tick = furi_get_tick();
271264
}
272265

273266
// Start the animation again once we have reached the end of the bitmaps
@@ -276,7 +269,8 @@ static int32_t app_acc_worker(void* ctx) {
276269
bitmapMatrix = appAcc->bitmapMatrix;
277270

278271
// If we have multiple bitmaps, play a short vibration to signal the end of the message
279-
if(bitmapMatrix->next_bitmap != NULL) {
272+
//(but not in fast mode)
273+
if(bitmapMatrix->next_bitmap != NULL && (message_duration_swipe>2)) {
280274
for(int i = 0; i < 2; i++) {
281275
furi_hal_vibro_on(true);
282276
furi_delay_ms(100 * i);
@@ -285,7 +279,6 @@ static int32_t app_acc_worker(void* ctx) {
285279
}
286280

287281
direction_change_count = 0;
288-
tick = furi_get_tick();
289282
}
290283
}
291284
}

401lightMessengerApp/401LightMsg_acc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ typedef struct AppAcc {
3939
uint16_t cycles; // Cycles counter
4040
uint16_t cyclesAvg; // Average cycles
4141
uint16_t cyclesCenter;
42+
uint32_t swipes_count;
4243
bool direction;
4344
// void *data;
4445
} AppAcc;

401lightMessengerApp/401LightMsg_config.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,16 +131,19 @@ const char* const lightmsg_mirror_text[] = {
131131
};
132132

133133
// Speed (ms) to change text being display (0=never, 1 sec, 0.5 sec)
134+
// ERRATA: Now represent a "swipe number"
134135
const uint32_t lightmsg_speed_value[] = {
135136
UINT32_MAX,
136-
1000,
137-
500,
137+
16,
138+
8,
139+
2,
138140
};
139141

140142
const char* const lightmsg_speed_text[] = {
141143
"Off",
142144
"Slow",
143-
"Fast",
145+
"Medium",
146+
"Fast"
144147
};
145148

146149
// Delay in microseconds (us) to wait after rendering a column

401lightMessengerApp/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,6 @@ v1.3.1
1818

1919
v1.4
2020
- Merge of the animation function from @jamisonderek
21+
22+
v1.4.1
23+
- Enhance animation display
569 Bytes
Loading
-19.3 KB
Loading

401lightMessengerApp/README.md

Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ This modules embeds an i2c **accelerometer**(LIS2XX12), and an array of **16 RGB
1212

1313
## How to use
1414

15-
Plug the device on the GPIO header of the flipper zero, then download an install the latest firmware's version on your flipper zero.
15+
Plug the device on the GPIO header of the flipper zero, then download an install the latest firmware's version on your flipper zero.
1616
Once everything is installed, navigate to **/Apps/GPIO/401LightMessenger** to launch the application.
1717

1818
![lm02](./README.assets/lm01.png)
@@ -29,38 +29,38 @@ Entering the "**Text**" application, you're prompted with a text-input, allowing
2929

3030
![text](./README.assets/doc_text.png)
3131

32-
Select "save" to save your text. The module should flash once, and the text "Swipe" appears on the screen.
32+
Select "save" to save your text. The module should flash once, and the text "Swipe" appears on the screen.
3333

3434
### Bitmap
3535

36-
Entering the "**Bitmap**" application, you're invited to select a bitmap file to be displayed.
36+
Entering the "**Bitmap**" application, you're invited to select a bitmap file to be displayed.
3737

3838
![lm_bmp02](./README.assets/doc_bitmap.png)
3939

4040

4141

42-
Only 1bpp bitmaps, with a hight of 16px can be displayed. Once selected, the text "Swipe" appears on the screen.
42+
Only 1bpp bitmaps, with a hight of 16px can be displayed. Once selected, the text "Swipe" appears on the screen.
4343

4444

4545

4646
### Bitmap Editor
4747

48-
Entering "**BitmapEditor**", you have two options: Create a new doodle, or edit one you've already created/imported.
48+
Entering "**BitmapEditor**", you have two options: Create a new doodle, or edit one you've already created/imported.
4949

5050
![lm_bmp02](./README.assets/doc_bitmap_edit.png)
5151

5252
#### New bitmap
5353

54-
When selecting "New", you shall enter the width of the doodle you want to draw.
54+
When selecting "New", you shall enter the width of the doodle you want to draw.
5555

5656
![lm_bmp02](./README.assets/lm_bmpedit03.png)
5757

5858
Choose the desired width by using left and right arrows, then press OK.
59-
You'll then have to choose a name for the new file.
59+
You'll then have to choose a name for the new file.
6060

6161
![lm_bmp02](./README.assets/lm_bmpedit04.png)
6262

63-
When done, select "**save**". To draw, just move the cursor using the arrows, and toggle pixels by pressing the "**Ok**" button. To save, press "**Return**".
63+
When done, select "**save**". To draw, just move the cursor using the arrows, and toggle pixels by pressing the "**Ok**" button. To save, press "**Return**".
6464

6565
**V1.3:** If you press longer on "**Ok**", then "**Hold**" will be activated, continuously drawing by flipping underlying pixels. No more Ok button smashing.
6666

@@ -78,35 +78,34 @@ Starting from version 1.4, you can display animations thanks to [@jamisonderek i
7878

7979
##### Using the Script:
8080

81-
1. Download [convertFzLM.sh](./convertFzLM.sh).
81+
1. Download [convertFzLM.sh](https://raw.githubusercontent.com/tixlegeek/fzLightMessenger/refs/heads/tixlegeek/enhance_swipe_anim/401lightMessengerApp/convertFzLM.sh).
8282

83-
2. Grab a cool GIF. For this example, we'll use a short sequence from BadApple:
83+
2. Grab a cool GIF. For this example, we'll use a short GIF sequence:
8484

85-
![badapple](./README.assets/badaple.gif)
85+
![badapple](./README.assets/pacman.gif)
8686

8787
3. Use the script to convert the image into a BMP sequence:
8888

89-
```
90-
$ ./convertFzLM.sh badaple.gif
91-
[+] Frames have been extracted and saved in "./badaple/...":
92-
badaple_0.bmp badaple_2.bmp badaple_5.bmp badaple_8.bmp
93-
badaple_10.bmp badaple_3.bmp badaple_6.bmp badaple_9.bmp
94-
badaple_1.bmp badaple_4.bmp badaple_7.bmp
89+
```bash
90+
$ ./convertFzLM.sh pacman-video-game.gif # (-i to invert colors)
91+
[*] Frame 0 ← index 0
92+
[*] Frame 1 ← index 1
93+
[*] Frame 2 ← index 2
94+
[*] Frame 3 ← index 3
95+
[✓] Done. 4 frame(s) written to 'pacman-video-game' (true 1bpp BMPv1, no dithering)
9596
```
9697

97-
​ This should give you this:![bitmap squence](./README.assets/sequence_screenshot.png)
98+
​ This should give you this:![bitmap squence](./README.assets/sequence_screenshot.png)
9899

99-
4. Using QFlipper, copy the directory in ```SD Card/app_assets/401_light_msg```
100+
4. Using **QFlipper**, copy the directory in ```SD Card/app_assets/401_light_msg```
100101

101102
> [!NOTE]
102103
>
103104
> A Flipper Zero reboot may be necessary after closing QFlipper.
104105
105-
5. On lightMessenger app, go to **Configuration** and set "A Flipper Zero reboot may be necessary after closing QFlipper." to **Slow** or **Fast**
106-
6. Open "badaple/badaple_0.bmp" as Bitmap, and swipe
107-
7. You may need to squint a little to see it:
108-
109-
![badapple_hw](./README.assets/badapple_hw.gif)
106+
5. On lightMessenger app, go to **Configuration** and set Word by word " to **Slow** ,**Medium** or **Fast**
107+
6. Open "pacman/pacman_0.bmp" as **Bitmap**, and swipe
108+
7. You may need to squint a little to see it
110109

111110
### FlashLight
112111

@@ -126,9 +125,9 @@ The orientation allows to choose between "Wheel Up" and "WheelDown". Select the
126125

127126
#### Color
128127

129-
Multiple color shaders are available:
128+
Multiple color shaders are available:
130129

131-
- Single colors (R3d, 0r4ng3, Y3ll0w, Gr33n, Cy4n, Blu3, Purpl3)
130+
- Single colors (R3d, 0r4ng3, Y3ll0w, Gr33n, Cy4n, Blu3, Purpl3)
132131
- Animated (Ny4nC4t, R4inb0w, Sp4rkl3, V4p0rw4v3, R3dBlu3)
133132

134133
## Swipe
@@ -144,7 +143,7 @@ The display is based on [PoV](https://en.wikipedia.org/wiki/Persistence_of_visio
144143
145144

146145

147-
You can also photobomb if you build up enought skill.
146+
You can also photobomb if you build up enought skill.
148147

149148
![lm02](./README.assets/doc01.png)
150149

@@ -202,7 +201,7 @@ ufbt launch
202201
- If you get `[ERROR] Error: could not open port : PermissionError(13, 'Access is denied.', None, 5)` verify that no other applications are running. If needed press Back+Left to reboot the Flipper Zero.
203202
- If you are not on Official firmware, you may need to update your [index-url](https://github.com/jamisonderek/flipper-zero-tutorials/wiki/UFBT#switching-target-firmware).
204203

205-
### Hardware
204+
### Hardware
206205

207206
![schematics](./README.assets/schematics.png)
208207

@@ -233,12 +232,12 @@ To add a new configuration entry:
233232
- Update the [401_config.h](401lightMessengerApp/401_config.h) file:
234233
- Add a new property to the `Configuration` structure.
235234
- Define a new `LIGHTMSG_DEFAULT_{name}` with a default setting. Typically indexes are used instead of values.
236-
235+
237236
- Update the [401_config.c](401lightMessengerApp/401_config.c) file:
238237
- Add an entry in `config_default_init` to initialize your property.
239238
- Add an entry in `config_to_json` to serialize your property.
240239
- Add an entry in `json_to_config` to deserialize your property.
241-
240+
242241

243242
> [!NOTE]
244243
>
@@ -265,4 +264,3 @@ AppData* appData = (AppData*)app->data;
265264
Configuration* light_msg_data = (Configuration*)appData->config;
266265
uint8_t value = listmsg_{name}_value[light_msg_data->{name}]; // Replace {name} with your feature name.
267266
```
268-

401lightMessengerApp/application.fam

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@ App(
1111
fap_icon="images/401_lghtmsg_icon_10px.png",
1212
fap_category="GPIO",
1313
fap_icon_assets="images",
14-
fap_version="1.4",
14+
fap_version="1.4.1",
1515
)
Lines changed: 71 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,85 @@
11
#!/bin/bash
22

3-
# Create a 10 frame 1bpp bitmap sequence from any gif:
3+
# Converts a GIF to up to 10 true 1bpp BMPv1 frames (≤42x16) without dithering.
4+
# Optional -i flag inverts black/white logic.
5+
# Dependencies: ffmpeg, ImageMagick (`convert`)
46

5-
# Check if an input file is provided
6-
if [ -z "$1" ]; then
7-
echo "[?] Usage: $0 <input_gif>"
7+
set -euo pipefail
8+
9+
# --- Dependency checks ---
10+
command -v ffmpeg >/dev/null || { echo "[x] ffmpeg not found. Install it with: sudo apt install ffmpeg"; exit 1; }
11+
command -v convert >/dev/null || { echo "[x] ImageMagick 'convert' not found. Install it with: sudo apt install imagemagick"; exit 1; }
12+
13+
# --- Parse options ---
14+
INVERT=0
15+
while getopts "i" opt; do
16+
case "$opt" in
17+
i) INVERT=1 ;; # -i: Invert black/white logic
18+
esac
19+
done
20+
shift $((OPTIND - 1))
21+
22+
# --- Check arguments ---
23+
if [ $# -ne 1 ]; then
24+
echo "Usage: $0 [-i] input.gif"
825
exit 1
926
fi
1027

11-
# Input GIF file from arguments
12-
input_gif="$1"
28+
INPUT_GIF="$1"
29+
[ ! -f "$INPUT_GIF" ] && { echo "[x] File not found: $INPUT_GIF"; exit 1; }
1330

14-
# Extract the file name without the extension
15-
file_name=$(basename "$input_gif" | sed 's/\(.*\)\..*/\1/')
31+
# --- Setup ---
32+
BASE=$(basename "$INPUT_GIF" .gif)
33+
OUTDIR="$BASE"
34+
mkdir -p "$OUTDIR"
1635

17-
if [ ! -d $file_name ]; then
18-
echo "[+] Creating $file_name directory..."
19-
mkdir -p $file_name || exit 1
20-
fi
36+
# --- Count frames ---
37+
TOTAL_FRAMES=$(ffprobe -v error -select_streams v:0 -count_frames \
38+
-show_entries stream=nb_read_frames \
39+
-of default=nokey=1:noprint_wrappers=1 "$INPUT_GIF")
40+
TOTAL_FRAMES=${TOTAL_FRAMES:-1}
41+
FRAME_COUNT=$(( TOTAL_FRAMES < 10 ? TOTAL_FRAMES : 10 ))
2142

22-
# Get the total number of frames using ffprobe
23-
total_frames=$(ffprobe -v error -select_streams v:0 -show_entries stream=nb_frames -of default=noprint_wrappers=1:nokey=1 "$input_gif")
43+
# --- Compute evenly spaced frame indices ---
44+
INDICES=()
45+
for i in $(seq 0 $((FRAME_COUNT - 1))); do
46+
INDICES+=($((i * TOTAL_FRAMES / FRAME_COUNT)))
47+
done
2448

25-
# Check if total_frames is successfully retrieved
26-
if [ -z "$total_frames" ]; then
27-
echo "[x] Failed to get the total frame count from the GIF."
28-
exit 1
29-
fi
49+
# --- Extract all frames as PNGs ---
50+
ffmpeg -v error -i "$INPUT_GIF" -vsync 0 -start_number 0 "$OUTDIR/${BASE}_raw_%d.png"
3051

31-
# Calculate the frame interval (total_frames / 10)
32-
frame_interval=$((total_frames / 10))
52+
# --- Process selected frames ---
53+
COUNT=0
54+
for IDX in "${INDICES[@]}"; do
55+
RAW="$OUTDIR/${BASE}_raw_${IDX}.png"
56+
CROP="$OUTDIR/${BASE}_crop_${COUNT}.png"
57+
OUT="$OUTDIR/${BASE}_${COUNT}.bmp"
3358

34-
# Check if the interval is valid (greater than 0)
35-
if [ "$frame_interval" -le 0 ]; then
36-
echo "[x] Frame interval is too small. The GIF might have fewer than 10 frames."
37-
exit 1
38-
fi
59+
echo "[*] Frame $COUNT ← index $IDX"
60+
61+
# Resize to 16px height, crop 42x16 from top-left
62+
convert "$RAW" -resize x16 \
63+
-gravity NorthWest -crop 42x16+0+0 +repage \
64+
"$CROP"
65+
66+
# Threshold and optional inversion
67+
if [ "$INVERT" -eq 1 ]; then
68+
FILTER="format=gray,geq=lum_expr='gt(lum(X,Y)\,128)*255'"
69+
else
70+
FILTER="format=gray,geq=lum_expr='lte(lum(X,Y)\,128)*255'"
71+
fi
72+
73+
# Convert to true 1bpp BMP
74+
ffmpeg -v error -y -i "$CROP" \
75+
-vf "$FILTER" \
76+
-pix_fmt monow "$OUT"
77+
78+
rm -f "$CROP"
79+
COUNT=$((COUNT + 1))
80+
done
3981

40-
# Run ffmpeg to select 10 frames, resize, and crop
41-
ffmpeg -i "$input_gif" -pix_fmt monow -loglevel quiet -start_number 0 -vf "select='not(mod(n\,$frame_interval))',scale=42:-1,crop=42:16" -vsync vfr "${file_name}/${file_name}_%d.bmp"
82+
# --- Cleanup ---
83+
rm -f "$OUTDIR/${BASE}_raw_"*.png
4284

43-
echo "[+] Frames have been extracted and saved in \"./${file_name}/...\":"
44-
ls "${file_name}/"
85+
echo "[✓] Done. $COUNT frame(s) written to '$OUTDIR' (true 1bpp BMPv1, no dithering)"
-190 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)