/*
* 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
// get_y_image() wraps a decoder Y plane whose real stride (linesize[0]) can be
// smaller than FFALIGN(width,32). Image::Rotate/Flip must use the source
// Image's own linesize, not a re-derived 32-aligned stride, or they read past
// the end of the borrowed plane (crash) and skew the output. Width 15 is
// deliberately not a multiple of 32 (FFALIGN(15,32)=32) and the source is
// packed tight (linesize == width), exactly like the decoder's Y plane.
// width*height=150 keeps every pixel value distinct in a byte so any stride
// drift is detectable.
namespace {
constexpr unsigned int kW = 15;
constexpr unsigned int kH = 10;
// Image::Initialise() dereferences config.font_file_location, which is null in
// the unit-test harness (no zm.conf loaded). Give it a non-null value so the
// first Image construction doesn't throw before exercising the rotate/flip code.
void EnsureImageInit() {
if (!config.font_file_location) config.font_file_location = "";
}
// Backing allocation larger than the tight 15x10 plane so that a regression
// (an over-read using stride FFALIGN(15,32)=32) stays inside our allocation and
// corrupts the result rather than segfaulting the test runner.
std::vector MakeTightGray8Plane() {
std::vector backing(32 * kH + 64, 0);
for (unsigned int y = 0; y < kH; y++)
for (unsigned int x = 0; x < kW; x++)
backing[y * kW + x] = static_cast(y * kW + x);
return backing;
}
} // namespace
TEST_CASE("Image::Rotate 180 honors a non-32-aligned source linesize", "[Image]") {
EnsureImageInit();
std::vector backing = MakeTightGray8Plane();
Image img(kW, /*linesize*/ kW, kH, /*colours*/ 1, ZM_SUBPIX_ORDER_NONE, backing.data(), /*padding*/ 0);
img.Rotate(180);
REQUIRE(img.Width() == kW);
REQUIRE(img.Height() == kH);
for (unsigned int y = 0; y < kH; y++) {
for (unsigned int x = 0; x < kW; x++) {
const uint8_t expected = static_cast((kH - 1 - y) * kW + (kW - 1 - x));
REQUIRE(*img.Buffer(x, y) == expected);
}
}
}
TEST_CASE("Image::Flip horizontal honors a non-32-aligned source linesize", "[Image]") {
EnsureImageInit();
std::vector backing = MakeTightGray8Plane();
Image img(kW, /*linesize*/ kW, kH, /*colours*/ 1, ZM_SUBPIX_ORDER_NONE, backing.data(), /*padding*/ 0);
img.Flip(true);
REQUIRE(img.Width() == kW);
REQUIRE(img.Height() == kH);
for (unsigned int y = 0; y < kH; y++) {
for (unsigned int x = 0; x < kW; x++) {
const uint8_t expected = static_cast(y * kW + (kW - 1 - x));
REQUIRE(*img.Buffer(x, y) == expected);
}
}
}
TEST_CASE("Image::Flip vertical honors a non-32-aligned source linesize", "[Image]") {
EnsureImageInit();
std::vector backing = MakeTightGray8Plane();
Image img(kW, /*linesize*/ kW, kH, /*colours*/ 1, ZM_SUBPIX_ORDER_NONE, backing.data(), /*padding*/ 0);
img.Flip(false);
REQUIRE(img.Width() == kW);
REQUIRE(img.Height() == kH);
for (unsigned int y = 0; y < kH; y++) {
for (unsigned int x = 0; x < kW; x++) {
const uint8_t expected = static_cast((kH - 1 - y) * kW + x);
REQUIRE(*img.Buffer(x, y) == expected);
}
}
}
// 90/270 also swap dimensions (dst is kH wide x kW tall), exercising both the
// source-stride fix and the destination dimension/stride handling.
TEST_CASE("Image::Rotate 90 honors a non-32-aligned source linesize", "[Image]") {
EnsureImageInit();
std::vector backing = MakeTightGray8Plane();
Image img(kW, /*linesize*/ kW, kH, /*colours*/ 1, ZM_SUBPIX_ORDER_NONE, backing.data(), /*padding*/ 0);
img.Rotate(90);
REQUIRE(img.Width() == kH);
REQUIRE(img.Height() == kW);
// 90deg: dst(X,Y) == src(x=Y, y=kH-1-X)
for (unsigned int Y = 0; Y < kW; Y++) {
for (unsigned int X = 0; X < kH; X++) {
const uint8_t expected = static_cast((kH - 1 - X) * kW + Y);
REQUIRE(*img.Buffer(X, Y) == expected);
}
}
}
TEST_CASE("Image::Rotate 270 honors a non-32-aligned source linesize", "[Image]") {
EnsureImageInit();
std::vector backing = MakeTightGray8Plane();
Image img(kW, /*linesize*/ kW, kH, /*colours*/ 1, ZM_SUBPIX_ORDER_NONE, backing.data(), /*padding*/ 0);
img.Rotate(270);
REQUIRE(img.Width() == kH);
REQUIRE(img.Height() == kW);
// 270deg: dst(X,Y) == src(x=kW-1-Y, y=X)
for (unsigned int Y = 0; Y < kW; Y++) {
for (unsigned int X = 0; X < kH; X++) {
const uint8_t expected = static_cast(X * kW + (kW - 1 - Y));
REQUIRE(*img.Buffer(X, Y) == expected);
}
}
}