Files
crosspoint-reader-mod/src/activities/util/BmpViewerActivity.cpp
Lev Roland-Kalb 7717ae2683 feat: Added BmpViewer activity for viewing .bmp images in file browser (#887)
## Summary

* **What is the goal of this PR?** (e.g., Implements the new feature for
file uploading.)

Implements new feature for viewing .bmp files directly from the "Browse
Files" menu.

* **What changes are included?**

You can now view .bmp files when browsing. You can click the select
button to open the file, and then click back to close it and continue
browsing in the same location. Once open a file will display on the
screen with no additional options to interact outside of exiting with
the back button.

The attached video shows this feature in action:


https://github.com/user-attachments/assets/9659b6da-abf7-4458-b158-e11c248c8bef

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

The changes implemented in #884 are also present here as this feature is
actually what led to me noticing this issue. I figured I would add that
PR as a separate request in case that one could be more easily merged
given this feature is significantly more complicated and will likely be
subject to more intense review.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? **YES**

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-21 12:27:25 +03:00

101 lines
3.6 KiB
C++

#include "BmpViewerActivity.h"
#include <Bitmap.h>
#include <GfxRenderer.h>
#include <HalStorage.h>
#include <I18n.h>
#include "components/UITheme.h"
#include "fontIds.h"
BmpViewerActivity::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 BmpViewerActivity::onEnter() {
Activity::onEnter();
// Removed the redundant initial renderer.clearScreen()
FsFile file;
const auto pageWidth = renderer.getScreenWidth();
const auto pageHeight = renderer.getScreenHeight();
Rect popupRect = GUI.drawPopup(renderer, tr(STR_LOADING_POPUP));
GUI.fillPopupProgress(renderer, popupRect, 20); // Initial 20% progress
// 1. Open the file
if (Storage.openFileForRead("BMP", filePath, file)) {
Bitmap bitmap(file, true);
// 2. Parse headers to get dimensions
if (bitmap.parseHeaders() == BmpReaderError::Ok) {
int x, y;
if (bitmap.getWidth() > pageWidth || bitmap.getHeight() > pageHeight) {
float ratio = static_cast<float>(bitmap.getWidth()) / static_cast<float>(bitmap.getHeight());
const float screenRatio = static_cast<float>(pageWidth) / static_cast<float>(pageHeight);
if (ratio > screenRatio) {
// Wider than screen
x = 0;
y = std::round((static_cast<float>(pageHeight) - static_cast<float>(pageWidth) / ratio) / 2);
} else {
// Taller than screen
x = std::round((static_cast<float>(pageWidth) - static_cast<float>(pageHeight) * ratio) / 2);
y = 0;
}
} else {
// Center small images
x = (pageWidth - bitmap.getWidth()) / 2;
y = (pageHeight - bitmap.getHeight()) / 2;
}
// 4. Prepare Rendering
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
GUI.fillPopupProgress(renderer, popupRect, 50);
renderer.clearScreen();
// Assuming drawBitmap defaults to 0,0 crop if omitted, or pass explicitly: drawBitmap(bitmap, x, y, pageWidth,
// pageHeight, 0, 0)
renderer.drawBitmap(bitmap, x, y, pageWidth, pageHeight, 0, 0);
// Draw UI hints on the base layer
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
// Single pass for non-grayscale images
renderer.displayBuffer(HalDisplay::FULL_REFRESH);
} else {
// Handle file parsing error
renderer.clearScreen();
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Invalid BMP File");
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
}
file.close();
} else {
// Handle file open error
renderer.clearScreen();
renderer.drawCenteredText(UI_10_FONT_ID, pageHeight / 2, "Could not open file");
const auto labels = mappedInput.mapLabels(tr(STR_BACK), "", "", "");
GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
renderer.displayBuffer(HalDisplay::FULL_REFRESH);
}
}
void BmpViewerActivity::onExit() {
Activity::onExit();
renderer.clearScreen();
renderer.displayBuffer(HalDisplay::FAST_REFRESH);
}
void BmpViewerActivity::loop() {
// Keep CPU awake/polling so 1st click works
Activity::loop();
if (mappedInput.wasReleased(MappedInputManager::Button::Back)) {
if (onGoBack) onGoBack();
return;
}
}