Files
calyp/lib/CalypFrame.cpp
2024-10-03 01:31:38 +01:00

1584 lines
47 KiB
C++

/* This file is a part of Calyp project
* Copyright (C) 2014-2021 by Joao Carreira (jfmcarreira@gmail.com)
* Luis Lucas (luisfrlucas@gmail.com)
*
* 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.
*/
/**
* \file CalypFrame.cpp
* \brief Video Frame handling
*/
#include "CalypFrame.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <thread>
#include <vector>
#include "CalypDefs.h"
#include "PixelFormats.h"
#include "config.h"
#ifdef USE_OPENCV
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#endif
#define DATA_ALIGN 1 ///< use 32-bit aligned malloc/free
#if DATA_ALIGN && _WIN32 && ( _MSC_VER > 1300 )
#define xMalloc( len ) _aligned_malloc( len, 32 )
#define xFreeMem( ptr ) _aligned_free( ptr )
#else
#define xMalloc( len ) malloc( len )
#define xFreeMem( ptr ) free( ptr )
#endif
constexpr auto kNumBitsInByte = 8;
constexpr auto kMinBitsPerPixel = 8;
constexpr auto kMaxBitsPerPixel = 16;
std::vector<std::string> CalypFrame::supportedColorSpacesListNames()
{
return std::vector<std::string>{
"YUV",
"RGB",
"GRAY",
"ARGB",
};
}
constexpr auto CalypFrame::numberOfFormats() -> std::size_t
{
return kNumberOfPixelFormats;
}
auto CalypFrame::findPixelFormat( const std::string& name ) -> std::optional<ClpPixelFormats>
{
return findPixelFormat( std::string_view{ name } );
}
auto CalypFrame::findPixelFormat( std::string_view name ) -> std::optional<ClpPixelFormats>
{
auto ichar_equals = []( char lhs, char rhs ) { return std::tolower( lhs ) == std::tolower( rhs ); };
for( const auto& [key, fmt] : g_CalypPixFmtDescriptorsMap )
{
if( std::ranges::equal( fmt.name, name, ichar_equals ) ) return key;
}
return {};
}
auto CalypFrame::pelFormatColorSpace( ClpPixelFormats idx ) -> int
{
return g_CalypPixFmtDescriptorsMap.at( idx ).colorSpace;
}
auto CalypFrame::supportedPixelFormatListNames() -> std::map<ClpPixelFormats, std::string_view>
{
std::map<ClpPixelFormats, std::string_view> formatsList;
for( const auto& [key, fmt] : g_CalypPixFmtDescriptorsMap )
{
formatsList[key] = fmt.name;
}
return formatsList;
}
auto CalypFrame::supportedPixelFormatListNames( int colorSpace ) -> std::map<ClpPixelFormats, std::string_view>
{
std::map<ClpPixelFormats, std::string_view> formatsList;
for( const auto& [key, fmt] : g_CalypPixFmtDescriptorsMap )
{
if( fmt.colorSpace == colorSpace )
{
formatsList[key] = fmt.name;
}
}
return formatsList;
}
auto CalypFrame::pixelFormatName( ClpPixelFormats idx ) -> std::string_view
{
return g_CalypPixFmtDescriptorsMap.at( idx ).name;
}
struct CalypFrame::CalypFramePrivate
{
bool m_bInit{ false };
//! Struct with the pixel format description.
const CalypPixelFormatDescriptor* m_pcPelFormat{ nullptr };
unsigned int m_uiWidth{ 0 }; //!< Width of the frame
unsigned int m_uiHeight{ 0 }; //!< Height of the frame
ClpPixelFormats m_iPixelFormat{
ClpPixelFormats::Invalid }; //!< Pixel format number (it follows the list of supported pixel formats)
unsigned int m_uiBitsPel{ 0 }; //!< Bits per pixel/channel
unsigned int m_uiHalfPelValue{ 0 }; //!< Bits per pixel/channel
bool m_bHasNegativeValues{ false }; //!< Half of the scale correspond to negative values
ClpPel*** m_pppcInputPel{ nullptr };
bool m_bHasRGBPel{ false }; //!< Flag indicating that the ARGB buffer was computed
bool bHasRGBPel{ false }; //!< Flag indicating that the ARGB buffer was computed
std::vector<std::uint8_t> m_pcARGB32; //!< Buffer with the ARGB pixels used in Qt libs
/** Histogram control variables **/
bool m_bHasHistogram{ false };
bool m_bHistogramRunning{ false };
/** The histogram data.*/
std::vector<unsigned int> m_puiHistogram;
/** If the image is RGB and calcLuma is true, we have 1 more channel */
unsigned int m_uiHistoChannels{ 0 };
/** Numbers of histogram segments depending of image bytes depth*/
unsigned int m_uiHistoSegments{ 0 };
CalypFramePrivate() = default;
CalypFramePrivate( const CalypFramePrivate& ) = delete;
CalypFramePrivate( CalypFramePrivate&& ) = delete;
CalypFramePrivate& operator=( const CalypFramePrivate& ) = delete;
CalypFramePrivate& operator=( CalypFramePrivate&& ) = delete;
void init( unsigned int width, unsigned int height, ClpPixelFormats pelFormat, unsigned bitsPixel )
{
init( width, height, pelFormat, bitsPixel, false );
}
void init( unsigned int width, unsigned int height, ClpPixelFormats pelFormat, unsigned bitsPixel,
bool hasNegativeValues )
{
m_uiWidth = width;
m_uiHeight = height;
m_iPixelFormat = pelFormat;
m_uiBitsPel = bitsPixel < kMinBitsPerPixel ? kMinBitsPerPixel : bitsPixel;
m_uiHalfPelValue = 1UL << ( m_uiBitsPel - 1 );
m_bHasNegativeValues = hasNegativeValues;
if( m_uiWidth == 0 || m_uiHeight == 0 || m_iPixelFormat == ClpPixelFormats::Invalid ||
bitsPixel > kMaxBitsPerPixel )
{
throw CalypFailure( "CalypFrame", "Cannot create a CalypFrame of this type" );
}
m_pcPelFormat = &( g_CalypPixFmtDescriptorsMap.at( pelFormat ) );
int iNumberChannels = m_pcPelFormat->numberChannels;
std::size_t mem_size = 0;
std::size_t num_of_ptrs = 0;
for( int ch = 0; ch < iNumberChannels; ch++ )
{
int ratioW = ch > 0 ? m_pcPelFormat->log2ChromaWidth : 0;
int ratioH = ch > 0 ? m_pcPelFormat->log2ChromaHeight : 0;
// Add pointers mem
num_of_ptrs += CHROMASHIFT( m_uiHeight, ratioH );
// Add pixel mem
mem_size += CHROMASHIFT( m_uiHeight, ratioH ) * CHROMASHIFT( m_uiWidth, ratioW ) * sizeof( ClpPel );
}
mem_size += num_of_ptrs * sizeof( ClpPel* ) + sizeof( ClpPel** ) * iNumberChannels;
m_pppcInputPel = (ClpPel***)xMalloc( mem_size ); // NOLINT
ClpPel** pelPtrMem = (ClpPel**)( m_pppcInputPel + iNumberChannels ); // NOLINT
ClpPel* pelMem = (ClpPel*)( pelPtrMem + num_of_ptrs ); // NOLINT
for( int ch = 0; ch < iNumberChannels; ch++ )
{
int ratioW = ch > 0 ? m_pcPelFormat->log2ChromaWidth : 0;
int ratioH = ch > 0 ? m_pcPelFormat->log2ChromaHeight : 0;
m_pppcInputPel[ch] = pelPtrMem;
for( unsigned int h = 0; h < CHROMASHIFT( m_uiHeight, ratioH ); h++ )
{
*pelPtrMem = pelMem;
pelPtrMem++;
pelMem += CHROMASHIFT( m_uiWidth, ratioW );
}
}
/* Alloc ARGB memory */
m_pcARGB32.resize( m_uiHeight * m_uiWidth * 4 );
m_bHasHistogram = false;
m_bHistogramRunning = false;
m_uiHistoSegments = 1UL << m_uiBitsPel;
if( m_pcPelFormat->colorSpace == CLP_COLOR_RGB || m_pcPelFormat->colorSpace == CLP_COLOR_RGBA )
{
m_uiHistoChannels = m_pcPelFormat->numberChannels + 1;
}
else
{
m_uiHistoChannels = m_pcPelFormat->numberChannels;
}
m_puiHistogram.resize( m_uiHistoSegments * m_uiHistoChannels );
m_bInit = true;
}
auto getRealHistogramChannel( int channel ) const -> int
{
if( channel < 0 )
{
assert( false );
return 0;
}
if( channel < HistogramChannels::HIST_LUMA || channel == HistogramChannels::HIST_ALL_CHANNELS )
{
return channel;
}
if( m_pcPelFormat->colorSpace == CLP_COLOR_GRAY && channel == HistogramChannels::HIST_LUMA )
{
return channel - HistogramChannels::HIST_LUMA;
}
if( m_pcPelFormat->colorSpace == CLP_COLOR_YUV && channel >= HistogramChannels::HIST_LUMA &&
channel < HistogramChannels::HIST_CHROMA_V )
{
return channel - HistogramChannels::HIST_LUMA;
}
if( m_pcPelFormat->colorSpace == CLP_COLOR_RGB || m_pcPelFormat->colorSpace == CLP_COLOR_RGBA )
{
if( channel == HistogramChannels::HIST_LUMA )
{
return static_cast<int>( m_uiHistoChannels ) - 1;
}
if( channel >= HistogramChannels::HIST_COLOR_R && channel <= HistogramChannels::HIST_COLOR_A )
{
return channel - CLP_COLOR_R;
}
}
assert( false );
return 0;
}
~CalypFramePrivate()
{
while( m_bHistogramRunning )
{
std::this_thread::sleep_for( std::chrono::milliseconds( 10 ) );
}
m_bHasHistogram = false;
if( m_pppcInputPel ) xFreeMem( m_pppcInputPel ); // NOLINT
}
};
/*!
* \brief Constructors
*/
CalypFrame::CalypFrame( unsigned int width, unsigned int height, ClpPixelFormats pelFormat, unsigned bitsPixel )
: m_d{ std::make_unique<CalypFramePrivate>() }
{
m_d->init( width, height, pelFormat, bitsPixel );
}
CalypFrame::CalypFrame( unsigned int width, unsigned int height, ClpPixelFormats pelFormat, unsigned bitsPixel,
bool hasNegativeValues )
: m_d{ std::make_unique<CalypFramePrivate>() }
{
m_d->init( width, height, pelFormat, bitsPixel, hasNegativeValues );
}
CalypFrame::CalypFrame( const CalypFrame& other ) : m_d{ std::make_unique<CalypFramePrivate>() }
{
m_d->init( other.getWidth(), other.getHeight(), other.getPelFormat(), other.getBitsPel(),
other.getHasNegativeValues() );
copyFrom( &other );
}
auto CalypFrame::operator=( const CalypFrame& other ) -> CalypFrame&
{
// Self-assignment detection
if( &other == this ) return *this;
// Copy the resource
m_d = std::make_unique<CalypFramePrivate>();
m_d->init( other.getWidth(), other.getHeight(), other.getPelFormat(), other.getBitsPel(),
other.getHasNegativeValues() );
copyFrom( &other );
return *this;
}
CalypFrame::CalypFrame( const CalypFrame& other, unsigned int x, unsigned int y, unsigned int width,
unsigned int height )
: m_d{ std::make_unique<CalypFramePrivate>() }
{
const CalypPixelFormatDescriptor* pcPelFormat = &( g_CalypPixFmtDescriptorsMap.at( other.getPelFormat() ) );
if( pcPelFormat->log2ChromaWidth > 0 )
{
if( x % ( 1UL << pcPelFormat->log2ChromaWidth ) != 0 ) x--;
if( ( x + width ) % ( 1UL << pcPelFormat->log2ChromaWidth ) != 0 ) width++;
}
if( pcPelFormat->log2ChromaHeight > 0 )
{
if( y % ( 1UL << pcPelFormat->log2ChromaHeight ) != 0 ) y--;
if( ( y + height ) % ( 1UL << pcPelFormat->log2ChromaHeight ) != 0 ) height++;
}
m_d->init( width, height, other.getPelFormat(), other.getBitsPel() );
copyFrom( other, x, y );
}
CalypFrame::CalypFrame( const CalypFrame* other, unsigned int posX, unsigned int posY, unsigned int areaWidth,
unsigned int areaHeight )
: m_d{ std::make_unique<CalypFramePrivate>() }
{
if( !other ) return;
const CalypPixelFormatDescriptor* pcPelFormat = &( g_CalypPixFmtDescriptorsMap.at( other->getPelFormat() ) );
if( pcPelFormat->log2ChromaWidth > 0 )
{
if( posX % ( 1UL << pcPelFormat->log2ChromaWidth ) != 0 ) posX--;
if( ( posX + areaWidth ) % ( 1UL << pcPelFormat->log2ChromaWidth ) != 0 ) areaWidth++;
}
if( pcPelFormat->log2ChromaHeight > 0 )
{
if( posY % ( 1UL << pcPelFormat->log2ChromaHeight ) != 0 ) posY--;
if( ( posY + areaHeight ) % ( 1UL << pcPelFormat->log2ChromaHeight ) != 0 ) areaHeight++;
}
m_d->init( areaWidth, areaHeight, other->getPelFormat(), other->getBitsPel() );
copyFrom( other, posX, posY );
}
CalypFrame::~CalypFrame() = default;
bool CalypFrame::haveSameFmt( const CalypFrame& other, unsigned int match ) const
{
bool bRet = true;
if( match & MATCH_COLOR_SPACE ) bRet &= ( getColorSpace() == other.getColorSpace() );
if( match & MATCH_RESOLUTION ) bRet &= ( getWidth() == other.getWidth() ) && ( getHeight() == other.getHeight() );
if( match & MATCH_PEL_FMT ) bRet &= ( getPelFormat() == other.getPelFormat() );
if( match & MATCH_BITS ) bRet &= ( getBitsPel() == other.getBitsPel() );
if( match & MATCH_COLOR_SPACE_IGNORE_GRAY )
bRet &= ( getColorSpace() == CLP_COLOR_GRAY || getColorSpace() == other.getColorSpace() );
if( match & MATCH_BYTES_PER_FRAME ) bRet &= ( getBytesPerFrame() == other.getBytesPerFrame() );
return bRet;
}
auto CalypFrame::haveSameFmt( const CalypFrame* other, unsigned int match ) const -> bool
{
if( other == nullptr ) return false;
return haveSameFmt( *other, match );
}
auto CalypFrame::getPelFormat() const -> ClpPixelFormats
{
return m_d->m_iPixelFormat;
}
auto CalypFrame::getPelFmtName() const -> std::string
{
return std::string( m_d->m_pcPelFormat->name );
}
int CalypFrame::getColorSpace() const
{
return m_d->m_pcPelFormat->colorSpace;
}
auto CalypFrame::getNumberChannels() const -> unsigned int
{
return m_d->m_pcPelFormat->numberChannels;
}
unsigned CalypFrame::getWidth( unsigned channel ) const
{
return CHROMASHIFT( m_d->m_uiWidth, channel > 0 ? m_d->m_pcPelFormat->log2ChromaWidth : 0 );
}
unsigned CalypFrame::getHeight( unsigned channel ) const
{
return CHROMASHIFT( m_d->m_uiHeight, channel > 0 ? m_d->m_pcPelFormat->log2ChromaHeight : 0 );
}
bool CalypFrame::getHasNegativeValues() const
{
return m_d->m_bHasNegativeValues;
}
std::uint64_t CalypFrame::getPixels( unsigned channel ) const
{
return getWidth( channel ) * getHeight( channel );
}
std::uint64_t CalypFrame::getTotalNumberOfPixels() const
{
std::uint64_t numberPixels = 0;
for( unsigned i = 0; i < getNumberChannels(); i++ )
{
numberPixels += getPixels( i );
}
return numberPixels;
}
unsigned CalypFrame::getChromaWidthRatio() const
{
return m_d->m_pcPelFormat->log2ChromaWidth;
}
unsigned CalypFrame::getChromaHeightRatio() const
{
return m_d->m_pcPelFormat->log2ChromaHeight;
}
std::uint64_t CalypFrame::getChromaLength() const
{
return getWidth( 1 ) * getHeight( 1 );
}
auto CalypFrame::getBitsPel() const -> unsigned int
{
return m_d->m_uiBitsPel;
}
std::uint64_t CalypFrame::getBytesPerFrame() const
{
return getBytesPerFrame( m_d->m_uiWidth, m_d->m_uiHeight, m_d->m_iPixelFormat, m_d->m_uiBitsPel );
}
std::uint64_t CalypFrame::getBytesPerFrame( unsigned int uiWidth, unsigned int uiHeight, ClpPixelFormats pelFormat,
unsigned int bitsPixel )
{
const auto& pcPelFormat = g_CalypPixFmtDescriptorsMap.at( pelFormat );
unsigned int bytesPerPixel = ( bitsPixel - 1 ) / kNumBitsInByte + 1;
std::uint64_t numberBytes = uiWidth * uiHeight;
if( pcPelFormat.numberChannels > 1 )
{
std::uint64_t numberBytesChroma =
CHROMASHIFT( uiWidth, pcPelFormat.log2ChromaWidth ) * CHROMASHIFT( uiHeight, pcPelFormat.log2ChromaHeight );
numberBytes += ( pcPelFormat.numberChannels - 1 ) * numberBytesChroma;
}
return numberBytes * bytesPerPixel;
}
void CalypFrame::reset()
{
const ClpPel pelValue = 1UL << ( m_d->m_uiBitsPel - 1 );
for( std::size_t ch = 0; ch < m_d->m_pcPelFormat->numberChannels; ch++ )
{
int ratioW = ch > 0 ? m_d->m_pcPelFormat->log2ChromaWidth : 0;
int ratioH = ch > 0 ? m_d->m_pcPelFormat->log2ChromaHeight : 0;
ClpPel* pPel = m_d->m_pppcInputPel[ch][0];
for( std::size_t i = 0; i < CHROMASHIFT( m_d->m_uiHeight, ratioH ) * CHROMASHIFT( m_d->m_uiWidth, ratioW ); i++ )
{
*pPel++ = pelValue;
}
}
}
ClpPel*** CalypFrame::getPelBufferYUV() const
{
return m_d->m_pppcInputPel;
}
ClpPel*** CalypFrame::getPelBufferYUV()
{
m_d->m_bHasHistogram = false;
m_d->m_bHasRGBPel = false;
return m_d->m_pppcInputPel;
}
auto CalypFrame::getRGBBuffer() const -> std::optional<std::span<const std::uint8_t>>
{
if( !m_d->m_bHasRGBPel )
{
return std::nullopt;
}
return std::span{ m_d->m_pcARGB32 };
}
ClpPel CalypFrame::operator()( unsigned int ch, unsigned int xPos, unsigned int yPos, bool absolute ) const
{
if( ch >= m_d->m_pcPelFormat->numberChannels ) return 0;
int pixel = m_d->m_pppcInputPel[ch][yPos][xPos];
if( !absolute && m_d->m_bHasNegativeValues )
{
pixel = pixel - static_cast<int>( m_d->m_uiHalfPelValue );
}
return pixel;
}
CalypPixel CalypFrame::operator()( unsigned int xPos, unsigned int yPos ) const
{
return getPixel( xPos, yPos );
}
CalypPixel CalypFrame::getPixel( unsigned int xPos, unsigned int yPos ) const
{
assert( xPos < m_d->m_uiWidth );
assert( yPos < m_d->m_uiHeight );
CalypPixel PixelValue( m_d->m_pcPelFormat->colorSpace );
for( unsigned int ch = 0; ch < m_d->m_pcPelFormat->numberChannels; ch++ )
{
int ratioH = ch > 0 ? m_d->m_pcPelFormat->log2ChromaWidth : 0;
int ratioW = ch > 0 ? m_d->m_pcPelFormat->log2ChromaHeight : 0;
PixelValue[ch] = m_d->m_pppcInputPel[ch][( yPos >> ratioH )][( xPos >> ratioW )];
}
return PixelValue;
}
CalypPixel CalypFrame::getPixel( unsigned int xPos, unsigned int yPos, CalypColorSpace eColorSpace ) const
{
return getPixel( xPos, yPos ).convertPixel( eColorSpace );
}
void CalypFrame::setPixel( unsigned int xPos, unsigned int yPos, CalypPixel pixel )
{
for( unsigned int ch = 0; ch < m_d->m_pcPelFormat->numberChannels; ch++ )
{
int ratioH = ch > 0 ? m_d->m_pcPelFormat->log2ChromaWidth : 0;
int ratioW = ch > 0 ? m_d->m_pcPelFormat->log2ChromaHeight : 0;
m_d->m_pppcInputPel[ch][( yPos >> ratioH )][( xPos >> ratioW )] = pixel[ch];
}
m_d->m_bHasHistogram = false;
m_d->m_bHasRGBPel = false;
}
void CalypFrame::copyFrom( const CalypFrame& other )
{
if( !haveSameFmt( other, MATCH_COLOR_SPACE | MATCH_BYTES_PER_FRAME | MATCH_BITS ) ) return;
m_d->m_bHasRGBPel = false;
m_d->m_bHasHistogram = false;
memcpy( &( m_d->m_pppcInputPel[0][0][0] ), &( other.getPelBufferYUV()[0][0][0] ),
getTotalNumberOfPixels() * sizeof( ClpPel ) );
}
void CalypFrame::copyFrom( const CalypFrame* other )
{
if( other != nullptr ) copyFrom( *other );
}
void CalypFrame::copyFrom( const CalypFrame& other, unsigned x, unsigned y )
{
if( !haveSameFmt( other, MATCH_COLOR_SPACE | MATCH_BITS ) ) return;
// TODO: @JC Protect width and height
ClpPel*** pInput = other.getPelBufferYUV();
for( unsigned int ch = 0; ch < m_d->m_pcPelFormat->numberChannels; ch++ )
{
int ratioH = ch > 0 ? m_d->m_pcPelFormat->log2ChromaWidth : 0;
int ratioW = ch > 0 ? m_d->m_pcPelFormat->log2ChromaHeight : 0;
for( unsigned int i = 0; i < CHROMASHIFT( m_d->m_uiHeight, ratioH ); i++ )
{
memcpy( &( m_d->m_pppcInputPel[ch][i][0] ), &( pInput[ch][( y >> ratioH ) + i][( x >> ratioW )] ),
( m_d->m_uiWidth >> ratioW ) * sizeof( ClpPel ) );
}
}
m_d->m_bHasRGBPel = false;
m_d->m_bHasHistogram = false;
}
void CalypFrame::copyFrom( const CalypFrame* other, unsigned x, unsigned y )
{
if( other ) copyFrom( *other, x, y );
}
void CalypFrame::copyTo( const CalypFrame& other, unsigned x, unsigned y ) const
{
if( !haveSameFmt( other, MATCH_COLOR_SPACE | MATCH_PEL_FMT | MATCH_BITS ) ) return;
ClpPel*** pInput = other.getPelBufferYUV();
unsigned width = other.getWidth();
// TODO: Protect width and height
for( unsigned int ch = 0; ch < m_d->m_pcPelFormat->numberChannels; ch++ )
{
int ratioH = ch > 0 ? m_d->m_pcPelFormat->log2ChromaWidth : 0;
int ratioW = ch > 0 ? m_d->m_pcPelFormat->log2ChromaHeight : 0;
for( unsigned int i = 0; i < other.getHeight( ch ); i++ )
{
memcpy( &( m_d->m_pppcInputPel[ch][( y >> ratioH ) + i][( x >> ratioW )] ), &( pInput[ch][i][0] ),
( width >> ratioW ) * sizeof( ClpPel ) );
}
}
m_d->m_bHasRGBPel = false;
m_d->m_bHasHistogram = false;
}
void CalypFrame::copyTo( const CalypFrame* other, unsigned x, unsigned y ) const
{
if( other != nullptr ) copyTo( *other, x, y );
}
void CalypFrame::frameFromBuffer( std::span<const ClpByte> buff, int iEndianness, std::uint64_t uiBuffSize )
{
if( uiBuffSize != getBytesPerFrame() ) return;
frameFromBuffer( buff, iEndianness );
}
void CalypFrame::frameFromBuffer( std::span<const ClpByte> buff, int iEndianness )
{
std::array<const ClpByte*, CalypPixel::getMaxNumberOfComponents()> ppBuff{ nullptr };
int bytesPixel = ( m_d->m_uiBitsPel - 1 ) / kNumBitsInByte + 1;
int startByte = 0;
int endByte = bytesPixel;
int incByte = 1;
int maxval = ( 1UL << m_d->m_uiBitsPel ) - 1;
if( iEndianness == CLP_BIG_ENDIAN )
{
startByte = bytesPixel - 1;
endByte = -1;
incByte = -1;
}
ppBuff[0] = buff.data();
for( std::size_t i = 1; i < CalypPixel::getMaxNumberOfComponents(); i++ )
{
int ratioW = i > 1 ? m_d->m_pcPelFormat->log2ChromaWidth : 0;
int ratioH = i > 1 ? m_d->m_pcPelFormat->log2ChromaHeight : 0;
ppBuff[i] =
ppBuff[i - 1] + CHROMASHIFT( m_d->m_uiHeight, ratioH ) * CHROMASHIFT( m_d->m_uiWidth, ratioW ) * bytesPixel;
}
memset( m_d->m_pppcInputPel[0][0], 0, getTotalNumberOfPixels() * sizeof( ClpPel ) );
for( std::size_t ch = 0; ch < m_d->m_pcPelFormat->numberChannels; ch++ )
{
int ratioW = ch > 0 ? m_d->m_pcPelFormat->log2ChromaWidth : 0;
int ratioH = ch > 0 ? m_d->m_pcPelFormat->log2ChromaHeight : 0;
unsigned step = m_d->m_pcPelFormat->comp[ch].step_minus1 * bytesPixel;
ClpPel* pPel = m_d->m_pppcInputPel[ch][0];
const ClpByte* pTmpBuff =
ppBuff[m_d->m_pcPelFormat->comp[ch].plane] + ( m_d->m_pcPelFormat->comp[ch].offset_plus1 - 1 ) * bytesPixel;
for( std::size_t p = 0; p < CHROMASHIFT( m_d->m_uiHeight, ratioH ) * CHROMASHIFT( m_d->m_uiWidth, ratioW ); p++ )
{
for( int b = startByte; b != endByte; b += incByte )
{
pPel[p] += *pTmpBuff << ( b * kNumBitsInByte );
pTmpBuff++;
}
// Check max value and bound it to "maxval" to prevent segfault when
// calculating histogram
if( pPel[p] > maxval ) pPel[p] = 0;
pTmpBuff += step;
}
}
m_d->m_bHasRGBPel = false;
m_d->m_bHasHistogram = false;
}
void CalypFrame::frameToBuffer( std::span<ClpByte> output, int iEndianness ) const
{
const int bytesPixel = ( m_d->m_uiBitsPel - 1 ) / kNumBitsInByte + 1;
std::array<ClpByte*, CalypPixel::getMaxNumberOfComponents()> ppBuff{ nullptr };
int startByte = 0;
int endByte = bytesPixel;
int incByte = 1;
if( iEndianness == CLP_BIG_ENDIAN )
{
startByte = bytesPixel - 1;
endByte = -1;
incByte = -1;
}
ppBuff[0] = output.data();
for( std::size_t i = 1; i < CalypPixel::getMaxNumberOfComponents(); i++ )
{
int ratioW = i > 1 ? m_d->m_pcPelFormat->log2ChromaWidth : 0;
int ratioH = i > 1 ? m_d->m_pcPelFormat->log2ChromaHeight : 0;
ppBuff[i] =
ppBuff[i - 1] + CHROMASHIFT( m_d->m_uiHeight, ratioH ) * CHROMASHIFT( m_d->m_uiWidth, ratioW ) * bytesPixel;
}
for( std::size_t ch = 0; ch < m_d->m_pcPelFormat->numberChannels; ch++ )
{
int ratioW = ch > 0 ? m_d->m_pcPelFormat->log2ChromaWidth : 0;
int ratioH = ch > 0 ? m_d->m_pcPelFormat->log2ChromaHeight : 0;
int step = ( m_d->m_pcPelFormat->comp[ch].step_minus1 ) * bytesPixel;
ClpPel* pTmpPel = m_d->m_pppcInputPel[ch][0];
ClpByte* pTmpBuff =
ppBuff[m_d->m_pcPelFormat->comp[ch].plane] + ( m_d->m_pcPelFormat->comp[ch].offset_plus1 - 1 ) * bytesPixel;
for( std::size_t i = 0; i < CHROMASHIFT( m_d->m_uiHeight, ratioH ) * CHROMASHIFT( m_d->m_uiWidth, ratioW ); i++ )
{
for( int b = startByte; b != endByte; b += incByte )
{
*pTmpBuff = *pTmpPel >> ( kNumBitsInByte * b );
pTmpBuff++;
}
pTmpPel++;
pTmpBuff += step;
}
}
}
template <typename T>
inline constexpr auto convert_to_pel_argb( const T a, const T r, const T g, const T b ) -> std::uint32_t
{
return ( ( a & 0xff ) << 24 ) | ( ( r & 0xff ) << 16 ) | ( ( g & 0xff ) << 8 ) | ( b & 0xff ); // NOLINT
}
template <typename T>
inline constexpr auto convert_to_pel_argb( const T r, const T g, const T b ) -> std::uint32_t
{
return convert_to_pel_argb<T>( 0xffu, r, g, b ); // NOLINT
}
template <typename T>
constexpr void convert_yuv_to_rgb( const T iY, const T iU, const T iV, T& iR, T& iG, T& iB )
{
auto clamp_pel_value = []( auto x ) {
if( x < 0 ) return 0;
if( x > 255 ) return 255;
return x;
};
iR = iY + ( ( 1436 * ( iV - 128 ) ) >> 10 );
iG = iY - ( ( 352 * ( iU - 128 ) + 731 * ( iV - 128 ) ) >> 10 );
iB = iY + ( ( 1812 * ( iU - 128 ) ) >> 10 );
iR = clamp_pel_value( iR );
iG = clamp_pel_value( iG );
iB = clamp_pel_value( iB );
}
void fillRGBBufferYUV420p( ClpPel*** pppInputPel, uint32_t* pARGB, unsigned uiWidth, unsigned uiHeight, int shiftBits )
{
ClpPel* pY = pppInputPel[CLP_LUMA][0];
ClpPel* pU = pppInputPel[CLP_CHROMA_U][0];
ClpPel* pV = pppInputPel[CLP_CHROMA_V][0];
for( unsigned y = 0; y < uiHeight; y += 2 )
{
for( unsigned x = 0; x < uiWidth; x += 2 )
{
int iR, iG, iB; // NOLINT
int iY = pY[y * uiWidth + x] >> shiftBits;
int iU = pU[y * uiWidth / 4 + x / 2] >> shiftBits;
int iV = pV[y * uiWidth / 4 + x / 2] >> shiftBits;
convert_yuv_to_rgb( iY, iU, iV, iR, iG, iB );
pARGB[y * uiWidth + x] = convert_to_pel_argb( iR, iG, iB );
iY = pY[y * uiWidth + x + 1] >> shiftBits;
convert_yuv_to_rgb( iY, iU, iV, iR, iG, iB );
pARGB[y * uiWidth + x + 1] = convert_to_pel_argb( iR, iG, iB );
iY = pY[y * uiWidth + uiWidth + x] >> shiftBits;
convert_yuv_to_rgb( iY, iU, iV, iR, iG, iB );
pARGB[y * uiWidth + uiWidth + x] = convert_to_pel_argb( iR, iG, iB );
iY = pY[y * uiWidth + uiWidth + x + 1] >> shiftBits;
convert_yuv_to_rgb( iY, iU, iV, iR, iG, iB );
pARGB[y * uiWidth + uiWidth + x + 1] = convert_to_pel_argb( iR, iG, iB );
}
}
}
void CalypFrame::fillRGBBuffer( std::optional<std::size_t> channel ) const
{
auto shiftBits = static_cast<int>( m_d->m_uiBitsPel ) - 8;
m_d->m_bHasRGBPel = true;
// 4 bytes for A, R, G and B
auto* pARGB = reinterpret_cast<uint32_t*>( m_d->m_pcARGB32.data() );
if( m_d->m_pcPelFormat->colorSpace == CLP_COLOR_GRAY || ( channel.has_value() && *channel == 0 ) )
{
ClpPel* pY = m_d->m_pppcInputPel[0][0];
for( unsigned int i = 0; i < m_d->m_uiHeight * m_d->m_uiWidth; i++ )
{
unsigned char finalPel = ( *pY++ ) >> shiftBits;
*pARGB++ = convert_to_pel_argb( finalPel, finalPel, finalPel );
}
}
else if( channel.has_value() && *channel > 0 )
{
ClpPel* pLine = m_d->m_pppcInputPel[*channel][0];
for( unsigned y = 0; y < CHROMASHIFT( m_d->m_uiHeight, m_d->m_pcPelFormat->log2ChromaHeight ); y++ )
{
for( int i = 0; i < 1UL << m_d->m_pcPelFormat->log2ChromaHeight; i++ )
{
ClpPel* pPel = pLine;
for( unsigned x = 0; x < CHROMASHIFT( m_d->m_uiWidth, m_d->m_pcPelFormat->log2ChromaWidth ); x++ )
{
unsigned char finalPel = ( *pPel++ ) >> shiftBits;
for( int j = 0; j < ( 1UL << m_d->m_pcPelFormat->log2ChromaWidth ); j++ )
{
*pARGB++ = convert_to_pel_argb( finalPel, finalPel, finalPel );
}
}
}
pLine += CHROMASHIFT( m_d->m_uiWidth, m_d->m_pcPelFormat->log2ChromaWidth );
}
}
else if( m_d->m_pcPelFormat->colorSpace == CLP_COLOR_RGB )
{
ClpPel* pR = m_d->m_pppcInputPel[CLP_COLOR_R][0];
ClpPel* pG = m_d->m_pppcInputPel[CLP_COLOR_G][0];
ClpPel* pB = m_d->m_pppcInputPel[CLP_COLOR_B][0];
for( unsigned int i = 0; i < m_d->m_uiHeight * m_d->m_uiWidth; i++ )
*pARGB++ = convert_to_pel_argb( ( *pR++ ) >> shiftBits, ( *pG++ ) >> shiftBits, ( *pB++ ) >> shiftBits );
}
else if( m_d->m_pcPelFormat->colorSpace == CLP_COLOR_RGBA )
{
ClpPel* pR = m_d->m_pppcInputPel[CLP_COLOR_R][0];
ClpPel* pG = m_d->m_pppcInputPel[CLP_COLOR_G][0];
ClpPel* pB = m_d->m_pppcInputPel[CLP_COLOR_B][0];
ClpPel* pA = m_d->m_pppcInputPel[CLP_COLOR_A][0];
for( unsigned int i = 0; i < m_d->m_uiHeight * m_d->m_uiWidth; i++ )
*pARGB++ = convert_to_pel_argb( ( *pA++ ) >> shiftBits, ( *pR++ ) >> shiftBits, ( *pG++ ) >> shiftBits,
( *pB++ ) >> shiftBits );
}
else if( m_d->m_pcPelFormat->colorSpace == CLP_COLOR_YUV )
{
if( m_d->m_iPixelFormat == ClpPixelFormats::YUV420p )
{
fillRGBBufferYUV420p( m_d->m_pppcInputPel, pARGB, m_d->m_uiWidth, m_d->m_uiHeight, shiftBits );
return;
}
ClpPel* pLineY = m_d->m_pppcInputPel[CLP_LUMA][0];
ClpPel* pLineU = m_d->m_pppcInputPel[CLP_CHROMA_U][0];
ClpPel* pLineV = m_d->m_pppcInputPel[CLP_CHROMA_V][0];
unsigned int uiChromaStride = CHROMASHIFT( m_d->m_uiWidth, m_d->m_pcPelFormat->log2ChromaWidth );
int iR, iG, iB; // NOLINT
uint32_t* pARGBLine = pARGB;
for( unsigned y = 0; y < CHROMASHIFT( m_d->m_uiHeight, m_d->m_pcPelFormat->log2ChromaHeight ); y++ )
{
for( int i = 0; i < 1UL << m_d->m_pcPelFormat->log2ChromaHeight; i++ )
{
ClpPel* pY = pLineY;
ClpPel* pU = pLineU;
ClpPel* pV = pLineV;
uint32_t* pARGBAux = pARGBLine;
for( unsigned x = 0; x < CHROMASHIFT( m_d->m_uiWidth, m_d->m_pcPelFormat->log2ChromaWidth ); x++ )
{
int iU = *pU++;
iU >>= shiftBits;
int iV = *pV++;
iV >>= shiftBits;
for( int j = 0; j < ( 1UL << m_d->m_pcPelFormat->log2ChromaWidth ); j++ )
{
int iY = *pY++;
iY >>= shiftBits;
convert_yuv_to_rgb( iY, iU, iV, iR, iG, iB );
*pARGBAux++ = convert_to_pel_argb( iR, iG, iB );
}
}
pLineY += m_d->m_uiWidth;
pARGBLine += m_d->m_uiWidth;
}
pLineU += uiChromaStride;
pLineV += uiChromaStride;
}
}
}
void CalypFrame::fillRGBBuffer() const
{
if( m_d->m_bHasRGBPel ) return;
fillRGBBuffer( {} );
}
/**
* Histogram
*/
void CalypFrame::calcHistogram()
{
if( m_d->m_bHasHistogram || m_d->m_puiHistogram.empty() ) return;
m_d->m_bHistogramRunning = true;
std::fill( m_d->m_puiHistogram.begin(), m_d->m_puiHistogram.end(), 0 );
unsigned int numberChannels = m_d->m_pcPelFormat->numberChannels;
for( unsigned int ch = 0; ch < numberChannels; ch++ )
{
unsigned int size = CHROMASHIFT( m_d->m_uiWidth, ch > 0 ? m_d->m_pcPelFormat->log2ChromaWidth : 0 ) *
CHROMASHIFT( m_d->m_uiHeight, ch > 0 ? m_d->m_pcPelFormat->log2ChromaHeight : 0 );
ClpPel* chPel = &( m_d->m_pppcInputPel[ch][0][0] );
for( unsigned int i = 0; i < size; i++ )
{
m_d->m_puiHistogram[*chPel + ch * m_d->m_uiHistoSegments]++;
chPel++;
}
}
if( m_d->m_pcPelFormat->colorSpace == CLP_COLOR_RGB || m_d->m_pcPelFormat->colorSpace == CLP_COLOR_RGBA )
{
for( unsigned int y = 0; y < m_d->m_uiHeight; y++ )
for( unsigned int x = 0; x < m_d->m_uiWidth; x++ )
{
ClpPel luma = getPixel( x, y ).convertPixel( CLP_COLOR_YUV )[0];
m_d->m_puiHistogram[luma + ( m_d->m_uiHistoChannels - 1 ) * m_d->m_uiHistoSegments]++;
}
}
m_d->m_bHasHistogram = true;
m_d->m_bHistogramRunning = false;
}
int CalypFrame::getNumHistogramSegment() const
{
return m_d->m_uiHistoSegments;
}
unsigned int CalypFrame::getMinimumPelValue( unsigned channel ) const
{
if( !m_d->m_bHasHistogram ) return 0;
channel = m_d->getRealHistogramChannel( channel );
if( channel < 0 ) return 0;
int indexStart;
int indexEnd;
if( channel == HIST_ALL_CHANNELS )
{
indexStart = 0;
indexEnd = m_d->m_uiHistoChannels * m_d->m_uiHistoSegments;
}
else
{
indexStart = channel * m_d->m_uiHistoSegments;
indexEnd = indexStart + m_d->m_uiHistoSegments;
}
for( int i = indexStart; i < indexEnd; i++ )
{
if( m_d->m_puiHistogram[i] > 0 )
{
return i - indexStart;
}
}
return 0;
}
unsigned int CalypFrame::getMaximumPelValue( unsigned channel ) const
{
if( !m_d->m_bHasHistogram ) return 0;
channel = m_d->getRealHistogramChannel( channel );
if( channel < 0 ) return 0;
int indexStart;
int indexEnd;
if( channel == HIST_ALL_CHANNELS )
{
indexStart = m_d->m_uiHistoChannels * m_d->m_uiHistoSegments;
indexEnd = 0;
}
else
{
indexStart = ( channel + 1 ) * m_d->m_uiHistoSegments - 1;
indexEnd = indexStart - m_d->m_uiHistoSegments;
}
for( int i = indexStart; i > indexEnd; i-- )
{
if( m_d->m_puiHistogram[i] > 0 )
{
return i - indexEnd - 1;
}
}
return 0;
}
unsigned int CalypFrame::getNEBins( unsigned channel ) const
{
if( !m_d->m_bHasHistogram ) return 0;
channel = m_d->getRealHistogramChannel( channel );
if( channel < 0 ) return 0;
int indexStart;
int indexEnd;
if( channel == HIST_ALL_CHANNELS )
{
indexStart = 0;
indexEnd = m_d->m_uiHistoChannels * m_d->m_uiHistoSegments;
}
else
{
indexStart = channel * m_d->m_uiHistoSegments;
indexEnd = indexStart + m_d->m_uiHistoSegments;
}
int nEBins = 0;
for( int i = indexStart; i < indexEnd; i++ )
{
if( m_d->m_puiHistogram[i] > 0 )
{
nEBins++;
}
}
return nEBins;
}
unsigned int CalypFrame::getMaximum( unsigned channel ) const
{
if( !m_d->m_bHasHistogram ) return 0;
channel = m_d->getRealHistogramChannel( channel );
if( channel < 0 ) return 0;
unsigned int maxValue = 0;
int indexStart{ 0 };
int indexEnd{ 0 };
if( channel == HIST_ALL_CHANNELS )
{
indexStart = 0;
indexEnd = m_d->m_uiHistoChannels * m_d->m_uiHistoSegments;
}
else
{
indexStart = channel * m_d->m_uiHistoSegments;
indexEnd = indexStart + m_d->m_uiHistoSegments;
}
for( int x = indexStart; x < indexEnd; x++ )
{
if( m_d->m_puiHistogram[x] > maxValue )
{
maxValue = m_d->m_puiHistogram[x];
}
}
return maxValue;
}
unsigned int CalypFrame::getNumPixelsRange( unsigned channel, unsigned int start, unsigned int end ) const
{
if( !m_d->m_bHasHistogram || start < 0 || end > m_d->m_uiHistoSegments - 1 || start > end )
{
return 0;
}
channel = m_d->getRealHistogramChannel( channel );
if( channel < 0 )
{
return 0;
}
unsigned int count = 0;
int indexStart{ 0 };
indexStart = channel * m_d->m_uiHistoSegments;
for( unsigned int i = start; i <= end; i++ )
{
count += m_d->m_puiHistogram[indexStart + i];
}
return count;
}
double CalypFrame::getMean( unsigned channel, unsigned int start, unsigned int end ) const
{
if( !m_d->m_bHasHistogram || start < 0 || end > m_d->m_uiHistoSegments - 1 || start > end )
{
return 0.0;
}
channel = m_d->getRealHistogramChannel( channel );
if( channel < 0 )
{
return 0.0;
}
double mean{ 0.0 };
int indexStart = channel * m_d->m_uiHistoSegments;
for( unsigned int i = start; i <= end; i++ )
{
mean += i * m_d->m_puiHistogram[indexStart + i];
}
auto count = getNumPixelsRange( channel, start, end );
if( count > 0.0 )
{
return mean / count;
}
return mean;
}
int CalypFrame::getMedian( unsigned channel, unsigned int start, unsigned int end ) const
{
if( !m_d->m_bHasHistogram || start < 0 || end > m_d->m_uiHistoSegments - 1 || start > end )
{
return 0;
}
channel = m_d->getRealHistogramChannel( channel );
if( channel < 0 )
{
return 0;
}
double sum = 0.0;
int indexStart = channel * m_d->m_uiHistoSegments;
double count = getNumPixelsRange( channel, start, end );
for( unsigned int i = start; i <= end; i++ )
{
sum += m_d->m_puiHistogram[indexStart + i];
if( sum * 2 > count ) return i;
}
return 0;
}
double CalypFrame::getStdDev( unsigned channel, unsigned int start, unsigned int end ) const
{
if( !m_d->m_bHasHistogram || start < 0 || end > m_d->m_uiHistoSegments - 1 || start > end )
{
return 0.0;
}
channel = m_d->getRealHistogramChannel( channel );
if( channel < 0 )
{
return 0.0;
}
int indexStart = channel * m_d->m_uiHistoSegments;
double mean = getMean( channel, start, end );
double count = getNumPixelsRange( channel, start, end );
double dev = 0.0;
if( count == 0.0 ) count = 1.0;
/*------------ original
for ( i = start ; i <= end ; i++ )
{
dev += ( i - mean ) * ( i - mean ) * d->m_puiHistogram[indexStart +i];
}
return sqrt( dev / count );
-----------------------*/
for( unsigned int i = start; i <= end; i++ )
{
dev += ( i * i ) * m_d->m_puiHistogram[indexStart + i];
}
return sqrt( ( dev - count * mean * mean ) / ( count - 1 ) );
}
double CalypFrame::getHistogramValue( unsigned channel, unsigned int bin ) const
{
if( !m_d->m_bHasHistogram || bin < 0 || bin > m_d->m_uiHistoSegments - 1 ) return 0.0;
channel = m_d->getRealHistogramChannel( channel );
if( channel < 0 )
{
return 0;
}
int indexStart = channel * m_d->m_uiHistoSegments;
return m_d->m_puiHistogram[indexStart + bin];
}
double CalypFrame::getEntropy( unsigned channel, unsigned int start, unsigned int end ) const
{
if( !m_d->m_bHasHistogram ) return 0;
channel = m_d->getRealHistogramChannel( channel );
if( channel < 0 ) return 0;
int indexStart = channel * m_d->m_uiHistoSegments;
double numValues = getNumPixelsRange( channel, start, end );
double entropy = 0.0;
for( unsigned b = start; b <= end; b++ )
{
if( m_d->m_puiHistogram[indexStart + b] == 0 ) continue;
double prob = m_d->m_puiHistogram[indexStart + b] / numValues;
entropy -= prob * log2( prob );
}
return entropy;
}
/*
**************************************************************
* interface to other libs
**************************************************************
*/
bool CalypFrame::toMat( cv::Mat& cvMat, bool convertToGray, bool scale, unsigned channel ) const
{
bool bRet = false;
#ifdef USE_OPENCV
if( convertToGray &&
!( m_d->m_pcPelFormat->colorSpace == CLP_COLOR_YUV || m_d->m_pcPelFormat->colorSpace == CLP_COLOR_GRAY ) )
{
return bRet;
}
unsigned numBytes = getBitsPel() > kNumBitsInByte ? 2 : 1;
auto numChannels = getNumberChannels();
double scaleFactor = 1UL << ( numBytes * kNumBitsInByte - getBitsPel() );
if( scale == 0 ) scaleFactor = 1;
if( convertToGray )
{
channel = channel >= numChannels ? 0 : channel;
numChannels = 1;
}
auto imgWidth = getWidth( channel );
auto imgHeight = getHeight( channel );
const auto cvPrecision = getBitsPel() > 8 ? CV_16U : CV_8U;
cvMat.create( imgHeight, imgWidth, CV_MAKETYPE( cvPrecision, numChannels ) );
if( numChannels > 1 )
{
cv::Mat tmpMat( imgHeight, imgWidth, CV_MAKETYPE( cvPrecision, numChannels ) );
auto* cv_data = tmpMat.data;
for( unsigned y = 0; y < imgHeight; y++ )
{
for( unsigned x = 0; x < imgWidth; x++ )
{
CalypPixel currPel = getPixel( x, y );
currPel *= scaleFactor;
for( unsigned int ch = 0; ch < numChannels; ch++ )
for( auto b = 0; b < numBytes; b++ )
{
unsigned char pel = currPel[ch] >> ( kNumBitsInByte * b );
*cv_data++ = pel;
}
}
}
// TODO: check for other formats
switch( getColorSpace() )
{
case CLP_COLOR_YUV:
cv::cvtColor( tmpMat, cvMat, cv::COLOR_YCrCb2RGB );
break;
case CLP_COLOR_RGB:
case CLP_COLOR_RGBA:
cvMat = tmpMat;
break;
default:
assert( false );
}
}
else
{
unsigned char* cv_data = cvMat.data;
auto* pel = getPelBufferYUV()[channel][0];
for( unsigned y = 0; y < imgHeight * imgWidth; y++ )
{
auto currPel = static_cast<ClpPel>( *pel++ * scaleFactor );
for( auto b = 0; b < numBytes; b++ )
{
*cv_data++ = currPel >> ( kNumBitsInByte * b );
}
}
}
bRet = true;
#endif
return bRet;
}
auto CalypFrame::fromMat( cv::Mat& cvMat, int channel ) -> bool
{
bool bRet = false;
#ifdef USE_OPENCV
unsigned numBytes = getBitsPel() > kNumBitsInByte ? 2 : 1;
auto numChannels = getNumberChannels();
auto cvPrecision = getBitsPel() > kNumBitsInByte ? CV_16U : CV_8U;
if( !m_d->m_bInit )
{
uchar depth = cvMat.type() & CV_MAT_DEPTH_MASK;
if( m_d->m_iPixelFormat == ClpPixelFormats::Invalid )
{
switch( cvMat.channels() )
{
case 1:
m_d->m_iPixelFormat = ClpPixelFormats::Gray;
break;
case 3:
m_d->m_iPixelFormat = ClpPixelFormats::BGR24;
break;
default:
assert( false );
return false;
}
}
m_d->m_uiBitsPel = depth == CV_8U ? kNumBitsInByte : kMinBitsPerPixel * 2;
m_d->init( cvMat.cols, cvMat.rows, m_d->m_iPixelFormat, m_d->m_uiBitsPel );
}
m_d->m_bHasRGBPel = false;
m_d->m_bHasHistogram = false;
if( channel >= 0 )
{
numChannels = 1;
}
else
{
channel = 0;
}
if( cvMat.channels() != static_cast<int>( numChannels ) )
{
return false;
}
auto imgWidth = getWidth( channel );
auto imgHeight = getHeight( channel );
if( numChannels > 1 )
{
cv::Mat tmpMat( imgHeight, imgWidth, CV_MAKETYPE( cvPrecision, numChannels ) );
switch( getColorSpace() )
{
case CLP_COLOR_YUV:
cv::cvtColor( tmpMat, cvMat, cv::COLOR_RGB2YCrCb );
break;
}
auto* cv_data = tmpMat.data;
auto cv_step = tmpMat.step;
CalypPixel currPel;
for( unsigned int y = 0; y < imgHeight; y++ )
{
for( unsigned int x = 0; x < imgWidth; x++ )
{
for( unsigned int ch = 0; ch < numChannels; ch++ )
{
currPel[ch] = 0;
for( unsigned b = 0; b < numBytes; b++ )
{
currPel[ch] = currPel[ch] + ( *( cv_data + y * cv_step + x * numChannels * numBytes + ch + b )
<< ( kNumBitsInByte * b ) );
}
}
setPixel( x, y, currPel );
}
}
}
else
{
auto* cv_data = cvMat.data;
ClpPel* pel = getPelBufferYUV()[channel][0];
for( unsigned int y = 0; y < imgHeight * imgWidth; y++ )
{
ClpPel curr_pel{ 0 };
for( unsigned b = 0; b < numBytes; b++ )
{
curr_pel += ( *cv_data++ ) << ( kNumBitsInByte * b );
}
*pel++ = curr_pel;
}
}
bRet = true;
#endif
return bRet;
}
/*
**************************************************************
* Quality Related Function API
**************************************************************
*/
std::vector<std::string> CalypFrame::supportedQualityMetricsList()
{
return std::vector<std::string>{
"PSNR",
"MSE",
"SSIM",
"WS-PSNR",
};
}
std::vector<std::string> CalypFrame::supportedQualityMetricsUnitsList()
{
return std::vector<std::string>{
"dB",
"",
"",
"dB",
};
}
double CalypFrame::getQuality( int metric, CalypFrame* Org, unsigned int component )
{
if( component >= getNumberChannels() ) return 0;
switch( metric )
{
case PSNR_METRIC:
return getPSNR( Org, component );
break;
case MSE_METRIC:
return getMSE( Org, component );
break;
case SSIM_METRIC:
return getSSIM( Org, component );
break;
case WSPSNR_METRIC:
return getWSPNR( Org, component );
break;
default:
assert( 0 );
};
return 0;
}
auto CalypFrame::getMSE( CalypFrame* org, unsigned int component ) -> double
{
ClpPel* pPelYUV = getPelBufferYUV()[component][0];
ClpPel* pOrgPelYUV = org->getPelBufferYUV()[component][0];
std::uint64_t numberOfPixels = org->getHeight( component ) * org->getWidth( component );
std::uint64_t ssd = 0;
for( unsigned int i = 0; i < numberOfPixels; i++ )
{
int diff = int( *pPelYUV++ ) - int( *pOrgPelYUV++ );
ssd += diff * diff;
}
if( ssd == 0.0 )
{
return 0.0;
}
return double( ssd ) / double( numberOfPixels );
}
auto CalypFrame::getPSNR( CalypFrame* org, unsigned int component ) -> double
{
std::uint64_t uiMaxValue = ( 1UL << org->getBitsPel() ) - 1;
double dPSNR = 100;
double dMSE = getMSE( org, component );
if( dMSE != 0 ) dPSNR = 10 * log10( double( uiMaxValue * uiMaxValue ) / dMSE );
return dPSNR;
}
auto compute_ssim( ClpPel** refImg, ClpPel** encImg, int width, int height, int win_width, int win_height,
int max_pel_value_comp, int overlapSize ) -> float
{
static const float K1 = 0.01f, K2 = 0.03f;
float max_pix_value_sqd;
float C1, C2;
float win_pixels = (float)( win_width * win_height );
#ifdef UNBIASED_VARIANCE
float win_pixels_bias = win_pixels - 1;
#else
float win_pixels_bias = win_pixels;
#endif
float mb_ssim, meanOrg, meanEnc;
float varOrg, varEnc, covOrgEnc;
int imeanOrg, imeanEnc, ivarOrg, ivarEnc, icovOrgEnc;
float cur_distortion = 0.0;
int i, j, n, m, win_cnt = 0;
max_pix_value_sqd = (float)( max_pel_value_comp * max_pel_value_comp );
C1 = K1 * K1 * max_pix_value_sqd;
C2 = K2 * K2 * max_pix_value_sqd;
for( j = 0; j <= height - win_height; j += overlapSize )
{
for( i = 0; i <= width - win_width; i += overlapSize )
{
imeanOrg = 0;
imeanEnc = 0;
ivarOrg = 0;
ivarEnc = 0;
icovOrgEnc = 0;
for( n = j; n < j + win_height; n++ )
{
for( m = i; m < i + win_width; m++ )
{
imeanOrg += refImg[n][m];
imeanEnc += encImg[n][m];
ivarOrg += refImg[n][m] * refImg[n][m];
ivarEnc += encImg[n][m] * encImg[n][m];
icovOrgEnc += refImg[n][m] * encImg[n][m];
}
}
meanOrg = (float)imeanOrg / win_pixels;
meanEnc = (float)imeanEnc / win_pixels;
varOrg = ( (float)ivarOrg - ( (float)imeanOrg ) * meanOrg ) / win_pixels_bias;
varEnc = ( (float)ivarEnc - ( (float)imeanEnc ) * meanEnc ) / win_pixels_bias;
covOrgEnc = ( (float)icovOrgEnc - ( (float)imeanOrg ) * meanEnc ) / win_pixels_bias;
mb_ssim = (float)( ( 2.0 * meanOrg * meanEnc + C1 ) * ( 2.0 * covOrgEnc + C2 ) );
mb_ssim /= (float)( meanOrg * meanOrg + meanEnc * meanEnc + C1 ) * ( varOrg + varEnc + C2 );
cur_distortion += mb_ssim;
win_cnt++;
}
}
cur_distortion /= (float)win_cnt;
if( cur_distortion >= 1.0 && cur_distortion < 1.01 ) // avoid float accuracy problem at very low QP(e.g.2)
cur_distortion = 1.0;
return cur_distortion;
}
auto CalypFrame::getSSIM( CalypFrame* org, unsigned int component ) -> double
{
double dSSIM = 1;
if( component == CLP_LUMA )
{
dSSIM = compute_ssim( m_d->m_pppcInputPel[component], org->getPelBufferYUV()[component], m_d->m_uiWidth,
m_d->m_uiHeight, 8, 8, 255, 8 );
}
else
{
dSSIM = compute_ssim( m_d->m_pppcInputPel[component], org->getPelBufferYUV()[component], getWidth( component ),
getHeight( component ), 4, 4, 255, 4 );
}
return dSSIM;
}
auto CalypFrame::getWSPNR( CalypFrame* org, unsigned int component ) -> double
{
ClpPel* pPelYUV = getPelBufferYUV()[component][0];
ClpPel* pOrgPelYUV = org->getPelBufferYUV()[component][0];
unsigned height = getHeight( component );
unsigned width = getWidth( component );
double ssd = 0;
double weight_sum = 0;
for( unsigned y = 0; y < height; y++ )
{
for( unsigned x = 0; x < width; x++ )
{
int diff = static_cast<int>( *pPelYUV++ ) - static_cast<int>( *pOrgPelYUV++ );
double weight = std::cos( ( ( y + 0.5 - height / 2.0 ) * S_PI / height ) );
ssd += static_cast<double>( diff * diff * 1000 ) * weight;
weight_sum += weight * 1000;
}
}
if( ssd == 0.0 ) return 100.00;
auto uiMaxValue = ( 1UL << org->getBitsPel() ) - 1;
return 10 * std::log10( static_cast<double>( uiMaxValue * uiMaxValue ) * weight_sum / ssd );
}