~aleteoryx/muditaos

41d6e786b6264925caac34650d512e30b8848f8d — Adam Wulkiewicz 3 years ago 6ebd155
[MOS-550] Implement eink partial refresh

Detect parts of the screen changed since last update and merge them into
bigger regions. These regions defines parts of the context sent to the
display.
Refresh the region covering all of the parts since this is the most time
consuming part and the size of the refreshed region doesn't change the
time much.
Refresh the whole screen if deep refresh is requested and previously
fast refresh was used. This is needed to prevent unwanted artifacts in
some cases.

Refactor some parts of the gui and display code.
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