Files
zoneminder/src/zm_image.cpp
T
Isaac ConnorandClaude Opus 5 b11c3deb44 fix: compute p_pixfmt after setjmp in ReadJpeg/DecodeJpeg
Both functions initialised p_pixfmt before the setjmp() that installs the
libjpeg error recovery point, and only read it afterwards. GCC warns that
a longjmp() out of zm_jpeg_error_exit could restore a stale register copy:

  warning: variable 'p_pixfmt' might be clobbered by 'longjmp' or 'vfork'
  [-Wclobbered]

Move the initialisation below the setjmp() block instead of marking the
variable volatile. Nothing between the old and new positions uses the
value and the error paths do not reference it, so behaviour is unchanged
and no live value crosses the jump.

This unblocks builds using ENABLE_WERROR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 21:26:05 -04:00

6058 lines
225 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// ZoneMinder Image Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes
//
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "zm_image.h"
#include "zm_font.h"
#include "zm_poly.h"
#include "zm_swscale.h"
#include "zm_utils.h"
#include <libavutil/pixdesc.h>
#include <algorithm>
#include <fcntl.h>
#include <mutex>
#include <sys/stat.h>
#include <unistd.h>
#include <vector>
static unsigned char y_table_global[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 34, 36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 55, 57, 58, 59, 60, 61, 62, 64, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 76, 78, 79, 80, 81, 82, 83, 85, 86, 87, 88, 89, 90, 91, 93, 94, 95, 96, 97, 98, 100, 101, 102, 103, 104, 105, 107, 108, 109, 110, 111, 112, 114, 115, 116, 117, 118, 119, 121, 122, 123, 124, 125, 126, 128, 129, 130, 131, 132, 133, 135, 136, 137, 138, 139, 140, 142, 143, 144, 145, 146, 147, 149, 150, 151, 152, 153, 154, 156, 157, 158, 159, 160, 161, 163, 164, 165, 166, 167, 168, 170, 171, 172, 173, 174, 175, 176, 178, 179, 180, 181, 182, 183, 185, 186, 187, 188, 189, 190, 192, 193, 194, 195, 196, 197, 199, 200, 201, 202, 203, 204, 206, 207, 208, 209, 210, 211, 213, 214, 215, 216, 217, 218, 220, 221, 222, 223, 224, 225, 227, 228, 229, 230, 231, 232, 234, 235, 236, 237, 238, 239, 241, 242, 243, 244, 245, 246, 248, 249, 250, 251, 252, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255};
static signed char uv_table_global[] = {-127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -127, -125, -124, -123, -122, -121, -120, -119, -117, -116, -115, -114, -113, -112, -111, -109, -108, -107, -106, -105, -104, -103, -102, -100, -99, -98, -97, -96, -95, -94, -92, -91, -90, -89, -88, -87, -86, -85, -83, -82, -81, -80, -79, -78, -77, -75, -74, -73, -72, -71, -70, -69, -68, -66, -65, -64, -63, -62, -61, -60, -58, -57, -56, -55, -54, -53, -52, -51, -49, -48, -47, -46, -45, -44, -43, -41, -40, -39, -38, -37, -36, -35, -34, -32, -31, -30, -29, -28, -27, -26, -24, -23, -22, -21, -20, -19, -18, -17, -15, -14, -13, -12, -11, -10, -9, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 60, 61, 62, 63, 64, 65, 66, 68, 69, 70, 71, 72, 73, 74, 75, 77, 78, 79, 80, 81, 82, 83, 85, 86, 87, 88, 89, 90, 91, 92, 94, 95, 96, 97, 98, 99, 100, 102, 103, 104, 105, 106, 107, 108, 109, 111, 112, 113, 114, 115, 116, 117, 119, 120, 121, 122, 123, 124, 125, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, 127};
static short r_v_table_global[] = {-179, -178, -176, -175, -173, -172, -171, -169, -168, -166, -165, -164, -162, -161, -159, -158, -157, -155, -154, -152, -151, -150, -148, -147, -145, -144, -143, -141, -140, -138, -137, -135, -134, -133, -131, -130, -128, -127, -126, -124, -123, -121, -120, -119, -117, -116, -114, -113, -112, -110, -109, -107, -106, -105, -103, -102, -100, -99, -98, -96, -95, -93, -92, -91, -89, -88, -86, -85, -84, -82, -81, -79, -78, -77, -75, -74, -72, -71, -70, -68, -67, -65, -64, -63, -61, -60, -58, -57, -56, -54, -53, -51, -50, -49, -47, -46, -44, -43, -42, -40, -39, -37, -36, -35, -33, -32, -30, -29, -28, -26, -25, -23, -22, -21, -19, -18, -16, -15, -14, -12, -11, -9, -8, -7, -5, -4, -2, -1, 0, 1, 2, 4, 5, 7, 8, 9, 11, 12, 14, 15, 16, 18, 19, 21, 22, 23, 25, 26, 28, 29, 30, 32, 33, 35, 36, 37, 39, 40, 42, 43, 44, 46, 47, 49, 50, 51, 53, 54, 56, 57, 58, 60, 61, 63, 64, 65, 67, 68, 70, 71, 72, 74, 75, 77, 78, 79, 81, 82, 84, 85, 86, 88, 89, 91, 92, 93, 95, 96, 98, 99, 100, 102, 103, 105, 106, 107, 109, 110, 112, 113, 114, 116, 117, 119, 120, 121, 123, 124, 126, 127, 128, 130, 131, 133, 134, 135, 137, 138, 140, 141, 143, 144, 145, 147, 148, 150, 151, 152, 154, 155, 157, 158, 159, 161, 162, 164, 165, 166, 168, 169, 171, 172, 173, 175, 176};
static short g_u_table_global[] = {-44, -43, -43, -43, -42, -42, -41, -41, -41, -40, -40, -40, -39, -39, -39, -38, -38, -38, -37, -37, -37, -36, -36, -36, -35, -35, -35, -34, -34, -34, -33, -33, -33, -32, -32, -31, -31, -31, -30, -30, -30, -29, -29, -29, -28, -28, -28, -27, -27, -27, -26, -26, -26, -25, -25, -25, -24, -24, -24, -23, -23, -23, -22, -22, -22, -21, -21, -20, -20, -20, -19, -19, -19, -18, -18, -18, -17, -17, -17, -16, -16, -16, -15, -15, -15, -14, -14, -14, -13, -13, -13, -12, -12, -12, -11, -11, -11, -10, -10, -9, -9, -9, -8, -8, -8, -7, -7, -7, -6, -6, -6, -5, -5, -5, -4, -4, -4, -3, -3, -3, -2, -2, -2, -1, -1, -1, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 20, 20, 20, 21, 21, 22, 22, 22, 23, 23, 23, 24, 24, 24, 25, 25, 25, 26, 26, 26, 27, 27, 27, 28, 28, 28, 29, 29, 29, 30, 30, 30, 31, 31, 31, 32, 32, 33, 33, 33, 34, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 40, 40, 40, 41, 41, 41, 42, 42, 43, 43};
static short g_v_table_global[] = {-91, -90, -89, -89, -88, -87, -87, -86, -85, -84, -84, -83, -82, -82, -81, -80, -79, -79, -78, -77, -77, -76, -75, -74, -74, -73, -72, -72, -71, -70, -69, -69, -68, -67, -67, -66, -65, -64, -64, -63, -62, -62, -61, -60, -59, -59, -58, -57, -57, -56, -55, -54, -54, -53, -52, -52, -51, -50, -49, -49, -48, -47, -47, -46, -45, -44, -44, -43, -42, -42, -41, -40, -39, -39, -38, -37, -37, -36, -35, -34, -34, -33, -32, -32, -31, -30, -29, -29, -28, -27, -27, -26, -25, -24, -24, -23, -22, -22, -21, -20, -19, -19, -18, -17, -17, -16, -15, -14, -14, -13, -12, -12, -11, -10, -9, -9, -8, -7, -7, -6, -5, -4, -4, -3, -2, -2, -1, 0, 0, 0, 1, 2, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 9, 10, 11, 12, 12, 13, 14, 14, 15, 16, 17, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 24, 25, 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 39, 40, 41, 42, 42, 43, 44, 44, 45, 46, 47, 47, 48, 49, 49, 50, 51, 52, 52, 53, 54, 54, 55, 56, 57, 57, 58, 59, 59, 60, 61, 62, 62, 63, 64, 64, 65, 66, 67, 67, 68, 69, 69, 70, 71, 72, 72, 73, 74, 74, 75, 76, 77, 77, 78, 79, 79, 80, 81, 82, 82, 83, 84, 84, 85, 86, 87, 87, 88, 89, 89};
static short b_u_table_global[] = {-226, -225, -223, -221, -219, -217, -216, -214, -212, -210, -209, -207, -205, -203, -202, -200, -198, -196, -194, -193, -191, -189, -187, -186, -184, -182, -180, -178, -177, -175, -173, -171, -170, -168, -166, -164, -163, -161, -159, -157, -155, -154, -152, -150, -148, -147, -145, -143, -141, -139, -138, -136, -134, -132, -131, -129, -127, -125, -124, -122, -120, -118, -116, -115, -113, -111, -109, -108, -106, -104, -102, -101, -99, -97, -95, -93, -92, -90, -88, -86, -85, -83, -81, -79, -77, -76, -74, -72, -70, -69, -67, -65, -63, -62, -60, -58, -56, -54, -53, -51, -49, -47, -46, -44, -42, -40, -38, -37, -35, -33, -31, -30, -28, -26, -24, -23, -21, -19, -17, -15, -14, -12, -10, -8, -7, -5, -3, -1, 0, 1, 3, 5, 7, 8, 10, 12, 14, 15, 17, 19, 21, 23, 24, 26, 28, 30, 31, 33, 35, 37, 38, 40, 42, 44, 46, 47, 49, 51, 53, 54, 56, 58, 60, 62, 63, 65, 67, 69, 70, 72, 74, 76, 77, 79, 81, 83, 85, 86, 88, 90, 92, 93, 95, 97, 99, 101, 102, 104, 106, 108, 109, 111, 113, 115, 116, 118, 120, 122, 124, 125, 127, 129, 131, 132, 134, 136, 138, 139, 141, 143, 145, 147, 148, 150, 152, 154, 155, 157, 159, 161, 163, 164, 166, 168, 170, 171, 173, 175, 177, 178, 180, 182, 184, 186, 187, 189, 191, 193, 194, 196, 198, 200, 202, 203, 205, 207, 209, 210, 212, 214, 216, 217, 219, 221, 223};
bool Image::initialised = false;
static unsigned char *y_table;
static signed char *uv_table;
static short *r_v_table;
static short *g_v_table;
static short *g_u_table;
static short *b_u_table;
struct SwsContext *sws_convert_context = nullptr;
jpeg_compress_struct *Image::writejpg_ccinfo[101] = { };
jpeg_compress_struct *Image::encodejpg_ccinfo[101] = { };
jpeg_decompress_struct *Image::readjpg_dcinfo = nullptr;
jpeg_decompress_struct *Image::decodejpg_dcinfo = nullptr;
struct zm_error_mgr Image::jpg_err;
/* Pointer to blend function. */
static blend_fptr_t fptr_blend;
/* Pointer to delta8 functions */
static delta_fptr_t fptr_delta8_rgb;
static delta_fptr_t fptr_delta8_bgr;
static delta_fptr_t fptr_delta8_rgba;
static delta_fptr_t fptr_delta8_bgra;
static delta_fptr_t fptr_delta8_argb;
static delta_fptr_t fptr_delta8_abgr;
static delta_fptr_t fptr_delta8_gray8;
/* Pointers to deinterlace_4field functions */
static deinterlace_4field_fptr_t fptr_deinterlace_4field_rgba;
static deinterlace_4field_fptr_t fptr_deinterlace_4field_bgra;
static deinterlace_4field_fptr_t fptr_deinterlace_4field_argb;
static deinterlace_4field_fptr_t fptr_deinterlace_4field_abgr;
static deinterlace_4field_fptr_t fptr_deinterlace_4field_gray8;
/* Pointer to image buffer memory copy function */
imgbufcpy_fptr_t fptr_imgbufcpy;
/* Font */
static ZmFont font;
std::mutex jpeg_mutex;
void Image::update_function_pointers() {
/* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements
* previous tests were %16 or %12 but that is incorrect. Should just be %4
*/
if (pixels %4) {
// have to use non-loop unrolled functions
delta8_rgb = &std_delta8_rgb;
delta8_bgr = &std_delta8_bgr;
delta8_rgba = &std_delta8_rgba;
delta8_bgra = &std_delta8_bgra;
delta8_argb = &std_delta8_argb;
delta8_abgr = &std_delta8_abgr;
delta8_gray8 = &std_delta8_gray8;
blend = &std_blend;
Debug(1, "Using slow std functions because pixels %d mod 4=%d", pixels, pixels%4);
} else {
// Use either sse or neon, or loop unrolled version
delta8_rgb = fptr_delta8_rgb;
delta8_bgr = fptr_delta8_bgr;
delta8_rgba = fptr_delta8_rgba;
delta8_bgra = fptr_delta8_bgra;
delta8_argb = fptr_delta8_argb;
delta8_abgr = fptr_delta8_abgr;
delta8_gray8 = fptr_delta8_gray8;
blend = fptr_blend;
}
}
// This constructor is not used anywhere
Image::Image() :
delta8_rgb(&std_delta8_rgb),
delta8_bgr(&std_delta8_bgr),
delta8_rgba(&std_delta8_rgba),
delta8_bgra(&std_delta8_bgra),
delta8_argb(&std_delta8_argb),
delta8_abgr(&std_delta8_abgr),
delta8_gray8(&std_delta8_gray8),
blend(&std_blend),
width(0),
linesize(0),
height(0),
pixels(0),
colours(0),
padding(0),
size(0),
subpixelorder(0),
allocation(0),
buffer(nullptr),
buffertype(ZM_BUFTYPE_DONTFREE),
holdbuffer(0),
blend_buffer_(nullptr),
blend_buffer_size_(0) {
if (!initialised)
Initialise();
// Update blend to fast function determined by Initialise, I'm sure this can be improve.
blend = fptr_blend;
}
Image::Image(const std::string &filename) {
if ( !initialised )
Initialise();
filename_ = filename;
width = 0;
linesize = 0;
height = 0;
padding = 0;
pixels = 0;
colours = 0;
subpixelorder = 0;
size = 0;
allocation = 0;
buffer = 0;
buffertype = ZM_BUFTYPE_DONTFREE;
holdbuffer = 0;
blend_buffer_ = nullptr;
blend_buffer_size_ = 0;
ReadJpeg(filename, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
update_function_pointers();
}
Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer,
unsigned long p_allocation, unsigned int p_padding) :
width(p_width),
height(p_height),
colours(p_colours),
padding(p_padding),
subpixelorder(p_subpixelorder),
allocation(p_allocation),
buffer(p_buffer),
holdbuffer(0),
blend_buffer_(nullptr),
blend_buffer_size_(0) {
if (!initialised)
Initialise();
pixels = width * height;
if (!subpixelorder and (colours>1)) {
Debug(1, "Defaulting to RGBA Cuz %d %d", subpixelorder, colours);
// Default to RGBA when no subpixelorder is specified.
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
}
imagePixFormat = AVPixFormat();
linesize = FFALIGN(av_image_get_linesize(imagePixFormat, width, 0), 32);
size = av_image_get_buffer_size(imagePixFormat, width, height, 32);
Debug(1, "Choosing pixformat %s", toString().c_str());
if (p_buffer) {
if (!allocation) allocation = size;
buffertype = ZM_BUFTYPE_DONTFREE;
buffer = p_buffer;
} else {
Debug(3, "line size: %d =? %d width %d Size %d ?= %d", linesize,
av_image_get_linesize(imagePixFormat, width, 0),
width, linesize * height + padding, size);
AllocImgBuffer(size);
}
update_function_pointers();
}
Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) :
width(p_width),
height(p_height),
colours(p_colours),
padding(p_padding),
subpixelorder(p_subpixelorder),
buffer(p_buffer),
holdbuffer(0),
blend_buffer_(nullptr),
blend_buffer_size_(0) {
if (!initialised)
Initialise();
pixels = width * height;
linesize = p_width * p_colours;
if (!subpixelorder and (colours>1)) {
// Default to RGBA when no subpixelorder is specified.
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
}
imagePixFormat = AVPixFormat();
if (p_buffer) {
size = linesize * height + padding;
allocation = size;
buffertype = ZM_BUFTYPE_DONTFREE;
buffer = p_buffer;
} else {
size = av_image_get_buffer_size(imagePixFormat, width, height, 32);
linesize = FFALIGN(av_image_get_linesize(imagePixFormat, width, 0), 32);
Debug(4, "line size: %d =? %d width %d Size %d ?= %d", linesize,
av_image_get_linesize(imagePixFormat, width, 0),
width, linesize * height + padding, size);
AllocImgBuffer(size);
}
update_function_pointers();
}
Image::Image(int p_width, int p_linesize, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) :
width(p_width),
linesize(p_linesize),
height(p_height),
colours(p_colours),
padding(p_padding),
subpixelorder(p_subpixelorder),
buffer(p_buffer),
blend_buffer_(nullptr),
blend_buffer_size_(0) {
if ( !initialised )
Initialise();
pixels = width*height;
size = linesize*height + padding;
buffer = nullptr;
holdbuffer = 0;
if ( p_buffer ) {
allocation = size;
buffertype = ZM_BUFTYPE_DONTFREE;
buffer = p_buffer;
} else {
AllocImgBuffer(size);
}
if (!subpixelorder and colours>1) {
// Default to RGBA when no subpixelorder is specified.
subpixelorder = ZM_SUBPIX_ORDER_RGBA;
}
imagePixFormat = AVPixFormat();
update_function_pointers();
}
Image::Image(const AVFrame *frame, int p_width, int p_height) :
colours(ZM_COLOUR_RGB32),
padding(0),
subpixelorder(ZM_SUBPIX_ORDER_RGBA),
imagePixFormat(AV_PIX_FMT_RGBA),
buffer(0),
holdbuffer(0),
blend_buffer_(nullptr),
blend_buffer_size_(0) {
width = (p_width == -1 ? frame->width : p_width);
height = (p_height == -1 ? frame->height : p_height);
pixels = width * height;
zm_dump_video_frame(frame, "Image.Assign(frame)");
// FIXME
//(AVPixelFormat)frame->format;
size = av_image_get_buffer_size(AV_PIX_FMT_RGBA, width, height, 32);
// av_image_get_linesize isn't aligned, so we have to do that.
linesize = FFALIGN(av_image_get_linesize(AV_PIX_FMT_RGBA, width, 0), 32);
AllocImgBuffer(size);
this->Assign(frame);
}
Image::Image(const AVFrame *frame) : Image() {
// Delegate to the default ctor first so buffer/buffertype/allocation
// are fully initialised before AssignDirect runs. AssignDirect's failure
// and success paths both call DumpImgBuffer() to release any previously
// owned buffer; without this delegation those calls would read
// uninitialised members and potentially free a garbage pointer.
AssignDirect(frame);
}
static void dont_free(void *opaque, uint8_t *data) {
}
int Image::PopulateFrame(AVFrame *frame) const {
Debug(1, "PopulateFrame: width %d height %d linesize %d colours %d imagesize %d %s",
width, height, linesize, colours, size,
av_get_pix_fmt_name(imagePixFormat)
);
AVBufferRef *ref = av_buffer_create(buffer, size,
dont_free, /* Free callback */
nullptr, /* opaque */
0 /* flags */
);
if (!ref) {
Error("Failed to create av_buffer");
return -1;
}
frame->buf[0] = ref;
// From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too.
int rc_size = av_image_fill_arrays(
frame->data, frame->linesize,
buffer, imagePixFormat, width, height,
32 //alignment
);
if (rc_size < 0) {
Error("Problem setting up data pointers into image %s",
av_make_error_string(rc_size).c_str());
return rc_size;
}
frame->width = width;
frame->height = height;
frame->format = imagePixFormat;
zm_dump_video_frame(frame, "Image.Populate(frame)");
return 1;
} // int Image::PopulateFrame(AVFrame *frame)
bool Image::Assign(const AVFrame *frame) {
/* Assume the dimensions etc are correct. FIXME */
if (!frame) {
Error("Null frame passed to Image::Assign");
return false;
}
zm_dump_video_frame(frame, "source frame in Image::Assign");
// imagePixFormat is the canonical destination format; the deprecated
// AVPixFormat() getter re-derives via (colours, subpixelorder) and would
// pick the wrong swscale target if those legacy fields drift out of sync
// (e.g. the GRAY8/YUV420P alias collision).
const AVPixelFormat format = imagePixFormat;
// Map deprecated YUVJ* formats to their non-J equivalents before handing the
// format to swscale. Passing YUVJ420P/YUVJ422P/etc directly makes swscale emit
// "deprecated pixel format used, make sure you did set range correctly" (seen
// in nph-zms). This mirrors what SWScale::Convert already does. Both sides
// need it: imagePixFormat is itself YUVJ* whenever the monitor passes through
// a full-range h264/mjpeg source.
const AVPixelFormat orig_src_fmt = static_cast<AVPixelFormat>(frame->format);
const AVPixelFormat src_fmt = fix_deprecated_pix_fmt(orig_src_fmt);
const AVPixelFormat dst_fmt = fix_deprecated_pix_fmt(format);
// If source and destination format + dimensions match, do a direct plane
// copy instead of running through sws_scale. This avoids the overhead of
// the swscale pipeline for identity conversions (e.g. YUVJ422P→YUVJ422P).
if (src_fmt == dst_fmt
&& frame->width == static_cast<int>(width)
&& frame->height == static_cast<int>(height)) {
Debug(4, "Same format %s %dx%d, using av_image_copy",
zm_get_pix_fmt_name(format), width, height);
av_frame_ptr temp_frame{av_frame_alloc()};
if (!temp_frame) {
Error("Unable to allocate destination frame");
return false;
}
// PopulateFrame can fail (av_buffer_create / av_image_fill_arrays errors);
// calling av_image_copy on an unpopulated temp_frame is undefined.
if (PopulateFrame(temp_frame.get()) < 0) {
Error("PopulateFrame failed; skipping av_image_copy fast path");
return false;
}
temp_frame->pts = frame->pts;
// u_buffer/v_buffer are not members of Image on master; av_image_copy
// reads the planes directly from temp_frame->data, so we don't need to
// mirror them onto the Image either.
av_image_copy(temp_frame->data, temp_frame->linesize,
(const uint8_t **)frame->data, frame->linesize,
format, width, height);
update_function_pointers();
return true;
}
sws_convert_context = sws_getCachedContext(
sws_convert_context,
frame->width, frame->height, src_fmt,
width, height, dst_fmt,
SWS_BICUBIC,
nullptr, nullptr, nullptr);
if (sws_convert_context == nullptr) {
Error("Unable to create conversion context");
return false;
}
zm_sws_set_ranges(sws_convert_context, orig_src_fmt, format);
bool result = Assign(frame, sws_convert_context);
update_function_pointers();
return result;
} // end Image::Assign(const AVFrame *frame)
bool Image::Assign(const AVFrame *frame, SwsContext *convert_context) {
av_frame_ptr temp_frame{av_frame_alloc()};
if (!temp_frame) {
Error("Unable to allocate destination frame");
return false;
}
PopulateFrame(temp_frame.get());
zm_dump_video_frame(frame, "source frame before convert");
temp_frame->pts = frame->pts;
Debug(1, "Assign src linesize: %d, dest linesize: %d", frame->linesize[0], temp_frame->linesize[0]);
int ret = sws_scale(convert_context,
frame->data, frame->linesize, 0, frame->height,
temp_frame->data, temp_frame->linesize);
if (ret < 0) {
AVPixelFormat format = (AVPixelFormat)AVPixFormat();
Error("Unable to convert raw format %u %s %ux%u to target format %u %s %ux%u: %s",
frame->format, av_get_pix_fmt_name(static_cast<AVPixelFormat>(frame->format)), frame->width, frame->height,
format, av_get_pix_fmt_name(format), width, height,
av_make_error_string(ret).c_str());
return false;
}
zm_dump_video_frame(temp_frame, "dest frame after convert");
update_function_pointers();
return true;
} // end Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *temp_frame)
Image::Image(const Image &p_image) {
if ( !initialised )
Initialise();
width = p_image.width;
linesize = p_image.linesize;
padding = 0;
height = p_image.height;
pixels = p_image.pixels;
colours = p_image.colours;
subpixelorder = p_image.subpixelorder;
size = p_image.size; // allocation is set in AllocImgBuffer
buffer = nullptr;
holdbuffer = 0;
blend_buffer_ = nullptr;
blend_buffer_size_ = 0;
AllocImgBuffer(size);
(*fptr_imgbufcpy)(buffer, p_image.buffer, size);
annotation_ = p_image.annotation_;
imagePixFormat = p_image.imagePixFormat;
update_function_pointers();
}
Image::~Image() {
DumpImgBuffer();
if (blend_buffer_) {
zm_freealigned(blend_buffer_);
blend_buffer_ = nullptr;
blend_buffer_size_ = 0;
}
}
const std::string Image::toString() {
return stringtf("%dx%d colours:%d pixfmt:%d %s size %d", width, height, colours,
imagePixFormat, av_get_pix_fmt_name(imagePixFormat), size);
}
/* Should be called as part of program shutdown to free everything */
void Image::Deinitialise() {
if (!initialised) return;
initialised = false;
if (readjpg_dcinfo) {
jpeg_destroy_decompress(readjpg_dcinfo);
delete readjpg_dcinfo;
readjpg_dcinfo = nullptr;
}
if (decodejpg_dcinfo) {
jpeg_destroy_decompress(decodejpg_dcinfo);
delete decodejpg_dcinfo;
decodejpg_dcinfo = nullptr;
}
for (unsigned int quality=0; quality <= 100; quality += 1) {
if (writejpg_ccinfo[quality]) {
jpeg_destroy_compress(writejpg_ccinfo[quality]);
delete writejpg_ccinfo[quality];
writejpg_ccinfo[quality] = nullptr;
}
if (encodejpg_ccinfo[quality]) {
jpeg_destroy_compress(encodejpg_ccinfo[quality]);
delete encodejpg_ccinfo[quality];
encodejpg_ccinfo[quality] = nullptr;
}
} // end foreach quality
if (sws_convert_context) {
sws_freeContext(sws_convert_context);
sws_convert_context = nullptr;
}
} // end void Image::Deinitialise()
void Image::Initialise() {
/* Assign the blend pointer to function */
if ( config.fast_image_blends ) {
if ( config.cpu_extensions && sse_version >= 20 ) {
fptr_blend = &sse2_fastblend; /* SSE2 fast blend */
Debug(4, "Blend: Using SSE2 fast blend function");
} else if ( config.cpu_extensions && neonversion >= 1 ) {
#if defined(__aarch64__)
fptr_blend = &neon64_armv8_fastblend; /* ARM Neon (AArch64) fast blend */
Debug(4, "Blend: Using ARM Neon (AArch64) fast blend function");
#elif defined(__arm__)
fptr_blend = &neon32_armv7_fastblend; /* ARM Neon (AArch32) fast blend */
Debug(4, "Blend: Using ARM Neon (AArch32) fast blend function");
#else
Panic("Bug: Non ARM platform but neon present");
#endif
} else {
fptr_blend = &std_fastblend; /* standard fast blend */
Debug(4, "Blend: Using fast blend function");
}
} else {
fptr_blend = &std_blend;
Debug(4, "Blend: Using standard blend function");
}
__attribute__((aligned(64))) uint8_t blend1[128] = {
86,58,54,63,149,62,209,34,148,46,186,176,9,236,193,254,113,146,228,220,123,164,92,98,9,72,67,156,63,118,96,167,
48,224,106,176,201,245,223,219,198,50,100,31,68,77,33,76,166,90,254,128,191,82,84,32,3,171,147,248,14,196,141,179,
79,237,121,11,132,37,194,225,45,171,169,167,56,64,193,85,147,33,97,221,94,97,90,44,191,248,65,8,17,240,167,207,
224,23,71,74,81,1,46,110,227,94,163,170,55,155,52,147,224,154,237,35,255,26,229,11,223,242,118,155,82,37,189,2
};
__attribute__((aligned(64))) uint8_t blend2[128] = {
92,188,203,118,121,231,252,218,126,88,80,72,123,16,91,131,109,0,57,56,95,204,74,8,137,94,6,69,18,146,229,194,
146,230,13,146,95,48,185,65,162,47,152,172,184,111,245,143,247,105,49,42,89,37,145,255,221,200,103,80,98,39,14,227,
227,46,46,59,248,7,83,20,157,79,36,161,237,55,77,175,232,200,38,170,198,239,89,19,82,88,130,120,203,184,141,117,
228,140,150,107,103,195,74,130,42,11,150,70,176,204,198,188,38,252,174,104,128,106,31,17,141,231,62,104,179,29,143,130
};
__attribute__((aligned(64))) uint8_t blendexp[128] = {
86,73,71,69,145,82,214,56,145,51,173,163,22,209,180,239,112,128,207,200,119,168,89,87,24,74,59,145,57,121,111,170,
59,224,94,172,188,221,218,200,193,49,106,47,81,81,58,84,175,91,229,117,178,76,91,58,29,174,141,227,24,177,125,184,
96,214,112,16,145,33,180,200,58,159,153,166,77,62,179,95,157,53,89,214,106,114,89,41,177,228,72,21,39,233,163,196,
224,37,80,77,83,24,49,112,204,84,161,158,69,160,69,151,201,165,229,43,239,35,205,11,213,240,111,148,93,36,183,17
};
__attribute__((aligned(64))) uint8_t blendres[128];
/* Run the blend function */
(*fptr_blend)(blend1,blend2,blendres,128,12.0);
/* Compare results with expected results */
for ( int i=0; i < 128; i++ ) {
if ( abs(blendexp[i] - blendres[i]) > 3 ) {
Panic("Blend function failed self-test: Results differ from the expected results. Column %u Expected %u Got %u",i,blendexp[i],blendres[i]);
}
}
fptr_delta8_rgb = &std_delta8_rgb;
fptr_delta8_bgr = &std_delta8_bgr;
/* Assign the delta functions */
if ( config.cpu_extensions ) {
if ( sse_version >= 35 ) {
/* SSSE3 available */
fptr_delta8_rgba = &ssse3_delta8_rgba;
fptr_delta8_bgra = &ssse3_delta8_bgra;
fptr_delta8_argb = &ssse3_delta8_argb;
fptr_delta8_abgr = &ssse3_delta8_abgr;
fptr_delta8_gray8 = &sse2_delta8_gray8;
Debug(4, "Delta: Using SSSE3 delta functions");
} else if ( sse_version >= 20 ) {
/* SSE2 available */
fptr_delta8_rgba = &sse2_delta8_rgba;
fptr_delta8_bgra = &sse2_delta8_bgra;
fptr_delta8_argb = &sse2_delta8_argb;
fptr_delta8_abgr = &sse2_delta8_abgr;
fptr_delta8_gray8 = &sse2_delta8_gray8;
Debug(4, "Delta: Using SSE2 delta functions");
} else if ( neonversion >= 1 ) {
/* ARM Neon available */
#if defined(__aarch64__)
fptr_delta8_rgba = &neon64_armv8_delta8_rgba;
fptr_delta8_bgra = &neon64_armv8_delta8_bgra;
fptr_delta8_argb = &neon64_armv8_delta8_argb;
fptr_delta8_abgr = &neon64_armv8_delta8_abgr;
fptr_delta8_gray8 = &neon64_armv8_delta8_gray8;
Debug(4, "Delta: Using ARM Neon (AArch64) delta functions");
#elif defined(__arm__)
fptr_delta8_rgba = &neon32_armv7_delta8_rgba;
fptr_delta8_bgra = &neon32_armv7_delta8_bgra;
fptr_delta8_argb = &neon32_armv7_delta8_argb;
fptr_delta8_abgr = &neon32_armv7_delta8_abgr;
fptr_delta8_gray8 = &neon32_armv7_delta8_gray8;
Debug(4, "Delta: Using ARM Neon (AArch32) delta functions");
#else
Panic("Bug: Non ARM platform but neon present");
#endif
} else {
/* No suitable SSE version available */
fptr_delta8_rgba = &fast_delta8_rgba;
fptr_delta8_bgra = &fast_delta8_bgra;
fptr_delta8_argb = &fast_delta8_argb;
fptr_delta8_abgr = &fast_delta8_abgr;
fptr_delta8_gray8 = &fast_delta8_gray8;
Debug(4,"Delta: Using standard delta functions");
}
} else {
/* CPU extensions disabled */
fptr_delta8_rgba = &fast_delta8_rgba;
fptr_delta8_bgra = &fast_delta8_bgra;
fptr_delta8_argb = &fast_delta8_argb;
fptr_delta8_abgr = &fast_delta8_abgr;
fptr_delta8_gray8 = &fast_delta8_gray8;
Debug(4,"Delta: CPU extensions disabled, using standard delta functions");
}
__attribute__((aligned(64))) uint8_t delta8_1[128] = {
221,22,234,254,8,140,15,28,166,13,203,56,92,250,79,225,19,59,241,145,253,33,87,204,97,168,229,180,3,108,205,177,
41,108,65,149,4,87,16,240,56,50,135,64,153,3,219,214,239,55,169,180,167,45,243,56,191,119,145,250,102,145,73,32,
207,213,189,167,147,83,217,30,113,51,142,125,219,97,60,5,135,195,95,133,21,197,150,82,134,93,198,97,97,49,117,24,
242,253,242,5,190,71,182,1,0,69,25,181,139,84,242,79,150,158,29,215,98,100,245,16,86,165,18,98,46,100,139,19
};
__attribute__((aligned(64))) uint8_t delta8_2[128] = {
236,22,153,161,50,141,15,130,89,251,33,5,140,201,225,194,138,76,248,89,25,26,29,93,250,251,48,157,41,126,140,152,
170,177,134,14,234,99,3,105,217,76,38,233,89,30,93,48,234,40,202,80,184,4,250,71,183,249,76,78,184,148,185,120,
137,214,238,57,50,93,29,60,99,207,40,15,43,28,177,118,60,231,90,47,198,251,250,241,212,114,249,17,95,161,216,218,
51,178,137,161,213,108,35,72,65,24,5,176,110,15,0,2,137,58,0,133,197,1,122,169,175,33,223,138,37,114,52,186
};
__attribute__((aligned(64))) uint8_t delta8_gray8_exp[128] = {
15,0,81,93,42,1,0,102,77,238,170,51,48,49,146,31,119,17,7,56,228,7,58,111,153,83,181,23,38,18,65,25,
129,69,69,135,230,12,13,135,161,26,97,169,64,27,126,166,5,15,33,100,17,41,7,15,8,130,69,172,82,3,112,88,
70,1,49,110,97,10,188,30,14,156,102,110,176,69,117,113,75,36,5,86,177,54,100,159,78,21,51,80,2,112,99,194,
191,75,105,156,23,37,147,71,65,45,20,5,29,69,242,77,13,100,29,82,99,99,123,153,89,132,205,40,9,14,87,167
};
__attribute__((aligned(64))) uint8_t delta8_rgba_exp[32] = {
13,11,189,60,41,68,112,28,84,66,68,48,14,30,91,36,24,54,113,101,41,90,39,82,107,47,46,80,69,102,130,21
};
__attribute__((aligned(64))) uint8_t delta8_gray8_res[128];
__attribute__((aligned(64))) uint8_t delta8_rgba_res[32];
/* Run the delta8 grayscale function */
(*fptr_delta8_gray8)(delta8_1,delta8_2,delta8_gray8_res,128);
/* Compare results with expected results */
for ( int i=0; i < 128; i++ ) {
if ( abs(delta8_gray8_exp[i] - delta8_gray8_res[i]) > 7 ) {
Panic("Delta grayscale function failed self-test: Results differ from the expected results. Column %u Expected %u Got %u",i,delta8_gray8_exp[i],delta8_gray8_res[i]);
}
}
/* Run the delta8 RGBA function */
(*fptr_delta8_rgba)(delta8_1,delta8_2,delta8_rgba_res, 32);
/* Compare results with expected results */
for ( int i=0; i < 32; i++ ) {
if ( abs(delta8_rgba_exp[i] - delta8_rgba_res[i]) > 7 ) {
Panic("Delta RGBA function failed self-test: Results differ from the expected results. Column %u Expected %u Got %u",i,delta8_rgba_exp[i],delta8_rgba_res[i]);
}
}
/*
SSSE3 deinterlacing functions were removed because they were usually equal
or slower than the standard code (compiled with -O2 or better)
The function is too complicated to be vectorized efficiently on SSSE3
*/
fptr_deinterlace_4field_rgba = &std_deinterlace_4field_rgba;
fptr_deinterlace_4field_bgra = &std_deinterlace_4field_bgra;
fptr_deinterlace_4field_argb = &std_deinterlace_4field_argb;
fptr_deinterlace_4field_abgr = &std_deinterlace_4field_abgr;
fptr_deinterlace_4field_gray8 = &std_deinterlace_4field_gray8;
Debug(4, "Deinterlace: Using standard functions");
#if defined(__i386__) && !defined(__x86_64__)
/* Use SSE2 aligned memory copy? */
if ( config.cpu_extensions && sse_version >= 20 ) {
fptr_imgbufcpy = &sse2_aligned_memcpy;
Debug(4, "Image buffer copy: Using SSE2 aligned memcpy");
} else {
fptr_imgbufcpy = &memcpy;
Debug(4, "Image buffer copy: Using standard memcpy");
}
#else
fptr_imgbufcpy = &memcpy;
Debug(4, "Image buffer copy: Using standard memcpy");
#endif
y_table = y_table_global;
uv_table = uv_table_global;
r_v_table = r_v_table_global;
g_v_table = g_v_table_global;
g_u_table = g_u_table_global;
b_u_table = b_u_table_global;
FontLoadError res = font.LoadFontFile(config.font_file_location);
if (res == FontLoadError::kFileNotFound) {
// Try compile-time default as fallback
std::string fallback = std::string(ZM_FONTDIR) + "/default.zmfnt";
if (fallback != config.font_file_location) {
Warning("Font not found at configured path %s, trying %s",
config.font_file_location, fallback.c_str());
res = font.LoadFontFile(fallback.c_str());
}
}
if (res == FontLoadError::kFileNotFound) {
Error("Invalid font location: %s", config.font_file_location);
} else if (res == FontLoadError::kInvalidFile) {
Error("Invalid font file.");
}
initialised = true;
}
/* Requests a writeable buffer to the image. This is safer than buffer() because this way we can guarantee that a buffer of required size exists */
uint8_t* Image::WriteBuffer(
const unsigned int p_width,
const unsigned int p_height,
const unsigned int p_colours,
const unsigned int p_subpixelorder) {
AVPixelFormat p_pixfmt = zm_pixformat_from_colours(p_colours, p_subpixelorder);
if (p_pixfmt == AV_PIX_FMT_NONE) {
Error("WriteBuffer called with unsupported (colours=%u, subpixelorder=%u) pair; "
"no AVPixelFormat mapping",
p_colours, p_subpixelorder);
return nullptr;
}
if ( ! ( p_height > 0 && p_width > 0 ) ) {
Error("WriteBuffer called with invalid width or height: %d %d", p_width, p_height);
return nullptr;
}
if ( p_width != width || p_height != height || p_colours != colours || p_subpixelorder != subpixelorder ) {
// Derive size/linesize from the AVPixelFormat. Using p_width * p_colours
// is wrong for planar formats: with the GRAY8/YUV420P alias collision,
// p_colours=1 for YUV420P/YUV422P, but those formats need ~1.5x/2x more
// bytes for the chroma planes.
int av_size = av_image_get_buffer_size(p_pixfmt, p_width, p_height, 32);
if (av_size < 0) {
Error("WriteBuffer: av_image_get_buffer_size failed for fmt=%d %ux%u",
p_pixfmt, p_width, p_height);
return nullptr;
}
// FFALIGN to 32 to match what av_image_get_buffer_size with align=32
// assumes for the buffer layout, and what sws_scale writes when it
// produces this format/width pair. Using the unaligned natural linesize
// here would mismatch the buffer's actual row stride and cause
// diagonal-shift artefacts in any consumer that walks the buffer at
// Image::linesize stride (e.g. EncodeJpeg's non-planar path).
int av_linesize = FFALIGN(av_image_get_linesize(p_pixfmt, p_width, 0), 32);
if (av_linesize < 0) {
Error("WriteBuffer: av_image_get_linesize failed for fmt=%d width=%u",
p_pixfmt, p_width);
return nullptr;
}
unsigned int newsize = static_cast<unsigned int>(av_size);
if ( buffer == nullptr ) {
AllocImgBuffer(newsize);
} else {
if ( allocation < newsize ) {
if ( holdbuffer ) {
Error("Held buffer is undersized for requested buffer");
return nullptr;
} else {
/* Replace buffer with a bigger one */
//DumpImgBuffer(); // Done in AllocImgBuffer too
AllocImgBuffer(newsize);
}
}
}
width = p_width;
height = p_height;
colours = p_colours;
linesize = static_cast<unsigned int>(av_linesize);
subpixelorder = p_subpixelorder;
imagePixFormat = p_pixfmt;
pixels = height*width;
size = newsize;
} // end if need to re-alloc buffer
return buffer;
}
void Image::AssignDirect(const AVFrame *frame) {
auto invalidate = [&]() {
// Release any previously-owned buffer before overwriting buffer/buffertype;
// a held (DONTFREE) buffer is left in place by DumpImgBuffer, so this is
// safe whether the Image currently owns its memory or wraps somebody
// else's. Without this step a ZM_BUFTYPE_ZM allocation would be leaked
// every time AssignDirect(frame) hits the invalidate path.
DumpImgBuffer();
width = 0;
height = 0;
pixels = 0;
buffer = nullptr;
imagePixFormat = AV_PIX_FMT_NONE;
colours = 0;
subpixelorder = 0;
size = 0;
allocation = 0;
linesize = 0;
holdbuffer = true;
buffertype = ZM_BUFTYPE_DONTFREE;
};
AVPixelFormat frame_fmt = static_cast<AVPixelFormat>(frame->format);
unsigned int probe_colours, probe_subpix;
// Validate the format against our supported set before touching any state.
if (!zm_colours_from_pixformat(frame_fmt, probe_colours, probe_subpix)) {
Error("AssignDirect: unsupported pixel format %d on frame %dx%d; "
"fully invalidating Image", frame->format, frame->width, frame->height);
invalidate();
return;
}
if (!frame->data[0]) {
Error("AssignDirect: AVFrame has null data[0]; fully invalidating Image");
invalidate();
return;
}
// av_image_get_buffer_size(..., align=32) assumes 32-byte stride
// alignment, but the AVFrame may have been allocated with a different
// alignment by the decoder. Derive the size from the *actual* frame
// strides instead — av_image_fill_pointers computes expected per-plane
// offsets given a tightly-packed buffer using the frame's linesize[]; the
// last (plane+1) offset is the total contiguous span. We also sanity-
// check that the frame's plane pointers really are contiguous starting
// at data[0] (a non-contiguous AVFrame can't be wrapped with a single
// Image::buffer pointer).
uint8_t *expected_planes[4] = {nullptr, nullptr, nullptr, nullptr};
int frame_strides[4] = {
frame->linesize[0], frame->linesize[1], frame->linesize[2], frame->linesize[3]
};
int total_size = av_image_fill_pointers(expected_planes, frame_fmt, frame->height,
frame->data[0], frame_strides);
if (total_size <= 0) {
Error("AssignDirect: av_image_fill_pointers returned %d for %s %dx%d; invalidating",
total_size, av_get_pix_fmt_name(frame_fmt), frame->width, frame->height);
invalidate();
return;
}
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame_fmt);
const int plane_count = desc ? av_pix_fmt_count_planes(frame_fmt) : 1;
for (int p = 1; p < plane_count && p < 4; p++) {
if (frame->data[p] && frame->data[p] != expected_planes[p]) {
Error("AssignDirect: AVFrame plane %d not contiguous with data[0] "
"(expected %p, got %p) for %s %dx%d; invalidating",
p, expected_planes[p], frame->data[p],
av_get_pix_fmt_name(frame_fmt), frame->width, frame->height);
invalidate();
return;
}
}
// Release any previously-owned buffer before we replace it with
// frame->data[0]. DumpImgBuffer is a no-op for DONTFREE buffers, so this
// is safe whether the Image currently owns its memory or wraps somebody
// else's. Without this step a ZM_BUFTYPE_ZM allocation would be leaked
// every time AssignDirect(frame) succeeds.
DumpImgBuffer();
width = frame->width;
height = frame->height;
buffer = frame->data[0];
linesize = frame->linesize[0];
imagePixFormat = frame_fmt;
colours = probe_colours;
subpixelorder = probe_subpix;
allocation = size = static_cast<unsigned int>(total_size);
pixels = width * height;
holdbuffer = true;
buffertype = ZM_BUFTYPE_DONTFREE;
// Re-bind format-specific function pointers (delta/blend/convert
// dispatch) to the new format. Without this, an Image that previously
// held a different format keeps its old fptr_* and subsequent ops take
// the wrong optimized path.
update_function_pointers();
}
/* Assign an existing buffer to the image instead of copying from a source buffer.
The goal is to reduce the amount of memory copying and increase efficiency and buffer reusing.
*/
void Image::AssignDirect(
const unsigned int p_width,
const unsigned int p_height,
const unsigned int p_colours,
const unsigned int p_subpixelorder,
uint8_t *new_buffer,
const size_t buffer_size,
const int p_buffertype) {
if ( new_buffer == nullptr ) {
Error("Attempt to directly assign buffer from a NULL pointer");
return;
}
if ( !p_height || !p_width ) {
Error("Attempt to directly assign buffer with invalid width or height: %d %d", p_width, p_height);
return;
}
AVPixelFormat new_pix_fmt = zm_pixformat_from_colours(p_colours, p_subpixelorder);
if (new_pix_fmt == AV_PIX_FMT_NONE) {
Error("Attempt to directly assign buffer with unexpected colours per pixel: %d", p_colours);
return;
}
// p_width * p_height * p_colours undercounts planar formats (YUV420P needs
// ~1.5x; with the GRAY8 alias colours=1 here would otherwise treat it as
// GRAY8 size W*H). Use av_image_* so the size and linesize match what the
// pixel format actually requires.
int av_size = av_image_get_buffer_size(new_pix_fmt, p_width, p_height, 32);
// FFALIGN the linesize to 32 to match the buffer's actual row stride
// (av_image_get_buffer_size with align=32 assumes this layout, and
// sws_scale writes in this layout). Storing the unaligned natural
// linesize would cause downstream consumers that walk the buffer at
// Image::linesize stride to drift sideways every row.
int av_linesize = FFALIGN(av_image_get_linesize(new_pix_fmt, p_width, 0), 32);
if (av_size < 0 || av_linesize < 0) {
Error("AssignDirect: av_image sizing failed for %s %ux%u", av_get_pix_fmt_name(new_pix_fmt), p_width, p_height);
return;
}
size_t new_buffer_size = static_cast<size_t>(av_size);
if ( buffer_size < new_buffer_size ) {
Error("Attempt to directly assign buffer from an undersized buffer of size: %zu, needed %dx%d %s = %zu",
buffer_size, p_width, p_height, av_get_pix_fmt_name(new_pix_fmt), new_buffer_size);
return;
}
if ( holdbuffer && buffer ) {
if ( new_buffer_size > allocation ) {
Error("Held buffer is undersized for assigned buffer");
return;
} else {
/* Copy into the held buffer */
if ( new_buffer != buffer ) {
(*fptr_imgbufcpy)(buffer, new_buffer, new_buffer_size);
}
/* Free the new buffer */
DumpBuffer(new_buffer, p_buffertype);
}
} else {
/* Free an existing buffer if any */
DumpImgBuffer();
allocation = buffer_size;
buffertype = p_buffertype;
buffer = new_buffer;
}
width = p_width;
height = p_height;
colours = p_colours;
linesize = static_cast<unsigned int>(av_linesize);
subpixelorder = p_subpixelorder;
imagePixFormat = new_pix_fmt;
pixels = width * height;
size = new_buffer_size;
update_function_pointers();
} // end void Image::AssignDirect
void Image::Assign(
const unsigned int p_width,
const unsigned int p_height,
const unsigned int p_colours,
const unsigned int p_subpixelorder,
const uint8_t* new_buffer,
const size_t buffer_size) {
if ( new_buffer == nullptr ) {
Error("Attempt to assign buffer from a NULL pointer");
return;
}
if ( !p_height || !p_width ) {
Error("Attempt to assign buffer with invalid width or height: %d %d", p_width, p_height);
return;
}
AVPixelFormat new_pix_fmt = zm_pixformat_from_colours(p_colours, p_subpixelorder);
if (new_pix_fmt == AV_PIX_FMT_NONE) {
Error("Attempt to assign buffer with unexpected colours per pixel: %d", p_colours);
return;
}
// Source and destination may use different alignment. Callers pass
// device-side buffers sized at align=1 (e.g. Camera::ImageSize() is now
// packed) while ZM's internal Image keeps a 32-byte-aligned layout for
// SIMD. Validate the source size against the packed layout and the
// destination/allocation against the aligned layout, then copy
// plane-by-plane so per-row padding doesn't read past the source.
int packed_size = av_image_get_buffer_size(new_pix_fmt, p_width, p_height, 1);
int aligned_size = av_image_get_buffer_size(new_pix_fmt, p_width, p_height, 32);
if (packed_size < 0 || aligned_size < 0) {
Error("av_image_get_buffer_size failed (%d/%d) for %s %ux%u",
packed_size, aligned_size, av_get_pix_fmt_name(new_pix_fmt), p_width, p_height);
return;
}
if (buffer_size < static_cast<size_t>(packed_size)) {
Error("Attempt to assign buffer from an undersized buffer of size: %zu (need %d packed)",
buffer_size, packed_size);
return;
}
const unsigned int new_size = static_cast<unsigned int>(aligned_size);
if ( !buffer || p_width != width || p_height != height || p_colours != colours || p_subpixelorder != subpixelorder ) {
if ( holdbuffer && buffer ) {
if ( new_size > allocation ) {
Error("Held buffer is undersized for assigned buffer");
return;
}
} else {
if ( (new_size > allocation) || !buffer ) {
DumpImgBuffer();
AllocImgBuffer(new_size);
}
}
width = p_width;
height = p_height;
pixels = width*height;
colours = p_colours;
subpixelorder = p_subpixelorder;
imagePixFormat = new_pix_fmt;
size = new_size;
// Keep linesize in sync with the new format. av_image_get_linesize is
// signed; the format validation above guarantees a recognised format
// here so the call will not return a negative value.
linesize = FFALIGN(av_image_get_linesize(new_pix_fmt, p_width, 0), 32);
}
if (new_buffer != buffer) {
// Plane-aware copy: src is laid out packed (align=1), dst is laid out
// aligned (align=32). av_image_copy walks each plane using its own
// src/dst linesize so per-row padding doesn't read past the source.
uint8_t *src_data[4] = {nullptr, nullptr, nullptr, nullptr};
int src_strides[4] = {0, 0, 0, 0};
uint8_t *dst_data[4] = {nullptr, nullptr, nullptr, nullptr};
int dst_strides[4] = {0, 0, 0, 0};
if (av_image_fill_arrays(src_data, src_strides,
const_cast<uint8_t *>(new_buffer),
new_pix_fmt, p_width, p_height, 1) < 0
|| av_image_fill_arrays(dst_data, dst_strides, buffer,
new_pix_fmt, p_width, p_height, 32) < 0) {
Error("Assign: av_image_fill_arrays failed for %s %ux%u",
av_get_pix_fmt_name(new_pix_fmt), p_width, p_height);
return;
}
av_image_copy(dst_data, dst_strides,
const_cast<const uint8_t **>(src_data), src_strides,
new_pix_fmt, p_width, p_height);
}
update_function_pointers();
}
void Image::Assign(const Image &image) {
// image.size is the AVPixelFormat-aware buffer size (av_image_get_buffer_size,
// including chroma planes for planar YUV); image.height * image.linesize is
// only the Y plane for planar formats and would silently leave U/V planes
// uninitialised in the destination — producing a Cb=Cr=0 "solid green" image
// when the dest is later interpreted as YCbCr.
unsigned int new_size = image.size;
if ( image.buffer == nullptr ) {
Error("Attempt to assign image with an empty buffer");
return;
}
if (image.imagePixFormat == AV_PIX_FMT_NONE) {
// Real cause is unset/unknown AVPixelFormat metadata, not the
// (colours, subpixelorder) values themselves — those can legitimately
// be 1/something for planar YUV. Report the underlying state.
Error("Attempt to assign image with missing AVPixelFormat metadata "
"(imagePixFormat=AV_PIX_FMT_NONE, legacy colours=%u subpixelorder=%u)",
image.colours, image.subpixelorder);
return;
}
if ( !buffer
|| image.width != width || image.height != height
|| image.colours != colours || image.subpixelorder != subpixelorder
) {
if ( holdbuffer && buffer ) {
if ( new_size > allocation ) {
Error("Held buffer is undersized for assigned buffer (need %u, have %lu)", new_size, allocation);
return;
}
} else {
if ((new_size > allocation) || !buffer) {
// DumpImgBuffer(); This is also done in AllocImgBuffer
AllocImgBuffer(new_size);
}
}
width = image.width;
height = image.height;
pixels = width*height;
colours = image.colours;
subpixelorder = image.subpixelorder;
imagePixFormat = image.imagePixFormat;
size = new_size;
linesize = image.linesize;
update_function_pointers();
}
if ( image.buffer != buffer ) {
if (image.linesize != linesize) {
// Dimensions/colours/subpixelorder all match but linesizes disagree.
// This happens when src and dst use different alignment conventions
// (e.g. dst is FFALIGN'd to 32 from av_image_get_buffer_size while
// src came from a held-buffer ctor with a packed driver stride).
//
// For planar formats (YUV420P/J420P/YUV422P/J422P) a per-row copy of
// just the Y plane would leave U/V uninitialised in the destination,
// producing solid-green output downstream. Plane-aware copying via
// av_image_copy_plane would be needed and the caller may as well
// reconvert; refuse explicitly.
if (zm_is_yuv420(image.imagePixFormat)
|| image.imagePixFormat == AV_PIX_FMT_YUV422P
|| image.imagePixFormat == AV_PIX_FMT_YUVJ422P) {
Error("Image::Assign: planar format %s with stride mismatch (src %d vs dst %d) "
"is not supported by the per-row copy; chroma planes would be lost. "
"Caller must convert or reallocate.",
av_get_pix_fmt_name(image.imagePixFormat), image.linesize, linesize);
return;
}
// Packed formats: copy min(src, dst) bytes per row so we never write
// past the destination row capacity AND never read past the source
// row capacity. Source padding (when src > dst) is discarded; dest
// padding (when src < dst) is left untouched. Both directions are
// safe — the previous flat-memcpy path silently over-read the
// source when image.linesize < linesize.
const unsigned int copy_bytes = std::min(image.linesize, linesize);
Debug(1, "Must copy line by line due to different line size %d != %d (copy %u)",
image.linesize, linesize, copy_bytes);
uint8_t *src_ptr = image.buffer;
uint8_t *dst_ptr = buffer;
for (unsigned int i=0; i< image.height; i++) {
(*fptr_imgbufcpy)(dst_ptr, src_ptr, copy_bytes);
src_ptr += image.linesize;
dst_ptr += linesize;
}
} else {
Debug(4, "Doing full copy line size %d == %d, %u bytes", image.linesize, linesize, size);
(*fptr_imgbufcpy)(buffer, image.buffer, size);
}
}
}
Image *Image::HighlightEdges(
Rgb colour,
unsigned int p_colours,
unsigned int p_subpixelorder,
const Box *limits
) {
if ( imagePixFormat != AV_PIX_FMT_GRAY8 ) {
Panic("Attempt to highlight image edges when colours = %d", colours);
}
/* Convert the colour's RGBA subpixel order into the image's subpixel order */
colour = rgb_convert(colour, p_subpixelorder);
AVPixelFormat p_pixfmt = zm_pixformat_from_colours(p_colours, p_subpixelorder);
/* Create a new image of the target format */
Image *high_image = new Image(width, height, p_colours, p_subpixelorder);
uint8_t* high_buff = high_image->WriteBuffer(width, height, p_colours, p_subpixelorder);
/* Set image to all black */
high_image->Clear();
unsigned int lo_x = limits ? limits->Lo().x_ : 0;
unsigned int lo_y = limits ? limits->Lo().y_ : 0;
unsigned int hi_x = limits ? limits->Hi().x_ : width - 1;
unsigned int hi_y = limits ? limits->Hi().y_ : height - 1;
// Source (this) is GRAY8 with src_linesize per row. The destination Image
// has its own (possibly different) row stride; use that for high_buff
// addressing so non-32-aligned widths don't overflow the destination on
// the last row, and use src_linesize when looking up neighbour pixels
// (p ± linesize) so we follow the actual row stride rather than `width`.
const unsigned int src_linesize = linesize;
const unsigned int dst_linesize = high_image->LineSize();
if ( p_pixfmt == AV_PIX_FMT_GRAY8 ) {
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
const uint8_t* p = buffer + (y * src_linesize) + lo_x;
uint8_t* phigh = high_buff + (y * dst_linesize) + lo_x;
for ( unsigned int x = lo_x; x <= hi_x; x++, p++, phigh++ ) {
bool edge = false;
if ( *p ) {
edge = (x > 0 && !*(p-1)) || (x < (width-1) && !*(p+1))
|| (y > 0 && !*(p-src_linesize)) || (y < (height-1) && !*(p+src_linesize));
}
if ( edge ) {
*phigh = colour;
}
}
}
} else if ( zm_is_rgb24(p_pixfmt) ) {
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
const uint8_t* p = buffer + (y * src_linesize) + lo_x;
uint8_t* phigh = high_buff + (y * dst_linesize) + lo_x * 3;
for ( unsigned int x = lo_x; x <= hi_x; x++, p++, phigh += 3 ) {
bool edge = false;
if ( *p ) {
edge = (x > 0 && !*(p-1)) || (x < (width-1) && !*(p+1))
|| (y > 0 && !*(p-src_linesize)) || (y < (height-1) && !*(p+src_linesize));
}
if ( edge ) {
RED_PTR_RGBA(phigh) = RED_VAL_RGBA(colour);
GREEN_PTR_RGBA(phigh) = GREEN_VAL_RGBA(colour);
BLUE_PTR_RGBA(phigh) = BLUE_VAL_RGBA(colour);
}
}
}
} else if ( zm_is_rgb32(p_pixfmt) ) {
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
const uint8_t* p = buffer + (y * src_linesize) + lo_x;
Rgb* phigh = (Rgb*)(high_buff + (y * dst_linesize) + (lo_x << 2));
for ( unsigned int x = lo_x; x <= hi_x; x++, p++, phigh++ ) {
bool edge = false;
if ( *p ) {
edge = (x > 0 && !*(p-1)) || (x < (width-1) && !*(p+1))
|| (y > 0 && !*(p-src_linesize)) || (y < (height-1) && !*(p+src_linesize));
}
if ( edge ) {
*phigh = colour;
}
}
}
} else if ( zm_is_yuv420(p_pixfmt) ) {
// Single alarm colour over a transparent (Y=0) background; Overlay() onto
// a YUV420 image keys on a non-zero luma marker. Write the colour's luma
// at each edge pixel and its chroma at the shared 2x2 chroma sample.
const YUV yuv = brg_to_yuv(colour);
const uint8_t Yc = Y_VAL(yuv), Uc = U_VAL(yuv), Vc = V_VAL(yuv);
uint8_t *hplane[4] = {};
int hstride[4] = {};
if (av_image_fill_arrays(hplane, hstride, high_buff, p_pixfmt, width, height, 32) < 0) {
Error("HighlightEdges: av_image_fill_arrays failed for YUV420 %ux%u", width, height);
return high_image;
}
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
const uint8_t* p = buffer + (y * src_linesize) + lo_x;
for ( unsigned int x = lo_x; x <= hi_x; x++, p++ ) {
bool edge = false;
if ( *p ) {
edge = (x > 0 && !*(p-1)) || (x < (width-1) && !*(p+1))
|| (y > 0 && !*(p-src_linesize)) || (y < (height-1) && !*(p+src_linesize));
}
if ( edge ) {
hplane[0][y * hstride[0] + x] = Yc ? Yc : 1; // keep the luma marker non-zero
hplane[1][(y / 2) * hstride[1] + (x / 2)] = Uc;
hplane[2][(y / 2) * hstride[2] + (x / 2)] = Vc;
}
}
}
}
return high_image;
}
bool Image::ReadRaw(const std::string &filename) {
FILE *infile;
if ( (infile = fopen(filename.c_str(), "rb")) == nullptr ) {
Error("Can't open %s: %s", filename.c_str(), strerror(errno));
return false;
}
struct stat statbuf;
if (fstat(fileno(infile), &statbuf) < 0) {
fclose(infile);
Error("Can't fstat %s: %s", filename.c_str(), strerror(errno));
return false;
}
if ((unsigned int)statbuf.st_size != size) {
fclose(infile);
Error("Raw file size mismatch, expected %d bytes, found %jd", size, static_cast<intmax_t>(statbuf.st_size));
return false;
}
if (fread(buffer, size, 1, infile) < 1) {
fclose(infile);
Error("Unable to read from '%s': %s", filename.c_str(), strerror(errno));
return false;
}
fclose(infile);
return true;
}
bool Image::WriteRaw(const std::string &filename) const {
FILE *outfile;
if ((outfile = fopen(filename.c_str(), "wb")) == nullptr) {
Error("Can't open %s: %s", filename.c_str(), strerror(errno));
return false;
}
if (fwrite(buffer, size, 1, outfile) != 1) {
Error("Unable to write to '%s': %s", filename.c_str(), strerror(errno));
fclose(outfile);
return false;
}
fclose(outfile);
return true;
}
bool Image::ReadJpeg(const std::string &filename, unsigned int p_colours, unsigned int p_subpixelorder) {
unsigned int new_width, new_height, new_colours, new_subpixelorder;
if (!readjpg_dcinfo) {
readjpg_dcinfo = new jpeg_decompress_struct;
readjpg_dcinfo->err = jpeg_std_error(&jpg_err.pub);
jpg_err.pub.error_exit = zm_jpeg_error_exit;
jpg_err.pub.emit_message = zm_jpeg_emit_message;
jpeg_create_decompress(readjpg_dcinfo);
}
FILE *infile;
if ((infile = fopen(filename.c_str(), "rb")) == nullptr) {
Error("Can't open %s: %s", filename.c_str(), strerror(errno));
return false;
}
if (setjmp(jpg_err.setjmp_buffer)) {
jpeg_abort_decompress(readjpg_dcinfo);
fclose(infile);
return false;
}
/* Computed after setjmp() so it cannot be clobbered by the longjmp() out of the error handler */
const AVPixelFormat p_pixfmt = zm_pixformat_from_colours(p_colours, p_subpixelorder);
jpeg_stdio_src(readjpg_dcinfo, infile);
jpeg_read_header(readjpg_dcinfo, true);
if ((readjpg_dcinfo->num_components != 1) && (readjpg_dcinfo->num_components != 3)) {
Error("Unexpected colours when reading jpeg image: %d", colours);
jpeg_abort_decompress(readjpg_dcinfo);
fclose(infile);
return false;
}
/* Check if the image has at least one huffman table defined. If not, use the standard ones */
/* This is required for the MJPEG capture palette of USB devices */
if (readjpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr) {
zm_use_std_huff_tables(readjpg_dcinfo);
}
new_width = readjpg_dcinfo->image_width;
new_height = readjpg_dcinfo->image_height;
if ((width != new_width) || (height != new_height)) {
Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", width, height, new_width, new_height);
width = new_width;
height = new_height;
}
if (p_pixfmt == AV_PIX_FMT_GRAY8) {
readjpg_dcinfo->out_color_space = JCS_GRAYSCALE;
new_colours = ZM_COLOUR_GRAY8;
new_subpixelorder = ZM_SUBPIX_ORDER_NONE;
} else if (zm_is_rgb32(p_pixfmt)) {
#ifdef JCS_EXTENSIONS
new_colours = ZM_COLOUR_RGB32;
if (p_pixfmt == AV_PIX_FMT_BGRA) {
readjpg_dcinfo->out_color_space = JCS_EXT_BGRX;
new_subpixelorder = ZM_SUBPIX_ORDER_BGRA;
} else if (p_pixfmt == AV_PIX_FMT_ARGB) {
readjpg_dcinfo->out_color_space = JCS_EXT_XRGB;
new_subpixelorder = ZM_SUBPIX_ORDER_ARGB;
} else if (p_pixfmt == AV_PIX_FMT_ABGR) {
readjpg_dcinfo->out_color_space = JCS_EXT_XBGR;
new_subpixelorder = ZM_SUBPIX_ORDER_ABGR;
} else {
/* Assume RGBA */
readjpg_dcinfo->out_color_space = JCS_EXT_RGBX;
new_subpixelorder = ZM_SUBPIX_ORDER_RGBA;
}
#else
Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead.");
new_colours = ZM_COLOUR_RGB24;
readjpg_dcinfo->out_color_space = JCS_RGB;
new_subpixelorder = ZM_SUBPIX_ORDER_RGB;
#endif
} else {
/* Assume RGB24/BGR24 */
new_colours = ZM_COLOUR_RGB24;
if (p_pixfmt == AV_PIX_FMT_BGR24) {
#ifdef JCS_EXTENSIONS
readjpg_dcinfo->out_color_space = JCS_EXT_BGR;
new_subpixelorder = ZM_SUBPIX_ORDER_BGR;
#else
Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead.");
readjpg_dcinfo->out_color_space = JCS_RGB;
new_subpixelorder = ZM_SUBPIX_ORDER_RGB;
#endif
} else {
/* Assume RGB */
/*
#ifdef JCS_EXTENSIONS
cinfo->out_color_space = JCS_EXT_RGB;
#else
cinfo->out_color_space = JCS_RGB;
#endif
*/
readjpg_dcinfo->out_color_space = JCS_RGB;
new_subpixelorder = ZM_SUBPIX_ORDER_RGB;
}
} // end format dispatch
if (WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr) {
Error("Failed requesting writeable buffer for reading JPEG image.");
jpeg_abort_decompress(readjpg_dcinfo);
fclose(infile);
return false;
}
jpeg_start_decompress(readjpg_dcinfo);
JSAMPROW row_pointer = buffer;
while (readjpg_dcinfo->output_scanline < readjpg_dcinfo->output_height) {
jpeg_read_scanlines(readjpg_dcinfo, &row_pointer, 1);
row_pointer += linesize;
}
jpeg_finish_decompress(readjpg_dcinfo);
fclose(infile);
return true;
}
// Multiple calling formats to permit inclusion (or not) of non blocking, quality_override and timestamp (exif), with suitable defaults.
// Note quality=zero means default
bool Image::WriteJpeg(const std::string &filename, int quality_override) const {
return Image::WriteJpeg(filename, quality_override, {}, false);
}
bool Image::WriteJpeg(const std::string &filename) const {
return Image::WriteJpeg(filename, 0, {}, false);
}
bool Image::WriteJpeg(const std::string &filename, bool on_blocking_abort) const {
return Image::WriteJpeg(filename, 0, {}, on_blocking_abort);
}
bool Image::WriteJpeg(const std::string &filename, SystemTimePoint timestamp) const {
return Image::WriteJpeg(filename, 0, timestamp, false);
}
bool Image::WriteJpeg(const std::string &filename, int quality_override, SystemTimePoint timestamp) const {
return Image::WriteJpeg(filename, quality_override, timestamp, false);
}
bool Image::WriteJpeg(const std::string &filename,
const int &quality_override,
SystemTimePoint timestamp,
bool on_blocking_abort) const {
if (config.colour_jpeg_files && (imagePixFormat == AV_PIX_FMT_GRAY8)) {
Image temp_image(*this);
temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
return temp_image.WriteJpeg(filename, quality_override, timestamp, on_blocking_abort);
}
// jpeg libs are not thread safe
std::unique_lock<std::mutex> lck(jpeg_mutex);
int quality = quality_override ? quality_override : config.jpeg_file_quality;
jpeg_compress_struct *cinfo = writejpg_ccinfo[quality];
FILE *outfile = nullptr;
int raw_fd = 0;
if (!cinfo) {
cinfo = writejpg_ccinfo[quality] = new jpeg_compress_struct;
cinfo->err = jpeg_std_error(&jpg_err.pub);
jpeg_create_compress(cinfo);
}
if (!on_blocking_abort) {
jpg_err.pub.error_exit = zm_jpeg_error_exit;
jpg_err.pub.emit_message = zm_jpeg_emit_message;
} else {
jpg_err.pub.error_exit = zm_jpeg_error_silent;
jpg_err.pub.emit_message = zm_jpeg_emit_silence;
if (setjmp(jpg_err.setjmp_buffer)) {
jpeg_abort_compress(cinfo);
Debug(1,
"Aborted a write mid-stream and %s and %d",
(outfile == nullptr) ? "closing file" : "file not opened",
raw_fd);
if (raw_fd)
close(raw_fd);
if (outfile)
fclose(outfile);
return false;
}
}
if (!on_blocking_abort) {
raw_fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
} else {
raw_fd = open(filename.c_str(), O_WRONLY | O_NONBLOCK | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
}
if (raw_fd < 0)
return false;
outfile = fdopen(raw_fd, "wb");
if (outfile == nullptr) {
close(raw_fd);
return false;
}
struct flock fl({});
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
if (fcntl(raw_fd, F_SETLKW, &fl) == -1) {
Error("Couldn't get lock on %s, continuing", filename.c_str());
}
jpeg_stdio_dest(cinfo, outfile);
cinfo->image_width = width; /* image width and height, in pixels */
cinfo->image_height = height;
if (imagePixFormat == AV_PIX_FMT_GRAY8) {
cinfo->input_components = 1;
cinfo->in_color_space = JCS_GRAYSCALE;
} else if (zm_is_rgb32(imagePixFormat)) {
#ifdef JCS_EXTENSIONS
cinfo->input_components = 4;
if (imagePixFormat == AV_PIX_FMT_RGBA) {
cinfo->in_color_space = JCS_EXT_RGBX;
} else if (imagePixFormat == AV_PIX_FMT_BGRA) {
cinfo->in_color_space = JCS_EXT_BGRX;
} else if (imagePixFormat == AV_PIX_FMT_ARGB) {
cinfo->in_color_space = JCS_EXT_XRGB;
} else if (imagePixFormat == AV_PIX_FMT_ABGR) {
cinfo->in_color_space = JCS_EXT_XBGR;
} else {
Warning("Unknown subpixelorder %d", subpixelorder);
/* Assume RGBA */
cinfo->in_color_space = JCS_EXT_RGBX;
}
#else
Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source");
jpeg_abort_compress(cinfo);
fl.l_type = F_UNLCK;
fcntl(raw_fd, F_SETLK, &fl);
fclose(outfile);
return false;
#endif
} else if (imagePixFormat == AV_PIX_FMT_YUV420P || imagePixFormat == AV_PIX_FMT_YUVJ420P
|| imagePixFormat == AV_PIX_FMT_YUV422P || imagePixFormat == AV_PIX_FMT_YUVJ422P) {
cinfo->input_components = 3;
cinfo->in_color_space = JCS_YCbCr;
} else {
/* Assume RGB24/BGR24 */
cinfo->input_components = 3;
if (imagePixFormat == AV_PIX_FMT_BGR24) {
#ifdef JCS_EXTENSIONS
cinfo->in_color_space = JCS_EXT_BGR;
#else
Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source");
jpeg_abort_compress(cinfo);
fl.l_type = F_UNLCK;
fcntl(raw_fd, F_SETLK, &fl);
fclose(outfile);
return false;
#endif
} else {
/* Assume RGB */
cinfo->in_color_space = JCS_RGB;
}
} // end format dispatch
jpeg_set_defaults(cinfo);
jpeg_set_quality(cinfo, quality, FALSE);
cinfo->dct_method = JDCT_FASTEST;
jpeg_start_compress(cinfo, TRUE);
if (config.add_jpeg_comments && !annotation_.empty()) {
jpeg_write_marker(cinfo, JPEG_COM, reinterpret_cast<const JOCTET *>(annotation_.c_str()), annotation_.size());
}
// If we have a non-zero time (meaning a parameter was passed in), then form a simple exif segment with that time as DateTimeOriginal and SubsecTimeOriginal
// No timestamp just leave off the exif section.
if (timestamp.time_since_epoch() > Seconds(0)) {
#define EXIFTIMES_MS_OFFSET 0x36 // three decimal digits for milliseconds
#define EXIFTIMES_MS_LEN 0x03
#define EXIFTIMES_OFFSET 0x3E // 19 characters format '2015:07:21 13:14:45' not including quotes
#define EXIFTIMES_LEN 0x13 // = 19
#define EXIF_CODE 0xE1
// This is a lot of stuff to allocate on the stack. Recommend char *timebuf[64];
char timebuf[64], msbuf[64];
tm timestamp_tm = {};
time_t timestamp_t = std::chrono::system_clock::to_time_t(timestamp);
strftime(timebuf, sizeof timebuf, "%Y:%m:%d %H:%M:%S", localtime_r(&timestamp_t, &timestamp_tm));
Seconds ts_sec = std::chrono::duration_cast<Seconds>(timestamp.time_since_epoch());
Microseconds ts_usec = std::chrono::duration_cast<Microseconds>(timestamp.time_since_epoch() - ts_sec);
// we only use milliseconds because that's all defined in exif, but this is the whole microseconds because we have it
snprintf(msbuf, sizeof msbuf, "%06d", static_cast<int32>(ts_usec.count()));
unsigned char exiftimes[82] = {
0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00,
0x69, 0x87, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x03, 0x90, 0x02, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x91, 0x92,
0x02, 0x00, 0x04, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0x00
};
memcpy(&exiftimes[EXIFTIMES_OFFSET], timebuf, EXIFTIMES_LEN);
memcpy(&exiftimes[EXIFTIMES_MS_OFFSET], msbuf, EXIFTIMES_MS_LEN);
jpeg_write_marker(cinfo, EXIF_CODE, (const JOCTET *) exiftimes, sizeof(exiftimes));
}
if (imagePixFormat == AV_PIX_FMT_YUV420P || imagePixFormat == AV_PIX_FMT_YUVJ420P
|| imagePixFormat == AV_PIX_FMT_YUV422P || imagePixFormat == AV_PIX_FMT_YUVJ422P) {
// Planar YUV: interleave Y/U/V plane rows into per-scanline YCbCr triples.
// The previous code here unpacked the buffer as if it were packed YUYV
// 4:2:2, which read way past the W*H*1.5 YUV420P buffer end on every
// frame — a latent crash that became reachable when image_buffer was
// pinned to YUV420P. Use av_image_fill_arrays for plane offsets so this
// works for both 4:2:0 and 4:2:2 layouts.
uint8_t *plane[4] = {nullptr, nullptr, nullptr, nullptr};
int plane_linesizes[4] = {0, 0, 0, 0};
if (av_image_fill_arrays(plane, plane_linesizes, buffer, imagePixFormat, width, height, 32) < 0) {
Error("WriteJpeg: av_image_fill_arrays failed for %s", av_get_pix_fmt_name(imagePixFormat));
jpeg_abort_compress(cinfo);
fl.l_type = F_UNLCK;
fcntl(raw_fd, F_SETLK, &fl);
fclose(outfile);
return false;
}
const bool is_422 = (imagePixFormat == AV_PIX_FMT_YUV422P || imagePixFormat == AV_PIX_FMT_YUVJ422P);
const unsigned chroma_v_shift = is_422 ? 0u : 1u;
std::vector<uint8_t> tmprow(static_cast<size_t>(width) * 3);
JSAMPROW row_ptr = tmprow.data();
while (cinfo->next_scanline < cinfo->image_height) {
const unsigned y = cinfo->next_scanline;
const unsigned cy = y >> chroma_v_shift;
const uint8_t *yrow = plane[0] + static_cast<size_t>(y) * plane_linesizes[0];
const uint8_t *urow = plane[1] + static_cast<size_t>(cy) * plane_linesizes[1];
const uint8_t *vrow = plane[2] + static_cast<size_t>(cy) * plane_linesizes[2];
for (unsigned x = 0; x < width; ++x) {
tmprow[x * 3 + 0] = yrow[x];
tmprow[x * 3 + 1] = urow[x >> 1];
tmprow[x * 3 + 2] = vrow[x >> 1];
}
jpeg_write_scanlines(cinfo, &row_ptr, 1);
}
} else {
JSAMPROW row_pointer = buffer; /* pointer to a single row */
while (cinfo->next_scanline < cinfo->image_height) {
jpeg_write_scanlines(cinfo, &row_pointer, 1);
row_pointer += linesize;
}
}
jpeg_finish_compress(cinfo);
fl.l_type = F_UNLCK; /* set to unlock same region */
if (fcntl(raw_fd, F_SETLK, &fl) == -1) {
Error("Failed to unlock %s", filename.c_str());
}
fclose(outfile);
return true;
}
bool Image::DecodeJpeg(const JOCTET *inbuffer, int inbuffer_size, unsigned int p_colours, unsigned int p_subpixelorder) {
unsigned int new_width, new_height, new_colours, new_subpixelorder;
if (!decodejpg_dcinfo) {
decodejpg_dcinfo = new jpeg_decompress_struct;
decodejpg_dcinfo->err = jpeg_std_error(&jpg_err.pub);
jpg_err.pub.error_exit = zm_jpeg_error_exit;
jpg_err.pub.emit_message = zm_jpeg_emit_message;
jpeg_create_decompress(decodejpg_dcinfo);
}
if (setjmp(jpg_err.setjmp_buffer)) {
jpeg_abort_decompress(decodejpg_dcinfo);
return false;
}
/* Computed after setjmp() so it cannot be clobbered by the longjmp() out of the error handler */
const AVPixelFormat p_pixfmt = zm_pixformat_from_colours(p_colours, p_subpixelorder);
zm_jpeg_mem_src(decodejpg_dcinfo, inbuffer, inbuffer_size);
jpeg_read_header(decodejpg_dcinfo, TRUE);
if ((decodejpg_dcinfo->num_components != 1) && (decodejpg_dcinfo->num_components != 3)) {
Error("Unexpected colours when reading jpeg image: %d", colours);
jpeg_abort_decompress(decodejpg_dcinfo);
return false;
}
/* Check if the image has at least one huffman table defined. If not, use the standard ones */
/* This is required for the MJPEG capture palette of USB devices */
if (decodejpg_dcinfo->dc_huff_tbl_ptrs[0] == nullptr) {
zm_use_std_huff_tables(decodejpg_dcinfo);
}
new_width = decodejpg_dcinfo->image_width;
new_height = decodejpg_dcinfo->image_height;
if ((width != new_width) || (height != new_height)) {
Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u",
width, height, new_width, new_height);
}
if (p_pixfmt == AV_PIX_FMT_GRAY8) {
decodejpg_dcinfo->out_color_space = JCS_GRAYSCALE;
new_colours = ZM_COLOUR_GRAY8;
new_subpixelorder = ZM_SUBPIX_ORDER_NONE;
} else if (zm_is_rgb32(p_pixfmt)) {
#ifdef JCS_EXTENSIONS
new_colours = ZM_COLOUR_RGB32;
if (p_pixfmt == AV_PIX_FMT_BGRA) {
decodejpg_dcinfo->out_color_space = JCS_EXT_BGRX;
new_subpixelorder = ZM_SUBPIX_ORDER_BGRA;
} else if (p_pixfmt == AV_PIX_FMT_ARGB) {
decodejpg_dcinfo->out_color_space = JCS_EXT_XRGB;
new_subpixelorder = ZM_SUBPIX_ORDER_ARGB;
} else if (p_pixfmt == AV_PIX_FMT_ABGR) {
decodejpg_dcinfo->out_color_space = JCS_EXT_XBGR;
new_subpixelorder = ZM_SUBPIX_ORDER_ABGR;
} else {
/* Assume RGBA */
decodejpg_dcinfo->out_color_space = JCS_EXT_RGBX;
new_subpixelorder = ZM_SUBPIX_ORDER_RGBA;
}
#else
Warning("libjpeg-turbo is required for reading a JPEG directly into a RGB32 buffer, reading into a RGB24 buffer instead.");
new_colours = ZM_COLOUR_RGB24;
decodejpg_dcinfo->out_color_space = JCS_RGB;
new_subpixelorder = ZM_SUBPIX_ORDER_RGB;
#endif
} else {
/* Assume RGB24/BGR24 */
new_colours = ZM_COLOUR_RGB24;
if (p_pixfmt == AV_PIX_FMT_BGR24) {
#ifdef JCS_EXTENSIONS
decodejpg_dcinfo->out_color_space = JCS_EXT_BGR;
new_subpixelorder = ZM_SUBPIX_ORDER_BGR;
#else
Warning("libjpeg-turbo is required for reading a JPEG directly into a BGR24 buffer, reading into a RGB24 buffer instead.");
decodejpg_dcinfo->out_color_space = JCS_RGB;
new_subpixelorder = ZM_SUBPIX_ORDER_RGB;
#endif
} else {
/* Assume RGB */
/*
#ifdef JCS_EXTENSIONS
cinfo->out_color_space = JCS_EXT_RGB;
#else
cinfo->out_color_space = JCS_RGB;
#endif
*/
decodejpg_dcinfo->out_color_space = JCS_RGB;
new_subpixelorder = ZM_SUBPIX_ORDER_RGB;
}
} // end format dispatch
if (WriteBuffer(new_width, new_height, new_colours, new_subpixelorder) == nullptr) {
Error("Failed requesting writeable buffer for reading JPEG image.");
jpeg_abort_decompress(decodejpg_dcinfo);
return false;
}
jpeg_start_decompress(decodejpg_dcinfo);
JSAMPROW row_pointer = buffer; /* pointer to a single row */
while (decodejpg_dcinfo->output_scanline < decodejpg_dcinfo->output_height) {
jpeg_read_scanlines(decodejpg_dcinfo, &row_pointer, 1);
row_pointer += linesize;
}
jpeg_finish_decompress(decodejpg_dcinfo);
return true;
}
bool Image::EncodeJpeg(JOCTET *outbuffer, size_t *outbuffer_size, int quality_override) const {
if ( config.colour_jpeg_files && (imagePixFormat == AV_PIX_FMT_GRAY8) ) {
Image temp_image(*this);
temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
return temp_image.EncodeJpeg(outbuffer, outbuffer_size, quality_override);
}
std::unique_lock<std::mutex> lck(jpeg_mutex);
int quality = quality_override ? quality_override : config.jpeg_stream_quality;
struct jpeg_compress_struct *cinfo = encodejpg_ccinfo[quality];
if ( !cinfo ) {
cinfo = encodejpg_ccinfo[quality] = new jpeg_compress_struct;
cinfo->err = jpeg_std_error(&jpg_err.pub);
jpg_err.pub.error_exit = zm_jpeg_error_exit;
jpg_err.pub.emit_message = zm_jpeg_emit_message;
jpeg_create_compress(cinfo);
}
zm_jpeg_mem_dest(cinfo, outbuffer, outbuffer_size);
cinfo->image_width = width; /* image width and height, in pixels */
cinfo->image_height = height;
const bool is_yuv_planar =
(imagePixFormat == AV_PIX_FMT_YUV420P || imagePixFormat == AV_PIX_FMT_YUVJ420P
|| imagePixFormat == AV_PIX_FMT_YUV422P || imagePixFormat == AV_PIX_FMT_YUVJ422P);
if (imagePixFormat == AV_PIX_FMT_GRAY8) {
cinfo->input_components = 1;
cinfo->in_color_space = JCS_GRAYSCALE;
} else if (zm_is_rgb32(imagePixFormat)) {
#ifdef JCS_EXTENSIONS
cinfo->input_components = 4;
if (imagePixFormat == AV_PIX_FMT_RGBA) {
cinfo->in_color_space = JCS_EXT_RGBX;
} else if (imagePixFormat == AV_PIX_FMT_BGRA) {
cinfo->in_color_space = JCS_EXT_BGRX;
} else if (imagePixFormat == AV_PIX_FMT_ARGB) {
cinfo->in_color_space = JCS_EXT_XRGB;
} else if (imagePixFormat == AV_PIX_FMT_ABGR) {
cinfo->in_color_space = JCS_EXT_XBGR;
} else {
Warning("unknown subpixelorder %d", subpixelorder);
/* Assume RGBA */
cinfo->in_color_space = JCS_EXT_RGBX;
}
#else
Error("libjpeg-turbo is required for JPEG encoding directly from RGB32 source");
jpeg_abort_compress(cinfo);
return false;
#endif
} else if (is_yuv_planar) {
cinfo->input_components = 3;
cinfo->in_color_space = JCS_YCbCr;
} else {
/* Assume RGB24/BGR24 */
cinfo->input_components = 3;
if (imagePixFormat == AV_PIX_FMT_BGR24) {
#ifdef JCS_EXTENSIONS
cinfo->in_color_space = JCS_EXT_BGR;
#else
Error("libjpeg-turbo is required for JPEG encoding directly from BGR24 source");
jpeg_abort_compress(cinfo);
return false;
#endif
} else {
/* Assume RGB */
cinfo->in_color_space = JCS_RGB;
}
} // end format dispatch
jpeg_set_defaults(cinfo);
jpeg_set_quality(cinfo, quality, FALSE);
cinfo->dct_method = JDCT_FASTEST;
jpeg_start_compress(cinfo, TRUE);
if (is_yuv_planar) {
// Planar YUV: interleave Y/U/V plane rows into per-scanline YCbCr triples
// for libjpeg's JCS_YCbCr input. av_image_fill_arrays computes plane
// pointers and per-plane linesizes from the AVPixelFormat, so this works
// for both 4:2:0 (YUV420P/YUVJ420P) and 4:2:2 (YUV422P/YUVJ422P) without
// hand-coded plane offsets.
uint8_t *plane[4] = {nullptr, nullptr, nullptr, nullptr};
int plane_linesizes[4] = {0, 0, 0, 0};
if (av_image_fill_arrays(plane, plane_linesizes, buffer, imagePixFormat, width, height, 32) < 0) {
Error("EncodeJpeg: av_image_fill_arrays failed for %s", av_get_pix_fmt_name(imagePixFormat));
jpeg_abort_compress(cinfo);
return false;
}
const bool is_422 = (imagePixFormat == AV_PIX_FMT_YUV422P || imagePixFormat == AV_PIX_FMT_YUVJ422P);
const unsigned chroma_v_shift = is_422 ? 0u : 1u; // 4:2:0 halves vertical too
std::vector<uint8_t> tmprow(static_cast<size_t>(width) * 3);
JSAMPROW row_ptr = tmprow.data();
while (cinfo->next_scanline < cinfo->image_height) {
const unsigned y = cinfo->next_scanline;
const unsigned cy = y >> chroma_v_shift;
const uint8_t *yrow = plane[0] + static_cast<size_t>(y) * plane_linesizes[0];
const uint8_t *urow = plane[1] + static_cast<size_t>(cy) * plane_linesizes[1];
const uint8_t *vrow = plane[2] + static_cast<size_t>(cy) * plane_linesizes[2];
for (unsigned x = 0; x < width; ++x) {
tmprow[x * 3 + 0] = yrow[x];
tmprow[x * 3 + 1] = urow[x >> 1]; // chroma is 2x horizontal subsampled in 4:2:0/4:2:2
tmprow[x * 3 + 2] = vrow[x >> 1];
}
jpeg_write_scanlines(cinfo, &row_ptr, 1);
}
} else {
JSAMPROW row_pointer = buffer;
while ( cinfo->next_scanline < cinfo->image_height ) {
jpeg_write_scanlines(cinfo, &row_pointer, 1);
row_pointer += linesize;
}
}
jpeg_finish_compress(cinfo);
return true;
}
#if HAVE_ZLIB_H
bool Image::Unzip( const Bytef *inbuffer, unsigned long inbuffer_size ) {
unsigned long zip_size = size;
int result = uncompress( buffer, &zip_size, inbuffer, inbuffer_size );
if ( result != Z_OK ) {
Error("Unzip failed, result = %d", result);
return false;
}
if ( zip_size != (unsigned int)size ) {
Error("Unzip failed, size mismatch, expected %d bytes, got %ld", size, zip_size);
return false;
}
return true;
}
bool Image::Zip( Bytef *outbuffer, unsigned long *outbuffer_size, int compression_level ) const {
int result = compress2( outbuffer, outbuffer_size, buffer, size, compression_level );
if ( result != Z_OK ) {
Error("Zip failed, result = %d", result);
return false;
}
return true;
}
#endif // HAVE_ZLIB_H
bool Image::Crop( unsigned int lo_x, unsigned int lo_y, unsigned int hi_x, unsigned int hi_y ) {
unsigned int new_width = (hi_x-lo_x)+1;
unsigned int new_height = (hi_y-lo_y)+1;
if ( lo_x > hi_x || lo_y > hi_y ) {
Error("Invalid or reversed crop region %d,%d -> %d,%d", lo_x, lo_y, hi_x, hi_y);
return false;
}
if ( hi_x > (width-1) || ( hi_y > (height-1) ) ) {
Error("Attempting to crop outside image, %d,%d -> %d,%d not in %d,%d",
lo_x, lo_y, hi_x, hi_y, width-1, height-1);
return false;
}
if ( (new_width == width) && (new_height == height) ) {
return true;
}
unsigned int new_stride = new_width * colours;
unsigned int new_size = new_stride * new_height;
uint8_t *new_buffer = AllocBuffer(new_size);
for ( unsigned int y = lo_y, ny = 0; y <= hi_y; y++, ny++ ) {
unsigned char *pbuf = &buffer[((y*linesize)+(lo_x*colours))];
unsigned char *pnbuf = &new_buffer[ny*new_stride];
memcpy(pnbuf, pbuf, new_stride);
}
AssignDirect(new_width, new_height, colours, subpixelorder, new_buffer, new_size, ZM_BUFTYPE_ZM);
return true;
}
bool Image::Crop(const Box &limits) {
return Crop(limits.Lo().x_, limits.Lo().y_, limits.Hi().x_, limits.Hi().y_);
}
/* Far from complete */
/* Need to implement all possible of overlays possible */
void Image::Overlay( const Image &image ) {
if ( !(width == image.width && height == image.height) ) {
Panic("Attempt to overlay different sized images, expected %dx%d, got %dx%d",
width, height, image.width, image.height);
}
// Pre-AVPixelFormat the (colours, subpixelorder) pair was the canonical
// format identifier and a colours-match-but-subpixelorder-mismatch warning
// was meaningful. With imagePixFormat now canonical the old check fires
// false positives whenever GRAY8 (colours=1, subpixelorder=NONE=2) is
// overlaid onto YUV420P (colours=1 due to the GRAY8/YUV420P alias
// collision, subpixelorder=YUV420P=11) — the dispatch below handles this
// case correctly via zm_bytes_per_pixel(...) == 1. Only warn if the
// AVPixelFormat actually matches but the ZM metadata diverges, which would
// indicate a real format-tracking bug.
if (imagePixFormat == image.imagePixFormat
&& (colours != image.colours || subpixelorder != image.subpixelorder)) {
Warning("Overlay: imagePixFormat matches (%s) but ZM (colours,subpixelorder) "
"diverges: (%u,%u) vs (%u,%u) — stale metadata?",
av_get_pix_fmt_name(imagePixFormat),
colours, subpixelorder, image.colours, image.subpixelorder);
}
// Drive every branch row-by-row using each image's own linesize. Walking
// linearly with `buffer + size` as the end was unsafe in two ways:
// (a) source and destination linesizes can differ (e.g. one came in via
// AssignDirect with a held buffer at a non-FFALIGN stride), so a shared
// index drifted across rows; (b) the destination's `size` includes chroma
// planes for planar YUV destinations, so iterating to `buffer + size`
// walked past the source's Y plane and clobbered the destination's
// chroma. After Colourise() the destination's linesize is updated to the
// new format's stride, so we re-read it inside each branch.
/* YUV420 on top of YUV420 - copy luma + chroma using luma as the mask */
if ( zm_is_yuv420(imagePixFormat) && zm_is_yuv420(image.imagePixFormat) ) {
// The overlay (a zone alarm highlight) is built in the target's format
// with a Clear()ed (Y=0) transparent background, so a non-zero source
// luma marks a pixel to paint. Copy that luma, and copy the shared
// chroma sample whenever any of the luma pixels it covers is marked.
uint8_t *dplane[4] = {};
int dstride[4] = {};
const uint8_t *splane[4] = {};
int sstride[4] = {};
if (av_image_fill_arrays(dplane, dstride, buffer, imagePixFormat, width, height, 32) < 0
|| av_image_fill_arrays(const_cast<uint8_t **>(splane), sstride, image.buffer,
image.imagePixFormat, width, height, 32) < 0) {
Error("Overlay: av_image_fill_arrays failed for YUV420 %ux%u", width, height);
return;
}
for (unsigned int y = 0; y < height; y++) {
const uint8_t *psrc = splane[0] + y * sstride[0];
uint8_t *pdest = dplane[0] + y * dstride[0];
for (unsigned int x = 0; x < width; x++) {
if (psrc[x]) pdest[x] = psrc[x];
}
}
const unsigned int cw = (width + 1) / 2;
const unsigned int ch = (height + 1) / 2;
for (unsigned int cy = 0; cy < ch; cy++) {
for (unsigned int cx = 0; cx < cw; cx++) {
bool marked = false;
for (unsigned int dy = 0; dy < 2 && !marked; dy++) {
const unsigned int ly = cy * 2 + dy;
if (ly >= height) break;
for (unsigned int dx = 0; dx < 2; dx++) {
const unsigned int lx = cx * 2 + dx;
if (lx < width && splane[0][ly * sstride[0] + lx]) { marked = true; break; }
}
}
if (marked) {
dplane[1][cy * dstride[1] + cx] = splane[1][cy * sstride[1] + cx];
dplane[2][cy * dstride[2] + cx] = splane[2][cy * sstride[2] + cx];
}
}
}
/* Grayscale/YUV420 on top of grayscale/YUV420 - complete */
} else if ( zm_bytes_per_pixel(imagePixFormat) == 1 && zm_bytes_per_pixel(image.imagePixFormat) == 1 ) {
// Overlay only the luma/primary plane. Width is shared (panic above).
for (unsigned int y = 0; y < height; y++) {
const uint8_t *psrc = image.buffer + y * image.linesize;
uint8_t *pdest = buffer + y * linesize;
for (unsigned int x = 0; x < width; x++, psrc++, pdest++) {
if (*psrc) *pdest = *psrc;
}
}
/* RGB24 on top of grayscale - convert to same format first - complete */
} else if ( imagePixFormat == AV_PIX_FMT_GRAY8 && zm_is_rgb24(image.imagePixFormat) ) {
Colourise(image.colours, image.subpixelorder);
for (unsigned int y = 0; y < height; y++) {
const uint8_t *psrc = image.buffer + y * image.linesize;
uint8_t *pdest = buffer + y * linesize;
for (unsigned int x = 0; x < width; x++, psrc += 3, pdest += 3) {
if (RED_PTR_RGBA(psrc) || GREEN_PTR_RGBA(psrc) || BLUE_PTR_RGBA(psrc)) {
RED_PTR_RGBA(pdest) = RED_PTR_RGBA(psrc);
GREEN_PTR_RGBA(pdest) = GREEN_PTR_RGBA(psrc);
BLUE_PTR_RGBA(pdest) = BLUE_PTR_RGBA(psrc);
}
}
}
/* RGB32 on top of grayscale - convert to same format first - complete */
} else if ( imagePixFormat == AV_PIX_FMT_GRAY8 && zm_is_rgb32(image.imagePixFormat) ) {
Colourise(image.colours, image.subpixelorder);
const bool alpha_last = (imagePixFormat == AV_PIX_FMT_RGBA || imagePixFormat == AV_PIX_FMT_BGRA);
for (unsigned int y = 0; y < height; y++) {
const Rgb *prsrc = (const Rgb *)(image.buffer + y * image.linesize);
Rgb *prdest = (Rgb *)(buffer + y * linesize);
for (unsigned int x = 0; x < width; x++, prsrc++, prdest++) {
if (alpha_last) {
if (RED_PTR_RGBA(prsrc) || GREEN_PTR_RGBA(prsrc) || BLUE_PTR_RGBA(prsrc))
*prdest = *prsrc;
} else {
if (RED_PTR_ABGR(prsrc) || GREEN_PTR_ABGR(prsrc) || BLUE_PTR_ABGR(prsrc))
*prdest = *prsrc;
}
}
}
/* RGB24/RGB32 on top of YUV420 - convert marked pixels to YUV - complete */
} else if ( zm_is_yuv420(imagePixFormat)
&& (zm_is_rgb24(image.imagePixFormat) || zm_is_rgb32(image.imagePixFormat)) ) {
// The zone alarm highlight is built from the monitor's configured colours,
// but the analysis image follows the decoder's native format (usually
// YUV420P via passthrough). Converting the whole target to RGB would be
// wasteful; instead convert just the marked (non-black) highlight pixels
// to YUV and write luma plus the shared 2x2 chroma sample.
uint8_t *dplane[4] = {};
int dstride[4] = {};
if (av_image_fill_arrays(dplane, dstride, buffer, imagePixFormat, width, height, 32) < 0) {
Error("Overlay: av_image_fill_arrays failed for YUV420 %ux%u", width, height);
return;
}
const unsigned int psize = zm_bytes_per_pixel(image.imagePixFormat);
// Byte offsets of R,G,B within an overlay pixel for its subpixel order.
unsigned int ro = 0, go = 1, bo = 2; // RGB24 / RGBA
switch (image.imagePixFormat) {
case AV_PIX_FMT_BGR24:
case AV_PIX_FMT_BGRA: ro = 2; go = 1; bo = 0; break;
case AV_PIX_FMT_ARGB: ro = 1; go = 2; bo = 3; break;
case AV_PIX_FMT_ABGR: ro = 3; go = 2; bo = 1; break;
default: break;
}
for (unsigned int y = 0; y < height; y++) {
const uint8_t *psrc = image.buffer + y * image.linesize;
for (unsigned int x = 0; x < width; x++, psrc += psize) {
const uint8_t r = psrc[ro], g = psrc[go], b = psrc[bo];
if (r || g || b) {
const YUV yuv = brg_to_yuv(r | (g << 8) | (b << 16));
dplane[0][y * dstride[0] + x] = Y_VAL(yuv);
dplane[1][(y / 2) * dstride[1] + (x / 2)] = U_VAL(yuv);
dplane[2][(y / 2) * dstride[2] + (x / 2)] = V_VAL(yuv);
}
}
}
/* Grayscale/YUV420 on top of RGB24 - complete */
} else if ( zm_is_rgb24(imagePixFormat) && zm_bytes_per_pixel(image.imagePixFormat) == 1 ) {
for (unsigned int y = 0; y < height; y++) {
const uint8_t *psrc = image.buffer + y * image.linesize;
uint8_t *pdest = buffer + y * linesize;
for (unsigned int x = 0; x < width; x++, psrc++, pdest += 3) {
if (*psrc) {
RED_PTR_RGBA(pdest) = GREEN_PTR_RGBA(pdest) = BLUE_PTR_RGBA(pdest) = *psrc;
}
}
}
/* RGB24 on top of RGB24 - not complete. need to take care of different subpixel orders */
} else if ( zm_is_rgb24(imagePixFormat) && zm_is_rgb24(image.imagePixFormat) ) {
for (unsigned int y = 0; y < height; y++) {
const uint8_t *psrc = image.buffer + y * image.linesize;
uint8_t *pdest = buffer + y * linesize;
for (unsigned int x = 0; x < width; x++, psrc += 3, pdest += 3) {
if (RED_PTR_RGBA(psrc) || GREEN_PTR_RGBA(psrc) || BLUE_PTR_RGBA(psrc)) {
RED_PTR_RGBA(pdest) = RED_PTR_RGBA(psrc);
GREEN_PTR_RGBA(pdest) = GREEN_PTR_RGBA(psrc);
BLUE_PTR_RGBA(pdest) = BLUE_PTR_RGBA(psrc);
}
}
}
/* RGB32 on top of RGB24 - TO BE DONE */
} else if ( zm_is_rgb24(imagePixFormat) && zm_is_rgb32(image.imagePixFormat) ) {
Error("Overlay of RGB32 on top of RGB24 is not supported.");
/* Grayscale/YUV420 on top of RGB32 - complete */
} else if ( zm_is_rgb32(imagePixFormat) && zm_bytes_per_pixel(image.imagePixFormat) == 1 ) {
const bool alpha_last = (imagePixFormat == AV_PIX_FMT_RGBA || imagePixFormat == AV_PIX_FMT_BGRA);
for (unsigned int y = 0; y < height; y++) {
const uint8_t *psrc = image.buffer + y * image.linesize;
Rgb *prdest = (Rgb *)(buffer + y * linesize);
for (unsigned int x = 0; x < width; x++, psrc++, prdest++) {
if (*psrc) {
if (alpha_last) {
RED_PTR_RGBA(prdest) = *psrc;
} else {
RED_PTR_ABGR(prdest) = GREEN_PTR_ABGR(prdest) = BLUE_PTR_ABGR(prdest) = *psrc;
}
}
}
}
/* RGB24 on top of RGB32 - TO BE DONE */
} else if ( zm_is_rgb32(imagePixFormat) && zm_is_rgb24(image.imagePixFormat) ) {
Error("Overlay of RGB24 on top of RGB32 is not supported.");
/* RGB32 on top of RGB32 - not complete. need to take care of different subpixel orders */
} else if ( zm_is_rgb32(imagePixFormat) && zm_is_rgb32(image.imagePixFormat) ) {
const bool alpha_last = (image.imagePixFormat == AV_PIX_FMT_RGBA || image.imagePixFormat == AV_PIX_FMT_BGRA);
for (unsigned int y = 0; y < height; y++) {
const Rgb *prsrc = (const Rgb *)(image.buffer + y * image.linesize);
Rgb *prdest = (Rgb *)(buffer + y * linesize);
for (unsigned int x = 0; x < width; x++, prsrc++, prdest++) {
if (alpha_last) {
if (RED_PTR_RGBA(prsrc) || GREEN_PTR_RGBA(prsrc) || BLUE_PTR_RGBA(prsrc))
*prdest = *prsrc;
} else {
if (RED_PTR_ABGR(prsrc) || GREEN_PTR_ABGR(prsrc) || BLUE_PTR_ABGR(prsrc))
*prdest = *prsrc;
}
}
}
}
}
/* RGB32 compatible: complete */
void Image::Overlay( const Image &image, const unsigned int lo_x, const unsigned int lo_y ) {
if ( !(width < image.width || height < image.height) ) {
Panic("Attempt to overlay image too big for destination, %dx%d > %dx%d",
image.width, image.height, width, height );
}
if ( !(width < (lo_x+image.width) || height < (lo_y+image.height)) ) {
Panic("Attempt to overlay image outside of destination bounds, %dx%d @ %dx%d > %dx%d",
image.width, image.height, lo_x, lo_y, width, height );
}
if ( !(colours == image.colours) ) {
Panic("Attempt to partial overlay differently coloured images, expected %d, got %d",
colours, image.colours);
}
unsigned int hi_x = (lo_x+image.width)-1;
unsigned int hi_y = (lo_y+image.height-1);
// Both buffers may carry per-row padding (linesize > width*colours after
// FFALIGN). Rebase source and destination per row using each image's
// own linesize so the sequential psrc++ advance can't drift across rows.
if ( zm_bytes_per_pixel(imagePixFormat) == 1 ) {
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
const uint8_t *psrc = image.buffer + (y - lo_y) * image.linesize;
uint8_t *pdest = buffer + y * linesize + lo_x;
for ( unsigned int x = lo_x; x <= hi_x; x++ ) {
*pdest++ = *psrc++;
}
}
} else if ( zm_is_rgb24(imagePixFormat) ) {
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
const uint8_t *psrc = image.buffer + (y - lo_y) * image.linesize;
uint8_t *pdest = buffer + y * linesize + lo_x * colours;
for ( unsigned int x = lo_x; x <= hi_x; x++ ) {
*pdest++ = *psrc++;
*pdest++ = *psrc++;
*pdest++ = *psrc++;
}
}
} else if ( zm_is_rgb32(imagePixFormat) ) {
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
const Rgb *psrc = (const Rgb*)(image.buffer + (y - lo_y) * image.linesize);
Rgb *pdest = (Rgb*)(buffer + y * linesize + (lo_x << 2));
for ( unsigned int x = lo_x; x <= hi_x; x++ ) {
*pdest++ = *psrc++;
}
}
} else {
Error("Overlay called with unexpected colours: %d", colours);
}
} // end void Image::Overlay( const Image &image, unsigned int x, unsigned int y )
void Image::Blend( const Image &image, int transparency ) {
if ( !(
width == image.width && height == image.height
&& colours == image.colours
&& subpixelorder == image.subpixelorder
) ) {
Error("Attempt to blend different sized images, expected %dx%dx%d %d, got %dx%dx%d %d",
width, height, colours, subpixelorder, image.width, image.height, image.colours, image.subpixelorder );
return;
}
if ( transparency <= 0 )
return;
// Reuse persistent blend buffer to avoid per-frame alloc/free
if (blend_buffer_size_ < size) {
if (blend_buffer_) zm_freealigned(blend_buffer_);
blend_buffer_ = AllocBuffer(size);
blend_buffer_size_ = size;
}
#ifdef ZM_IMAGE_PROFILING
TimePoint start = std::chrono::steady_clock::now();
#endif
/* Do the blending */
(*blend)(buffer, image.buffer, blend_buffer_, size, transparency);
#ifdef ZM_IMAGE_PROFILING
TimePoint end = std::chrono::steady_clock::now();
std::chrono::nanoseconds diff = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start);
uint64 mil_pixels = static_cast<uint64>(size / (static_cast<long double>(diff.count()) / 1000));
Debug(5, "Blend: %u colours blended in %" PRIi64 " nanoseconds, % " PRIi64 " million colours/s\n",
size,
static_cast<int64>(diff.count()),
mil_pixels);
#endif
if (holdbuffer) {
(*fptr_imgbufcpy)(buffer, blend_buffer_, size);
} else {
std::swap(buffer, blend_buffer_);
buffertype = ZM_BUFTYPE_ZM;
}
}
Image *Image::Merge(unsigned int n_images, Image *images[]) {
if ( n_images == 1 ) return new Image(*images[0]);
unsigned int width = images[0]->width;
unsigned int height = images[0]->height;
unsigned int colours = images[0]->colours;
for ( unsigned int i = 1; i < n_images; i++ ) {
if ( !(width == images[i]->width && height == images[i]->height && colours == images[i]->colours) ) {
Panic("Attempt to merge different sized images, expected %dx%dx%d, got %dx%dx%d, for image %d",
width, height, colours, images[i]->width, images[i]->height, images[i]->colours, i );
}
}
Image *result = new Image(width, height, images[0]->colours, images[0]->subpixelorder);
unsigned int size = result->size;
uint8_t *pdest = result->buffer;
for ( unsigned int i = 0; i < size; i++ ) {
unsigned int total = 0;
for ( unsigned int j = 0; j < n_images; j++ ) {
total += images[j]->buffer[i];
}
*pdest++ = total/n_images;
}
return result;
}
Image *Image::Merge(unsigned int n_images, Image *images[], double weight) {
if ( n_images == 1 ) return new Image(*images[0]);
unsigned int width = images[0]->width;
unsigned int height = images[0]->height;
unsigned int colours = images[0]->colours;
for ( unsigned int i = 1; i < n_images; i++ ) {
if ( !(width == images[i]->width && height == images[i]->height && colours == images[i]->colours) ) {
Panic("Attempt to merge different sized images, expected %dx%dx%d, got %dx%dx%d, for image %d",
width, height, colours, images[i]->width, images[i]->height, images[i]->colours, i );
}
}
Image *result = new Image(*images[0]);
unsigned int size = result->size;
double factor = 1.0*weight;
for ( unsigned int i = 1; i < n_images; i++ ) {
uint8_t *pdest = result->buffer;
uint8_t *psrc = images[i]->buffer;
for ( unsigned int j = 0; j < size; j++ ) {
*pdest = (uint8_t)(((*pdest)*(1.0-factor))+((*psrc)*factor));
pdest++;
psrc++;
}
factor *= weight;
}
return result;
}
Image *Image::Highlight( unsigned int n_images, Image *images[], const Rgb threshold, const Rgb ref_colour ) {
if ( n_images == 1 ) return new Image(*images[0]);
unsigned int width = images[0]->width;
unsigned int height = images[0]->height;
unsigned int colours = images[0]->colours;
for ( unsigned int i = 1; i < n_images; i++ ) {
if ( !(width == images[i]->width && height == images[i]->height && colours == images[i]->colours) ) {
Panic( "Attempt to highlight different sized images, expected %dx%dx%d, got %dx%dx%d, for image %d",
width, height, colours, images[i]->width, images[i]->height, images[i]->colours, i );
}
}
Image *result = new Image(width, height, images[0]->colours, images[0]->subpixelorder);
unsigned int n_pixels = result->pixels;
for ( unsigned int c = 0; c < colours; c++ ) {
unsigned int ref_colour_rgb = RGB_VAL(ref_colour,c);
unsigned int threshold_val = RGB_VAL(threshold,c);
uint8_t *pdest = result->buffer + c;
for ( unsigned int i = 0; i < n_pixels; i++, pdest += colours ) {
unsigned int count = 0;
for ( unsigned int j = 0; j < n_images; j++ ) {
uint8_t psrc_val = images[j]->buffer[i*colours + c];
unsigned int diff = (psrc_val > ref_colour_rgb) ? psrc_val - ref_colour_rgb : ref_colour_rgb - psrc_val;
if (diff >= threshold_val) {
count++;
}
}
*pdest = (count*255)/n_images;
}
}
return result;
}
/* New function to allow buffer re-using instead of allocating memory for the delta image every time */
bool Image::Delta(const Image &image, Image* targetimage) const {
if ( !(width == image.width && height == image.height && colours == image.colours && subpixelorder == image.subpixelorder) ) {
Error( "Attempt to get delta of different sized images, expected %dx%dx%d %d, got %dx%dx%d %d",
width, height, colours, subpixelorder, image.width, image.height, image.colours, image.subpixelorder);
return false;
}
// DumpImgBuffer() nulls buffer while leaving width/height/colours intact, so
// the size check above is no guarantee the pixels are present. Without this a
// dumped reference image (e.g. dropped on resume) walks the delta helpers from
// a null base pointer and faults at address 0 on the first row (refs #4983).
if ( buffer == nullptr || image.buffer == nullptr ) {
Error("Attempt to get delta with a null buffer (this %p, other %p)", buffer, image.buffer);
return false;
}
uint8_t *pdiff = targetimage->WriteBuffer(width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE);
if ( pdiff == nullptr ) {
Error("Failed requesting writeable buffer for storing the delta image");
return false;
}
#ifdef ZM_IMAGE_PROFILING
TimePoint start = std::chrono::steady_clock::now();
#endif
// The delta8_* SIMD helpers process N contiguous bytes / pixels without
// any concept of row stride. With FFALIGN'd linesize, an image's buffer
// may have per-row padding (linesize > width*bpp), so passing the full
// pixel count would read padding bytes as image data and produce wrong
// motion deltas. Drive the helpers row by row instead — width pixels per
// row of `this`, of `image`, and of the GRAY8 target, each at their own
// linesize stride.
const unsigned int src_linesize = linesize;
const unsigned int img_linesize = image.linesize;
const unsigned int dst_linesize = targetimage->LineSize();
auto delta_row = [&](void (*fn)(const uint8_t *, const uint8_t *, uint8_t *, unsigned long)) {
for (unsigned int y = 0; y < height; y++) {
fn(buffer + y * src_linesize,
image.buffer + y * img_linesize,
pdiff + y * dst_linesize,
width);
}
};
if (imagePixFormat == AV_PIX_FMT_BGR24) {
/* BGR subpixel order */
delta_row(delta8_bgr);
} else if (zm_is_rgb24(imagePixFormat)) {
/* Assume RGB subpixel order */
delta_row(delta8_rgb);
} else if (imagePixFormat == AV_PIX_FMT_ARGB) {
/* ARGB subpixel order */
delta_row(delta8_argb);
} else if (imagePixFormat == AV_PIX_FMT_ABGR) {
/* ABGR subpixel order */
delta_row(delta8_abgr);
} else if (imagePixFormat == AV_PIX_FMT_BGRA) {
/* BGRA subpixel order */
delta_row(delta8_bgra);
} else if (zm_is_rgb32(imagePixFormat)) {
/* Assume RGBA subpixel order */
delta_row(delta8_rgba);
} else if (zm_bytes_per_pixel(imagePixFormat) == 1) {
delta_row(delta8_gray8);
} else {
Panic("Delta called with unexpected pixel format %d (%s); legacy colours=%d",
imagePixFormat, zm_get_pix_fmt_name(imagePixFormat), colours);
}
#ifdef ZM_IMAGE_PROFILING
TimePoint end = std::chrono::steady_clock::now();
std::chrono::nanoseconds diff = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start);
uint64 mil_pixels = static_cast<uint64>(size / (static_cast<long double>(diff.count()) / 1000));
Debug(5, "Delta: %u delta pixels generated in %" PRIi64 " nanoseconds, % " PRIi64 " million pixels/s",
size,
static_cast<int64>(diff.count()),
mil_pixels);
#endif
return true;
}
const Vector2 Image::centreCoord(const char *text, int size = 1) const {
int index = 0;
int line_no = 0;
int text_len = strlen(text);
int line_len = 0;
int max_line_len = 0;
const char *line = text;
while ( (index < text_len) && (line_len = strcspn( line, "\n" )) ) {
if ( line_len > max_line_len )
max_line_len = line_len;
index += line_len;
while ( text[index] == '\n' ) {
index++;
}
line = text+index;
line_no++;
}
FontVariant const &font_variant = font.GetFontVariant(size - 1);
uint16_t char_width = font_variant.GetCharWidth();
uint16_t char_height = font_variant.GetCharHeight();
int x = (width - (max_line_len * char_width )) / 2;
int y = (height - (line_no * char_height) ) / 2;
return {x, y};
}
/* RGB32 compatible: complete */
void Image::MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour ) {
const uint8_t pixel_r_col = RED_VAL_RGBA(pixel_colour);
const uint8_t pixel_g_col = GREEN_VAL_RGBA(pixel_colour);
const uint8_t pixel_b_col = BLUE_VAL_RGBA(pixel_colour);
const uint8_t pixel_bw_col = pixel_colour & 0xff;
const Rgb pixel_rgb_col = rgb_convert(pixel_colour,subpixelorder);
unsigned int i = 0;
for ( unsigned int y = 0; y < height; y++ ) {
// Re-base the row pointer each iteration: with FFALIGN'd linesize there
// may be per-row padding between width*colours and linesize, and a
// monotonically-incremented ptr would walk into that padding (and
// misaddress subsequent rows).
unsigned char *ptr = buffer + y * linesize;
if ( zm_bytes_per_pixel(imagePixFormat) == 1 ) {
for ( unsigned int x = 0; x < width; x++, ptr++ ) {
if ( p_bitmask[i] )
*ptr = pixel_bw_col;
i++;
}
} else if ( zm_is_rgb24(imagePixFormat) ) {
for ( unsigned int x = 0; x < width; x++, ptr += colours ) {
if ( p_bitmask[i] ) {
RED_PTR_RGBA(ptr) = pixel_r_col;
GREEN_PTR_RGBA(ptr) = pixel_g_col;
BLUE_PTR_RGBA(ptr) = pixel_b_col;
}
i++;
}
} else if ( zm_is_rgb32(imagePixFormat) ) {
for ( unsigned int x = 0; x < width; x++, ptr += colours ) {
Rgb *temp_ptr = (Rgb*)ptr;
if ( p_bitmask[i] )
*temp_ptr = pixel_rgb_col;
i++;
}
} else {
Panic("MaskPrivacy called with unexpected colours: %d", colours);
return;
}
} // end foreach y
// Planar YUV formats: the loop above masked only the Y plane, leaving the
// chroma (U/V) planes intact. Source colour bleeds through the mask
// because chroma still carries the original hue — a privacy leak. Set
// chroma samples covering any masked Y pixel to the neutral value (128).
// For YUV420P chroma is subsampled 2:2; for YUV422P it is 2:1 horizontal.
// Use the conservative rule: if any covered Y bit is set in the bitmask,
// neutralise the corresponding chroma sample.
const bool planar_420 = zm_is_yuv420(imagePixFormat);
const bool planar_422 = (imagePixFormat == AV_PIX_FMT_YUV422P
|| imagePixFormat == AV_PIX_FMT_YUVJ422P);
if (planar_420 || planar_422) {
uint8_t *plane_ptrs[4] = {nullptr, nullptr, nullptr, nullptr};
int plane_linesizes[4] = {0, 0, 0, 0};
int fill_size = av_image_fill_arrays(plane_ptrs, plane_linesizes, buffer,
imagePixFormat, width, height, 32);
if (fill_size < 0 || !plane_ptrs[1] || !plane_ptrs[2]) {
Warning("MaskPrivacy: av_image_fill_arrays failed for %s; chroma not masked",
av_get_pix_fmt_name(imagePixFormat));
return;
}
// Chroma plane dimensions use ceiling division so an odd width/height
// doesn't drop the last chroma column/row — that would leave a strip
// along the right/bottom edge with original colour bleeding through
// the privacy mask.
const unsigned int c_height = planar_420 ? (height + 1) / 2 : height;
const unsigned int c_width = (width + 1) / 2;
const int u_stride = plane_linesizes[1];
const int v_stride = plane_linesizes[2];
uint8_t *u_plane = plane_ptrs[1];
uint8_t *v_plane = plane_ptrs[2];
for (unsigned int cy = 0; cy < c_height; cy++) {
for (unsigned int cx = 0; cx < c_width; cx++) {
const unsigned int y_x0 = cx * 2;
const unsigned int y_x1 = y_x0 + 1;
const unsigned int y_top = planar_420 ? cy * 2 : cy;
const unsigned int y_bot = planar_420 ? y_top + 1 : y_top;
bool mask_hit = p_bitmask[y_top * width + y_x0]
|| (y_x1 < width && p_bitmask[y_top * width + y_x1]);
if (planar_420 && !mask_hit && y_bot < height) {
mask_hit = p_bitmask[y_bot * width + y_x0]
|| (y_x1 < width && p_bitmask[y_bot * width + y_x1]);
}
if (mask_hit) {
u_plane[cy * u_stride + cx] = 128;
v_plane[cy * v_stride + cx] = 128;
}
}
}
}
}
/* RGB32 compatible: complete */
/* Bitmap decoding trick has been adopted from here:
https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/
*/
void Image::Annotate(
const std::string &text,
const Vector2 &coord,
const uint8 size,
const Rgb fg_colour,
const Rgb bg_colour) {
annotation_ = text;
const Rgb fg_rgb_col = rgb_convert(fg_colour, subpixelorder);
const Rgb bg_rgb_col = rgb_convert(bg_colour, subpixelorder);
FontVariant const &font_variant = font.GetFontVariant(size - 1);
const uint16 char_width = font_variant.GetCharWidth();
const uint16 char_height = font_variant.GetCharHeight();
std::vector<std::string> lines = Split(annotation_, '\n');
std::size_t max_line_length = 0;
for (const std::string &s : lines) {
max_line_length = std::max(max_line_length, s.size());
}
uint32 x0_max = width - (max_line_length * char_width);
uint32 y0_max = height - (lines.size() * char_height);
// Calculate initial coordinates of annotation so that everything is displayed even if the
// user set coordinates would prevent that.
uint32 x0 = zm::clamp(static_cast<uint32>(coord.x_), 0u, x0_max);
uint32 y0 = zm::clamp(static_cast<uint32>(coord.y_), 0u, y0_max);
uint32 y = y0;
for (const std::string &line : lines) {
uint32 x = x0;
// Use linesize (which may include FFALIGN'd padding) for row stride
// rather than width*bytesPerPixel, so non-32-aligned widths don't
// skew annotation placement into per-row padding.
if (zm_bytes_per_pixel(imagePixFormat) == 1) {
uint8 *ptr = &buffer[y * linesize + x0];
for (char c : line) {
for (uint64 cp_row : font_variant.GetCodepoint(c)) {
if (bg_colour != kRGBTransparent) {
std::fill(ptr, ptr + char_width, static_cast<uint8>(bg_colour & 0xff));
}
while (cp_row != 0) {
uint32 column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding();
*(ptr + column_idx) = fg_colour & 0xff;
cp_row = cp_row & (cp_row - 1);
}
ptr += linesize;
}
ptr -= (linesize * char_height);
ptr += char_width;
x += char_width;
if (x >= width) {
break;
}
}
} else if (zm_is_rgb24(imagePixFormat)) {
constexpr uint8 bytesPerPixel = 3;
uint8 *ptr = &buffer[y * linesize + x0 * bytesPerPixel];
for (char c : line) {
for (uint64 cp_row : font_variant.GetCodepoint(c)) {
if (bg_colour != kRGBTransparent) {
for (uint16 i = 0; i < char_width; i++) { // We need to set individual r,g,b components
uint8 *colour_ptr = ptr + (i * bytesPerPixel);
RED_PTR_RGBA(colour_ptr) = RED_VAL_RGBA(bg_colour);
GREEN_PTR_RGBA(colour_ptr) = GREEN_VAL_RGBA(bg_colour);
BLUE_PTR_RGBA(colour_ptr) = BLUE_VAL_RGBA(bg_colour);
}
}
while (cp_row != 0) {
uint32 column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding();
uint8 *colour_ptr = ptr + (column_idx * bytesPerPixel);
RED_PTR_RGBA(colour_ptr) = RED_VAL_RGBA(fg_colour);
GREEN_PTR_RGBA(colour_ptr) = GREEN_VAL_RGBA(fg_colour);
BLUE_PTR_RGBA(colour_ptr) = BLUE_VAL_RGBA(fg_colour);
cp_row = cp_row & (cp_row - 1);
}
ptr += linesize;
}
ptr -= (linesize * char_height);
ptr += char_width * bytesPerPixel;
x += char_width;
if (x >= width) {
break;
}
}
} else if (zm_is_rgb32(imagePixFormat)) {
constexpr uint8 bytesPerPixel = 4;
// Rgb is 4 bytes (rgb32); convert linesize bytes -> Rgb stride.
const unsigned int rgb_stride = linesize / sizeof(Rgb);
Rgb *ptr = reinterpret_cast<Rgb *>(&buffer[y * linesize + x0 * bytesPerPixel]);
for (char c : line) {
for (uint64 cp_row : font_variant.GetCodepoint(c)) {
if (bg_colour != kRGBTransparent) {
std::fill(ptr, ptr + char_width, bg_rgb_col);
}
while (cp_row != 0) {
uint32 column_idx = char_width - __builtin_ctzll(cp_row) + font_variant.GetCharPadding();
*(ptr + column_idx) = fg_rgb_col;
cp_row = cp_row & (cp_row - 1);
}
ptr += rgb_stride;
}
ptr -= (rgb_stride * char_height);
ptr += char_width;
x += char_width;
if (x >= width) {
break;
}
}
} else {
Error("Annotate called with unexpected colours: %d", colours);
return;
}
y += char_height;
if (y >= height) {
break;
}
}
}
void Image::Timestamp(const char *label, SystemTimePoint when, const Vector2 &coord, int label_size) {
char time_text[64];
tm when_tm = {};
time_t when_t = std::chrono::system_clock::to_time_t(when);
strftime(time_text, sizeof(time_text), "%y/%m/%d %H:%M:%S", localtime_r(&when_t, &when_tm));
if (label) {
// Assume label is max 64, + ' - ' + 64 chars of time_text
char text[132];
snprintf(text, sizeof(text), "%s - %s", label, time_text);
Annotate(text, coord, label_size);
} else {
Annotate(time_text, coord, label_size);
}
}
/* RGB32 compatible: complete */
void Image::Colourise(const unsigned int p_reqcolours, const unsigned int p_reqsubpixelorder) {
Debug(9, "Colourise: Req colours: %u Req subpixel order: %u Current colours: %u Current subpixel order: %u",p_reqcolours,p_reqsubpixelorder,colours,subpixelorder);
if ( imagePixFormat != AV_PIX_FMT_GRAY8) {
Warning("Target image is already colourised, colours: %u",colours);
return;
}
AVPixelFormat p_req_pixfmt = zm_pixformat_from_colours(p_reqcolours, p_reqsubpixelorder);
// AssignDirect validates buffer_size against av_image_get_buffer_size(...,
// align=32) and uses an FFALIGN'd linesize. Allocate accordingly and copy
// row by row using the source (GRAY8) and destination (RGB) strides, so
// we don't undersize the output buffer for non-32-aligned widths and
// don't smear pixels across row boundaries.
int new_size_signed = av_image_get_buffer_size(p_req_pixfmt, width, height, 32);
int dst_linesize_signed = av_image_get_linesize(p_req_pixfmt, width, 0);
if (new_size_signed < 0 || dst_linesize_signed < 0) {
Error("Colourise: av_image sizing failed for %s %ux%u",
zm_get_pix_fmt_name(p_req_pixfmt), width, height);
return;
}
const size_t new_size = static_cast<size_t>(new_size_signed);
const unsigned int dst_linesize = FFALIGN(dst_linesize_signed, 32);
const unsigned int src_linesize = linesize;
if ( zm_is_rgb32(p_req_pixfmt) ) {
/* RGB32 */
Rgb* new_buffer = (Rgb*)AllocBuffer(new_size);
if ( p_req_pixfmt == AV_PIX_FMT_ABGR || p_req_pixfmt == AV_PIX_FMT_ARGB ) {
/* ARGB\ABGR subpixel order. alpha byte is first (mem+0), so we need to shift the pixel left in the end */
for (unsigned int y = 0; y < height; y++) {
const uint8_t *psrc = buffer + y * src_linesize;
Rgb *pdest = (Rgb *)((uint8_t *)new_buffer + y * dst_linesize);
for (unsigned int x = 0; x < width; x++) {
Rgb subpixel = psrc[x];
Rgb newpixel = subpixel;
newpixel = (newpixel << 8) | subpixel;
newpixel = (newpixel << 8) | subpixel;
pdest[x] = (newpixel << 8);
}
}
} else {
/* RGBA\BGRA subpixel order, alpha byte is last (mem+3) */
for (unsigned int y = 0; y < height; y++) {
const uint8_t *psrc = buffer + y * src_linesize;
Rgb *pdest = (Rgb *)((uint8_t *)new_buffer + y * dst_linesize);
for (unsigned int x = 0; x < width; x++) {
Rgb subpixel = psrc[x];
Rgb newpixel = subpixel;
newpixel = (newpixel << 8) | subpixel;
newpixel = (newpixel << 8) | subpixel;
pdest[x] = newpixel;
}
}
}
/* Directly assign the new buffer and make sure it will be freed when not needed anymore */
AssignDirect( width, height, p_reqcolours, p_reqsubpixelorder, (uint8_t*)new_buffer, new_size, ZM_BUFTYPE_ZM);
} else if ( zm_is_rgb24(p_req_pixfmt) ) {
/* RGB24 */
uint8_t *new_buffer = AllocBuffer(new_size);
for (unsigned int y = 0; y < height; y++) {
const uint8_t *psrc = buffer + y * src_linesize;
uint8_t *pdest = new_buffer + y * dst_linesize;
for (unsigned int x = 0; x < width; x++, pdest += 3) {
RED_PTR_RGBA(pdest) = GREEN_PTR_RGBA(pdest) = BLUE_PTR_RGBA(pdest) = psrc[x];
}
}
/* Directly assign the new buffer and make sure it will be freed when not needed anymore */
AssignDirect( width, height, p_reqcolours, p_reqsubpixelorder, new_buffer, new_size, ZM_BUFTYPE_ZM);
} else {
Error("Colourise called with unexpected colours: %d", colours);
return;
}
}
/* RGB32 compatible: complete */
void Image::DeColourise() {
const AVPixelFormat src_pixfmt = imagePixFormat;
const unsigned int src_subpixelorder = subpixelorder;
if ( zm_is_rgb32(src_pixfmt) && config.cpu_extensions && sse_version >= 35 ) {
/* Use SSSE3 functions */
switch (src_subpixelorder) {
case ZM_SUBPIX_ORDER_BGRA:
ssse3_convert_bgra_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_ARGB:
ssse3_convert_argb_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_ABGR:
ssse3_convert_abgr_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_RGBA:
default:
ssse3_convert_rgba_gray8(buffer,buffer,pixels);
break;
}
} else {
/* Use standard functions */
if ( zm_is_rgb32(src_pixfmt) ) {
if ( pixels % 16 ) {
switch (src_subpixelorder) {
case ZM_SUBPIX_ORDER_BGRA:
std_convert_bgra_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_ARGB:
std_convert_argb_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_ABGR:
std_convert_abgr_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_RGBA:
default:
std_convert_rgba_gray8(buffer,buffer,pixels);
break;
}
} else {
switch (src_subpixelorder) {
case ZM_SUBPIX_ORDER_BGRA:
fast_convert_bgra_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_ARGB:
fast_convert_argb_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_ABGR:
fast_convert_abgr_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_RGBA:
default:
fast_convert_rgba_gray8(buffer,buffer,pixels);
break;
}
} // end if pixels % 16 to use loop unrolled functions
} else {
/* Assume RGB24 */
if ( pixels % 12 ) {
switch (src_subpixelorder) {
case ZM_SUBPIX_ORDER_BGR:
std_convert_bgr_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_RGB:
default:
std_convert_rgb_gray8(buffer,buffer,pixels);
break;
}
} else {
switch (src_subpixelorder) {
case ZM_SUBPIX_ORDER_BGR:
fast_convert_bgr_gray8(buffer,buffer,pixels);
break;
case ZM_SUBPIX_ORDER_RGB:
default:
fast_convert_rgb_gray8(buffer,buffer,pixels);
break;
}
} // end if pixels % 12 to use loop unrolled functions
}
}
colours = ZM_COLOUR_GRAY8;
subpixelorder = ZM_SUBPIX_ORDER_NONE;
imagePixFormat = AV_PIX_FMT_GRAY8;
// Keep size and linesize consistent with the new format; downstream ops
// (Flip/Rotate allocate via size; row-stride loops use linesize) need both
// to match the GRAY8 32-byte-aligned layout, not the previous RGB sizing.
int new_size = av_image_get_buffer_size(AV_PIX_FMT_GRAY8, width, height, 32);
int new_linesize = av_image_get_linesize(AV_PIX_FMT_GRAY8, width, 0);
if (new_size > 0) size = static_cast<unsigned int>(new_size);
if (new_linesize > 0) linesize = FFALIGN(new_linesize, 32);
update_function_pointers();
}
/* RGB32 compatible: complete */
void Image::Fill( Rgb colour, const Box *limits ) {
if ( !(zm_bytes_per_pixel(imagePixFormat) == 1 || zm_is_rgb24(imagePixFormat) || zm_is_rgb32(imagePixFormat)) ) {
Panic("Attempt to fill image with unexpected colours %d", colours);
}
/* Convert the colour's RGBA subpixel order into the image's subpixel order */
colour = rgb_convert(colour,subpixelorder);
unsigned int lo_x = limits ? limits->Lo().x_ : 0;
unsigned int lo_y = limits ? limits->Lo().y_ : 0;
unsigned int hi_x = limits ? limits->Hi().x_ : width - 1;
unsigned int hi_y = limits ? limits->Hi().y_ : height - 1;
// Use linesize as the row stride so non-32-aligned widths don't write
// into padding bytes or shift subsequent rows.
if ( zm_bytes_per_pixel(imagePixFormat) == 1 ) {
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
unsigned char *p = &buffer[y * linesize + lo_x];
for ( unsigned int x = lo_x; x <= hi_x; x++, p++) {
*p = colour;
}
}
} else if ( zm_is_rgb24(imagePixFormat) ) {
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
unsigned char *p = &buffer[y * linesize + lo_x * colours];
for ( unsigned int x = lo_x; x <= hi_x; x++, p += 3) {
RED_PTR_RGBA(p) = RED_VAL_RGBA(colour);
GREEN_PTR_RGBA(p) = GREEN_VAL_RGBA(colour);
BLUE_PTR_RGBA(p) = BLUE_VAL_RGBA(colour);
}
}
} else if ( zm_is_rgb32(imagePixFormat) ) { /* RGB32 */
for ( unsigned int y = lo_y; y <= (unsigned int)hi_y; y++ ) {
Rgb *p = (Rgb*)&buffer[y * linesize + (lo_x << 2)];
for ( unsigned int x = lo_x; x <= (unsigned int)hi_x; x++, p++) {
/* Fast, copies the entire pixel in a single pass */
*p = colour;
}
}
}
}
/* RGB32 compatible: complete */
void Image::Fill( Rgb colour, int density, const Box *limits ) {
/* Allow the faster version to be used if density is not used (density=1) */
if ( density <= 1 )
return Fill(colour,limits);
if ( !(zm_bytes_per_pixel(imagePixFormat) == 1 || zm_is_rgb24(imagePixFormat) || zm_is_rgb32(imagePixFormat)) ) {
Panic("Attempt to fill image with unexpected colours %d", colours);
}
/* Convert the colour's RGBA subpixel order into the image's subpixel order */
colour = rgb_convert(colour, subpixelorder);
unsigned int lo_x = limits ? limits->Lo().x_ : 0;
unsigned int lo_y = limits ? limits->Lo().y_ : 0;
unsigned int hi_x = limits ? limits->Hi().x_ : width - 1;
unsigned int hi_y = limits ? limits->Hi().y_ : height - 1;
// Use linesize as the row stride so non-32-aligned widths don't write
// into padding bytes or shift subsequent rows.
if ( zm_bytes_per_pixel(imagePixFormat) == 1 ) {
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
unsigned char *p = &buffer[y * linesize + lo_x];
for ( unsigned int x = lo_x; x <= hi_x; x++, p++) {
if ( ( x == lo_x || x == hi_x || y == lo_y || y == hi_y ) || (!(x%density) && !(y%density) ) )
*p = colour;
}
}
} else if ( zm_is_rgb24(imagePixFormat) ) {
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
unsigned char *p = &buffer[y * linesize + lo_x * colours];
for ( unsigned int x = lo_x; x <= hi_x; x++, p += 3) {
if ( ( x == lo_x || x == hi_x || y == lo_y || y == hi_y ) || (!(x%density) && !(y%density) ) ) {
RED_PTR_RGBA(p) = RED_VAL_RGBA(colour);
GREEN_PTR_RGBA(p) = GREEN_VAL_RGBA(colour);
BLUE_PTR_RGBA(p) = BLUE_VAL_RGBA(colour);
}
}
}
} else if ( zm_is_rgb32(imagePixFormat) ) { /* RGB32 */
for ( unsigned int y = lo_y; y <= hi_y; y++ ) {
Rgb* p = (Rgb*)&buffer[y * linesize + (lo_x << 2)];
for ( unsigned int x = lo_x; x <= hi_x; x++, p++) {
if ( ( x == lo_x || x == hi_x || y == lo_y || y == hi_y ) || (!(x%density) && !(y%density) ) )
/* Fast, copies the entire pixel in a single pass */
*p = colour;
}
}
}
}
/* RGB32 compatible: complete */
void Image::Outline( Rgb colour, const Polygon &polygon ) {
if ( !(zm_bytes_per_pixel(imagePixFormat) == 1 || zm_is_rgb24(imagePixFormat) || zm_is_rgb32(imagePixFormat)) ) {
Panic("Attempt to outline image with unexpected colours %d", colours);
}
/* Convert the colour's RGBA subpixel order into the image's subpixel order */
colour = rgb_convert(colour, subpixelorder);
size_t n_coords = polygon.GetVertices().size();
for (size_t j = 0, i = n_coords - 1; j < n_coords; i = j++) {
const Vector2 &p1 = polygon.GetVertices()[i];
const Vector2 &p2 = polygon.GetVertices()[j];
// The last pixel we can draw is width/height - 1. Clamp to that value.
int x1 = zm::clamp(p1.x_, 0, static_cast<int>(width - 1));
int x2 = zm::clamp(p2.x_, 0, static_cast<int>(width - 1));
int y1 = zm::clamp(p1.y_, 0, static_cast<int>(height - 1));
int y2 = zm::clamp(p2.y_, 0, static_cast<int>(height - 1));
double dx = x2 - x1;
double dy = y2 - y1;
double grad;
if ( fabs(dx) <= fabs(dy) ) {
if ( y1 != y2 )
grad = dx/dy;
else
grad = width;
double x;
int y, yinc = (y1<y2)?1:-1;
grad *= yinc;
if ( zm_bytes_per_pixel(imagePixFormat) == 1 ) {
for ( x = x1, y = y1; y != y2; y += yinc, x += grad ) {
buffer[y * linesize + int(round(x))] = colour;
}
} else if ( zm_is_rgb24(imagePixFormat) ) {
for ( x = x1, y = y1; y != y2; y += yinc, x += grad ) {
unsigned char *p = &buffer[y * linesize + int(round(x)) * colours];
RED_PTR_RGBA(p) = RED_VAL_RGBA(colour);
GREEN_PTR_RGBA(p) = GREEN_VAL_RGBA(colour);
BLUE_PTR_RGBA(p) = BLUE_VAL_RGBA(colour);
}
} else if ( zm_is_rgb32(imagePixFormat) ) {
for ( x = x1, y = y1; y != y2; y += yinc, x += grad ) {
*(Rgb*)(buffer + y * linesize + (int(round(x)) << 2)) = colour;
}
}
} else {
//Debug( 9, "dx > dy" );
if ( x1 != x2 )
grad = dy/dx;
else
grad = height;
//Debug( 9, "grad: %.2lf", grad );
double y;
int x, xinc = (x1<x2)?1:-1;
grad *= xinc;
if ( zm_bytes_per_pixel(imagePixFormat) == 1 ) {
//Debug( 9, "x1:%d, x2:%d, y1:%d, y2:%d, gr:%.2lf", x1, x2, y1, y2, grad );
for ( y = y1, x = x1; x != x2; x += xinc, y += grad ) {
//Debug( 9, "x:%d, y:%.2f", x, y );
buffer[int(round(y)) * linesize + x] = colour;
}
} else if ( zm_is_rgb24(imagePixFormat) ) {
for ( y = y1, x = x1; x != x2; x += xinc, y += grad ) {
unsigned char *p = &buffer[int(round(y)) * linesize + x * colours];
RED_PTR_RGBA(p) = RED_VAL_RGBA(colour);
GREEN_PTR_RGBA(p) = GREEN_VAL_RGBA(colour);
BLUE_PTR_RGBA(p) = BLUE_VAL_RGBA(colour);
}
} else if ( zm_is_rgb32(imagePixFormat) ) {
for ( y = y1, x = x1; x != x2; x += xinc, y += grad ) {
*(Rgb*)(buffer + int(round(y)) * linesize + (x << 2)) = colour;
}
}
}
} // end foreach coordinate in the polygon
}
// Polygon filling is based on the Scan-line Polygon filling algorithm
void Image::Fill(Rgb colour, int density, const Polygon &polygon) {
if (!(zm_bytes_per_pixel(imagePixFormat) == 1 || zm_is_rgb24(imagePixFormat) || zm_is_rgb32(imagePixFormat))) {
Panic("Attempt to fill image with unexpected colours %d", colours);
}
/* Convert the colour's RGBA subpixel order into the image's subpixel order */
colour = rgb_convert(colour, subpixelorder);
size_t n_coords = polygon.GetVertices().size();
if (n_coords < 3) {
Error("Not enough vertices in polygon!");
return;
}
std::vector<PolygonFill::Edge> global_edges;
global_edges.reserve(n_coords);
for (size_t j = 0, i = n_coords - 1; j < n_coords; i = j++) {
const Vector2 &p1 = polygon.GetVertices()[i];
const Vector2 &p2 = polygon.GetVertices()[j];
// Do not add horizontal edges to the global edge table.
if (p1.y_ == p2.y_)
continue;
Vector2 d = p2 - p1;
global_edges.emplace_back(std::min(p1.y_, p2.y_),
std::max(p1.y_, p2.y_),
p1.y_ < p2.y_ ? p1.x_ : p2.x_,
d.x_ / static_cast<double>(d.y_));
}
if (global_edges.empty()) return;
std::sort(global_edges.begin(), global_edges.end(), PolygonFill::Edge::CompareYX);
std::vector<PolygonFill::Edge> active_edges;
active_edges.reserve(global_edges.size());
int32 scan_line = global_edges[0].min_y;
while (!global_edges.empty() || !active_edges.empty()) {
// Deactivate edges with max_y < current scan line
for (auto it = active_edges.begin(); it != active_edges.end();) {
if (scan_line >= it->max_y) {
it = active_edges.erase(it);
} else {
it->min_x += it->_1_m;
++it;
}
}
// Activate edges with min_y == current scan line
for (auto it = global_edges.begin(); it != global_edges.end();) {
if (it->min_y == scan_line) {
active_edges.emplace_back(*it);
it = global_edges.erase(it);
} else {
++it;
}
}
// Not enough edges to perform the fill operation.
// Continue to next line.
if (active_edges.size() < 2) {
continue;
}
std::sort(active_edges.begin(), active_edges.end(), PolygonFill::Edge::CompareX);
if (!(scan_line % density)) {
// Fill between pairs of active edges (parity rule). Stepping one
// edge at a time would incorrectly fill the gaps between arms of
// a non-convex polygon (e.g. a banana shape).
for (auto it = active_edges.begin(); it + 1 < active_edges.end(); it += 2) {
int32 lo_x = static_cast<int32>(it->min_x);
int32 hi_x = static_cast<int32>((it + 1)->min_x);
if (zm_bytes_per_pixel(imagePixFormat) == 1) {
uint8 *p = &buffer[scan_line * linesize + lo_x];
for (int32 x = lo_x; x <= hi_x; x++, p++) {
if (!(x % density)) {
*p = colour;
}
}
} else if (zm_is_rgb24(imagePixFormat)) {
constexpr uint8 bytesPerPixel = 3;
uint8 *ptr = &buffer[scan_line * linesize + lo_x * bytesPerPixel];
for (int32 x = lo_x; x <= hi_x; x++, ptr += bytesPerPixel) {
if (!(x % density)) {
RED_PTR_RGBA(ptr) = RED_VAL_RGBA(colour);
GREEN_PTR_RGBA(ptr) = GREEN_VAL_RGBA(colour);
BLUE_PTR_RGBA(ptr) = BLUE_VAL_RGBA(colour);
}
}
} else if (zm_is_rgb32(imagePixFormat)) {
constexpr uint8 bytesPerPixel = 4;
Rgb *ptr = reinterpret_cast<Rgb *>(&buffer[scan_line * linesize + lo_x * bytesPerPixel]);
for (int32 x = lo_x; x <= hi_x; x++, ptr++) {
if (!(x % density)) {
*ptr = colour;
}
}
}
}
} // end if (!(scan_line % density))
scan_line++;
} // end while
}
namespace {
// Ceiling-divide an unsigned dimension by 2^shift. AV_CEIL_RSHIFT must NOT
// be used here: its runtime form is -((-(a)) >> (b)), which relies on
// arithmetic shift of a negative value and silently produces 2^31 + a/2^b
// when `a` is unsigned (logical shift), sending plane loops billions of
// samples out of bounds.
inline unsigned int ceil_rshift(unsigned int a, unsigned int shift) {
return (a + (1u << shift) - 1) >> shift;
}
// Rotate `src_w` × `src_h` plane (bpp bytes per sample, src_linesize stride)
// into dst (dst_linesize stride) according to angle (90/180/270).
// For 90/270: dst dims are src_h × src_w. For 180: dst dims are src_w × src_h.
void rotate_plane(const uint8_t *src, int src_linesize,
unsigned int src_w, unsigned int src_h,
uint8_t *dst, int dst_linesize,
unsigned int bpp, int angle) {
if (angle == 90) {
// (sx, sy) -> (dx, dy) = (src_h-1-sy, sx). Equivalently, for each
// destination row dy=sx, write the column src_w-1-dy from the source.
for (unsigned int sy = 0; sy < src_h; sy++) {
const uint8_t *src_row = src + sy * src_linesize;
const unsigned int dx = src_h - 1 - sy;
for (unsigned int sx = 0; sx < src_w; sx++) {
const unsigned int dy = sx;
uint8_t *dst_px = dst + dy * dst_linesize + dx * bpp;
const uint8_t *src_px = src_row + sx * bpp;
for (unsigned int b = 0; b < bpp; b++) dst_px[b] = src_px[b];
}
}
} else if (angle == 180) {
// (sx, sy) -> (dx, dy) = (src_w-1-sx, src_h-1-sy).
for (unsigned int sy = 0; sy < src_h; sy++) {
const uint8_t *src_row = src + sy * src_linesize;
uint8_t *dst_row = dst + (src_h - 1 - sy) * dst_linesize;
for (unsigned int sx = 0; sx < src_w; sx++) {
const unsigned int dx = src_w - 1 - sx;
uint8_t *dst_px = dst_row + dx * bpp;
const uint8_t *src_px = src_row + sx * bpp;
for (unsigned int b = 0; b < bpp; b++) dst_px[b] = src_px[b];
}
}
} else if (angle == 270) {
// (sx, sy) -> (dx, dy) = (sy, src_w-1-sx).
for (unsigned int sy = 0; sy < src_h; sy++) {
const uint8_t *src_row = src + sy * src_linesize;
const unsigned int dx = sy;
for (unsigned int sx = 0; sx < src_w; sx++) {
const unsigned int dy = src_w - 1 - sx;
uint8_t *dst_px = dst + dy * dst_linesize + dx * bpp;
const uint8_t *src_px = src_row + sx * bpp;
for (unsigned int b = 0; b < bpp; b++) dst_px[b] = src_px[b];
}
}
}
}
} // namespace
void Image::Rotate(int angle) {
angle %= 360;
if ( !angle || angle%90 ) {
return;
}
// For 90°/270° the chroma subsampling pattern itself transposes. YUV420P
// has symmetric (2:2) subsampling and survives — the rotated chroma is
// still W/2 × H/2 of the rotated image. YUV422P has horizontal-only
// subsampling; after a 90°/270° rotation it would need vertical-only
// subsampling, which is not the same AVPixelFormat. Refuse rather than
// silently producing a non-conformant buffer.
const bool is_422 = (imagePixFormat == AV_PIX_FMT_YUV422P
|| imagePixFormat == AV_PIX_FMT_YUVJ422P);
if ((angle == 90 || angle == 270) && is_422) {
Error("Rotate %d° not supported for %s (asymmetric chroma subsampling); "
"convert to RGB/YUV420P first", angle, av_get_pix_fmt_name(imagePixFormat));
return;
}
const unsigned int new_width = (angle == 180) ? width : height;
const unsigned int new_height = (angle == 180) ? height : width;
int new_size_signed = av_image_get_buffer_size(imagePixFormat, new_width, new_height, 32);
if (new_size_signed < 0) {
Error("Rotate: av_image_get_buffer_size failed for %s %ux%u",
av_get_pix_fmt_name(imagePixFormat), new_width, new_height);
return;
}
const size_t new_size = static_cast<size_t>(new_size_signed);
uint8_t* rotate_buffer = AllocBuffer(new_size);
uint8_t *src_planes[4] = {nullptr, nullptr, nullptr, nullptr};
int src_strides[4] = {0, 0, 0, 0};
uint8_t *dst_planes[4] = {nullptr, nullptr, nullptr, nullptr};
int dst_strides[4] = {0, 0, 0, 0};
if (av_image_fill_arrays(src_planes, src_strides, buffer, imagePixFormat, width, height, 32) < 0
|| av_image_fill_arrays(dst_planes, dst_strides, rotate_buffer, imagePixFormat, new_width, new_height, 32) < 0) {
Error("Rotate: av_image_fill_arrays failed for %s", av_get_pix_fmt_name(imagePixFormat));
DumpBuffer(rotate_buffer, ZM_BUFTYPE_ZM);
return;
}
// av_image_fill_arrays re-derives the source stride at 32-byte alignment, but
// buffer may be a borrowed plane (e.g. get_y_image wraps a decoder Y plane)
// whose real stride is the Image's own linesize and can be smaller than
// FFALIGN(width,32). Using the assumed stride would read past the end of that
// plane. For ZM-allocated images linesize == src_strides[0], so this is a
// no-op there. Only plane 0 is overridden: the only borrowed case is a
// single-plane GRAY8 Y image, and planar ZM images are self-consistent.
src_strides[0] = linesize;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(imagePixFormat);
if (!desc) {
Error("Rotate: av_pix_fmt_desc_get failed for %s", av_get_pix_fmt_name(imagePixFormat));
DumpBuffer(rotate_buffer, ZM_BUFTYPE_ZM);
return;
}
const bool planar = (desc->flags & AV_PIX_FMT_FLAG_PLANAR) != 0;
if (planar) {
// Each plane has 1 byte per sample. Plane 0 is luma at full resolution;
// planes 1/2 are chroma subsampled by desc->log2_chroma_w/h. Use ceiling
// division — flooring would drop the last chroma column/row for odd luma
// dimensions and leave part of U/V unrotated.
const unsigned int cw = ceil_rshift(width, desc->log2_chroma_w);
const unsigned int ch = ceil_rshift(height, desc->log2_chroma_h);
rotate_plane(src_planes[0], src_strides[0], width, height,
dst_planes[0], dst_strides[0], 1, angle);
rotate_plane(src_planes[1], src_strides[1], cw, ch,
dst_planes[1], dst_strides[1], 1, angle);
rotate_plane(src_planes[2], src_strides[2], cw, ch,
dst_planes[2], dst_strides[2], 1, angle);
} else {
// Packed format: single plane with bpp bytes per pixel.
const unsigned int bpp = zm_bytes_per_pixel(imagePixFormat);
if (bpp == 0) {
Error("Rotate: bytes_per_pixel unknown for %s", av_get_pix_fmt_name(imagePixFormat));
DumpBuffer(rotate_buffer, ZM_BUFTYPE_ZM);
return;
}
rotate_plane(src_planes[0], src_strides[0], width, height,
dst_planes[0], dst_strides[0], bpp, angle);
}
AssignDirect(new_width, new_height, colours, subpixelorder, rotate_buffer, new_size, ZM_BUFTYPE_ZM);
} // void Image::Rotate(int angle)
namespace {
// Flip a single plane (bpp bytes per sample) into dst. leftright=true is
// horizontal flip; leftright=false is vertical flip. src/dst dimensions
// stay the same; only the order of pixels/rows changes.
void flip_plane(const uint8_t *src, int src_linesize,
unsigned int plane_w, unsigned int plane_h,
uint8_t *dst, int dst_linesize,
unsigned int bpp, bool leftright) {
if (leftright) {
for (unsigned int y = 0; y < plane_h; y++) {
const uint8_t *src_row = src + y * src_linesize;
uint8_t *dst_row = dst + y * dst_linesize;
for (unsigned int x = 0; x < plane_w; x++) {
const uint8_t *src_px = src_row + x * bpp;
uint8_t *dst_px = dst_row + (plane_w - 1 - x) * bpp;
for (unsigned int b = 0; b < bpp; b++) dst_px[b] = src_px[b];
}
}
} else {
// Vertical flip: row y maps to row (plane_h - 1 - y); copy bpp*plane_w
// bytes per row (the data portion; padding bytes are left untouched).
const size_t row_data_bytes = static_cast<size_t>(plane_w) * bpp;
for (unsigned int y = 0; y < plane_h; y++) {
memcpy(dst + (plane_h - 1 - y) * dst_linesize,
src + y * src_linesize,
row_data_bytes);
}
}
}
} // namespace
/* RGB32 compatible: complete; planar YUV (YUV420P/J420P/YUV422P/J422P) flipped per-plane */
void Image::Flip( bool leftright ) {
// Size the destination from the 32-aligned layout the planes are written at,
// not from this->size: a borrowed source (get_y_image wraps a decoder Y
// plane) records a tight size from its real linesize, which is smaller than
// the aligned destination needs. For ZM-allocated images this equals size.
int flip_size_signed = av_image_get_buffer_size(imagePixFormat, width, height, 32);
if (flip_size_signed < 0) {
Error("Flip: av_image_get_buffer_size failed for %s %ux%u",
av_get_pix_fmt_name(imagePixFormat), width, height);
return;
}
const size_t flip_size = static_cast<size_t>(flip_size_signed);
uint8_t* flip_buffer = AllocBuffer(flip_size);
uint8_t *src_planes[4] = {nullptr, nullptr, nullptr, nullptr};
int src_strides[4] = {0, 0, 0, 0};
uint8_t *dst_planes[4] = {nullptr, nullptr, nullptr, nullptr};
int dst_strides[4] = {0, 0, 0, 0};
if (av_image_fill_arrays(src_planes, src_strides, buffer, imagePixFormat, width, height, 32) < 0
|| av_image_fill_arrays(dst_planes, dst_strides, flip_buffer, imagePixFormat, width, height, 32) < 0) {
Error("Flip: av_image_fill_arrays failed for %s", av_get_pix_fmt_name(imagePixFormat));
DumpBuffer(flip_buffer, ZM_BUFTYPE_ZM);
return;
}
// Use the Image's real stride for the borrowed-plane source rather than the
// 32-aligned stride av_image_fill_arrays assumes. See Image::Rotate for the
// rationale; no-op for self-consistent ZM-allocated images.
src_strides[0] = linesize;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(imagePixFormat);
if (!desc) {
Error("Flip: av_pix_fmt_desc_get failed for %s", av_get_pix_fmt_name(imagePixFormat));
DumpBuffer(flip_buffer, ZM_BUFTYPE_ZM);
return;
}
const bool planar = (desc->flags & AV_PIX_FMT_FLAG_PLANAR) != 0;
if (planar) {
// Ceiling division so odd luma dimensions don't drop the last chroma
// column/row.
const unsigned int cw = ceil_rshift(width, desc->log2_chroma_w);
const unsigned int ch = ceil_rshift(height, desc->log2_chroma_h);
flip_plane(src_planes[0], src_strides[0], width, height,
dst_planes[0], dst_strides[0], 1, leftright);
flip_plane(src_planes[1], src_strides[1], cw, ch,
dst_planes[1], dst_strides[1], 1, leftright);
flip_plane(src_planes[2], src_strides[2], cw, ch,
dst_planes[2], dst_strides[2], 1, leftright);
} else {
const unsigned int bpp = zm_bytes_per_pixel(imagePixFormat);
if (bpp == 0) {
Error("Flip: bytes_per_pixel unknown for %s", av_get_pix_fmt_name(imagePixFormat));
DumpBuffer(flip_buffer, ZM_BUFTYPE_ZM);
return;
}
flip_plane(src_planes[0], src_strides[0], width, height,
dst_planes[0], dst_strides[0], bpp, leftright);
}
AssignDirect(width, height, colours, subpixelorder, flip_buffer, flip_size, ZM_BUFTYPE_ZM);
}
void Image::Scale(const unsigned int new_width, const unsigned int new_height) {
if (width == new_width and height == new_height) return;
// imagePixFormat is the canonical source of truth; the deprecated
// AVPixFormat() getter re-derives via (colours, subpixelorder) and would
// hit the GRAY8/YUV420P alias collision if those legacy fields ever drift.
const AVPixelFormat format = imagePixFormat;
// (new_width+1)*(new_height+1)*colours undercounts planar formats: for
// YUV420P, colours=1 due to the GRAY8 alias, but the buffer needs
// ~1.5x for chroma. Use av_image_get_buffer_size so this works for any
// AVPixelFormat. SWScale::Convert checks the buffer size against this
// and errors out if undersized.
int new_size = av_image_get_buffer_size(format, new_width, new_height, 32);
if (new_size < 0) {
Error("Scale: av_image_get_buffer_size failed for %s %ux%u", av_get_pix_fmt_name(format), new_width, new_height);
return;
}
size_t scale_buffer_size = static_cast<size_t>(new_size);
uint8_t* scale_buffer = AllocBuffer(scale_buffer_size);
// Reuse one SWScale (and thus one cached sws context) per thread. A fresh
// local one meant a full sws_init_context on every scaled frame — for a
// montage of N streams that is N context builds per frame period, and it is
// what made the (now-fixed) deprecated-format warning a flood rather than a
// one-off. sws_getCachedContext re-inits by itself if the geometry changes.
static thread_local SWScale swscale;
static thread_local const bool swscale_ready = swscale.init();
if (!swscale_ready) {
Error("Scale: failed to initialise SWScale");
DumpBuffer(scale_buffer, ZM_BUFTYPE_ZM);
return;
}
// Both buffers use Image's align-32 layout: `buffer` is ours, and
// scale_buffer is adopted by AssignDirect below, which derives
// size/linesize with av_image_* at align=32.
if (swscale.Convert(buffer, allocation, scale_buffer, scale_buffer_size,
format, format, width, height, new_width, new_height, 32, 32) < 0) {
Error("Scale: sws_scale conversion failed (%ux%u %s -> %ux%u)",
width, height, av_get_pix_fmt_name(format), new_width, new_height);
DumpBuffer(scale_buffer, ZM_BUFTYPE_ZM);
return;
}
AssignDirect(new_width, new_height, colours, subpixelorder, scale_buffer, scale_buffer_size, ZM_BUFTYPE_ZM);
}
void Image::Scale(const unsigned int factor) {
if ( !factor ) {
Error("Bogus scale factor %d found", factor);
return;
}
if ( factor == ZM_SCALE_BASE ) {
return;
}
// Delegate to the (new_width, new_height) variant, which uses sws_scale.
// The previous hand-rolled pixel-doubling/decimation loops here did not
// understand planar YUV layouts (only the Y plane was scaled, the U/V
// planes were dropped), so a YUV420P image came out garbled.
unsigned int new_width = (width*factor)/ZM_SCALE_BASE;
unsigned int new_height = (height*factor)/ZM_SCALE_BASE;
Scale(new_width, new_height);
}
void Image::Deinterlace_Discard() {
/* Simple deinterlacing. Copy the even lines into the odd lines */
// ICON: These can be drastically improved. But who cares?
// Use linesize as the per-row byte stride so non-32-aligned widths copy
// from/to the correct row offsets. Still copies `width` pixels per row
// (the padding bytes are untouched). Stop one short of the last row
// when height is odd — y == height-1 would write to row height which
// is past the buffer.
if (height < 2) return;
const unsigned int last_src = static_cast<unsigned int>(height) - 1;
if ( zm_bytes_per_pixel(imagePixFormat) == 1 ) {
const uint8_t *psrc;
uint8_t *pdest;
for (unsigned int y = 0; y < last_src; y += 2) {
psrc = buffer + y * linesize;
pdest = buffer + (y + 1) * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pdest++ = *psrc++;
}
}
} else if ( zm_is_rgb24(imagePixFormat) ) {
const uint8_t *psrc;
uint8_t *pdest;
for (unsigned int y = 0; y < last_src; y += 2) {
psrc = buffer + y * linesize;
pdest = buffer + (y + 1) * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pdest++ = *psrc++;
*pdest++ = *psrc++;
*pdest++ = *psrc++;
}
}
} else if ( zm_is_rgb32(imagePixFormat) ) {
const Rgb *psrc;
Rgb *pdest;
for (unsigned int y = 0; y < last_src; y += 2) {
psrc = (Rgb*)(buffer + y * linesize);
pdest = (Rgb*)(buffer + (y + 1) * linesize);
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pdest++ = *psrc++;
}
}
} else {
Error("Deinterlace called with unexpected colours: %d", colours);
}
}
void Image::Deinterlace_Linear() {
/* Simple deinterlacing. The odd lines are average of the line above and line below */
const uint8_t *pbelow, *pabove;
uint8_t *pcurrent;
// Use linesize as the per-row byte stride so non-32-aligned widths read
// and write the correct rows. The inner loops still process `width`
// pixels per row; per-row padding bytes are not touched.
if ( zm_bytes_per_pixel(imagePixFormat) == 1 ) {
for (unsigned int y = 1; y < (unsigned int)(height-1); y += 2) {
pabove = buffer + (y - 1) * linesize;
pbelow = buffer + (y + 1) * linesize;
pcurrent = buffer + y * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pcurrent++ = (*pabove++ + *pbelow++) >> 1;
}
}
/* Special case for the last line */
pcurrent = buffer + (height - 1) * linesize;
pabove = buffer + (height - 2) * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pcurrent++ = *pabove++;
}
} else if ( zm_is_rgb24(imagePixFormat) ) {
for (unsigned int y = 1; y < (unsigned int)(height-1); y += 2) {
pabove = buffer + (y - 1) * linesize;
pbelow = buffer + (y + 1) * linesize;
pcurrent = buffer + y * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pcurrent++ = (*pabove++ + *pbelow++) >> 1;
*pcurrent++ = (*pabove++ + *pbelow++) >> 1;
*pcurrent++ = (*pabove++ + *pbelow++) >> 1;
}
}
/* Special case for the last line */
pcurrent = buffer + (height - 1) * linesize;
pabove = buffer + (height - 2) * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pcurrent++ = *pabove++;
*pcurrent++ = *pabove++;
*pcurrent++ = *pabove++;
}
} else if ( zm_is_rgb32(imagePixFormat) ) {
for (unsigned int y = 1; y < (unsigned int)(height-1); y += 2) {
pabove = buffer + (y - 1) * linesize;
pbelow = buffer + (y + 1) * linesize;
pcurrent = buffer + y * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pcurrent++ = (*pabove++ + *pbelow++) >> 1;
*pcurrent++ = (*pabove++ + *pbelow++) >> 1;
*pcurrent++ = (*pabove++ + *pbelow++) >> 1;
*pcurrent++ = (*pabove++ + *pbelow++) >> 1;
}
}
/* Special case for the last line */
pcurrent = buffer + (height - 1) * linesize;
pabove = buffer + (height - 2) * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pcurrent++ = *pabove++;
*pcurrent++ = *pabove++;
*pcurrent++ = *pabove++;
*pcurrent++ = *pabove++;
}
} else {
Error("Deinterlace called with unexpected colours: %d", colours);
}
}
void Image::Deinterlace_Blend() {
/* Simple deinterlacing. Blend the fields together. 50% blend */
uint8_t *pabove, *pcurrent;
// Use linesize as the per-row byte stride so non-32-aligned widths read
// and write the correct rows. The inner loops still process `width`
// pixels per row; per-row padding bytes are not touched.
if ( zm_bytes_per_pixel(imagePixFormat) == 1 ) {
for (unsigned int y = 1; y < (unsigned int)height; y += 2) {
pabove = buffer + (y - 1) * linesize;
pcurrent = buffer + y * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pabove = (*pabove + *pcurrent) >> 1;
*pcurrent++ = *pabove++;
}
}
} else if ( zm_is_rgb24(imagePixFormat) ) {
for (unsigned int y = 1; y < (unsigned int)height; y += 2) {
pabove = buffer + (y - 1) * linesize;
pcurrent = buffer + y * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pabove = (*pabove + *pcurrent) >> 1;
*pcurrent++ = *pabove++;
*pabove = (*pabove + *pcurrent) >> 1;
*pcurrent++ = *pabove++;
*pabove = (*pabove + *pcurrent) >> 1;
*pcurrent++ = *pabove++;
}
}
} else if ( zm_is_rgb32(imagePixFormat) ) {
for (unsigned int y = 1; y < (unsigned int)height; y += 2) {
pabove = buffer + (y - 1) * linesize;
pcurrent = buffer + y * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
*pabove = (*pabove + *pcurrent) >> 1;
*pcurrent++ = *pabove++;
*pabove = (*pabove + *pcurrent) >> 1;
*pcurrent++ = *pabove++;
*pabove = (*pabove + *pcurrent) >> 1;
*pcurrent++ = *pabove++;
*pabove = (*pabove + *pcurrent) >> 1;
*pcurrent++ = *pabove++;
}
}
} else {
Error("Deinterlace called with unexpected colours: %d", colours);
}
}
void Image::Deinterlace_Blend_CustomRatio(int divider) {
/* Simple deinterlacing. Blend the fields together at a custom ratio. */
/* 1 = 50% blending */
/* 2 = 25% blending */
/* 3 = 12.% blending */
/* 4 = 6.25% blending */
uint8_t *pabove, *pcurrent;
uint8_t subpix1, subpix2;
if ( divider < 1 || divider > 4 ) {
Error("Deinterlace called with invalid blend ratio");
}
// Use linesize as the per-row byte stride so non-32-aligned widths read
// and write the correct rows. Inner loops process `width` pixels.
if ( zm_bytes_per_pixel(imagePixFormat) == 1 ) {
for (unsigned int y = 1; y < (unsigned int)height; y += 2) {
pabove = buffer + (y - 1) * linesize;
pcurrent = buffer + y * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
subpix1 = ((*pabove - *pcurrent)>>divider) + *pcurrent;
subpix2 = ((*pcurrent - *pabove)>>divider) + *pabove;
*pcurrent++ = subpix1;
*pabove++ = subpix2;
}
}
} else if ( zm_is_rgb24(imagePixFormat) ) {
for (unsigned int y = 1; y < (unsigned int)height; y += 2) {
pabove = buffer + (y - 1) * linesize;
pcurrent = buffer + y * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
subpix1 = ((*pabove - *pcurrent)>>divider) + *pcurrent;
subpix2 = ((*pcurrent - *pabove)>>divider) + *pabove;
*pcurrent++ = subpix1;
*pabove++ = subpix2;
subpix1 = ((*pabove - *pcurrent)>>divider) + *pcurrent;
subpix2 = ((*pcurrent - *pabove)>>divider) + *pabove;
*pcurrent++ = subpix1;
*pabove++ = subpix2;
subpix1 = ((*pabove - *pcurrent)>>divider) + *pcurrent;
subpix2 = ((*pcurrent - *pabove)>>divider) + *pabove;
*pcurrent++ = subpix1;
*pabove++ = subpix2;
}
}
} else if ( zm_is_rgb32(imagePixFormat) ) {
for (unsigned int y = 1; y < (unsigned int)height; y += 2) {
pabove = buffer + (y - 1) * linesize;
pcurrent = buffer + y * linesize;
for (unsigned int x = 0; x < (unsigned int)width; x++) {
subpix1 = ((*pabove - *pcurrent)>>divider) + *pcurrent;
subpix2 = ((*pcurrent - *pabove)>>divider) + *pabove;
*pcurrent++ = subpix1;
*pabove++ = subpix2;
subpix1 = ((*pabove - *pcurrent)>>divider) + *pcurrent;
subpix2 = ((*pcurrent - *pabove)>>divider) + *pabove;
*pcurrent++ = subpix1;
*pabove++ = subpix2;
subpix1 = ((*pabove - *pcurrent)>>divider) + *pcurrent;
subpix2 = ((*pcurrent - *pabove)>>divider) + *pabove;
*pcurrent++ = subpix1;
*pabove++ = subpix2;
subpix1 = ((*pabove - *pcurrent)>>divider) + *pcurrent;
subpix2 = ((*pcurrent - *pabove)>>divider) + *pabove;
*pcurrent++ = subpix1;
*pabove++ = subpix2;
}
}
} else {
Error("Deinterlace called with unexpected colours: %d", colours);
}
}
void Image::Deinterlace_4Field(const Image* next_image, unsigned int threshold) {
if ( !(width == next_image->width && height == next_image->height && colours == next_image->colours && subpixelorder == next_image->subpixelorder) ) {
Panic( "Attempt to deinterlace different sized images, expected %dx%dx%d %d, got %dx%dx%d %d", width, height, colours, subpixelorder, next_image->width, next_image->height, next_image->colours, next_image->subpixelorder);
}
// The *_deinterlace_4field_* SIMD helpers use `width` as the row stride
// internally and have no concept of per-row padding. With FFALIGN'd
// linesize the source row stride may exceed width*bpp, so feeding them
// the held buffers directly would treat padding bytes as image data and
// corrupt the output for non-32-aligned widths. Pack both inputs into
// tightly-laid-out temp buffers (linesize == width*bpp), run the helper,
// then copy only the data bytes back into our linesize-padded buffer
// (leaving padding untouched). The fast path — no copies — is used
// whenever linesize already equals width*bpp on both images, which is
// the common case for 32-aligned widths.
const unsigned int bpp = (zm_bytes_per_pixel(imagePixFormat) == 1)
? 1
: zm_bytes_per_pixel(imagePixFormat);
const size_t row_data_bytes = static_cast<size_t>(width) * bpp;
const bool needs_pack = (linesize != row_data_bytes) || (next_image->linesize != row_data_bytes);
uint8_t *col1 = buffer;
uint8_t *col2 = next_image->buffer;
uint8_t *tmp1 = nullptr;
uint8_t *tmp2 = nullptr;
if (needs_pack) {
const size_t packed_size = row_data_bytes * height;
tmp1 = AllocBuffer(packed_size);
tmp2 = AllocBuffer(packed_size);
for (unsigned int y = 0; y < height; y++) {
memcpy(tmp1 + y * row_data_bytes, buffer + y * linesize, row_data_bytes);
memcpy(tmp2 + y * row_data_bytes, next_image->buffer + y * next_image->linesize, row_data_bytes);
}
col1 = tmp1;
col2 = tmp2;
}
if (imagePixFormat == AV_PIX_FMT_BGR24) {
/* BGR subpixel order */
std_deinterlace_4field_bgr(col1, col2, threshold, width, height);
} else if (zm_is_rgb24(imagePixFormat)) {
/* Assume RGB subpixel order */
std_deinterlace_4field_rgb(col1, col2, threshold, width, height);
} else if (imagePixFormat == AV_PIX_FMT_ARGB) {
/* ARGB subpixel order */
(*fptr_deinterlace_4field_argb)(col1, col2, threshold, width, height);
} else if (imagePixFormat == AV_PIX_FMT_ABGR) {
/* ABGR subpixel order */
(*fptr_deinterlace_4field_abgr)(col1, col2, threshold, width, height);
} else if (imagePixFormat == AV_PIX_FMT_BGRA) {
/* BGRA subpixel order */
(*fptr_deinterlace_4field_bgra)(col1, col2, threshold, width, height);
} else if (zm_is_rgb32(imagePixFormat)) {
/* Assume RGBA subpixel order */
(*fptr_deinterlace_4field_rgba)(col1, col2, threshold, width, height);
} else if (zm_bytes_per_pixel(imagePixFormat) == 1) {
(*fptr_deinterlace_4field_gray8)(col1, col2, threshold, width, height);
} else {
if (needs_pack) {
DumpBuffer(tmp1, ZM_BUFTYPE_ZM);
DumpBuffer(tmp2, ZM_BUFTYPE_ZM);
}
Panic("Deinterlace_4Field called with unexpected pixel format %d (%s)",
imagePixFormat, zm_get_pix_fmt_name(imagePixFormat));
}
if (needs_pack) {
// Copy the processed Y/primary plane back into our padded buffer,
// touching only the data bytes per row so existing padding is intact.
for (unsigned int y = 0; y < height; y++) {
memcpy(buffer + y * linesize, tmp1 + y * row_data_bytes, row_data_bytes);
}
DumpBuffer(tmp1, ZM_BUFTYPE_ZM);
DumpBuffer(tmp2, ZM_BUFTYPE_ZM);
}
}
/************************************************* BLEND FUNCTIONS *************************************************/
#if defined(__i386__) || defined(__x86_64__)
__attribute__((noinline,__target__("sse2")))
#endif
void sse2_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) {
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
static double current_blendpercent = 0.0;
static uint32_t clearmask = 0;
static uint32_t divider = 0;
if ( current_blendpercent != blendpercent ) {
/* Attempt to match the blending percent to one of the possible values */
if ( blendpercent < 2.34375 ) {
// 1.5625% blending
divider = 6;
clearmask = 0x03030303;
} else if ( blendpercent < 4.6875 ) {
// 3.125% blending
divider = 5;
clearmask = 0x07070707;
} else if ( blendpercent < 9.375 ) {
// 6.25% blending
divider = 4;
clearmask = 0x0F0F0F0F;
} else if ( blendpercent < 18.75 ) {
// 12.5% blending
divider = 3;
clearmask = 0x1F1F1F1F;
} else if ( blendpercent < 37.5 ) {
// 25% blending
divider = 2;
clearmask = 0x3F3F3F3F;
} else {
// 50% blending
divider = 1;
clearmask = 0x7F7F7F7F;
}
current_blendpercent = blendpercent;
}
__asm__ __volatile__(
"movd %4, %%xmm3\n\t"
"movd %5, %%xmm4\n\t"
"pshufd $0x0, %%xmm3, %%xmm3\n\t"
"sub $0x10, %0\n\t"
"sub $0x10, %1\n\t"
"sub $0x10, %2\n\t"
"sse2_fastblend_iter:\n\t"
"movdqa (%0,%3),%%xmm0\n\t"
"movdqa %%xmm0,%%xmm2\n\t"
"movdqa (%1,%3),%%xmm1\n\t"
"psrlq %%xmm4,%%xmm0\n\t"
"psrlq %%xmm4,%%xmm1\n\t"
"pand %%xmm3,%%xmm1\n\t"
"pand %%xmm3,%%xmm0\n\t"
"psubb %%xmm0,%%xmm1\n\t"
"paddb %%xmm2,%%xmm1\n\t"
"movntdq %%xmm1,(%2,%3)\n\t"
"sub $0x10, %3\n\t"
"jnz sse2_fastblend_iter\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count), "m" (clearmask), "m" (divider)
: "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory"
);
#else
Panic("SSE function called on a non x86\\x86-64 platform");
#endif
}
__attribute__((noinline)) void std_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) {
static int divider = 0;
static double current_blendpercent = 0.0;
const uint8_t* const max_ptr = result + count;
if ( current_blendpercent != blendpercent ) {
/* Attempt to match the blending percent to one of the possible values */
if ( blendpercent < 2.34375 ) {
// 1.5625% blending
divider = 6;
} else if ( blendpercent < 4.6875 ) {
// 3.125% blending
divider = 5;
} else if ( blendpercent < 9.375 ) {
// 6.25% blending
divider = 4;
} else if ( blendpercent < 18.75 ) {
// 12.5% blending
divider = 3;
} else if ( blendpercent < 37.5 ) {
// 25% blending
divider = 2;
} else {
// 50% blending
divider = 1;
}
current_blendpercent = blendpercent;
}
while ( result < max_ptr ) {
result[0] = ((col2[0] - col1[0])>>divider) + col1[0];
result[1] = ((col2[1] - col1[1])>>divider) + col1[1];
result[2] = ((col2[2] - col1[2])>>divider) + col1[2];
result[3] = ((col2[3] - col1[3])>>divider) + col1[3];
result[4] = ((col2[4] - col1[4])>>divider) + col1[4];
result[5] = ((col2[5] - col1[5])>>divider) + col1[5];
result[6] = ((col2[6] - col1[6])>>divider) + col1[6];
result[7] = ((col2[7] - col1[7])>>divider) + col1[7];
result[8] = ((col2[8] - col1[8])>>divider) + col1[8];
result[9] = ((col2[9] - col1[9])>>divider) + col1[9];
result[10] = ((col2[10] - col1[10])>>divider) + col1[10];
result[11] = ((col2[11] - col1[11])>>divider) + col1[11];
result[12] = ((col2[12] - col1[12])>>divider) + col1[12];
result[13] = ((col2[13] - col1[13])>>divider) + col1[13];
result[14] = ((col2[14] - col1[14])>>divider) + col1[14];
result[15] = ((col2[15] - col1[15])>>divider) + col1[15];
col1 += 16;
col2 += 16;
result += 16;
}
}
/* FastBlend Neon for AArch32 */
#if (defined(__arm__) && defined(__ARM_PCS_VFP) && !defined(ZM_STRIP_NEON))
__attribute__((noinline,__target__("fpu=neon")))
#endif
void neon32_armv7_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) {
#if (defined(__arm__) && defined(__ARM_PCS_VFP) && !defined(ZM_STRIP_NEON))
static int8_t divider = 0;
static double current_blendpercent = 0.0;
if(current_blendpercent != blendpercent) {
/* Attempt to match the blending percent to one of the possible values */
if(blendpercent < 2.34375) {
// 1.5625% blending
divider = 6;
} else if(blendpercent >= 2.34375 && blendpercent < 4.6875) {
// 3.125% blending
divider = 5;
} else if(blendpercent >= 4.6875 && blendpercent < 9.375) {
// 6.25% blending
divider = 4;
} else if(blendpercent >= 9.375 && blendpercent < 18.75) {
// 12.5% blending
divider = 3;
} else if(blendpercent >= 18.75 && blendpercent < 37.5) {
// 25% blending
divider = 2;
} else if(blendpercent >= 37.5) {
// 50% blending
divider = 1;
}
// We only have instruction to shift left by a variable, going negative shifts right :)
divider *= -1;
current_blendpercent = blendpercent;
}
/* Q0(D0,D1) = col1+0 */
/* Q1(D2,D3) = col1+16 */
/* Q2(D4,D5) = col1+32 */
/* Q3(D6,D7) = col1+48 */
/* Q4(D8,D9) = col2+0 */
/* Q5(D10,D11) = col2+16 */
/* Q6(D12,D13) = col2+32 */
/* Q7(D14,D15) = col2+48 */
/* Q8(D16,D17) = col1tmp+0 */
/* Q9(D18,D19) = col1tmp+16 */
/* Q10(D20,D21) = col1tmp+32 */
/* Q11(D22,D23) = col1tmp+48 */
/* Q12(D24,D25) = divider */
__asm__ __volatile__ (
"mov r12, %4\n\t"
"vdup.8 q12, r12\n\t"
"neon32_armv7_fastblend_iter%=:\n\t"
"vldm %0!, {q0,q1,q2,q3}\n\t"
"vldm %1!, {q4,q5,q6,q7}\n\t"
"pld [%0, #256]\n\t"
"pld [%1, #256]\n\t"
"vrshl.u8 q8, q0, q12\n\t"
"vrshl.u8 q9, q1, q12\n\t"
"vrshl.u8 q10, q2, q12\n\t"
"vrshl.u8 q11, q3, q12\n\t"
"vrshl.u8 q4, q4, q12\n\t"
"vrshl.u8 q5, q5, q12\n\t"
"vrshl.u8 q6, q6, q12\n\t"
"vrshl.u8 q7, q7, q12\n\t"
"vsub.i8 q4, q4, q8\n\t"
"vsub.i8 q5, q5, q9\n\t"
"vsub.i8 q6, q6, q10\n\t"
"vsub.i8 q7, q7, q11\n\t"
"vadd.i8 q4, q4, q0\n\t"
"vadd.i8 q5, q5, q1\n\t"
"vadd.i8 q6, q6, q2\n\t"
"vadd.i8 q7, q7, q3\n\t"
"vstm %2!, {q4,q5,q6,q7}\n\t"
"subs %3, %3, #64\n\t"
"bne neon32_armv7_fastblend_iter%=\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (divider)
: "%r12", "%q0", "%q1", "%q2", "%q3", "%q4", "%q5", "%q6", "%q7", "%q8", "%q9", "%q10", "%q11", "%q12", "cc", "memory"
);
#else
Panic("Neon function called on a non-ARM platform or Neon code is absent");
#endif
}
__attribute__((noinline)) void neon64_armv8_fastblend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) {
#if (defined(__aarch64__) && !defined(ZM_STRIP_NEON))
static double current_blendpercent = 0.0;
static int8_t divider = 0;
if (current_blendpercent != blendpercent) {
/* Attempt to match the blending percent to one of the possible values */
if(blendpercent < 2.34375) {
// 1.5625% blending
divider = 6;
} else if(blendpercent >= 2.34375 && blendpercent < 4.6875) {
// 3.125% blending
divider = 5;
} else if(blendpercent >= 4.6875 && blendpercent < 9.375) {
// 6.25% blending
divider = 4;
} else if(blendpercent >= 9.375 && blendpercent < 18.75) {
// 12.5% blending
divider = 3;
} else if(blendpercent >= 18.75 && blendpercent < 37.5) {
// 25% blending
divider = 2;
} else if(blendpercent >= 37.5) {
// 50% blending
divider = 1;
}
// We only have instruction to shift left by a variable, going negative shifts right :)
divider *= -1;
current_blendpercent = blendpercent;
}
/* V16 = col1+0 */
/* V17 = col1+16 */
/* V18 = col1+32 */
/* V19 = col1+48 */
/* V20 = col2+0 */
/* V21 = col2+16 */
/* V22 = col2+32 */
/* V23 = col2+48 */
/* V24 = col1tmp+0 */
/* V25 = col1tmp+16 */
/* V26 = col1tmp+32 */
/* V27 = col1tmp+48 */
/* V28 = divider */
__asm__ __volatile__ (
"mov x12, %4\n\t"
"dup v28.16b, w12\n\t"
"neon64_armv8_fastblend_iter%=:\n\t"
"ldp q16, q17, [%0], #32\n\t"
"ldp q18, q19, [%0], #32\n\t"
"ldp q20, q21, [%1], #32\n\t"
"ldp q22, q23, [%1], #32\n\t"
"prfm pldl1keep, [%0, #256]\n\t"
"prfm pldl1keep, [%1, #256]\n\t"
"urshl v24.16b, v16.16b, v28.16b\n\t"
"urshl v25.16b, v17.16b, v28.16b\n\t"
"urshl v26.16b, v18.16b, v28.16b\n\t"
"urshl v27.16b, v19.16b, v28.16b\n\t"
"urshl v20.16b, v20.16b, v28.16b\n\t"
"urshl v21.16b, v21.16b, v28.16b\n\t"
"urshl v22.16b, v22.16b, v28.16b\n\t"
"urshl v23.16b, v23.16b, v28.16b\n\t"
"sub v20.16b, v20.16b, v24.16b\n\t"
"sub v21.16b, v21.16b, v25.16b\n\t"
"sub v22.16b, v22.16b, v26.16b\n\t"
"sub v23.16b, v23.16b, v27.16b\n\t"
"add v20.16b, v20.16b, v16.16b\n\t"
"add v21.16b, v21.16b, v17.16b\n\t"
"add v22.16b, v22.16b, v18.16b\n\t"
"add v23.16b, v23.16b, v19.16b\n\t"
"stp q20, q21, [%2], #32\n\t"
"stp q22, q23, [%2], #32\n\t"
"subs %3, %3, #64\n\t"
"bne neon64_armv8_fastblend_iter%=\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (divider)
: "%x12", "%v16", "%v17", "%v18", "%v19", "%v20", "%v21", "%v22", "%v23", "%v24", "%v25", "%v26", "%v27", "%v28", "cc", "memory"
);
#else
Panic("Neon function called on a non-ARM platform or Neon code is absent");
#endif
}
__attribute__((noinline)) void std_blend(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, double blendpercent) {
Warning("Using slow std_blend");
double divide = blendpercent / 100.0;
double opacity = 1.0 - divide;
const uint8_t* const max_ptr = result + count;
while ( result < max_ptr ) {
*result++ = (*col1++ * opacity) + (*col2++ * divide);
}
}
/************************************************* DELTA FUNCTIONS *************************************************/
/* Grayscale */
__attribute__((noinline)) void fast_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 16 bytes (16 grayscale pixels) at a time */
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
result[0] = abs(col1[0] - col2[0]);
result[1] = abs(col1[1] - col2[1]);
result[2] = abs(col1[2] - col2[2]);
result[3] = abs(col1[3] - col2[3]);
result[4] = abs(col1[4] - col2[4]);
result[5] = abs(col1[5] - col2[5]);
result[6] = abs(col1[6] - col2[6]);
result[7] = abs(col1[7] - col2[7]);
result[8] = abs(col1[8] - col2[8]);
result[9] = abs(col1[9] - col2[9]);
result[10] = abs(col1[10] - col2[10]);
result[11] = abs(col1[11] - col2[11]);
result[12] = abs(col1[12] - col2[12]);
result[13] = abs(col1[13] - col2[13]);
result[14] = abs(col1[14] - col2[14]);
result[15] = abs(col1[15] - col2[15]);
col1 += 16;
col2 += 16;
result += 16;
}
}
__attribute__((noinline)) void std_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
result[0] = abs(col1[0] - col2[0]);
col1 += 1;
col2 += 1;
result += 1;
}
}
/* RGB24: RGB */
__attribute__((noinline)) void fast_delta8_rgb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 12 bytes (4 rgb24 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
r = abs(col1[0] - col2[0]);
g = abs(col1[1] - col2[1]);
b = abs(col1[2] - col2[2]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
r = abs(col1[3] - col2[3]);
g = abs(col1[4] - col2[4]);
b = abs(col1[5] - col2[5]);
result[1] = (r + r + b + g + g + g + g + g)>>3;
r = abs(col1[6] - col2[6]);
g = abs(col1[7] - col2[7]);
b = abs(col1[8] - col2[8]);
result[2] = (r + r + b + g + g + g + g + g)>>3;
r = abs(col1[9] - col2[9]);
g = abs(col1[10] - col2[10]);
b = abs(col1[11] - col2[11]);
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 12;
col2 += 12;
result += 4;
}
}
__attribute__((noinline)) void std_delta8_rgb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 12 bytes (4 rgb24 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
r = abs(col1[0] - col2[0]);
g = abs(col1[1] - col2[1]);
b = abs(col1[2] - col2[2]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 3;
col2 += 3;
result += 1;
}
}
/* RGB24: BGR */
__attribute__((noinline)) void fast_delta8_bgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 12 bytes (4 rgb24 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
b = abs(col1[0] - col2[0]);
g = abs(col1[1] - col2[1]);
r = abs(col1[2] - col2[2]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
b = abs(col1[3] - col2[3]);
g = abs(col1[4] - col2[4]);
r = abs(col1[5] - col2[5]);
result[1] = (r + r + b + g + g + g + g + g)>>3;
b = abs(col1[6] - col2[6]);
g = abs(col1[7] - col2[7]);
r = abs(col1[8] - col2[8]);
result[2] = (r + r + b + g + g + g + g + g)>>3;
b = abs(col1[9] - col2[9]);
g = abs(col1[10] - col2[10]);
r = abs(col1[11] - col2[11]);
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 12;
col2 += 12;
result += 4;
}
}
__attribute__((noinline)) void std_delta8_bgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 12 bytes (4 rgb24 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
b = abs(col1[0] - col2[0]);
g = abs(col1[1] - col2[1]);
r = abs(col1[2] - col2[2]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 3;
col2 += 3;
result += 1;
}
}
/* RGB32: RGBA */
__attribute__((noinline)) void fast_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
r = abs(col1[0] - col2[0]);
g = abs(col1[1] - col2[1]);
b = abs(col1[2] - col2[2]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
r = abs(col1[4] - col2[4]);
g = abs(col1[5] - col2[5]);
b = abs(col1[6] - col2[6]);
result[1] = (r + r + b + g + g + g + g + g)>>3;
r = abs(col1[8] - col2[8]);
g = abs(col1[9] - col2[9]);
b = abs(col1[10] - col2[10]);
result[2] = (r + r + b + g + g + g + g + g)>>3;
r = abs(col1[12] - col2[12]);
g = abs(col1[13] - col2[13]);
b = abs(col1[14] - col2[14]);
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 16;
col2 += 16;
result += 4;
}
}
__attribute__((noinline)) void std_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
r = abs(col1[0] - col2[0]);
g = abs(col1[1] - col2[1]);
b = abs(col1[2] - col2[2]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 4;
col2 += 4;
result += 1;
}
}
/* RGB32: BGRA */
__attribute__((noinline)) void fast_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
b = abs(col1[0] - col2[0]);
g = abs(col1[1] - col2[1]);
r = abs(col1[2] - col2[2]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
b = abs(col1[4] - col2[4]);
g = abs(col1[5] - col2[5]);
r = abs(col1[6] - col2[6]);
result[1] = (r + r + b + g + g + g + g + g)>>3;
b = abs(col1[8] - col2[8]);
g = abs(col1[9] - col2[9]);
r = abs(col1[10] - col2[10]);
result[2] = (r + r + b + g + g + g + g + g)>>3;
b = abs(col1[12] - col2[12]);
g = abs(col1[13] - col2[13]);
r = abs(col1[14] - col2[14]);
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 16;
col2 += 16;
result += 4;
}
}
__attribute__((noinline)) void std_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
b = abs(col1[0] - col2[0]);
g = abs(col1[1] - col2[1]);
r = abs(col1[2] - col2[2]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 4;
col2 += 4;
result += 1;
}
}
/* RGB32: ARGB */
__attribute__((noinline)) void fast_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
r = abs(col1[1] - col2[1]);
g = abs(col1[2] - col2[2]);
b = abs(col1[3] - col2[3]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
r = abs(col1[5] - col2[5]);
g = abs(col1[6] - col2[6]);
b = abs(col1[7] - col2[7]);
result[1] = (r + r + b + g + g + g + g + g)>>3;
r = abs(col1[9] - col2[9]);
g = abs(col1[10] - col2[10]);
b = abs(col1[11] - col2[11]);
result[2] = (r + r + b + g + g + g + g + g)>>3;
r = abs(col1[13] - col2[13]);
g = abs(col1[14] - col2[14]);
b = abs(col1[15] - col2[15]);
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 16;
col2 += 16;
result += 4;
}
}
__attribute__((noinline)) void std_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
r = abs(col1[1] - col2[1]);
g = abs(col1[2] - col2[2]);
b = abs(col1[3] - col2[3]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 4;
col2 += 4;
result += 1;
}
}
/* RGB32: ABGR */
__attribute__((noinline)) void fast_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
/* Loop unrolling is used to work on 16 bytes (4 rgb32 pixels) at a time */
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
b = abs(col1[1] - col2[1]);
g = abs(col1[2] - col2[2]);
r = abs(col1[3] - col2[3]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
b = abs(col1[5] - col2[5]);
g = abs(col1[6] - col2[6]);
r = abs(col1[7] - col2[7]);
result[1] = (r + r + b + g + g + g + g + g)>>3;
b = abs(col1[9] - col2[9]);
g = abs(col1[10] - col2[10]);
r = abs(col1[11] - col2[11]);
result[2] = (r + r + b + g + g + g + g + g)>>3;
b = abs(col1[13] - col2[13]);
g = abs(col1[14] - col2[14]);
r = abs(col1[15] - col2[15]);
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 16;
col2 += 16;
result += 4;
}
}
__attribute__((noinline)) void std_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
int r,g,b;
const uint8_t* const max_ptr = result + count;
while (result < max_ptr) {
b = abs(col1[1] - col2[1]);
g = abs(col1[2] - col2[2]);
r = abs(col1[3] - col2[3]);
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 4;
col2 += 4;
result += 1;
}
}
/* Grayscale Neon for AArch32 */
#if (defined(__arm__) && defined(__ARM_PCS_VFP) && !defined(ZM_STRIP_NEON))
__attribute__((noinline,__target__("fpu=neon")))
#endif
void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
#if (defined(__arm__) && defined(__ARM_PCS_VFP) && !defined(ZM_STRIP_NEON))
/* Q0(D0,D1) = col1+0 */
/* Q1(D2,D3) = col1+16 */
/* Q2(D4,D5) = col1+32 */
/* Q3(D6,D7) = col1+48 */
/* Q4(D8,D9) = col2+0 */
/* Q5(D10,D11) = col2+16 */
/* Q6(D12,D13) = col2+32 */
/* Q7(D14,D15) = col2+48 */
__asm__ __volatile__ (
"neon32_armv7_delta8_gray8_iter%=:\n\t"
"vldm %0!, {q0,q1,q2,q3}\n\t"
"vldm %1!, {q4,q5,q6,q7}\n\t"
"pld [%0, #512]\n\t"
"pld [%1, #512]\n\t"
"vabd.u8 q0, q0, q4\n\t"
"vabd.u8 q1, q1, q5\n\t"
"vabd.u8 q2, q2, q6\n\t"
"vabd.u8 q3, q3, q7\n\t"
"vstm %2!, {q0,q1,q2,q3}\n\t"
"subs %3, %3, #64\n\t"
"bne neon32_armv7_delta8_gray8_iter%=\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count)
: "%q0", "%q1", "%q2", "%q3", "%q4", "%q5", "%q6", "%q7", "cc", "memory"
);
#else
Panic("Neon function called on a non-ARM platform or Neon code is absent");
#endif
}
/* Grayscale Neon for AArch64 */
__attribute__((noinline)) void neon64_armv8_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
#if (defined(__aarch64__) && !defined(ZM_STRIP_NEON))
/* V16 = col1+0 */
/* V17 = col1+16 */
/* V18 = col1+32 */
/* V19 = col1+48 */
/* V20 = col2+0 */
/* V21 = col2+16 */
/* V22 = col2+32 */
/* V23 = col2+48 */
__asm__ __volatile__ (
"neon64_armv8_delta8_gray8_iter%=:\n\t"
"ldp q16, q17, [%0], #32\n\t"
"ldp q18, q19, [%0], #32\n\t"
"ldp q20, q21, [%1], #32\n\t"
"ldp q22, q23, [%1], #32\n\t"
"prfm pldl1keep, [%0, #512]\n\t"
"prfm pldl1keep, [%1, #512]\n\t"
"uabd v16.16b, v16.16b, v20.16b\n\t"
"uabd v17.16b, v17.16b, v21.16b\n\t"
"uabd v18.16b, v18.16b, v22.16b\n\t"
"uabd v19.16b, v19.16b, v23.16b\n\t"
"stp q16, q17, [%2], #32\n\t"
"stp q18, q19, [%2], #32\n\t"
"subs %3, %3, #64\n\t"
"bne neon64_armv8_delta8_gray8_iter%=\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count)
: "%v16", "%v17", "%v18", "%v19", "%v20", "%v21", "%v22", "%v23", "cc", "memory"
);
#else
Panic("Neon function called on a non-ARM platform or Neon code is absent");
#endif
}
/* RGB32 Neon for AArch32 */
#if (defined(__arm__) && defined(__ARM_PCS_VFP) && !defined(ZM_STRIP_NEON))
__attribute__((noinline,__target__("fpu=neon")))
#endif
void neon32_armv7_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) {
#if (defined(__arm__) && defined(__ARM_PCS_VFP) && !defined(ZM_STRIP_NEON))
/* Q0(D0,D1) = col1+0 */
/* Q1(D2,D3) = col1+16 */
/* Q2(D4,D5) = col1+32 */
/* Q3(D6,D7) = col1+48 */
/* Q4(D8,D9) = col2+0 */
/* Q5(D10,D11) = col2+16 */
/* Q6(D12,D13) = col2+32 */
/* Q7(D14,D15) = col2+48 */
/* Q8(D16,D17) = multiplier */
__asm__ __volatile__ (
"mov r12, %4\n\t"
"vdup.32 q8, r12\n\t"
"neon32_armv7_delta8_rgb32_iter%=:\n\t"
"vldm %0!, {q0,q1,q2,q3}\n\t"
"vldm %1!, {q4,q5,q6,q7}\n\t"
"pld [%0, #256]\n\t"
"pld [%1, #256]\n\t"
"vabd.u8 q0, q0, q4\n\t"
"vabd.u8 q1, q1, q5\n\t"
"vabd.u8 q2, q2, q6\n\t"
"vabd.u8 q3, q3, q7\n\t"
"vrshr.u8 q0, q0, #3\n\t"
"vrshr.u8 q1, q1, #3\n\t"
"vrshr.u8 q2, q2, #3\n\t"
"vrshr.u8 q3, q3, #3\n\t"
"vmul.i8 q0, q0, q8\n\t"
"vmul.i8 q1, q1, q8\n\t"
"vmul.i8 q2, q2, q8\n\t"
"vmul.i8 q3, q3, q8\n\t"
"vpadd.i8 d0, d0, d1\n\t"
"vpadd.i8 d2, d2, d3\n\t"
"vpadd.i8 d4, d4, d5\n\t"
"vpadd.i8 d6, d6, d7\n\t"
"vpadd.i8 d0, d0, d0\n\t"
"vpadd.i8 d1, d2, d2\n\t"
"vpadd.i8 d2, d4, d4\n\t"
"vpadd.i8 d3, d6, d6\n\t"
"vst4.32 {d0[0],d1[0],d2[0],d3[0]}, [%2]!\n\t"
"subs %3, %3, #16\n\t"
"bne neon32_armv7_delta8_rgb32_iter%=\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (multiplier)
: "%r12", "%q0", "%q1", "%q2", "%q3", "%q4", "%q5", "%q6", "%q7", "%q8", "cc", "memory"
);
#else
Panic("Neon function called on a non-ARM platform or Neon code is absent");
#endif
}
/* RGB32 Neon for AArch64 */
__attribute__((noinline)) void neon64_armv8_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) {
#if (defined(__aarch64__) && !defined(ZM_STRIP_NEON))
/* V16 = col1+0 */
/* V17 = col1+16 */
/* V18 = col1+32 */
/* V19 = col1+48 */
/* V20 = col2+0 */
/* V21 = col2+16 */
/* V22 = col2+32 */
/* V23 = col2+48 */
/* V24 = multiplier */
__asm__ __volatile__ (
"mov x12, %4\n\t"
"dup v24.4s, w12\n\t"
"neon64_armv8_delta8_rgb32_iter%=:\n\t"
"ldp q16, q17, [%0], #32\n\t"
"ldp q18, q19, [%0], #32\n\t"
"ldp q20, q21, [%1], #32\n\t"
"ldp q22, q23, [%1], #32\n\t"
"prfm pldl1keep, [%0, #256]\n\t"
"prfm pldl1keep, [%1, #256]\n\t"
"uabd v16.16b, v16.16b, v20.16b\n\t"
"uabd v17.16b, v17.16b, v21.16b\n\t"
"uabd v18.16b, v18.16b, v22.16b\n\t"
"uabd v19.16b, v19.16b, v23.16b\n\t"
"urshr v16.16b, v16.16b, #3\n\t"
"urshr v17.16b, v17.16b, #3\n\t"
"urshr v18.16b, v18.16b, #3\n\t"
"urshr v19.16b, v19.16b, #3\n\t"
"mul v16.16b, v16.16b, v24.16b\n\t"
"mul v17.16b, v17.16b, v24.16b\n\t"
"mul v18.16b, v18.16b, v24.16b\n\t"
"mul v19.16b, v19.16b, v24.16b\n\t"
"addp v16.16b, v16.16b, v16.16b\n\t"
"addp v17.16b, v17.16b, v17.16b\n\t"
"addp v18.16b, v18.16b, v18.16b\n\t"
"addp v19.16b, v19.16b, v19.16b\n\t"
"addp v16.16b, v16.16b, v16.16b\n\t"
"addp v17.16b, v17.16b, v17.16b\n\t"
"addp v18.16b, v18.16b, v18.16b\n\t"
"addp v19.16b, v19.16b, v19.16b\n\t"
"st4 {v16.s, v17.s, v18.s, v19.s}[0], [%2], #16\n\t"
"subs %3, %3, #16\n\t"
"bne neon64_armv8_delta8_rgb32_iter%=\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count), "r" (multiplier)
: "%x12", "%v16", "%v17", "%v18", "%v19", "%v20", "%v21", "%v22", "%v23", "%v24", "cc", "memory"
);
#else
Panic("Neon function called on a non-ARM platform or Neon code is absent");
#endif
}
/* RGB32: RGBA Neon for AArch32 */
void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x00010502);
}
/* RGB32: BGRA Neon for AArch32 */
void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x00020501);
}
/* RGB32: ARGB Neon for AArch32 */
void neon32_armv7_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x01050200);
}
/* RGB32: ABGR Neon for AArch32 */
void neon32_armv7_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
neon32_armv7_delta8_rgb32(col1, col2, result, count, 0x02050100);
}
/* RGB32: RGBA Neon for AArch64 */
void neon64_armv8_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x00010502);
}
/* RGB32: BGRA Neon for AArch64 */
void neon64_armv8_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x00020501);
}
/* RGB32: ARGB Neon for AArch64 */
void neon64_armv8_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x01050200);
}
/* RGB32: ABGR Neon for AArch64 */
void neon64_armv8_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
neon64_armv8_delta8_rgb32(col1, col2, result, count, 0x02050100);
}
/* Grayscale SSE2 */
#if defined(__i386__) || defined(__x86_64__)
__attribute__((noinline,__target__("sse2")))
#endif
void sse2_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
__asm__ __volatile__ (
"sub $0x10, %0\n\t"
"sub $0x10, %1\n\t"
"sub $0x10, %2\n\t"
"sse2_delta8_gray8_iter:\n\t"
"movdqa (%0,%3), %%xmm1\n\t"
"movdqa (%1,%3), %%xmm2\n\t"
"movdqa %%xmm1, %%xmm3\n\t"
"movdqa %%xmm2, %%xmm4\n\t"
"pmaxub %%xmm1, %%xmm2\n\t"
"pminub %%xmm3, %%xmm4\n\t"
"psubb %%xmm4, %%xmm2\n\t"
"movntdq %%xmm2, (%2,%3)\n\t"
"sub $0x10, %3\n\t"
"jnz sse2_delta8_gray8_iter\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count)
: "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory"
);
#else
Panic("SSE function called on a non x86\\x86-64 platform");
#endif
}
/* RGB32: RGBA SSE2 */
#if defined(__i386__) || defined(__x86_64__)
__attribute__((noinline,__target__("sse2")))
#endif
void sse2_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
__asm__ __volatile__ (
"mov $0x1F1F1F1F, %%eax\n\t"
"movd %%eax, %%xmm4\n\t"
"pshufd $0x0, %%xmm4, %%xmm4\n\t"
"mov $0xff, %%eax\n\t"
"movd %%eax, %%xmm0\n\t"
"pshufd $0x0, %%xmm0, %%xmm0\n\t"
"sub $0x10, %0\n\t"
"sub $0x10, %1\n\t"
"sub $0x4, %2\n\t"
"sse2_delta8_rgba_iter:\n\t"
"movdqa (%0,%3,4), %%xmm1\n\t"
"movdqa (%1,%3,4), %%xmm2\n\t"
"psrlq $0x3, %%xmm1\n\t"
"psrlq $0x3, %%xmm2\n\t"
"pand %%xmm4, %%xmm1\n\t"
"pand %%xmm4, %%xmm2\n\t"
"movdqa %%xmm1, %%xmm5\n\t"
"movdqa %%xmm2, %%xmm6\n\t"
"pmaxub %%xmm1, %%xmm2\n\t"
"pminub %%xmm5, %%xmm6\n\t"
"psubb %%xmm6, %%xmm2\n\t"
"movdqa %%xmm2, %%xmm3\n\t"
"psrld $0x8, %%xmm2\n\t"
"pand %%xmm0, %%xmm2\n\t"
"movdqa %%xmm2, %%xmm1\n\t"
"pslld $0x2, %%xmm2\n\t"
"paddd %%xmm1, %%xmm2\n\t"
"movdqa %%xmm3, %%xmm1\n\t"
"pand %%xmm0, %%xmm1\n\t"
"paddd %%xmm1, %%xmm1\n\t"
"paddd %%xmm2, %%xmm1\n\t"
"movdqa %%xmm3, %%xmm2\n\t"
"psrld $0x10, %%xmm2\n\t"
"pand %%xmm0, %%xmm2\n\t"
"paddd %%xmm2, %%xmm1\n\t"
"packssdw %%xmm1, %%xmm1\n\t"
"packuswb %%xmm1, %%xmm1\n\t"
"movd %%xmm1, %%eax\n\t"
"movnti %%eax, (%2,%3)\n\t"
"sub $0x4, %3\n\t"
"jnz sse2_delta8_rgba_iter\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count)
: "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory"
);
#else
Panic("SSE function called on a non x86\\x86-64 platform");
#endif
}
/* RGB32: BGRA SSE2 */
#if defined(__i386__) || defined(__x86_64__)
__attribute__((noinline,__target__("sse2")))
#endif
void sse2_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
__asm__ __volatile__ (
"mov $0x1F1F1F1F, %%eax\n\t"
"movd %%eax, %%xmm4\n\t"
"pshufd $0x0, %%xmm4, %%xmm4\n\t"
"mov $0xff, %%eax\n\t"
"movd %%eax, %%xmm0\n\t"
"pshufd $0x0, %%xmm0, %%xmm0\n\t"
"sub $0x10, %0\n\t"
"sub $0x10, %1\n\t"
"sub $0x4, %2\n\t"
"sse2_delta8_bgra_iter:\n\t"
"movdqa (%0,%3,4), %%xmm1\n\t"
"movdqa (%1,%3,4), %%xmm2\n\t"
"psrlq $0x3, %%xmm1\n\t"
"psrlq $0x3, %%xmm2\n\t"
"pand %%xmm4, %%xmm1\n\t"
"pand %%xmm4, %%xmm2\n\t"
"movdqa %%xmm1, %%xmm5\n\t"
"movdqa %%xmm2, %%xmm6\n\t"
"pmaxub %%xmm1, %%xmm2\n\t"
"pminub %%xmm5, %%xmm6\n\t"
"psubb %%xmm6, %%xmm2\n\t"
"movdqa %%xmm2, %%xmm3\n\t"
"psrld $0x8, %%xmm2\n\t"
"pand %%xmm0, %%xmm2\n\t"
"movdqa %%xmm2, %%xmm1\n\t"
"pslld $0x2, %%xmm2\n\t"
"paddd %%xmm1, %%xmm2\n\t"
"movdqa %%xmm3, %%xmm1\n\t"
"pand %%xmm0, %%xmm1\n\t"
"paddd %%xmm2, %%xmm1\n\t"
"movdqa %%xmm3, %%xmm2\n\t"
"psrld $0x10, %%xmm2\n\t"
"pand %%xmm0, %%xmm2\n\t"
"paddd %%xmm2, %%xmm2\n\t"
"paddd %%xmm2, %%xmm1\n\t"
"packssdw %%xmm1, %%xmm1\n\t"
"packuswb %%xmm1, %%xmm1\n\t"
"movd %%xmm1, %%eax\n\t"
"movnti %%eax, (%2,%3)\n\t"
"sub $0x4, %3\n\t"
"jnz sse2_delta8_bgra_iter\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count)
: "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory"
);
#else
Panic("SSE function called on a non x86\\x86-64 platform");
#endif
}
/* RGB32: ARGB SSE2 */
#if defined(__i386__) || defined(__x86_64__)
__attribute__((noinline,__target__("sse2")))
#endif
void sse2_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
__asm__ __volatile__ (
"mov $0x1F1F1F1F, %%eax\n\t"
"movd %%eax, %%xmm4\n\t"
"pshufd $0x0, %%xmm4, %%xmm4\n\t"
"mov $0xff, %%eax\n\t"
"movd %%eax, %%xmm0\n\t"
"pshufd $0x0, %%xmm0, %%xmm0\n\t"
"sub $0x10, %0\n\t"
"sub $0x10, %1\n\t"
"sub $0x4, %2\n\t"
"sse2_delta8_argb_iter:\n\t"
"movdqa (%0,%3,4), %%xmm1\n\t"
"movdqa (%1,%3,4), %%xmm2\n\t"
"psrlq $0x3, %%xmm1\n\t"
"psrlq $0x3, %%xmm2\n\t"
"pand %%xmm4, %%xmm1\n\t"
"pand %%xmm4, %%xmm2\n\t"
"movdqa %%xmm1, %%xmm5\n\t"
"movdqa %%xmm2, %%xmm6\n\t"
"pmaxub %%xmm1, %%xmm2\n\t"
"pminub %%xmm5, %%xmm6\n\t"
"psubb %%xmm6, %%xmm2\n\t"
"movdqa %%xmm2, %%xmm3\n\t"
"psrld $0x10, %%xmm2\n\t"
"pand %%xmm0, %%xmm2\n\t"
"movdqa %%xmm2, %%xmm1\n\t"
"pslld $0x2, %%xmm2\n\t"
"paddd %%xmm1, %%xmm2\n\t"
"movdqa %%xmm3, %%xmm1\n\t"
"psrld $0x8, %%xmm1\n\t"
"pand %%xmm0, %%xmm1\n\t"
"paddd %%xmm1, %%xmm1\n\t"
"paddd %%xmm2, %%xmm1\n\t"
"movdqa %%xmm3, %%xmm2\n\t"
"psrld $0x18, %%xmm2\n\t"
"pand %%xmm0, %%xmm2\n\t"
"paddd %%xmm2, %%xmm1\n\t"
"packssdw %%xmm1, %%xmm1\n\t"
"packuswb %%xmm1, %%xmm1\n\t"
"movd %%xmm1, %%eax\n\t"
"movnti %%eax, (%2,%3)\n\t"
"sub $0x4, %3\n\t"
"jnz sse2_delta8_argb_iter\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count)
: "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory"
);
#else
Panic("SSE function called on a non x86\\x86-64 platform");
#endif
}
/* RGB32: ABGR SSE2 */
#if defined(__i386__) || defined(__x86_64__)
__attribute__((noinline,__target__("sse2")))
#endif
void sse2_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
__asm__ __volatile__ (
"mov $0x1F1F1F1F, %%eax\n\t"
"movd %%eax, %%xmm4\n\t"
"pshufd $0x0, %%xmm4, %%xmm4\n\t"
"mov $0xff, %%eax\n\t"
"movd %%eax, %%xmm0\n\t"
"pshufd $0x0, %%xmm0, %%xmm0\n\t"
"sub $0x10, %0\n\t"
"sub $0x10, %1\n\t"
"sub $0x4, %2\n\t"
"sse2_delta8_abgr_iter:\n\t"
"movdqa (%0,%3,4), %%xmm1\n\t"
"movdqa (%1,%3,4), %%xmm2\n\t"
"psrlq $0x3, %%xmm1\n\t"
"psrlq $0x3, %%xmm2\n\t"
"pand %%xmm4, %%xmm1\n\t"
"pand %%xmm4, %%xmm2\n\t"
"movdqa %%xmm1, %%xmm5\n\t"
"movdqa %%xmm2, %%xmm6\n\t"
"pmaxub %%xmm1, %%xmm2\n\t"
"pminub %%xmm5, %%xmm6\n\t"
"psubb %%xmm6, %%xmm2\n\t"
"movdqa %%xmm2, %%xmm3\n\t"
"psrld $0x10, %%xmm2\n\t"
"pand %%xmm0, %%xmm2\n\t"
"movdqa %%xmm2, %%xmm1\n\t"
"pslld $0x2, %%xmm2\n\t"
"paddd %%xmm1, %%xmm2\n\t"
"movdqa %%xmm3, %%xmm1\n\t"
"psrld $0x8, %%xmm1\n\t"
"pand %%xmm0, %%xmm1\n\t"
"paddd %%xmm2, %%xmm1\n\t"
"movdqa %%xmm3, %%xmm2\n\t"
"psrld $0x18, %%xmm2\n\t"
"pand %%xmm0, %%xmm2\n\t"
"paddd %%xmm2, %%xmm2\n\t"
"paddd %%xmm2, %%xmm1\n\t"
"packssdw %%xmm1, %%xmm1\n\t"
"packuswb %%xmm1, %%xmm1\n\t"
"movd %%xmm1, %%eax\n\t"
"movnti %%eax, (%2,%3)\n\t"
"sub $0x4, %3\n\t"
"jnz sse2_delta8_abgr_iter\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count)
: "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "cc", "memory"
);
#else
Panic("SSE function called on a non x86\\x86-64 platform");
#endif
}
/* RGB32 SSSE3 */
#if defined(__i386__) || defined(__x86_64__)
__attribute__((noinline,__target__("ssse3")))
#endif
void ssse3_delta8_rgb32(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count, uint32_t multiplier) {
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
/* XMM0 - zero */
/* XMM1 - col1 */
/* XMM2 - col2 */
/* XMM3 - multiplier */
/* XMM4 - divide mask */
__asm__ __volatile__ (
"mov $0x1F1F1F1F, %%eax\n\t"
"movd %%eax, %%xmm4\n\t"
"pshufd $0x0, %%xmm4, %%xmm4\n\t"
"mov %4, %%eax\n\t"
"movd %%eax, %%xmm3\n\t"
"pshufd $0x0, %%xmm3, %%xmm3\n\t"
"pxor %%xmm0, %%xmm0\n\t"
"sub $0x10, %0\n\t"
"sub $0x10, %1\n\t"
"sub $0x4, %2\n\t"
"ssse3_delta8_rgb32_iter:\n\t"
"movdqa (%0,%3,4), %%xmm1\n\t"
"movdqa (%1,%3,4), %%xmm2\n\t"
"psrlq $0x3, %%xmm1\n\t"
"psrlq $0x3, %%xmm2\n\t"
"pand %%xmm4, %%xmm1\n\t"
"pand %%xmm4, %%xmm2\n\t"
"psubb %%xmm2, %%xmm1\n\t"
"pabsb %%xmm1, %%xmm1\n\t"
"pmaddubsw %%xmm3, %%xmm1\n\t"
"phaddw %%xmm0, %%xmm1\n\t"
"packuswb %%xmm1, %%xmm1\n\t"
"movd %%xmm1, %%eax\n\t"
"movnti %%eax, (%2,%3)\n\t"
"sub $0x4, %3\n\t"
"jnz ssse3_delta8_rgb32_iter\n\t"
:
: "r" (col1), "r" (col2), "r" (result), "r" (count), "g" (multiplier)
: "%eax", "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "cc", "memory"
);
#else
Panic("SSE function called on a non x86\\x86-64 platform");
#endif
}
/* RGB32: RGBA SSSE3 */
void ssse3_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
ssse3_delta8_rgb32(col1, col2, result, count, 0x00010502);
}
/* RGB32: BGRA SSSE3 */
void ssse3_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
ssse3_delta8_rgb32(col1, col2, result, count, 0x00020501);
}
/* RGB32: ARGB SSSE3 */
void ssse3_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
ssse3_delta8_rgb32(col1, col2, result, count, 0x01050200);
}
/* RGB32: ABGR SSSE3 */
void ssse3_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count) {
ssse3_delta8_rgb32(col1, col2, result, count, 0x02050100);
}
/************************************************* CONVERT FUNCTIONS *************************************************/
/* RGB24 to grayscale */
__attribute__((noinline)) void fast_convert_rgb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
r = col1[0];
g = col1[1];
b = col1[2];
result[0] = (r + r + b + g + g + g + g + g)>>3;
r = col1[3];
g = col1[4];
b = col1[5];
result[1] = (r + r + b + g + g + g + g + g)>>3;
r = col1[6];
g = col1[7];
b = col1[8];
result[2] = (r + r + b + g + g + g + g + g)>>3;
r = col1[9];
g = col1[10];
b = col1[11];
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 12;
result += 4;
}
}
__attribute__((noinline)) void std_convert_rgb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
r = col1[0];
g = col1[1];
b = col1[2];
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 3;
result += 1;
}
}
/* BGR24 to grayscale */
__attribute__((noinline)) void fast_convert_bgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
b = col1[0];
g = col1[1];
r = col1[2];
result[0] = (r + r + b + g + g + g + g + g)>>3;
b = col1[3];
g = col1[4];
r = col1[5];
result[1] = (r + r + b + g + g + g + g + g)>>3;
b = col1[6];
g = col1[7];
r = col1[8];
result[2] = (r + r + b + g + g + g + g + g)>>3;
b = col1[9];
g = col1[10];
r = col1[11];
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 12;
result += 4;
}
}
__attribute__((noinline)) void std_convert_bgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
b = col1[0];
g = col1[1];
r = col1[2];
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 3;
result += 1;
}
}
/* RGBA to grayscale */
__attribute__((noinline)) void fast_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
r = col1[0];
g = col1[1];
b = col1[2];
result[0] = (r + r + b + g + g + g + g + g)>>3;
r = col1[4];
g = col1[5];
b = col1[6];
result[1] = (r + r + b + g + g + g + g + g)>>3;
r = col1[8];
g = col1[9];
b = col1[10];
result[2] = (r + r + b + g + g + g + g + g)>>3;
r = col1[12];
g = col1[13];
b = col1[14];
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 16;
result += 4;
}
}
__attribute__((noinline)) void std_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
r = col1[0];
g = col1[1];
b = col1[2];
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 4;
result += 1;
}
}
/* BGRA to grayscale */
__attribute__((noinline)) void fast_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
b = col1[0];
g = col1[1];
r = col1[2];
result[0] = (r + r + b + g + g + g + g + g)>>3;
b = col1[4];
g = col1[5];
r = col1[6];
result[1] = (r + r + b + g + g + g + g + g)>>3;
b = col1[8];
g = col1[9];
r = col1[10];
result[2] = (r + r + b + g + g + g + g + g)>>3;
b = col1[12];
g = col1[13];
r = col1[14];
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 16;
result += 4;
}
}
__attribute__((noinline)) void std_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
b = col1[0];
g = col1[1];
r = col1[2];
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 4;
result += 1;
}
}
/* ARGB to grayscale */
__attribute__((noinline)) void fast_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
r = col1[1];
g = col1[2];
b = col1[3];
result[0] = (r + r + b + g + g + g + g + g)>>3;
r = col1[5];
g = col1[6];
b = col1[7];
result[1] = (r + r + b + g + g + g + g + g)>>3;
r = col1[9];
g = col1[10];
b = col1[11];
result[2] = (r + r + b + g + g + g + g + g)>>3;
r = col1[13];
g = col1[14];
b = col1[15];
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 16;
result += 4;
}
}
__attribute__((noinline)) void std_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
r = col1[1];
g = col1[2];
b = col1[3];
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 4;
result += 1;
}
}
/* ABGR to grayscale */
__attribute__((noinline)) void fast_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
b = col1[1];
g = col1[2];
r = col1[3];
result[0] = (r + r + b + g + g + g + g + g)>>3;
b = col1[5];
g = col1[6];
r = col1[7];
result[1] = (r + r + b + g + g + g + g + g)>>3;
b = col1[9];
g = col1[10];
r = col1[11];
result[2] = (r + r + b + g + g + g + g + g)>>3;
b = col1[13];
g = col1[14];
r = col1[15];
result[3] = (r + r + b + g + g + g + g + g)>>3;
col1 += 16;
result += 4;
}
}
__attribute__((noinline)) void std_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
b = col1[1];
g = col1[2];
r = col1[3];
result[0] = (r + r + b + g + g + g + g + g)>>3;
col1 += 4;
result += 1;
}
}
/* Converts a YUYV image into grayscale by extracting the Y channel */
__attribute__((noinline)) void fast_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
const uint16_t* yuvbuf = (const uint16_t*)col1;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
result[0] = (uint8_t)yuvbuf[0];
result[1] = (uint8_t)yuvbuf[1];
result[2] = (uint8_t)yuvbuf[2];
result[3] = (uint8_t)yuvbuf[3];
result[4] = (uint8_t)yuvbuf[4];
result[5] = (uint8_t)yuvbuf[5];
result[6] = (uint8_t)yuvbuf[6];
result[7] = (uint8_t)yuvbuf[7];
result[8] = (uint8_t)yuvbuf[8];
result[9] = (uint8_t)yuvbuf[9];
result[10] = (uint8_t)yuvbuf[10];
result[11] = (uint8_t)yuvbuf[11];
result[12] = (uint8_t)yuvbuf[12];
result[13] = (uint8_t)yuvbuf[13];
result[14] = (uint8_t)yuvbuf[14];
result[15] = (uint8_t)yuvbuf[15];
yuvbuf += 16;
result += 16;
}
}
__attribute__((noinline)) void std_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
const uint16_t* yuvbuf = (const uint16_t*)col1;
const uint8_t* const max_ptr = result + count;
while(result < max_ptr) {
result[0] = (uint8_t)yuvbuf[0];
yuvbuf += 1;
result += 1;
}
}
/* RGB32 to grayscale SSSE3 */
#if defined(__i386__) || defined(__x86_64__)
__attribute__((noinline,__target__("ssse3")))
#endif
void ssse3_convert_rgb32_gray8(const uint8_t* col1, uint8_t* result, unsigned long count, uint32_t multiplier) {
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
/* XMM0 - zero */
/* XMM1 - col1 */
/* XMM3 - multiplier */
/* XMM4 - divide mask */
__asm__ __volatile__ (
"mov $0x1F1F1F1F, %%eax\n\t"
"movd %%eax, %%xmm4\n\t"
"pshufd $0x0, %%xmm4, %%xmm4\n\t"
"mov %3, %%eax\n\t"
"movd %%eax, %%xmm3\n\t"
"pshufd $0x0, %%xmm3, %%xmm3\n\t"
"pxor %%xmm0, %%xmm0\n\t"
"sub $0x10, %0\n\t"
"sub $0x4, %1\n\t"
"ssse3_convert_rgb32_gray8_iter:\n\t"
"movdqa (%0,%2,4), %%xmm1\n\t"
"psrlq $0x3, %%xmm1\n\t"
"pand %%xmm4, %%xmm1\n\t"
"pmaddubsw %%xmm3, %%xmm1\n\t"
"phaddw %%xmm0, %%xmm1\n\t"
"packuswb %%xmm1, %%xmm1\n\t"
"movd %%xmm1, %%eax\n\t"
"movnti %%eax, (%1,%2)\n\t"
"sub $0x4, %2\n\t"
"jnz ssse3_convert_rgb32_gray8_iter\n\t"
:
: "r" (col1), "r" (result), "r" (count), "g" (multiplier)
: "%eax", "%xmm0", "%xmm1", "%xmm3", "%xmm4", "cc", "memory"
);
#else
Panic("SSE function called on a non x86\\x86-64 platform");
#endif
}
/* RGBA to grayscale SSSE3 */
void ssse3_convert_rgba_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
ssse3_convert_rgb32_gray8(col1, result, count, 0x00010502);
}
/* BGRA to grayscale SSSE3 */
void ssse3_convert_bgra_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
ssse3_convert_rgb32_gray8(col1, result, count, 0x00020501);
}
/* ARGB to grayscale SSSE3 */
void ssse3_convert_argb_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
ssse3_convert_rgb32_gray8(col1, result, count, 0x01050200);
}
/* ABGR to grayscale SSSE3 */
void ssse3_convert_abgr_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
ssse3_convert_rgb32_gray8(col1, result, count, 0x02050100);
}
/* Converts a YUYV image into grayscale by extracting the Y channel */
#if defined(__i386__) || defined(__x86_64__)
__attribute__((noinline,__target__("ssse3")))
#endif
void ssse3_convert_yuyv_gray8(const uint8_t* col1, uint8_t* result, unsigned long count) {
#if ((defined(__i386__) || defined(__x86_64__) || defined(ZM_KEEP_SSE)) && !defined(ZM_STRIP_SSE))
unsigned long i = 0;
__attribute__((aligned(16))) static const uint8_t movemask1[16] = {0,2,4,6,8,10,12,14,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
__attribute__((aligned(16))) static const uint8_t movemask2[16] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0,2,4,6,8,10,12,14};
/* XMM0 - General purpose */
/* XMM1 - General purpose */
/* XMM2 - unused */
/* XMM3 - shift mask 1 */
/* XMM4 - shift mask 2 */
/* XMM5 - unused*/
/* XMM6 - unused */
/* XMM7 - unused */
__asm__ __volatile__ (
"movdqa %4, %%xmm3\n\t"
"movdqa %5, %%xmm4\n\t"
"algo_ssse3_convert_yuyv_gray8:\n\t"
"movdqa (%0), %%xmm0\n\t"
"pshufb %%xmm3, %%xmm0\n\t"
"movdqa 0x10(%0), %%xmm1\n\t"
"pshufb %%xmm4, %%xmm1\n\t"
"por %%xmm1, %%xmm0\n\t"
"movntdq %%xmm0, (%1)\n\t"
"add $0x10, %3\n\t"
"add $0x10, %1\n\t"
"add $0x20, %0\n\t"
"cmp %2, %3\n\t"
"jb algo_ssse3_convert_yuyv_gray8\n\t"
:
#if (defined(_DEBUG) && !defined(__x86_64__)) /* Use one less register to allow compilation to success on 32bit with omit frame pointer disabled */
: "r" (col1), "r" (result), "m" (count), "r" (i), "m" (*movemask1), "m" (*movemask2)
#else
: "r" (col1), "r" (result), "r" (count), "r" (i), "m" (*movemask1), "m" (*movemask2)
#endif
: "%xmm3", "%xmm4", "cc", "memory"
);
#else
Panic("SSE function called on a non x86\\x86-64 platform");
#endif
}
/* YUYV to RGB24 - relocated from zm_local_camera.cpp */
__attribute__((noinline)) void zm_convert_yuyv_rgb(const uint8_t* col1, uint8_t* result, unsigned long count) {
int32 r,g,b;
int32 y1,y2,u,v;
for(unsigned int i=0; i < count; i += 2, col1 += 4, result += 6) {
y1 = col1[0];
u = col1[1];
y2 = col1[2];
v = col1[3];
r = y1 + r_v_table[v];
g = y1 - (g_u_table[u]+g_v_table[v]);
b = y1 + b_u_table[u];
result[0] = r<0?0:(r>255?255:r);
result[1] = g<0?0:(g>255?255:g);
result[2] = b<0?0:(b>255?255:b);
r = y2 + r_v_table[v];
g = y2 - (g_u_table[u]+g_v_table[v]);
b = y2 + b_u_table[u];
result[3] = r<0?0:(r>255?255:r);
result[4] = g<0?0:(g>255?255:g);
result[5] = b<0?0:(b>255?255:b);
}
}
/* YUYV to RGBA - modified the one above */
__attribute__((noinline)) void zm_convert_yuyv_rgba(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
unsigned int y1,y2,u,v;
for(unsigned int i=0; i < count; i += 2, col1 += 4, result += 8) {
y1 = col1[0];
u = col1[1];
y2 = col1[2];
v = col1[3];
r = y1 + r_v_table[v];
g = y1 - (g_u_table[u]+g_v_table[v]);
b = y1 + b_u_table[u];
result[0] = r>255?255:r;
result[1] = g>255?255:g;
result[2] = b>255?255:b;
r = y2 + r_v_table[v];
g = y2 - (g_u_table[u]+g_v_table[v]);
b = y2 + b_u_table[u];
result[4] = r>255?255:r;
result[5] = g>255?255:g;
result[6] = b>255?255:b;
}
}
/* RGB555 to RGB24 - relocated from zm_local_camera.cpp */
__attribute__((noinline)) void zm_convert_rgb555_rgb(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
for(unsigned int i=0; i < count; i++, col1 += 2, result += 3) {
b = ((*col1)<<3)&0xf8;
g = (((*(col1+1))<<6)|((*col1)>>2))&0xf8;
r = ((*(col1+1))<<1)&0xf8;
result[0] = r;
result[1] = g;
result[2] = b;
}
}
/* RGB555 to RGBA - modified the one above */
__attribute__((noinline)) void zm_convert_rgb555_rgba(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
for(unsigned int i=0; i < count; i++, col1 += 2, result += 4) {
b = ((*col1)<<3)&0xf8;
g = (((*(col1+1))<<6)|((*col1)>>2))&0xf8;
r = ((*(col1+1))<<1)&0xf8;
result[0] = r;
result[1] = g;
result[2] = b;
}
}
/* RGB565 to RGB24 - relocated from zm_local_camera.cpp */
__attribute__((noinline)) void zm_convert_rgb565_rgb(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
for(unsigned int i=0; i < count; i++, col1 += 2, result += 3) {
b = ((*col1)<<3)&0xf8;
g = (((*(col1+1))<<5)|((*col1)>>3))&0xfc;
r = (*(col1+1))&0xf8;
result[0] = r;
result[1] = g;
result[2] = b;
}
}
/* RGB565 to RGBA - modified the one above */
__attribute__((noinline)) void zm_convert_rgb565_rgba(const uint8_t* col1, uint8_t* result, unsigned long count) {
unsigned int r,g,b;
for ( unsigned int i=0; i < count; i++, col1 += 2, result += 4 ) {
b = ((*col1)<<3)&0xf8;
g = (((*(col1+1))<<5)|((*col1)>>3))&0xfc;
r = (*(col1+1))&0xf8;
result[0] = r;
result[1] = g;
result[2] = b;
}
}
/************************************************* DEINTERLACE FUNCTIONS *************************************************/
/* Grayscale */
__attribute__((noinline)) void std_deinterlace_4field_gray8(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) {
uint8_t *pcurrent, *pabove, *pncurrent, *pnabove, *pbelow;
const uint8_t* const max_ptr = col1 + (width*(height-1));
const uint8_t *max_ptr2;
pcurrent = col1 + width;
pncurrent = col2 + width;
pabove = col1;
pnabove = col2;
pbelow = col1 + (width*2);
while(pcurrent < max_ptr) {
max_ptr2 = pcurrent + width;
while(pcurrent < max_ptr2) {
if((unsigned int)((abs(*pnabove - *pabove) + abs(*pncurrent - *pcurrent)) >> 1) >= threshold) {
*pcurrent = (*pabove + *pbelow) >> 1;
}
pabove++;
pnabove++;
pcurrent++;
pncurrent++;
pbelow++;
}
pcurrent += width;
pncurrent += width;
pabove += width;
pnabove += width;
pbelow += width;
}
/* Special case for the last line */
max_ptr2 = pcurrent + width;
while(pcurrent < max_ptr2) {
if((unsigned int)((abs(*pnabove - *pabove) + abs(*pncurrent - *pcurrent)) >> 1) >= threshold) {
*pcurrent = *pabove;
}
pabove++;
pnabove++;
pcurrent++;
pncurrent++;
}
}
/* RGB */
__attribute__((noinline)) void std_deinterlace_4field_rgb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) {
uint8_t *pcurrent, *pabove, *pncurrent, *pnabove, *pbelow;
const unsigned int row_width = width*3;
const uint8_t* const max_ptr = col1 + (row_width * (height-1));
const uint8_t *max_ptr2;
unsigned int b, g, r;
unsigned int delta1, delta2;
pcurrent = col1 + (width*3);
pncurrent = col2 + (width*3);
pabove = col1;
pnabove = col2;
pbelow = col1 + ((width*2)*3);
while(pcurrent < max_ptr) {
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
r = abs(pnabove[0] - pabove[0]);
g = abs(pnabove[1] - pabove[1]);
b = abs(pnabove[2] - pabove[2]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
r = abs(pncurrent[0] - pcurrent[0]);
g = abs(pncurrent[1] - pcurrent[1]);
b = abs(pncurrent[2] - pcurrent[2]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[0] = (pabove[0] + pbelow[0]) >> 1;
pcurrent[1] = (pabove[1] + pbelow[1]) >> 1;
pcurrent[2] = (pabove[2] + pbelow[2]) >> 1;
}
pabove += 3;
pnabove += 3;
pcurrent += 3;
pncurrent += 3;
pbelow += 3;
}
pcurrent += row_width;
pncurrent += row_width;
pabove += row_width;
pnabove += row_width;
pbelow += row_width;
}
/* Special case for the last line */
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
r = abs(pnabove[0] - pabove[0]);
g = abs(pnabove[1] - pabove[1]);
b = abs(pnabove[2] - pabove[2]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
r = abs(pncurrent[0] - pcurrent[0]);
g = abs(pncurrent[1] - pcurrent[1]);
b = abs(pncurrent[2] - pcurrent[2]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[0] = pabove[0];
pcurrent[1] = pabove[1];
pcurrent[2] = pabove[2];
}
pabove += 3;
pnabove += 3;
pcurrent += 3;
pncurrent += 3;
}
}
/* BGR */
__attribute__((noinline)) void std_deinterlace_4field_bgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) {
uint8_t *pcurrent, *pabove, *pncurrent, *pnabove, *pbelow;
const unsigned int row_width = width*3;
const uint8_t* const max_ptr = col1 + (row_width * (height-1));
const uint8_t *max_ptr2;
unsigned int b, g, r;
unsigned int delta1, delta2;
pcurrent = col1 + (width*3);
pncurrent = col2 + (width*3);
pabove = col1;
pnabove = col2;
pbelow = col1 + ((width*2)*3);
while(pcurrent < max_ptr) {
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
b = abs(pnabove[0] - pabove[0]);
g = abs(pnabove[1] - pabove[1]);
r = abs(pnabove[2] - pabove[2]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
b = abs(pncurrent[0] - pcurrent[0]);
g = abs(pncurrent[1] - pcurrent[1]);
r = abs(pncurrent[2] - pcurrent[2]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[0] = (pabove[0] + pbelow[0]) >> 1;
pcurrent[1] = (pabove[1] + pbelow[1]) >> 1;
pcurrent[2] = (pabove[2] + pbelow[2]) >> 1;
}
pabove += 3;
pnabove += 3;
pcurrent += 3;
pncurrent += 3;
pbelow += 3;
}
pcurrent += row_width;
pncurrent += row_width;
pabove += row_width;
pnabove += row_width;
pbelow += row_width;
}
/* Special case for the last line */
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
b = abs(pnabove[0] - pabove[0]);
g = abs(pnabove[1] - pabove[1]);
r = abs(pnabove[2] - pabove[2]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
b = abs(pncurrent[0] - pcurrent[0]);
g = abs(pncurrent[1] - pcurrent[1]);
r = abs(pncurrent[2] - pcurrent[2]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[0] = pabove[0];
pcurrent[1] = pabove[1];
pcurrent[2] = pabove[2];
}
pabove += 3;
pnabove += 3;
pcurrent += 3;
pncurrent += 3;
}
}
/* RGBA */
__attribute__((noinline)) void std_deinterlace_4field_rgba(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) {
uint8_t *pcurrent, *pabove, *pncurrent, *pnabove, *pbelow;
const unsigned int row_width = width*4;
const uint8_t* const max_ptr = col1 + (row_width * (height-1));
const uint8_t *max_ptr2;
unsigned int b, g, r;
unsigned int delta1, delta2;
pcurrent = col1 + row_width;
pncurrent = col2 + row_width;
pabove = col1;
pnabove = col2;
pbelow = col1 + (row_width*2);
while(pcurrent < max_ptr) {
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
r = abs(pnabove[0] - pabove[0]);
g = abs(pnabove[1] - pabove[1]);
b = abs(pnabove[2] - pabove[2]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
r = abs(pncurrent[0] - pcurrent[0]);
g = abs(pncurrent[1] - pcurrent[1]);
b = abs(pncurrent[2] - pcurrent[2]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[0] = (pabove[0] + pbelow[0]) >> 1;
pcurrent[1] = (pabove[1] + pbelow[1]) >> 1;
pcurrent[2] = (pabove[2] + pbelow[2]) >> 1;
}
pabove += 4;
pnabove += 4;
pcurrent += 4;
pncurrent += 4;
pbelow += 4;
}
pcurrent += row_width;
pncurrent += row_width;
pabove += row_width;
pnabove += row_width;
pbelow += row_width;
}
/* Special case for the last line */
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
r = abs(pnabove[0] - pabove[0]);
g = abs(pnabove[1] - pabove[1]);
b = abs(pnabove[2] - pabove[2]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
r = abs(pncurrent[0] - pcurrent[0]);
g = abs(pncurrent[1] - pcurrent[1]);
b = abs(pncurrent[2] - pcurrent[2]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[0] = pabove[0];
pcurrent[1] = pabove[1];
pcurrent[2] = pabove[2];
}
pabove += 4;
pnabove += 4;
pcurrent += 4;
pncurrent += 4;
}
}
/* BGRA */
__attribute__((noinline)) void std_deinterlace_4field_bgra(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) {
uint8_t *pcurrent, *pabove, *pncurrent, *pnabove, *pbelow;
const unsigned int row_width = width*4;
const uint8_t* const max_ptr = col1 + (row_width * (height-1));
const uint8_t *max_ptr2;
unsigned int b, g, r;
unsigned int delta1, delta2;
pcurrent = col1 + row_width;
pncurrent = col2 + row_width;
pabove = col1;
pnabove = col2;
pbelow = col1 + (row_width*2);
while(pcurrent < max_ptr) {
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
b = abs(pnabove[0] - pabove[0]);
g = abs(pnabove[1] - pabove[1]);
r = abs(pnabove[2] - pabove[2]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
b = abs(pncurrent[0] - pcurrent[0]);
g = abs(pncurrent[1] - pcurrent[1]);
r = abs(pncurrent[2] - pcurrent[2]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[0] = (pabove[0] + pbelow[0]) >> 1;
pcurrent[1] = (pabove[1] + pbelow[1]) >> 1;
pcurrent[2] = (pabove[2] + pbelow[2]) >> 1;
}
pabove += 4;
pnabove += 4;
pcurrent += 4;
pncurrent += 4;
pbelow += 4;
}
pcurrent += row_width;
pncurrent += row_width;
pabove += row_width;
pnabove += row_width;
pbelow += row_width;
}
/* Special case for the last line */
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
b = abs(pnabove[0] - pabove[0]);
g = abs(pnabove[1] - pabove[1]);
r = abs(pnabove[2] - pabove[2]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
b = abs(pncurrent[0] - pcurrent[0]);
g = abs(pncurrent[1] - pcurrent[1]);
r = abs(pncurrent[2] - pcurrent[2]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[0] = pabove[0];
pcurrent[1] = pabove[1];
pcurrent[2] = pabove[2];
}
pabove += 4;
pnabove += 4;
pcurrent += 4;
pncurrent += 4;
}
}
/* ARGB */
__attribute__((noinline)) void std_deinterlace_4field_argb(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) {
uint8_t *pcurrent, *pabove, *pncurrent, *pnabove, *pbelow;
const unsigned int row_width = width*4;
const uint8_t* const max_ptr = col1 + (row_width * (height-1));
const uint8_t *max_ptr2;
unsigned int b, g, r;
unsigned int delta1, delta2;
pcurrent = col1 + row_width;
pncurrent = col2 + row_width;
pabove = col1;
pnabove = col2;
pbelow = col1 + (row_width*2);
while(pcurrent < max_ptr) {
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
r = abs(pnabove[1] - pabove[1]);
g = abs(pnabove[2] - pabove[2]);
b = abs(pnabove[3] - pabove[3]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
r = abs(pncurrent[1] - pcurrent[1]);
g = abs(pncurrent[2] - pcurrent[2]);
b = abs(pncurrent[3] - pcurrent[3]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[1] = (pabove[1] + pbelow[1]) >> 1;
pcurrent[2] = (pabove[2] + pbelow[2]) >> 1;
pcurrent[3] = (pabove[3] + pbelow[3]) >> 1;
}
pabove += 4;
pnabove += 4;
pcurrent += 4;
pncurrent += 4;
pbelow += 4;
}
pcurrent += row_width;
pncurrent += row_width;
pabove += row_width;
pnabove += row_width;
pbelow += row_width;
}
/* Special case for the last line */
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
r = abs(pnabove[1] - pabove[1]);
g = abs(pnabove[2] - pabove[2]);
b = abs(pnabove[3] - pabove[3]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
r = abs(pncurrent[1] - pcurrent[1]);
g = abs(pncurrent[2] - pcurrent[2]);
b = abs(pncurrent[3] - pcurrent[3]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[1] = pabove[1];
pcurrent[2] = pabove[2];
pcurrent[3] = pabove[3];
}
pabove += 4;
pnabove += 4;
pcurrent += 4;
pncurrent += 4;
}
}
/* ABGR */
__attribute__((noinline)) void std_deinterlace_4field_abgr(uint8_t* col1, uint8_t* col2, unsigned int threshold, unsigned int width, unsigned int height) {
uint8_t *pcurrent, *pabove, *pncurrent, *pnabove, *pbelow;
const unsigned int row_width = width*4;
const uint8_t* const max_ptr = col1 + (row_width * (height-1));
const uint8_t *max_ptr2;
unsigned int b, g, r;
unsigned int delta1, delta2;
pcurrent = col1 + row_width;
pncurrent = col2 + row_width;
pabove = col1;
pnabove = col2;
pbelow = col1 + (row_width*2);
while(pcurrent < max_ptr) {
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
b = abs(pnabove[1] - pabove[1]);
g = abs(pnabove[2] - pabove[2]);
r = abs(pnabove[3] - pabove[3]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
b = abs(pncurrent[1] - pcurrent[1]);
g = abs(pncurrent[2] - pcurrent[2]);
r = abs(pncurrent[3] - pcurrent[3]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[1] = (pabove[1] + pbelow[1]) >> 1;
pcurrent[2] = (pabove[2] + pbelow[2]) >> 1;
pcurrent[3] = (pabove[3] + pbelow[3]) >> 1;
}
pabove += 4;
pnabove += 4;
pcurrent += 4;
pncurrent += 4;
pbelow += 4;
}
pcurrent += row_width;
pncurrent += row_width;
pabove += row_width;
pnabove += row_width;
pbelow += row_width;
}
/* Special case for the last line */
max_ptr2 = pcurrent + row_width;
while(pcurrent < max_ptr2) {
b = abs(pnabove[1] - pabove[1]);
g = abs(pnabove[2] - pabove[2]);
r = abs(pnabove[3] - pabove[3]);
delta1 = (r + r + b + g + g + g + g + g)>>3;
b = abs(pncurrent[1] - pcurrent[1]);
g = abs(pncurrent[2] - pcurrent[2]);
r = abs(pncurrent[3] - pcurrent[3]);
delta2 = (r + r + b + g + g + g + g + g)>>3;
if(((delta1 + delta2) >> 1) >= threshold) {
pcurrent[1] = pabove[1];
pcurrent[2] = pabove[2];
pcurrent[3] = pabove[3];
}
pabove += 4;
pnabove += 4;
pcurrent += 4;
pncurrent += 4;
}
}
AVPixelFormat Image::AVPixFormat() const {
return zm_pixformat_from_colours(colours, subpixelorder);
}
AVPixelFormat Image::AVPixFormat(AVPixelFormat new_pixelformat) {
// Reject unrecognised formats up-front: av_image_get_buffer_size /
// av_image_get_linesize return negative for them, and assigning that into
// unsigned size/linesize would wrap to a huge value and corrupt later
// bounds checks. On failure, leave the Image's format/size/linesize
// untouched so callers can recover or skip the slot.
unsigned int probe_colours, probe_subpix;
if (!zm_colours_from_pixformat(new_pixelformat, probe_colours, probe_subpix)) {
Error("Image::AVPixFormat: refusing to set unknown pixelformat %d %s; keeping current %s",
new_pixelformat, zm_get_pix_fmt_name(new_pixelformat),
zm_get_pix_fmt_name(imagePixFormat));
return imagePixFormat;
}
int new_size = av_image_get_buffer_size(new_pixelformat, width, height, 32);
int new_linesize = av_image_get_linesize(new_pixelformat, width, 0);
if (new_size < 0 || new_linesize < 0) {
Error("Image::AVPixFormat: av_image_get_* returned %d/%d for %s; keeping current %s",
new_size, new_linesize, zm_get_pix_fmt_name(new_pixelformat),
zm_get_pix_fmt_name(imagePixFormat));
return imagePixFormat;
}
Debug(4, "Old size: %u, old pixelformat %s", size, zm_get_pix_fmt_name(imagePixFormat));
colours = probe_colours;
subpixelorder = probe_subpix;
imagePixFormat = new_pixelformat;
size = static_cast<unsigned int>(new_size);
linesize = FFALIGN(new_linesize, 32);
Debug(4, "New size: %u new pixelformat %s", size, zm_get_pix_fmt_name(new_pixelformat));
return imagePixFormat;
}