mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-02 23:45:08 -04:00
- Image::Colourise sizing-failure Error: av_image_get_buffer_size / av_image_get_linesize return negative for AV_PIX_FMT_NONE and unrecognised formats, so this branch fires exactly when av_get_pix_fmt_name(p_req_pixfmt) can return nullptr. Switched to zm_get_pix_fmt_name. - Image::Deinterlace_4Field fallback Panic: same issue — fires when imagePixFormat is outside our dispatch set, where av_get_pix_fmt_name may return nullptr. Switched to zm_get_pix_fmt_name. refs #4788 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5884 lines
216 KiB
C++
5884 lines
216 KiB
C++
//
|
||
// 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;
|
||
const AVPixelFormat src_fmt = static_cast<AVPixelFormat>(frame->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 == format
|
||
&& 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, format,
|
||
SWS_BICUBIC,
|
||
nullptr, nullptr, nullptr);
|
||
if (sws_convert_context == nullptr) {
|
||
Error("Unable to create conversion context");
|
||
return false;
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
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;
|
||
AVPixelFormat p_pixfmt = zm_pixformat_from_colours(p_colours, p_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;
|
||
}
|
||
|
||
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(×tamp_t, ×tamp_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;
|
||
AVPixelFormat p_pixfmt = zm_pixformat_from_colours(p_colours, p_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;
|
||
}
|
||
|
||
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.
|
||
|
||
/* Grayscale/YUV420 on top of grayscale/YUV420 - complete */
|
||
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/YUV420 - convert to same format first - complete */
|
||
} else if ( zm_bytes_per_pixel(imagePixFormat) == 1 && 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/YUV420 - convert to same format first - complete */
|
||
} else if ( zm_bytes_per_pixel(imagePixFormat) == 1 && 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;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
|
||
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 {
|
||
// 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;
|
||
}
|
||
|
||
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
|
||
// (AV_CEIL_RSHIFT) — flooring would drop the last chroma column/row for
|
||
// odd luma dimensions and leave part of U/V unrotated.
|
||
const unsigned int cw = AV_CEIL_RSHIFT(width, desc->log2_chroma_w);
|
||
const unsigned int ch = AV_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 ) {
|
||
uint8_t* flip_buffer = AllocBuffer(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;
|
||
}
|
||
|
||
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 = AV_CEIL_RSHIFT(width, desc->log2_chroma_w);
|
||
const unsigned int ch = AV_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, 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);
|
||
|
||
SWScale swscale;
|
||
swscale.init();
|
||
if (swscale.Convert(buffer, allocation, scale_buffer, scale_buffer_size,
|
||
format, format, width, height, new_width, new_height) < 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;
|
||
}
|