I extended alireza answer to allow a zero allocation answer.
- Allow user to give a preallocated, or a
cv::Mat
as input
cv::resize
input image immediatly to output mat
- Color top and bottom box with
cv::rectangle
#include <opencv2/imgproc.hpp>
void resizeKeepAspectRatio(const cv::Mat& src, cv::Mat& dst, const cv::Size& dstSize, const cv::Scalar& backgroundColor = {})
{
// Don't handle anything in this corner case
if(dstSize.width <= 0 || dstSize.height <= 0)
return;
// Not job is needed here, let's avoid any copy
if(src.cols == dstSize.width && src.rows == dstSize.height)
{
dst = src;
return;
}
// Try not to reallocate memory if possible
cv::Mat output = [&]()
{
if(dst.data != src.data && dst.cols == dstSize.width && dst.rows == dstSize.height && dst.type() == src.type())
return dst;
return cv::Mat(dstSize.height, dstSize.width, src.type());
}();
// 'src' inside 'dst'
const auto imageBox = [&]()
{
const auto h1 = int(dstSize.width * (src.rows / (double)src.cols));
const auto w2 = int(dstSize.height * (src.cols / (double)src.rows));
const bool horizontal = h1 <= dstSize.height;
const auto width = horizontal ? dstSize.width : w2;
const auto height = horizontal ? h1 : dstSize.height;
const auto x = horizontal ? 0 : int(double(dstSize.width - width) / 2.);
const auto y = horizontal ? int(double(dstSize.height - height) / 2.) : 0;
return cv::Rect(x, y, width, height);
}();
cv::Rect firstBox;
cv::Rect secondBox;
if(imageBox.width > imageBox.height)
{
// ┌──────────────► x
// │ ┌────────────┐
// │ │┼┼┼┼┼┼┼┼┼┼┼┼│ firstBox
// │ x────────────►
// │ │ │
// │ ▼────────────┤
// │ │┼┼┼┼┼┼┼┼┼┼┼┼│ secondBox
// │ └────────────┘
// ▼
// y
firstBox.x = 0;
firstBox.width = dstSize.width;
firstBox.y = 0;
firstBox.height = imageBox.y;
secondBox.x = 0;
secondBox.width = dstSize.width;
secondBox.y = imageBox.y + imageBox.height;
secondBox.height = dstSize.height - secondBox.y;
}
else
{
// ┌──────────────► x
// │ ┌──x──────►──┐
// │ │┼┼│ │┼┼│
// │ │┼┼│ │┼┼│
// │ │┼┼│ │┼┼│
// │ └──▼──────┴──┘
// ▼ firstBox secondBox
// y
firstBox.y = 0;
firstBox.height = dstSize.height;
firstBox.x = 0;
firstBox.width = imageBox.x;
secondBox.y = 0;
secondBox.height = dstSize.height;
secondBox.x = imageBox.x + imageBox.width;
secondBox.width = dstSize.width - secondBox.x;
}
// Resizing to final image avoid useless memory allocation
cv::Mat outputImage = output(imageBox);
assert(outputImage.cols == imageBox.width);
assert(outputImage.rows == imageBox.height);
const auto* dataBeforeResize = outputImage.data;
cv::resize(src, outputImage, cv::Size(outputImage.cols, outputImage.rows));
assert(dataBeforeResize == outputImage.data);
const auto drawBox = [&](const cv::Rect& box)
{
if(box.width > 0 && box.height > 0)
{
cv::rectangle(output, cv::Point(box.x, box.y), cv::Point(box.x + box.width, box.y + box.height), backgroundColor, -1);
}
};
drawBox(firstBox);
drawBox(secondBox);
// Finally copy output to dst, like that user can use src & dst to the same cv::Mat
dst = output;
}
With this function, dst
mat can be reused without any reallocation.
cv::Mat src(200, 100, CV_8UC3, cv::Scalar(1,100,200));
cv::Size dstSize(300, 400)
cv::Mat dst;
resizeKeepAspectRatio(src, dst, dstSize); // dst get allocated
resizeKeepAspectRatio(src, dst, dstSize); // dst get reused