From bf5e74120fab2f659b451cb49738be1df526632d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 16 Sep 2026 14:21:09 -0500 Subject: [PATCH] [image] Check the rotated benchmark frame against the plain one The benchmark now draws once at each rotation and requires the rotated frame to be the transpose of the plain one before timing, so a wrong traversal fails the run instead of passing silently. --- esphome/components/image/image.cpp | 6 ++--- .../components/image/bench_image_draw.cpp | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/esphome/components/image/image.cpp b/esphome/components/image/image.cpp index c0592b33fe6..2ecbe836b79 100644 --- a/esphome/components/image/image.cpp +++ b/esphome/components/image/image.cpp @@ -6,9 +6,9 @@ namespace esphome::image { // Walks the clipped image range with rows (ROWS_OUTER) or columns as the outer -// loop. The selects fold at compile time and the pixel body must be -// ESPHOME_ALWAYS_INLINE: at -Os GCC otherwise leaves it out of line and calls it -// once per pixel. +// loop. The coordinate selection folds at compile time, and the pixel body must +// be ESPHOME_ALWAYS_INLINE: at -Os GCC otherwise leaves it out of line and calls +// it once per pixel. template static void for_each_pixel(int x_start, int x_end, int y_start, int y_end, F &&f) { const int outer_start = ROWS_OUTER ? y_start : x_start; diff --git a/tests/benchmarks/components/image/bench_image_draw.cpp b/tests/benchmarks/components/image/bench_image_draw.cpp index 83530e0653c..9bab48ae274 100644 --- a/tests/benchmarks/components/image/bench_image_draw.cpp +++ b/tests/benchmarks/components/image/bench_image_draw.cpp @@ -29,8 +29,29 @@ class BenchDisplay : public display::DisplayBuffer { void draw_absolute_pixel_internal(int x, int y, Color color) override { this->frame_[y * kStride + x] = color.r; } std::unique_ptr frame_; + + public: + uint8_t pixel(int x, int y) const { return this->frame_[y * kStride + x]; } }; +// Draws the image at rotation 0 and 90 and checks the second frame is the +// transpose of the first, which is what a wrong traversal order would break. +static bool frames_match(const Image &image) { + BenchDisplay plain; + BenchDisplay rotated; + rotated.set_rotation(display::DISPLAY_ROTATION_90_DEGREES); + const_cast(image).draw(0, 0, &plain, display::COLOR_ON, display::COLOR_OFF); + const_cast(image).draw(0, 0, &rotated, display::COLOR_ON, display::COLOR_OFF); + for (int y = 0; y < kHeight; y++) { + for (int x = 0; x < kWidth; x++) { + // DisplayBuffer maps (x, y) to (width - y - 1, x) at 90 degrees + if (plain.pixel(x, y) != rotated.pixel(kStride - y - 1, x)) + return false; + } + } + return true; +} + // Deterministic pseudo random pixel bytes so the alpha branch sees a mix of // pixels. Sized for the widest format, the image reads only what it needs. static std::unique_ptr make_pixels() { @@ -50,6 +71,10 @@ static void draw_image(benchmark::State &state, ImageType type, Transparency tra Image image(data.get(), kWidth, kHeight, type, transparency); BenchDisplay display; display.set_rotation(rotation); + if (!frames_match(image)) { + state.SkipWithError("rotated draw is not the transpose of the plain draw"); + return; + } for (auto _ : state) { image.draw(0, 0, &display, display::COLOR_ON, display::COLOR_OFF);