feat: add BmpViewer activity for viewing .bmp images in file browser (port upstream PR #887)

New BmpViewerActivity opens, parses, and renders BMP files with centered
aspect-ratio-preserving display and localized back navigation. Library
file filter extended to include .bmp. ReaderActivity routes BMP paths to
the new viewer. LyraTheme button hint backgrounds switched to rounded
rect fills to prevent overflow artifacts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
cottongin
2026-02-21 18:37:43 -05:00
parent 9d9bc019a2
commit 2eae521b6a
6 changed files with 114 additions and 3 deletions

View File

@@ -0,0 +1,72 @@
#include "BmpViewerActivity.h"
#include <HalDisplay.h>
#include <HalStorage.h>
#include <I18n.h>
#include "Bitmap.h"
#include "components/UITheme.h"
#include "fontIds.h"
void BmpViewerActivity::onEnter() {
Activity::onEnter();
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
// Show loading indicator while BMP is parsed
renderer.clearScreen();
renderer.drawCenteredText(UI_10_FONT_ID, (pageHeight - renderer.getLineHeight(UI_10_FONT_ID)) / 2,
tr(STR_LOADING), true, EpdFontFamily::BOLD);
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
FsFile file;
if (!Storage.openFileForRead("BMP", filePath, file)) {
LOG_ERR("BMP", "Failed to open file: %s", filePath.c_str());
loadFailed = true;
return;
}
Bitmap bitmap(file, true);
if (bitmap.parseHeaders() != BmpReaderError::Ok) {
LOG_ERR("BMP", "Failed to parse BMP headers: %s", filePath.c_str());
file.close();
loadFailed = true;
return;
}
LOG_DBG("BMP", "Loaded %s (%d x %d)", filePath.c_str(), bitmap.getWidth(), bitmap.getHeight());
// Compute centered position; drawBitmap handles aspect-ratio-preserving scaling
const float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
int x, y;
if (ratio > screenRatio) {
x = 0;
y = std::round((static_cast<float>(pageHeight) - static_cast<float>(pageWidth) / ratio) / 2);
} else {
x = std::round((static_cast<float>(pageWidth) - static_cast<float>(pageHeight) * ratio) / 2);
y = 0;
}
renderer.clearScreen();
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight);
file.close();
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(HalDisplay::HALF_REFRESH);
}
void BmpViewerActivity::loop() {
if (loadFailed) {
loadFailed = false;
onGoBack();
return;
}
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
onGoBack();
}
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <functional>
#include <string>
#include <utility>
#include "../Activity.h"
class BmpViewerActivity final : public Activity {
std::string filePath;
const std::function<void()> onGoBack;
bool loadFailed = false;
public:
explicit BmpViewerActivity(GfxRenderer& renderer, MappedInputManager& mappedInput, std::string path,
std::function<void()> onGoBack)
: Activity("BmpViewer", renderer, mappedInput),
filePath(std::move(path)),
onGoBack(std::move(onGoBack)) {}
void onEnter() override;
void loop() override;
};