Skip to content

Conversation

netmindz
Copy link
Collaborator

@netmindz netmindz commented Jun 15, 2025

Port of wled#3921 to MoonModules WLED-MM
Also includes #226 for ease of testing

Summary by CodeRabbit

  • New Features

    • Introduced a GIF Player web interface for WLED devices, enabling upload, viewing, and management of GIF, BMP, and PNG images.
    • Added playback controls (play/pause, stop) and preset management for images.
    • Added a "GIF" button in the Colors tab for quick access to the GIF Player.
    • Enabled serving the GIF Player page via the device’s HTTP server with gzip compression.
  • Improvements

    • Enhanced user feedback with toast notifications and responsive UI elements.
    • Added a utility function for consistent URL handling within the interface.

Manut38 added 8 commits June 15, 2025 12:35
- loading spinner now showing on upload
- hostname value now trimmed and checked for change before refresh
- remove invisible upload button
they will now use the existing presets, even if they were previously renamed
essentially sending the "off" command
- move advanced features to config dialog
Copy link

coderabbitai bot commented Jun 15, 2025

Walkthrough

A GIF player feature was integrated into the WLED project. This includes adding a new HTML interface for managing and playing GIF images, updating the main UI to link to this page, extending server logic to serve the GIF player, and introducing a utility function for URL handling. Build tooling was updated to process the new HTML file.

Changes

File(s) Change Summary
Build Tooling
tools/cdata.js
Added processing for gifplayer.htm to generate html_gifplayer.h; updated output list in a commented-out section.
New GIF Player UI
wled00/data/gifplayer/gifplayer.htm
New HTML file implementing a complete GIF player UI with embedded JS/CSS for WLED device image management.
Main UI Update
wled00/data/index.htm
Added a "GIF" button in the Colors tab that navigates to the GIF player page.
Utility Function
wled00/data/index.js
Added getURL(path) utility function for protocol/IP-aware URL construction.
Server Integration
wled00/wled_server.cpp
Added conditional serving of gifplayer.htm via a new HTTP GET route and header inclusion.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant WLED Server

    User->>Browser: Clicks "GIF" button
    Browser->>WLED Server: GET /gifplayer.htm
    WLED Server-->>Browser: Serve gifplayer.htm (from memory if enabled)
    Browser->>User: Display GIF player UI

    User->>Browser: Upload/select GIF image
    Browser->>WLED Server: POST image file
    WLED Server-->>Browser: Acknowledge upload

    User->>Browser: Play/pause/stop GIF
    Browser->>WLED Server: Send JSON command
    WLED Server-->>Browser: Respond with status
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes
The changes span multiple components including build tooling, server code, and a substantial new HTML/JS UI file. The complexity lies mainly in the new client-side interface and server integration, requiring moderate review time.

Possibly related PRs

  • GIF Playback #226: Adds the GIF player web UI and its integration, complementing this PR which introduces the core GIF playback functionality and related firmware support.

Poem

A hop to the GIF world, a leap through the wire,
Now WLED plays images that flicker and inspire!
With a click on a button, a gallery appears—
Upload, play, and manage, bring digital cheers.
From server to browser, the journey is swift,
This bunny’s new feature is a graphical gift!
🐰✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02fd904 and f28166b.

📒 Files selected for processing (1)
  • wled00/data/index.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/data/index.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (esp8266_4MB_S)
  • GitHub Check: Builds (esp32c3dev_2MB_M)
  • GitHub Check: Builds (esp32s2_PSRAM_S)
  • GitHub Check: Builds (seeed_esp32c3_4MB_S)
  • GitHub Check: Builds (esp01_1MB_S)
  • GitHub Check: Builds (esp8266pro_16MB_M)
  • GitHub Check: Builds (esp8266_2MB_S)
  • GitHub Check: Builds (esp32s2_PSRAM_M)
  • GitHub Check: Builds (esp32c3dev_4MB_M)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_M)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp32S3_16MB_PSRAM_M_HUB75)
  • GitHub Check: Builds (esp32_4MB_V4_S)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32S3_8MB_M)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (1)
