mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2026-09-15 02:52:58 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 43077c79cf | |||
| 06651dc97c | |||
| 67975d76a0 | |||
| 282e4da1cb | |||
| e7359563c6 | |||
| 03ca87b13e |
@@ -51,6 +51,7 @@
|
||||
.dep/
|
||||
/build*
|
||||
CMakeFiles/
|
||||
cmake-build-debug/
|
||||
|
||||
# Debugging
|
||||
.gdbinit*
|
||||
|
||||
@@ -176,7 +176,9 @@ set(CPPSRC
|
||||
${COMMON}/ui_language.cpp
|
||||
${COMMON}/utility.cpp
|
||||
${COMMON}/wm8731.cpp
|
||||
${COMMON}/ads1110.cpp
|
||||
${COMMON}/performance_counter.cpp
|
||||
${COMMON}/bmpfile.cpp
|
||||
app_settings.cpp
|
||||
audio.cpp
|
||||
baseband_api.cpp
|
||||
@@ -260,6 +262,7 @@ set(CPPSRC
|
||||
ui/ui_textentry.cpp
|
||||
ui/ui_tone_key.cpp
|
||||
ui/ui_transmitter.cpp
|
||||
ui/ui_bmpview.cpp
|
||||
apps/acars_app.cpp
|
||||
apps/ais_app.cpp
|
||||
apps/analog_audio_app.cpp
|
||||
@@ -282,6 +285,7 @@ set(CPPSRC
|
||||
apps/ui_aprs_rx.cpp
|
||||
apps/ui_aprs_tx.cpp
|
||||
apps/ui_bht_tx.cpp
|
||||
apps/ui_bmp_file_viewer.cpp
|
||||
apps/ui_btle_rx.cpp
|
||||
# apps/ui_coasterp.cpp
|
||||
apps/ui_debug.cpp
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ui_bmp_file_viewer.hpp"
|
||||
|
||||
extern ui::SystemView* system_view_ptr;
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace ui {
|
||||
|
||||
BMPFileViewer::BMPFileViewer(
|
||||
NavigationView& nav,
|
||||
const std::filesystem::path& path)
|
||||
: nav_{nav},
|
||||
path_{path} {
|
||||
add_children(
|
||||
{&bmp});
|
||||
bmp.set_enter_pass(true); // pass the enter key to me, so i can exit. this will disable zoom + pos reset
|
||||
set_focusable(true);
|
||||
system_view_ptr->set_app_fullscreen(true);
|
||||
}
|
||||
|
||||
BMPFileViewer::~BMPFileViewer() {
|
||||
system_view_ptr->set_app_fullscreen(false);
|
||||
}
|
||||
|
||||
void BMPFileViewer::focus() {
|
||||
bmp.focus();
|
||||
}
|
||||
|
||||
bool BMPFileViewer::on_key(KeyEvent k) {
|
||||
if (k == KeyEvent::Select) {
|
||||
nav_.pop();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void BMPFileViewer::paint(Painter&) {
|
||||
bmp.load_bmp(path_);
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UI_BMP_FILE_VIEWER_H__
|
||||
#define __UI_BMP_FILE_VIEWER_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_navigation.hpp"
|
||||
#include "ui_painter.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "file.hpp"
|
||||
#include "ui_bmpview.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
class BMPFileViewer : public View {
|
||||
public:
|
||||
BMPFileViewer(NavigationView& nav, const std::filesystem::path& path);
|
||||
~BMPFileViewer();
|
||||
bool on_key(KeyEvent key) override;
|
||||
void paint(Painter& painter) override;
|
||||
void focus() override;
|
||||
|
||||
private:
|
||||
NavigationView& nav_;
|
||||
std::filesystem::path path_{};
|
||||
BMPViewer bmp{{0, 0, 240, 320}};
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
#endif // __UI_BMP_FILE_VIEWER_H__
|
||||
@@ -41,6 +41,11 @@ DfuMenu::DfuMenu(NavigationView& nav)
|
||||
&text_info_line_8,
|
||||
&text_info_line_9,
|
||||
&text_info_line_10});
|
||||
|
||||
if (portapack::battery_ads1110.isDetected()) {
|
||||
add_child(&voltage_label);
|
||||
add_child(&text_info_line_11);
|
||||
}
|
||||
}
|
||||
|
||||
void DfuMenu::paint(Painter& painter) {
|
||||
@@ -48,6 +53,8 @@ void DfuMenu::paint(Painter& painter) {
|
||||
size_t m0_fragmented_free_space = 0;
|
||||
const auto m0_fragments = chHeapStatus(NULL, &m0_fragmented_free_space);
|
||||
|
||||
auto lines = (portapack::battery_ads1110.isDetected() ? 11 : 10) + 2;
|
||||
|
||||
text_info_line_1.set(to_string_dec_uint(chCoreStatus(), 6));
|
||||
text_info_line_2.set(to_string_dec_uint(m0_fragmented_free_space, 6));
|
||||
text_info_line_3.set(to_string_dec_uint(m0_fragments, 6));
|
||||
@@ -58,9 +65,11 @@ void DfuMenu::paint(Painter& painter) {
|
||||
text_info_line_8.set(to_string_dec_uint(shared_memory.m4_performance_counter, 6));
|
||||
text_info_line_9.set(to_string_dec_uint(shared_memory.m4_buffer_missed, 6));
|
||||
text_info_line_10.set(to_string_dec_uint(chTimeNow() / 1000, 6));
|
||||
if (portapack::battery_ads1110.isDetected()) {
|
||||
text_info_line_11.set(to_string_decimal_padding(portapack::battery_ads1110.readVoltage(), 3, 6));
|
||||
}
|
||||
|
||||
constexpr auto margin = 5;
|
||||
constexpr auto lines = 10 + 2;
|
||||
|
||||
painter.fill_rectangle(
|
||||
{{6 * CHARACTER_WIDTH - margin, 3 * LINE_HEIGHT - margin},
|
||||
|
||||
@@ -58,7 +58,9 @@ class DfuMenu : public View {
|
||||
{{6 * CHARACTER_WIDTH, 11 * LINE_HEIGHT}, "M4 stack:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 12 * LINE_HEIGHT}, "M4 cpu %:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 13 * LINE_HEIGHT}, "M4 miss:", Color::dark_cyan()},
|
||||
{{6 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "uptime:", Color::dark_cyan()}};
|
||||
{{6 * CHARACTER_WIDTH, 14 * LINE_HEIGHT}, "Uptime:", Color::dark_cyan()}};
|
||||
|
||||
Labels voltage_label{{{6 * CHARACTER_WIDTH, 15 * LINE_HEIGHT}, "Voltage:", Color::dark_cyan()}};
|
||||
|
||||
Text text_info_line_1{{15 * CHARACTER_WIDTH, 5 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_2{{15 * CHARACTER_WIDTH, 6 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
@@ -70,6 +72,7 @@ class DfuMenu : public View {
|
||||
Text text_info_line_8{{15 * CHARACTER_WIDTH, 12 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_9{{15 * CHARACTER_WIDTH, 13 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_10{{15 * CHARACTER_WIDTH, 14 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
Text text_info_line_11{{15 * CHARACTER_WIDTH, 15 * LINE_HEIGHT, 6 * CHARACTER_WIDTH, 1 * LINE_HEIGHT}, ""};
|
||||
};
|
||||
|
||||
class DfuMenu2 : public View {
|
||||
|
||||
@@ -29,11 +29,13 @@
|
||||
#include "ui_playlist.hpp"
|
||||
#include "ui_remote.hpp"
|
||||
#include "ui_ss_viewer.hpp"
|
||||
#include "ui_bmp_file_viewer.hpp"
|
||||
#include "ui_text_editor.hpp"
|
||||
#include "ui_iq_trim.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "portapack.hpp"
|
||||
#include "event_m0.hpp"
|
||||
#include "file_path.hpp"
|
||||
|
||||
using namespace portapack;
|
||||
namespace fs = std::filesystem;
|
||||
@@ -694,7 +696,12 @@ bool FileManagerView::handle_file_open() {
|
||||
nav_.push<ScreenshotViewer>(path);
|
||||
return true;
|
||||
} else if (path_iequal(bmp_ext, ext)) {
|
||||
nav_.push<SplashViewer>(path);
|
||||
if (path_iequal(current_path, u"/" + splash_dir)) {
|
||||
nav_.push<SplashViewer>(path); // splash, so load that viewer
|
||||
} else {
|
||||
nav_.push<BMPFileViewer>(path); // any other bmp
|
||||
}
|
||||
|
||||
reload_current(false);
|
||||
return true;
|
||||
} else if (path_iequal(rem_ext, ext)) {
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "portapack_persistent_memory.hpp"
|
||||
using namespace portapack;
|
||||
|
||||
extern ui::SystemView* system_view_ptr;
|
||||
|
||||
namespace ui {
|
||||
|
||||
TouchCalibrationView::TouchCalibrationView(
|
||||
@@ -40,6 +42,7 @@ TouchCalibrationView::TouchCalibrationView(
|
||||
&image_verify_1,
|
||||
&image_verify_2,
|
||||
&label_calibrate,
|
||||
&label_calibrate_2,
|
||||
&label_verify,
|
||||
&label_success,
|
||||
&label_failure,
|
||||
@@ -51,6 +54,12 @@ TouchCalibrationView::TouchCalibrationView(
|
||||
button_ok.on_select = [this](Button&) { this->on_ok(); };
|
||||
|
||||
set_phase(Phase::Calibrate0);
|
||||
|
||||
system_view_ptr->get_status_view()->set_back_hidden(true);
|
||||
}
|
||||
|
||||
TouchCalibrationView::~TouchCalibrationView() {
|
||||
system_view_ptr->get_status_view()->set_back_hidden(false);
|
||||
}
|
||||
|
||||
void TouchCalibrationView::focus() {
|
||||
@@ -70,6 +79,7 @@ void TouchCalibrationView::update_target() {
|
||||
image_verify_2.hidden(phase != Phase::Verify2);
|
||||
|
||||
label_calibrate.hidden(!phase_calibrate);
|
||||
label_calibrate_2.hidden(!phase_calibrate && !phase_verify);
|
||||
label_verify.hidden(!phase_verify);
|
||||
label_success.hidden(phase != Phase::Success);
|
||||
label_failure.hidden(phase != Phase::Failure);
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace ui {
|
||||
class TouchCalibrationView : public View {
|
||||
public:
|
||||
TouchCalibrationView(NavigationView& nav);
|
||||
~TouchCalibrationView();
|
||||
|
||||
void focus() override;
|
||||
|
||||
@@ -110,9 +111,13 @@ class TouchCalibrationView : public View {
|
||||
Color::black()};
|
||||
|
||||
Text label_calibrate{
|
||||
{16, 5 * 16, 26 * 8, 1 * 16},
|
||||
{2 * 8, 5 * 16, 26 * 8, 1 * 16},
|
||||
"Touch targets to calibrate"};
|
||||
|
||||
Text label_calibrate_2{
|
||||
{1 * 8, 6 * 16, 28 * 8, 1 * 16},
|
||||
"(hold position using stylus)"};
|
||||
|
||||
Text label_verify{
|
||||
{28, 5 * 16, 23 * 8, 1 * 16},
|
||||
"Touch targets to verify"};
|
||||
|
||||
@@ -70,6 +70,10 @@ File::~File() {
|
||||
f_close(&f);
|
||||
}
|
||||
|
||||
void File::close() {
|
||||
f_close(&f);
|
||||
}
|
||||
|
||||
File::Result<File::Size> File::read(void* data, Size bytes_to_read) {
|
||||
UINT bytes_read = 0;
|
||||
const auto result = f_read(&f, data, bytes_to_read, &bytes_read);
|
||||
@@ -98,6 +102,10 @@ File::Offset File::tell() const {
|
||||
return f_tell(&f);
|
||||
}
|
||||
|
||||
File::Result<bool> File::eof() {
|
||||
return f_eof(&f);
|
||||
}
|
||||
|
||||
File::Result<File::Offset> File::seek(Offset new_position) {
|
||||
/* NOTE: Returns *old* position, not new position */
|
||||
const auto old_position = tell();
|
||||
|
||||
@@ -332,6 +332,7 @@ class File {
|
||||
|
||||
// TODO: Return Result<>.
|
||||
Optional<Error> open(const std::filesystem::path& filename, bool read_only = true, bool create = false);
|
||||
void close();
|
||||
Optional<Error> append(const std::filesystem::path& filename);
|
||||
Optional<Error> create(const std::filesystem::path& filename);
|
||||
|
||||
@@ -342,6 +343,7 @@ class File {
|
||||
Result<Offset> seek(uint64_t Offset);
|
||||
Result<Offset> truncate();
|
||||
Size size() const;
|
||||
Result<bool> eof();
|
||||
|
||||
template <size_t N>
|
||||
Result<Size> write(const std::array<uint8_t, N>& data) {
|
||||
|
||||
@@ -144,6 +144,7 @@ Continuous (Fox-oring)
|
||||
|
||||
#include "rffc507x.hpp" /* c/m, avoiding initial short ON Ant_DC_Bias pulse, from cold reset */
|
||||
rffc507x::RFFC507x first_if;
|
||||
ui::SystemView* system_view_ptr;
|
||||
|
||||
static void event_loop() {
|
||||
static ui::Context context;
|
||||
@@ -151,6 +152,8 @@ static void event_loop() {
|
||||
context,
|
||||
portapack::display.screen_rect()};
|
||||
|
||||
system_view_ptr = &system_view;
|
||||
|
||||
EventDispatcher event_dispatcher{&system_view, context};
|
||||
static MessageHandlerRegistration message_handler_display_sleep{
|
||||
Message::ID::DisplaySleep,
|
||||
|
||||
@@ -52,6 +52,7 @@ using asahi_kasei::ak4951::AK4951;
|
||||
#include "sd_card.hpp"
|
||||
#include "string_format.hpp"
|
||||
#include "bitmap.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
|
||||
namespace portapack {
|
||||
|
||||
@@ -84,6 +85,7 @@ ClockManager clock_manager{
|
||||
|
||||
WM8731 audio_codec_wm8731{i2c0, 0x1a};
|
||||
AK4951 audio_codec_ak4951{i2c0, 0x12};
|
||||
ads1110::ADS1110 battery_ads1110{i2c0, 0x48};
|
||||
|
||||
ReceiverModel receiver_model;
|
||||
TransmitterModel transmitter_model;
|
||||
@@ -585,6 +587,7 @@ init_status_t init() {
|
||||
chThdSleepMilliseconds(10);
|
||||
|
||||
audio::init(portapack_audio_codec());
|
||||
battery_ads1110.init();
|
||||
|
||||
if (lcd_fast_setup)
|
||||
draw_splash_screen_icon(4, ui::bitmap_icon_speaker);
|
||||
|
||||
@@ -33,6 +33,8 @@
|
||||
#include "backlight.hpp"
|
||||
#include "usb_serial.hpp"
|
||||
|
||||
#include "ads1110.hpp"
|
||||
|
||||
#include "radio.hpp"
|
||||
#include "clock_manager.hpp"
|
||||
#include "temperature_logger.hpp"
|
||||
@@ -61,6 +63,7 @@ extern portapack::USBSerial usb_serial;
|
||||
extern si5351::Si5351 clock_generator;
|
||||
extern ClockManager clock_manager;
|
||||
|
||||
extern ads1110::ADS1110 battery_ads1110;
|
||||
extern ReceiverModel receiver_model;
|
||||
extern TransmitterModel transmitter_model;
|
||||
|
||||
|
||||
@@ -159,6 +159,7 @@ std::string to_string_decimal(float decimal, int8_t precision) {
|
||||
double fractional_part;
|
||||
|
||||
std::string result;
|
||||
if (precision > 9) precision = 9; // we will convert to uin32_t, and that is the max it can hold.
|
||||
|
||||
fractional_part = modf(decimal, &integer_part) * pow(10, precision);
|
||||
|
||||
@@ -171,6 +172,31 @@ std::string to_string_decimal(float decimal, int8_t precision) {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string to_string_decimal_padding(float decimal, int8_t precision, const int32_t l) {
|
||||
double integer_part;
|
||||
double fractional_part;
|
||||
|
||||
std::string result;
|
||||
if (precision > 9) precision = 9; // we will convert to uin32_t, and that is the max it can hold.
|
||||
|
||||
fractional_part = modf(decimal, &integer_part) * pow(10, precision);
|
||||
|
||||
if (fractional_part < 0) {
|
||||
fractional_part = -fractional_part;
|
||||
}
|
||||
|
||||
result = to_string_dec_int(integer_part) + "." + to_string_dec_uint(fractional_part, precision, '0');
|
||||
|
||||
// Add padding with spaces to meet the length requirement
|
||||
if (result.length() < (uint32_t)l) {
|
||||
int padding_length = l - result.length();
|
||||
std::string padding(padding_length, ' ');
|
||||
result = padding + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// right-justified frequency in Hz, always 10 characters
|
||||
std::string to_string_freq(const uint64_t f) {
|
||||
std::string final_str{""};
|
||||
|
||||
@@ -54,6 +54,7 @@ std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
|
||||
std::string to_string_dec_uint(const uint32_t n, const int32_t l, const char fill = ' ');
|
||||
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill = 0);
|
||||
std::string to_string_decimal(float decimal, int8_t precision);
|
||||
std::string to_string_decimal_padding(float decimal, int8_t precision, const int32_t l);
|
||||
|
||||
std::string to_string_hex(uint64_t n, int32_t length);
|
||||
std::string to_string_hex_array(uint8_t* array, int32_t length);
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
#include "ui_bmpview.hpp"
|
||||
#include "usb_serial_asyncmsg.hpp"
|
||||
#include "portapack.hpp"
|
||||
|
||||
bool BMPViewer::load_bmp(const std::filesystem::path& file) {
|
||||
if (!bmp.open(file, true)) return false;
|
||||
// calc default zoom level to fit screen, and min / max zoom too
|
||||
auto rect = screen_rect();
|
||||
auto d_height = rect.height();
|
||||
auto d_width = rect.width();
|
||||
auto b_width = bmp.get_width();
|
||||
auto b_height = bmp.get_real_height();
|
||||
// aspects
|
||||
// if image is smaller then our vp
|
||||
auto x_w = d_width / b_width;
|
||||
auto x_h = d_height / b_height;
|
||||
if (x_w < 1 && x_h < 1) {
|
||||
// not zoom in, but zoom out
|
||||
x_w = b_width / d_width;
|
||||
x_h = b_height / d_height;
|
||||
x_w = (127 < x_w) ? 127 : x_w;
|
||||
x_h = (127 < x_h) ? 127 : x_h;
|
||||
zoom_fit = (x_h > x_w) ? -1 * x_h : -1 * x_w;
|
||||
} else {
|
||||
x_w = (127 < x_w) ? 127 : x_w;
|
||||
x_h = (127 < x_h) ? 127 : x_h;
|
||||
zoom_fit = (x_h > x_w) ? x_h : x_w;
|
||||
}
|
||||
if (zoom_fit > max_zoom) zoom_fit = max_zoom;
|
||||
min_zoom = zoom_fit - 3;
|
||||
|
||||
reset_pos();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BMPViewer::move_pos(int32_t delta_x, int32_t delta_y) {
|
||||
if (!bmp.is_loaded()) return false;
|
||||
|
||||
auto rect = screen_rect();
|
||||
auto d_height = rect.height();
|
||||
auto d_width = rect.width();
|
||||
auto ocx = cx; // save old pos
|
||||
auto ocy = cy;
|
||||
// top left protection
|
||||
if (delta_x < 0 && cx <= (uint32_t)(-1 * delta_x))
|
||||
cx = 0;
|
||||
else
|
||||
cx += delta_x;
|
||||
|
||||
if (delta_y < 0 && cy <= (uint32_t)(-1 * delta_y))
|
||||
cy = 0;
|
||||
else
|
||||
cy += delta_y;
|
||||
// right bottom protection
|
||||
float zt = zoom < 0 ? -1.0f / (float)zoom : (float)zoom;
|
||||
if (zt == 0) zt = 1;
|
||||
if (cy + (uint32_t)(d_height / zt) > bmp.get_real_height()) {
|
||||
cy = (bmp.get_real_height() < (uint32_t)(d_height / zt)) ? 0 : bmp.get_real_height() - (uint32_t)(d_height / zt);
|
||||
}
|
||||
if (cx + (uint32_t)(d_width / zt) > bmp.get_width()) {
|
||||
cx = (bmp.get_width() < (uint32_t)(d_width / zt)) ? 0 : bmp.get_width() - (uint32_t)(d_width / zt);
|
||||
}
|
||||
bool ret = !(cx == ocx && ocy == cy); // was any change?
|
||||
if (ret) set_dirty();
|
||||
return ret;
|
||||
}
|
||||
|
||||
void BMPViewer::set_zoom(int8_t new_zoom) {
|
||||
if (!bmp.is_loaded()) return;
|
||||
if (new_zoom > max_zoom) new_zoom = max_zoom;
|
||||
if (new_zoom < min_zoom) new_zoom = min_zoom;
|
||||
if (new_zoom == 0) new_zoom = 1;
|
||||
if (new_zoom == -1) new_zoom = 1;
|
||||
zoom = new_zoom;
|
||||
auto rect = screen_rect();
|
||||
auto d_height = rect.height();
|
||||
auto d_width = rect.width();
|
||||
if (zoom < 0) {
|
||||
mvx = d_width / 3 * (-1.0 * zoom);
|
||||
mvy = d_height / 3 * (-1.0 * zoom);
|
||||
} else {
|
||||
mvx = d_width / zoom / 3;
|
||||
mvy = d_height / zoom / 3;
|
||||
}
|
||||
move_pos(0, 0); // fix based on zoom, without real move (if not edge case)
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
// reads a lint from the bmp's bx, by coordinate to the line that's size is cnt. according to zoom
|
||||
void BMPViewer::get_line(ui::Color* line, uint32_t bx, uint32_t by, uint32_t cnt) {
|
||||
if (!bmp.is_loaded()) return;
|
||||
uint32_t last_targetx = 65534;
|
||||
for (uint32_t x = 0; x < cnt; x++) {
|
||||
uint32_t targetx = (zoom < 0) ? bx + x * -1 * zoom : bx + x / zoom; // on zoom out could probably avg the pixels, or apply some smoothing, but this is way faster.
|
||||
if (last_targetx == targetx) {
|
||||
line[x] = line[x - 1];
|
||||
continue;
|
||||
}
|
||||
last_targetx = targetx;
|
||||
if (!bmp.seek(targetx, by)) {
|
||||
line[x] = Color::white(); // can't seek there
|
||||
} else {
|
||||
bmp.read_next_px(line[x], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BMPViewer::paint(Painter& painter) {
|
||||
if (!bmp.is_loaded()) {
|
||||
painter.draw_string({48, 24}, ui::Styles::white, "Can't load BMP");
|
||||
return;
|
||||
}
|
||||
// get where i can paint
|
||||
auto rect = screen_rect();
|
||||
auto d_height = rect.height();
|
||||
auto d_width = rect.width();
|
||||
|
||||
uint32_t by = cy; // we start to read from there
|
||||
uint32_t last_by = 65534;
|
||||
ui::Color* line = new ui::Color[d_width];
|
||||
for (int32_t y = 0; y < d_height; y++) {
|
||||
by = cy + ((zoom < 0) ? y * -1 * zoom : y / (int32_t)zoom);
|
||||
if (by != last_by) get_line(line, cx, by, d_width);
|
||||
last_by = by;
|
||||
portapack::display.draw_pixels({rect.left(), rect.top() + y, d_width, 1}, line, d_width);
|
||||
}
|
||||
delete line;
|
||||
}
|
||||
|
||||
int8_t BMPViewer::get_zoom() {
|
||||
return zoom;
|
||||
}
|
||||
|
||||
BMPViewer::BMPViewer(Rect parent_rect)
|
||||
: Widget{parent_rect} {
|
||||
set_focusable(true);
|
||||
}
|
||||
|
||||
BMPViewer::BMPViewer(Rect parent_rect, const std::filesystem::path& file)
|
||||
: Widget{parent_rect} {
|
||||
set_focusable(true);
|
||||
load_bmp(file);
|
||||
}
|
||||
|
||||
void BMPViewer::on_focus() {
|
||||
set_highlighted(true);
|
||||
}
|
||||
|
||||
void BMPViewer::on_blur() {
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
void BMPViewer::reset_pos() {
|
||||
if (!bmp.is_loaded()) return;
|
||||
cx = 0;
|
||||
cy = 0;
|
||||
set_zoom(zoom_fit);
|
||||
set_dirty();
|
||||
}
|
||||
|
||||
bool BMPViewer::on_key(const KeyEvent key) {
|
||||
if (!bmp.is_loaded()) return false;
|
||||
if (key == KeyEvent::Up) {
|
||||
return move_pos(0, -1 * mvy);
|
||||
}
|
||||
if (key == KeyEvent::Down) {
|
||||
return move_pos(0, mvy);
|
||||
}
|
||||
if (key == KeyEvent::Left) {
|
||||
return move_pos(-1 * mvx, 0);
|
||||
}
|
||||
if (key == KeyEvent::Right) {
|
||||
return move_pos(mvx, 0);
|
||||
}
|
||||
if (key == KeyEvent::Select) {
|
||||
if (!enter_pass) {
|
||||
reset_pos();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BMPViewer::on_encoder(EncoderEvent delta) {
|
||||
if (!bmp.is_loaded()) return false;
|
||||
if (delta > 0) {
|
||||
set_zoom(zoom + delta); // 0 handled in set_zoom
|
||||
return true;
|
||||
}
|
||||
if (delta < 0) {
|
||||
if (zoom == 1) // not 0, but -1
|
||||
set_zoom(-2);
|
||||
else
|
||||
set_zoom(zoom + delta); // decrease
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// sets if the enter key should be passed to parent or handled. true = pass it, false = handle as reset pos+zoom
|
||||
void BMPViewer::set_enter_pass(bool pass) {
|
||||
enter_pass = pass;
|
||||
}
|
||||
|
||||
bool BMPViewer::get_enter_pass() {
|
||||
return enter_pass;
|
||||
}
|
||||
|
||||
bool BMPViewer::on_keyboard(const KeyboardEvent event) {
|
||||
if (!bmp.is_loaded()) return false;
|
||||
if (event == '+') {
|
||||
set_zoom(zoom + 1);
|
||||
return true;
|
||||
}
|
||||
if (event == '-') {
|
||||
if (zoom == 1) // not 0, but -1
|
||||
set_zoom(-2);
|
||||
else
|
||||
set_zoom(zoom - 1); // decrease
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __UIBMPVIEW_H__
|
||||
#define __UIBMPVIEW_H__
|
||||
|
||||
#include "ui.hpp"
|
||||
#include "ui_widget.hpp"
|
||||
#include "bmpfile.hpp"
|
||||
#include "ui_styles.hpp"
|
||||
|
||||
class BMPViewer : public Widget {
|
||||
public:
|
||||
BMPViewer(Rect parent_rect);
|
||||
BMPViewer(Rect parent_rect, const std::filesystem::path& file);
|
||||
BMPViewer(const BMPViewer& other) = delete;
|
||||
BMPViewer& operator=(const BMPViewer& other) = delete;
|
||||
|
||||
bool load_bmp(const std::filesystem::path& file);
|
||||
|
||||
void paint(Painter& painter) override;
|
||||
void on_focus() override;
|
||||
void on_blur() override;
|
||||
bool on_key(const KeyEvent key) override;
|
||||
bool on_encoder(EncoderEvent delta) override;
|
||||
bool on_keyboard(const KeyboardEvent event) override;
|
||||
|
||||
void reset_pos();
|
||||
void set_zoom(int8_t new_zoom);
|
||||
int8_t get_zoom();
|
||||
|
||||
void set_enter_pass(bool pass);
|
||||
bool get_enter_pass();
|
||||
|
||||
private:
|
||||
void get_line(ui::Color* line, uint32_t bx, uint32_t by, uint32_t cnt);
|
||||
bool move_pos(int32_t delta_x, int32_t delta_y);
|
||||
BMPFile bmp{};
|
||||
int8_t zoom = 1; // positive = zoom in, negative = zoom out 0-invalid 1- no zoom
|
||||
int8_t zoom_fit = 1; // if this value is set, the image will fit the screen the most
|
||||
int8_t max_zoom = 10;
|
||||
int8_t min_zoom = -20; // will be calculated on load
|
||||
uint32_t cx = 0; // current top-left coordinate
|
||||
uint32_t cy = 0;
|
||||
uint32_t mvx = 1; // how much to move on key
|
||||
uint32_t mvy = 1;
|
||||
bool enter_pass = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -422,6 +422,10 @@ void SystemStatusView::set_back_enabled(bool new_value) {
|
||||
}
|
||||
}
|
||||
|
||||
void SystemStatusView::set_back_hidden(bool new_value) {
|
||||
button_back.hidden(new_value);
|
||||
}
|
||||
|
||||
void SystemStatusView::set_title_image_enabled(bool new_value) {
|
||||
if (new_value) {
|
||||
add_child(&button_title);
|
||||
@@ -896,6 +900,9 @@ Context& SystemView::context() const {
|
||||
NavigationView* SystemView::get_navigation_view() {
|
||||
return &navigation_view;
|
||||
}
|
||||
SystemStatusView* SystemView::get_status_view() {
|
||||
return &status_view;
|
||||
}
|
||||
|
||||
void SystemView::toggle_overlay() {
|
||||
static uint8_t last_perf_counter_status = shared_memory.request_m4_performance_counter;
|
||||
@@ -938,6 +945,15 @@ void SystemView::paint_overlay() {
|
||||
}
|
||||
}
|
||||
|
||||
void SystemView::set_app_fullscreen(bool fullscreen) {
|
||||
auto parent_rect = screen_rect();
|
||||
Dim status_view_height = (fullscreen) ? 0 : 16;
|
||||
status_view.hidden(fullscreen);
|
||||
navigation_view.set_parent_rect(
|
||||
{{0, status_view_height},
|
||||
{parent_rect.width(), static_cast<Dim>(parent_rect.height() - status_view_height)}});
|
||||
}
|
||||
|
||||
/* ***********************************************************************/
|
||||
|
||||
void BMPView::focus() {
|
||||
|
||||
@@ -185,6 +185,7 @@ class SystemStatusView : public View {
|
||||
SystemStatusView(NavigationView& nav);
|
||||
|
||||
void set_back_enabled(bool new_value);
|
||||
void set_back_hidden(bool new_value);
|
||||
void set_title_image_enabled(bool new_value);
|
||||
void set_title(const std::string new_value);
|
||||
|
||||
@@ -376,8 +377,10 @@ class SystemView : public View {
|
||||
Context& context() const override;
|
||||
void toggle_overlay();
|
||||
void paint_overlay();
|
||||
void set_app_fullscreen(bool fullscreen);
|
||||
|
||||
NavigationView* get_navigation_view();
|
||||
SystemStatusView* get_status_view();
|
||||
|
||||
private:
|
||||
uint8_t overlay_active{0};
|
||||
|
||||
@@ -37,6 +37,8 @@ class UsbSerialAsyncmsg {
|
||||
|
||||
template <typename VECTORCOVER>
|
||||
static void asyncmsg(const std::vector<VECTORCOVER>& data);
|
||||
|
||||
static void asyncmsg(const char* data); // string literal
|
||||
};
|
||||
|
||||
/*Notes:
|
||||
@@ -45,15 +47,16 @@ class UsbSerialAsyncmsg {
|
||||
* - so delete all debug things before you push your code to production
|
||||
* - use this client to filter only PP devices: https://github.com/zxkmm/Pyserial-Demo-portapack
|
||||
* - usage:
|
||||
* #include "usb_serial_debug_bridge.hpp"
|
||||
* portapack::async_tx_enabled = true; // note that use this when debugging, unless the msg would be forbidden. but don't use this in production, since it's not real async and multiple serial transmittions will broken each other. if this class is used in other scene in the future, just use command to cover (protect your serial tramsnitton) in your extern thing: asyncmsg enable --- your cmd --- asyncmsg disable
|
||||
* #include "usb_serial_asyncmsg.cpp"
|
||||
* UsbSerialAsyncmsg::asyncmsg("Hello PP");
|
||||
* */
|
||||
|
||||
/// value
|
||||
// to_string_bin/ to_string_decimal/ to_string_hex/ to_string_hex_array/ to_string_dec_uint/ to_string_dec_int etc seems usellss so i didn't add them here
|
||||
// usage: UsbSerialAsyncmsg::asyncmsg(num);
|
||||
|
||||
template <>
|
||||
// usage: UsbSerialAsyncmsg::asyncmsg(num);
|
||||
void UsbSerialAsyncmsg::asyncmsg<int64_t>(const int64_t& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
@@ -117,6 +120,14 @@ void UsbSerialAsyncmsg::asyncmsg<uint64_t>(const uint64_t& data) {
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_dec_int(data).c_str());
|
||||
}
|
||||
|
||||
template <>
|
||||
void UsbSerialAsyncmsg::asyncmsg<float>(const float& data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", to_string_decimal(data, 7).c_str());
|
||||
}
|
||||
|
||||
/// fs things
|
||||
|
||||
template <>
|
||||
@@ -130,6 +141,8 @@ void UsbSerialAsyncmsg::asyncmsg<std::filesystem::path>(const std::filesystem::p
|
||||
}
|
||||
|
||||
/// string
|
||||
|
||||
// string obj
|
||||
template <>
|
||||
// usage: UsbSerialAsyncmsg::asyncmsg(str);
|
||||
void UsbSerialAsyncmsg::asyncmsg<std::string>(const std::string& data) {
|
||||
@@ -139,6 +152,15 @@ void UsbSerialAsyncmsg::asyncmsg<std::string>(const std::string& data) {
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", data.c_str());
|
||||
}
|
||||
|
||||
// string literal AKA char[]
|
||||
// usage: UsbSerialAsyncmsg::asyncmsg("abc");
|
||||
void UsbSerialAsyncmsg::asyncmsg(const char* data) {
|
||||
if (!portapack::async_tx_enabled) {
|
||||
return;
|
||||
}
|
||||
chprintf((BaseSequentialStream*)&SUSBD1, "%s\r\n", data);
|
||||
}
|
||||
|
||||
/// vec worker
|
||||
// ussgae: UsbSerialAsyncmsg::asyncmsg(vec);
|
||||
template <typename VECTORCOVER>
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2024 jLynx.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "ads1110.hpp"
|
||||
#include "utility.hpp"
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ads1110 {
|
||||
|
||||
constexpr float BATTERY_MIN_VOLTAGE = 3.0;
|
||||
constexpr float BATTERY_MAX_VOLTAGE = 4.0;
|
||||
|
||||
void ADS1110::init() {
|
||||
if (!detected_) {
|
||||
detected_ = detect();
|
||||
}
|
||||
if (detected_) {
|
||||
// Set the configuration register
|
||||
write(0x8C);
|
||||
}
|
||||
}
|
||||
|
||||
bool ADS1110::detect() {
|
||||
uint8_t data[3];
|
||||
if (bus.receive(bus_address, data, 3)) {
|
||||
// Check if the received data is valid
|
||||
uint8_t configRegister = data[2];
|
||||
if ((configRegister & 0x0F) == 0x0C) {
|
||||
// The configuration register value matches the expected value (0x8C)
|
||||
detected_ = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ADS1110::write(const uint8_t value) {
|
||||
return bus.transmit(bus_address, &value, 1);
|
||||
}
|
||||
|
||||
float ADS1110::readVoltage() {
|
||||
// Read the conversion result
|
||||
uint8_t data[3];
|
||||
if (!bus.receive(bus_address, data, 3)) {
|
||||
return 0.0f; // Return 0 if the read fails
|
||||
}
|
||||
|
||||
uint16_t raw = (static_cast<uint16_t>(data[0]) << 8) | data[1];
|
||||
|
||||
// Calculate the voltage based on the output code
|
||||
float voltage = 0.0f;
|
||||
float minCode = 0;
|
||||
float pga = 0.0f;
|
||||
|
||||
uint8_t pga_rate = data[2] & 0x03;
|
||||
switch (pga_rate) {
|
||||
case 0:
|
||||
pga = 1.0f;
|
||||
break;
|
||||
case 1:
|
||||
pga = 2.0f;
|
||||
break;
|
||||
case 2:
|
||||
pga = 4.0f;
|
||||
break;
|
||||
case 3:
|
||||
pga = 8.0f;
|
||||
break;
|
||||
default:
|
||||
// Handle invalid data rate
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t data_rate = (data[2] >> 2) & 0x03;
|
||||
switch (data_rate) {
|
||||
case 0: // 240
|
||||
minCode = -2048.0;
|
||||
break;
|
||||
case 1: // 60
|
||||
minCode = -8192.0;
|
||||
break;
|
||||
case 2: // 30
|
||||
minCode = -16384.0;
|
||||
break;
|
||||
case 3: // 15
|
||||
minCode = -32768.0;
|
||||
break;
|
||||
default:
|
||||
// Handle invalid data rate
|
||||
break;
|
||||
}
|
||||
|
||||
// 2.048 is the reference voltage & 2.0 is to make up for the voltage divider
|
||||
voltage = raw / (-1.0 * minCode) * pga * 2.048 * 2.0;
|
||||
|
||||
return voltage;
|
||||
}
|
||||
|
||||
void ADS1110::getBatteryInfo(float& batteryPercentage, float& voltage) {
|
||||
voltage = readVoltage();
|
||||
|
||||
// Calculate the remaining battery percentage
|
||||
batteryPercentage = (voltage - BATTERY_MIN_VOLTAGE) /
|
||||
(BATTERY_MAX_VOLTAGE - BATTERY_MIN_VOLTAGE) * 100.0;
|
||||
|
||||
// Limit the values to the valid range
|
||||
batteryPercentage = std::clamp(batteryPercentage, 0.0f, 100.0f);
|
||||
// ToDo: if its > 4, then 100%, if < 3 then 0%
|
||||
}
|
||||
|
||||
} /* namespace ads1110 */
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright (C) 2024 jLynx.
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __ADS1110_H__
|
||||
#define __ADS1110_H__
|
||||
|
||||
#include <cstdint>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
#include "i2c_pp.hpp"
|
||||
|
||||
namespace ads1110 {
|
||||
|
||||
using address_t = uint8_t;
|
||||
using reg_t = uint16_t;
|
||||
|
||||
class ADS1110 {
|
||||
public:
|
||||
constexpr ADS1110(I2C& bus, const I2C::address_t bus_address)
|
||||
: bus(bus), bus_address(bus_address), detected_(false) {}
|
||||
|
||||
void init();
|
||||
bool isDetected() const { return detected_; }
|
||||
|
||||
float readVoltage();
|
||||
void getBatteryInfo(float& batteryPercentage, float& voltage);
|
||||
|
||||
private:
|
||||
I2C& bus;
|
||||
const I2C::address_t bus_address;
|
||||
bool detected_;
|
||||
|
||||
bool write(const uint8_t value);
|
||||
bool detect();
|
||||
};
|
||||
|
||||
} /* namespace ads1110 */
|
||||
|
||||
#endif /* __ADS1110_H__ */
|
||||
@@ -29,7 +29,7 @@ struct bmp_header_t {
|
||||
uint32_t image_data;
|
||||
uint32_t BIH_size;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
int32_t height; // can be negative, to signal the bottom-up or reserve status
|
||||
uint16_t planes;
|
||||
uint16_t bpp;
|
||||
uint32_t compression;
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "bmpfile.hpp"
|
||||
|
||||
bool BMPFile::is_loaded() {
|
||||
return is_opened;
|
||||
}
|
||||
|
||||
// fix height info
|
||||
uint32_t BMPFile::get_real_height() {
|
||||
if (!is_opened) return 0;
|
||||
return bmp_header.height >= 0 ? (uint32_t)bmp_header.height : (uint32_t)(-1 * bmp_header.height);
|
||||
}
|
||||
|
||||
// get bmp width
|
||||
uint32_t BMPFile::get_width() {
|
||||
if (!is_opened) return 0;
|
||||
return bmp_header.width;
|
||||
}
|
||||
|
||||
// get if the rows are bottom up (for most bmp), or up to bottom (negative height, we use it for write)
|
||||
bool BMPFile::is_bottomup() {
|
||||
return (bmp_header.height >= 0);
|
||||
}
|
||||
|
||||
BMPFile::~BMPFile() {
|
||||
close();
|
||||
}
|
||||
|
||||
// closes file
|
||||
void BMPFile::close() {
|
||||
is_opened = false;
|
||||
bmpimage.close();
|
||||
}
|
||||
|
||||
// creates a new bmp file. for now, hardcoded to 3 byte colour depth
|
||||
bool BMPFile::create(const std::filesystem::path& file, uint32_t x, uint32_t y) {
|
||||
is_opened = false;
|
||||
is_read_ony = true;
|
||||
bmpimage.close(); // if already open, close before open a new
|
||||
if (file_exists(file)) {
|
||||
delete_file(file); // overwrite
|
||||
}
|
||||
auto result = bmpimage.open(file, false, true);
|
||||
if (!result.value().ok()) return false;
|
||||
file_pos = 0;
|
||||
byte_per_row = (x * 3 % 4 == 0) ? x * 3 : (x * 3 + (4 - ((x * 3) % 4))); // with padding
|
||||
bmpimage.seek(file_pos);
|
||||
bmp_header.signature = 0x4D42;
|
||||
bmp_header.planes = 1;
|
||||
bmp_header.compression = 0;
|
||||
bmp_header.bpp = 24; // 3 byte depth
|
||||
bmp_header.width = x;
|
||||
bmp_header.height = 0; // for now, will expand
|
||||
bmp_header.image_data = 0x36;
|
||||
bmp_header.BIH_size = 0x28;
|
||||
bmp_header.h_res = 100;
|
||||
bmp_header.v_res = 100;
|
||||
byte_per_px = 3;
|
||||
type = 1;
|
||||
bmp_header.size = sizeof(bmp_header) + get_real_height() * byte_per_row; // with padding! --will update later with expand
|
||||
bmp_header.data_size = bmp_header.size - sizeof(bmp_header_t);
|
||||
bmp_header.colors_count = 0;
|
||||
bmp_header.icolors_count = 0;
|
||||
|
||||
bmpimage.write(&bmp_header, sizeof(bmp_header_t));
|
||||
file_pos = bmp_header.image_data;
|
||||
is_opened = true;
|
||||
is_read_ony = false;
|
||||
if (!expand_y(y)) return false; // will fill with 0, and update header data
|
||||
seek(0, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
// opens the file and parses header data. return true on success
|
||||
bool BMPFile::open(const std::filesystem::path& file, bool readonly) {
|
||||
is_opened = false;
|
||||
is_read_ony = true;
|
||||
bmpimage.close(); // if already open, close before open a new
|
||||
|
||||
auto result = bmpimage.open(file, readonly, false);
|
||||
if (!result.value().ok()) return false;
|
||||
file_pos = 0;
|
||||
bmpimage.seek(file_pos);
|
||||
auto read_size = bmpimage.read(&bmp_header, sizeof(bmp_header_t));
|
||||
if (!((bmp_header.signature == 0x4D42) && // "BM" Signature
|
||||
(bmp_header.planes == 1) && // Seems always to be 1
|
||||
(bmp_header.compression == 0 || bmp_header.compression == 3))) { // No compression
|
||||
return false;
|
||||
}
|
||||
char buffer[257];
|
||||
switch (bmp_header.bpp) {
|
||||
case 16:
|
||||
file_pos = 0x36;
|
||||
memset(buffer, 0, 16);
|
||||
bmpimage.read(buffer, 16);
|
||||
byte_per_px = 2;
|
||||
if (buffer[1] == 0x7C)
|
||||
type = 3; // A1R5G5B5
|
||||
else
|
||||
type = 0; // R5G6B5
|
||||
break;
|
||||
case 24:
|
||||
type = 1;
|
||||
byte_per_px = 3;
|
||||
break;
|
||||
case 32:
|
||||
type = 2;
|
||||
byte_per_px = 4;
|
||||
break;
|
||||
default:
|
||||
// not supported
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
byte_per_row = (bmp_header.width * byte_per_px % 4 == 0) ? bmp_header.width * byte_per_px : (bmp_header.width * byte_per_px + (4 - ((bmp_header.width * byte_per_px) % 4)));
|
||||
file_pos = bmp_header.image_data;
|
||||
is_opened = true;
|
||||
is_read_ony = readonly;
|
||||
currx = 0;
|
||||
curry = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// jumps to next pixel. false on the end
|
||||
bool BMPFile::advance_curr_px(uint32_t num = 1) {
|
||||
if (curry >= get_real_height()) return false;
|
||||
uint32_t rowsToAdvance = (currx + num) / bmp_header.width;
|
||||
uint32_t nx = (currx + num) % bmp_header.width;
|
||||
uint32_t ny = curry + rowsToAdvance;
|
||||
if (ny >= get_real_height()) {
|
||||
return false;
|
||||
}
|
||||
seek(nx, ny);
|
||||
return true;
|
||||
}
|
||||
|
||||
// reads next px, then advance the pos (and seek). return false on error
|
||||
bool BMPFile::read_next_px(ui::Color& px, bool seek = true) {
|
||||
if (!is_opened) return false;
|
||||
uint8_t buffer[4];
|
||||
auto res = bmpimage.read(buffer, byte_per_px);
|
||||
if (res.is_error()) return false;
|
||||
switch (type) {
|
||||
case 0: // R5G6B5
|
||||
case 3: // A1R5G5B5
|
||||
if (!type)
|
||||
px = ui::Color((uint16_t)buffer[0] | ((uint16_t)buffer[1] << 8));
|
||||
else
|
||||
px = ui::Color(((uint16_t)buffer[0] & 0x1F) | ((uint16_t)buffer[0] & 0xE0) << 1 | ((uint16_t)buffer[1] & 0x7F) << 9);
|
||||
break;
|
||||
case 1: // 24
|
||||
default:
|
||||
px = ui::Color(buffer[2], buffer[1], buffer[0]);
|
||||
|
||||
break;
|
||||
case 2: // 32
|
||||
px = ui::Color(buffer[2], buffer[1], buffer[0]);
|
||||
break;
|
||||
}
|
||||
if (seek) advance_curr_px();
|
||||
return true;
|
||||
}
|
||||
|
||||
// if you set this, then the expanded part (or the newly created) will be filled with this color. but the expansion or the creation will be slower.
|
||||
void BMPFile::set_bg_color(ui::Color background) {
|
||||
bg = background;
|
||||
use_bg = true;
|
||||
}
|
||||
|
||||
// delete bg color. default. creation or expansion will be fast, but the file will contain random garbage. no problem if you write all pixels later.
|
||||
void BMPFile::delete_db_color() {
|
||||
use_bg = false;
|
||||
}
|
||||
|
||||
// writes a color data to the current position, and advances 1 px. true on success, false on error
|
||||
bool BMPFile::write_next_px(ui::Color& px) {
|
||||
if (!is_opened) return false;
|
||||
if (is_read_ony) return false;
|
||||
uint8_t buffer[4];
|
||||
switch (type) {
|
||||
case 0: // R5G6B5
|
||||
case 3: // A1R5G5B5
|
||||
if (!type) {
|
||||
buffer[0] = (px.r() << 3) | (px.g() >> 3); // todo test in future
|
||||
buffer[1] = (px.g() << 5) | px.b();
|
||||
} else {
|
||||
buffer[0] = (1 << 7) | (px.r() << 2) | (px.g() >> 3); // todo test in future
|
||||
buffer[1] = (px.g() << 5) | px.b();
|
||||
}
|
||||
break;
|
||||
case 1: // 24
|
||||
default:
|
||||
buffer[2] = px.r();
|
||||
buffer[1] = px.g();
|
||||
buffer[0] = px.b();
|
||||
break;
|
||||
case 2: // 32
|
||||
buffer[2] = px.r();
|
||||
buffer[1] = px.g();
|
||||
buffer[0] = px.b();
|
||||
buffer[3] = 255;
|
||||
break;
|
||||
}
|
||||
auto res = bmpimage.write(buffer, byte_per_px);
|
||||
if (res.is_error()) return false;
|
||||
advance_curr_px();
|
||||
return true;
|
||||
}
|
||||
|
||||
// positions in the file to the given pixel. 0 based indexing
|
||||
bool BMPFile::seek(uint32_t x, uint32_t y) {
|
||||
if (!is_opened) return false;
|
||||
if (x >= bmp_header.width) return false;
|
||||
if (y >= get_real_height()) return false;
|
||||
if (!BMPFile::is_bottomup()) {
|
||||
file_pos = bmp_header.image_data; // nav to start pos.
|
||||
file_pos += y * byte_per_row;
|
||||
file_pos += x * byte_per_px;
|
||||
bmpimage.seek(file_pos);
|
||||
currx = x;
|
||||
curry = y;
|
||||
} else {
|
||||
file_pos = bmp_header.image_data; // nav to start pos.
|
||||
file_pos += (get_real_height() - y - 1) * byte_per_row;
|
||||
file_pos += x * byte_per_px;
|
||||
bmpimage.seek(file_pos);
|
||||
currx = x;
|
||||
curry = y;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// expands the image with a delta (y). also seek's t it's begining. in bottumup format, it should be used carefully!
|
||||
bool BMPFile::expand_y_delta(uint32_t delta_y) {
|
||||
return expand_y(get_real_height() + delta_y);
|
||||
}
|
||||
|
||||
// expands the image to a new y size. also seek's t it's begining. in bottumup format, it should be used carefully!
|
||||
bool BMPFile::expand_y(uint32_t new_y) {
|
||||
if (!is_opened) return false; // not yet opened
|
||||
uint32_t old_height = get_real_height();
|
||||
if (new_y < old_height) return true; // already bigger
|
||||
if (is_read_ony) return false; // can't expand
|
||||
uint32_t delta = (new_y - old_height) * byte_per_row;
|
||||
bmp_header.size += delta;
|
||||
bmp_header.data_size += delta;
|
||||
bmp_header.height = -1 * new_y; //-1*, so no bottom-up structure needed. easier to expand.
|
||||
bmpimage.seek(0);
|
||||
bmpimage.write(&bmp_header, sizeof(bmp_header)); // overwrite header
|
||||
bmpimage.seek(bmp_header.size); // seek to new end to expand
|
||||
// fill with bg color if needed
|
||||
if (use_bg) {
|
||||
seek(0, old_height); // to the new begin
|
||||
size_t newpxcount = ((new_y - old_height) * bmp_header.width);
|
||||
for (size_t i = 0; i < newpxcount; ++i)
|
||||
write_next_px(bg);
|
||||
}
|
||||
|
||||
if (is_bottomup()) {
|
||||
seek(0, new_y - old_height); // seek to the new chunk begin
|
||||
} else {
|
||||
seek(0, curry + 1); // seek to the begin of the new chunk
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright (C) 2024 HTotoo
|
||||
*
|
||||
* This file is part of PortaPack.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; see the file COPYING. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef __BMPFILE__H
|
||||
#define __BMPFILE__H
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "file.hpp"
|
||||
#include "bmp.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
class BMPFile {
|
||||
public:
|
||||
~BMPFile();
|
||||
bool open(const std::filesystem::path& file, bool readonly);
|
||||
bool create(const std::filesystem::path& file, uint32_t x, uint32_t y);
|
||||
void close();
|
||||
bool is_loaded();
|
||||
bool seek(uint32_t x, uint32_t y);
|
||||
bool expand_y(uint32_t new_y);
|
||||
bool expand_y_delta(uint32_t delta_y);
|
||||
uint32_t getbpr() { return byte_per_row; };
|
||||
|
||||
bool read_next_px(ui::Color& px, bool seek);
|
||||
bool write_next_px(ui::Color& px);
|
||||
uint32_t get_real_height();
|
||||
uint32_t get_width();
|
||||
bool is_bottomup();
|
||||
void set_bg_color(ui::Color background);
|
||||
void delete_db_color();
|
||||
|
||||
private:
|
||||
bool advance_curr_px(uint32_t num);
|
||||
bool is_opened = false;
|
||||
bool is_read_ony = true;
|
||||
|
||||
File bmpimage{};
|
||||
size_t file_pos = 0;
|
||||
bmp_header_t bmp_header{};
|
||||
uint8_t type = 0;
|
||||
uint8_t byte_per_px = 1;
|
||||
uint32_t byte_per_row = 0;
|
||||
|
||||
uint32_t currx = 0;
|
||||
uint32_t curry = 0;
|
||||
ui::Color bg{};
|
||||
bool use_bg = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
+1
-1
Submodule hackrf updated: f5dd48a5bc...22c9ff10f9
Reference in New Issue
Block a user