/* * This file is part of the ZoneMinder Project. See AUTHORS file for Copyright information * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include "zm_catch2.h" #include "zm_config.h" #include "zm_image.h" #include "zm_rgb.h" #include #include #include // Image::Annotate() draws the monitor's timestamp label onto every captured frame. The label // text, position and size come from monitor fields any monitor editor can set, so no // combination of them may write outside the frame. refs GHSA-j5gf-rfm2-vhw9 // // The fixture font's glyphs are 10x10 at size 1 and 13x13 at size 4. namespace { constexpr uint8_t kFrameFill = 0x55; constexpr uint8_t kGuardFill = 0xAB; constexpr unsigned int kGuardBytes = 4096; void LoadFixtureFont() { // Another test may have initialised Image without a usable font; reload with the fixture. Image::Deinitialise(); config.font_file_location = "data/fonts/04_valid.zmfnt"; Image::Initialise(); } // A frame drawn in place in a caller-owned buffer, followed by a guard region that must // never be written. struct GuardedFrame { unsigned int width; unsigned int height; unsigned int colours; std::vector backing; GuardedFrame(unsigned int w, unsigned int h, unsigned int c) : width(w), height(h), colours(c), backing(w * h * c + kGuardBytes, kGuardFill) { std::fill(backing.begin(), backing.begin() + FrameBytes(), kFrameFill); } unsigned int FrameBytes() const { return width * height * colours; } Image MakeImage() { int order = ZM_SUBPIX_ORDER_NONE; if (colours == ZM_COLOUR_RGB24) order = ZM_SUBPIX_ORDER_RGB; if (colours == ZM_COLOUR_RGB32) order = ZM_SUBPIX_ORDER_RGBA; return Image(static_cast(width), static_cast(height), static_cast(colours), order, backing.data(), 0u); } bool GuardIntact() const { return std::all_of(backing.begin() + FrameBytes(), backing.end(), [](uint8_t b) { return b == kGuardFill; }); } bool PixelUntouched(unsigned int x, unsigned int y) const { const uint8_t *p = &backing[(y * width + x) * colours]; return std::all_of(p, p + colours, [](uint8_t b) { return b == kFrameFill; }); } bool AnythingDrawn() const { return std::any_of(backing.begin(), backing.begin() + FrameBytes(), [](uint8_t b) { return b != kFrameFill; }); } }; } // namespace TEST_CASE("Image::Annotate keeps a label wider than the frame inside the buffer", "[Image][Annotate]") { LoadFixtureFont(); const unsigned int colours = GENERATE(ZM_COLOUR_GRAY8, ZM_COLOUR_RGB24, ZM_COLOUR_RGB32); // 29 characters at 10 px is 290 px, far wider than the 64 px frame. Before the fix the // unsigned clamp bound wrapped and the requested X position was used unchanged. GuardedFrame frame(64, 24, colours); Image image = frame.MakeImage(); image.Annotate("Front Door - 26/10/08 12:00:00", Vector2(10000, 10000), 1); REQUIRE(frame.GuardIntact()); REQUIRE(frame.AnythingDrawn()); } TEST_CASE("Image::Annotate does not draw a glyph that would cross the right edge", "[Image][Annotate]") { LoadFixtureFont(); const unsigned int colours = GENERATE(ZM_COLOUR_GRAY8, ZM_COLOUR_RGB24, ZM_COLOUR_RGB32); // 35 px wide: glyphs fit at x = 0, 10 and 20; a fourth at x = 30 would spill into the next // row and, on the bottom row, past the end of the frame. GuardedFrame frame(35, 12, colours); Image image = frame.MakeImage(); image.Annotate("ABCDEFGH", Vector2(0, 0), 1); REQUIRE(frame.GuardIntact()); for (unsigned int y = 0; y < frame.height; y++) { for (unsigned int x = 30; x < frame.width; x++) { INFO("x=" << x << " y=" << y); REQUIRE(frame.PixelUntouched(x, y)); } } } TEST_CASE("Image::Annotate keeps a label taller than the frame inside the buffer", "[Image][Annotate]") { LoadFixtureFont(); const unsigned int colours = GENERATE(ZM_COLOUR_GRAY8, ZM_COLOUR_RGB24, ZM_COLOUR_RGB32); // Three 13 px lines in a 16 px frame: y's clamp bound wrapped the same way as x's. GuardedFrame frame(64, 16, colours); Image image = frame.MakeImage(); image.Annotate("AB\nCD\nEF", Vector2(10000, 10000), 4); REQUIRE(frame.GuardIntact()); REQUIRE(frame.AnythingDrawn()); } TEST_CASE("Image::Annotate places a fitting label at the far corner when asked past it", "[Image][Annotate]") { LoadFixtureFont(); GuardedFrame frame(64, 24, ZM_COLOUR_RGB32); Image image = frame.MakeImage(); image.Annotate("AB", Vector2(10000, 10000), 1); REQUIRE(frame.GuardIntact()); // Two 10x10 glyphs pushed into the bottom-right corner cover x 44..63, y 14..23. REQUIRE_FALSE(frame.PixelUntouched(44, 14)); REQUIRE_FALSE(frame.PixelUntouched(63, 23)); REQUIRE(frame.PixelUntouched(43, 23)); REQUIRE(frame.PixelUntouched(63, 13)); } TEST_CASE("Image::Annotate accepts an out-of-range label size", "[Image][Annotate]") { LoadFixtureFont(); const unsigned int size = GENERATE(0u, 5u, 255u); GuardedFrame frame(64, 24, ZM_COLOUR_RGB32); Image image = frame.MakeImage(); REQUIRE_NOTHROW(image.Annotate("AB", Vector2(0, 0), static_cast(size))); REQUIRE_NOTHROW(image.centreCoord("AB", static_cast(size))); REQUIRE(frame.GuardIntact()); }