wled00/data/gifplayer/gifplayer.htm (1)

835-837: AbortSignal.timeout() is not yet supported in all browsers

Fetch will throw TypeError: AbortSignal.timeout is not a function on e.g. Safari ≤ 17 or older Chrome.
Consider a polyfill or manual AbortController for wider compatibility.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7cb8eeb and 3ccd5e1.

📒 Files selected for processing (4)
  • tools/cdata.js (2 hunks)
  • wled00/data/gifplayer/gifplayer.htm (1 hunks)
  • wled00/data/index.htm (1 hunks)
  • wled00/wled_server.cpp (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (20)
  • GitHub Check: Builds (esp01_1MB_S)
  • GitHub Check: Builds (athom_music_esp32_4MB_M)
  • GitHub Check: Builds (esp8266pro_16MB_M)
  • GitHub Check: Builds (esp8266_2MB_S)
  • GitHub Check: Builds (esp8266_4MB_M)
  • GitHub Check: Builds (esp32c3dev_4MB_M)
  • GitHub Check: Builds (seeed_esp32c3_4MB_S)
  • GitHub Check: Builds (esp32S3_WROOM-2_M)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32S3_8MB_M)
  • GitHub Check: Builds (esp32S3_8MB_PSRAM_M)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32_4MB_V4_S)
  • GitHub Check: Builds (esp32_4MB_M)
  • GitHub Check: Builds (esp32_16MB_M_eth)
  • GitHub Check: Builds (esp32_4MB_XL)
  • GitHub Check: Builds (esp32_4MB_S)
  • GitHub Check: Builds (esp32_4MB_M_eth)
🔇 Additional comments (2)
tools/cdata.js (1)

226-228: Confirm gzip-generation order

writeHtmlGzipped() is invoked for gifplayer.htm after the other UI pages, which is fine, but remember: build order controls the numeric IDs compiled into PROGMEM.
If older user-code assumes a fixed index sequence, add the new page at the end everywhere (server routes, enum lists) or document the breaking change.

wled00/wled_server.cpp (1)

12-14: Header must be gated behind the same compile-time flag

#include "html_gifplayer.h" is already wrapped in #ifndef WLED_DISABLE_GIFPLAYER, good.
Just check that tools/cdata.js really emits PAGE_gifplayer_L (capital L) – a typo here will only surface at link-time.

Comment on lines +1131 to +1134
// await loading of image list, actual images will load asynchronously
if (await images());
{
// await loading of presets.json
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Logic bug – stray semicolon after if

if (await images());
{
  // this block always executes
}

Remove the extra semicolon.

-if (await images());
-{
+if (await images()) {
   ...
 }
🤖 Prompt for AI Agents
In wled00/data/gifplayer/gifplayer.htm around lines 1131 to 1134, there is a
stray semicolon immediately after the if condition "if (await images());" which
causes the following block to always execute regardless of the condition. Remove
the semicolon after the if statement to ensure the block only executes when the
condition is true.

Comment on lines +756 to +760
const hostname = gId("hostname");
const imgDialog = gId("image-dialog");
const settingsDialog = gId("settings-dialog");
const contentDivConnected = gId("content-connected");
let selectedImage;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrong element reference – will always be null

const contentDivConnected = gId("content-connected");

The actual element is id="contentConnected".
Either change the JS or the markup; otherwise contentDivConnected is null and any future access will throw.

🤖 Prompt for AI Agents
In wled00/data/gifplayer/gifplayer.htm around lines 756 to 760, the variable
contentDivConnected is assigned using gId("content-connected"), but the actual
element ID in the markup is "contentConnected". To fix this, update the
JavaScript to use gId("contentConnected") so it correctly references the
existing element and does not return null.

Comment on lines +1018 to +1024

// find next lowest ID / ID of existing preset with segment name == image name
let existingPreset = false;
existingPreset = findPresetID(selectedImage.name);
items = Object.keys(presetsJson);
const id = existingPreset || parseInt(items.pop()) + 1;
const pName = existingPreset
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Implicit globals (items, formData, newWledUrl …)

Several variables are assigned without let/const, leaking them to window:

  • items = Object.keys(...)
  • formData = new FormData()
  • newWledUrl = ...

Always declare with let/const to avoid hard-to-trace bugs.

-let items = Object.keys(...);
+const items = Object.keys(...);

-let formData = new FormData();
+const formData = new FormData();

Also applies to: 1142-1144

🤖 Prompt for AI Agents
In wled00/data/gifplayer/gifplayer.htm around lines 1018 to 1024 and also lines
1142 to 1144, variables like items, formData, and newWledUrl are assigned
without let or const, causing them to become implicit globals. To fix this,
declare these variables explicitly using let or const at their first assignment
to prevent polluting the global scope and avoid potential bugs.

Comment on lines +1196 to +1204
source.files = e.dataTransfer.files;
const { name } = source.files[0];
upload();
});

