Files
esphome/tests/components/runtime_image/test_double_buffer.cpp
T

147 lines
5.4 KiB
C++

#include <gtest/gtest.h>
#include <cstdint>
#include <vector>
#include "esphome/components/runtime_image/runtime_image.h"
namespace esphome::runtime_image::testing {
// 3x2 24bpp BMP (rows padded to 4 bytes)
static const uint8_t BMP_3X2[] = {
0x42, 0x4D, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x22, 0x11, 0x77, 0x88, 0x99, 0xEF, 0xCD, 0xAB, 0x00,
0x00, 0x00, 0x20, 0x10, 0xE0, 0x40, 0xC0, 0x30, 0xA0, 0x60, 0x50, 0x00, 0x00, 0x00,
};
// 2x1 24bpp BMP (row padded to 8 bytes)
static const uint8_t BMP_2X1[] = {
0x42, 0x4D, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x06, 0x05, 0x04, 0x00, 0x00,
};
template<size_t N> static void load(RuntimeImage &img, const uint8_t (&bmp)[N]) {
std::vector<uint8_t> data(bmp, bmp + N);
ASSERT_TRUE(img.begin_decode(data.size()));
ASSERT_EQ(img.feed_data(data.data(), data.size()), static_cast<int>(data.size()));
ASSERT_TRUE(img.end_decode());
}
class DoubleBufferedImage : public RuntimeImage {
public:
DoubleBufferedImage() : RuntimeImage(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE) {
this->set_double_buffer(true);
}
};
// The image on show is not decoded over: the new one appears only when it is complete
TEST(RuntimeImageDoubleBuffer, ImageOnShowStaysUntilTheNextIsDecoded) {
DoubleBufferedImage img;
load(img, BMP_3X2);
const uint8_t *first = img.get_data_start();
ASSERT_NE(first, nullptr);
const std::vector<uint8_t> shown(first, first + 3 * 2 * 3);
std::vector<uint8_t> data(BMP_2X1, BMP_2X1 + sizeof(BMP_2X1));
ASSERT_TRUE(img.begin_decode(data.size()));
ASSERT_EQ(img.feed_data(data.data(), data.size()), static_cast<int>(data.size()));
EXPECT_EQ(img.get_data_start(), first);
EXPECT_EQ(img.get_width(), 3);
EXPECT_EQ(img.get_height(), 2);
EXPECT_EQ(std::vector<uint8_t>(first, first + shown.size()), shown);
ASSERT_TRUE(img.end_decode());
EXPECT_NE(img.get_data_start(), first);
EXPECT_EQ(img.get_width(), 2);
EXPECT_EQ(img.get_height(), 1);
}
// Images of one size take turns in the same two buffers
TEST(RuntimeImageDoubleBuffer, SameSizeImagesReuseBothBuffers) {
DoubleBufferedImage img;
load(img, BMP_3X2);
const uint8_t *first = img.get_data_start();
load(img, BMP_3X2);
const uint8_t *second = img.get_data_start();
EXPECT_NE(second, first);
load(img, BMP_3X2);
EXPECT_EQ(img.get_data_start(), first);
load(img, BMP_3X2);
EXPECT_EQ(img.get_data_start(), second);
}
TEST(RuntimeImageDoubleBuffer, AbortedDecodeLeavesTheImageOnShow) {
DoubleBufferedImage img;
load(img, BMP_3X2);
const uint8_t *first = img.get_data_start();
std::vector<uint8_t> data(BMP_2X1, BMP_2X1 + sizeof(BMP_2X1));
ASSERT_TRUE(img.begin_decode(data.size()));
img.feed_data(data.data(), data.size() / 2 + 8);
img.abort_decode();
EXPECT_FALSE(img.is_decoding());
EXPECT_EQ(img.get_data_start(), first);
EXPECT_EQ(img.get_width(), 3);
EXPECT_TRUE(img.is_loaded());
load(img, BMP_2X1);
EXPECT_EQ(img.get_width(), 2);
}
// An aborted first decode leaves nothing on show
TEST(RuntimeImageDoubleBuffer, AbortedFirstDecodeIsNotLoaded) {
DoubleBufferedImage img;
std::vector<uint8_t> data(BMP_3X2, BMP_3X2 + sizeof(BMP_3X2));
ASSERT_TRUE(img.begin_decode(data.size()));
img.feed_data(data.data(), data.size() / 2 + 8);
img.abort_decode();
EXPECT_EQ(img.get_data_start(), nullptr);
EXPECT_FALSE(img.is_loaded());
}
// Without double buffering an aborted decode has drawn over the image, so it goes
TEST(RuntimeImageDoubleBuffer, AbortedDecodeWithOneBufferReleasesTheImage) {
RuntimeImage img(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
load(img, BMP_3X2);
std::vector<uint8_t> data(BMP_3X2, BMP_3X2 + sizeof(BMP_3X2));
ASSERT_TRUE(img.begin_decode(data.size()));
img.feed_data(data.data(), data.size() / 2 + 8);
img.abort_decode();
EXPECT_EQ(img.get_data_start(), nullptr);
EXPECT_FALSE(img.is_loaded());
}
// Both buffers are freed; LeakSanitizer reports one that is not
TEST(RuntimeImageDoubleBuffer, ReleaseFreesBothBuffers) {
DoubleBufferedImage img;
load(img, BMP_3X2);
load(img, BMP_3X2);
img.release();
EXPECT_EQ(img.get_data_start(), nullptr);
EXPECT_EQ(img.get_width(), 0);
EXPECT_FALSE(img.is_loaded());
}
TEST(RuntimeImageDoubleBuffer, ExternalBufferIsRefused) {
DoubleBufferedImage img;
std::vector<uint8_t> buffer(img.get_buffer_size(3, 2));
EXPECT_FALSE(img.set_external_buffer(buffer.data(), 3, 2));
load(img, BMP_3X2);
EXPECT_NE(img.get_data_start(), buffer.data());
}
// Progressive display turns double buffering off, so an external buffer is allowed
TEST(RuntimeImageDoubleBuffer, ExternalBufferIsAllowedWithProgressiveDisplay) {
DoubleBufferedImage img;
img.set_progressive_display(true);
std::vector<uint8_t> buffer(img.get_buffer_size(3, 2));
EXPECT_TRUE(img.set_external_buffer(buffer.data(), 3, 2));
img.release();
}
} // namespace esphome::runtime_image::testing