diff --git a/YUViewApp/YUViewApp.pro b/YUViewApp/YUViewApp.pro index 29b37ec2b..abc7bcf49 100644 --- a/YUViewApp/YUViewApp.pro +++ b/YUViewApp/YUViewApp.pro @@ -2,9 +2,14 @@ QT += core gui widgets opengl xml concurrent network TARGET = YUView TEMPLATE = app -CONFIG += c++17 CONFIG -= debug_and_release +CONFIG += c++20 +gcc { + # For gcc 9, setting 20 does not work. Must set c++2a. + equals(QMAKE_GCC_MAJOR_VERSION, 9): QMAKE_CXXFLAGS += -std=c++2a +} + SOURCES += $$files(src/*.cpp, false) HEADERS += $$files(src/*.h, false) diff --git a/YUViewLib/YUViewLib.pro b/YUViewLib/YUViewLib.pro index 2ac10025a..760c56b1b 100644 --- a/YUViewLib/YUViewLib.pro +++ b/YUViewLib/YUViewLib.pro @@ -2,10 +2,15 @@ QT += core gui widgets opengl xml concurrent network TEMPLATE = lib CONFIG += staticlib -CONFIG += c++17 CONFIG -= debug_and_release CONFIG += object_parallel_to_source +CONFIG += c++20 +gcc { + # For gcc 9, setting 20 does not work. Must set c++2a. + equals(QMAKE_GCC_MAJOR_VERSION, 9): QMAKE_CXXFLAGS += -std=c++2a +} + SOURCES += $$files(src/*.cpp, true) HEADERS += $$files(src/*.h, true) diff --git a/YUViewLib/src/common/Functions.h b/YUViewLib/src/common/Functions.h index 546278d4c..0c06db52e 100644 --- a/YUViewLib/src/common/Functions.h +++ b/YUViewLib/src/common/Functions.h @@ -34,6 +34,7 @@ #include +#include #include #include @@ -101,4 +102,15 @@ template inline T clip(T val, Range range) std::optional toUnsigned(const std::string_view text); std::optional toInt(const std::string_view text); +template inline T scaleValueByPercent(T value, P percent) +{ + static_assert(std::is_integral::value, "Type T must be an integral type."); + static_assert(std::is_integral

::value || std::is_floating_point

::value, + "Type P must be an integral or floating point type type."); + + const auto factor = static_cast(percent) / 100.0; + const auto scaledValue = static_cast(value) * factor; + return static_cast(std::round(scaledValue)); +} + } // namespace functions diff --git a/YUViewLib/src/common/Modified.h b/YUViewLib/src/common/Modified.h new file mode 100644 index 000000000..1c1a2536c --- /dev/null +++ b/YUViewLib/src/common/Modified.h @@ -0,0 +1,58 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * 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 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +template class modified +{ +public: + modified() = default; + modified(const T &value) : internalValue(value), initialValue(value) {} + + T operator*() const { return this->internalValue; } + T value() const { return this->internalValue; } + const T *operator->() const { return &this->internalValue; } + T *operator->() { return &this->internalValue; } + operator T() const { return this->internalValue; } + + void operator=(const T &newValue) { this->internalValue = newValue; } + + bool operator==(const T &other) const { return this->internalValue == other; } + bool operator==(const modified &other) const { return this->internalValue == other.value(); } + + [[nodiscard]] bool wasModified() const { return !(this->internalValue == this->initialValue); } + void setUnmodified() { this->initialValue = this->internalValue; } + +private: + T internalValue{}; + T initialValue{}; +}; diff --git a/YUViewLib/src/common/Typedef.h b/YUViewLib/src/common/Typedef.h index a1fd7e3ba..dcfd0e2da 100644 --- a/YUViewLib/src/common/Typedef.h +++ b/YUViewLib/src/common/Typedef.h @@ -170,9 +170,9 @@ template struct Range T min{}; T max{}; - bool operator!=(const Range &other) const + bool operator==(const Range &other) const { - return this->min != other.min || this->max != other.max; + return this->min == other.min && this->max == other.max; } }; diff --git a/YUViewLib/src/decoder/decoderDav1d.cpp b/YUViewLib/src/decoder/decoderDav1d.cpp index 7792a9625..ef6ebd27e 100644 --- a/YUViewLib/src/decoder/decoderDav1d.cpp +++ b/YUViewLib/src/decoder/decoderDav1d.cpp @@ -40,11 +40,13 @@ #include #include +#include namespace decoder { -using Subsampling = video::yuv::Subsampling; +using stats::StatisticsTypeBuilder; +using video::yuv::Subsampling; // Debug the decoder (0:off 1:interactive decoder only 2:caching decoder only 3:both) #define DECODERDAV1D_DEBUG_OUTPUT 0 @@ -587,195 +589,238 @@ void decoderDav1d::fillStatisticList(stats::StatisticsData &statisticsData) cons { using namespace stats::color; - stats::StatisticsType predMode(0, "Pred Mode", ColorMapper({0, 1}, PredefinedType::Jet)); - predMode.description = "The prediction mode (intra/inter) per block"; - predMode.setMappingValues({"INTRA", "INTER"}); - statisticsData.addStatType(predMode); - - // LastActiveSegId indicates the real maximum. But that can also vary per frame. - // 255 is the maximum maximum. - stats::StatisticsType segmentID(1, "Segment ID", ColorMapper({0, 255}, PredefinedType::Jet)); - segmentID.description = - "Specifies which segment is associated with the current intra block being decoded"; - statisticsData.addStatType(segmentID); - - stats::StatisticsType skip(2, "skip", ColorMapper({0, 1}, Color(0, 0, 0), Color(255, 0, 0))); - skip.description = "Equal to 0 indicates that there may be some transform coefficients for this " - "block. 1 Indicates there are none."; - statisticsData.addStatType(skip); - - stats::StatisticsType skip_mode( - 3, "skip_mode", ColorMapper({0, 1}, Color(0, 0, 0), Color(0, 255, 0))); - skip_mode.description = "Equal to 1 indicates that signaling of most of the mode info is skipped"; - statisticsData.addStatType(skip_mode); - - // Intra specific values - - stats::StatisticsType intraPredModeLuma( - 4, "intra pred mode (Y)", ColorMapper({0, 13}, PredefinedType::Jet)); - intraPredModeLuma.description = "Intra prediction mode Luma (Y)"; - intraPredModeLuma.setMappingValues({"DC_PRED", - "VERT_PRED", - "HOR_PRED", - "DIAG_DOWN_LEFT_PRED", - "DIAG_DOWN_RIGHT_PRED", - "VERT_RIGHT_PRED", - "HOR_DOWN_PRED", - "HOR_UP_PRED", - "VERT_LEFT_PRED", - "SMOOTH_PRED", - "SMOOTH_V_PRED", - "SMOOTH_H_PRED", - "PAETH_PRED", - "CFL_PRED"}); - statisticsData.addStatType(intraPredModeLuma); - - stats::StatisticsType intraPredModeChroma( - 5, "intra pred mode (UV)", ColorMapper({0, 12}, PredefinedType::Jet)); - intraPredModeChroma.description = "Intra prediction mode Chroma (UV)"; - intraPredModeChroma.setMappingValues({"DC_PRED", - "VERT_PRED", - "HOR_PRED", - "DIAG_DOWN_LEFT_PRED", - "DIAG_DOWN_RIGHT_PRED", - "VERT_RIGHT_PRED", - "HOR_DOWN_PRED", - "HOR_UP_PRED", - "VERT_LEFT_PRED", - "SMOOTH_PRED", - "SMOOTH_V_PRED", - "SMOOTH_H_PRED", - "PAETH_PRED"}); - - statisticsData.addStatType(intraPredModeChroma); - - stats::StatisticsType paletteSizeLuma( - 6, "palette size (Y)", ColorMapper({0, 255}, Color(0, 0, 0), Color(0, 0, 255))); - statisticsData.addStatType(paletteSizeLuma); - - stats::StatisticsType paletteSizeChroma( - 7, "palette size (U)", ColorMapper({0, 255}, Color(0, 0, 0), Color(0, 0, 255))); - statisticsData.addStatType(paletteSizeChroma); - - stats::StatisticsType intraAngleDeltaLuma( - 8, "intra angle delta (Y)", ColorMapper({-3, 4}, PredefinedType::Col3_bblg)); - intraAngleDeltaLuma.description = - "Offset to be applied to the intra prediction angle specified by the prediction mode"; - statisticsData.addStatType(intraAngleDeltaLuma); - - stats::StatisticsType intraAngleDeltaChroma( - 9, "intra angle delta (UV)", ColorMapper({-3, 4}, PredefinedType::Col3_bblg)); - intraAngleDeltaChroma.description = - "Offset to be applied to the intra prediction angle specified by the prediction mode"; - statisticsData.addStatType(intraAngleDeltaChroma); - - stats::StatisticsType intraDirLuma(10, "Intra direction luma", 4); - intraDirLuma.description = "Intra prediction direction luma"; - statisticsData.addStatType(intraDirLuma); - - stats::StatisticsType intraDirChroma(11, "Intra direction chroma", 4); - intraDirChroma.description = "Intra prediction direction chroma"; - statisticsData.addStatType(intraDirChroma); - - stats::StatisticsType chromaFromLumaAlphaU( - 12, "chroma from luma alpha (U)", ColorMapper({-128, 128}, PredefinedType::Col3_bblg)); - chromaFromLumaAlphaU.description = - "CflAlphaU: contains the signed value of the alpha component for the U component"; - statisticsData.addStatType(chromaFromLumaAlphaU); - - stats::StatisticsType chromaFromLumaAlphaV( - 13, "chroma from luma alpha (V)", ColorMapper({-128, 128}, PredefinedType::Col3_bblg)); - chromaFromLumaAlphaV.description = - "CflAlphaU: contains the signed value of the alpha component for the U component"; - statisticsData.addStatType(chromaFromLumaAlphaV); - - // Inter specific values - - stats::StatisticsType refFrames0( - 14, "ref frame index 0", ColorMapper({0, 7}, PredefinedType::Jet)); - statisticsData.addStatType(refFrames0); - - stats::StatisticsType refFrames1( - 15, "ref frame index 1", ColorMapper({0, 7}, PredefinedType::Jet)); - statisticsData.addStatType(refFrames1); - - stats::StatisticsType compoundPredType( - 16, "compound prediction type", ColorMapper({0, 4}, PredefinedType::Jet)); - compoundPredType.setMappingValues({"COMP_INTER_NONE", - "COMP_INTER_WEIGHTED_AVG", - "COMP_INTER_AVG", - "COMP_INTER_SEG", - "COMP_INTER_WEDGE"}); - statisticsData.addStatType(compoundPredType); - - stats::StatisticsType wedgeIndex(17, "wedge index", ColorMapper({0, 16}, PredefinedType::Jet)); - statisticsData.addStatType(wedgeIndex); - - stats::StatisticsType maskSign( - 18, "mask sign", ColorMapper({0, 1}, Color(0, 0, 0), Color(0, 255, 255))); - statisticsData.addStatType(maskSign); - - stats::StatisticsType interMode(19, "inter mode", ColorMapper({0, 7}, PredefinedType::Jet)); - interMode.setMappingValues({"NEARESTMV_NEARESTMV", + statisticsData.addStatType( + StatisticsTypeBuilder(0, "Pred Mode") + .withDescription("The prediction mode (intra/inter) per block") + .withValueDataOptions({.colorMapper = ColorMapper({0, 1}, PredefinedType::Jet)}) + .withMappingValues({"INTRA", "INTER"}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(1, "Segment ID") + .withDescription( + "Specifies which segment is associated with the current intra block being " + "decoded") + .withValueDataOptions({.colorMapper = ColorMapper({0, 255}, PredefinedType::Jet)}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(2, "Skip") + .withDescription( + "Equal to 0 indicates that there may be some transform coefficients for this " + "block. 1 Indicates there are none.") + .withValueDataOptions( + {.colorMapper = ColorMapper({0, 1}, Color(0, 0, 0), Color(255, 0, 0))}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(3, "Skip Mode") + .withDescription( + "Equal to 1 indicates that signaling of most of the mode info is skipped") + .withValueDataOptions( + {.colorMapper = ColorMapper({0, 1}, Color(0, 0, 0), Color(0, 255, 0))}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(4, "Intra Pred Mode (Y)") + .withDescription("Intra prediction mode Luma") + .withValueDataOptions({.colorMapper = ColorMapper({0, 13}, PredefinedType::Jet)}) + .withMappingValues({"DC_PRED", + "VERT_PRED", + "HOR_PRED", + "DIAG_DOWN_LEFT_PRED", + "DIAG_DOWN_RIGHT_PRED", + "VERT_RIGHT_PRED", + "HOR_DOWN_PRED", + "HOR_UP_PRED", + "VERT_LEFT_PRED", + "SMOOTH_PRED", + "SMOOTH_V_PRED", + "SMOOTH_H_PRED", + "PAETH_PRED", + "CFL_PRED"}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(5, "Intra Pred Mode (UV)") + .withDescription("Intra prediction mode Chroma") + .withValueDataOptions({.colorMapper = ColorMapper({0, 12}, PredefinedType::Jet)}) + .withMappingValues({"DC_PRED", + "VERT_PRED", + "HOR_PRED", + "DIAG_DOWN_LEFT_PRED", + "DIAG_DOWN_RIGHT_PRED", + "VERT_RIGHT_PRED", + "HOR_DOWN_PRED", + "HOR_UP_PRED", + "VERT_LEFT_PRED", + "SMOOTH_PRED", + "SMOOTH_V_PRED", + "SMOOTH_H_PRED", + "PAETH_PRED"}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(6, "Palette Size (Y)") + .withDescription("Palette Size Luma") + .withValueDataOptions( + {.colorMapper = ColorMapper({0, 255}, Color(0, 0, 0), Color(0, 0, 255))}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(7, "Palette Size Chroma") + .withValueDataOptions( + {.colorMapper = ColorMapper({0, 255}, Color(0, 0, 0), Color(0, 0, 255))}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(8, "Intra Angle Delta (Y)") + .withDescription("Offset to be applied to the intra prediction angle specified by the " + "prediction mode") + .withValueDataOptions({.colorMapper = ColorMapper({-3, 4}, PredefinedType::Col3_bblg)}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(9, "Intra Angle Delta (UV)") + .withDescription("Offset to be applied to the chroma prediction angle specified by the " + "prediction mode") + .withValueDataOptions({.colorMapper = ColorMapper({-3, 4}, PredefinedType::Col3_bblg)}) + .build()); + + statisticsData.addStatType(StatisticsTypeBuilder(10, "Intra Direction Luma") + .withDescription("Intra prediction direction luma") + .withVectorDataOptions({.scale = 4}) + .build()); + + statisticsData.addStatType(StatisticsTypeBuilder(11, "Intra Direction Chroma") + .withDescription("Intra prediction direction chroma") + .withVectorDataOptions({.scale = 4}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(12, "Chroma from Luma Alpha (U)") + .withDescription( + "CflAlphaU: Contains the signed value of the alpha component for the U component") + .withValueDataOptions( + {.colorMapper = ColorMapper({-128, 128}, PredefinedType::Col3_bblg)}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(13, "Chroma from Luma Alpha (V)") + .withDescription( + "CflAlphaV: Contains the signed value of the alpha component for the V component") + .withValueDataOptions( + {.colorMapper = ColorMapper({-128, 128}, PredefinedType::Col3_bblg)}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(14, "Ref Frame Index 0") + .withDescription("Reference frame index from List 0") + .withValueDataOptions({.colorMapper = ColorMapper({0, 7}, PredefinedType::Jet)}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(15, "Ref Frame Index 1") + .withDescription("Reference frame index from List 1") + .withValueDataOptions({.colorMapper = ColorMapper({0, 7}, PredefinedType::Jet)}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(16, "Compound Prediction Type") + .withDescription("The type of compound prediction used") + .withValueDataOptions({.colorMapper = ColorMapper({0, 4}, PredefinedType::Jet)}) + .withMappingValues({"COMP_INTER_NONE", + "COMP_INTER_WEIGHTED_AVG", + "COMP_INTER_AVG", + "COMP_INTER_SEG", + "COMP_INTER_WEDGE"}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(17, "Wedge Index") + .withValueDataOptions({.colorMapper = ColorMapper({0, 16}, PredefinedType::Jet)}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(18, "Mask Sign") + .withValueDataOptions( + {.colorMapper = ColorMapper({0, 1}, Color(0, 0, 0), Color(0, 255, 255))}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(19, "Inter Mode") + .withValueDataOptions({.colorMapper = ColorMapper({0, 7}, PredefinedType::Jet)}) + .withMappingValues({"NEARESTMV_NEARESTMV", "NEARMV_NEARMV", "NEARESTMV_NEWMV", "NEWMV_NEARESTMV", "NEARMV_NEWMV", "NEWMV_NEARMV", "GLOBALMV_GLOBALMV", - "NEWMV_NEWMV"}); - statisticsData.addStatType(interMode); - - stats::StatisticsType drlIndex( - 20, "dynamic reference list index", ColorMapper({0, 16}, Color(0, 0, 0), Color(0, 255, 255))); - statisticsData.addStatType(drlIndex); - - stats::StatisticsType interintraType( - 21, "inter-intra type", ColorMapper({0, 2}, PredefinedType::Jet)); - interintraType.setMappingValues({"INTER_INTRA_NONE", "INTER_INTRA_BLEND", "INTER_INTRA_WEDGE"}); - statisticsData.addStatType(interintraType); - - stats::StatisticsType interintraMode( - 22, "inter-intra mode", ColorMapper({0, 4}, PredefinedType::Jet)); - interintraMode.setMappingValues( - {"II_DC_PRED", "II_VERT_PRED", "II_HOR_PRED", "II_SMOOTH_PRED", "N_INTER_INTRA_PRED_MODES"}); - statisticsData.addStatType(interintraMode); - - stats::StatisticsType motionMode(23, "motion mode", ColorMapper({0, 2}, PredefinedType::Jet)); - motionMode.setMappingValues({"MM_TRANSLATION", "MM_OBMC", "MM_WARP"}); - statisticsData.addStatType(motionMode); - - stats::StatisticsType motionVec0(24, "Motion Vector 0", 4); - motionVec0.description = "The motion vector for component 0"; - statisticsData.addStatType(motionVec0); - - stats::StatisticsType motionVec1(25, "Motion Vector 1", 4); - motionVec1.description = "The motion vector for component 1"; - statisticsData.addStatType(motionVec1); - - stats::StatisticsType transformDepth( - 26, "Transform Size", ColorMapper({0, 19}, PredefinedType::Jet)); - transformDepth.description = "The transform size"; - transformDepth.setMappingValues({"TX_4X4", - "TX_8X8", - "TX_16X16", - "TX_32X32", - "TX_64X64", - "RTX_4X8", - "RTX_8X4", - "RTX_8X16", - "RTX_16X8", - "RTX_16X32", - "RTX_32X16", - "RTX_32X64", - "RTX_64X32", - "RTX_4X16", - "RTX_16X4", - "RTX_8X32", - "RTX_32X8", - "RTX_16X64", - "RTX_64X16"}); - statisticsData.addStatType(transformDepth); + "NEWMV_NEWMV"}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(20, "Dynamic Reference List Index") + .withValueDataOptions( + {.colorMapper = ColorMapper({0, 16}, Color(0, 0, 0), Color(0, 255, 255))}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(21, "Inter-Intra Type") + .withValueDataOptions({.colorMapper = ColorMapper({0, 2}, PredefinedType::Jet)}) + .withMappingValues({"INTER_INTRA_NONE", "INTER_INTRA_BLEND", "INTER_INTRA_WEDGE"}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(22, "Inter-Intra Mode") + .withValueDataOptions({.colorMapper = ColorMapper({0, 4}, PredefinedType::Jet)}) + .withMappingValues({"II_DC_PRED", + "II_VERT_PRED", + "II_HOR_PRED", + "II_SMOOTH_PRED", + "N_INTER_INTRA_PRED_MODES"}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(23, "Motion Mode") + .withValueDataOptions({.colorMapper = ColorMapper({0, 2}, PredefinedType::Jet)}) + .withMappingValues({"MM_TRANSLATION", "MM_OBMC", "MM_WARP"}) + .build()); + + statisticsData.addStatType(StatisticsTypeBuilder(24, "Motion Vector 0") + .withDescription("The motion vector for component 0") + .withVectorDataOptions({.scale = 4}) + .build()); + + statisticsData.addStatType(StatisticsTypeBuilder(25, "Motion Vector 1") + .withDescription("The motion vector for component 1") + .withVectorDataOptions({.scale = 4}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(26, "Transform Size") + .withValueDataOptions({.colorMapper = ColorMapper({0, 19}, PredefinedType::Jet)}) + .withMappingValues({"TX_4X4", + "TX_8X8", + "TX_16X16", + "TX_32X32", + "TX_64X64", + "RTX_4X8", + "RTX_8X4", + "RTX_8X16", + "RTX_16X8", + "RTX_16X32", + "RTX_32X16", + "RTX_32X64", + "RTX_64X32", + "RTX_4X16", + "RTX_16X4", + "RTX_8X32", + "RTX_32X8", + "RTX_16X64", + "RTX_64X16"}) + .build()); } void decoderDav1d::cacheStatistics(const Dav1dPictureWrapper &img) diff --git a/YUViewLib/src/decoder/decoderFFmpeg.cpp b/YUViewLib/src/decoder/decoderFFmpeg.cpp index d6d88de41..429929a53 100644 --- a/YUViewLib/src/decoder/decoderFFmpeg.cpp +++ b/YUViewLib/src/decoder/decoderFFmpeg.cpp @@ -33,6 +33,7 @@ #include "decoderFFmpeg.h" #include +#include #define DECODERFFMPEG_DEBUG_OUTPUT 0 #if DECODERFFMPEG_DEBUG_OUTPUT && !NDEBUG @@ -45,6 +46,8 @@ namespace decoder { +using stats::StatisticsTypeBuilder; + decoderFFmpeg::decoderFFmpeg(FFmpeg::AVCodecIDWrapper codecID, Size size, QByteArray extradata, @@ -429,13 +432,20 @@ bool decoderFFmpeg::decodeFrame() void decoderFFmpeg::fillStatisticList(stats::StatisticsData &statisticsData) const { - auto sourceColorMapper = + const auto sourceColorMapper = stats::color::ColorMapper({-2, 2}, stats::color::PredefinedType::Col3_bblg); - statisticsData.addStatType(stats::StatisticsType(0, "Source -", sourceColorMapper)); - statisticsData.addStatType(stats::StatisticsType(1, "Source +", sourceColorMapper)); - statisticsData.addStatType(stats::StatisticsType(2, "Motion Vector -", 4)); - statisticsData.addStatType(stats::StatisticsType(3, "Motion Vector +", 4)); + statisticsData.addStatType(StatisticsTypeBuilder(0, "Source -") + .withValueDataOptions({.colorMapper = sourceColorMapper}) + .build()); + statisticsData.addStatType(StatisticsTypeBuilder(1, "Source +") + .withValueDataOptions({.colorMapper = sourceColorMapper}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(2, "Motion Vector -").withVectorDataOptions({.scale = 4}).build()); + statisticsData.addStatType( + StatisticsTypeBuilder(3, "Motion Vector +").withVectorDataOptions({.scale = 4}).build()); } bool decoderFFmpeg::createDecoder(FFmpeg::AVCodecIDWrapper codecID, diff --git a/YUViewLib/src/decoder/decoderHM.cpp b/YUViewLib/src/decoder/decoderHM.cpp index 4f9444e7f..c99717036 100644 --- a/YUViewLib/src/decoder/decoderHM.cpp +++ b/YUViewLib/src/decoder/decoderHM.cpp @@ -39,6 +39,10 @@ #include #include +#include + +using stats::StatisticsType; +using stats::StatisticsTypeBuilder; namespace decoder { @@ -517,66 +521,64 @@ void decoderHM::fillStatisticList(stats::StatisticsData &statisticsData) const using namespace stats::color; // Ask the decoder how many internals types there are - unsigned int nrTypes = this->lib.libHMDEC_get_internal_type_number(); + const auto nrTypes = this->lib.libHMDEC_get_internal_type_number(); for (unsigned int i = 0; i < nrTypes; i++) { - auto name = QString(this->lib.libHMDEC_get_internal_type_name(i)); - auto description = QString(this->lib.libHMDEC_get_internal_type_description(i)); - auto statType = this->lib.libHMDEC_get_internal_type(i); - int max = 0; + const auto name = std::string(this->lib.libHMDEC_get_internal_type_name(i)); + const auto description = std::string(this->lib.libHMDEC_get_internal_type_description(i)); + const auto statType = this->lib.libHMDEC_get_internal_type(i); + int max = 0; if (statType == LIBHMDEC_TYPE_RANGE || statType == LIBHMDEC_TYPE_RANGE_ZEROCENTER) { unsigned int uMax = this->lib.libHMDEC_get_internal_type_max(i); max = (uMax > INT_MAX) ? INT_MAX : uMax; } + auto typeBuilder = StatisticsTypeBuilder(i, name).withDescription(description); + if (statType == LIBHMDEC_TYPE_FLAG) { - stats::StatisticsType flag(i, name, ColorMapper({0, 1}, PredefinedType::Jet)); - flag.description = description; - statisticsData.addStatType(flag); + typeBuilder = typeBuilder.withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({0, 1}, PredefinedType::Jet)})); } else if (statType == LIBHMDEC_TYPE_RANGE) { - stats::StatisticsType range(i, name, ColorMapper({0, max}, PredefinedType::Jet)); - range.description = description; - statisticsData.addStatType(range); + typeBuilder = typeBuilder.withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({0, max}, PredefinedType::Jet)})); } else if (statType == LIBHMDEC_TYPE_RANGE_ZEROCENTER) { - stats::StatisticsType rangeZero(i, name, ColorMapper({-max, max}, PredefinedType::Col3_bblg)); - rangeZero.description = description; - statisticsData.addStatType(rangeZero); + typeBuilder = typeBuilder.withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({-max, max}, PredefinedType::Col3_bblg)})); } else if (statType == LIBHMDEC_TYPE_VECTOR) { - auto scale = this->lib.libHMDEC_get_internal_type_vector_scaling(i); - stats::StatisticsType vec(i, name, scale); - vec.description = description; - statisticsData.addStatType(vec); + const auto scale = static_cast(this->lib.libHMDEC_get_internal_type_vector_scaling(i)); + typeBuilder = + typeBuilder.withVectorDataOptions(StatisticsType::VectorDataOptions({.scale = scale})); } else if (statType == LIBHMDEC_TYPE_INTRA_DIR) { - stats::StatisticsType intraDir(i, name, ColorMapper({0, 34}, PredefinedType::Jet)); - intraDir.description = description; - intraDir.hasVectorData = true; - intraDir.renderVectorData = true; - intraDir.vectorScale = 32; - // Don't draw the vector values for the intra dir. They don't have actual meaning. - intraDir.renderVectorDataValues = false; - intraDir.setMappingValues( - {"INTRA_PLANAR", "INTRA_DC", "INTRA_ANGULAR_2", "INTRA_ANGULAR_3", - "INTRA_ANGULAR_4", "INTRA_ANGULAR_5", "INTRA_ANGULAR_6", "INTRA_ANGULAR_7", - "INTRA_ANGULAR_8", "INTRA_ANGULAR_9", "INTRA_ANGULAR_10", "INTRA_ANGULAR_11", - "INTRA_ANGULAR_12", "INTRA_ANGULAR_13", "INTRA_ANGULAR_14", "INTRA_ANGULAR_15", - "INTRA_ANGULAR_16", "INTRA_ANGULAR_17", "INTRA_ANGULAR_18", "INTRA_ANGULAR_19", - "INTRA_ANGULAR_20", "INTRA_ANGULAR_21", "INTRA_ANGULAR_22", "INTRA_ANGULAR_23", - "INTRA_ANGULAR_24", "INTRA_ANGULAR_25", "INTRA_ANGULAR_26", "INTRA_ANGULAR_27", - "INTRA_ANGULAR_28", "INTRA_ANGULAR_29", "INTRA_ANGULAR_30", "INTRA_ANGULAR_31", - "INTRA_ANGULAR_32", "INTRA_ANGULAR_33", "INTRA_ANGULAR_34"}); - statisticsData.addStatType(intraDir); + typeBuilder = + typeBuilder + .withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({0, 34}, PredefinedType::Jet)})) + .withVectorDataOptions( + StatisticsType::VectorDataOptions({.renderDataValues = true, .scale = 32})) + .withMappingValues( + {"INTRA_PLANAR", "INTRA_DC", "INTRA_ANGULAR_2", "INTRA_ANGULAR_3", + "INTRA_ANGULAR_4", "INTRA_ANGULAR_5", "INTRA_ANGULAR_6", "INTRA_ANGULAR_7", + "INTRA_ANGULAR_8", "INTRA_ANGULAR_9", "INTRA_ANGULAR_10", "INTRA_ANGULAR_11", + "INTRA_ANGULAR_12", "INTRA_ANGULAR_13", "INTRA_ANGULAR_14", "INTRA_ANGULAR_15", + "INTRA_ANGULAR_16", "INTRA_ANGULAR_17", "INTRA_ANGULAR_18", "INTRA_ANGULAR_19", + "INTRA_ANGULAR_20", "INTRA_ANGULAR_21", "INTRA_ANGULAR_22", "INTRA_ANGULAR_23", + "INTRA_ANGULAR_24", "INTRA_ANGULAR_25", "INTRA_ANGULAR_26", "INTRA_ANGULAR_27", + "INTRA_ANGULAR_28", "INTRA_ANGULAR_29", "INTRA_ANGULAR_30", "INTRA_ANGULAR_31", + "INTRA_ANGULAR_32", "INTRA_ANGULAR_33", "INTRA_ANGULAR_34"}); } + + statisticsData.addStatType(typeBuilder.build()); } } diff --git a/YUViewLib/src/decoder/decoderLibde265.cpp b/YUViewLib/src/decoder/decoderLibde265.cpp index e87a82ef1..26da0f18d 100644 --- a/YUViewLib/src/decoder/decoderLibde265.cpp +++ b/YUViewLib/src/decoder/decoderLibde265.cpp @@ -39,6 +39,10 @@ #include #include +#include + +using stats::StatisticsType; +using stats::StatisticsTypeBuilder; namespace decoder { @@ -893,98 +897,110 @@ void decoderLibde265::fillStatisticList(stats::StatisticsData &statisticsData) c { using namespace stats::color; - stats::StatisticsType sliceIdx( - 0, "Slice Index", ColorMapper({0, 10}, Color(0, 0, 0), Color(255, 0, 0))); - sliceIdx.description = "The slice index reported per CTU"; - statisticsData.addStatType(sliceIdx); - - stats::StatisticsType partSize(1, "Part Size", ColorMapper({0, 7}, PredefinedType::Jet)); - partSize.description = "The partition size of each CU into PUs"; - partSize.setMappingValues({"PART_2Nx2N", - "PART_2NxN", - "PART_Nx2N", - "PART_NxN", - "PART_2NxnU", - "PART_2NxnD", - "PART_nLx2N", - "PART_nRx2N"}); - statisticsData.addStatType(partSize); - - stats::StatisticsType predMode(2, "Pred Mode", ColorMapper({0, 2}, PredefinedType::Jet)); - predMode.description = "The internal libde265 prediction mode (intra/inter/skip) per CU"; - predMode.setMappingValues({"INTRA", "INTER", "SKIP"}); - statisticsData.addStatType(predMode); - - stats::StatisticsType pcmFlag( - 3, "PCM flag", ColorMapper({0, 1}, Color(0, 0, 0), Color(255, 0, 0))); - pcmFlag.description = "The PCM flag per CU"; - statisticsData.addStatType(pcmFlag); - - stats::StatisticsType transQuantBypass( - 4, "Transquant Bypass Flag", ColorMapper({0, 1}, Color(0, 0, 0), Color(255, 0, 0))); - transQuantBypass.description = "The transquant bypass flag per CU"; - statisticsData.addStatType(transQuantBypass); - - stats::StatisticsType refIdx0(5, "Ref POC 0", ColorMapper({-16, 16}, PredefinedType::Col3_bblg)); - refIdx0.description = "The reference POC in LIST 0 relative to the current POC per PU"; - statisticsData.addStatType(refIdx0); - - stats::StatisticsType refIdx1(6, "Ref POC 1", ColorMapper({-16, 16}, PredefinedType::Col3_bblg)); - refIdx1.description = "The reference POC in LIST 1 relative to the current POC per PU"; - statisticsData.addStatType(refIdx1); - - stats::StatisticsType motionVec0(7, "Motion Vector 0", 4); - motionVec0.description = "The motion vector in LIST 0 per PU"; - statisticsData.addStatType(motionVec0); - - stats::StatisticsType motionVec1(8, "Motion Vector 1", 4); - motionVec1.description = "The motion vector in LIST 1 per PU"; - statisticsData.addStatType(motionVec1); - - stats::StatisticsType intraDirY(9, "Intra Dir Luma", ColorMapper({0, 34}, PredefinedType::Jet)); - intraDirY.description = - "The intra mode for the luma component per TU (intra prediction is performed on a TU level)"; - intraDirY.hasVectorData = true; - intraDirY.renderVectorData = true; - intraDirY.vectorScale = 32; - // Don't draw the vector values for the intra dir. They don't have actual meaning. - intraDirY.renderVectorDataValues = false; - intraDirY.setMappingValues( - {"INTRA_PLANAR", "INTRA_DC", "INTRA_ANGULAR_2", "INTRA_ANGULAR_3", - "INTRA_ANGULAR_4", "INTRA_ANGULAR_5", "INTRA_ANGULAR_6", "INTRA_ANGULAR_7", - "INTRA_ANGULAR_8", "INTRA_ANGULAR_9", "INTRA_ANGULAR_10", "INTRA_ANGULAR_11", - "INTRA_ANGULAR_12", "INTRA_ANGULAR_13", "INTRA_ANGULAR_14", "INTRA_ANGULAR_15", - "INTRA_ANGULAR_16", "INTRA_ANGULAR_17", "INTRA_ANGULAR_18", "INTRA_ANGULAR_19", - "INTRA_ANGULAR_20", "INTRA_ANGULAR_21", "INTRA_ANGULAR_22", "INTRA_ANGULAR_23", - "INTRA_ANGULAR_24", "INTRA_ANGULAR_25", "INTRA_ANGULAR_26", "INTRA_ANGULAR_27", - "INTRA_ANGULAR_28", "INTRA_ANGULAR_29", "INTRA_ANGULAR_30", "INTRA_ANGULAR_31", - "INTRA_ANGULAR_32", "INTRA_ANGULAR_33", "INTRA_ANGULAR_34"}); - statisticsData.addStatType(intraDirY); - - stats::StatisticsType intraDirC( - 10, "Intra Dir Chroma", ColorMapper({0, 34}, PredefinedType::Jet)); - intraDirC.description = "The intra mode for the chroma component per TU (intra prediction is " - "performed on a TU level)"; - intraDirC.hasVectorData = true; - intraDirC.renderVectorData = true; - intraDirC.renderVectorDataValues = false; - intraDirC.vectorScale = 32; - intraDirC.setMappingValues( - {"INTRA_PLANAR", "INTRA_DC", "INTRA_ANGULAR_2", "INTRA_ANGULAR_3", - "INTRA_ANGULAR_4", "INTRA_ANGULAR_5", "INTRA_ANGULAR_6", "INTRA_ANGULAR_7", - "INTRA_ANGULAR_8", "INTRA_ANGULAR_9", "INTRA_ANGULAR_10", "INTRA_ANGULAR_11", - "INTRA_ANGULAR_12", "INTRA_ANGULAR_13", "INTRA_ANGULAR_14", "INTRA_ANGULAR_15", - "INTRA_ANGULAR_16", "INTRA_ANGULAR_17", "INTRA_ANGULAR_18", "INTRA_ANGULAR_19", - "INTRA_ANGULAR_20", "INTRA_ANGULAR_21", "INTRA_ANGULAR_22", "INTRA_ANGULAR_23", - "INTRA_ANGULAR_24", "INTRA_ANGULAR_25", "INTRA_ANGULAR_26", "INTRA_ANGULAR_27", - "INTRA_ANGULAR_28", "INTRA_ANGULAR_29", "INTRA_ANGULAR_30", "INTRA_ANGULAR_31", - "INTRA_ANGULAR_32", "INTRA_ANGULAR_33", "INTRA_ANGULAR_34"}); - statisticsData.addStatType(intraDirC); - - stats::StatisticsType transformDepth( - 11, "Transform Depth", ColorMapper({0, 3}, Color(0, 0, 0), Color(0, 255, 0))); - transformDepth.description = "The transform depth within the transform tree per TU"; - statisticsData.addStatType(transformDepth); + statisticsData.addStatType( + StatisticsTypeBuilder(0, "Slice Index") + .withDescription("The slice index reported per CTU") + .withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({0, 10}, Color(0, 0, 0), Color(255, 0, 0))})) + .build()); + + statisticsData.addStatType(StatisticsTypeBuilder(1, "Part Size") + .withDescription("The partition size of each CU into PUs") + .withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({0, 7}, PredefinedType::Jet)})) + .withMappingValues({"PART_2Nx2N", + "PART_2NxN", + "PART_Nx2N", + "PART_NxN", + "PART_2NxnU", + "PART_2NxnD", + "PART_nLx2N", + "PART_nRx2N"}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(2, "Pred Mode") + .withDescription("The internal libde265 prediction mode (intra/inter/skip) per CU") + .withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({0, 2}, PredefinedType::Jet)})) + .withMappingValues({"INTRA", "INTER", "SKIP"}) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(3, "PCM flag") + .withDescription("The PCM flag per CU") + .withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({0, 1}, Color(0, 0, 0), Color(255, 0, 0))})) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(4, "Transquant Bypass Flag") + .withDescription("The transquant bypass flag per CU") + .withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({0, 1}, Color(0, 0, 0), Color(255, 0, 0))})) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(5, "Ref POC 0") + .withDescription("The reference POC in LIST 0 relative to the current POC per PU") + .withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({-16, 16}, PredefinedType::Col3_bblg)})) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(6, "Ref POC 1") + .withDescription("The reference POC in LIST 1 relative to the current POC per PU") + .withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({-16, 16}, PredefinedType::Col3_bblg)})) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(7, "Motion Vector 0") + .withDescription("The motion vector in LIST 0 per PU") + .withVectorDataOptions(StatisticsType::VectorDataOptions({.scale = 4})) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(8, "Motion Vector 1") + .withDescription("The motion vector in LIST 1 per PU") + .withVectorDataOptions(StatisticsType::VectorDataOptions({.scale = 4})) + .build()); + + const auto INTRA_DIR_LIST = { + "INTRA_PLANAR", "INTRA_DC", "INTRA_ANGULAR_2", "INTRA_ANGULAR_3", + "INTRA_ANGULAR_4", "INTRA_ANGULAR_5", "INTRA_ANGULAR_6", "INTRA_ANGULAR_7", + "INTRA_ANGULAR_8", "INTRA_ANGULAR_9", "INTRA_ANGULAR_10", "INTRA_ANGULAR_11", + "INTRA_ANGULAR_12", "INTRA_ANGULAR_13", "INTRA_ANGULAR_14", "INTRA_ANGULAR_15", + "INTRA_ANGULAR_16", "INTRA_ANGULAR_17", "INTRA_ANGULAR_18", "INTRA_ANGULAR_19", + "INTRA_ANGULAR_20", "INTRA_ANGULAR_21", "INTRA_ANGULAR_22", "INTRA_ANGULAR_23", + "INTRA_ANGULAR_24", "INTRA_ANGULAR_25", "INTRA_ANGULAR_26", "INTRA_ANGULAR_27", + "INTRA_ANGULAR_28", "INTRA_ANGULAR_29", "INTRA_ANGULAR_30", "INTRA_ANGULAR_31", + "INTRA_ANGULAR_32", "INTRA_ANGULAR_33", "INTRA_ANGULAR_34"}; + + statisticsData.addStatType( + StatisticsTypeBuilder(9, "Intra Dir Luma") + .withDescription("The intra mode for the luma component per TU (intra prediction is " + "performed on a TU level)") + .withVectorDataOptions( + StatisticsType::VectorDataOptions({.renderDataValues = false, .scale = 32})) + .withMappingValues(INTRA_DIR_LIST) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(10, "Intra Dir Chroma") + .withDescription("The intra mode for the chroma component per TU (intra prediction is " + "performed on a TU level)") + .withVectorDataOptions( + StatisticsType::VectorDataOptions({.renderDataValues = false, .scale = 32})) + .withMappingValues(INTRA_DIR_LIST) + .build()); + + statisticsData.addStatType( + StatisticsTypeBuilder(11, "Transform Depth") + .withDescription("The transform depth within the transform tree per TU") + .withValueDataOptions(StatisticsType::ValueDataOptions( + {.colorMapper = ColorMapper({0, 3}, Color(0, 0, 0), Color(0, 255, 0))})) + .build()); } bool decoderLibde265::checkLibraryFile(QString libFilePath, QString &error) diff --git a/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp b/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp index e70c9bd0f..53fec4f87 100644 --- a/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp +++ b/YUViewLib/src/playlistitem/playlistItemStatisticsFile.cpp @@ -77,9 +77,9 @@ playlistItemStatisticsFile::playlistItemStatisticsFile(const QString &itemNameOr this->openStatisticsFile(); this->statisticsUIHandler.setStatisticsData(&this->statisticsData); - connect(&this->statisticsUIHandler, - &stats::StatisticUIHandler::updateItem, - [this](bool redraw) { emit SignalItemChanged(redraw, RECACHE_NONE); }); + connect(&this->statisticsUIHandler, &stats::StatisticUIHandler::updateItem, [this](bool redraw) { + emit SignalItemChanged(redraw, RECACHE_NONE); + }); } playlistItemStatisticsFile::~playlistItemStatisticsFile() @@ -299,8 +299,9 @@ void playlistItemStatisticsFile::openStatisticsFile() this->timer.start(1000, this); this->breakBackgroundAtomic.store(false); this->backgroundParserFuture = QtConcurrent::run( - [=](stats::StatisticsFileBase *file) - { file->readFrameAndTypePositionsFromFile(std::ref(this->breakBackgroundAtomic)); }, + [=, this](stats::StatisticsFileBase *file) { + file->readFrameAndTypePositionsFromFile(std::ref(this->breakBackgroundAtomic)); + }, this->file.get()); DEBUG_STAT( diff --git a/YUViewLib/src/statistics/ColorMapper.cpp b/YUViewLib/src/statistics/ColorMapper.cpp index ee498f61a..43f6c3fa5 100644 --- a/YUViewLib/src/statistics/ColorMapper.cpp +++ b/YUViewLib/src/statistics/ColorMapper.cpp @@ -152,8 +152,7 @@ Color ColorMapper::getColor(double value) const // The value scaled from 0 to 1 within the range (rangeMin ... rangeMax) auto valScaled = (value - this->valueRange.min) / rangeWidth; - auto interpolate = [&valScaled](int start, int end) - { + auto interpolate = [&valScaled](int start, int end) { auto range = end - start; auto rangeScaled = std::floor(valScaled * double(range) + 0.5); return start + int(rangeScaled); @@ -449,19 +448,25 @@ void ColorMapper::loadPlaylist(const QStringPairList &attributes) } } -bool ColorMapper::operator!=(const ColorMapper &other) const +bool ColorMapper::operator==(const ColorMapper &other) const { if (this->mappingType != other.mappingType) - return true; + return false; if (this->mappingType == MappingType::Gradient) - return this->valueRange != other.valueRange || - this->gradientColorStart != other.gradientColorStart || - this->gradientColorEnd != other.gradientColorEnd; + return this->valueRange == other.valueRange && + this->gradientColorStart == other.gradientColorStart && + this->gradientColorEnd == other.gradientColorEnd; if (this->mappingType == MappingType::Map) - return this->colorMap != other.colorMap; + return this->colorMap == other.colorMap; if (this->mappingType == MappingType::Predefined) - return this->valueRange != other.valueRange || this->predefinedType != other.predefinedType; + return this->valueRange == other.valueRange && // + this->predefinedType == other.predefinedType; return false; } +bool ColorMapper::operator!=(const ColorMapper &other) const +{ + return !(*this == other); +} + } // namespace stats::color diff --git a/YUViewLib/src/statistics/ColorMapper.h b/YUViewLib/src/statistics/ColorMapper.h index f19af61d2..e405ae85a 100644 --- a/YUViewLib/src/statistics/ColorMapper.h +++ b/YUViewLib/src/statistics/ColorMapper.h @@ -130,6 +130,7 @@ class ColorMapper // Two colorMappers are identical if they will return the same color when asked for any value. // When changing the type of one of the mappers, this might not be true anymore. + bool operator==(const ColorMapper &other) const; bool operator!=(const ColorMapper &other) const; MappingType mappingType{MappingType::Predefined}; @@ -137,7 +138,7 @@ class ColorMapper Range valueRange{}; Color gradientColorStart{0, 0, 0}; Color gradientColorEnd{0, 0, 255}; - ColorMap colorMap; + ColorMap colorMap{}; Color colorMapOther{}; PredefinedType predefinedType{PredefinedType::Jet}; }; diff --git a/YUViewLib/src/statistics/StatisticUIHandler.cpp b/YUViewLib/src/statistics/StatisticUIHandler.cpp index f9b941449..34fc3671f 100644 --- a/YUViewLib/src/statistics/StatisticUIHandler.cpp +++ b/YUViewLib/src/statistics/StatisticUIHandler.cpp @@ -40,9 +40,9 @@ #endif #include +#include #include #include -#include namespace stats { @@ -64,7 +64,10 @@ StatisticUIHandler::StatisticUIHandler() Qt::QueuedConnection); } -void StatisticUIHandler::setStatisticsData(StatisticsData *data) { this->statisticsData = data; } +void StatisticUIHandler::setStatisticsData(StatisticsData *data) +{ + this->statisticsData = data; +} QLayout *StatisticUIHandler::createStatisticsHandlerControls(bool recreateControlsOnly) { @@ -89,9 +92,10 @@ QLayout *StatisticUIHandler::createStatisticsHandlerControls(bool recreateContro auto &statType = statTypes.at(row); // Append the name (with the check box to enable/disable the statistics item) - QCheckBox *itemNameCheck = new QCheckBox(statType.typeName, ui.scrollAreaWidgetContents); + QCheckBox *itemNameCheck = + new QCheckBox(QString::fromStdString(statType.typeName), ui.scrollAreaWidgetContents); itemNameCheck->setChecked(statType.render); - itemNameCheck->setToolTip(statType.description); + itemNameCheck->setToolTip(QString::fromStdString(statType.description)); ui.gridLayout->addWidget(itemNameCheck, int(row + 2), 0); connect(itemNameCheck, &QCheckBox::stateChanged, @@ -115,7 +119,7 @@ QLayout *StatisticUIHandler::createStatisticsHandlerControls(bool recreateContro QPushButton *pushButton = new QPushButton( functionsGui::convertIcon(":img_edit.png"), QString(), ui.scrollAreaWidgetContents); ui.gridLayout->addWidget(pushButton, int(row + 2), 2); - connect(pushButton, &QPushButton::released, this, [=] { onStyleButtonClicked(row); }); + connect(pushButton, &QPushButton::released, this, [=, this] { onStyleButtonClicked(row); }); itemStyleButtons[0].push_back(pushButton); } @@ -155,7 +159,8 @@ QWidget *StatisticUIHandler::getSecondaryStatisticsHandlerControls(bool recreate auto &statType = statTypes.at(row); // Append the name (with the check box to enable/disable the statistics item) - QCheckBox *itemNameCheck = new QCheckBox(statType.typeName, ui2.scrollAreaWidgetContents); + QCheckBox *itemNameCheck = + new QCheckBox(QString::fromStdString(statType.typeName), ui2.scrollAreaWidgetContents); itemNameCheck->setChecked(statType.render); ui2.gridLayout->addWidget(itemNameCheck, int(row + 2), 0); connect(itemNameCheck, @@ -180,7 +185,7 @@ QWidget *StatisticUIHandler::getSecondaryStatisticsHandlerControls(bool recreate QPushButton *pushButton = new QPushButton( functionsGui::convertIcon(":img_edit.png"), QString(), ui2.scrollAreaWidgetContents); ui2.gridLayout->addWidget(pushButton, int(row + 2), 2); - connect(pushButton, &QPushButton::released, this, [=] { onStyleButtonClicked(row); }); + connect(pushButton, &QPushButton::released, this, [=, this] { onStyleButtonClicked(row); }); itemStyleButtons[1].push_back(pushButton); } @@ -321,8 +326,8 @@ void StatisticUIHandler::updateStatisticsHandlerControls() } // First run a check if all statisticsTypes are identical - bool controlsStillValid = true; - auto &statTypes = this->statisticsData->getStatisticsTypes(); + bool controlsStillValid = true; + auto &statTypes = this->statisticsData->getStatisticsTypes(); if (statTypes.size() != itemNameCheckBoxes[0].size()) // There are more or less statistics types as before controlsStillValid = false; @@ -330,7 +335,7 @@ void StatisticUIHandler::updateStatisticsHandlerControls() { for (unsigned row = 0; row < statTypes.size(); row++) { - if (itemNameCheckBoxes[0][row]->text() != statTypes[row].typeName) + if (itemNameCheckBoxes[0][row]->text().toStdString() != statTypes[row].typeName) { // One of the statistics types changed it's name or the order of statistics types changed. // Either way, we will create new controls. @@ -410,11 +415,11 @@ void StatisticUIHandler::updateStatisticsHandlerControls() { // In the new list of statistics types we found one that has the same name as this one. // This is enough indication. Apply the old settings to this new type. - statTypes[j].render = statsTypeListBackup[i].render; - statTypes[j].renderValueData = statsTypeListBackup[i].renderValueData; - statTypes[j].renderVectorData = statsTypeListBackup[i].renderVectorData; - statTypes[j].renderGrid = statsTypeListBackup[i].renderGrid; - statTypes[j].alphaFactor = statsTypeListBackup[i].alphaFactor; + statTypes[j].render = statsTypeListBackup[i].render; + statTypes[j].valueDataOptions = statsTypeListBackup[i].valueDataOptions; + statTypes[j].vectorDataOptions = statsTypeListBackup[i].vectorDataOptions; + statTypes[j].gridOptions = statsTypeListBackup[i].gridOptions; + statTypes[j].alphaFactor = statsTypeListBackup[i].alphaFactor; } } } diff --git a/YUViewLib/src/statistics/StatisticsData.cpp b/YUViewLib/src/statistics/StatisticsData.cpp index aa93a2bd7..c34506a89 100644 --- a/YUViewLib/src/statistics/StatisticsData.cpp +++ b/YUViewLib/src/statistics/StatisticsData.cpp @@ -32,6 +32,8 @@ #include "StatisticsData.h" +#include "StatisticsTypePlaylistHandler.h" + #include // Activate this if you want to know when what is loaded. @@ -202,9 +204,6 @@ QStringPairList StatisticsData::getValuesAt(const QPoint &pos) const for (auto it = this->statsTypes.rbegin(); it != this->statsTypes.rend(); it++) { - if (!it->renderGrid) - continue; - if (it->typeID == INT_INVALID || this->frameCache.count(it->typeID) == 0) // no active statistics data continue; @@ -217,11 +216,12 @@ QStringPairList StatisticsData::getValuesAt(const QPoint &pos) const if (rect.contains(pos)) { int value = valueItem.value; - auto valTxt = it->getValueTxt(value); - if (valTxt.isEmpty() && it->scaleValueToBlockSize) - valTxt = QString("%1").arg(float(value) / (valueItem.size[0] * valueItem.size[1])); + auto valTxt = it->getValueText(value); + if (valTxt.empty() && it->valueDataOptions && it->valueDataOptions->scaleToBlockSize) + valTxt = std::to_string(float(value) / (valueItem.size[0] * valueItem.size[1])); - valueList.append(QStringPair(it->typeName, valTxt)); + valueList.append( + QStringPair(QString::fromStdString(it->typeName), QString::fromStdString(valTxt))); foundStats = true; } } @@ -232,20 +232,21 @@ QStringPairList StatisticsData::getValuesAt(const QPoint &pos) const QRect(vectorItem.pos[0], vectorItem.pos[1], vectorItem.size[0], vectorItem.size[1]); if (rect.contains(pos)) { - double x{}; - double y{}; + double x{}; + double y{}; + const auto scale = it->vectorDataOptions->scale; if (vectorItem.isLine) { - x = double(vectorItem.point[1].x - vectorItem.point[0].x) / it->vectorScale; - y = double(vectorItem.point[1].y - vectorItem.point[0].y) / it->vectorScale; + x = double(vectorItem.point[1].x - vectorItem.point[0].x) / scale; + y = double(vectorItem.point[1].y - vectorItem.point[0].y) / scale; } else { - x = double(vectorItem.point[0].x) / it->vectorScale; - y = double(vectorItem.point[0].y) / it->vectorScale; + x = double(vectorItem.point[0].x) / scale; + y = double(vectorItem.point[0].y) / scale; } - valueList.append( - QStringPair(QString("%1").arg(it->typeName), QString("(%1,%2)").arg(x).arg(y))); + valueList.append(QStringPair(QString("%1").arg(QString::fromStdString(it->typeName)), + QString("(%1,%2)").arg(x).arg(y))); foundStats = true; } } @@ -256,14 +257,17 @@ QStringPairList StatisticsData::getValuesAt(const QPoint &pos) const affineTFItem.pos[0], affineTFItem.pos[1], affineTFItem.size[0], affineTFItem.size[1]); if (rect.contains(pos)) { + const auto scale = it->vectorDataOptions->scale; for (unsigned i = 0; i < 3; i++) { - auto xScaled = float(affineTFItem.point[i].x / it->vectorScale); - auto yScaled = float(affineTFItem.point[i].y / it->vectorScale); + auto xScaled = float(affineTFItem.point[i].x / scale); + auto yScaled = float(affineTFItem.point[i].y / scale); valueList.append( - QStringPair(QString("%1_%2[x]").arg(it->typeName).arg(i), QString::number(xScaled))); + QStringPair(QString("%1_%2[x]").arg(QString::fromStdString(it->typeName)).arg(i), + QString::number(xScaled))); valueList.append( - QStringPair(QString("%1_%2[y]").arg(it->typeName).arg(i), QString::number(yScaled))); + QStringPair(QString("%1_%2[y]").arg(QString::fromStdString(it->typeName)).arg(i), + QString::number(yScaled))); } foundStats = true; } @@ -276,8 +280,9 @@ QStringPairList StatisticsData::getValuesAt(const QPoint &pos) const if (stats::polygonContainsPoint(valueItem.corners, Point(pos.x(), pos.y()))) { int value = valueItem.value; - auto valTxt = it->getValueTxt(value); - valueList.append(QStringPair(it->typeName, valTxt)); + auto valTxt = it->getValueText(value); + valueList.append( + QStringPair(QString::fromStdString(it->typeName), QString::fromStdString(valTxt))); foundStats = true; } } @@ -288,22 +293,23 @@ QStringPairList StatisticsData::getValuesAt(const QPoint &pos) const continue; // need at least triangle -- or more corners if (stats::polygonContainsPoint(polygonVectorItem.corners, Point(pos.x(), pos.y()))) { - if (it->renderVectorData) + if (it->vectorDataOptions && it->vectorDataOptions->render) { // The length of the vector - auto xScaled = (float)polygonVectorItem.point.x / it->vectorScale; - auto yScaled = (float)polygonVectorItem.point.y / it->vectorScale; - valueList.append( - QStringPair(QString("%1[x]").arg(it->typeName), QString::number(xScaled))); - valueList.append( - QStringPair(QString("%1[y]").arg(it->typeName), QString::number(yScaled))); + const auto scale = it->vectorDataOptions->scale; + auto xScaled = (float)polygonVectorItem.point.x / scale; + auto yScaled = (float)polygonVectorItem.point.y / scale; + valueList.append(QStringPair(QString("%1[x]").arg(QString::fromStdString(it->typeName)), + QString::number(xScaled))); + valueList.append(QStringPair(QString("%1[y]").arg(QString::fromStdString(it->typeName)), + QString::number(yScaled))); foundStats = true; } } } if (!foundStats) - valueList.append(QStringPair(it->typeName, "-")); + valueList.append(QStringPair(QString::fromStdString(it->typeName), "-")); } return valueList; @@ -355,13 +361,13 @@ void StatisticsData::addStatType(const StatisticsType &type) void StatisticsData::savePlaylist(YUViewDomElement &root) const { for (const auto &type : this->statsTypes) - type.savePlaylist(root); + StatisticsTypePlaylistHandler::saveToPlaylist(type, root); } void StatisticsData::loadPlaylist(const YUViewDomElement &root) { for (auto &type : this->statsTypes) - type.loadPlaylist(root); + StatisticsTypePlaylistHandler::tryToLoadFromPlaylist(type, root); } } // namespace stats diff --git a/YUViewLib/src/statistics/StatisticsDataPainting.cpp b/YUViewLib/src/statistics/StatisticsDataPainting.cpp index f421055a8..0d1111211 100644 --- a/YUViewLib/src/statistics/StatisticsDataPainting.cpp +++ b/YUViewLib/src/statistics/StatisticsDataPainting.cpp @@ -72,7 +72,7 @@ QPoint getPolygonCenter(const QPolygon &polygon) return p; } -Qt::PenStyle patternToQPenStyle(stats::Pattern &pattern) +Qt::PenStyle patternToQPenStyle(const stats::Pattern &pattern) { if (pattern == stats::Pattern::Solid) return Qt::SolidLine; @@ -87,43 +87,48 @@ Qt::PenStyle patternToQPenStyle(stats::Pattern &pattern) return Qt::SolidLine; } -QPen styleToPen(stats::LineDrawStyle &style) +QPen styleToPen(const stats::LineDrawStyle &style) { return QPen(functionsGui::toQColor(style.color), style.width, patternToQPenStyle(style.pattern)); } -void paintVector(QPainter * painter, +void paintVector(QPainter *painter, const stats::StatisticsType &statisticsType, - const double & zoomFactor, - const int & x1, - const int & y1, - const int & x2, - const int & y2, - const float & vx, - const float & vy, + const double &zoomFactor, + const int &x1, + const int &y1, + const int &x2, + const int &y2, + const float &vx, + const float &vy, bool isLine, - const int & xMin, - const int & xMax, - const int & yMin, - const int & yMax) + const int &xMin, + const int &xMax, + const int &yMin, + const int &yMax) { + if (!statisticsType.vectorDataOptions) + return; // Is the arrow (possibly) visible? if (!(x1 < xMin && x2 < xMin) && !(x1 > xMax && x2 > xMax) && !(y1 < yMin && y2 < yMin) && !(y1 > yMax && y2 > yMax)) { + const auto &vector = statisticsType.vectorDataOptions.value(); + // Set the pen for drawing - auto vectorStyle = statisticsType.vectorStyle; - auto arrowColor = functionsGui::toQColor(vectorStyle.color); - if (statisticsType.mapVectorToColor) + auto vectorStyle = statisticsType.vectorDataOptions->style; + auto arrowColor = functionsGui::toQColor(vectorStyle->color); + if (vector.mapToColor) arrowColor.setHsvF( functions::clip((std::atan2(vy, vx) + M_PI) / (2 * M_PI), 0.0, 1.0), 1.0, 1.0); - arrowColor.setAlpha(arrowColor.alpha() * ((float)statisticsType.alphaFactor / 100.0)); + arrowColor.setAlpha( + functions::scaleValueByPercent(arrowColor.alpha(), *statisticsType.alphaFactor)); - if (statisticsType.scaleVectorToZoom) - vectorStyle.width = vectorStyle.width * zoomFactor / 8; + if (vector.scaleToZoom) + vectorStyle->width = vectorStyle->width * zoomFactor / 8; - painter->setPen(QPen(arrowColor, vectorStyle.width, patternToQPenStyle(vectorStyle.pattern))); + painter->setPen(QPen(arrowColor, vectorStyle->width, patternToQPenStyle(vectorStyle->pattern))); painter->setBrush(arrowColor); // Draw the arrow tip, or a circle if the vector is (0,0) if the zoom factor is not 1 or @@ -140,14 +145,12 @@ void paintVector(QPainter * painter, { // The size of the arrow head const int headSize = - (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM && !statisticsType.scaleVectorToZoom) - ? 8 - : zoomFactor / 2; + (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM && !vector.scaleToZoom) ? 8 : zoomFactor / 2; - if (statisticsType.arrowHead != stats::StatisticsType::ArrowHead::none) + if (vector.arrowHead != stats::StatisticsType::ArrowHead::none) { // We draw an arrow head. This means that we will have to draw a shortened line - const int shorten = (statisticsType.arrowHead == stats::StatisticsType::ArrowHead::arrow) + const int shorten = (vector.arrowHead == stats::StatisticsType::ArrowHead::arrow) ? headSize * 2 : headSize * 0.5; @@ -166,7 +169,7 @@ void paintVector(QPainter * painter, // Draw the not shortened line painter->drawLine(x1, y1, x2, y2); - if (statisticsType.arrowHead == stats::StatisticsType::ArrowHead::arrow) + if (vector.arrowHead == stats::StatisticsType::ArrowHead::arrow) { // Save the painter state, translate to the arrow tip, rotate the painter and draw the // normal triangle. @@ -182,11 +185,11 @@ void paintVector(QPainter * painter, // Restore. Revert translation/rotation of the painter. painter->restore(); } - else if (statisticsType.arrowHead == stats::StatisticsType::ArrowHead::circle) + else if (vector.arrowHead == stats::StatisticsType::ArrowHead::circle) painter->drawEllipse(x2 - headSize / 2, y2 - headSize / 2, headSize, headSize); } - if (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM && statisticsType.renderVectorDataValues) + if (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM && vector.renderDataValues) { if (isLine) { @@ -255,7 +258,7 @@ void paintVector(QPainter * painter, } // namespace -void stats::paintStatisticsData(QPainter * painter, +void stats::paintStatisticsData(QPainter *painter, stats::StatisticsData &statisticsData, int frameIndex, double zoomFactor) @@ -293,7 +296,7 @@ void stats::paintStatisticsData(QPainter * painter, bool oneBlockStatRendered = false; for (const auto &type : statsTypes) { - if (type.render && type.hasValueData) + if (type.render && type.valueDataOptions) { if (oneBlockStatRendered) { @@ -336,16 +339,16 @@ void stats::paintStatisticsData(QPainter * painter, continue; int value = valueItem.value; // This value determines the color for this item - if (it->renderValueData) + if (it->valueDataOptions && it->valueDataOptions->render) { // Get the right color for the item and draw it. Color rectColor; - if (it->scaleValueToBlockSize) - rectColor = - it->colorMapper.getColor(float(value) / (valueItem.size[0] * valueItem.size[1])); + if (it->valueDataOptions->scaleToBlockSize) + rectColor = it->valueDataOptions->colorMapper->getColor( + float(value) / (valueItem.size[0] * valueItem.size[1])); else - rectColor = it->colorMapper.getColor(value); - rectColor.setAlpha(rectColor.alpha() * ((float)it->alphaFactor / 100.0)); + rectColor = it->valueDataOptions->colorMapper->getColor(value); + rectColor.setAlpha(functions::scaleValueByPercent(rectColor.alpha(), *it->alphaFactor)); auto rectQColor = functionsGui::toQColor(rectColor); painter->setBrush(rectQColor); @@ -353,19 +356,19 @@ void stats::paintStatisticsData(QPainter * painter, } // optionally, draw a grid around the region - if (it->renderGrid) + if (it->gridOptions.render) { // Set the grid color (no fill) - auto gridStyle = it->gridStyle; - if (it->scaleGridToZoom) - gridStyle.width = gridStyle.width * zoomFactor; + auto gridStyle = it->gridOptions.style; + if (it->gridOptions.scaleToZoom) + gridStyle->width = gridStyle->width * zoomFactor; - painter->setPen(styleToPen(gridStyle)); + painter->setPen(styleToPen(*gridStyle)); painter->setBrush(QBrush(QColor(Qt::color0), Qt::NoBrush)); // no fill color // Save the line width (if thicker) - if (gridStyle.width > maxLineWidth) - maxLineWidth = gridStyle.width; + if (gridStyle->width > maxLineWidth) + maxLineWidth = gridStyle->width; painter->drawRect(displayRect); } @@ -373,9 +376,9 @@ void stats::paintStatisticsData(QPainter * painter, // Save the position/text in order to draw the values later if (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM) { - auto valTxt = it->getValueTxt(value); - if (valTxt.isEmpty() && it->scaleValueToBlockSize) - valTxt = QString("%1").arg(float(value) / (valueItem.size[0] * valueItem.size[1])); + auto valTxt = it->getValueText(value); + if (valTxt.empty() && it->valueDataOptions && it->valueDataOptions->scaleToBlockSize) + valTxt = std::to_string(float(value) / (valueItem.size[0] * valueItem.size[1])); auto typeTxt = it->typeName; auto statTxt = moreThanOneBlockStatRendered ? typeTxt + ":" + valTxt : valTxt; @@ -385,11 +388,11 @@ void stats::paintStatisticsData(QPainter * painter, { // No value for this point yet. Append it and start a new QStringList drawStatPoints.append(displayRect.topLeft()); - drawStatTexts.append(QStringList(statTxt)); + drawStatTexts.append(QStringList(QString::fromStdString(statTxt))); } else // There is already a value for this point. Just append the text. - drawStatTexts[i].append(statTxt); + drawStatTexts[i].append(QString::fromStdString(statTxt)); } } } @@ -423,16 +426,18 @@ void stats::paintStatisticsData(QPainter * painter, if (isVisible) { int value = valueItem.value; // This value determines the color for this item - if (it->renderValueData) + if (it->valueDataOptions && it->valueDataOptions->render) { + const auto &valueOptions = it->valueDataOptions.value(); + // Get the right color for the item and draw it. Color color; - if (it->scaleValueToBlockSize) - color = it->colorMapper.getColor( + if (valueOptions.scaleToBlockSize) + color = valueOptions.colorMapper->getColor( float(value) / (boundingRect.size().width() * boundingRect.size().height())); else - color = it->colorMapper.getColor(value); - color.setAlpha(color.alpha() * ((float)it->alphaFactor / 100.0)); + color = valueOptions.colorMapper->getColor(value); + color.setAlpha(functions::scaleValueByPercent(color.alpha(), *it->alphaFactor)); // Fill polygon QPainterPath path; @@ -444,19 +449,19 @@ void stats::paintStatisticsData(QPainter * painter, } // optionally, draw a grid around the region - if (it->renderGrid) + if (it->gridOptions.render) { // Set the grid color (no fill) - auto gridStyle = it->gridStyle; - if (it->scaleGridToZoom) - gridStyle.width = gridStyle.width * zoomFactor; + auto gridStyle = it->gridOptions.style; + if (it->gridOptions.scaleToZoom) + gridStyle->width = gridStyle->width * zoomFactor; painter->setPen(styleToPen(gridStyle)); painter->setBrush(QBrush(QColor(Qt::color0), Qt::NoBrush)); // no fill color // Save the line width (if thicker) - if (gridStyle.width > maxLineWidth) - maxLineWidth = gridStyle.width; + if (gridStyle->width > maxLineWidth) + maxLineWidth = gridStyle->width; painter->drawPolygon(displayPolygon); } @@ -465,7 +470,7 @@ void stats::paintStatisticsData(QPainter * painter, // // Save the position/text in order to draw the values later if (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM) { - auto valTxt = it->getValueTxt(value); + auto valTxt = it->getValueText(value); auto typeTxt = it->typeName; auto statTxt = moreThanOneBlockStatRendered ? typeTxt + ":" + valTxt : valTxt; @@ -474,17 +479,17 @@ void stats::paintStatisticsData(QPainter * painter, { // No value for this point yet. Append it and start a new QStringList drawStatPoints.append(getPolygonCenter(displayPolygon)); - drawStatTexts.append(QStringList(statTxt)); + drawStatTexts.append(QStringList(QString::fromStdString(statTxt))); } else // There is already a value for this point. Just append the text. - drawStatTexts[i].append(statTxt); + drawStatTexts[i].append(QString::fromStdString(statTxt)); } } } } - // Step three: Draw the values of the block types + // Step three: Draw the values of the block types∆ if (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM) { // For every point, draw only one block of values. So for every point, we check if there are @@ -517,8 +522,10 @@ void stats::paintStatisticsData(QPainter * painter, rect.width() * zoomFactor, rect.height() * zoomFactor); - if (it->renderVectorData) + if (it->vectorDataOptions && it->vectorDataOptions->render) { + const auto &vectorOptions = it->vectorDataOptions.value(); + // Calculate the start and end point of the arrow. The vector starts at center of the block. int x1, y1, x2, y2; float vx, vy; @@ -528,8 +535,8 @@ void stats::paintStatisticsData(QPainter * painter, y1 = displayRect.top() + zoomFactor * vectorItem.point[0].y; x2 = displayRect.left() + zoomFactor * vectorItem.point[1].x; y2 = displayRect.top() + zoomFactor * vectorItem.point[1].y; - vx = (float)(x2 - x1) / it->vectorScale; - vy = (float)(y2 - y1) / it->vectorScale; + vx = (float)(x2 - x1) / vectorOptions.scale; + vy = (float)(y2 - y1) / vectorOptions.scale; } else { @@ -537,8 +544,8 @@ void stats::paintStatisticsData(QPainter * painter, y1 = displayRect.top() + displayRect.height() / 2; // The length of the vector - vx = (float)vectorItem.point[0].x / it->vectorScale; - vy = (float)vectorItem.point[0].y / it->vectorScale; + vx = (float)vectorItem.point[0].x / vectorOptions.scale; + vy = (float)vectorItem.point[0].y / vectorOptions.scale; // The end point of the vector x2 = x1 + zoomFactor * vx; @@ -552,17 +559,17 @@ void stats::paintStatisticsData(QPainter * painter, if (arrowVisible) { // Set the pen for drawing - auto vectorStyle = it->vectorStyle; - auto arrowColor = functionsGui::toQColor(vectorStyle.color); - if (it->mapVectorToColor) + auto vectorStyle = it->vectorDataOptions->style; + auto arrowColor = functionsGui::toQColor(vectorStyle->color); + if (it->vectorDataOptions->mapToColor) arrowColor.setHsvF( functions::clip((std::atan2(vy, vx) + M_PI) / (2 * M_PI), 0.0, 1.0), 1.0, 1.0); - arrowColor.setAlpha(arrowColor.alpha() * ((float)it->alphaFactor / 100.0)); - if (it->scaleVectorToZoom) - vectorStyle.width = vectorStyle.width * zoomFactor / 8; + arrowColor.setAlpha(functions::scaleValueByPercent(arrowColor.alpha(), *it->alphaFactor)); + if (it->vectorDataOptions->scaleToZoom) + vectorStyle->width = vectorStyle->width * zoomFactor / 8; painter->setPen( - QPen(arrowColor, vectorStyle.width, patternToQPenStyle(vectorStyle.pattern))); + QPen(arrowColor, vectorStyle->width, patternToQPenStyle(vectorStyle->pattern))); painter->setBrush(arrowColor); // Draw the arrow tip, or a circle if the vector is (0,0) if the zoom factor is not 1 or @@ -579,16 +586,16 @@ void stats::paintStatisticsData(QPainter * painter, { // The size of the arrow head const int headSize = - (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM && !it->scaleVectorToZoom) + (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM && !it->vectorDataOptions->scaleToZoom) ? 8 : zoomFactor / 2; - if (it->arrowHead != StatisticsType::ArrowHead::none) + const auto &arrowHead = it->vectorDataOptions->arrowHead; + if (arrowHead != StatisticsType::ArrowHead::none) { // We draw an arrow head. This means that we will have to draw a shortened line - const int shorten = (it->arrowHead == StatisticsType::ArrowHead::arrow) - ? headSize * 2 - : headSize * 0.5; + const int shorten = + (arrowHead == StatisticsType::ArrowHead::arrow) ? headSize * 2 : headSize * 0.5; if (std::sqrt(vx * vx * zoomFactor * zoomFactor + vy * vy * zoomFactor * zoomFactor) > shorten) { @@ -604,7 +611,7 @@ void stats::paintStatisticsData(QPainter * painter, // Draw the not shortened line painter->drawLine(x1, y1, x2, y2); - if (it->arrowHead == StatisticsType::ArrowHead::arrow) + if (arrowHead == StatisticsType::ArrowHead::arrow) { // Save the painter state, translate to the arrow tip, rotate the painter and draw // the normal triangle. @@ -621,11 +628,12 @@ void stats::paintStatisticsData(QPainter * painter, // Restore. Revert translation/rotation of the painter. painter->restore(); } - else if (it->arrowHead == StatisticsType::ArrowHead::circle) + else if (arrowHead == StatisticsType::ArrowHead::circle) painter->drawEllipse(x2 - headSize / 2, y2 - headSize / 2, headSize, headSize); } - if (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM && it->renderVectorDataValues) + if (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM && + it->vectorDataOptions->renderDataValues) { if (vectorItem.isLine) { @@ -699,11 +707,11 @@ void stats::paintStatisticsData(QPainter * painter, if (rectVisible) { // optionally, draw a grid around the region that the arrow is defined for - if (it->renderGrid && rectVisible) + if (it->gridOptions.render && rectVisible) { - auto gridStyle = it->gridStyle; - if (it->scaleGridToZoom) - gridStyle.width = gridStyle.width * zoomFactor; + auto gridStyle = it->gridOptions.style; + if (it->gridOptions.scaleToZoom) + gridStyle->width = gridStyle->width * zoomFactor; painter->setPen(styleToPen(gridStyle)); painter->setBrush(QBrush(QColor(Qt::color0), Qt::NoBrush)); // no fill color @@ -729,7 +737,7 @@ void stats::paintStatisticsData(QPainter * painter, if (!rectVisible) continue; - if (it->renderVectorData) + if (it->vectorDataOptions && it->vectorDataOptions->render) { // affine vectors start at bottom left, top left and top right of the block // mv0: LT, mv1: RT, mv2: LB @@ -745,12 +753,13 @@ void stats::paintStatisticsData(QPainter * painter, yLBstart = displayRect.bottom(); // The length of the vectors - vxLT = (float)affineTFItem.point[0].x / it->vectorScale; - vyLT = (float)affineTFItem.point[0].y / it->vectorScale; - vxRT = (float)affineTFItem.point[1].x / it->vectorScale; - vyRT = (float)affineTFItem.point[1].y / it->vectorScale; - vxLB = (float)affineTFItem.point[2].x / it->vectorScale; - vyLB = (float)affineTFItem.point[2].y / it->vectorScale; + const auto scale = it->vectorDataOptions->scale; + vxLT = (float)affineTFItem.point[0].x / scale; + vyLT = (float)affineTFItem.point[0].y / scale; + vxRT = (float)affineTFItem.point[1].x / scale; + vyRT = (float)affineTFItem.point[1].y / scale; + vxLB = (float)affineTFItem.point[2].x / scale; + vyLB = (float)affineTFItem.point[2].y / scale; // The end point of the vectors xLTend = xLTstart + zoomFactor * vxLT; @@ -805,11 +814,11 @@ void stats::paintStatisticsData(QPainter * painter, } // optionally, draw a grid around the region that the arrow is defined for - if (it->renderGrid && rectVisible) + if (it->gridOptions.render && rectVisible) { - auto gridStyle = it->gridStyle; - if (it->scaleGridToZoom) - gridStyle.width = gridStyle.width * zoomFactor; + auto gridStyle = it->gridOptions.style; + if (it->gridOptions.scaleToZoom) + gridStyle->width = gridStyle->width * zoomFactor; painter->setPen(styleToPen(gridStyle)); painter->setBrush(QBrush(QColor(Qt::color0), Qt::NoBrush)); // no fill color @@ -844,7 +853,7 @@ void stats::paintStatisticsData(QPainter * painter, if (!isVisible) continue; - if (it->renderVectorData) + if (it->vectorDataOptions && it->vectorDataOptions->render) { // start vector at center of the block int center_x, center_y, head_x, head_y; @@ -861,8 +870,9 @@ void stats::paintStatisticsData(QPainter * painter, center_y /= displayPolygon.size(); // The length of the vector - vx = (float)vectorItem.point.x / it->vectorScale; - vy = (float)vectorItem.point.y / it->vectorScale; + const auto scale = it->vectorDataOptions->scale; + vx = (float)vectorItem.point.x / scale; + vy = (float)vectorItem.point.y / scale; // The end point of the vector head_x = center_x + zoomFactor * vx; @@ -873,17 +883,17 @@ void stats::paintStatisticsData(QPainter * painter, !(center_y < yMin && head_y < yMin) && !(center_y > yMax && head_y > yMax)) { // Set the pen for drawing - auto vectorStyle = it->vectorStyle; - auto arrowColor = functionsGui::toQColor(vectorStyle.color); - if (it->mapVectorToColor) + auto vectorStyle = it->vectorDataOptions->style; + auto arrowColor = functionsGui::toQColor(vectorStyle->color); + if (it->vectorDataOptions->mapToColor) arrowColor.setHsvF( functions::clip((std::atan2(vy, vx) + M_PI) / (2 * M_PI), 0.0, 1.0), 1.0, 1.0); - arrowColor.setAlpha(arrowColor.alpha() * ((float)it->alphaFactor / 100.0)); - if (it->scaleVectorToZoom) - vectorStyle.width = vectorStyle.width * zoomFactor / 8; + arrowColor.setAlpha(functions::scaleValueByPercent(arrowColor.alpha(), *it->alphaFactor)); + if (it->vectorDataOptions->scaleToZoom) + vectorStyle->width = vectorStyle->width * zoomFactor / 8; painter->setPen( - QPen(arrowColor, vectorStyle.width, patternToQPenStyle(vectorStyle.pattern))); + QPen(arrowColor, vectorStyle->width, patternToQPenStyle(vectorStyle->pattern))); painter->setBrush(arrowColor); // Draw the arrow tip, or a circle if the vector is (0,0) if the zoom factor is not 1 or @@ -900,15 +910,16 @@ void stats::paintStatisticsData(QPainter * painter, { // The size of the arrow head const int headSize = - (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM && !it->scaleVectorToZoom) + (zoomFactor >= STATISTICS_DRAW_VALUES_ZOOM && !it->vectorDataOptions->scaleToZoom) ? 8 : zoomFactor / 2; - if (it->arrowHead != StatisticsType::ArrowHead::none) + + const auto &arrowHead = it->vectorDataOptions->arrowHead; + if (arrowHead != StatisticsType::ArrowHead::none) { // We draw an arrow head. This means that we will have to draw a shortened line - const int shorten = (it->arrowHead == StatisticsType::ArrowHead::arrow) - ? headSize * 2 - : headSize * 0.5; + const int shorten = + (arrowHead == StatisticsType::ArrowHead::arrow) ? headSize * 2 : headSize * 0.5; if (std::sqrt(vx * vx * zoomFactor * zoomFactor + vy * vy * zoomFactor * zoomFactor) > shorten) { @@ -924,7 +935,7 @@ void stats::paintStatisticsData(QPainter * painter, // Draw the not shortened line painter->drawLine(center_x, center_y, head_x, head_y); - if (it->arrowHead == StatisticsType::ArrowHead::arrow) + if (arrowHead == StatisticsType::ArrowHead::arrow) { // Save the painter state, translate to the arrow tip, rotate the painter and draw // the normal triangle. @@ -941,7 +952,7 @@ void stats::paintStatisticsData(QPainter * painter, // Restore. Revert translation/rotation of the painter. painter->restore(); } - else if (it->arrowHead == StatisticsType::ArrowHead::circle) + else if (arrowHead == StatisticsType::ArrowHead::circle) painter->drawEllipse( head_x - headSize / 2, head_y - headSize / 2, headSize, headSize); } @@ -976,11 +987,11 @@ void stats::paintStatisticsData(QPainter * painter, } // optionally, draw the polygon outline - if (it->renderGrid && isVisible) + if (it->gridOptions.render && isVisible) { - auto gridStyle = it->gridStyle; - if (it->scaleGridToZoom) - gridStyle.width = gridStyle.width * zoomFactor; + auto gridStyle = it->gridOptions.style; + if (it->gridOptions.scaleToZoom) + gridStyle->width = gridStyle->width * zoomFactor; painter->setPen(styleToPen(gridStyle)); painter->setBrush(QBrush(QColor(Qt::color0), Qt::NoBrush)); // no fill color diff --git a/YUViewLib/src/statistics/StatisticsFileCSV.cpp b/YUViewLib/src/statistics/StatisticsFileCSV.cpp index 7eeeb2272..07106a3db 100644 --- a/YUViewLib/src/statistics/StatisticsFileCSV.cpp +++ b/YUViewLib/src/statistics/StatisticsFileCSV.cpp @@ -32,6 +32,8 @@ #include "StatisticsFileCSV.h" +#include + #include #include @@ -320,9 +322,9 @@ void StatisticsFileCSV::loadStatisticData(StatisticsData &statisticsData, int po [type](StatisticsType &t) { return t.typeID == type; }); Q_ASSERT_X(statIt != statTypes.end(), Q_FUNC_INFO, "Stat type not found."); - if (vectorData && statIt->hasVectorData) + if (vectorData && statIt->vectorDataOptions) statisticsData[type].addBlockVector(posX, posY, width, height, values[0], values[1]); - else if (lineData && statIt->hasVectorData) + else if (lineData && statIt->vectorDataOptions) statisticsData[type].addLine( posX, posY, width, height, values[0], values[1], values[2], values[3]); else @@ -354,11 +356,16 @@ void StatisticsFileCSV::readHeaderFromFile(StatisticsData &statisticsData) statisticsData.clear(); - // scan header lines first - // also count the lines per Frame for more efficient memory allocation - // if an ID is used twice, the data of the first gets overwritten - bool typeParsingActive = false; - StatisticsType aType; + struct ParsedData + { + int typeID{}; + std::string typeName{}; + + std::optional valueDataOptions; + std::optional vectorDataOptions; + StatisticsType::GridOptions gridOptions; + }; + std::optional type; while (!this->file.atEnd()) { @@ -373,44 +380,43 @@ void StatisticsFileCSV::readHeaderFromFile(StatisticsData &statisticsData) continue; // either a new type or a line which is not header finishes the last type - if (((rowItemList[1] == "type") || (rowItemList[0][0] != '%')) && typeParsingActive) + if (((rowItemList[1] == "type") || (rowItemList[0][0] != '%')) && type) { // Last type is complete. Store this initial state. - aType.setInitialState(); - statisticsData.addStatType(aType); + statisticsData.addStatType(StatisticsTypeBuilder(type->typeID, type->typeName) + .withOptionalValueDataOptions(type->valueDataOptions) + .withOptionalVectorDataOptions(type->vectorDataOptions) + .withGridOptions(type->gridOptions) + .build()); - // start from scratch for next item - aType = StatisticsType(); - typeParsingActive = false; + type.reset(); // if we found a non-header line, stop here if (rowItemList[0][0] != '%') return; } - if (rowItemList[1] == "type") // new type + if (rowItemList[1] == "type") { - aType.typeID = rowItemList[2].toInt(); - aType.typeName = rowItemList[3]; + // Start of a new type + type.emplace(); + type->typeID = rowItemList[2].toInt(); + type->typeName = rowItemList[3].toStdString(); // The next entry (4) is "map", "range", or "vector" if (rowItemList.count() >= 5) { if (rowItemList[4] == "map" || rowItemList[4] == "range") { - aType.hasValueData = true; - aType.renderValueData = true; + type->valueDataOptions = StatisticsType::ValueDataOptions(); } else if (rowItemList[4] == "vector" || rowItemList[4] == "line") { - aType.hasVectorData = true; - aType.renderVectorData = true; + type->vectorDataOptions = StatisticsType::VectorDataOptions(); if (rowItemList[4] == "line") - aType.arrowHead = StatisticsType::ArrowHead::none; + type->vectorDataOptions->arrowHead = StatisticsType::ArrowHead::none; } } - - typeParsingActive = true; } else if (rowItemList[1] == "mapColor") { @@ -422,8 +428,8 @@ void StatisticsFileCSV::readHeaderFromFile(StatisticsData &statisticsData) auto b = (unsigned char)rowItemList[5].toInt(); auto a = (unsigned char)rowItemList[6].toInt(); - aType.colorMapper.mappingType = color::MappingType::Map; - aType.colorMapper.colorMap[id] = Color(r, g, b, a); + type->valueDataOptions->colorMapper->mappingType = color::MappingType::Map; + type->valueDataOptions->colorMapper->colorMap[id] = Color(r, g, b, a); } else if (rowItemList[1] == "range") { @@ -442,7 +448,7 @@ void StatisticsFileCSV::readHeaderFromFile(StatisticsData &statisticsData) a = rowItemList[11].toInt(); auto maxColor = Color(r, g, b, a); - aType.colorMapper = color::ColorMapper({min, max}, minColor, maxColor); + type->valueDataOptions->colorMapper = color::ColorMapper({min, max}, minColor, maxColor); } else if (rowItemList[1] == "defaultRange") { @@ -451,31 +457,31 @@ void StatisticsFileCSV::readHeaderFromFile(StatisticsData &statisticsData) int max = rowItemList[3].toInt(); auto rangeName = rowItemList[4].toStdString(); - aType.colorMapper = color::ColorMapper({min, max}, rangeName); + type->valueDataOptions->colorMapper = color::ColorMapper({min, max}, rangeName); } else if (rowItemList[1] == "vectorColor") { - auto r = (unsigned char)rowItemList[2].toInt(); - auto g = (unsigned char)rowItemList[3].toInt(); - auto b = (unsigned char)rowItemList[4].toInt(); - auto a = (unsigned char)rowItemList[5].toInt(); - aType.vectorStyle.color = Color(r, g, b, a); + auto r = (unsigned char)rowItemList[2].toInt(); + auto g = (unsigned char)rowItemList[3].toInt(); + auto b = (unsigned char)rowItemList[4].toInt(); + auto a = (unsigned char)rowItemList[5].toInt(); + type->vectorDataOptions->style->color = Color(r, g, b, a); } else if (rowItemList[1] == "gridColor") { - auto r = (unsigned char)rowItemList[2].toInt(); - auto g = (unsigned char)rowItemList[3].toInt(); - auto b = (unsigned char)rowItemList[4].toInt(); - auto a = 255; - aType.gridStyle.color = Color(r, g, b, a); + auto r = (unsigned char)rowItemList[2].toInt(); + auto g = (unsigned char)rowItemList[3].toInt(); + auto b = (unsigned char)rowItemList[4].toInt(); + auto a = 255; + type->gridOptions.style->color = Color(r, g, b, a); } else if (rowItemList[1] == "scaleFactor") { - aType.vectorScale = rowItemList[2].toInt(); + type->vectorDataOptions->scale = rowItemList[2].toInt(); } else if (rowItemList[1] == "scaleToBlockSize") { - aType.scaleValueToBlockSize = (rowItemList[2] == "1"); + type->valueDataOptions->scaleToBlockSize = (rowItemList[2] == "1"); } else if (rowItemList[1] == "seq-specs") { diff --git a/YUViewLib/src/statistics/StatisticsFileVTMBMS.cpp b/YUViewLib/src/statistics/StatisticsFileVTMBMS.cpp index 5f01aaf27..075a9c09b 100644 --- a/YUViewLib/src/statistics/StatisticsFileVTMBMS.cpp +++ b/YUViewLib/src/statistics/StatisticsFileVTMBMS.cpp @@ -185,373 +185,10 @@ void StatisticsFileVTMBMS::readFrameAndTypePositionsFromFile(std::atomic_bool &b void StatisticsFileVTMBMS::loadStatisticData(StatisticsData &statisticsData, int poc, int typeID) { - if (!this->file.isOk()) - return; - - try - { - statisticsData.setFrameIndex(poc); - - std::unique_lock lock(statisticsData.accessMutex); - - if (this->pocStartList.count(poc) == 0) - { - // There are no statistics in the file for the given frame and index. - statisticsData[typeID] = {}; - return; - } - - auto startPos = this->pocStartList[poc]; - - QTextStream in(this->file.getQFile()); - in.seek(startPos); - - QRegularExpression pocRegex("BlockStat: POC ([0-9]+)"); - - // prepare regex for selected type - auto &statTypes = statisticsData.getStatisticsTypes(); - auto statIt = std::find_if(statTypes.begin(), - statTypes.end(), - [typeID](StatisticsType &t) { return t.typeID == typeID; }); - Q_ASSERT_X(statIt != statTypes.end(), Q_FUNC_INFO, "Stat type not found."); - QRegularExpression typeRegex(" " + statIt->typeName + "="); // for catching lines of the type - - // for extracting scalar value statistics, need to match: - // BlockStat: POC 1 @( 112, 88) [ 8x 8] PredMode=0 - QRegularExpression scalarRegex( - "POC ([0-9]+) @\\( *([0-9]+), *([0-9]+)\\) *\\[ *([0-9]+)x *([0-9]+)\\] *\\w+=([0-9\\-]+)"); - // for extracting vector value statistics, need to match: - // BlockStat: POC 1 @( 120, 80) [ 8x 8] MVL0={ -24, -2} - QRegularExpression vectorRegex("POC ([0-9]+) @\\( *([0-9]+), *([0-9]+)\\) *\\[ *([0-9]+)x " - "*([0-9]+)\\] *\\w+={ *([0-9\\-]+), *([0-9\\-]+)}"); - // for extracting affine transform value statistics, need to match: - // BlockStat: POC 2 @( 192, 96) [64x32] AffineMVL0={-324,-116,-276,-116,-324, -92} - QRegularExpression affineTFRegex( - "POC ([0-9]+) @\\( *([0-9]+), *([0-9]+)\\) *\\[ *([0-9]+)x *([0-9]+)\\] *\\w+={ " - "*([0-9\\-]+), *([0-9\\-]+), *([0-9\\-]+), *([0-9\\-]+), *([0-9\\-]+), *([0-9\\-]+)}"); - // for extracting scalar polygon statistics, need to match: - // BlockStat: POC 2 @[(505, 384)--(511, 384)--(511, 415)--] GeoPUInterIntraFlag=0 - // BlockStat: POC 2 @[(416, 448)--(447, 448)--(447, 478)--(416, 463)--] GeoPUInterIntraFlag=0 - // will capture 3-5 points. other polygons are not supported - QRegularExpression scalarPolygonRegex( - "POC ([0-9]+) @\\[((?:\\( *[0-9]+, *[0-9]+\\)--){3,5})\\] *\\w+=([0-9\\-]+)"); - // for extracting vector polygon statistics: - QRegularExpression vectorPolygonRegex( - "POC ([0-9]+) @\\[((?:\\( *[0-9]+, *[0-9]+\\)--){3,5})\\] *\\w+={ *([0-9\\-]+), " - "*([0-9\\-]+)}"); - // for extracting the partitioning line, we extract - // BlockStat: POC 2 @( 192, 96) [64x32] Line={0,0,31,31} - QRegularExpression lineRegex( - "POC ([0-9]+) @\\( *([0-9]+), *([0-9]+)\\) *\\[ *([0-9]+)x *([0-9]+)\\] *\\w+={ " - "*([0-9\\-]+), *([0-9\\-]+), *([0-9\\-]+), *([0-9\\-]+)}"); - - while (!in.atEnd()) - { - // read one line - auto aLine = in.readLine(); - auto pocMatch = pocRegex.match(aLine); - // ignore not matching lines - if (pocMatch.hasMatch()) - { - auto pocRow = pocMatch.captured(1).toInt(); - if (poc != pocRow) - break; - - // filter lines of different types - auto typeMatch = typeRegex.match(aLine); - if (typeMatch.hasMatch()) - { - int posX, posY, scalar, vecX, vecY; - unsigned width, height; - - QRegularExpressionMatch statisitcMatch; - // extract statistics info - // try block types - if (statIt->isPolygon == false) - { - if (statIt->hasValueData) - statisitcMatch = scalarRegex.match(aLine); - else if (statIt->hasVectorData) - { - statisitcMatch = vectorRegex.match(aLine); - if (!statisitcMatch.hasMatch()) - statisitcMatch = lineRegex.match(aLine); - } - else if (statIt->hasAffineTFData) - statisitcMatch = affineTFRegex.match(aLine); - } - else - // try polygons - { - if (statIt->hasValueData) - statisitcMatch = scalarPolygonRegex.match(aLine); - else if (statIt->hasVectorData) - statisitcMatch = vectorPolygonRegex.match(aLine); - } - if (!statisitcMatch.hasMatch()) - { - this->errorMessage = QString("Error while parsing statistic: ") + QString(aLine); - continue; - } - - // useful for debugging: - // QStringList all_captured = statisitcMatch.capturedTexts(); - - pocRow = statisitcMatch.captured(1).toInt(); - width = statisitcMatch.captured(4).toUInt(); - height = statisitcMatch.captured(5).toUInt(); - // if there is a new POC, we are done here! - if (poc != pocRow) - break; - - // process block statistics - if (statIt->isPolygon == false) - { - posX = statisitcMatch.captured(2).toInt(); - posY = statisitcMatch.captured(3).toInt(); - - // Check if block is within the image range - if (blockOutsideOfFramePOC == -1 && - (posX + int(width) > int(statisticsData.getFrameSize().width) || - posY + int(height) > int(statisticsData.getFrameSize().height))) - // Block not in image. Warn about this. - blockOutsideOfFramePOC = poc; - - if (statIt->hasVectorData) - { - vecX = statisitcMatch.captured(6).toInt(); - vecY = statisitcMatch.captured(7).toInt(); - if (statisitcMatch.lastCapturedIndex() > 7) - { - auto vecX1 = statisitcMatch.captured(8).toInt(); - auto vecY1 = statisitcMatch.captured(9).toInt(); - statisticsData[typeID].addLine(posX, posY, width, height, vecX, vecY, vecX1, vecY1); - } - else - { - statisticsData[typeID].addBlockVector(posX, posY, width, height, vecX, vecY); - } - } - else if (statIt->hasAffineTFData) - { - auto vecX0 = statisitcMatch.captured(6).toInt(); - auto vecY0 = statisitcMatch.captured(7).toInt(); - auto vecX1 = statisitcMatch.captured(8).toInt(); - auto vecY1 = statisitcMatch.captured(9).toInt(); - auto vecX2 = statisitcMatch.captured(10).toInt(); - auto vecY2 = statisitcMatch.captured(11).toInt(); - statisticsData[typeID].addBlockAffineTF( - posX, posY, width, height, vecX0, vecY0, vecX1, vecY1, vecX2, vecY2); - } - else - { - scalar = statisitcMatch.captured(6).toInt(); - statisticsData[typeID].addBlockValue(posX, posY, width, height, scalar); - } - } - else - // process polygon statistics - { - auto corners = statisitcMatch.captured(2); - auto cornerList = corners.split("--"); - QRegularExpression cornerRegex("\\( *([0-9]+), *([0-9]+)\\)"); - stats::Polygon points; - for (const auto &corner : cornerList) - { - auto cornerMatch = cornerRegex.match(corner); - if (cornerMatch.hasMatch()) - { - auto x = cornerMatch.captured(1).toInt(); - auto y = cornerMatch.captured(2).toInt(); - points.push_back({x, y}); - - // Check if polygon is within the image range - if (this->blockOutsideOfFramePOC == -1 && - (x + width > statisticsData.getFrameSize().width || - y + height > statisticsData.getFrameSize().height)) - // Block not in image. Warn about this. - this->blockOutsideOfFramePOC = poc; - } - } - - if (statIt->hasVectorData) - { - vecX = statisitcMatch.captured(3).toInt(); - vecY = statisitcMatch.captured(4).toInt(); - statisticsData[typeID].addPolygonVector(points, vecX, vecY); - } - else if (statIt->hasValueData) - { - scalar = statisitcMatch.captured(3).toInt(); - statisticsData[typeID].addPolygonValue(points, scalar); - } - } - } - } - } - - if (!statisticsData.hasDataForTypeID(typeID)) - { - // There are no statistics in the file for the given frame and index. - statisticsData[typeID] = {}; - return; - } - - } // try - catch (const char *str) - { - std::cerr << "Error while parsing: " << str << '\n'; - this->errorMessage = QString("Error while parsing meta data: ") + QString(str); - return; - } - catch (...) - { - std::cerr << "Error while parsing."; - this->errorMessage = QString("Error while parsing meta data."); - return; - } - - return; } void StatisticsFileVTMBMS::readHeaderFromFile(StatisticsData &statisticsData) { - try - { - if (!this->file.isOk()) - return; - - statisticsData.clear(); - - while (!this->file.atEnd()) - { - // read one line - auto aLineByteArray = this->file.readLine(); - QString aLine(aLineByteArray); - - // if we found a non-header line, stop here - if (aLine[0] != '#') - return; - - // extract statistics information from header lines - // match: - // # Sequence size: [832x 480] - QRegularExpression sequenceSizeRegex("# Sequence size: \\[([0-9]+)x *([0-9]+)\\]"); - - // match: - // # Block Statistic Type: MergeFlag; Flag - QRegularExpression availableStatisticsRegex( - "# Block Statistic Type: *([0-9a-zA-Z_]+); *([0-9a-zA-Z]+); *(.*)"); - - // get sequence size - auto sequenceSizeMatch = sequenceSizeRegex.match(aLine); - if (sequenceSizeMatch.hasMatch()) - { - statisticsData.setFrameSize( - Size(sequenceSizeMatch.captured(1).toInt(), sequenceSizeMatch.captured(2).toInt())); - } - - // get available statistics - auto availableStatisticsMatch = availableStatisticsRegex.match(aLine); - if (availableStatisticsMatch.hasMatch()) - { - StatisticsType aType; - // Store initial state. - aType.setInitialState(); - - // set name - aType.typeName = availableStatisticsMatch.captured(1); - - // with -1, an id will be automatically assigned - aType.typeID = -1; - - // check if scalar or vector - auto statType = availableStatisticsMatch.captured(2); - if (statType.contains( - "AffineTFVectors")) // "Vector" is contained in this, need to check it first - { - auto scaleInfo = availableStatisticsMatch.captured(3); - QRegularExpression scaleInfoRegex("Scale: *([0-9]+)"); - auto scaleInfoMatch = scaleInfoRegex.match(scaleInfo); - int scale; - if (scaleInfoMatch.hasMatch()) - scale = scaleInfoMatch.captured(1).toInt(); - else - scale = 1; - - aType.hasAffineTFData = true; - aType.renderVectorData = true; - aType.vectorScale = scale; - aType.vectorStyle.color = Color(255, 0, 0); - } - else if (statType.contains("Vector")) - { - auto scaleInfo = availableStatisticsMatch.captured(3); - QRegularExpression scaleInfoRegex("Scale: *([0-9]+)"); - auto scaleInfoMatch = scaleInfoRegex.match(scaleInfo); - int scale; - if (scaleInfoMatch.hasMatch()) - scale = scaleInfoMatch.captured(1).toInt(); - else - scale = 1; - - aType.hasVectorData = true; - aType.renderVectorData = true; - aType.vectorScale = scale; - aType.vectorStyle.color = Color(255, 0, 0); - } - else if (statType.contains("Flag")) - { - aType.hasValueData = true; - aType.renderValueData = true; - aType.colorMapper = color::ColorMapper({0, 1}, color::PredefinedType::Jet); - } - else if (statType.contains("Integer")) // for now do the same as for Flags - { - auto rangeInfo = availableStatisticsMatch.captured(3); - QRegularExpression rangeInfoRegex("\\[([0-9\\-]+), *([0-9\\-]+)\\]"); - auto rangeInfoMatch = rangeInfoRegex.match(rangeInfo); - int minVal = 0; - int maxVal = 100; - if (rangeInfoMatch.hasMatch()) - { - minVal = rangeInfoMatch.captured(1).toInt(); - maxVal = rangeInfoMatch.captured(2).toInt(); - } - - aType.hasValueData = true; - aType.renderValueData = true; - aType.colorMapper = color::ColorMapper({minVal, maxVal}, color::PredefinedType::Jet); - } - else if (statType.contains("Line")) - { - aType.hasVectorData = true; - aType.renderVectorData = true; - aType.vectorScale = 1; - aType.arrowHead = StatisticsType::ArrowHead::none; - aType.gridStyle.color = Color(255, 255, 255); - aType.vectorStyle.color = Color(255, 255, 255); - } - - // check whether is was a geometric partitioning statistic with polygon shape - if (statType.contains("Polygon")) - aType.isPolygon = true; - - // add the new type if it is not already in the list - statisticsData.addStatType(aType); // check if in list is done by addStatsType - } - } - } // try - catch (const char *str) - { - std::cerr << "Error while parsing meta data: " << str << '\n'; - this->errorMessage = QString("Error while parsing meta data: ") + QString(str); - } - catch (...) - { - std::cerr << "Error while parsing meta data."; - this->errorMessage = QString("Error while parsing meta data."); - } } } // namespace stats diff --git a/YUViewLib/src/statistics/StatisticsType.cpp b/YUViewLib/src/statistics/StatisticsType.cpp index bc68925f0..291d3f28e 100644 --- a/YUViewLib/src/statistics/StatisticsType.cpp +++ b/YUViewLib/src/statistics/StatisticsType.cpp @@ -37,223 +37,72 @@ namespace stats { -namespace +bool LineDrawStyle::operator==(const LineDrawStyle &other) const { - -// Get a string with all values of the QPen -QString convertPenToString(const LineDrawStyle &style) -{ - auto colorHex = QString::fromStdString(style.color.toHex()); - auto patternIt = std::find(AllPatterns.begin(), AllPatterns.end(), style.pattern); - if (patternIt == AllPatterns.end()) - return {}; - auto patternIdx = std::distance(AllPatterns.begin(), patternIt); - return QString("%1 %2 %3").arg(colorHex).arg(style.width).arg(patternIdx); + return color == other.color && width == other.width && pattern == other.pattern; } -// The inverse functio to get a QPen from the string -LineDrawStyle convertStringToPen(const QString &str) +std::string StatisticsType::getValueText(const int val) const { - LineDrawStyle style; - QStringList split = str.split(" "); - if (split.length() == 3) - { - style.color = Color(split[0].toStdString()); - style.width = split[1].toDouble(); - auto patternIdx = split[2].toInt(); - if (patternIdx >= 0 && unsigned(patternIdx) < AllPatterns.size()) - style.pattern = AllPatterns[patternIdx]; - } - return style; -} + if (this->valuesToText.contains(val)) + return this->valuesToText.at(val) + " (" + std::to_string(val) + ")"; -std::vector AllArrowHeads = {StatisticsType::ArrowHead::arrow, - StatisticsType::ArrowHead::circle, - StatisticsType::ArrowHead::none}; - -} // namespace - -StatisticsType::StatisticsType(int typeID, const QString &typeName) - : typeID(typeID), typeName(typeName) -{ + return std::to_string(val); } -StatisticsType::StatisticsType(int typeID, const QString &typeName, int vectorScaling) - : StatisticsType(typeID, typeName) +bool StatisticsType::ValueDataOptions::operator==(const ValueDataOptions &rhs) const { - this->hasVectorData = true; - this->renderVectorData = true; - this->vectorScale = vectorScaling; - - this->setInitialState(); + return this->render == rhs.render && // + this->scaleToBlockSize == rhs.scaleToBlockSize && // + this->colorMapper == rhs.colorMapper; } -// Convenience constructor for a statistics type with block data and a named color map -StatisticsType::StatisticsType(int typeID, - const QString & typeName, - const color::ColorMapper &colorMapper, - bool hasAndRenderVectorData) - : StatisticsType(typeID, typeName) +bool StatisticsType::VectorDataOptions::operator==(const VectorDataOptions &rhs) const { - // There is value data. Set up a color mapper. - this->hasValueData = true; - this->renderValueData = true; - this->colorMapper = colorMapper; - - this->hasVectorData = hasAndRenderVectorData; - this->renderVectorData = hasAndRenderVectorData; - - this->setInitialState(); + return this->render == rhs.render && // + this->renderDataValues == rhs.renderDataValues && // + this->scaleToZoom == rhs.scaleToZoom && // + this->style == rhs.style && // + this->scale == rhs.scale && // + this->mapToColor == rhs.mapToColor && // + this->arrowHead == rhs.arrowHead; } -void StatisticsType::setInitialState() +bool StatisticsType::GridOptions::operator==(const GridOptions &rhs) const { - this->init.render = this->render; - this->init.alphaFactor = this->alphaFactor; - - this->init.renderValueData = this->renderValueData; - this->init.scaleValueToBlockSize = this->scaleValueToBlockSize; - this->init.colorMapper = this->colorMapper; - - this->init.renderVectorData = this->renderVectorData; - this->init.scaleVectorToZoom = this->scaleVectorToZoom; - this->init.vectorStyle = this->vectorStyle; - this->init.vectorScale = this->vectorScale; - this->init.mapVectorToColor = this->mapVectorToColor; - this->init.arrowHead = this->arrowHead; - - this->init.renderGrid = this->renderGrid; - this->init.gridStyle = this->gridStyle; - this->init.scaleGridToZoom = this->scaleGridToZoom; + return this->render == rhs.render && // + this->style == rhs.style && // + this->scaleToZoom == rhs.scaleToZoom; } -/* Save all the settings of the statistics type that have changed from the initial state - */ -void StatisticsType::savePlaylist(YUViewDomElement &root) const +StatisticsType::StatisticsType(int typeId, std::string typeName) + : typeID(typeId), typeName(std::move(typeName)) { - bool statChanged = - (init.render != render || init.alphaFactor != alphaFactor || - init.renderValueData != renderValueData || - init.scaleValueToBlockSize != scaleValueToBlockSize || init.colorMapper != colorMapper || - init.renderVectorData != renderVectorData || init.scaleVectorToZoom != scaleVectorToZoom || - init.vectorStyle != vectorStyle || init.vectorScale != vectorScale || - init.mapVectorToColor != mapVectorToColor || init.arrowHead != arrowHead || - init.renderGrid != renderGrid || init.gridStyle != gridStyle || - init.scaleGridToZoom != scaleGridToZoom); - - if (!statChanged) - return; - - YUViewDomElement newChild = root.ownerDocument().createElement(QString("statType%1").arg(typeID)); - newChild.appendChild(root.ownerDocument().createTextNode(typeName)); - - // Append only the parameters that changed - if (init.render != render) - newChild.setAttribute("render", render); - if (init.alphaFactor != alphaFactor) - newChild.setAttribute("alphaFactor", alphaFactor); - if (init.renderValueData != renderValueData) - newChild.setAttribute("renderValueData", renderValueData); - if (init.scaleValueToBlockSize != scaleValueToBlockSize) - newChild.setAttribute("scaleValueToBlockSize", scaleValueToBlockSize); - if (init.colorMapper != this->colorMapper) - this->colorMapper.savePlaylist(newChild); - if (init.renderVectorData != renderVectorData) - newChild.setAttribute("renderVectorData", renderVectorData); - if (init.scaleVectorToZoom != scaleVectorToZoom) - newChild.setAttribute("scaleVectorToZoom", scaleVectorToZoom); - if (init.vectorStyle != vectorStyle) - newChild.setAttribute("vectorStyle", convertPenToString(vectorStyle)); - if (init.vectorScale != vectorScale) - newChild.setAttribute("vectorScale", vectorScale); - if (init.mapVectorToColor != mapVectorToColor) - newChild.setAttribute("mapVectorToColor", mapVectorToColor); - if (init.arrowHead != arrowHead) - { - if (const auto index = vectorIndexOf(stats::AllArrowHeads, arrowHead)) - newChild.setAttribute("renderarrowHead", static_cast(*index)); - } - if (init.renderGrid != renderGrid) - newChild.setAttribute("renderGrid", renderGrid); - if (init.gridStyle != gridStyle) - newChild.setAttribute("gridStyle", convertPenToString(gridStyle)); - if (init.scaleGridToZoom != scaleGridToZoom) - newChild.setAttribute("scaleGridToZoom", scaleGridToZoom); - - root.appendChild(newChild); } -void StatisticsType::loadPlaylist(const YUViewDomElement &root) +void StatisticsType::saveInitialState() { - auto [name, attributes] = root.findChildValueWithAttributes(QString("statType%1").arg(typeID)); - - if (name != this->typeName) - // The name of this type with the right ID and the name in the playlist don't match?... - return; - - // Parse and set all the attributes that are in the playlist - for (int i = 0; i < attributes.length(); i++) + this->render.setUnmodified(); + this->alphaFactor.setUnmodified(); + if (this->valueDataOptions) { - if (attributes[i].first == "render") - render = (attributes[i].second != "0"); - else if (attributes[i].first == "alphaFactor") - alphaFactor = attributes[i].second.toInt(); - else if (attributes[i].first == "renderValueData") - renderValueData = (attributes[i].second != "0"); - else if (attributes[i].first == "scaleValueToBlockSize") - scaleValueToBlockSize = (attributes[i].second != "0"); - else if (attributes[i].first == "renderVectorData") - renderVectorData = (attributes[i].second != "0"); - else if (attributes[i].first == "scaleVectorToZoom") - scaleVectorToZoom = (attributes[i].second != "0"); - else if (attributes[i].first == "vectorPen") - vectorStyle = convertStringToPen(attributes[i].second); - else if (attributes[i].first == "vectorScale") - vectorScale = attributes[i].second.toInt(); - else if (attributes[i].first == "mapVectorToColor") - mapVectorToColor = (attributes[i].second != "0"); - else if (attributes[i].first == "renderarrowHead") - { - auto idx = attributes[i].second.toInt(); - if (idx >= 0 && unsigned(idx) < AllArrowHeads.size()) - arrowHead = AllArrowHeads[idx]; - } - else if (attributes[i].first == "renderGrid") - renderGrid = (attributes[i].second != "0"); - else if (attributes[i].first == "gridPen") - gridStyle = convertStringToPen(attributes[i].second); - else if (attributes[i].first == "scaleGridToZoom") - scaleGridToZoom = (attributes[i].second != "0"); + this->valueDataOptions->render.setUnmodified(); + this->valueDataOptions->scaleToBlockSize.setUnmodified(); + this->valueDataOptions->colorMapper.setUnmodified(); } - - this->colorMapper.loadPlaylist(attributes); -} - -// If the internal valueMap can map the value to text, text and value will be returned. -// Otherwise just the value as QString will be returned. -QString StatisticsType::getValueTxt(int val) const -{ - if (valMap.count(val) > 0) + if (this->vectorDataOptions) { - // A text for this value van be shown. - return QString("%1 (%2)").arg(valMap.at(val)).arg(val); + this->vectorDataOptions->render.setUnmodified(); + this->vectorDataOptions->renderDataValues.setUnmodified(); + this->vectorDataOptions->scaleToZoom.setUnmodified(); + this->vectorDataOptions->style.setUnmodified(); + this->vectorDataOptions->scale.setUnmodified(); + this->vectorDataOptions->mapToColor.setUnmodified(); + this->vectorDataOptions->arrowHead.setUnmodified(); } - return QString("%1").arg(val); -} - -void StatisticsType::setMappingValues(std::vector values) -{ - // We assume linear increasing typed IDs - for (int i = 0; i < int(values.size()); i++) - this->valMap[i] = values[i]; -} - -QString StatisticsType::getMappedValue(int typeID) const -{ - if (this->valMap.count(typeID) == 0) - return {}; - - return this->valMap.at(typeID); + this->gridOptions.render.setUnmodified(); + this->gridOptions.style.setUnmodified(); + this->gridOptions.scaleToZoom.setUnmodified(); } -} // namespace stats \ No newline at end of file +} // namespace stats diff --git a/YUViewLib/src/statistics/StatisticsType.h b/YUViewLib/src/statistics/StatisticsType.h index 252f2a58e..2016180d7 100644 --- a/YUViewLib/src/statistics/StatisticsType.h +++ b/YUViewLib/src/statistics/StatisticsType.h @@ -32,8 +32,8 @@ #pragma once +#include #include -#include #include "ColorMapper.h" @@ -58,10 +58,7 @@ struct LineDrawStyle double width{0.25}; Pattern pattern{Pattern::Solid}; - bool operator!=(const LineDrawStyle &other) const - { - return color != other.color || width != other.width || pattern != other.pattern; - } + bool operator==(const LineDrawStyle &other) const; }; /* This class defines a type of statistic to render. Each statistics type entry defines the name and @@ -70,98 +67,71 @@ struct LineDrawStyle */ class StatisticsType { + friend class StatisticsTypeBuilder; + friend class StatisticsTypePlaylistHandler; + public: - StatisticsType(int typeID = INT_INVALID, const QString &typeName = "?"); - StatisticsType(int typeID, const QString &typeName, int vectorScaling); - StatisticsType(int typeID, - const QString & typeName, - const color::ColorMapper &colorMapper, - bool hasAndRenderVectorData = false); + int typeID{}; + std::string typeName{}; + std::string description{}; - // Save all the values that the user could change. When saving to playlist we can save only the - // changed values to playlist. - void setInitialState(); + std::string getValueText(const int val) const; - // Load/Save status of statistics from playlist file - void savePlaylist(YUViewDomElement &root) const; - void loadPlaylist(const YUViewDomElement &root); + // These are corresponding to the controls in the properties panel + modified render{}; + modified alphaFactor{50}; - // Every statistics type has an ID, a name and possibly a description - int typeID{}; - QString typeName{}; - QString description{}; + struct ValueDataOptions + { + modified render{true}; + modified scaleToBlockSize{}; + modified colorMapper{}; - // Get the value text (from the value map (if there is an entry)) - QString getValueTxt(int val) const; + bool operator==(const ValueDataOptions &rhs) const; + }; - void setMappingValues(std::vector values); - QString getMappedValue(int typeID) const; + std::optional valueDataOptions; - // Is this statistics type rendered and what is the alpha value? - // These are corresponding to the controls in the properties panel - bool render{}; - int alphaFactor{50}; - - // Value data (a certain value, that is set for a block) - bool hasValueData{}; // Does this type have value data? - bool renderValueData{}; // Do we render the value data? - bool scaleValueToBlockSize{}; // Scale the values according to the size of the block - color::ColorMapper colorMapper; // How do we map values to color? - - // Vector data (a vector that is set for a block) - bool hasVectorData{}; // Does this type have any vector data? - bool hasAffineTFData{}; - bool renderVectorData{}; // Do we draw the vector data? - bool renderVectorDataValues{}; // Do we draw the values of the vector next to the vector (by - // default true). - bool scaleVectorToZoom{}; - LineDrawStyle vectorStyle; // How do we draw the vectors - int vectorScale{1}; // Every vector value (x,y) has to be divided by this value before displaying - // it (e.g. 1/4 th pixel accuracy) - bool mapVectorToColor{}; // Color the vectors depending on their direction enum class ArrowHead { arrow, circle, none }; - ArrowHead arrowHead{ - ArrowHead::arrow}; // Do we draw an arrow, a circle or nothing at the end of the arrow? - // Do we (and if yes how) draw a grid around each block (vector or value) - bool renderGrid{true}; - LineDrawStyle gridStyle; - bool scaleGridToZoom{}; - - // is statistic drawn as a block or as a polygon? - bool isPolygon{}; + struct VectorDataOptions + { + modified render{true}; + modified renderDataValues{true}; + modified scaleToZoom{}; + modified style{}; + modified scale{}; + modified mapToColor{}; + modified arrowHead{}; + + bool operator==(const VectorDataOptions &rhs) const; + }; -private: - // If set, this map is used to map values to text - std::map valMap; + std::optional vectorDataOptions; - // Backup values for setDefaultState() - struct initialState + struct GridOptions { - bool render; - int alphaFactor; - - bool renderValueData; - bool scaleValueToBlockSize; - color::ColorMapper colorMapper; - - bool renderVectorData; - bool scaleVectorToZoom; - LineDrawStyle vectorStyle; - int vectorScale; - bool mapVectorToColor; - ArrowHead arrowHead; - - bool renderGrid; - LineDrawStyle gridStyle; - bool scaleGridToZoom; + modified render{}; + modified style{}; + modified scaleToZoom{}; + + bool operator==(const GridOptions &rhs) const; }; - initialState init; + + GridOptions gridOptions; + +private: + StatisticsType() = delete; + StatisticsType(int typeId, std::string typeName); + + std::map valuesToText; + + void saveInitialState(); }; } // namespace stats diff --git a/YUViewLib/src/statistics/StatisticsTypeBuilder.h b/YUViewLib/src/statistics/StatisticsTypeBuilder.h new file mode 100644 index 000000000..944a6f9cd --- /dev/null +++ b/YUViewLib/src/statistics/StatisticsTypeBuilder.h @@ -0,0 +1,125 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * 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 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +namespace stats +{ + +class StatisticsTypeBuilder +{ +public: + StatisticsTypeBuilder() = delete; + StatisticsTypeBuilder(int typeID, const std::string &typeName) : statisticsType(typeID, typeName) + { + } + + StatisticsTypeBuilder &withDescription(const std::string &description) + { + this->statisticsType.description = description; + return *this; + } + + StatisticsTypeBuilder &withRender(const bool render) + { + this->statisticsType.render = render; + return *this; + } + + StatisticsTypeBuilder withAlphaFactor(int alphaFactor) + { + this->statisticsType.alphaFactor = alphaFactor; + return *this; + } + + StatisticsTypeBuilder + withValueDataOptions(const StatisticsType::ValueDataOptions &valueDataOptions) + { + this->statisticsType.valueDataOptions = valueDataOptions; + return *this; + } + + StatisticsTypeBuilder withOptionalValueDataOptions( + const std::optional &valueDataOptions) + { + this->statisticsType.valueDataOptions = valueDataOptions; + return *this; + } + + StatisticsTypeBuilder + withVectorDataOptions(const StatisticsType::VectorDataOptions &vectorDataOptions) + { + this->statisticsType.vectorDataOptions = vectorDataOptions; + return *this; + } + + StatisticsTypeBuilder withOptionalVectorDataOptions( + const std::optional &vectorDataOptions) + { + this->statisticsType.vectorDataOptions = vectorDataOptions; + return *this; + } + + StatisticsTypeBuilder withGridOptions(const StatisticsType::GridOptions &gridOptions) + { + this->statisticsType.gridOptions = gridOptions; + return *this; + } + + StatisticsTypeBuilder &withMappingValues(const std::vector &mappingValues) + { + int i = 0; + for (const auto &value : mappingValues) + this->statisticsType.valuesToText[i++] = value; + return *this; + } + + StatisticsTypeBuilder &withMappingValues(const std::initializer_list &mappingValues) + { + int typeId = 0; + for (const auto &value : mappingValues) + this->statisticsType.valuesToText[typeId++] = value; + + return *this; + } + + StatisticsType build() + { + this->statisticsType.saveInitialState(); + return this->statisticsType; + } + +private: + StatisticsType statisticsType; +}; + +} // namespace stats diff --git a/YUViewLib/src/statistics/StatisticsTypePlaylistHandler.cpp b/YUViewLib/src/statistics/StatisticsTypePlaylistHandler.cpp new file mode 100644 index 000000000..763d3b94a --- /dev/null +++ b/YUViewLib/src/statistics/StatisticsTypePlaylistHandler.cpp @@ -0,0 +1,238 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * 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 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "StatisticsTypePlaylistHandler.h" + +namespace stats +{ + +namespace +{ + +// Get a string with all values of the QPen +QString convertPenToString(const LineDrawStyle &style) +{ + auto colorHex = QString::fromStdString(style.color.toHex()); + auto patternIt = std::find(AllPatterns.begin(), AllPatterns.end(), style.pattern); + if (patternIt == AllPatterns.end()) + return {}; + auto patternIdx = std::distance(AllPatterns.begin(), patternIt); + return QString("%1 %2 %3").arg(colorHex).arg(style.width).arg(patternIdx); +} + +// The inverse functio to get a QPen from the string +LineDrawStyle convertStringToPen(const QString &str) +{ + LineDrawStyle style; + QStringList split = str.split(" "); + if (split.length() == 3) + { + style.color = Color(split[0].toStdString()); + style.width = split[1].toDouble(); + auto patternIdx = split[2].toInt(); + if (patternIdx >= 0 && unsigned(patternIdx) < AllPatterns.size()) + style.pattern = AllPatterns[patternIdx]; + } + return style; +} + +void addModifiedValuesToElement(YUViewDomElement &element, + const std::optional &options) +{ + if (!options) + return; + + if (options->render.wasModified()) + element.setAttribute("renderValueData", options->render); + if (options->scaleToBlockSize.wasModified()) + element.setAttribute("scaleValueToBlockSize", options->render); + if (options->colorMapper.wasModified()) + options->colorMapper->savePlaylist(element); +} + +std::vector AllArrowHeads = {StatisticsType::ArrowHead::arrow, + StatisticsType::ArrowHead::circle, + StatisticsType::ArrowHead::none}; + +void addModifiedValuesToElement(YUViewDomElement &element, + const std::optional &options) +{ + if (!options) + return; + + if (options->render.wasModified()) + element.setAttribute("renderVectorData", options->render); + if (options->renderDataValues.wasModified()) + element.setAttribute("renderVectorDataValues", options->renderDataValues); + if (options->scaleToZoom.wasModified()) + element.setAttribute("scaleVectorToZoom", options->scaleToZoom); + if (options->style.wasModified()) + element.setAttribute("vectorStyle", convertPenToString(options->style)); + if (options->scale.wasModified()) + element.setAttribute("vectorScale", options->scale); + if (options->mapToColor.wasModified()) + element.setAttribute("mapVectorToColor", options->mapToColor); + if (options->arrowHead.wasModified()) + { + if (const auto index = vectorIndexOf(AllArrowHeads, *options->arrowHead)) + element.setAttribute("renderarrowHead", static_cast(*index)); + } +} + +void addModifiedValuesToElement(YUViewDomElement &element, + const StatisticsType::GridOptions &options) +{ + if (options.render.wasModified()) + element.setAttribute("renderGrid", options.render); + if (options.style.wasModified()) + element.setAttribute("gridStyle", convertPenToString(options.style)); + if (options.scaleToZoom.wasModified()) + element.setAttribute("scaleGridToZoom", options.scaleToZoom); +} + +} // namespace + +void StatisticsTypePlaylistHandler::saveToPlaylist(const StatisticsType &type, + YUViewDomElement &root) +{ + const auto valueDataOptionsModified = + type.valueDataOptions && (type.valueDataOptions->render.wasModified() || + type.valueDataOptions->scaleToBlockSize.wasModified() || + type.valueDataOptions->colorMapper.wasModified()); + + const auto vectorDataOptionsModified = + type.vectorDataOptions && + (type.vectorDataOptions->render.wasModified() || + type.vectorDataOptions->renderDataValues.wasModified() || + type.vectorDataOptions->scaleToZoom.wasModified() || + type.vectorDataOptions->style.wasModified() || type.vectorDataOptions->scale.wasModified() || + type.vectorDataOptions->mapToColor.wasModified() || + type.vectorDataOptions->arrowHead.wasModified()); + + const auto gridOptionsModified = type.gridOptions.render.wasModified() || // + type.gridOptions.style.wasModified() || // + type.gridOptions.scaleToZoom.wasModified(); + + const auto allValuesIdenticalToInitialValues = (!type.render.wasModified() && // + !type.alphaFactor.wasModified() && // + !valueDataOptionsModified && // + !vectorDataOptionsModified && // + !gridOptionsModified); + + if (allValuesIdenticalToInitialValues) + return; + + YUViewDomElement newChild = + root.ownerDocument().createElement(QString("statType%1").arg(type.typeID)); + newChild.appendChild(root.ownerDocument().createTextNode(QString::fromStdString(type.typeName))); + + // Append only the parameters that changed + if (type.render.wasModified()) + newChild.setAttribute("render", type.render); + if (type.alphaFactor.wasModified()) + newChild.setAttribute("alphaFactor", type.alphaFactor); + + addModifiedValuesToElement(newChild, type.valueDataOptions); + addModifiedValuesToElement(newChild, type.vectorDataOptions); + addModifiedValuesToElement(newChild, type.gridOptions); + + root.appendChild(newChild); +} + +void StatisticsTypePlaylistHandler::tryToLoadFromPlaylist(StatisticsType &type, + const YUViewDomElement &root) +{ + const auto [name, attributes] = + root.findChildValueWithAttributes(QString("statType%1").arg(type.typeID)); + + if (name.toStdString() != type.typeName) + // The name of this type with the right ID and the name in the playlist don't match?... + return; + + // Parse and set all the attributes that are in the playlist + for (const auto &[name, value] : attributes) + { + if (name == "render") + type.render = (value != "0"); + else if (name == "alphaFactor") + type.alphaFactor = value.toInt(); + else if (name == "renderValueData" || name == "scaleValueToBlockSize" || + name == "colorMapperType") + { + if (!type.valueDataOptions) + type.valueDataOptions = StatisticsType::ValueDataOptions(); + + if (name == "renderValueData") + type.valueDataOptions->render = (value != "0"); + else if (name == "scaleValueToBlockSize") + type.valueDataOptions->scaleToBlockSize = (value != "0"); + else if (name == "colorMapperType") + type.valueDataOptions->colorMapper->loadPlaylist(attributes); + } + else if (name == "renderVectorData" || name == "renderVectorDataValues" || + name == "scaleVectorToZoom" || name == "vectorStyle" || name == "vectorScale" || + name == "mapVectorToColor" || name == "renderarrowHead") + { + if (!type.vectorDataOptions) + type.vectorDataOptions = StatisticsType::VectorDataOptions(); + + if (name == "renderVectorData") + type.vectorDataOptions->render = (value != "0"); + else if (name == "renderVectorDataValues") + type.vectorDataOptions->renderDataValues = (value != "0"); + else if (name == "scaleVectorToZoom") + type.vectorDataOptions->scaleToZoom = (value != "0"); + else if (name == "vectorStyle") + type.vectorDataOptions->style = convertStringToPen(value); + else if (name == "vectorScale") + type.vectorDataOptions->scale = value.toInt(); + else if (name == "mapVectorToColor") + type.vectorDataOptions->mapToColor = (value != "0"); + else if (name == "renderarrowHead") + { + const auto idx = value.toInt(); + if (idx >= 0 && unsigned(idx) < AllArrowHeads.size()) + type.vectorDataOptions->arrowHead = AllArrowHeads.at(idx); + } + } + else if (name == "renderGrid") + type.gridOptions.render = (value != "0"); + else if (name == "gridPen") + type.gridOptions.style = convertStringToPen(value); + else if (name == "scaleGridToZoom") + type.gridOptions.scaleToZoom = (value != "0"); + } + + type.saveInitialState(); +} + +} // namespace stats diff --git a/YUViewLib/src/statistics/StatisticsTypePlaylistHandler.h b/YUViewLib/src/statistics/StatisticsTypePlaylistHandler.h new file mode 100644 index 000000000..b23618730 --- /dev/null +++ b/YUViewLib/src/statistics/StatisticsTypePlaylistHandler.h @@ -0,0 +1,49 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * 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 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include + +#include "StatisticsType.h" + +namespace stats +{ + +class StatisticsTypePlaylistHandler +{ +public: + static void saveToPlaylist(const StatisticsType &type, YUViewDomElement &root); + static void tryToLoadFromPlaylist(StatisticsType &type, const YUViewDomElement &root); +}; + +} // namespace stats diff --git a/YUViewLib/src/ui/Statisticsstylecontrol.cpp b/YUViewLib/src/ui/Statisticsstylecontrol.cpp index c715cdb97..c298b4b51 100644 --- a/YUViewLib/src/ui/Statisticsstylecontrol.cpp +++ b/YUViewLib/src/ui/Statisticsstylecontrol.cpp @@ -86,12 +86,13 @@ void StatisticsStyleControl::setStatsItem(stats::StatisticsType *item) { DEBUG_STAT_STYLE("StatisticsStyleControl::setStatsItem %s", item->typeName.toStdString().c_str()); this->currentItem = item; - this->setWindowTitle("Edit statistics rendering: " + this->currentItem->typeName); + this->setWindowTitle("Edit statistics rendering: " + + QString::fromStdString(this->currentItem->typeName)); - if (this->currentItem->hasValueData) + if (this->currentItem->valueDataOptions) { this->ui.groupBoxBlockData->show(); - const auto &colorMapper = this->currentItem->colorMapper; + const auto &colorMapper = this->currentItem->valueDataOptions->colorMapper; this->ui.frameDataColor->setColorMapper(colorMapper); @@ -99,39 +100,41 @@ void StatisticsStyleControl::setStatsItem(stats::StatisticsType *item) {{MappingType::Predefined, 0}, {MappingType::Gradient, 1}, {MappingType::Map, 2}}); QSignalBlocker blockTabIndexChanged(this->ui.blockDataTab); - auto newIndex = MappingTypeToTabIndex.at(colorMapper.mappingType); + auto newIndex = MappingTypeToTabIndex.at(colorMapper->mappingType); this->ui.blockDataTab->setCurrentIndex(newIndex); this->on_blockDataTab_currentChanged(newIndex); } else this->ui.groupBoxBlockData->hide(); - if (this->currentItem->hasVectorData) + if (this->currentItem->vectorDataOptions) { this->ui.groupBoxVector->show(); - if (const auto penStyleIndex = - vectorIndexOf(stats::AllPatterns, this->currentItem->vectorStyle.pattern)) + const auto &options = *this->currentItem->vectorDataOptions; + + if (const auto penStyleIndex = vectorIndexOf(stats::AllPatterns, options.style->pattern)) this->ui.comboBoxVectorLineStyle->setCurrentIndex(static_cast(*penStyleIndex)); - this->ui.doubleSpinBoxVectorLineWidth->setValue(this->currentItem->vectorStyle.width); - this->ui.checkBoxVectorScaleToZoom->setChecked(this->currentItem->scaleVectorToZoom); - this->ui.comboBoxVectorHeadStyle->setCurrentIndex(int(this->currentItem->arrowHead)); - this->ui.checkBoxVectorMapToColor->setChecked(this->currentItem->mapVectorToColor); - this->ui.colorFrameVectorColor->setPlainColor( - functionsGui::toQColor(this->currentItem->vectorStyle.color)); - this->ui.colorFrameVectorColor->setEnabled(!this->currentItem->mapVectorToColor); - this->ui.pushButtonEditVectorColor->setEnabled(!this->currentItem->mapVectorToColor); + this->ui.doubleSpinBoxVectorLineWidth->setValue(options.style->width); + this->ui.checkBoxVectorScaleToZoom->setChecked(options.scaleToZoom); + this->ui.comboBoxVectorHeadStyle->setCurrentIndex(int(*options.arrowHead)); + this->ui.checkBoxVectorMapToColor->setChecked(options.mapToColor); + this->ui.colorFrameVectorColor->setPlainColor(functionsGui::toQColor(options.style->color)); + this->ui.colorFrameVectorColor->setEnabled(!options.mapToColor); + this->ui.pushButtonEditVectorColor->setEnabled(!options.mapToColor); } else this->ui.groupBoxVector->hide(); - this->ui.frameGridColor->setPlainColor( - functionsGui::toQColor(this->currentItem->gridStyle.color)); - this->ui.doubleSpinBoxGridLineWidth->setValue(this->currentItem->gridStyle.width); - this->ui.checkBoxGridScaleToZoom->setChecked(this->currentItem->scaleGridToZoom); + { + const auto &options = this->currentItem->gridOptions; + this->ui.frameGridColor->setPlainColor(functionsGui::toQColor(options.style->color)); + this->ui.doubleSpinBoxGridLineWidth->setValue(options.style->width); + this->ui.checkBoxGridScaleToZoom->setChecked(options.scaleToZoom); + } if (const auto penStyleIndex = - vectorIndexOf(stats::AllPatterns, this->currentItem->vectorStyle.pattern)) + vectorIndexOf(stats::AllPatterns, this->currentItem->vectorDataOptions->style->pattern)) this->ui.comboBoxGridLineStyle->setCurrentIndex(static_cast(*penStyleIndex)); this->resize(sizeHint()); @@ -139,60 +142,61 @@ void StatisticsStyleControl::setStatsItem(stats::StatisticsType *item) void StatisticsStyleControl::on_groupBoxVector_clicked(bool check) { - if (!this->currentItem) + if (!this->currentItem || !this->currentItem->vectorDataOptions) return; - this->currentItem->renderVectorData = check; + this->currentItem->vectorDataOptions->render = check; emit StyleChanged(); } void StatisticsStyleControl::on_groupBoxBlockData_clicked(bool check) { - if (!this->currentItem) + if (!this->currentItem || !this->currentItem->valueDataOptions) return; - this->currentItem->renderValueData = check; + this->currentItem->valueDataOptions->render = check; emit StyleChanged(); } void StatisticsStyleControl::on_checkBoxScaleValueToBlockSize_stateChanged(int val) { - if (!this->currentItem) + if (!this->currentItem || !this->currentItem->valueDataOptions) return; - this->currentItem->scaleValueToBlockSize = (val != 0); + + this->currentItem->valueDataOptions->scaleToBlockSize = (val != 0); emit StyleChanged(); } void StatisticsStyleControl::on_blockDataTab_currentChanged(int index) { - auto &colorMapper = this->currentItem->colorMapper; + auto &colorMapper = this->currentItem->valueDataOptions->colorMapper; if (index == 0) { - colorMapper.mappingType = MappingType::Predefined; + colorMapper->mappingType = MappingType::Predefined; this->ui.comboBoxPredefined->setCurrentIndex( - int(stats::color::PredefinedTypeMapper.indexOf(colorMapper.predefinedType))); - this->ui.spinBoxPredefinedRangeMin->setValue(colorMapper.valueRange.min); - this->ui.spinBoxPredefinedRangeMax->setValue(colorMapper.valueRange.max); + int(stats::color::PredefinedTypeMapper.indexOf(colorMapper->predefinedType))); + this->ui.spinBoxPredefinedRangeMin->setValue(colorMapper->valueRange.min); + this->ui.spinBoxPredefinedRangeMax->setValue(colorMapper->valueRange.max); } else if (index == 1) { - colorMapper.mappingType = MappingType::Gradient; + colorMapper->mappingType = MappingType::Gradient; this->ui.frameGradientStartColor->setPlainColor( - functionsGui::toQColor(colorMapper.gradientColorStart)); + functionsGui::toQColor(colorMapper->gradientColorStart)); this->ui.frameGradientEndColor->setPlainColor( - functionsGui::toQColor(colorMapper.gradientColorEnd)); - this->ui.spinBoxGradientRangeMin->setValue(colorMapper.valueRange.min); - this->ui.spinBoxGradientRangeMax->setValue(colorMapper.valueRange.max); + functionsGui::toQColor(colorMapper->gradientColorEnd)); + this->ui.spinBoxGradientRangeMin->setValue(colorMapper->valueRange.min); + this->ui.spinBoxGradientRangeMax->setValue(colorMapper->valueRange.max); } else if (index == 2) { - if (colorMapper.mappingType != MappingType::Map) + if (colorMapper->mappingType != MappingType::Map) { - colorMapper.colorMap = convertNonMapTypeToColorMap(colorMapper); + colorMapper->colorMap = convertNonMapTypeToColorMap(colorMapper); } - colorMapper.mappingType = MappingType::Map; + colorMapper->mappingType = MappingType::Map; if (auto customMapEntry = this->customColorMapStorage.indexOfColorMap( - colorMapper.colorMap, colorMapper.colorMapOther)) + colorMapper->colorMap, colorMapper->colorMapOther)) this->ui.comboBoxCustomMap->setCurrentIndex(int(*customMapEntry)); else this->ui.comboBoxCustomMap->setCurrentIndex(-1); @@ -203,52 +207,56 @@ void StatisticsStyleControl::on_blockDataTab_currentChanged(int index) void StatisticsStyleControl::on_comboBoxPredefined_currentIndexChanged(int index) { - if (!this->currentItem || this->currentItem->colorMapper.mappingType != MappingType::Predefined || + if (!this->currentItem || !this->currentItem->valueDataOptions || + this->currentItem->valueDataOptions->colorMapper->mappingType != MappingType::Predefined || index < 0) return; if (auto newType = stats::color::PredefinedTypeMapper.getValueAt(static_cast(index))) { - this->currentItem->colorMapper.predefinedType = *newType; - this->ui.frameDataColor->setColorMapper(this->currentItem->colorMapper); + this->currentItem->valueDataOptions->colorMapper->predefinedType = *newType; + this->ui.frameDataColor->setColorMapper(this->currentItem->valueDataOptions->colorMapper); emit StyleChanged(); } } void StatisticsStyleControl::on_spinBoxPredefinedRangeMin_valueChanged(int val) { - if (!this->currentItem || this->currentItem->colorMapper.mappingType != MappingType::Predefined) + if (!this->currentItem || !this->currentItem->valueDataOptions || + this->currentItem->valueDataOptions->colorMapper->mappingType != MappingType::Predefined) return; - this->currentItem->colorMapper.valueRange.min = val; - this->ui.frameDataColor->setColorMapper(this->currentItem->colorMapper); + this->currentItem->valueDataOptions->colorMapper->valueRange.min = val; + this->ui.frameDataColor->setColorMapper(this->currentItem->valueDataOptions->colorMapper); emit StyleChanged(); } void StatisticsStyleControl::on_spinBoxPredefinedRangeMax_valueChanged(int val) { - if (!this->currentItem || this->currentItem->colorMapper.mappingType != MappingType::Predefined) + if (!this->currentItem || !this->currentItem->valueDataOptions || + this->currentItem->valueDataOptions->colorMapper->mappingType != MappingType::Predefined) return; - this->currentItem->colorMapper.valueRange.max = val; - this->ui.frameDataColor->setColorMapper(this->currentItem->colorMapper); + this->currentItem->valueDataOptions->colorMapper->valueRange.max = val; + this->ui.frameDataColor->setColorMapper(this->currentItem->valueDataOptions->colorMapper); emit StyleChanged(); } void StatisticsStyleControl::on_frameGradientStartColor_clicked() { auto newQColor = QColorDialog::getColor( - functionsGui::toQColor(this->currentItem->colorMapper.gradientColorStart), + functionsGui::toQColor(this->currentItem->valueDataOptions->colorMapper->gradientColorStart), this, tr("Select color range minimum"), QColorDialog::ShowAlphaChannel); auto newColor = functionsGui::toColor(newQColor); - if (newQColor.isValid() && this->currentItem->colorMapper.gradientColorStart != newColor) + if (newQColor.isValid() && + this->currentItem->valueDataOptions->colorMapper->gradientColorStart != newColor) { - this->currentItem->colorMapper.gradientColorStart = newColor; + this->currentItem->valueDataOptions->colorMapper->gradientColorStart = newColor; this->ui.frameGradientStartColor->setPlainColor(newQColor); - this->ui.frameDataColor->setColorMapper(this->currentItem->colorMapper); + this->ui.frameDataColor->setColorMapper(this->currentItem->valueDataOptions->colorMapper); emit StyleChanged(); } } @@ -261,17 +269,18 @@ void StatisticsStyleControl::on_pushButtonGradientEditStartColor_clicked() void StatisticsStyleControl::on_frameGradientEndColor_clicked() { auto newQColor = QColorDialog::getColor( - functionsGui::toQColor(this->currentItem->colorMapper.gradientColorEnd), + functionsGui::toQColor(this->currentItem->valueDataOptions->colorMapper->gradientColorEnd), this, tr("Select color range maximum"), QColorDialog::ShowAlphaChannel); auto newColor = functionsGui::toColor(newQColor); - if (newQColor.isValid() && this->currentItem->colorMapper.gradientColorEnd != newColor) + if (newQColor.isValid() && + this->currentItem->valueDataOptions->colorMapper->gradientColorEnd != newColor) { - this->currentItem->colorMapper.gradientColorEnd = newColor; + this->currentItem->valueDataOptions->colorMapper->gradientColorEnd = newColor; this->ui.frameGradientEndColor->setPlainColor(newQColor); - this->ui.frameDataColor->setColorMapper(this->currentItem->colorMapper); + this->ui.frameDataColor->setColorMapper(this->currentItem->valueDataOptions->colorMapper); emit StyleChanged(); } } @@ -283,53 +292,58 @@ void StatisticsStyleControl::on_pushButtonGradientEditEndColor_clicked() void StatisticsStyleControl::on_spinBoxGradientRangeMin_valueChanged(int val) { - if (!this->currentItem || this->currentItem->colorMapper.mappingType != MappingType::Gradient) + if (!this->currentItem || + this->currentItem->valueDataOptions->colorMapper->mappingType != MappingType::Gradient) return; - this->currentItem->colorMapper.valueRange.min = val; - this->ui.frameDataColor->setColorMapper(this->currentItem->colorMapper); + this->currentItem->valueDataOptions->colorMapper->valueRange.min = val; + this->ui.frameDataColor->setColorMapper(this->currentItem->valueDataOptions->colorMapper); emit StyleChanged(); } void StatisticsStyleControl::on_spinBoxGradientRangeMax_valueChanged(int val) { - if (!this->currentItem || this->currentItem->colorMapper.mappingType != MappingType::Gradient) + if (!this->currentItem || + this->currentItem->valueDataOptions->colorMapper->mappingType != MappingType::Gradient) return; - this->currentItem->colorMapper.valueRange.max = val; - this->ui.frameDataColor->setColorMapper(this->currentItem->colorMapper); + this->currentItem->valueDataOptions->colorMapper->valueRange.max = val; + this->ui.frameDataColor->setColorMapper(this->currentItem->valueDataOptions->colorMapper); emit StyleChanged(); } void StatisticsStyleControl::on_comboBoxCustomMap_currentIndexChanged(int index) { - if (!this->currentItem || this->currentItem->colorMapper.mappingType != MappingType::Map || + if (!this->currentItem || + this->currentItem->valueDataOptions->colorMapper->mappingType != MappingType::Map || index < 0) return; - const auto customColormap = this->customColorMapStorage.at(size_t(index)); - this->currentItem->colorMapper.colorMap = customColormap.colorMap; - this->currentItem->colorMapper.colorMapOther = customColormap.other; - this->ui.frameDataColor->setColorMapper(this->currentItem->colorMapper); + const auto customColormap = this->customColorMapStorage.at(size_t(index)); + this->currentItem->valueDataOptions->colorMapper->colorMap = customColormap.colorMap; + this->currentItem->valueDataOptions->colorMapper->colorMapOther = customColormap.other; + this->ui.frameDataColor->setColorMapper(this->currentItem->valueDataOptions->colorMapper); emit StyleChanged(); } void StatisticsStyleControl::on_pushButtonEditMap_clicked() { - const auto originalColorMap = this->currentItem->colorMapper.colorMap; - const auto originalOtherColor = this->currentItem->colorMapper.colorMapOther; + const auto originalColorMap = this->currentItem->valueDataOptions->colorMapper->colorMap; + const auto originalOtherColor = this->currentItem->valueDataOptions->colorMapper->colorMapOther; StatisticsStyleControl_ColorMapEditor colorMapEditor(originalColorMap, originalOtherColor, this); - connect(&colorMapEditor, - &StatisticsStyleControl_ColorMapEditor::mapChanged, - [&]() - { - this->currentItem->colorMapper.colorMap = colorMapEditor.getColorMap(); - this->currentItem->colorMapper.colorMapOther = colorMapEditor.getOtherColor(); - this->ui.frameDataColor->setColorMapper(this->currentItem->colorMapper); - emit StyleChanged(); - }); + connect( + &colorMapEditor, + &StatisticsStyleControl_ColorMapEditor::mapChanged, + [&]() + { + this->currentItem->valueDataOptions->colorMapper->colorMap = colorMapEditor.getColorMap(); + this->currentItem->valueDataOptions->colorMapper->colorMapOther = + colorMapEditor.getOtherColor(); + this->ui.frameDataColor->setColorMapper(this->currentItem->valueDataOptions->colorMapper); + emit StyleChanged(); + }); if (colorMapEditor.exec() == QDialog::Accepted) { @@ -338,22 +352,24 @@ void StatisticsStyleControl::on_pushButtonEditMap_clicked() if (somethingChanged) { this->ui.comboBoxCustomMap->setCurrentIndex(-1); - this->currentItem->colorMapper.colorMap = colorMapEditor.getColorMap(); - this->currentItem->colorMapper.colorMapOther = colorMapEditor.getOtherColor(); + this->currentItem->valueDataOptions->colorMapper->colorMap = colorMapEditor.getColorMap(); + this->currentItem->valueDataOptions->colorMapper->colorMapOther = + colorMapEditor.getOtherColor(); } } else { - this->currentItem->colorMapper.colorMap = originalColorMap; - this->currentItem->colorMapper.colorMapOther = originalOtherColor; - this->ui.frameDataColor->setColorMapper(this->currentItem->colorMapper); + this->currentItem->valueDataOptions->colorMapper->colorMap = originalColorMap; + this->currentItem->valueDataOptions->colorMapper->colorMapOther = originalOtherColor; + this->ui.frameDataColor->setColorMapper(this->currentItem->valueDataOptions->colorMapper); emit StyleChanged(); } } void StatisticsStyleControl::on_pushButtonSaveMap_clicked() { - if (!this->currentItem || this->currentItem->colorMapper.mappingType != MappingType::Map) + if (!this->currentItem || + this->currentItem->valueDataOptions->colorMapper->mappingType != MappingType::Map) return; bool ok{}; @@ -376,10 +392,10 @@ void StatisticsStyleControl::on_pushButtonSaveMap_clicked() if (choice != QMessageBox::Yes) return; } - auto newIndex = - this->customColorMapStorage.saveAndGetIndex({name, - this->currentItem->colorMapper.colorMap, - this->currentItem->colorMapper.colorMapOther}); + auto newIndex = this->customColorMapStorage.saveAndGetIndex( + {name, + this->currentItem->valueDataOptions->colorMapper->colorMap, + this->currentItem->valueDataOptions->colorMapper->colorMapOther}); this->refreshComboBoxCustomMapFromStorage(); QSignalBlocker blockerPredefined(this->ui.comboBoxCustomMap); this->ui.comboBoxCustomMap->setCurrentIndex(int(newIndex)); @@ -401,49 +417,49 @@ void StatisticsStyleControl::on_pushButtonDeleteMap_clicked() void StatisticsStyleControl::on_comboBoxVectorLineStyle_currentIndexChanged(int index) { // Convert the selection to a pen style and set it - auto pattern = stats::AllPatterns.at(index); - this->currentItem->vectorStyle.pattern = pattern; + auto pattern = stats::AllPatterns.at(index); + this->currentItem->vectorDataOptions->style->pattern = pattern; emit StyleChanged(); } void StatisticsStyleControl::on_doubleSpinBoxVectorLineWidth_valueChanged(double width) { - this->currentItem->vectorStyle.width = width; + this->currentItem->vectorDataOptions->style->width = width; emit StyleChanged(); } void StatisticsStyleControl::on_checkBoxVectorScaleToZoom_stateChanged(int arg1) { - this->currentItem->scaleVectorToZoom = (arg1 != 0); + this->currentItem->vectorDataOptions->scaleToZoom = (arg1 != 0); emit StyleChanged(); } void StatisticsStyleControl::on_comboBoxVectorHeadStyle_currentIndexChanged(int index) { - this->currentItem->arrowHead = (stats::StatisticsType::ArrowHead)(index); + this->currentItem->vectorDataOptions->arrowHead = (stats::StatisticsType::ArrowHead)(index); emit StyleChanged(); } void StatisticsStyleControl::on_checkBoxVectorMapToColor_stateChanged(int arg1) { - this->currentItem->mapVectorToColor = (arg1 != 0); - ui.colorFrameVectorColor->setEnabled(!this->currentItem->mapVectorToColor); - ui.pushButtonEditVectorColor->setEnabled(!this->currentItem->mapVectorToColor); + this->currentItem->vectorDataOptions->mapToColor = (arg1 != 0); + ui.colorFrameVectorColor->setEnabled(!this->currentItem->vectorDataOptions->mapToColor); + ui.pushButtonEditVectorColor->setEnabled(!this->currentItem->vectorDataOptions->mapToColor); emit StyleChanged(); } void StatisticsStyleControl::on_colorFrameVectorColor_clicked() { - auto newQColor = - QColorDialog::getColor(functionsGui::toQColor(this->currentItem->vectorStyle.color), - this, - tr("Select vector color"), - QColorDialog::ShowAlphaChannel); + auto newQColor = QColorDialog::getColor( + functionsGui::toQColor(this->currentItem->vectorDataOptions->style->color), + this, + tr("Select vector color"), + QColorDialog::ShowAlphaChannel); auto newColor = functionsGui::toColor(newQColor); - if (newQColor.isValid() && newColor != this->currentItem->vectorStyle.color) + if (newQColor.isValid() && newColor != this->currentItem->vectorDataOptions->style->color) { - this->currentItem->vectorStyle.color = newColor; + this->currentItem->vectorDataOptions->style->color = newColor; this->ui.colorFrameVectorColor->setPlainColor(newQColor); emit StyleChanged(); } @@ -451,22 +467,22 @@ void StatisticsStyleControl::on_colorFrameVectorColor_clicked() void StatisticsStyleControl::on_groupBoxGrid_clicked(bool check) { - this->currentItem->renderGrid = check; + this->currentItem->gridOptions.render = check; emit StyleChanged(); } void StatisticsStyleControl::on_frameGridColor_clicked() { auto newQColor = - QColorDialog::getColor(functionsGui::toQColor(this->currentItem->gridStyle.color), + QColorDialog::getColor(functionsGui::toQColor(this->currentItem->gridOptions.style->color), this, tr("Select grid color"), QColorDialog::ShowAlphaChannel); auto newColor = functionsGui::toColor(newQColor); - if (newQColor.isValid() && newColor != this->currentItem->gridStyle.color) + if (newQColor.isValid() && newColor != this->currentItem->gridOptions.style->color) { - this->currentItem->gridStyle.color = newColor; + this->currentItem->gridOptions.style->color = newColor; this->ui.frameGridColor->setPlainColor(newQColor); emit StyleChanged(); } @@ -475,20 +491,20 @@ void StatisticsStyleControl::on_frameGridColor_clicked() void StatisticsStyleControl::on_comboBoxGridLineStyle_currentIndexChanged(int index) { // Convert the selection to a pen style and set it - auto pattern = stats::AllPatterns.at(index); - this->currentItem->gridStyle.pattern = pattern; + auto pattern = stats::AllPatterns.at(index); + this->currentItem->gridOptions.style->pattern = pattern; emit StyleChanged(); } void StatisticsStyleControl::on_doubleSpinBoxGridLineWidth_valueChanged(double width) { - this->currentItem->gridStyle.width = width; + this->currentItem->gridOptions.style->width = width; emit StyleChanged(); } void StatisticsStyleControl::on_checkBoxGridScaleToZoom_stateChanged(int arg1) { - this->currentItem->scaleGridToZoom = (arg1 != 0); + this->currentItem->gridOptions.scaleToZoom = (arg1 != 0); emit StyleChanged(); } diff --git a/YUViewLib/src/ui/views/SplitViewWidget.cpp b/YUViewLib/src/ui/views/SplitViewWidget.cpp index 185817de8..3b59f38a9 100644 --- a/YUViewLib/src/ui/views/SplitViewWidget.cpp +++ b/YUViewLib/src/ui/views/SplitViewWidget.cpp @@ -100,7 +100,7 @@ splitViewWidget::splitViewWidget(QWidget *parent) : MoveAndZoomableView(parent) setContextMenuPolicy(Qt::PreventContextMenu); // No test running yet - connect(&testProgrssUpdateTimer, &QTimer::timeout, this, [=] { updateTestProgress(); }); + connect(&testProgrssUpdateTimer, &QTimer::timeout, this, [this] { this->updateTestProgress(); }); // Initialize the font and the position of the zoom factor indication zoomFactorFont = QFont(SPLITVIEWWIDGET_ZOOMFACTOR_FONT, SPLITVIEWWIDGET_ZOOMFACTOR_FONTSIZE); @@ -235,7 +235,7 @@ void splitViewWidget::paintEvent(QPaintEvent *) // For the zoom box, calculate the pixel position under the cursor for each view. The following // things are calculated in this function: bool pixelPosInItem[2] = {false, - false}; //< Is the pixel position under the cursor within the item? + false}; //< Is the pixel position under the cursor within the item? QRect zoomPixelRect[2]; //< A QRect around the pixel that is under the cursor if (anyItemsSelected && this->drawZoomBox) { @@ -617,7 +617,7 @@ void splitViewWidget::setZoomBoxPixelUnderCursor(QPoint posA, } void splitViewWidget::paintZoomBox(int view, - QPainter & painter, + QPainter &painter, int xSplit, const QPoint &drawArea_botR, playlistItem *item, @@ -803,7 +803,7 @@ void splitViewWidget::paintRegularGrid(QPainter *painter, playlistItem *item) } } -void splitViewWidget::paintPixelRulersX(QPainter & painter, +void splitViewWidget::paintPixelRulersX(QPainter &painter, playlistItem *item, int xPixMin, int xPixMax, @@ -857,7 +857,7 @@ void splitViewWidget::paintPixelRulersX(QPainter & painter, } } -void splitViewWidget::paintPixelRulersY(QPainter & painter, +void splitViewWidget::paintPixelRulersY(QPainter &painter, playlistItem *item, int yPixMax, int xPos, @@ -1605,9 +1605,9 @@ void splitViewWidget::freezeView(bool freeze) } void splitViewWidget::getViewState(QPointF &offset, - double & zoom, - double & splitPoint, - int & mode) const + double &zoom, + double &splitPoint, + int &mode) const { offset = this->moveOffset; zoom = this->zoomFactor; @@ -1657,14 +1657,15 @@ void splitViewWidget::createMenuActions() const bool menuActionsCreatedYet = bool(this->actionSplitViewGroup); Q_ASSERT_X(!menuActionsCreatedYet, Q_FUNC_INFO, "Only call this initialization function once."); - auto configureAction = [this](QAction & action, + auto configureAction = [this](QAction &action, QActionGroup *const actionGroup, - const QString & text, + const QString &text, const bool checkable, const bool checked, void (splitViewWidget::*func)(bool), const QKeySequence &shortcut = {}, - const bool isEnabled = true) { + const bool isEnabled = true) + { action.setParent(this); action.setCheckable(checkable); action.setChecked(checked); diff --git a/YUViewLib/src/video/VideoCache.cpp b/YUViewLib/src/video/VideoCache.cpp index 7325bf383..d8a61854b 100644 --- a/YUViewLib/src/video/VideoCache.cpp +++ b/YUViewLib/src/video/VideoCache.cpp @@ -196,7 +196,7 @@ class VideoCache::loadingThread : public QThread { // We must wait until the worker is done. DEBUG_CACHING("loadingThread::quitWhenDone waiting for worker to finish..."); - connect(worker(), &loadingWorker::loadingFinished, this, [=] { + connect(worker(), &loadingWorker::loadingFinished, this, [=, this] { DEBUG_CACHING("loadingThread::quitWhenDone worker done -> quit"); quit(); }); @@ -264,8 +264,8 @@ VideoCache::VideoCache(PlaylistTreeWidget *playlistTreeWidget, &PlaybackController::signalPlaybackStarting, this, &VideoCache::updateCacheQueue); - connect(&statusUpdateTimer, &QTimer::timeout, this, [=] { emit updateCacheStatus(); }); - connect(&testProgrssUpdateTimer, &QTimer::timeout, this, [=] { updateTestProgress(); }); + connect(&statusUpdateTimer, &QTimer::timeout, this, [=, this] { emit updateCacheStatus(); }); + connect(&testProgrssUpdateTimer, &QTimer::timeout, this, [=, this] { updateTestProgress(); }); } VideoCache::~VideoCache() diff --git a/YUViewUnitTest/YUViewUnitTest.pro b/YUViewUnitTest/YUViewUnitTest.pro index 59e083a6b..e85885e20 100644 --- a/YUViewUnitTest/YUViewUnitTest.pro +++ b/YUViewUnitTest/YUViewUnitTest.pro @@ -1,4 +1,4 @@ -QT += core xml +QT += core xml widgets TARGET = YUViewUnitTest TEMPLATE = app @@ -6,7 +6,12 @@ TEMPLATE = app CONFIG += console CONFIG -= app_bundle CONFIG -= debug_and_release -CONFIG += c++17 + +CONFIG += c++20 +gcc { + # For gcc 9, setting 20 does not work. Must set c++2a. + equals(QMAKE_GCC_MAJOR_VERSION, 9): QMAKE_CXXFLAGS += -std=c++2a +} SOURCES += $$files(*.cpp, true) HEADERS += $$files(*.h, true) diff --git a/YUViewUnitTest/common/FunctionsTest.cpp b/YUViewUnitTest/common/FunctionsTest.cpp index b10bf40a4..8521e36c2 100644 --- a/YUViewUnitTest/common/FunctionsTest.cpp +++ b/YUViewUnitTest/common/FunctionsTest.cpp @@ -67,4 +67,21 @@ TEST(FunctionsTest, toInt) EXPECT_FALSE(functions::toInt("NotANumber")); } +TEST(FunctionsTest, scaleValueByPercent) +{ + EXPECT_EQ(functions::scaleValueByPercent(100, 0), 0); + EXPECT_EQ(functions::scaleValueByPercent(100, 1), 1); + EXPECT_EQ(functions::scaleValueByPercent(100, 50), 50); + EXPECT_EQ(functions::scaleValueByPercent(100, 99), 99); + EXPECT_EQ(functions::scaleValueByPercent(100, 100), 100); + EXPECT_EQ(functions::scaleValueByPercent(100, 200), 200); + + EXPECT_EQ(functions::scaleValueByPercent(100, 49.4), 49); + EXPECT_EQ(functions::scaleValueByPercent(100, 49.5), 50); + EXPECT_EQ(functions::scaleValueByPercent(100, 49.9), 50); + EXPECT_EQ(functions::scaleValueByPercent(100, 50.0), 50); + EXPECT_EQ(functions::scaleValueByPercent(100, 50.1), 50); + EXPECT_EQ(functions::scaleValueByPercent(100, 50.5), 51); +} + } // namespace diff --git a/YUViewUnitTest/common/ModifiedTest.cpp b/YUViewUnitTest/common/ModifiedTest.cpp new file mode 100644 index 000000000..f0e2f1f85 --- /dev/null +++ b/YUViewUnitTest/common/ModifiedTest.cpp @@ -0,0 +1,141 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * 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 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include + +TEST(ModifiedTest, TestInitialization_ShouldBeUnmodified) +{ + modified someValue = 22; + EXPECT_EQ(someValue, 22); + EXPECT_FALSE(someValue.wasModified()); + + modified somOtherValue{44}; + EXPECT_EQ(somOtherValue, 44); + EXPECT_FALSE(somOtherValue.wasModified()); +} + +TEST(ModifiedTest, TestModificationWithSameValue_ShouldReportUnmodified) +{ + modified someValue = 22; + someValue = 22; + EXPECT_FALSE(someValue.wasModified()); +} + +TEST(ModifiedTest, TestModificationWithNewValue_ShouldReportModification) +{ + modified someValue = 22; + someValue = 23; + EXPECT_TRUE(someValue.wasModified()); +} + +TEST(ModifiedTest, TestModificationWithNewValueAndThenWithInitialValue_ShouldReportUnmodified) +{ + modified someValue = 22; + someValue = 23; + EXPECT_TRUE(someValue.wasModified()); + + someValue = 22; + EXPECT_FALSE(someValue.wasModified()); +} + +TEST(ModifiedTest, TestValueRetrieval) +{ + modified someValue = 22; + EXPECT_EQ(*someValue, 22); + EXPECT_EQ(someValue.value(), 22); + + someValue = 33; + EXPECT_EQ(*someValue, 33); + EXPECT_EQ(someValue.value(), 33); +} + +TEST(ModifiedTest, TestOperatorArrow) +{ + struct TestStruct + { + int val; + bool operator==(const TestStruct &other) const { return this->val == other.val; } + }; + + modified someValue{}; + someValue->val = 55; + EXPECT_TRUE(someValue.wasModified()); + + someValue->val = 0; + EXPECT_FALSE(someValue.wasModified()); +} + +TEST(ModifiedTest, TestComparisonOperator_ShouldCompareToTDirectly) +{ + modified someValue = 22; + EXPECT_TRUE(someValue == 22); + EXPECT_EQ(someValue, 22); + + someValue = 33; + EXPECT_TRUE(someValue == 33); + EXPECT_EQ(someValue, 33); +} + +TEST(ModifiedTest, TestComparisonOfModifiedAndNotModifiedValues_ShouldOnlyCompareValues) +{ + modified unmodifiedValue = 22; + modified modifiedValue = 77; + modifiedValue = 22; + + EXPECT_FALSE(unmodifiedValue.wasModified()); + EXPECT_TRUE(modifiedValue.wasModified()); + EXPECT_EQ(unmodifiedValue, modifiedValue); +} + +TEST(ModifiedTest, TestSetUnmodified) +{ + modified someValue = 22; + + someValue = 33; + EXPECT_TRUE(someValue.wasModified()); + + someValue.setUnmodified(); + EXPECT_FALSE(someValue.wasModified()); +} + +TEST(ModiifedTest, TestImplicitConversionToT) +{ + modified someValue = 22; + const auto result = 44 + someValue; + EXPECT_EQ(result, 66); + + modified someFlag{true}; + EXPECT_TRUE(someFlag); + EXPECT_FALSE(!someFlag); +} diff --git a/YUViewUnitTest/statistics/StatisticsDataTest.cpp b/YUViewUnitTest/statistics/StatisticsDataTest.cpp index 479f8fb31..52cf6d1a0 100644 --- a/YUViewUnitTest/statistics/StatisticsDataTest.cpp +++ b/YUViewUnitTest/statistics/StatisticsDataTest.cpp @@ -33,21 +33,23 @@ #include #include +#include -namespace +namespace stats::test { TEST(StatisticsData, testPixelValueRetrievalInteger) { - stats::StatisticsData data; + StatisticsData data; constexpr auto typeID = 0; constexpr auto frameIndex = 0; - stats::StatisticsType valueType( - typeID, "Something", stats::color::ColorMapper({0, 10}, stats::color::PredefinedType::Jet)); - valueType.render = true; - data.addStatType(valueType); + data.addStatType(StatisticsTypeBuilder(typeID, "Something") + .withValueDataOptions({.colorMapper = stats::color::ColorMapper( + {0, 10}, stats::color::PredefinedType::Jet)}) + .withRender(true) + .build()); EXPECT_EQ(data.needsLoading(frameIndex), ItemLoadingState::LoadingNeeded); EXPECT_EQ(data.getTypesThatNeedLoading(frameIndex).size(), std::size_t(1)); @@ -68,15 +70,15 @@ TEST(StatisticsData, testPixelValueRetrievalInteger) TEST(StatisticsData, testPixelValueRetrievalVector) { - stats::StatisticsData data; + StatisticsData data; constexpr auto typeID = 0; constexpr auto frameIndex = 0; - using VectorScaling = int; - stats::StatisticsType valueType(typeID, "Something", VectorScaling(4)); - valueType.render = true; - data.addStatType(valueType); + data.addStatType(StatisticsTypeBuilder(typeID, "Something") + .withVectorDataOptions({.scale = 4}) + .withRender(true) + .build()); EXPECT_EQ(data.needsLoading(frameIndex), ItemLoadingState::LoadingNeeded); EXPECT_EQ(data.getTypesThatNeedLoading(frameIndex).size(), std::size_t(1)); @@ -95,4 +97,4 @@ TEST(StatisticsData, testPixelValueRetrievalVector) EXPECT_EQ(dataOutside.at(0), QStringPair({"Something", "-"})); } -} // namespace +} // namespace stats::test diff --git a/YUViewUnitTest/statistics/StatisticsFileCSVTest.cpp b/YUViewUnitTest/statistics/StatisticsFileCSVTest.cpp index 2a725b9b1..98aa3e25f 100644 --- a/YUViewUnitTest/statistics/StatisticsFileCSVTest.cpp +++ b/YUViewUnitTest/statistics/StatisticsFileCSVTest.cpp @@ -166,18 +166,18 @@ TEST(StatisticsFileCSV, testCSVFileParsing) // std::cout << "valueGridColors: " << valueGridColors.toStdString() << "\n"; const auto typeIDs = std::vector({9, 10, 11, 12, 7, 8, 5, 6, 0, 3, 4, 1}); - const auto typeNameNames = std::vector({"MVDL0", - "MVDL1", - "MVL0", - "MVL1", - "MVPIdxL0", - "MVPIdxL1", - "MergeIdxL0", - "MergeIdxL1", - "PredMode", - "RefFrmIdxL0", - "RefFrmIdxL1", - "Skipflag"}); + const auto typeNameNames = std::vector({"MVDL0", + "MVDL1", + "MVL0", + "MVL1", + "MVPIdxL0", + "MVPIdxL1", + "MergeIdxL0", + "MergeIdxL1", + "PredMode", + "RefFrmIdxL0", + "RefFrmIdxL1", + "Skipflag"}); const auto vectorColors = std::vector( {"#640000", "#006400", "#c80000", "#00c800", "", "", "", "", "", "", "", ""}); const auto vectorScaleFactors = std::vector({4, 4, 4, 4, -1, -1, -1, -1, -1, -1, -1, -1}); @@ -202,17 +202,17 @@ TEST(StatisticsFileCSV, testCSVFileParsing) EXPECT_EQ(t.typeID, typeIDs[i]); EXPECT_EQ(t.typeName, typeNameNames[i]); - if (t.hasVectorData) + if (t.vectorDataOptions) { - EXPECT_EQ(t.vectorStyle.color.toHex(), vectorColors[i]); - EXPECT_EQ(t.vectorScale, vectorScaleFactors[i]); + EXPECT_EQ(t.vectorDataOptions->style->color.toHex(), vectorColors[i]); + EXPECT_EQ(t.vectorDataOptions->scale, vectorScaleFactors[i]); } - if (t.hasValueData) + if (t.valueDataOptions) { - EXPECT_EQ(t.colorMapper.valueRange.min, valueColorRangeMin[i]); - EXPECT_EQ(t.colorMapper.valueRange.max, valueColorRangeMax[i]); - EXPECT_EQ(t.colorMapper.predefinedType, stats::color::PredefinedType::Jet); - EXPECT_EQ(t.gridStyle.color.toHex(), valueGridColors[i]); + EXPECT_EQ(t.valueDataOptions->colorMapper->valueRange.min, valueColorRangeMin[i]); + EXPECT_EQ(t.valueDataOptions->colorMapper->valueRange.max, valueColorRangeMax[i]); + EXPECT_EQ(t.valueDataOptions->colorMapper->predefinedType, stats::color::PredefinedType::Jet); + EXPECT_EQ(t.gridOptions.style->color.toHex(), valueGridColors[i]); } } diff --git a/YUViewUnitTest/statistics/StatisticsFileVTMBMSTest.cpp b/YUViewUnitTest/statistics/StatisticsFileVTMBMSTest.cpp deleted file mode 100644 index 4f6471c12..000000000 --- a/YUViewUnitTest/statistics/StatisticsFileVTMBMSTest.cpp +++ /dev/null @@ -1,220 +0,0 @@ -/* This file is part of YUView - The YUV player with advanced analytics toolset - * - * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY - * - * 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 3 of the License, or - * (at your option) any later version. - * - * In addition, as a special exception, the copyright holders give - * permission to link the code of portions of this program with the - * OpenSSL library under certain conditions as described in each - * individual source file, and distribute linked combinations including - * the two. - * - * You must obey the GNU General Public License in all respects for all - * of the code used other than OpenSSL. If you modify file(s) with this - * exception, you may extend this exception to your version of the - * file(s), but you are not obligated to do so. If you do not wish to do - * so, delete this exception statement from your version. If you delete - * this exception statement from all source files in the program, then - * also delete it here. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include - -#include "CheckFunctions.h" - -#include -#include - -namespace -{ - -ByteVector getVTMBSTestData() -{ - const std::string stats_str = - R"(# VTMBMS Block Statistics -# Sequence size: [2048x 872] -# Block Statistic Type: PredMode; Integer; [0, 4] -# Block Statistic Type: MVDL0; Vector; Scale: 4 -# Block Statistic Type: AffineMVL0; AffineTFVectors; Scale: 4 -# Block Statistic Type: GeoPartitioning; Line; -# Block Statistic Type: GeoMVL0; VectorPolygon; Scale: 4 -BlockStat: POC 0 @( 0, 0) [64x64] PredMode=1 -BlockStat: POC 0 @( 64, 0) [32x16] PredMode=1 -BlockStat: POC 0 @( 384, 0) [64x64] PredMode=2 -BlockStat: POC 0 @( 520, 32) [16x32] PredMode=4 -BlockStat: POC 0 @( 320, 0) [32x24] PredMode=1 -BlockStat: POC 0 @( 320, 64) [32x8] PredMode=1 -BlockStat: POC 2 @( 384, 128) [64x64] MVDL0={ 12, 3} -BlockStat: POC 2 @( 384, 128) [64x64] PredMode=1 -BlockStat: POC 2 @( 448, 128) [64x64] PredMode=2 -BlockStat: POC 2 @( 384, 192) [64x64] MVDL0={ 52, -44} -BlockStat: POC 2 @( 480, 192) [ 8x16] MVDL0={ 520, 888} -BlockStat: POC 2 @( 488, 192) [ 8x16] MVDL0={ -234, -256} -BlockStat: POC 2 @( 496, 192) [16x16] PredMode=2 -BlockStat: POC 8 @( 640, 128) [128x128] AffineMVL0={ -61,-128, -53,-101, -99,-139} -BlockStat: POC 8 @(1296, 128) [32x64] AffineMVL0={ -68, 32, -65, 39, -79, 25} -BlockStat: POC 8 @(1328, 128) [16x64] AffineMVL0={ -64, 40, -63, 43, -75, 33} -BlockStat: POC 8 @( 288, 544) [16x32] GeoPartitioning={ 5, 0, 6, 32} -BlockStat: POC 8 @(1156, 592) [ 8x 8] GeoPartitioning={ 1, 0, 8, 5} -BlockStat: POC 8 @( 276, 672) [ 8x16] GeoPartitioning={ 5, 0, 3, 16} -BlockStat: POC 8 @[(240, 384)--(256, 384)--(256, 430)--(240, 416)--] GeoMVL0={ 291, 233} -BlockStat: POC 8 @[(1156, 592)--(1157, 592)--(1164, 597)--(1164, 600)--(1156, 600)--] GeoMVL0={ 152, 24} -BlockStat: POC 8 @[(544, 760)--(545, 768)--(544, 768)--] GeoMVL0={ 180, 38} -)"; - - ByteVector data(stats_str.begin(), stats_str.end()); - return data; -} - -TEST(StatisticsFileCSV, testCSVFileParsing) -{ - yuviewTest::TemporaryFile vtmbmsFile(getVTMBSTestData()); - - stats::StatisticsData statData; - stats::StatisticsFileVTMBMS statFile(QString::fromStdString(vtmbmsFile.getFilePathString()), - statData); - - EXPECT_EQ(statData.getFrameSize(), Size(2048, 872)); - - auto types = statData.getStatisticsTypes(); - EXPECT_EQ(types.size(), size_t(5)); - - EXPECT_EQ(types[0].typeID, 1); - EXPECT_EQ(types[1].typeID, 2); - EXPECT_EQ(types[2].typeID, 3); - EXPECT_EQ(types[3].typeID, 4); - EXPECT_EQ(types[4].typeID, 5); - EXPECT_EQ(types[0].typeName, QString("PredMode")); - EXPECT_EQ(types[1].typeName, QString("MVDL0")); - EXPECT_EQ(types[2].typeName, QString("AffineMVL0")); - EXPECT_EQ(types[3].typeName, QString("GeoPartitioning")); - EXPECT_EQ(types[4].typeName, QString("GeoMVL0")); - - EXPECT_EQ(types[0].hasVectorData, false); - EXPECT_EQ(types[0].hasValueData, true); - EXPECT_EQ(types[0].colorMapper.valueRange.min, 0); - EXPECT_EQ(types[0].colorMapper.valueRange.max, 4); - EXPECT_EQ(types[0].colorMapper.predefinedType, stats::color::PredefinedType::Jet); - EXPECT_EQ(types[0].gridStyle.color.toHex(), std::string("#000000")); - - EXPECT_EQ(types[1].hasVectorData, true); - EXPECT_EQ(types[1].hasValueData, false); - auto debugName = types[1].vectorStyle.color.toHex(); - EXPECT_EQ(types[1].vectorStyle.color.toHex(), std::string("#ff0000")); - EXPECT_EQ(types[1].vectorScale, 4); - - EXPECT_EQ(types[2].hasVectorData, false); - EXPECT_EQ(types[2].hasValueData, false); - EXPECT_EQ(types[2].hasAffineTFData, true); - debugName = types[2].vectorStyle.color.toHex(); - EXPECT_EQ(types[2].vectorStyle.color.toHex(), std::string("#ff0000")); - EXPECT_EQ(types[2].vectorScale, 4); - - EXPECT_EQ(types[3].hasVectorData, true); - EXPECT_EQ(types[3].hasValueData, false); - debugName = types[3].vectorStyle.color.toHex(); - EXPECT_EQ(types[3].vectorStyle.color.toHex(), std::string("#ffffff")); - debugName = types[3].gridStyle.color.toHex(); - EXPECT_EQ(types[3].gridStyle.color.toHex(), std::string("#ffffff")); - EXPECT_EQ(types[3].vectorScale, 1); - - EXPECT_EQ(types[4].hasVectorData, true); - EXPECT_EQ(types[4].hasValueData, false); - debugName = types[4].vectorStyle.color.toHex(); - EXPECT_EQ(types[4].vectorStyle.color.toHex(), std::string("#ff0000")); - EXPECT_EQ(types[4].vectorScale, 4); - EXPECT_EQ(types[4].isPolygon, true); - - // We did not let the file parse the positions of the start of each poc/type yet so loading should - // not yield any data yet. - statFile.loadStatisticData(statData, 0, 1); - EXPECT_EQ(statData.getFrameIndex(), 0); - { - auto &frameData = statData[9]; - EXPECT_EQ(frameData.vectorData.size(), size_t(0)); - EXPECT_EQ(frameData.valueData.size(), size_t(0)); - EXPECT_EQ(frameData.affineTFData.size(), size_t(0)); - EXPECT_EQ(frameData.polygonValueData.size(), size_t(0)); - EXPECT_EQ(frameData.polygonVectorData.size(), size_t(0)); - } - - std::atomic_bool breakAtomic; - breakAtomic.store(false); - statFile.readFrameAndTypePositionsFromFile(std::ref(breakAtomic)); - - // Now we should get the data - statFile.loadStatisticData(statData, 0, 1); - EXPECT_EQ(statData.getFrameIndex(), 0); - EXPECT_EQ(statData[2].valueData.size(), size_t(0)); - yuviewTest::statistics::checkValueList(statData[1].valueData, - {{0, 0, 64, 64, 1}, - {64, 0, 32, 16, 1}, - {384, 0, 64, 64, 2}, - {520, 32, 16, 32, 4}, - {320, 0, 32, 24, 1}, - {320, 64, 32, 8, 1}}); - EXPECT_EQ(statData[1].vectorData.size(), size_t(0)); - - statFile.loadStatisticData(statData, 2, 1); - statFile.loadStatisticData(statData, 2, 2); - EXPECT_EQ(statData.getFrameIndex(), 2); - yuviewTest::statistics::checkValueList( - statData[1].valueData, {{384, 128, 64, 64, 1}, {448, 128, 64, 64, 2}, {496, 192, 16, 16, 2}}); - EXPECT_EQ(statData[1].vectorData.size(), size_t(0)); - - yuviewTest::statistics::checkVectorList(statData[2].vectorData, - {{384, 128, 64, 64, 12, 3}, - {384, 192, 64, 64, 52, -44}, - {480, 192, 8, 16, 520, 888}, - {488, 192, 8, 16, -234, -256}}); - EXPECT_EQ(statData[2].valueData.size(), size_t(0)); - - statFile.loadStatisticData(statData, 8, 3); - EXPECT_EQ(statData.getFrameIndex(), 8); - yuviewTest::statistics::checkAffineTFVectorList( - statData[3].affineTFData, - { - {640, 128, 128, 128, -61, -128, -53, -101, -99, -139}, - {1296, 128, 32, 64, -68, 32, -65, 39, -79, 25}, - {1328, 128, 16, 64, -64, 40, -63, 43, -75, 33}, - }); - - statFile.loadStatisticData(statData, 8, 4); - EXPECT_EQ(statData.getFrameIndex(), 8); - yuviewTest::statistics::checkLineList(statData[4].vectorData, - { - {288, 544, 16, 32, 5, 0, 6, 32}, - {1156, 592, 8, 8, 1, 0, 8, 5}, - {276, 672, 8, 16, 5, 0, 3, 16}, - }); - - statFile.loadStatisticData(statData, 8, 5); - EXPECT_EQ(statData.getFrameIndex(), 8); - yuviewTest::statistics::checkPolygonvectorList( - statData[5].polygonVectorData, - { - {291, - 233, - {240, 256, 256, 240, 0}, - {384, 384, 430, 416, 0}}, // 4 pt polygon, zeros for padding test struct - {152, 24, {1156, 1157, 1164, 1164, 1156}, {592, 592, 597, 600, 600}}, - {180, - 38, - {544, 545, 544, 0, 0}, - {760, 768, 768, 0, 0}} // 3 pt polygon, zeros for padding test struct - }); -} - -} // namespace diff --git a/YUViewUnitTest/statistics/StatisticsTypeBuilderTest.cpp b/YUViewUnitTest/statistics/StatisticsTypeBuilderTest.cpp new file mode 100644 index 000000000..6cfb32765 --- /dev/null +++ b/YUViewUnitTest/statistics/StatisticsTypeBuilderTest.cpp @@ -0,0 +1,163 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * 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 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include + +namespace stats::test +{ + +TEST(StatisticsTypeBuilderTest, DefaultValues) +{ + const auto statisticsType = StatisticsTypeBuilder(0, "").build(); + + EXPECT_EQ(statisticsType.typeID, 0); + EXPECT_TRUE(statisticsType.typeName.empty()); + EXPECT_TRUE(statisticsType.description.empty()); + EXPECT_FALSE(statisticsType.render); + EXPECT_EQ(statisticsType.alphaFactor, 50); + EXPECT_FALSE(statisticsType.valueDataOptions); + EXPECT_FALSE(statisticsType.vectorDataOptions); + EXPECT_FALSE(statisticsType.gridOptions.render); + EXPECT_EQ(statisticsType.gridOptions.style, LineDrawStyle()); + EXPECT_FALSE(statisticsType.gridOptions.scaleToZoom); +} + +TEST(StatisticsTypeBuilderTest, SetTypeNameDescriptionAndRenderValues) +{ + const auto statisticsType = StatisticsTypeBuilder(1, "TestType") + .withDescription("TestDescription") + .withRender(true) + .withAlphaFactor(75) + .build(); + + EXPECT_EQ(statisticsType.typeID, 1); + EXPECT_EQ(statisticsType.typeName, "TestType"); + EXPECT_EQ(statisticsType.description, "TestDescription"); + EXPECT_TRUE(statisticsType.render); + EXPECT_EQ(statisticsType.alphaFactor, 75); +} + +TEST(StatisticsTypeBuilderTest, SetValueDatDefaultValues) +{ + const auto statisticsType = + StatisticsTypeBuilder(0, "").withValueDataOptions(StatisticsType::ValueDataOptions()).build(); + + EXPECT_TRUE(statisticsType.valueDataOptions); + EXPECT_EQ(statisticsType.valueDataOptions->render, true); + EXPECT_EQ(statisticsType.valueDataOptions->scaleToBlockSize, false); + EXPECT_EQ(statisticsType.valueDataOptions->colorMapper, color::ColorMapper()); +} + +TEST(StatisticsTypeBuilderTest, SetValueDataCustomValues) +{ + const color::ColorMapper colorMapper({0, 255}, color::PredefinedType::Jet); + + const auto statisticsType = + stats::StatisticsTypeBuilder(0, "") + .withValueDataOptions( + {.render = false, .scaleToBlockSize = true, .colorMapper = colorMapper}) + .build(); + + EXPECT_TRUE(statisticsType.valueDataOptions); + EXPECT_EQ(statisticsType.valueDataOptions->render, false); + EXPECT_EQ(statisticsType.valueDataOptions->scaleToBlockSize, true); + EXPECT_EQ(statisticsType.valueDataOptions->colorMapper, colorMapper); +} + +TEST(StatisticsTypeBuilderTest, SetVectorDataDefaultValues) +{ + const auto statisticsType = StatisticsTypeBuilder(0, "") + .withVectorDataOptions(StatisticsType::VectorDataOptions()) + .build(); + + EXPECT_TRUE(statisticsType.vectorDataOptions); + EXPECT_TRUE(statisticsType.vectorDataOptions->render); + EXPECT_TRUE(statisticsType.vectorDataOptions->renderDataValues); + EXPECT_FALSE(statisticsType.vectorDataOptions->scaleToZoom); + EXPECT_EQ(statisticsType.vectorDataOptions->style, LineDrawStyle()); + EXPECT_EQ(statisticsType.vectorDataOptions->scale, 0); + EXPECT_FALSE(statisticsType.vectorDataOptions->mapToColor); + EXPECT_EQ(statisticsType.vectorDataOptions->arrowHead, StatisticsType::ArrowHead::arrow); +} + +TEST(StatisticsTypeBuilderTest, SetVectorDataCustomValues) +{ + const LineDrawStyle lineDrawStyle({Color(255, 0, 0), 2, Pattern::DashDot}); + + const auto statisticsType = StatisticsTypeBuilder(0, "") + .withVectorDataOptions({ + .render = false, + .renderDataValues = false, + .scaleToZoom = true, + .style = lineDrawStyle, + .scale = 3, + .mapToColor = true, + .arrowHead = StatisticsType::ArrowHead::circle, + }) + .build(); + + EXPECT_TRUE(statisticsType.vectorDataOptions); + EXPECT_FALSE(statisticsType.vectorDataOptions->render); + EXPECT_FALSE(statisticsType.vectorDataOptions->renderDataValues); + EXPECT_TRUE(statisticsType.vectorDataOptions->scaleToZoom); + EXPECT_EQ(statisticsType.vectorDataOptions->style, lineDrawStyle); + EXPECT_EQ(statisticsType.vectorDataOptions->scale, 3); + EXPECT_TRUE(statisticsType.vectorDataOptions->mapToColor); + EXPECT_EQ(statisticsType.vectorDataOptions->arrowHead, StatisticsType::ArrowHead::circle); +} + +TEST(StatisticsTypeBuilderTest, SetGridOptionsDefaultValues) +{ + const auto statisticsType = StatisticsTypeBuilder(0, "").withGridOptions({}).build(); + + EXPECT_FALSE(statisticsType.gridOptions.render); + EXPECT_EQ(statisticsType.gridOptions.style, LineDrawStyle()); + EXPECT_FALSE(statisticsType.gridOptions.scaleToZoom); +} + +TEST(StatisticsTypeBuilderTest, SetGridOptionsCustomValues) +{ + const LineDrawStyle lineDrawStyle({Color(123, 44, 99), 5, Pattern::DashDot}); + + const auto statisticsType = + StatisticsTypeBuilder(0, "") + .withGridOptions({.render = true, .style = lineDrawStyle, .scaleToZoom = true}) + .build(); + + EXPECT_TRUE(statisticsType.gridOptions.render); + EXPECT_EQ(statisticsType.gridOptions.style, lineDrawStyle); + EXPECT_TRUE(statisticsType.gridOptions.scaleToZoom); +} + +} // namespace stats::test diff --git a/YUViewUnitTest/statistics/StatisticsTypeTest.cpp b/YUViewUnitTest/statistics/StatisticsTypeTest.cpp new file mode 100644 index 000000000..206a0cd0d --- /dev/null +++ b/YUViewUnitTest/statistics/StatisticsTypeTest.cpp @@ -0,0 +1,197 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * 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 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include + +namespace stats::test +{ + +TEST(StatisticsTypeTest, GetValueText) +{ + const auto statisticsType = + StatisticsTypeBuilder(0, "").withMappingValues({"First", "Second", "Third"}).build(); + + EXPECT_EQ(statisticsType.getValueText(0), "First (0)"); + EXPECT_EQ(statisticsType.getValueText(1), "Second (1)"); + EXPECT_EQ(statisticsType.getValueText(2), "Third (2)"); + EXPECT_EQ(statisticsType.getValueText(3), "3"); + EXPECT_EQ(statisticsType.getValueText(99), "99"); + EXPECT_EQ(statisticsType.getValueText(1256), "1256"); + EXPECT_EQ(statisticsType.getValueText(-1), "-1"); +} + +TEST(StatisticsTypeTest, TestValueDataEqualityOperator) +{ + const StatisticsType::ValueDataOptions options = { + .render = true, .scaleToBlockSize = false, .colorMapper = color::ColorMapper()}; + + const StatisticsType::ValueDataOptions identicalOptions = { + .render = true, .scaleToBlockSize = false, .colorMapper = color::ColorMapper()}; + + const StatisticsType::ValueDataOptions optionsWithDifferentRenderFlag = { + .render = false, .scaleToBlockSize = false, .colorMapper = color::ColorMapper()}; + + const StatisticsType::ValueDataOptions optionsWithDifferentScaleToBlockSize = { + .render = true, .scaleToBlockSize = true, .colorMapper = color::ColorMapper()}; + + const StatisticsType::ValueDataOptions optionsWithDifferentColorMapper = { + .render = false, + .scaleToBlockSize = false, + .colorMapper = color::ColorMapper({0, 255}, Color(0, 0, 0), Color(0, 0, 255))}; + + EXPECT_TRUE(options == identicalOptions); + EXPECT_FALSE(options == optionsWithDifferentRenderFlag); + EXPECT_FALSE(options == optionsWithDifferentScaleToBlockSize); + EXPECT_FALSE(options == optionsWithDifferentColorMapper); +} + +TEST(StatisticsTypeTest, TestVectorDataEqualityOperator) +{ + const StatisticsType::VectorDataOptions options = {.render = true, + .renderDataValues = false, + .scaleToZoom = true, + .style = LineDrawStyle(), + .scale = 3, + .mapToColor = true, + .arrowHead = + StatisticsType::ArrowHead::circle}; + + const StatisticsType::VectorDataOptions identicalOptions = { + .render = true, + .renderDataValues = false, + .scaleToZoom = true, + .style = LineDrawStyle(), + .scale = 3, + .mapToColor = true, + .arrowHead = StatisticsType::ArrowHead::circle}; + + const StatisticsType::VectorDataOptions optionsWithDifferentRenderFlag = { + .render = false, + .renderDataValues = false, + .scaleToZoom = true, + .style = LineDrawStyle(), + .scale = 3, + .mapToColor = true, + .arrowHead = StatisticsType::ArrowHead::circle}; + + const StatisticsType::VectorDataOptions optionsWithDifferentRenderDataValuesFlag = { + .render = true, + .renderDataValues = true, + .scaleToZoom = true, + .style = LineDrawStyle(), + .scale = 3, + .mapToColor = true, + .arrowHead = StatisticsType::ArrowHead::circle}; + + const StatisticsType::VectorDataOptions optionsWithDifferentScaleToZoomFlag = { + .render = true, + .renderDataValues = false, + .scaleToZoom = false, + .style = LineDrawStyle(), + .scale = 3, + .mapToColor = true, + .arrowHead = StatisticsType::ArrowHead::circle}; + + const StatisticsType::VectorDataOptions optionsWithDifferentStyle = { + .render = true, + .renderDataValues = false, + .scaleToZoom = true, + .style = LineDrawStyle({Color(255, 0, 0), 2, Pattern::DashDot}), + .scale = 3, + .mapToColor = true, + .arrowHead = StatisticsType::ArrowHead::circle}; + + const StatisticsType::VectorDataOptions optionsWithDifferentScale = { + .render = true, + .renderDataValues = false, + .scaleToZoom = true, + .style = LineDrawStyle(), + .scale = 4, + .mapToColor = true, + .arrowHead = StatisticsType::ArrowHead::circle}; + + const StatisticsType::VectorDataOptions optionsWithDifferentMapToColorFlag = { + .render = true, + .renderDataValues = false, + .scaleToZoom = true, + .style = LineDrawStyle(), + .scale = 3, + .mapToColor = false, + .arrowHead = StatisticsType::ArrowHead::circle}; + + const StatisticsType::VectorDataOptions optionsWithDifferentArrowHead = { + .render = true, + .renderDataValues = false, + .scaleToZoom = true, + .style = LineDrawStyle(), + .scale = 3, + .mapToColor = true, + .arrowHead = StatisticsType::ArrowHead::arrow}; + + EXPECT_TRUE(options == identicalOptions); + EXPECT_FALSE(options == optionsWithDifferentRenderFlag); + EXPECT_FALSE(options == optionsWithDifferentRenderDataValuesFlag); + EXPECT_FALSE(options == optionsWithDifferentScaleToZoomFlag); + EXPECT_FALSE(options == optionsWithDifferentStyle); + EXPECT_FALSE(options == optionsWithDifferentScale); + EXPECT_FALSE(options == optionsWithDifferentMapToColorFlag); + EXPECT_FALSE(options == optionsWithDifferentArrowHead); +} + +TEST(StatisticsTypeTest, TestGridOptionsEqualityOperator) +{ + const StatisticsType::GridOptions options = { + .render = true, .style = LineDrawStyle(), .scaleToZoom = true}; + + const StatisticsType::GridOptions identicalOptions = { + .render = true, .style = LineDrawStyle(), .scaleToZoom = true}; + + const StatisticsType::GridOptions optionsWithDifferentRenderFlag = { + .render = false, .style = LineDrawStyle(), .scaleToZoom = true}; + + const StatisticsType::GridOptions optionsWithDifferentStyle = { + .render = true, + .style = LineDrawStyle({Color(255, 0, 0), 2, Pattern::DashDot}), + .scaleToZoom = true}; + + const StatisticsType::GridOptions optionsWithDifferentScaleToZoomFlag = { + .render = true, .style = LineDrawStyle(), .scaleToZoom = false}; + + EXPECT_TRUE(options == identicalOptions); + EXPECT_FALSE(options == optionsWithDifferentRenderFlag); + EXPECT_FALSE(options == optionsWithDifferentStyle); + EXPECT_FALSE(options == optionsWithDifferentScaleToZoomFlag); +} + +} // namespace stats::test