gId("dropzone").addEventListener("click", () => {
source.click();
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

DOM element accessed via implicit global source

Relying on the HTML-collection fallback (source → element with id source) is deprecated and breaks in strict mode.
Use gId('source') consistently.

- source.files = e.dataTransfer.files;
+ gId('source').files = e.dataTransfer.files;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
source.files = e.dataTransfer.files;
const { name } = source.files[0];
upload();
});
gId("dropzone").addEventListener("click", () => {
source.click();
});
gId('source').files = e.dataTransfer.files;
const { name } = source.files[0];
upload();
});
gId("dropzone").addEventListener("click", () => {
source.click();
});
🤖 Prompt for AI Agents
In wled00/data/gifplayer/gifplayer.htm around lines 1196 to 1204, the code uses
the implicit global variable `source` to access the DOM element, which is
deprecated and incompatible with strict mode. Replace all instances of `source`
with `gId('source')` to explicitly get the element by its ID, ensuring
consistent and standards-compliant DOM access.

Comment on lines +570 to +576
<div id="btnBack">
<div
class="btn-icon back"
id="btnBack"
onclick="window.location.href = WLED_URL;"
></div>
</div>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Duplicate ID btnBack violates HTML spec

The outer wrapper and the inner icon both use id="btnBack".
Duplicate IDs break getElementById() and CSS specificity.

-<div id="btnBack">
-  <div class="btn-icon back" id="btnBack" ...></div>
+<div id="btnBack">
+  <div class="btn-icon back" ...></div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div id="btnBack">
<div
class="btn-icon back"
id="btnBack"
onclick="window.location.href = WLED_URL;"
></div>
</div>
<div id="btnBack">
<div
class="btn-icon back"
onclick="window.location.href = WLED_URL;"
></div>
</div>
🤖 Prompt for AI Agents
In wled00/data/gifplayer/gifplayer.htm between lines 570 and 576, the outer div
and the inner div both use the same id "btnBack", which violates HTML
specifications by duplicating IDs. To fix this, remove the id attribute from the
inner div or rename it to a unique value so that only one element has the id
"btnBack". This will ensure getElementById() and CSS selectors work correctly
without conflicts.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
wled00/FX.cpp (1)

4664-4667: Remove dead / commented-out debug code

Lines 4664-4667 are legacy debugging snippets that are now superseded by the proposed explicit error handling. Keeping them commented clutters the file.

-  // if (status != 0 && status != 254 && status != 255) {
-  //   Serial.print("GIF renderer return: ");
-  //   Serial.println(status);
-  // }
wled00/image_loader.cpp (1)

38-44: openGif never clears gifDecodeFailed on success
A previous failure leaves the flag set, forcing IMAGE_ERROR_PREV even after a successful open. Reset it here or right after successful startDecoding().

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3ccd5e1 and 248e1a4.

📒 Files selected for processing (7)
  • platformio.ini (2 hunks)
  • wled00/FX.cpp (2 hunks)
  • wled00/FX.h (1 hunks)
  • wled00/FX_fcn.cpp (1 hunks)
  • wled00/fcn_declare.h (1 hunks)
  • wled00/image_loader.cpp (1 hunks)
  • wled00/wled_server.cpp (2 hunks)
