M .vscode/launch.json => .vscode/launch.json +2 -1
@@ 64,7 64,7 @@
"cwd": "${workspaceFolder}",
"gdbPath": "arm-none-eabi-gdb",
// "debuggerArgs": ["-x", "${workspaceFolder}/.gdbinit-1051"],
- // "serverArgs": ["-strict", "-ir", "-singlerun", "-speed", "25000"],
+ "serverArgs": ["-strict", "-ir", "-singlerun", "-speed", "25000"],
// "runToEntryPoint": "main",
// "runToMain": true,
"preLaunchCommands": [],
@@ 109,6 109,7 @@
"serverpath": "JLinkGDBServerCLExe",
"cwd": "${workspaceFolder}",
"gdbPath": "arm-none-eabi-gdb",
+ "serverArgs": ["-strict", "-ir", "-singlerun", "-speed", "25000"],
"interface": "swd",
"device": "MCIMXRT1051",
"jlinkscript": "${workspaceFolder}/evkbimxrt1050_sdram_init_T6.jlinkscript",
M .vscode/tasks.json => .vscode/tasks.json +38 -0
@@ 169,6 169,44 @@
"detail": "Configure cmake project and build with Ninja",
},
+ {
+ "type": "shell",
+ "label": "Configure PurePhone (RT1051, DEV)",
+ "command": "./configure.sh",
+ "args": [
+ "pure",
+ "rt1051",
+ "RelWithDebInfo",
+ "-DWITH_DEVELOPMENT_FEATURES=ON",
+ "-G",
+ "Ninja"
+ ],
+ "options": {
+ "cwd": "${workspaceFolder}"
+ },
+ "group": "build",
+ "detail": "Run cmake configure script",
+ },
+
+ {
+ "type": "shell",
+ "label": "Configure BellHybrid (RT1051, DEV)",
+ "command": "./configure.sh",
+ "args": [
+ "bell",
+ "rt1051",
+ "RelWithDebInfo",
+ "-DWITH_DEVELOPMENT_FEATURES=ON",
+ "-G",
+ "Ninja"
+ ],
+ "options": {
+ "cwd": "${workspaceFolder}"
+ },
+ "group": "build",
+ "detail": "Run cmake configure script",
+ },
+
// {
// "type": "shell",
// "label": "Run JLink Server for PurePhone (RT1051)",
M harmony_changelog.md => harmony_changelog.md +1 -0
@@ 12,6 12,7 @@
#### UI/UX:
* UI update (Home Screen settings).
+* Implemented partial refresh of the display.
#### Translations:
* Completed missing translations.
M module-apps/application-calendar/windows/CalendarMainWindow.cpp => module-apps/application-calendar/windows/CalendarMainWindow.cpp +1 -1
@@ 44,7 44,7 @@ namespace gui
offsetFromTop = style::window::default_vertical_pos + style::window::calendar::month_year_height;
monthWidth = style::window::default_body_width;
- monthHeight = style::window_height - style::window::default_vertical_pos - style::nav_bar::height;
+ monthHeight = style::window_height - offsetFromTop;
dayWidth = style::window::calendar::day_cell_width;
dayHeight = style::window::calendar::day_cell_height;
M module-bsp/board/linux/eink/ED028TC1.c => module-bsp/board/linux/eink/ED028TC1.c +2 -2
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
/**
@@ 119,7 119,7 @@ EinkStatus_e EinkResetAndInitialize()
return EinkOK;
}
-EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, uint8_t *buffer)
+EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, const uint8_t *buffer)
{
uint32_t offset_eink = frame.pos_y * BOARD_EINK_DISPLAY_RES_X + frame.pos_x;
uint32_t offset_buffer = 0;
M module-bsp/board/linux/eink/ED028TC1.h => module-bsp/board/linux/eink/ED028TC1.h +2 -2
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
/**
@@ 90,7 90,7 @@ extern "C"
* @return EinkNoMem - Could not allocate the temporary buffer
* EinkOK - Part of image send successfully
*/
- EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, uint8_t *buffer);
+ EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, const uint8_t *buffer);
/**
* @brief This function sets the waveform to the \ref EinkWaveformINIT to make the display clearing more deep and
M module-bsp/board/linux/eink/LinuxEinkDisplay.cpp => module-bsp/board/linux/eink/LinuxEinkDisplay.cpp +12 -2
@@ 47,9 47,19 @@ namespace hal::eink
displayColorMode = mode;
}
- EinkStatus LinuxEinkDisplay::showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode)
+ EinkStatus LinuxEinkDisplay::showImage(const std::vector<EinkFrame> &updateFrames,
+ const EinkFrame &refreshFrame,
+ const std::uint8_t *frameBuffer,
+ const EinkRefreshMode refreshMode)
{
- return translateStatus(EinkUpdateFrame(EinkFrame_t{0, 0, size.width, size.height}, frameBuffer));
+ for (const EinkFrame &frame : updateFrames) {
+ const std::uint8_t *buffer = frameBuffer + frame.pos_y * frame.size.width;
+ const auto status = translateStatus(
+ EinkUpdateFrame({frame.pos_x, frame.pos_y, frame.size.width, frame.size.height}, buffer));
+ if (status != EinkStatus::EinkOK)
+ return status;
+ }
+ return EinkStatus::EinkOK;
}
void LinuxEinkDisplay::prepareEarlyRequest([[maybe_unused]] const EinkRefreshMode refreshMode,
M module-bsp/board/linux/eink/LinuxEinkDisplay.hpp => module-bsp/board/linux/eink/LinuxEinkDisplay.hpp +4 -1
@@ 15,7 15,10 @@ namespace hal::eink
private:
void setMode(const EinkDisplayColorMode mode) noexcept override;
- EinkStatus showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) override;
+ EinkStatus showImage(const std::vector<EinkFrame> &updateFrames,
+ const EinkFrame &refreshFrame,
+ const std::uint8_t *frameBuffer,
+ const EinkRefreshMode refreshMode) override;
void prepareEarlyRequest(const EinkRefreshMode refreshMode, const WaveformTemperature behaviour) override;
void dither() override;
M module-bsp/board/rt1051/bsp/eink/ED028TC1.cpp => module-bsp/board/rt1051/bsp/eink/ED028TC1.cpp +22 -19
@@ 138,7 138,7 @@ static uint8_t s_einkMaskLut_2Bpp[16] = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3,
*
* @return
*/
-static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 187,7 187,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(uint8_t *dataIn,
*
* @return
*/
-static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 236,7 236,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(uint8_t *dataIn,
*
* @return
*/
-static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 285,7 285,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(uint8_t *dataIn,
*
* @return
*/
-static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 334,7 334,7 @@ static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(uint8_t *dataIn,
*
* @return
*/
-static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 383,7 383,7 @@ static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(uint8_t *data
*
* @return
*/
-static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 432,7 432,7 @@ static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(uint8_t *data
*
* @return
*/
-static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 487,7 487,7 @@ static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(uint8_t *data
* It is used when EINK_ROTATE_90_CLOCKWISE is not defined.
*/
-static uint8_t *s_EinkTransformFrameCoordinateSystemNoRotation_4Bpp(uint8_t *dataIn,
+static uint8_t *s_EinkTransformFrameCoordinateSystemNoRotation_4Bpp(const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 799,7 799,10 @@ EinkStatus_e EinkDitherDisplay()
return EinkOK;
}
-EinkStatus_e EinkUpdateFrame(EinkFrame_t frame, uint8_t *buffer, EinkBpp_e bpp, EinkDisplayColorMode_e invertColors)
+EinkStatus_e EinkUpdateFrame(EinkFrame_t frame,
+ const uint8_t *buffer,
+ EinkBpp_e bpp,
+ EinkDisplayColorMode_e invertColors)
{
uint8_t buf[10];
uint8_t pixelsInByte = 8 / bpp;
@@ 1004,7 1007,7 @@ EinkStatus_e EinkRefreshImage(EinkFrame_t frame, EinkDisplayTimingsMode_e refres
return EinkOK;
}
-__attribute__((optimize("O3"))) void EinkARGBToLuminance(uint8_t *dataIn,
+__attribute__((optimize("O3"))) void EinkARGBToLuminance(const uint8_t *dataIn,
uint8_t *dataOut,
uint32_t displayWidth,
uint32_t displayHeight)
@@ 1037,7 1040,7 @@ __attribute__((optimize("O3"))) void EinkARGBToLuminance(uint8_t *dataIn,
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_1Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1084,7 1087,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_2Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1135,7 1138,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_3Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1146,7 1149,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_1Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1193,7 1196,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoo
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_2Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1242,7 1245,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformAnimationFrameCoo
}
__attribute__((optimize("O3"))) static uint8_t *s_EinkTransformAnimationFrameCoordinateSystem_3Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1253,7 1256,7 @@ __attribute__((optimize("O3"))) static uint8_t *s_EinkTransformAnimationFrameCoo
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystem_4Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1300,7 1303,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy
}
__attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSystemNoRotation_4Bpp(
- uint8_t *dataIn,
+ const uint8_t *dataIn,
uint16_t windowWidthPx,
uint16_t windowHeightPx,
uint8_t *dataOut,
@@ 1315,7 1318,7 @@ __attribute__((optimize("O1"))) static uint8_t *s_EinkTransformFrameCoordinateSy
int32_t inputRow = 0;
int32_t inputCol = 0;
- for (inputRow = 0; inputRow < windowHeightPx - 1; ++inputRow) {
+ for (inputRow = 0; inputRow < windowHeightPx; ++inputRow) {
for (inputCol = windowWidthPx - 7; inputCol >= 0; inputCol -= pixelsInByte) {
// HACK: Did not create the loop for accessing pixels and merging them in the single byte for better
// performance.
M module-bsp/board/rt1051/bsp/eink/ED028TC1.h => module-bsp/board/rt1051/bsp/eink/ED028TC1.h +2 -2
@@ 382,7 382,7 @@ extern "C"
* EinkOK - Part of image send successfully
*/
EinkStatus_e EinkUpdateFrame(EinkFrame_t frame,
- uint8_t *buffer,
+ const uint8_t *buffer,
EinkBpp_e bpp,
EinkDisplayColorMode_e invertColors);
@@ 431,7 431,7 @@ extern "C"
* @param displayWidth [in] - display width in pixels
* @param displayHeight [in] - display height in pixels
*/
- void EinkARGBToLuminance(uint8_t *dataIn, uint8_t *dataOut, uint32_t displayWidth, uint32_t displayHeight);
+ void EinkARGBToLuminance(const uint8_t *dataIn, uint8_t *dataOut, uint32_t displayWidth, uint32_t displayHeight);
#if defined(__cplusplus)
}
M module-bsp/board/rt1051/bsp/eink/EinkDisplay.cpp => module-bsp/board/rt1051/bsp/eink/EinkDisplay.cpp +25 -16
@@ 4,10 4,10 @@
#include "EinkDisplay.hpp"
#include <board/BoardDefinitions.hpp>
-
#include <log/log.hpp>
#include <gsl/util>
+
#include <stdexcept>
#include <cstdio>
#include <cstring>
@@ 132,15 132,15 @@ namespace hal::eink
delete[] currentWaveform.LUTDData;
}
- EinkStatus EinkDisplay::updateDisplay(std::uint8_t *frameBuffer, [[maybe_unused]] const EinkRefreshMode refreshMode)
+ EinkStatus EinkDisplay::updateDisplay(EinkFrame frame, const std::uint8_t *frameBuffer)
{
- return update(frameBuffer);
+ return update(frame, frameBuffer);
}
- EinkStatus EinkDisplay::refreshDisplay(const EinkRefreshMode refreshMode)
+ EinkStatus EinkDisplay::refreshDisplay(EinkFrame frame, const EinkRefreshMode refreshMode)
{
const auto isDeepRefresh = refreshMode == EinkRefreshMode::REFRESH_DEEP;
- return refresh(isDeepRefresh ? EinkDisplayTimingsDeepCleanMode : EinkDisplayTimingsFastRefreshMode);
+ return refresh(frame, isDeepRefresh ? EinkDisplayTimingsDeepCleanMode : EinkDisplayTimingsFastRefreshMode);
}
EinkStatus EinkDisplay::prepareDisplay(const EinkRefreshMode refreshMode, const WaveformTemperature behaviour)
@@ 160,20 160,27 @@ namespace hal::eink
return setWaveform(EinkWaveforms_e::EinkWaveformDU2, temperature);
}
- EinkStatus EinkDisplay::showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode)
+ EinkStatus EinkDisplay::showImage(const std::vector<EinkFrame> &updateFrames,
+ const EinkFrame &refreshFrame,
+ const std::uint8_t *frameBuffer,
+ const EinkRefreshMode refreshMode)
{
if (const auto status = prepareDisplay(refreshMode, WaveformTemperature::KEEP_CURRENT);
status != EinkStatus::EinkOK) {
return status;
}
- if (const auto status = updateDisplay(frameBuffer, refreshMode); status != EinkStatus::EinkOK) {
- return status;
+ for (const EinkFrame &frame : updateFrames) {
+ const std::uint8_t *buffer = frameBuffer + frame.pos_y * frame.size.width;
+ if (const auto status = updateDisplay(frame, buffer); status != EinkStatus::EinkOK) {
+ return status;
+ }
}
- if (const auto status = refreshDisplay(refreshMode); status != EinkStatus::EinkOK) {
+ if (const auto status = refreshDisplay(refreshFrame, refreshMode); status != EinkStatus::EinkOK) {
return status;
}
+
return EinkStatus::EinkOK;
}
@@ 218,12 225,13 @@ namespace hal::eink
EinkFillScreenWithColor(EinkDisplayColorFilling_e::EinkDisplayColorWhite);
}
- EinkStatus EinkDisplay::update(std::uint8_t *displayBuffer)
+ EinkStatus EinkDisplay::update(EinkFrame frame, const std::uint8_t *displayBuffer)
{
- return translateStatus(EinkUpdateFrame(EinkFrame_t{0, 0, size.width, size.height},
- displayBuffer,
- getCurrentBitsPerPixelFormat(),
- translateDisplayColorMode(displayMode)));
+ return translateStatus(
+ EinkUpdateFrame(EinkFrame_t{frame.pos_x, frame.pos_y, frame.size.width, frame.size.height},
+ displayBuffer,
+ getCurrentBitsPerPixelFormat(),
+ translateDisplayColorMode(displayMode)));
}
EinkBpp_e EinkDisplay::getCurrentBitsPerPixelFormat() const noexcept
@@ 234,10 242,11 @@ namespace hal::eink
return Eink4Bpp;
}
- EinkStatus EinkDisplay::refresh(const EinkDisplayTimingsMode_e refreshMode)
+ EinkStatus EinkDisplay::refresh(EinkFrame frame, const EinkDisplayTimingsMode_e refreshMode)
{
currentWaveform.useCounter += 1;
- return translateStatus(EinkRefreshImage(EinkFrame_t{0, 0, size.width, size.height}, refreshMode));
+ return translateStatus(
+ EinkRefreshImage(EinkFrame_t{frame.pos_x, frame.pos_y, frame.size.width, frame.size.height}, refreshMode));
}
bool EinkDisplay::isNewWaveformNeeded(const EinkWaveforms_e newMode, const std::int32_t newTemperature) const
M module-bsp/board/rt1051/bsp/eink/EinkDisplay.hpp => module-bsp/board/rt1051/bsp/eink/EinkDisplay.hpp +8 -5
@@ 26,7 26,10 @@ namespace hal::eink
~EinkDisplay() noexcept;
void setMode(EinkDisplayColorMode mode) noexcept override;
- EinkStatus showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) override;
+ EinkStatus showImage(const std::vector<EinkFrame> &updateFrames,
+ const EinkFrame &refreshFrame,
+ const std::uint8_t *frameBuffer,
+ const EinkRefreshMode refreshMode) override;
void prepareEarlyRequest(EinkRefreshMode refreshMode, const WaveformTemperature behaviour) override;
EinkStatus resetAndInit() override;
@@ 48,11 51,11 @@ namespace hal::eink
std::int32_t getLastTemperature() const noexcept;
- EinkStatus update(std::uint8_t *displayBuffer);
- EinkStatus refresh(const EinkDisplayTimingsMode_e refreshMode);
+ EinkStatus update(EinkFrame frame, const std::uint8_t *displayBuffer);
+ EinkStatus refresh(EinkFrame frame, const EinkDisplayTimingsMode_e refreshMode);
- EinkStatus updateDisplay(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode);
- EinkStatus refreshDisplay(const EinkRefreshMode refreshMode);
+ EinkStatus updateDisplay(EinkFrame frame, const std::uint8_t *frameBuffer);
+ EinkStatus refreshDisplay(EinkFrame frame, const EinkRefreshMode refreshMode);
EinkStatus prepareDisplay(const EinkRefreshMode refreshMode, const WaveformTemperature behaviour);
FrameSize size;
M module-bsp/board/rt1051/bsp/eink/eink_dimensions.cpp => module-bsp/board/rt1051/bsp/eink/eink_dimensions.cpp +2 -1
@@ 22,7 22,8 @@ namespace hal::eink
#if defined(EINK_ROTATE_90_CLOCKWISE)
return BOARD_EINK_DISPLAY_RES_X - frame.pos_x - frame.width;
#else
- return BOARD_EINK_DISPLAY_RES_Y - frame.pos_y - frame.height;
+ // return BOARD_EINK_DISPLAY_RES_Y - frame.pos_y - frame.height;
+ return frame.pos_y;
#endif
}
M module-bsp/hal/include/hal/eink/AbstractEinkDisplay.hpp => module-bsp/hal/include/hal/eink/AbstractEinkDisplay.hpp +5 -1
@@ 2,6 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include <memory>
+#include <vector>
#include <devices/Device.hpp>
@@ 66,7 67,10 @@ namespace hal::eink
virtual ~AbstractEinkDisplay() = default;
virtual void setMode(const EinkDisplayColorMode mode) noexcept = 0;
- virtual EinkStatus showImage(std::uint8_t *frameBuffer, const EinkRefreshMode refreshMode) = 0;
+ virtual EinkStatus showImage(const std::vector<EinkFrame> &updateFrames,
+ const EinkFrame &refreshFrame,
+ const std::uint8_t *frameBuffer,
+ const EinkRefreshMode refreshMode) = 0;
virtual void prepareEarlyRequest(EinkRefreshMode refreshMode, const WaveformTemperature behaviour) = 0;
virtual void dither() = 0;
M module-gui/gui/core/BoundingBox.hpp => module-gui/gui/core/BoundingBox.hpp +4 -3
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 31,8 31,8 @@ namespace gui
Position x = zero_position, y = zero_position;
Length w = zero_size, h = zero_size;
};
- BoundingBox(Position x = zero_position, Position y = zero_position, Length w = 0, Length h = 0);
- virtual ~BoundingBox() = default;
+ BoundingBox() = default;
+ BoundingBox(Position x, Position y, Length w, Length h);
static bool intersect(const BoundingBox &box1, const BoundingBox &box2, BoundingBox &result);
@@ 44,6 44,7 @@ namespace gui
Position pos(gui::Axis axis) const;
std::string str() const;
/// logical sum of bounding box by another bounding box values
+ // NOTE: This function only increases the size, position is intact so this is not really a sum
void sum(const BoundingBox &box);
bool operator==(const BoundingBox &box) const;
bool operator!=(const BoundingBox &box) const;
M module-gui/gui/core/Context.cpp => module-gui/gui/core/Context.cpp +106 -43
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
/*
@@ 8,35 8,28 @@
* Author: robert
*/
-#include <ios>
-#include <cstring>
-
-#include "BoundingBox.hpp"
#include "Context.hpp"
+#include <cassert>
+#include <cstring>
+#include <ios>
+
namespace gui
{
-
- Context::Context() : x{0}, y{0}, w{0}, h{0}, data{nullptr}
- {}
-
- Context::Context(uint16_t width, uint16_t height) : x{0}, y{0}, w{width}, h{height}, data(new uint8_t[w * h])
- {
- memset(data, 15, w * h);
- }
-
- Context::~Context()
+ Context::Context(std::uint16_t width, std::uint16_t height) : w{width}, h{height}, data(new std::uint8_t[w * h])
{
- if (data != nullptr)
- delete[] data;
+ memset(data.get(), clearColor, w * h);
}
- Context *Context::get(int16_t gx, int16_t gy, uint16_t width, uint16_t height)
+ Context Context::get(std::int16_t gx, std::int16_t gy, std::uint16_t width, std::uint16_t height) const
{
- Context *retContext = new Context(width, height);
+ Context retContext = Context(width, height);
- retContext->x = gx;
- retContext->y = gy;
+ // Copy the whole block if context is fully inside and covering the whole width
+ if (gx == 0 && width == w && gy >= 0 && std::uint16_t(gy + height) <= h) {
+ memcpy(retContext.data.get(), data.get() + gy * w, w * height);
+ return retContext;
+ }
// create bounding boxes for the current context and return context
BoundingBox currentBox = BoundingBox(0, 0, w, h);
@@ 48,7 41,7 @@ namespace gui
Length sourceOffset = resultBox.y * w + resultBox.x;
Length destOffset = (resultBox.y - gy) * width + (resultBox.x - gx);
for (Length h = 0; h < resultBox.h; h++) {
- memcpy(retContext->data + destOffset, data + sourceOffset, resultBox.w);
+ memcpy(retContext.data.get() + destOffset, data.get() + sourceOffset, resultBox.w);
sourceOffset += w;
destOffset += width;
}
@@ 58,27 51,38 @@ namespace gui
return retContext;
}
- void Context::insert(int16_t ix, int16_t iy, Context *context)
+ void Context::insert(std::int16_t ix, std::int16_t iy, const Context &context)
{
+ // Copy the whole block if context is fully inside and covering the whole width
+ if (ix == 0 && context.w == w && iy >= 0 && std::uint16_t(iy + context.h) <= h) {
+ memcpy(data.get() + iy * w, context.data.get(), w * context.h);
+ return;
+ }
+
// create bounding boxes for the current context and return context
BoundingBox currentBox = BoundingBox(0, 0, w, h);
- BoundingBox insertBox = BoundingBox(ix, iy, context->w, context->h);
+ BoundingBox insertBox = BoundingBox(ix, iy, context.w, context.h);
BoundingBox resultBox;
// if boxes overlap copy part defined by result from current context to the new context.
if (BoundingBox::intersect(currentBox, insertBox, resultBox)) {
- Length sourceOffset = (resultBox.y - iy) * context->w + (resultBox.x - ix);
+ Length sourceOffset = (resultBox.y - iy) * context.w + (resultBox.x - ix);
Length destOffset = (resultBox.y) * w + (resultBox.x);
for (Length h = 0; h < resultBox.h; h++) {
- memcpy(data + destOffset, context->data + sourceOffset, resultBox.w);
- sourceOffset += context->w;
+ memcpy(data.get() + destOffset, context.data.get() + sourceOffset, resultBox.w);
+ sourceOffset += context.w;
destOffset += w;
}
}
}
- void Context::insertArea(
- int16_t ix, int16_t iy, int16_t iareaX, int16_t iareaY, int16_t iareaW, int16_t iareaH, Context *context)
+ void Context::insertArea(std::int16_t ix,
+ std::int16_t iy,
+ std::int16_t iareaX,
+ std::int16_t iareaY,
+ std::int16_t iareaW,
+ std::int16_t iareaH,
+ const Context &context)
{
// create bounding boxes for the current context and return context
BoundingBox currentBox = BoundingBox(0, 0, w, h);
@@ 87,39 91,98 @@ namespace gui
// if boxes overlap copy part defined by result from current context to the new context.
if (BoundingBox::intersect(currentBox, insertBox, resultBox)) {
- int16_t xBoxOffset = 0;
- int16_t yBoxOffset = 0;
+ std::int16_t xBoxOffset = 0;
+ std::int16_t yBoxOffset = 0;
if (iareaX < 0)
xBoxOffset = iareaX;
if (iareaY < 0)
yBoxOffset = iareaY;
- Length sourceOffset = (resultBox.y - iy - yBoxOffset) * context->w + (resultBox.x - ix - xBoxOffset);
+ Length sourceOffset = (resultBox.y - iy - yBoxOffset) * context.w + (resultBox.x - ix - xBoxOffset);
Length destOffset = (resultBox.y) * w + (resultBox.x);
for (Length h = 0; h < resultBox.h; h++) {
- memcpy(data + destOffset, context->data + sourceOffset, resultBox.w);
- sourceOffset += context->w;
+ memcpy(data.get() + destOffset, context.data.get() + sourceOffset, resultBox.w);
+ sourceOffset += context.w;
destOffset += w;
}
}
}
- void Context::fill(uint8_t colour)
+ struct LRange
+ {
+ LRange(std::uint16_t begin, std::uint16_t end) : begin(begin), end(end)
+ {}
+ void expand(LRange const &other)
+ {
+ begin = std::min(begin, other.begin);
+ end = std::max(end, other.end);
+ }
+ static LRange inversed(std::uint16_t end)
+ {
+ return {end, 0};
+ }
+ std::uint16_t begin, end;
+ };
+
+ inline BoundingBox makeBoundingBox(const LRange &rangeX, const LRange &rangeY)
+ {
+ return {rangeX.begin,
+ rangeY.begin,
+ std::uint16_t(rangeX.end - rangeX.begin),
+ std::uint16_t(rangeY.end - rangeY.begin)};
+ }
+
+ // Currently the algorithm only works properly for width divisible by 8 due to the use of 64b integers.
+ std::deque<BoundingBox> gui::Context::linesDiffs(const gui::Context &ctx1, const gui::Context &ctx2)
+ {
+ using casted_t = std::uint64_t;
+ const std::uint16_t w = ctx1.getW();
+ const std::uint16_t h = ctx1.getH();
+ assert(w == ctx2.getW() && h == ctx2.getH() && w % 8 == 0);
+ const std::uint16_t cw = w / sizeof(casted_t);
+ const auto data1 = reinterpret_cast<const casted_t *>(ctx1.getData());
+ const auto data2 = reinterpret_cast<const casted_t *>(ctx2.getData());
+
+ std::deque<BoundingBox> result;
+ LRange rangeY = LRange::inversed(h);
+ for (std::uint16_t y = 0; y < h; ++y) {
+ const auto begin1 = data1 + y * cw;
+ const auto end1 = begin1 + cw;
+ const auto begin2 = data2 + y * cw;
+ if (std::mismatch(begin1, end1, begin2).first != end1) {
+ if (rangeY.begin == h) { // diff pixels found first time
+ rangeY.begin = y;
+ }
+ }
+ else {
+ if (rangeY.begin != h) { // diff pixels found before
+ rangeY.end = y;
+ result.push_back(makeBoundingBox({0, w}, rangeY));
+ rangeY = LRange::inversed(h);
+ }
+ }
+ }
+ if (rangeY.begin != h) { // diff pixels found before
+ rangeY.end = h;
+ result.push_back(makeBoundingBox({0, w}, rangeY));
+ }
+ return result;
+ }
+
+ void Context::fill(std::uint8_t colour)
{
if (data) {
- memset(data, colour, w * h);
+ memset(data.get(), colour, w * h);
}
- // uint32_t size = 480*600;
- // memset( data, colour, size );
}
std::ostream &operator<<(std::ostream &out, const Context &c)
{
- out << "x:" << c.x << "y:" << c.y << "w:" << c.w << "h:" << c.h << std::endl;
+ out << "w:" << c.w << "h:" << c.h << std::endl;
- uint32_t offset = 0;
- for (uint32_t y = 0; y < c.h; y++) {
- for (uint32_t x = 0; x < c.w; x++) {
- uint32_t value = *(c.data + offset);
+ std::uint32_t offset = 0;
+ for (std::uint32_t y = 0; y < c.h; y++) {
+ for (std::uint32_t x = 0; x < c.w; x++) {
+ std::uint32_t value = *(c.data.get() + offset);
std::cout << std::hex << value;
offset++;
}
M module-gui/gui/core/Context.hpp => module-gui/gui/core/Context.hpp +50 -36
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
/*
@@ 11,83 11,97 @@
#ifndef GUI_CORE_CONTEXT_HPP_
#define GUI_CORE_CONTEXT_HPP_
+#include "BoundingBox.hpp"
+
+#include "module-gui/gui/Common.hpp"
+
#include <cstdint>
+#include <deque>
#include <iostream>
-#include "module-gui/gui/Common.hpp"
+#include <memory>
namespace gui
{
-
class Context
{
- protected:
- int16_t x, y;
- uint32_t w, h;
- uint8_t *data;
+ static constexpr std::uint8_t clearColor = 15;
public:
- Context();
- Context(uint16_t width, uint16_t height);
- virtual ~Context();
+ Context() = default;
+ Context(std::uint16_t width, std::uint16_t height);
/**
- * @brief Creates new context using provided coordinates. If there is no common part Context with 0 width and 0
- * height is returned.
+ * @brief Creates new context using provided coordinates. If there is no common part Context filled with
+ * clearColor is returned. The size of the returned context is defiend by parameters, so it may represent
+ * area outside the original context.
*/
- Context *get(int16_t gx, int16_t gy, uint16_t width, uint16_t height);
+ Context get(std::int16_t gx, std::int16_t gy, std::uint16_t width, std::uint16_t height) const;
/**
* @brief Pastes provided context into current one. Overlapping content will be inserted into current context.
*/
- void insert(int16_t ix, int16_t iy, Context *context);
+ void insert(std::int16_t ix, std::int16_t iy, const Context &context);
/**
* @brief Pastes provided context into current one. Overlapping content will be inserted into current context.
*/
- void insertArea(
- int16_t ix, int16_t iy, int16_t iareaX, int16_t iareaY, int16_t iareaW, int16_t iareaH, Context *context);
+ void insertArea(std::int16_t ix,
+ std::int16_t iy,
+ std::int16_t iareaX,
+ std::int16_t iareaY,
+ std::int16_t iareaW,
+ std::int16_t iareaH,
+ const Context &context);
+
+ /**
+ * @brief Calculate regions of difference between contexts. Each bounding box covers the whole width of the
+ * context. They are disjoint and sorted by y coordinate. The contexts has to have the same sizes.
+ */
+ static std::deque<BoundingBox> linesDiffs(const gui::Context &ctx1, const gui::Context &ctx2);
+
/**
* @brief Fills whole context with specified colour;
*/
- void fill(uint8_t colour);
+ void fill(std::uint8_t colour);
/**
* @brief returns pointer to context's data;
*/
- inline const uint8_t *getData() const
- {
- return data;
- }
- inline uint8_t *getData()
- {
- return data;
- }
- inline int16_t getX() const
+ inline const std::uint8_t *getData() const
{
- return x;
+ return data.get();
}
- inline int16_t getY() const
+ inline std::uint8_t *getData()
{
- return y;
+ return data.get();
}
- inline uint16_t getW() const
+ inline std::uint16_t getW() const
{
return w;
}
- inline uint16_t getH() const
+ inline std::uint16_t getH() const
{
return h;
}
+ inline BoundingBox getBoundingBox() const
+ {
+ return {0, 0, w, h};
+ }
- inline bool addressInData(const uint8_t *ptr) const
+ inline std::uint8_t getPixel(const Point point, uint8_t defaultColor = clearColor) const
{
- return (ptr >= data) && (ptr < data + w * h);
+ return hasPixel(point) ? data[point.y * w + point.x] : defaultColor;
}
- inline bool addressInData(const Point point) const noexcept
+ inline bool hasPixel(const Point point) const noexcept
{
- return (point.x >= 0 && static_cast<uint32_t>(point.x) <= w) &&
- (point.y >= 0 && static_cast<uint32_t>(point.y) <= h);
+ return (point.x >= 0 && static_cast<std::uint32_t>(point.x) < w) &&
+ (point.y >= 0 && static_cast<std::uint32_t>(point.y) < h);
}
friend std::ostream &operator<<(std::ostream &out, const Context &c);
+
+ private:
+ std::uint32_t w = 0;
+ std::uint32_t h = 0;
+ std::unique_ptr<uint8_t[]> data;
};
} /* namespace gui */
M module-gui/gui/core/DrawCommand.cpp => module-gui/gui/core/DrawCommand.cpp +15 -18
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "DrawCommand.hpp"
@@ 56,8 56,9 @@ namespace gui
return;
}
+ Context tempContext;
Context *drawingContext = ctx;
- Point position;
+ Point position(0, 0);
if (yaps & (RectangleYap::BottomLeft | RectangleYap::TopLeft)) {
position.x += yapSize;
@@ 76,7 77,8 @@ namespace gui
else {
const auto xCtx = areaX < 0 ? origin.x + areaX : origin.x;
const auto yCtx = areaY < 0 ? origin.y + areaY : origin.y;
- drawingContext = ctx->get(xCtx, yCtx, areaW, areaH);
+ tempContext = ctx->get(xCtx, yCtx, areaW, areaH);
+ drawingContext = &tempContext;
}
if (radius == 0) {
@@ 89,8 91,7 @@ namespace gui
}
if (drawingContext != ctx) {
- ctx->insertArea(origin.x, origin.y, areaX, areaY, width, height, drawingContext);
- delete drawingContext;
+ ctx->insertArea(origin.x, origin.y, areaX, areaY, width, height, tempContext);
}
}
@@ 116,7 117,7 @@ namespace gui
for (position.y = glyphOrigin.y - glyph->yoffset; position.y < glyphMaxY; ++position.y) {
for (position.x = glyphOrigin.x; position.x < glyphMaxX; ++position.x) {
- if (!ctx->addressInData(position)) {
+ if (!ctx->hasPixel(position)) {
log_warn_glyph(
"drawing out of: {x=%d,y=%d} vs {w=%d,h=%d}", position.x, position.y, ctx->getW(), ctx->getH());
return;
@@ 138,18 139,17 @@ namespace gui
}
// get copy of original context using x,y of draw coordinates and original size of the widget
- Context *drawCtx;
+ Context ctxCopy;
bool copyContext = false;
Point widgetOrigin = {0, 0};
// check if there is a need of making copy of context to use it as background
if ((areaW == width) && (areaH == height)) {
- drawCtx = ctx;
widgetOrigin = origin;
}
else {
copyContext = true;
- drawCtx = ctx->get(origin.x, origin.y, areaW, areaH);
+ ctxCopy = ctx->get(origin.x, origin.y, areaW, areaH);
}
// retrieve font used to draw text
@@ 164,6 164,7 @@ namespace gui
FontGlyph *glyph = font->getGlyph(idCurrent);
// do not start drawing outside of draw context.
+ Context *drawCtx = copyContext ? &ctxCopy : ctx;
if ((widgetOrigin.x + position.x + glyph->xoffset >= drawCtx->getW()) ||
(widgetOrigin.x + position.x + glyph->xoffset < 0)) {
LOG_FATAL("Drawing outside context's X boundary for glyph: %" PRIu32, glyph->id);
@@ 188,10 189,9 @@ namespace gui
}
// if drawing was performed in temporary context
- // reinsert drawCtx into bast context
+ // reinsert drawCtx into base context
if (copyContext) {
- ctx->insert(origin.x, origin.y, drawCtx);
- delete drawCtx;
+ ctx->insert(origin.x, origin.y, ctxCopy);
}
}
@@ 269,23 269,20 @@ namespace gui
}
// get copy of original context using x,y of draw coordinates and original size of the widget
- Context *drawCtx = ctx->get(origin.x, origin.y, areaW, areaH);
+ Context drawCtx = ctx->get(origin.x, origin.y, areaW, areaH);
if (imageMap->getType() == gui::ImageMap::Type::PIXMAP) {
auto pixMap = dynamic_cast<PixMap *>(imageMap);
assert(pixMap);
- drawPixMap(drawCtx, pixMap);
+ drawPixMap(&drawCtx, pixMap);
}
else if (imageMap->getType() == gui::ImageMap::Type::VECMAP) {
auto vecMap = dynamic_cast<VecMap *>(imageMap);
assert(vecMap);
- drawVecMap(drawCtx, vecMap);
+ drawVecMap(&drawCtx, vecMap);
}
// reinsert drawCtx into bast context
ctx->insert(origin.x, origin.y, drawCtx);
-
- // remove draw context
- delete drawCtx;
}
} /* namespace gui */
M module-gui/gui/core/ImageManager.cpp => module-gui/gui/core/ImageManager.cpp +22 -26
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "ImageManager.hpp"
@@ 176,31 176,27 @@ namespace gui
// Creation of square with crossed lines as fallback image
constexpr auto squareWidth = 15;
- std::list<std::unique_ptr<gui::DrawCommand>> commands;
- auto rectangle = std::make_unique<DrawRectangle>();
- rectangle->origin = {0, 0};
- rectangle->width = squareWidth;
- rectangle->height = squareWidth;
- rectangle->areaX = 0;
- rectangle->areaY = 0;
- rectangle->areaW = squareWidth;
- rectangle->areaH = squareWidth;
- commands.emplace_back(std::move(rectangle));
-
- auto line1 = std::make_unique<DrawLine>();
- line1->start = {0, 0};
- line1->end = {squareWidth, squareWidth};
- commands.emplace_back(std::move(line1));
-
- auto line2 = std::make_unique<DrawLine>();
- line2->start = {squareWidth - 1, 0};
- line2->end = {0, squareWidth - 1};
- commands.emplace_back(std::move(line2));
-
- auto renderContext = std::make_unique<Context>(squareWidth, squareWidth);
- Renderer().render(renderContext.get(), commands);
-
- return new PixMap(squareWidth, squareWidth, renderContext->getData());
+ DrawRectangle rectangle;
+ rectangle.origin = {0, 0};
+ rectangle.width = squareWidth;
+ rectangle.height = squareWidth;
+ rectangle.areaX = 0;
+ rectangle.areaY = 0;
+ rectangle.areaW = squareWidth;
+ rectangle.areaH = squareWidth;
+
+ DrawLine line1;
+ line1.start = {0, 0};
+ line1.end = {squareWidth, squareWidth};
+
+ DrawLine line2;
+ line2.start = {squareWidth - 1, 0};
+ line2.end = {0, squareWidth - 1};
+
+ Context renderContext(squareWidth, squareWidth);
+ Renderer().render(renderContext, rectangle, line1, line2);
+
+ return new PixMap(squareWidth, squareWidth, renderContext.getData());
}
auto ImageManager::getImageMapList(std::string ext1, std::string ext2)
M module-gui/gui/core/RawFont.cpp => module-gui/gui/core/RawFont.cpp +13 -15
@@ 239,24 239,22 @@ namespace gui
unsupported->xadvance =
unsupported->width + (2 * unsupported->xoffset); // use xoffset as margins on the left/right of the glyph
// populate with a bitmap (glyph)
- auto commandRect = std::make_unique<DrawRectangle>();
- commandRect->origin = {0, 0};
- commandRect->width = unsupported->width;
- commandRect->height = unsupported->height;
- commandRect->areaX = 0;
- commandRect->areaY = 0;
- commandRect->areaW = unsupported->width;
- commandRect->areaH = unsupported->height;
- commandRect->penWidth = unsupported->xoffset;
-
- auto renderCtx = std::make_unique<Context>(unsupported->width, unsupported->height);
- std::list<std::unique_ptr<gui::DrawCommand>> commands;
- commands.emplace_back(std::move(commandRect));
- Renderer().render(renderCtx.get(), commands);
+ DrawRectangle commandRect;
+ commandRect.origin = {0, 0};
+ commandRect.width = unsupported->width;
+ commandRect.height = unsupported->height;
+ commandRect.areaX = 0;
+ commandRect.areaY = 0;
+ commandRect.areaW = unsupported->width;
+ commandRect.areaH = unsupported->height;
+ commandRect.penWidth = unsupported->xoffset;
+
+ Context renderCtx(unsupported->width, unsupported->height);
+ Renderer().render(renderCtx, commandRect);
auto size = unsupported->width * unsupported->height;
unsupported->data = new uint8_t[size];
- std::memcpy(unsupported->data, renderCtx->getData(), size);
+ std::memcpy(unsupported->data, renderCtx.getData(), size);
}
void RawFont::setFallbackFont(RawFont *fallback)
M module-gui/gui/core/Renderer.cpp => module-gui/gui/core/Renderer.cpp +3 -3
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
// for memset
@@ 10,12 10,12 @@
namespace gui
{
- void Renderer::changeColorScheme(const std::unique_ptr<ColorScheme> &scheme)
+ void Renderer::changeColorScheme(const std::unique_ptr<ColorScheme> &scheme) const
{
renderer::PixelRenderer::updateColorScheme(scheme);
}
- void Renderer::render(Context *ctx, std::list<std::unique_ptr<DrawCommand>> &commands)
+ void Renderer::render(Context *ctx, const std::list<std::unique_ptr<DrawCommand>> &commands) const
{
if (ctx == nullptr) {
return;
M module-gui/gui/core/Renderer.hpp => module-gui/gui/core/Renderer.hpp +9 -5
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 34,10 34,14 @@ namespace gui
*/
public:
- virtual ~Renderer() = default;
-
- void render(Context *ctx, std::list<std::unique_ptr<DrawCommand>> &commands);
- void changeColorScheme(const std::unique_ptr<ColorScheme> &scheme);
+ void changeColorScheme(const std::unique_ptr<ColorScheme> &scheme) const;
+ void render(Context *ctx, const std::list<std::unique_ptr<DrawCommand>> &commands) const;
+
+ template <typename... Commands>
+ void render(Context &ctx, const Commands &...commands) const
+ {
+ (..., commands.draw(&ctx));
+ }
};
} /* namespace gui */
M module-gui/gui/core/renderers/RectangleRenderer.cpp => module-gui/gui/core/renderers/RectangleRenderer.cpp +2 -15
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "RectangleRenderer.hpp"
@@ 13,19 13,6 @@
namespace gui::renderer
{
- namespace
- {
- std::uint8_t getPixelColor(Context *ctx, const Point &point, std::uint8_t defaultColor)
- {
- const auto contextWidth = ctx->getW();
- const auto position = point.y * contextWidth + point.x;
- if (!ctx->addressInData(ctx->getData() + position)) {
- return defaultColor;
- }
- return *(ctx->getData() + position);
- }
- } // namespace
-
auto RectangleRenderer::DrawableStyle::from(const DrawRectangle &command) -> DrawableStyle
{
return DrawableStyle{command.penWidth,
@@ 183,7 170,7 @@ namespace gui::renderer
while (!q.empty()) {
const auto currPoint = q.front();
q.pop();
- if (const auto color = getPixelColor(ctx, currPoint, PixelRenderer::getColor(borderColor.intensity));
+ if (const auto color = ctx->getPixel(currPoint, PixelRenderer::getColor(borderColor.intensity));
color == PixelRenderer::getColor(borderColor.intensity) ||
color == PixelRenderer::getColor(fillColor.intensity)) {
continue;
M module-gui/gui/widgets/Rect.cpp => module-gui/gui/widgets/Rect.cpp +1 -1
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
/*
M module-gui/test/test-catch/test-context.cpp => module-gui/test/test-catch/test-context.cpp +37 -51
@@ 14,77 14,63 @@
TEST_CASE("test context size and position")
{
- auto ctx = new gui::Context(30, 30);
- ctx->fill(0);
+ auto ctx = gui::Context(30, 30);
+ ctx.fill(0);
- gui::Context *test = ctx->get(17, 17, 30, 30);
+ gui::Context test = ctx.get(17, 17, 30, 30);
- delete test;
- test = ctx->get(-17, -17, 30, 30);
- delete test;
-
- delete ctx;
+ test = ctx.get(-17, -17, 30, 30);
}
TEST_CASE("insertContextTest")
{
- gui::Context *dstCtx;
- gui::Context *insCtx;
+ gui::Context dstCtx;
+ gui::Context insCtx;
SECTION("RECTANGLE INSIDE")
{
- dstCtx = new gui::Context(30, 30);
- dstCtx->fill(0);
- insCtx = new gui::Context(static_cast<unsigned short>(28), static_cast<unsigned short>(28));
- insCtx->fill(15);
- dstCtx->insert(1, 1, insCtx);
- delete dstCtx;
- delete insCtx;
+ dstCtx = gui::Context(30, 30);
+ dstCtx.fill(0);
+ insCtx = gui::Context(28, 28);
+ insCtx.fill(15);
+ dstCtx.insert(1, 1, insCtx);
}
SECTION("2 COLUMNS ON RIGHT SIDE, TOP AND BOTTOM ROW UNTOUCHED")
{
- dstCtx = new gui::Context(static_cast<unsigned short>(30), static_cast<unsigned short>(30));
- dstCtx->fill(0);
- insCtx = new gui::Context(static_cast<unsigned short>(28), static_cast<unsigned short>(28));
- insCtx->fill(15);
- dstCtx->insert(28, 1, insCtx);
- delete dstCtx;
- delete insCtx;
+ dstCtx = gui::Context(30, 30);
+ dstCtx.fill(0);
+ insCtx = gui::Context(28, 28);
+ insCtx.fill(15);
+ dstCtx.insert(28, 1, insCtx);
}
SECTION("2 COLUMNS ON LEFT SIDE, TOP AND BOTTOM ROW UNTOUCHED")
{
- dstCtx = new gui::Context(static_cast<unsigned short>(30), static_cast<unsigned short>(30));
- dstCtx->fill(0);
- insCtx = new gui::Context(static_cast<unsigned short>(28), static_cast<unsigned short>(28));
- insCtx->fill(15);
- dstCtx->insert(-26, 1, insCtx);
- delete dstCtx;
- delete insCtx;
+ dstCtx = gui::Context(30, 30);
+ dstCtx.fill(0);
+ insCtx = gui::Context(28, 28);
+ insCtx.fill(15);
+ dstCtx.insert(-26, 1, insCtx);
}
SECTION("2 COLUMNS ON RIGHT SIDE")
{
- dstCtx = new gui::Context(static_cast<unsigned short>(30), static_cast<unsigned short>(30));
- dstCtx->fill(0);
- insCtx = new gui::Context(static_cast<unsigned short>(32), static_cast<unsigned short>(32));
- insCtx->fill(15);
- dstCtx->insert(28, -1, insCtx);
- delete dstCtx;
- delete insCtx;
+ dstCtx = gui::Context(30, 30);
+ dstCtx.fill(0);
+ insCtx = gui::Context(32, 32);
+ insCtx.fill(15);
+ dstCtx.insert(28, -1, insCtx);
}
SECTION("2 COLUMNS ON LEFT SIDE")
{
- dstCtx = new gui::Context(static_cast<unsigned short>(30), static_cast<unsigned short>(30));
- dstCtx->fill(0);
- insCtx = new gui::Context(static_cast<unsigned short>(32), static_cast<unsigned short>(32));
- insCtx->fill(15);
- dstCtx->insert(-30, -1, insCtx);
- delete dstCtx;
- delete insCtx;
+ dstCtx = gui::Context(30, 30);
+ dstCtx.fill(0);
+ insCtx = gui::Context(32, 32);
+ insCtx.fill(15);
+ dstCtx.insert(-30, -1, insCtx);
}
}
@@ 124,7 110,7 @@ TEST_CASE("Draw vector image test")
"ffffffffffffffffffffffffffffffff\n"
"ffffffffffffffffffffffffffffffff\n";
- auto context = std::make_unique<gui::Context>(static_cast<unsigned short>(32), static_cast<unsigned short>(32));
+ auto context = gui::Context(32, 32);
gui::ImageManager::getInstance().init(".");
auto id = gui::ImageManager::getInstance().getImageMapID("plus_32px_W_M");
@@ 138,13 124,13 @@ TEST_CASE("Draw vector image test")
drawCommand.imageID = id;
drawCommand.areaH = 32;
drawCommand.areaW = 32;
- drawCommand.draw(context.get());
+ drawCommand.draw(&context);
std::string dump;
- uint32_t offset = 0;
- for (uint32_t y = 0; y < context->getH(); y++) {
- for (uint32_t x = 0; x < context->getW(); x++) {
- uint32_t value = *(context->getData() + offset);
+ std::uint32_t offset = 0;
+ for (std::uint32_t y = 0; y < context.getH(); y++) {
+ for (std::uint32_t x = 0; x < context.getW(); x++) {
+ std::uint32_t value = *(context.getData() + offset);
std::stringstream stream;
stream << std::hex << value;
dump.append(stream.str());
M module-services/service-eink/ServiceEink.cpp => module-services/service-eink/ServiceEink.cpp +130 -4
@@ 175,7 175,78 @@ namespace service::eink
display->setMode(hal::eink::EinkDisplayColorMode::EinkDisplayColorModeStandard);
}
internal::StaticData::get().setInvertedMode(invertedModeRequested);
+
+ previousContext.reset();
+ previousRefreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE;
+ }
+
+ // Merge boxes if the gap between them is lesser than the threshold
+ template <typename BoxesContainer>
+ inline std::vector<gui::BoundingBox> mergeBoundingBoxes(const BoxesContainer &boxes, std::uint16_t gapThreshold)
+ {
+ std::vector<gui::BoundingBox> mergedBoxes;
+ if (boxes.empty())
+ return mergedBoxes;
+ mergedBoxes.reserve(boxes.size());
+ gui::BoundingBox merged = boxes.front();
+ for (std::size_t i = 1; i < boxes.size(); ++i) {
+ const auto &bb = boxes[i];
+ const auto gap = bb.y - (merged.y + merged.h);
+ if (gap < gapThreshold) {
+ merged.h = (bb.y + bb.h) - merged.y;
+ }
+ else {
+ mergedBoxes.push_back(merged);
+ merged = bb;
+ }
+ }
+ mergedBoxes.push_back(merged);
+ return mergedBoxes;
+ }
+
+ // Enlarge each box to match alignment-wide grid in y coordinate
+ template <typename BoxesContainer>
+ inline std::vector<hal::eink::EinkFrame> makeAlignedFrames(const BoxesContainer &boxes, std::uint16_t alignment)
+ {
+ std::vector<hal::eink::EinkFrame> frames;
+ if (boxes.empty())
+ return frames;
+ frames.reserve(boxes.size());
+ auto a = alignment;
+ for (const auto &bb : boxes) {
+ auto f = hal::eink::EinkFrame{
+ std::uint16_t(bb.x), std::uint16_t(bb.y), {std::uint16_t(bb.w), std::uint16_t(bb.h)}};
+ auto y = f.pos_y;
+ auto h = f.size.height;
+ f.pos_y = y / a * a;
+ f.size.height = (y - f.pos_y + h + (a - 1)) / a * a;
+ frames.push_back(f);
+ }
+ return frames;
+ }
+
+#if DEBUG_EINK_REFRESH == 1
+ inline std::string debug_toString(const gui::BoundingBox &bb)
+ {
+ return bb.str();
+ }
+
+ inline std::string debug_toString(const hal::eink::EinkFrame &f)
+ {
+ std::stringstream ss;
+ ss << '{' << f.pos_x << ' ' << f.pos_y << ' ' << f.size.width << ' ' << f.size.height << "}";
+ return ss.str();
+ }
+
+ template <typename Container>
+ inline void debug_handleImageMessage(const char *name, const Container &container)
+ {
+ std::stringstream ss;
+ for (auto const &el : container)
+ ss << debug_toString(el) << ' ';
+ LOG_INFO("%s: %s", name, ss.str().c_str());
}
+#endif
sys::MessagePointer ServiceEink::handleImageMessage(sys::Message *request)
{
@@ 189,11 260,66 @@ namespace service::eink
displayPowerOffTimer.stop();
auto displayPowerOffTimerReload = gsl::finally([this]() { displayPowerOffTimer.start(); });
- eInkSentinel->HoldMinimumFrequency();
- auto status = display->showImage(message->getData(), translateToEinkRefreshMode(message->getRefreshMode()));
- if (status != hal::eink::EinkStatus::EinkOK) {
- LOG_ERROR("Error during drawing image on eink: %s", magic_enum::enum_name(status).data());
+ const gui::Context &ctx = *message->getContext();
+ auto refreshMode = translateToEinkRefreshMode(message->getRefreshMode());
+
+ // Get areas changed since last update
+ std::vector<hal::eink::EinkFrame> updateFrames;
+ if (!previousContext) {
+ updateFrames = {{0, 0, {ctx.getW(), ctx.getH()}}};
+
+ previousContext.reset(new gui::Context(ctx.get(0, 0, ctx.getW(), ctx.getH())));
+ }
+ else if (refreshMode > previousRefreshMode) {
+ updateFrames = {{0, 0, {ctx.getW(), ctx.getH()}}};
+
+ previousContext->insert(0, 0, ctx);
}
+ else {
+ // Each bounding box cover the whole width of the context. They are disjoint and sorted by y coordinate.
+ const auto diffBoundingBoxes = gui::Context::linesDiffs(ctx, *previousContext);
+ if (!diffBoundingBoxes.empty()) {
+ const std::uint16_t gapThreshold = ctx.getH() / 4;
+ const std::uint16_t alignment = 8;
+
+ const auto mergedBoxes = mergeBoundingBoxes(diffBoundingBoxes, gapThreshold);
+ updateFrames = makeAlignedFrames(mergedBoxes, alignment);
+
+#if DEBUG_EINK_REFRESH == 1
+ debug_handleImageMessage("Boxes", diffBoundingBoxes);
+ debug_handleImageMessage("Merged boxes", mergedBoxes);
+#endif
+
+ previousContext->insert(0, 0, ctx);
+ }
+ }
+
+ if (!updateFrames.empty()) {
+ hal::eink::EinkFrame refreshFrame;
+ refreshFrame = updateFrames.front();
+ refreshFrame.size.height =
+ updateFrames.back().pos_y + updateFrames.back().size.height - updateFrames.front().pos_y;
+
+ eInkSentinel->HoldMinimumFrequency();
+
+ const auto status = display->showImage(updateFrames, refreshFrame, ctx.getData(), refreshMode);
+ if (status != hal::eink::EinkStatus::EinkOK) {
+ previousContext.reset();
+ refreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE;
+ LOG_ERROR("Error during drawing image on eink: %s", magic_enum::enum_name(status).data());
+ }
+
+#if DEBUG_EINK_REFRESH == 1
+ debug_handleImageMessage("Frames", updateFrames);
+ debug_handleImageMessage("Refresh frame", std::vector<hal::eink::EinkFrame>({refreshFrame}));
+#endif
+ }
+
+#if DEBUG_EINK_REFRESH == 1
+ LOG_INFO("Refresh context: %d %d, mode: %d", (int)ctx.getW(), (int)ctx.getH(), (int)refreshMode);
+#endif
+
+ previousRefreshMode = refreshMode;
return std::make_shared<service::eink::ImageDisplayedNotification>(message->getContextId());
}
M module-services/service-eink/ServiceEink.hpp => module-services/service-eink/ServiceEink.hpp +3 -0
@@ 3,6 3,7 @@
#pragma once
+#include <module-gui/gui/core/Context.hpp>
#include <system/Common.hpp>
#include <Service/Message.hpp>
#include <Service/Service.hpp>
@@ 68,6 69,8 @@ namespace service::eink
sys::TimerHandle displayPowerOffTimer;
std::shared_ptr<EinkSentinel> eInkSentinel;
std::unique_ptr<settings::Settings> settings;
+ std::unique_ptr<::gui::Context> previousContext;
+ hal::eink::EinkRefreshMode previousRefreshMode = hal::eink::EinkRefreshMode::REFRESH_NONE;
};
} // namespace service::eink
M module-services/service-eink/messages/ImageMessage.cpp => module-services/service-eink/messages/ImageMessage.cpp +2 -2
@@ 10,9 10,9 @@ namespace service::eink
: contextId{contextId}, context{context}, refreshMode{refreshMode}
{}
- auto ImageMessage::getData() noexcept -> std::uint8_t *
+ auto ImageMessage::getContext() noexcept -> ::gui::Context *
{
- return context->getData();
+ return context;
}
auto ImageMessage::getRefreshMode() const noexcept -> ::gui::RefreshModes
M module-services/service-eink/messages/ImageMessage.hpp => module-services/service-eink/messages/ImageMessage.hpp +2 -2
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 17,7 17,7 @@ namespace service::eink
ImageMessage(int contextId, ::gui::Context *context, ::gui::RefreshModes refreshMode);
[[nodiscard]] auto getContextId() const noexcept -> int;
- [[nodiscard]] auto getData() noexcept -> std::uint8_t *;
+ [[nodiscard]] auto getContext() noexcept -> ::gui::Context *;
[[nodiscard]] auto getRefreshMode() const noexcept -> ::gui::RefreshModes;
private:
M module-utils/log/api/log/debug.hpp => module-utils/log/api/log/debug.hpp +7 -6
@@ 4,19 4,20 @@
#pragma once
#define DEBUG_APPLICATION_MANAGEMENT 0 /// show verbose logs in ApplicationManager
-#define DEBUG_SCOPED_TIMINGS 0 /// show timings in measured functions
-#define DEBUG_CELLULAR_UART 0 /// show full modem uart communication
#define DEBUG_BLUETOOTH_HCI_COMS 0 /// show communication with BT module - transactions
#define DEBUG_BLUETOOTH_HCI_BYTES 0 /// show communication with BT module - all the HCI bytes
-#define DEBUG_SERVICE_MESSAGES 0 /// show messages prior to handling in service
+#define DEBUG_CELLULAR_UART 0 /// show full modem uart communication
#define DEBUG_DB_MODEL_DATA 0 /// show messages prior to handling in service
-#define DEBUG_SIM_IMPORT_DATA 0 /// show messages connected to sim data imports
+#define DEBUG_EINK_REFRESH 0 /// show bounding boxes and frames in partial refresh
#define DEBUG_FONT 0 /// show Font debug messages
#define DEBUG_GUI_TEXT 0 /// show basic debug messages for gui::Text - warning this can be hard on cpu
#define DEBUG_GUI_TEXT_LINES 0 /// show extended debug messages for gui::Text - lines building
#define DEBUG_GUI_TEXT_CURSOR 0 /// show extended debug messages for gui::Text - cursor handling
#define DEBUG_INPUT_EVENTS 0 /// show input events prints in system
-#define DEBUG_TIMER 0 /// debug timers system utility
+#define DEBUG_MISSING_ASSETS 0 /// show debug concerning missing assets
+#define DEBUG_SCOPED_TIMINGS 0 /// show timings in measured functions
#define DEBUG_SETTINGS_DB 0 /// show extensive settings logs for all applications
#define DEBUG_SERVICE_CELLULAR 0 /// show various logs in cellular service
-#define DEBUG_MISSING_ASSETS 0 /// show debug concerning missing assets
+#define DEBUG_SERVICE_MESSAGES 0 /// show messages prior to handling in service
+#define DEBUG_SIM_IMPORT_DATA 0 /// show messages connected to sim data imports
+#define DEBUG_TIMER 0 /// debug timers system utility
M pure_changelog.md => pure_changelog.md +1 -0
@@ 6,6 6,7 @@
* Added Polish translation to a calendar title
* Changed USB logging
* Separated system volume from Bluetooth device volume for A2DP
+* Implemented partial refresh of the display
### Fixed
* Fixed mixed SMS messages