✅ Files skipped from review due to trivial changes (3)
  • wled00/FX_fcn.cpp
  • platformio.ini
  • wled00/fcn_declare.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/wled_server.cpp
🧰 Additional context used
🧬 Code Graph Analysis (1)
wled00/FX.cpp (2)
wled00/image_loader.cpp (2)
  • renderImageToSegment (78-131)
  • renderImageToSegment (78-78)
wled00/fcn_declare.h (1)
  • renderImageToSegment (121-121)
⏰ Context from checks skipped due to timeout of 90000ms (20)
  • GitHub Check: Builds (adafruit_matrixportal_esp32s3_tinyUF2)
  • GitHub Check: Builds (seeed_esp32c3_4MB_S)
  • GitHub Check: Builds (athom_music_esp32_4MB_M)
  • GitHub Check: Builds (esp8266_4MB_S)
  • GitHub Check: Builds (esp32s2_PSRAM_M)
  • GitHub Check: Builds (esp32S3_WROOM-2_M)
  • GitHub Check: Builds (esp32_4MB_V4_HUB75_forum)
  • GitHub Check: Builds (esp32S3_8MB_PSRAM_M)
  • GitHub Check: Builds (esp32s2_PSRAM_S)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_M)
  • GitHub Check: Builds (esp32_16MB_V4_M)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32_16MB_V4_S)
  • GitHub Check: Builds (wemos_shield_esp32_4MB_M)
  • GitHub Check: Builds (esp32_pico_4MB_M)
  • GitHub Check: Builds (esp32_16MB_M_eth)
  • GitHub Check: Builds (esp32_16MB_M)
  • GitHub Check: Builds (esp32_4MB_S)
  • GitHub Check: Builds (esp32_4MB_M)
  • GitHub Check: Builds (esp32_4MB_M_debug)
🔇 Additional comments (4)
wled00/FX.h (2)

338-341:

✅ Verification successful

MODE_COUNT consistency check

MODE_COUNT is hard-coded to 198. With the new IMAGE mode the expected total increases by one; if any mode is still conditionally compiled out, the real count may diverge from this constant. Mismatches break array indexing and JSON dumps.

Verify that the number of FX_MODE_* defines (excluding commented lines) equals MODE_COUNT under every build configuration, or compute the value automatically in the build system instead of a literal.


🏁 Script executed:

#!/bin/bash
# Detect duplicates and count defined FX_MODE_* constants
rg --no-heading --trim --regexp "#define[ \t]+FX_MODE_" -n |
  awk '{print $3}' | sort -n | uniq -d
echo "Total modes defined:"
rg --no-heading --trim --regexp "#define[ \t]+FX_MODE_" -n | wc -l

Length of output: 223


MODE_COUNT matches total FX_MODE definitions
Verified there are exactly 198 FX_MODE_* defines and no duplicates, matching MODE_COUNT. No mismatch detected. Consider automating this count in the build system to avoid future drift when modes are added or conditionally compiled out.


188-191:

✅ Verification successful

New constant FX_MODE_IMAGE may be left without a registered effect

The enum now reserves ID 53 for FX_MODE_IMAGE, unconditionally, but the actual effect registration (in WS2812FX::setupEffectData() / FX.cpp) is normally wrapped in #ifdef WLED_ENABLE_GIF.
When the flag is not enabled, the vectors _mode / _modeData will have only MODE_COUNT - 1 items, so every access that assumes a valid entry at index 53 (e.g. UI look-ups, JSON export) will produce bogus data or out-of-bounds reads.

Two safe fixes:

-#define FX_MODE_IMAGE                   53
+#ifdef WLED_ENABLE_GIF
+#define FX_MODE_IMAGE                   53
+#endif

or, if you want to keep contiguous numbering, always push a stub entry (name = "Image", function = mode_static) when GIF support is disabled.

Please confirm that setupEffectData() does one of the above, otherwise runtime errors are likely.


🏁 Script executed:

#!/bin/bash
# Count how many effects are actually pushed into _modeData
rg --no-heading --line-number "_modeData\.push_back" | wc -l

Length of output: 64


🏁 Script executed:

#!/bin/bash
# List files containing setupEffectData
echo "Files containing 'setupEffectData':"
rg -l "setupEffectData" -g '*.cpp'

# Display the contents of WS2812FX::setupEffectData
echo -e "\nFunction WS2812FX::setupEffectData:"
rg -n "void WS2812FX::setupEffectData" -A200 -B5 -g '*.cpp'

# Show all _modeData.push_back calls with context
echo -e "\nAll _modeData.push_back occurrences:"
rg -n "_modeData\.push_back" -C3

Length of output: 18665


🏁 Script executed:

#!/bin/bash
echo "FX_MODE_IMAGE context in FX.h:"
rg -n "FX_MODE_IMAGE" -g 'wled00/FX.h' -A3 -B3

echo -e "\nMODE_COUNT define in FX.h:"
rg -n "#define MODE_COUNT" -g 'wled00/FX.h' -A3

echo -e "\nclass WS2812FX and _modeCount in FX.h:"
rg -n "class WS2812FX" -g 'wled00/FX.h' -A20 -B3
rg -n "_modeCount" -g 'wled00/FX.h' -A5

Length of output: 4266


No action needed: missing GIF effect is safely stubbed

The setupEffectData() routine always pre-fills all _mode/_modeData slots (0…MODE_COUNT–1) with a static stub, then selectively overwrites registered effects. Even when WLED_ENABLE_GIF is disabled, index 53 exists (holding the reserved stub), so there’s no out-of-bounds or empty vector entry.

wled00/FX.cpp (1)

9089-9094: Verify build when WLED_ENABLE_GIF is undefined

FX_MODE_IMAGE is conditionally registered under #ifdef WLED_ENABLE_GIF, but the enum value (defined in FX.h) and the _data_FX_MODE_IMAGE string are always compiled. That is fine, but ensure:

  1. No other unconditional references to mode_image or FX_MODE_IMAGE exist.
  2. FX_MODE_IMAGE’s numeric value does not collide with another mode when GIF is disabled (the index gap must still be respected by the UI).

If either condition is violated, wrap the enum or adjust numbering accordingly.

wled00/image_loader.cpp (1)

54-63: ⚠️ Potential issue

Out-of-bounds risk in up-scaling loops
outX + i / outY + j can exceed segment dimensions when gifWidth < activeSeg->width(). Guard the write to prevent overruns:

-      activeSeg->setPixelColorXY(outX + i, outY + j, …);
+      uint16_t xx = outX + i;
+      uint16_t yy = outY + j;
+      if (xx < activeSeg->width() && yy < activeSeg->height())
+        activeSeg->setPixelColorXY(xx, yy, …);

Likely an incorrect or invalid review comment.

Comment on lines 4653 to 4663
/*
Image effect
Draws a .gif image from filesystem on the matrix/strip
*/
uint16_t mode_image(void) {
#ifndef WLED_ENABLE_GIF
return mode_static();
#else
renderImageToSegment(SEGMENT);
return FRAMETIME;
#endif
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Capture and react to renderImageToSegment() return value

The error code returned by renderImageToSegment(SEGMENT) is silently discarded.
This makes troubleshooting impossible and leaves segments “stuck” on the IMAGE effect if the decoder fails (e.g., unsupported file, missing file, etc.).

Consider at minimum logging non-OK codes and falling back to mode_static() (or another safe effect) when an error persists.

 #else
-  renderImageToSegment(SEGMENT);
-  return FRAMETIME;
+  byte status = renderImageToSegment(SEGMENT);
+  if (status && status < 254) {          // 254/255 are WAITING/NO-OP codes
+    DEBUG_PRINTLN(F("GIF error: "));     // keep short to avoid serial flood
+    DEBUG_PRINTF("%u\n", status);
+    return mode_static();                // graceful fallback
+  }
+  return FRAMETIME;
 #endif
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/*
Image effect
Draws a .gif image from filesystem on the matrix/strip
*/
uint16_t mode_image(void) {
#ifndef WLED_ENABLE_GIF
return mode_static();
#else
renderImageToSegment(SEGMENT);
return FRAMETIME;
#endif
/*
Image effect
Draws a .gif image from filesystem on the matrix/strip
*/
uint16_t mode_image(void) {
#ifndef WLED_ENABLE_GIF
return mode_static();
#else
byte status = renderImageToSegment(SEGMENT);
if (status && status < 254) { // 254/255 are WAITING/NO-OP codes
DEBUG_PRINTLN(F("GIF error: ")); // keep short to avoid serial flood
DEBUG_PRINTF("%u\n", status);
return mode_static(); // graceful fallback
}
return FRAMETIME;
#endif
}
🤖 Prompt for AI Agents
In wled00/FX.cpp around lines 4653 to 4663, the return value of
renderImageToSegment(SEGMENT) is ignored, which prevents error detection and
recovery if the image rendering fails. Modify the code to capture the return
value, check if it indicates an error, log the error appropriately, and return
mode_static() or another safe fallback effect to avoid the segment getting stuck
on the image effect.

Comment on lines 85 to 104
if (strncmp(lastFilename +1, seg.name, 32) != 0) { // segment name changed, load new image
strncpy(lastFilename +1, seg.name, 32);
gifDecodeFailed = false;
if (strcmp(lastFilename + strlen(lastFilename) - 4, ".gif") != 0) {
gifDecodeFailed = true;
return IMAGE_ERROR_UNSUPPORTED_FORMAT;
}
if (file) file.close();
openGif(lastFilename);
if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; }
decoder.setScreenClearCallback(screenClearCallback);
decoder.setUpdateScreenCallback(updateScreenCallback);
decoder.setDrawPixelCallback(drawPixelCallback);
decoder.setFileSeekCallback(fileSeekCallback);
decoder.setFilePositionCallback(filePositionCallback);
decoder.setFileReadCallback(fileReadCallback);
decoder.setFileReadBlockCallback(fileReadBlockCallback);
decoder.setFileSizeCallback(fileSizeCallback);
decoder.alloc();
DEBUG_PRINTLN(F("Starting decoding"));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Memory leak on successive image changes
When the segment name changes a fresh decoder.alloc() is called without a preceding decoder.dealloc(), leaking heap every change. Invoke dealloc() (and close the prior file) before reallocating:

-    if (file) file.close();
+    if (file) file.close();
+    decoder.dealloc();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (strncmp(lastFilename +1, seg.name, 32) != 0) { // segment name changed, load new image
strncpy(lastFilename +1, seg.name, 32);
gifDecodeFailed = false;
if (strcmp(lastFilename + strlen(lastFilename) - 4, ".gif") != 0) {
gifDecodeFailed = true;
return IMAGE_ERROR_UNSUPPORTED_FORMAT;
}
if (file) file.close();
openGif(lastFilename);
if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; }
decoder.setScreenClearCallback(screenClearCallback);
decoder.setUpdateScreenCallback(updateScreenCallback);
decoder.setDrawPixelCallback(drawPixelCallback);
decoder.setFileSeekCallback(fileSeekCallback);
decoder.setFilePositionCallback(filePositionCallback);
decoder.setFileReadCallback(fileReadCallback);
decoder.setFileReadBlockCallback(fileReadBlockCallback);
decoder.setFileSizeCallback(fileSizeCallback);
decoder.alloc();
DEBUG_PRINTLN(F("Starting decoding"));
if (strncmp(lastFilename +1, seg.name, 32) != 0) { // segment name changed, load new image
strncpy(lastFilename +1, seg.name, 32);
gifDecodeFailed = false;
if (strcmp(lastFilename + strlen(lastFilename) - 4, ".gif") != 0) {
gifDecodeFailed = true;
return IMAGE_ERROR_UNSUPPORTED_FORMAT;
}
if (file) file.close();
decoder.dealloc();
openGif(lastFilename);
if (!file) { gifDecodeFailed = true; return IMAGE_ERROR_FILE_MISSING; }
decoder.setScreenClearCallback(screenClearCallback);
decoder.setUpdateScreenCallback(updateScreenCallback);
decoder.setDrawPixelCallback(drawPixelCallback);
decoder.setFileSeekCallback(fileSeekCallback);
decoder.setFilePositionCallback(filePositionCallback);
decoder.setFileReadCallback(fileReadCallback);
decoder.setFileReadBlockCallback(fileReadBlockCallback);
decoder.setFileSizeCallback(fileSizeCallback);
decoder.alloc();
DEBUG_PRINTLN(F("Starting decoding"));
🤖 Prompt for AI Agents
In wled00/image_loader.cpp around lines 85 to 104, the code calls
decoder.alloc() when the segment name changes without first calling
decoder.dealloc(), causing a memory leak on successive image changes. To fix
this, before calling decoder.alloc(), ensure you call decoder.dealloc() to free
previously allocated memory and also close the prior file if it is open. This
will prevent heap leaks by properly releasing resources before reallocating.

Comment on lines 115 to 128
uint32_t wait = currentFrameDelay * 2 - seg.speed * currentFrameDelay / 128;

// TODO consider handling this on FX level with a different frametime, but that would cause slow gifs to speed up during transitions
if (millis() - lastFrameDisplayTime < wait) return IMAGE_ERROR_WAITING;

decoder.getSize(&gifWidth, &gifHeight);

int result = decoder.decodeFrame(false);
if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; }

currentFrameDelay = decoder.getFrameDelay_ms();
unsigned long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate
currentFrameDelay = tooSlowBy > currentFrameDelay ? 0 : currentFrameDelay - tooSlowBy;
lastFrameDisplayTime = millis();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Unsigned arithmetic can underflow
tooSlowBy is computed with unsigned long; if the previous frame was faster than wait, the subtraction underflows, producing a huge value and zeroing currentFrameDelay unintentionally. Use signed math:

-  unsigned long tooSlowBy = (millis() - lastFrameDisplayTime) - wait;
+  long tooSlowBy = long(millis() - lastFrameDisplayTime) - long(wait);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uint32_t wait = currentFrameDelay * 2 - seg.speed * currentFrameDelay / 128;
// TODO consider handling this on FX level with a different frametime, but that would cause slow gifs to speed up during transitions
if (millis() - lastFrameDisplayTime < wait) return IMAGE_ERROR_WAITING;
decoder.getSize(&gifWidth, &gifHeight);
int result = decoder.decodeFrame(false);
if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; }
currentFrameDelay = decoder.getFrameDelay_ms();
unsigned long tooSlowBy = (millis() - lastFrameDisplayTime) - wait; // if last frame was longer than intended, compensate
currentFrameDelay = tooSlowBy > currentFrameDelay ? 0 : currentFrameDelay - tooSlowBy;
lastFrameDisplayTime = millis();
uint32_t wait = currentFrameDelay * 2 - seg.speed * currentFrameDelay / 128;
// TODO consider handling this on FX level with a different frametime, but that would cause slow gifs to speed up during transitions
if (millis() - lastFrameDisplayTime < wait) return IMAGE_ERROR_WAITING;
decoder.getSize(&gifWidth, &gifHeight);
int result = decoder.decodeFrame(false);
if (result < 0) { gifDecodeFailed = true; return IMAGE_ERROR_FRAME_DECODE; }
currentFrameDelay = decoder.getFrameDelay_ms();
long tooSlowBy = long(millis() - lastFrameDisplayTime) - long(wait); // if last frame was longer than intended, compensate
currentFrameDelay = tooSlowBy > currentFrameDelay ? 0 : currentFrameDelay - tooSlowBy;
lastFrameDisplayTime = millis();
🤖 Prompt for AI Agents
In wled00/image_loader.cpp around lines 115 to 128, the variable tooSlowBy is
computed using unsigned arithmetic which can underflow if the previous frame was
faster than wait, causing incorrect large values and zeroing currentFrameDelay
unintentionally. Change tooSlowBy to a signed integer type and perform the
subtraction using signed arithmetic to correctly handle cases where the elapsed
time is less than wait.

Comment on lines 12 to 17
File file;
char lastFilename[34] = "/";
GifDecoder<320,320,12,true> decoder;
bool gifDecodeFailed = false;
unsigned long lastFrameDisplayTime = 0, currentFrameDelay = 0;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

lastFilename is vulnerable to missing NUL-termination
strncpy(lastFilename + 1, …, 32) does not guarantee a trailing \0 when the source length ≥32, producing UB in the later strlen/strcmp checks and in openGif.

-    strncpy(lastFilename +1, seg.name, 32);
+    strncpy(lastFilename + 1, seg.name, 32);
+    lastFilename[33] = '\0';            // hard-stop
+    lastFilename[strlen(lastFilename)] = '\0';   // ensure NUL

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In wled00/image_loader.cpp around lines 12 to 17, the lastFilename array is
initialized with a fixed size of 34 and starts with "/". When using strncpy to
copy into lastFilename + 1 with a length of 32, it may not null-terminate if the
source string length is 32 or more, causing undefined behavior in subsequent
strlen, strcmp, or openGif calls. To fix this, ensure that after strncpy, you
explicitly set lastFilename[33] = '\0' to guarantee null-termination, or use a
safer string copy method that always null-terminates within the buffer size.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 248e1a4 and 1fe531f.

📒 Files selected for processing (2)
  • wled00/data/index.js (1 hunks)
  • wled00/wled_server.cpp (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • wled00/wled_server.cpp
⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: Builds (esp32s2_PSRAM_M)
  • GitHub Check: Builds (esp32c3mini_dio_4MB_M)
  • GitHub Check: Builds (esp32c3dev_2MB_M)
  • GitHub Check: Builds (esp32S3_WROOM-2_M)
  • GitHub Check: Builds (esp32S3_16MB_PSRAM_M_HUB75)
  • GitHub Check: Builds (esp32S3_4MB_S)
  • GitHub Check: Builds (esp32S3_8MB_M)
  • GitHub Check: Builds (esp32_4MB_PSRAM_S)
  • GitHub Check: Builds (esp32S3_8MB_PSRAM_M)
  • GitHub Check: Builds (esp32S3_8MB_S)
  • GitHub Check: Builds (esp32S3_4MB_PSRAM_S)
  • GitHub Check: Builds (wemos_shield_esp32_4MB_M)
  • GitHub Check: Builds (esp32_4MB_V4_S)
  • GitHub Check: Builds (esp32_16MB_M_eth)
  • GitHub Check: Builds (esp32_pico_4MB_M)
  • GitHub Check: Builds (esp32_4MB_M_debug)

Comment on lines +208 to +210
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Undefined locproto triggers a runtime exception

locproto is not declared anywhere in this file (and a quick scan shows no earlier definition).
When loc === true, calling getURL() will throw ReferenceError: locproto is not defined, breaking every navigation that relies on this helper.

- function getURL(path) {
-     return (loc ? locproto + "//" + locip : "") + path;
+// Builds a URL that honours the page’s current protocol.
+function getURL(path) {
+    const proto = (typeof location !== 'undefined') ? location.protocol : 'http:';
+    return (loc ? proto + "//" + locip : "") + path;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
}
// Builds a URL that honours the page’s current protocol.
function getURL(path) {
const proto = (typeof location !== 'undefined') ? location.protocol : 'http:';
return (loc ? proto + "//" + locip : "") + path;
}
🤖 Prompt for AI Agents
In wled00/data/index.js around lines 208 to 210, the variable locproto used in
getURL is not defined, causing a ReferenceError when loc is true. To fix this,
declare and initialize locproto appropriately before using it in the return
statement, ensuring it holds the correct protocol string (e.g., "http:" or
"https:") based on the context or environment.

netmindz and others added 2 commits June 29, 2025 20:18
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants