diff --git a/.gitmodules b/.gitmodules index fbcb25c31..712f09c06 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "external/stb"] path = external/stb url = https://github.com/nothings/stb.git +[submodule "external/linalg"] + path = external/linalg + url = https://github.com/sgorsten/linalg.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 69079e5d4..e3e72dea5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -550,14 +550,11 @@ if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-psabi") endif() - if(COMPILER_SUPPORTS_CXX17 AND Qt_VERSION EQUAL 6) - message(STATUS "Enabling support for C++17 for QT6") + if(COMPILER_SUPPORTS_CXX17) + message(STATUS "Enabling support for C++17") set(CMAKE_CXX_STANDARD 17) - elseif(COMPILER_SUPPORTS_CXX11) - message(STATUS "Enabling support for C++11") - set(CMAKE_CXX_STANDARD 11) else() - message(STATUS "No support for C++11 detected. Compilation will most likely fail on your compiler") + message(STATUS "No support for C++17 detected. Compilation will most likely fail on your compiler") endif() else() include(CheckCXXCompilerFlag) @@ -691,8 +688,8 @@ add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_D include (${CMAKE_CURRENT_SOURCE_DIR}/cmake/packages.cmake) # external targets -if (WIN32 AND TARGET stb AND TARGET flatbuffers AND TARGET protobuf-nanopb AND TARGET lunasvg AND TARGET flatc AND TARGET qmqtt AND TARGET liblzma AND TARGET sqlite3) - set_target_properties(stb qmqtt flatbuffers protobuf-nanopb lunasvg flatc resources uninstall liblzma sqlite3 PROPERTIES FOLDER ExternalLibsTargets) +if (WIN32 AND TARGET stb AND TARGET flatbuffers AND TARGET protobuf-nanopb AND TARGET lunasvg AND TARGET flatc AND TARGET qmqtt AND TARGET liblzma AND TARGET sqlite3 AND TARGET precompiled_hyperhdr_headers) + set_target_properties(stb qmqtt flatbuffers protobuf-nanopb lunasvg flatc resources uninstall liblzma sqlite3 precompiled_hyperhdr_headers PROPERTIES FOLDER ExternalLibsTargets) else() set_target_properties(resources uninstall PROPERTIES FOLDER ExternalLibsTargets) endif() diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index f4d1272c2..4107ef732 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -78,6 +78,14 @@ if(ENABLE_WS281XPWM) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/rpi_ws281x) endif() +#============================================================================= +# LINALG +#============================================================================= + +add_library(linalg INTERFACE) +target_compile_definitions(linalg INTERFACE LINALG_FORWARD_COMPATIBLE ) +target_include_directories(linalg INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/linalg") + #============================================================================= # LUNASVG #============================================================================= diff --git a/external/linalg b/external/linalg new file mode 160000 index 000000000..4460f1f5b --- /dev/null +++ b/external/linalg @@ -0,0 +1 @@ +Subproject commit 4460f1f5b85ccc81ffcf49aa450d454db58ca90e diff --git a/include/api/BaseAPI.h b/include/api/BaseAPI.h index f8d182e63..4039ca968 100644 --- a/include/api/BaseAPI.h +++ b/include/api/BaseAPI.h @@ -1,5 +1,10 @@ #pragma once +#ifndef PCH_ENABLED + #include + #include +#endif + #include #include #include @@ -18,12 +23,12 @@ class BaseAPI : public QObject struct ImageCmdData { - int priority; + int priority = 0; QString origin; - int64_t duration; - int width; - int height; - int scale; + int64_t duration = 0; + int width = 0; + int height = 0; + int scale = 0; QString format; QString imgName; QString imagedata; @@ -122,6 +127,7 @@ class BaseAPI : public QObject std::shared_ptr _systemGrabber; std::shared_ptr _performanceCounters; std::shared_ptr _discoveryWrapper; + std::unique_ptr> _lutCalibratorThread; struct { bool init = false; diff --git a/include/api/CallbackAPI.h b/include/api/CallbackAPI.h index 83d1404fd..187cec371 100644 --- a/include/api/CallbackAPI.h +++ b/include/api/CallbackAPI.h @@ -32,7 +32,6 @@ class CallbackAPI : public BaseAPI void subscribe(QJsonArray subsArr); protected: - std::unique_ptr _lutCalibrator; Image _liveImage; void stopDataConnections() override = 0; @@ -59,11 +58,12 @@ private slots: void instancesListChangedHandler(); void tokenChangeHandler(const QVector& def); void signalBenchmarkUpdateHandler(int status, QString message); - void lutCalibrationUpdateHandler(const QJsonObject& data); void performanceUpdateHandler(const QJsonObject& data); #ifdef ENABLE_BONJOUR void signalDiscoveryFoundServiceHandler(DiscoveryRecord::Service type, QList records); #endif +public slots: + void lutCalibrationUpdateHandler(const QJsonObject& data); private: QStringList _availableCommands; diff --git a/include/base/Grabber.h b/include/base/Grabber.h index 0ab0369af..954597fe1 100644 --- a/include/base/Grabber.h +++ b/include/base/Grabber.h @@ -117,9 +117,7 @@ class Grabber : public DetectionAutomatic, public DetectionManual, protected Lut void setSignalDetectionEnable(bool enable); - void setAutoSignalDetectionEnable(bool enable); - - void benchmarkCapture(int status, QString message); + void setAutoSignalDetectionEnable(bool enable); QList getVideoDeviceModesFullInfo(const QString& devicePath); @@ -163,6 +161,8 @@ public slots: QStringList getVideoDevices() const; + void signalSetLutHandler(MemoryBuffer* lut); + signals: void SignalNewCapturedFrame(const Image& image); @@ -176,6 +176,8 @@ public slots: int getTargetSystemFrameDimension(int& targetSizeX, int& targetSizeY); + int getTargetSystemFrameDimension(int actualWidth, int actualHeight, int& targetSizeX, int& targetSizeY); + void processSystemFrameBGRA(uint8_t* source, int lineSize = 0, bool useLut = true); void processSystemFrameBGR(uint8_t* source, int lineSize = 0); @@ -268,9 +270,6 @@ public slots: bool _signalDetectionEnabled; bool _signalAutoDetectionEnabled; QSemaphore _synchro; - - int _benchmarkStatus; - QString _benchmarkMessage; }; bool sortDevicePropertiesItem(const Grabber::DevicePropertiesItem& v1, const Grabber::DevicePropertiesItem& v2); diff --git a/include/base/GrabberWrapper.h b/include/base/GrabberWrapper.h index 6fc81411c..44c55dc9b 100644 --- a/include/base/GrabberWrapper.h +++ b/include/base/GrabberWrapper.h @@ -39,8 +39,6 @@ public slots: void stop(); void revive(); - void benchmarkCapture(int status, QString message); - QJsonObject getJsonInfo(); QJsonDocument startCalibration(); @@ -57,7 +55,6 @@ private slots: void SignalNewVideoImage(const QString& name, const Image& image); void SignalVideoStreamChanged(QString device, QString videoMode); void SignalCecKeyPressed(int key); - void SignalBenchmarkUpdate(int status, QString message); void SignalInstancePauseChanged(int instance, bool isEnabled); void SignalSetNewComponentStateToAllInstances(hyperhdr::Components component, bool enable); void SignalSaveCalibration(QString saveData); diff --git a/include/base/HyperHdrManager.h b/include/base/HyperHdrManager.h index d42891d71..1683dd071 100644 --- a/include/base/HyperHdrManager.h +++ b/include/base/HyperHdrManager.h @@ -6,6 +6,7 @@ #endif #include +#include #include #include #include @@ -43,6 +44,8 @@ class HyperHdrManager : public QObject bool areInstancesReady(); public slots: + void handleRequestComponent(hyperhdr::Components component, int hyperHdrInd, bool listen); + void setSmoothing(int time); void setSignalStateByCEC(bool enable); @@ -101,6 +104,9 @@ public slots: void SignalInstancePauseChanged(int instance, bool isEnabled); + void SignalBenchmarkUpdate(int status, QString message); + void SignalBenchmarkCapture(int status, QString message); + private slots: void handleInstanceJustStarted(); @@ -126,4 +132,5 @@ private slots: int _fireStarter; QMap _pendingRequests; + VideoBenchmark _videoBenchmark; }; diff --git a/include/flatbuffers/server/FlatBuffersServer.h b/include/flatbuffers/server/FlatBuffersServer.h index cfc44af82..854c4e696 100644 --- a/include/flatbuffers/server/FlatBuffersServer.h +++ b/include/flatbuffers/server/FlatBuffersServer.h @@ -37,6 +37,8 @@ class FlatBuffersServer : public QObject, protected LutLoader void SignalImportFromProto(int priority, int duration, const Image& image, QString clientDescription); public slots: + void handleRequestComponent(hyperhdr::Components component, int hyperHdrInd, bool listen); + void signalSetLutHandler(MemoryBuffer* lut); void handleSettingsUpdate(settings::type type, const QJsonDocument& config); void initServer(); int getHdrToneMappingEnabled(); @@ -70,4 +72,6 @@ private slots: QString _userLutFile; PixelFormat _currentLutPixelFormat; int _flatbufferToneMappingMode; + bool _quarterOfFrameMode; + bool _active; }; diff --git a/include/grabber/windows/DX/DxGrabber.h b/include/grabber/windows/DX/DxGrabber.h index 74cbe9915..ff3e6a5bc 100644 --- a/include/grabber/windows/DX/DxGrabber.h +++ b/include/grabber/windows/DX/DxGrabber.h @@ -18,6 +18,8 @@ #include #include #include + #include + #include #endif // util includes @@ -25,6 +27,52 @@ #include #include +template void SafeRelease(T** ppT) +{ + if (*ppT) + { + (*ppT)->Release(); + *ppT = NULL; + } +} + +struct DisplayHandle +{ + QString name; + int warningCounter = 6; + bool wideGamut = false; + int actualDivide = -1, actualWidth = 0, actualHeight = 0; + uint targetMonitorNits = 0; + ID3D11Texture2D* d3dConvertTexture = nullptr; + ID3D11RenderTargetView* d3dRenderTargetView = nullptr; + ID3D11ShaderResourceView* d3dConvertTextureView = nullptr; + ID3D11VertexShader* d3dVertexShader = nullptr; + ID3D11PixelShader* d3dPixelShader = nullptr; + ID3D11Buffer* d3dBuffer = nullptr; + ID3D11SamplerState* d3dSampler = nullptr; + ID3D11InputLayout* d3dVertexLayout = nullptr; + IDXGIOutputDuplication* d3dDuplicate = nullptr; + ID3D11Texture2D* d3dSourceTexture = nullptr; + DXGI_OUTDUPL_DESC surfaceProperties{}; + + DisplayHandle() = default; + DisplayHandle(const DisplayHandle&) = delete; + ~DisplayHandle() + { + SafeRelease(&d3dRenderTargetView); + SafeRelease(&d3dSourceTexture); + SafeRelease(&d3dConvertTextureView); + SafeRelease(&d3dConvertTexture); + SafeRelease(&d3dVertexShader); + SafeRelease(&d3dVertexLayout); + SafeRelease(&d3dPixelShader); + SafeRelease(&d3dSampler); + SafeRelease(&d3dBuffer); + SafeRelease(&d3dDuplicate); + printf("SmartPointer is removing: DisplayHandle for %s\n", QSTRING_CSTR(name)); + }; +}; + class DxGrabber : public Grabber { Q_OBJECT @@ -56,6 +104,13 @@ public slots: void restart(); private: + + const QString MULTI_MONITOR = "MULTI-MONITOR"; + + void captureFrame(DisplayHandle& display); + + int captureFrame(DisplayHandle& display, Image& image); + QString GetSharedLut(); void enumerateDevices(bool silent); @@ -70,29 +125,17 @@ public slots: bool initDirectX(QString selectedDeviceName); - bool initShaders(); - HRESULT deepScaledCopy(ID3D11Texture2D* source); + bool initShaders(DisplayHandle& display); + HRESULT deepScaledCopy(DisplayHandle& display, ID3D11Texture2D* source); QString _configurationPath; QTimer* _timer; QTimer* _retryTimer; - int _warningCounter; - int _actualDivide; - bool _wideGamut; + bool _multiMonitor; bool _dxRestartNow; + std::list> _handles; ID3D11Device* _d3dDevice; ID3D11DeviceContext* _d3dContext; - ID3D11Buffer* _d3dBuffer; - ID3D11SamplerState* _d3dSampler; - ID3D11InputLayout* _d3dVertexLayout; - ID3D11VertexShader* _d3dVertexShader; - ID3D11PixelShader* _d3dPixelShader; - ID3D11Texture2D* _d3dSourceTexture; - ID3D11Texture2D* _d3dConvertTexture; - ID3D11ShaderResourceView* _d3dConvertTextureView; - ID3D11RenderTargetView* _d3dRenderTargetView; - IDXGIOutputDuplication* _d3dDuplicate; - DXGI_OUTDUPL_DESC _surfaceProperties; Image _cacheImage; }; diff --git a/include/image/ColorRgb.h b/include/image/ColorRgb.h index 27591993d..e28556bfd 100644 --- a/include/image/ColorRgb.h +++ b/include/image/ColorRgb.h @@ -4,8 +4,11 @@ #include #include #include + #include #endif + + struct ColorRgb { uint8_t red = 0; @@ -100,6 +103,11 @@ struct ColorRgb return (x < 0) ? 0 : ((x > 255) ? 255 : uint8_t(x)); } + inline static uint8_t round(double x) + { + return (x < 0) ? 0 : ((x > 255) ? 255 : static_cast(std::lround(x))); + } + static void rgb2hsv(uint8_t red, uint8_t green, uint8_t blue, uint16_t& _hue, uint8_t& _saturation, uint8_t& _value); static void hsv2rgb(uint16_t hue, uint8_t saturation, uint8_t value, uint8_t& red, uint8_t& green, uint8_t& blue); static void rgb2hsl(uint8_t red, uint8_t green, uint8_t blue, uint16_t& hue, float& saturation, float& luminance); diff --git a/include/image/Image.h b/include/image/Image.h index 78c1d45b9..931c47601 100644 --- a/include/image/Image.h +++ b/include/image/Image.h @@ -2,6 +2,8 @@ #include +enum class PixelFormat; + template class Image { @@ -28,6 +30,8 @@ class Image void gradientVBox(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t r, uint8_t g, uint8_t b); + void insertHorizontal(int x, Image& source); + unsigned width() const; unsigned height() const; @@ -36,6 +40,10 @@ class Image ColorSpace& operator()(unsigned x, unsigned y); + void setOriginFormat(PixelFormat pf); + + PixelFormat getOriginFormat() const; + void resize(unsigned width, unsigned height); uint8_t* rawMem(); @@ -46,6 +54,9 @@ class Image void clear(); + bool save(const char* filename) const; + private: std::shared_ptr> _sharedData; + PixelFormat _pixelFormat; }; diff --git a/include/lut-calibrator/BestResult.h b/include/lut-calibrator/BestResult.h new file mode 100644 index 000000000..b615944f7 --- /dev/null +++ b/include/lut-calibrator/BestResult.h @@ -0,0 +1,129 @@ +#pragma once + +/* BestResult.h +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#ifndef PCH_ENABLED + #include + #include + #include + #include + #include + + #include + #include + #include +#endif + +#include +#include +#include +#include + +using namespace linalg; +using namespace aliases; +using namespace ColorSpaceMath; +using namespace BoardUtils; + +struct LchLists +{ + std::list low; + std::list mid; + std::list high; +}; + +struct BestResult +{ + YuvConverter::YUV_COEFS coef = YuvConverter::YUV_COEFS::BT601; + double4x4 coefMatrix; + double2 coefDelta; + int coloredAspectMode = 0; + std::pair colorAspect; + double3 aspect; + int bt2020Range = 0; + int altConvert = 0; + double3x3 altPrimariesToSrgb; + ColorSpaceMath::HDR_GAMMA gamma = ColorSpaceMath::HDR_GAMMA::PQ; + double gammaHLG = 0; + double nits = 0; + bool lchEnabled = false; + LchLists lchPrimaries; + + struct Signal + { + YuvConverter::COLOR_RANGE range = YuvConverter::COLOR_RANGE::FULL; + double yRange = 0; + double upYLimit = 0; + double downYLimit = 0; + double yShift = 0; + bool isSourceP010 = false; + } signal; + + long long int minError = MAX_CALIBRATION_ERROR; + + void serializePrimaries(std::stringstream& out) const + { + for (const auto& p : { lchPrimaries.low, lchPrimaries.mid, lchPrimaries.high }) + { + out << std::endl << "\t\t\tstd::list{" << std::endl << "\t\t\t\t"; + for (const auto& v : p) + { + out << "double4"; ColorSpaceMath::serialize(out, v); out << ", "; + } + out << std::endl << "\t\t\t}," << std::endl; + } + } + + void serialize(std::stringstream& out) const + { + out.precision(12); + out << "/*" << std::endl; + out << "BestResult bestResult;" << std::endl; + out << "bestResult.coef = YuvConverter::YUV_COEFS(" << std::to_string(coef) << ");" << std::endl; + out << "bestResult.coefMatrix = double4x4"; ColorSpaceMath::serialize(out, coefMatrix); out << ";" << std::endl; + out << "bestResult.coefDelta = double2"; ColorSpaceMath::serialize(out, coefDelta); out << ";" << std::endl; + out << "bestResult.coloredAspectMode = " << std::to_string(coloredAspectMode) << ";" << std::endl; + out << "bestResult.colorAspect = std::pair(double3"; ColorSpaceMath::serialize(out, colorAspect.first); out << ", double3"; ColorSpaceMath::serialize(out, colorAspect.second); out << ");" << std::endl; + out << "bestResult.aspect = double3"; ColorSpaceMath::serialize(out, aspect); out << ";" << std::endl; + out << "bestResult.bt2020Range = " << std::to_string(bt2020Range) << ";" << std::endl; + out << "bestResult.altConvert = " << std::to_string(altConvert) << ";" << std::endl; + out << "bestResult.altPrimariesToSrgb = double3x3"; ColorSpaceMath::serialize(out, altPrimariesToSrgb); out << ";" << std::endl; + out << "bestResult.gamma = ColorSpaceMath::HDR_GAMMA(" << std::to_string(gamma) << ");" << std::endl; + out << "bestResult.gammaHLG = " << std::to_string(gammaHLG) << ";" << std::endl; + out << "bestResult.lchEnabled = " << std::to_string(lchEnabled) << ";" << std::endl; + out << "bestResult.lchPrimaries = LchLists{"; serializePrimaries(out); out << "\t\t};" << std::endl; + out << "bestResult.nits = " << std::to_string(nits) << ";" << std::endl; + out << "bestResult.signal.range = YuvConverter::COLOR_RANGE(" << std::to_string(signal.range) << ");" << std::endl; + out << "bestResult.signal.yRange = " << std::to_string(signal.yRange) << ";" << std::endl; + out << "bestResult.signal.upYLimit = " << std::to_string(signal.upYLimit) << ";" << std::endl; + out << "bestResult.signal.downYLimit = " << std::to_string(signal.downYLimit) << ";" << std::endl; + out << "bestResult.signal.yShift = " << std::to_string(signal.yShift) << ";" << std::endl; + out << "bestResult.signal.isSourceP010 = " << std::to_string(signal.isSourceP010) << ";" << std::endl; + out << "bestResult.minError = " << std::to_string(std::round(minError * 100.0) / 30000.0) << ";" << std::endl; + out << "*/" << std::endl; + } +}; diff --git a/include/lut-calibrator/BoardUtils.h b/include/lut-calibrator/BoardUtils.h new file mode 100644 index 000000000..9a36427dc --- /dev/null +++ b/include/lut-calibrator/BoardUtils.h @@ -0,0 +1,105 @@ +#pragma once + +/* BoardUtils.h +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#ifndef PCH_ENABLED + #include + #include + #include +#endif + +#include +#include +#include +#include +#include +#include +#include + +namespace BoardUtils +{ + class CapturedColors; + + constexpr long long int MAX_CALIBRATION_ERROR = 500000; + + constexpr int SCREEN_BLOCKS_X = 48; + constexpr int SCREEN_BLOCKS_Y = 30; + constexpr int SCREEN_COLOR_STEP = 16; + constexpr int SCREEN_COLOR_DIMENSION = (256 / SCREEN_COLOR_STEP) + 1; + + constexpr int MAX_INDEX = SCREEN_COLOR_DIMENSION - 1; + + constexpr int IMPORT_SCALE = 1000000; + + constexpr int SCREEN_YUV_RANGE_LIMIT = 2; + + constexpr int SCREEN_CRC_LINES = 2; + constexpr int SCREEN_CRC_COUNT = 5; + constexpr int SCREEN_MAX_CRC_BRIGHTNESS_ERROR = 1; + constexpr int SCREEN_MAX_COLOR_NOISE_ERROR = 16; + constexpr int SCREEN_SAMPLES_PER_BOARD = (SCREEN_BLOCKS_X / 2) * (SCREEN_BLOCKS_Y - SCREEN_CRC_LINES); + const int SCREEN_LAST_BOARD_INDEX = std::pow(SCREEN_COLOR_DIMENSION, 3) / SCREEN_SAMPLES_PER_BOARD; + + int indexToColorAndPos(int index, byte3& color, int2& position); + CapturedColor readBlock(const Image& yuvImage, int2 position, byte3* _color = nullptr); + void getWhiteBlackColorLevels(const Image& yuvImage, CapturedColor& white, CapturedColor& black, int& line); + bool verifyBlackColorPattern(const Image& yuvImage, bool isFirstWhite, CapturedColor& black); + bool parseBoard(Logger* _log, const Image& yuvImage, int& boardIndex, CapturedColors& allColors, bool multiFrame = false); + Image loadTestBoardAsYuv(const std::string& filename); + void createTestBoards(const char* pattern = "D:/table_%1.png"); + bool verifyTestBoards(Logger* _log, const char* pattern = "D:/table_%1.png"); + + class CapturedColors + { + private: + int _capturedFlag = 0; + YuvConverter::COLOR_RANGE _range = YuvConverter::COLOR_RANGE::UNKNOWN; + + double _yRange = 0; + double _yShift = 0; + double _downYLimit = 0; + double _upYLimit = 0; + + public: + CapturedColors() = default; + + std::vector>> all = std::vector(BoardUtils::SCREEN_COLOR_DIMENSION, + std::vector>(BoardUtils::SCREEN_COLOR_DIMENSION, + std::vector (BoardUtils::SCREEN_COLOR_DIMENSION)));; + + bool isCaptured(int index) const; + bool areAllCaptured(); + void finilizeBoard(); + static void correctYRange(double3& yuv, double yRange, double upYLimit, double downYLimit, double yShift); + void getSignalParams(double& yRange, double& upYLimit, double& downYLimit, double& yShift); + void setCaptured(int index); + void setRange(YuvConverter::COLOR_RANGE range); + YuvConverter::COLOR_RANGE getRange() const; + bool saveResult(const char* filename = "D:/result.txt", const std::string& result = ""); + }; +}; diff --git a/include/lut-calibrator/CalibrationWorker.h b/include/lut-calibrator/CalibrationWorker.h new file mode 100644 index 000000000..218004b66 --- /dev/null +++ b/include/lut-calibrator/CalibrationWorker.h @@ -0,0 +1,100 @@ +#pragma once + +/* CalibrationWorker.h +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#include +#include +#include + +using namespace linalg; +using namespace aliases; +using namespace ColorSpaceMath; +using namespace BoardUtils; + +class CalibrationWorker : public QObject, public QRunnable +{ + Q_OBJECT + + BestResult bestResult; + YuvConverter* yuvConverter; + const int id; + const int krIndexStart; + const int krIndexEnd; + const int halfKDelta; + const bool precise; + const int coef; + const std::vector>& sampleColors; + const int gamma; + const double gammaHLG; + const double NITS; + const double3x3& bt2020_to_sRgb; + std::list> vertex; + std::atomic& weakBestScore; + const bool lchCorrection; + std::atomic& forcedExit; + std::atomic& progress; +public: + CalibrationWorker(BestResult* _bestResult, std::atomic& _weakBestScore, YuvConverter* _yuvConverter, const int _id, const int _krIndexStart, const int _krIndexEnd, const int _halfKDelta, const bool _precise, const int _coef, + const std::vector>& _sampleColors, const int _gamma, const double _gammaHLG, const double _NITS, const double3x3& _bt2020_to_sRgb, + const std::list& _vertex, const bool _lchCorrection, std::atomic& _forcedExit, std::atomic& _progress) : + yuvConverter(_yuvConverter), + id(_id), + krIndexStart(_krIndexStart), + krIndexEnd(_krIndexEnd), + halfKDelta(_halfKDelta), + precise(_precise), + coef(_coef), + sampleColors(_sampleColors), + gamma(_gamma), + gammaHLG(_gammaHLG), + NITS(_NITS), + bt2020_to_sRgb(_bt2020_to_sRgb), + weakBestScore(_weakBestScore), + lchCorrection(_lchCorrection), + forcedExit(_forcedExit), + progress(_progress) + { + bestResult = *_bestResult; + this->setAutoDelete(false); + for (auto& v : _vertex) + { + vertex.push_back(std::pair(v, double3{})); + } + }; + + void run() override; + void getBestResult(BestResult* otherScore) const + { + if (otherScore->minError > bestResult.minError) + { + (*otherScore) = bestResult; + } + }; +signals: + void notifyCalibrationMessage(QString message, bool started); +}; diff --git a/include/lut-calibrator/CapturedColor.h b/include/lut-calibrator/CapturedColor.h new file mode 100644 index 000000000..4ba97b4bc --- /dev/null +++ b/include/lut-calibrator/CapturedColor.h @@ -0,0 +1,91 @@ +#pragma once + +/* CapturedColor.h +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#ifndef PCH_ENABLED + #include + #include + #include + #include +#endif + +#include +#include +#include +#include + + +class CapturedColor +{ +public: + enum LchPrimaries { NONE, LOW, MID, HIGH }; + +private: + int totalSamples = 0; + int3 sourceRGB; + double3 sourceLch; + std::list finalRGB; + double3 color; + std::list> inputColors; + std::list> sortedInputYUVColors; + std::list> sortedInputYuvColors; + byte3 min, max; + byte3 colorInt; + byte3 arrayCoords; + LchPrimaries lchPrimary = LchPrimaries::NONE; + +public: + CapturedColor() = default; + + const double& y() const { return color.x; } + const double3& yuv() const { return color; } + const byte3& coords() const { return arrayCoords; } + + const uint8_t& Y() const { return colorInt.x; } + const uint8_t& U() const { return colorInt.y; } + const uint8_t& V() const { return colorInt.z; } + + void importColors(const CapturedColor& color); + bool calculateFinalColor(); + bool hasAllSamples(); + bool hasAnySample(); + std::list> getInputYUVColors() const; + std::list> getInputYuvColors() const; + void addColor(ColorRgb i); + void addColor(const byte3& i); + void setCoords(const byte3& i); + void setSourceRGB(byte3 _color); + int getSourceError(const int3& _color) const; + int3 getSourceRGB() const; + void setFinalRGB(byte3 input); + LchPrimaries isLchPrimary(double3* _lchCoords) const; + + std::list getFinalRGB() const; + + QString toString(); +}; diff --git a/include/lut-calibrator/ColorSpace.h b/include/lut-calibrator/ColorSpace.h new file mode 100644 index 000000000..800013c3d --- /dev/null +++ b/include/lut-calibrator/ColorSpace.h @@ -0,0 +1,211 @@ +#pragma once + +/* ColorSpace.h +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#ifndef PCH_ENABLED + #include + #include + #include + #include + #include + #include +#endif + +#include +#include + +using namespace linalg; +using namespace aliases; + +namespace ColorSpaceMath +{ + enum PRIMARIES { SRGB = 0, BT_2020, WIDE_GAMMUT }; + + QString gammaToString(HDR_GAMMA gamma); + + constexpr mat matrix(std::array m) + { + double3 c1(m[0], m[3], m[6]); + double3 c2(m[1], m[4], m[7]); + double3 c3(m[2], m[5], m[8]); + return double3x3(c1, c2, c3); + } + + constexpr mat matrix4(std::array m) + { + double4 c1(m[0], m[4], m[8], m[12]); + double4 c2(m[1], m[5], m[9], m[13]); + double4 c3(m[2], m[6], m[10], m[14]); + double4 c4(m[3], m[7], m[11], m[15]); + return double4x4(c1, c2, c3, c4); + } + + constexpr double3x3 matrix_bt2020_to_XYZ = matrix({ + 0.636958, 0.144617, 0.168881, + 0.262700, 0.677998, 0.059302, + 0.000000, 0.028073, 1.060985 + }); + + constexpr double3x3 matrix_sRgb_to_XYZ = matrix({ + 0.4124564, 0.3575761, 0.1804375, + 0.2126729, 0.7151522, 0.0721750, + 0.0193339, 0.1191920, 0.9503041 + }); + + std::vector getPrimaries(PRIMARIES primary); + + mat getPrimariesToXYZ(PRIMARIES primary); + + double3 bt2020_nonlinear_to_linear(double3 input); + + double3 bt2020_linear_to_nonlinear(double3 input); + + double srgb_nonlinear_to_linear(double input); + + double3 srgb_nonlinear_to_linear(double3 input); + + double3 srgb_linear_to_nonlinear(double3 input); + + double srgb_linear_to_nonlinear(double input); + + double3 from_BT2020_to_BT709(double3 a); + + double PQ_ST2084(double scale, double nonlinear); + + double3 PQ_ST2084(double scale, double3 nonlinear); + + double inverse_OETF_HLG(double input); + + double3 inverse_OETF_HLG(double3 input); + + double3 OOTF_HLG(double3 input, double gamma = 1.2); + double3 OOTF_HLG(double _input, double gamma = 1.2); + + double3 from_bt2020_to_XYZ(double3 x); + + double3 from_XYZ_to_bt2020(double3 x); + + double3 from_XYZ_to_sRGB(double3 x); + + double3 from_sRGB_to_XYZ(double3 x); + + double2 XYZ_to_xy(const double3& a); + + constexpr double3x3 to_XYZ( + const double2& red_xy, + const double2& green_xy, + const double2& blue_xy, + const double2& white_xy + ) + { + double3 r(red_xy.x, red_xy.y, 1.0 - (red_xy.x + red_xy.y)); + double3 g(green_xy.x, green_xy.y, 1.0 - (green_xy.x + green_xy.y)); + double3 b(blue_xy.x, blue_xy.y, 1.0 - (blue_xy.x + blue_xy.y)); + double3 w(white_xy.x, white_xy.y, 1.0 - (white_xy.x + white_xy.y)); + + w /= white_xy.y; + + double3x3 retMat(r, g, b); + + double3x3 invMat; + invMat = linalg::inverse(retMat); + + double3 scale = linalg::mul(invMat, w); + + retMat.x *= scale.x; + retMat.y *= scale.y; + retMat.z *= scale.z; + + return retMat; + }; + + double3 xyz_to_lab(const double3& xyz); + + double3 lab_to_xyz(const double3& lab); + + double3 lab_to_lch(const double3& lab); + + double3 lch_to_lab(double3 lch); + + double3 xyz_to_lch(const double3& xyz); + + double3 lch_to_xyz(const double3& lch); + + double3 rgb2hsv(double3 rgb); + + double3 hsv2rgb(double3 hsv); + + float3 rgb2hsv(float3 rgb); + + float3 hsv2rgb(float3 hsv); + + double2 primaryRotateAndScale(const double2 primary, + const double scaling, + const double rotation, + const std::vector& primaries, + bool truncate = false); + + byte3 to_byte3(const double3& v); + + int3 to_int3(const byte3& v); + + int3 to_int3(const double3& v); + + double3 to_double3(const byte3& v); + + void trim01(double3& input); + + + QString vecToString(const double2& v); + + QString vecToString(const double3& v); + + QString vecToString(const double4& v); + + QString vecToString(const byte3& v); + + QString vecToString(const int3& v); + + QString matToString(double4x4 m); + + QString matToString(double3x3 m); + + + + void serialize(std::stringstream& out, const double2& v); + + void serialize(std::stringstream& out, const double3& v); + + void serialize(std::stringstream& out, const double4& v); + + + void serialize(std::stringstream& out, const double4x4& m); + + void serialize(std::stringstream& out, const double3x3& m); +}; + diff --git a/include/lut-calibrator/LutCalibrator.h b/include/lut-calibrator/LutCalibrator.h index fc58ee831..8245bfa02 100644 --- a/include/lut-calibrator/LutCalibrator.h +++ b/include/lut-calibrator/LutCalibrator.h @@ -40,150 +40,73 @@ #include #include #include +#include class Logger; class GrabberWrapper; +class YuvConverter; +class CapturedColor; + +namespace BoardUtils +{ + class CapturedColors; +}; + +namespace linalg { + template struct mat; + template struct vec; +} + +namespace ColorSpaceMath { + enum HDR_GAMMA { PQ = 0, HLG, sRGB, BT2020inSRGB, PQinSRGB, P010 }; +} + +struct BestResult; class LutCalibrator : public QObject { Q_OBJECT -private: - static LutCalibrator* instance; - struct ColorStat - { - double red = 0, green = 0, blue = 0, count = 0, scaledRed = 1, scaledGreen = 1, scaledBlue = 1; - - ColorStat() = default; - - ColorStat(double r, double g, double b) - { - red = r; - green = g; - blue = b; - } - - void calculateFinalColor() - { - red = red / count; - green = green / count; - blue = blue / count; - - if (red > 1 && green > 1 && blue > 1) - { - double scale = qMax(red, qMax(green, blue)); - scaledRed = scale / red; - scaledGreen = scale / green; - scaledBlue = scale / blue; - } - } - - void reset() - { - red = 0; - green = 0; - blue = 0; - count = 0; - scaledRed = 0; - scaledGreen = 0; - scaledBlue = 0; - } - - void AddColor(ColorRgb y) - { - red += y.red; - green += y.green; - blue += y.blue; - count++; - } - - ColorStat& operator/=(const double x) - { - this->red /= x; - this->green /= x; - this->blue /= x; - return *this; - } - - QString toQString() - { - return QString("(%1, %2, %3)").arg(red).arg(green).arg(blue); - } - }; - - enum capColors { Red = 0, Green = 1, Blue = 2, Yellow = 3, Magenta = 4, Cyan = 5, Orange = 6, Pink = 7, Azure = 8, Brown = 9, Purple = 10, LowRed = 11, LowGreen = 12, LowBlue = 13, LowestGray = 14, - Gray1 = 15, Gray2 = 16, Gray3 = 17, Gray4 = 18, Gray5 = 19, Gray6 = 20, Gray7 = 21, Gray8 = 22, HighestGray = 23, White = 24, None = 25 }; - public: - LutCalibrator(); + LutCalibrator(QString rootpath, hyperhdr::Components defaultComp, bool debug, bool lchCorrection); ~LutCalibrator(); + static void sendReport(Logger* _log, QString report); + static QString CreateLutFile(Logger* _log, QString _rootPath, BestResult* bestResult, std::vector>>* all); signals: void SignalLutCalibrationUpdated(const QJsonObject& data); public slots: - void incomingCommand(QString rootpath, GrabberWrapper* grabberWrapper, hyperhdr::Components defaultComp, int checksum, ColorRgb startColor, ColorRgb endColor, bool limitedRange, double saturation, double luminance, double gammaR, double gammaG, double gammaB, int coef); + void startHandler(); void stopHandler(); void setVideoImage(const QString& name, const Image& image); void setSystemImage(const QString& name, const Image& image); void signalSetGlobalImageHandler(int priority, const Image& image, int timeout_ms, hyperhdr::Components origin); + void calibrate(); + void cancelCalibrationSafe(); + void notifyCalibrationMessage(QString message, bool started = false); private: + void fineTune(bool precise); + void printReport(); + QString generateReport(bool full); + bool set1to1LUT(); + void notifyCalibrationFinished(); + void error(QString message); void handleImage(const Image& image); - bool increaseColor(ColorRgb& color); - void storeColor(const ColorRgb& inputColor, const ColorRgb& color); - bool finalize(bool fastTrack = false); - bool correctionEnd(); - - inline int clampInt(int val, int min, int max) { return qMin(qMax(val, min), max);} - inline double clampDouble(double val, double min, double max) { return qMin(qMax(val, min), max); } - inline int clampToInt(double val, int min, int max) { return qMin(qMax(qRound(val), min), max); } - - double eotf(double scale, double x) noexcept; - double inverse_eotf(double x) noexcept; - double ootf(double v) noexcept; - double inverse_gamma(double x) noexcept; - void colorCorrection(double& r, double& g, double& b); - void balanceGray(int r, int g, int b, double& _r, double& _g, double& _b); - void fromBT2020toXYZ(double r, double g, double b, double& x, double& y, double& z); - void fromXYZtoBT709(double x, double y, double z, double& r, double& g, double& b); - void fromBT2020toBT709(double x, double y, double z, double& r, double& g, double& b); - void toneMapping(double xhdr, double yhdr, double zhdr, double& xsdr, double& ysdr, double& zsdr); - QString colorToQStr(capColors index); - QString colorToQStr(ColorRgb color); - QString calColorToQStr(capColors index); - void displayPreCalibrationInfo(); - void displayPostCalibrationInfo(); - double fineTune(double& optimalRange, double& optimalScale, int& optimalWhite, int& optimalStrategy); - double getError(ColorRgb first, ColorStat second); - void applyFilter(); + void calibration(); + void setupWhitePointCorrection(); + bool setTestData(); + void capturedPrimariesCorrection(ColorSpaceMath::HDR_GAMMA gamma, double gammaHLG, double nits, int coef, linalg::mat& convert_bt2020_to_XYZ, linalg::mat& convert_XYZ_to_corrected, bool printDebug = false); Logger* _log; - bool _mjpegCalibration; - bool _finish; - bool _limitedRange; - int _checksum; - int _currentCoef; - double _coefsResult[3]; - int _warningCRC; - int _warningMismatch; - double _saturation; - double _luminance; - double _gammaR; - double _gammaG; - double _gammaB; - qint64 _timeStamp; - ColorRgb _startColor; - ColorRgb _endColor; - ColorRgb _minColor; - ColorRgb _maxColor; - ColorStat _colorBalance[26]; + std::shared_ptr _capturedColors; + std::shared_ptr _yuvConverter; + std::shared_ptr< BestResult> bestResult; MemoryBuffer _lut; QString _rootPath; - - static ColorRgb primeColors[]; - - // Color coefs YUV to RGB: http://avisynth.nl/index.php/Color_conversions - // FCC, Rec.709, Rec.601 coefficients - ColorStat _coefs[3] = { ColorStat(0.3, 0.59, 0.11), ColorStat(0.2126, 0.7152, 0.0722), ColorStat(0.299, 0.587, 0.114)}; + bool _debug; + bool _lchCorrection; + hyperhdr::Components _defaultComp; + std::atomic _forcedExit; }; diff --git a/include/lut-calibrator/YuvConverter.h b/include/lut-calibrator/YuvConverter.h new file mode 100644 index 000000000..f061d6dca --- /dev/null +++ b/include/lut-calibrator/YuvConverter.h @@ -0,0 +1,70 @@ +#pragma once + +/* YuvConverter.h +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#ifndef PCH_ENABLED + #include + #include +#endif + +#include +#include + +using namespace linalg; +using namespace aliases; + +class YuvConverter +{ +public: + enum YUV_COEFS { FCC = 0, BT601 = 1, BT709 = 2, BT2020 = 3 }; + enum COLOR_RANGE { UNKNOWN = 0, FULL = 1, LIMITED = 2 }; + enum YUV_DIRECTION { FROM_RGB_TO_YUV = 0, FROM_YUV_TO_RGB = 1 }; + + const std::map knownCoeffs = { + {YUV_COEFS::FCC, {0.3, 0.11 } }, + {YUV_COEFS::BT601, {0.2990, 0.1140} }, + {YUV_COEFS::BT709, {0.2126, 0.0722} }, + {YUV_COEFS::BT2020, {0.2627, 0.0593} } + }; + + double3 multiplyColorMatrix(double4x4 matrix, const double3& input) const; + double3 toRgb(COLOR_RANGE range, YUV_COEFS coef, const double3& input) const; + double3 toYuv(COLOR_RANGE range, YUV_COEFS coef, const double3& input) const; + double3 toYuvBT709(COLOR_RANGE range, const double3& input) const; + QString coefToString(YUV_COEFS cf) const; + YuvConverter(); + QString toString(); + double2 getCoef(YUV_COEFS cf); + byte3 yuv_to_rgb(YUV_COEFS coef, COLOR_RANGE range, const byte3& input) const; + double4x4 create_yuv_to_rgb_matrix(COLOR_RANGE range, double Kr, double Kb) const; + +private: + std::map> yuv2rgb; + std::map> rgb2yuv; + std::map rgb2yuvBT709; +}; diff --git a/include/utils-image/utils-image.h b/include/utils-image/utils-image.h index 911727c89..5d31aad96 100644 --- a/include/utils-image/utils-image.h +++ b/include/utils-image/utils-image.h @@ -16,8 +16,9 @@ namespace utils_image { Image _IMAGE_SHARED_API load2image(const uint8_t* buffer, size_t size); + Image _IMAGE_SHARED_API load2image(const std::string& filename); void _IMAGE_SHARED_API svg2png(const std::string& svgFile, int width, int height, std::vector& buffer); ColorRgb _IMAGE_SHARED_API colorRgbfromString(const std::string& colorName); void _IMAGE_SHARED_API encodeJpeg(std::vector& buffer, Image& inputImage, bool scaleDown); + bool _IMAGE_SHARED_API savePng(const std::string& filename, const Image& image); }; - diff --git a/include/utils/FrameDecoder.h b/include/utils/FrameDecoder.h index b38dc8902..21a594e33 100644 --- a/include/utils/FrameDecoder.h +++ b/include/utils/FrameDecoder.h @@ -8,17 +8,23 @@ // some stuff for HDR tone mapping #define LUT_INDEX(y,u,v) ((y + (u<<8) + (v<<16))*3) +namespace FrameDecoderUtils +{ + double unpackChromaP010(double x); + double unpackLuminanceP010(double val); +} + class FrameDecoder { public: static void processImage( int _cropLeft, int _cropRight, int _cropTop, int _cropBottom, const uint8_t* data, const uint8_t* dataUV, int width, int height, int lineLength, - const PixelFormat pixelFormat, const uint8_t* lutBuffer, Image& outputImage); + const PixelFormat pixelFormat, const uint8_t* lutBuffer, Image& outputImage, bool toneMapping = true); static void processQImage( - const uint8_t* data, int width, int height, int lineLength, - const PixelFormat pixelFormat, const uint8_t* lutBuffer, Image& outputImage); + const uint8_t* data, const uint8_t* dataUV, int width, int height, int lineLength, + const PixelFormat pixelFormat, const uint8_t* lutBuffer, Image& outputImage, bool toneMapping = true); static void processSystemImageBGRA(Image& image, int targetSizeX, int targetSizeY, int startX, int startY, diff --git a/include/utils/GlobalSignals.h b/include/utils/GlobalSignals.h index d5259cd70..4be144493 100644 --- a/include/utils/GlobalSignals.h +++ b/include/utils/GlobalSignals.h @@ -94,5 +94,7 @@ class GlobalSignals : public QObject void SignalDiscoveryRequestToScan(DiscoveryRecord::Service type); - void SignalDiscoveryEvent(DiscoveryRecord message); + void SignalDiscoveryEvent(DiscoveryRecord message); + + void SignalSetLut(MemoryBuffer* lut); }; diff --git a/include/utils/Logger.h b/include/utils/Logger.h index 847a227f8..930281855 100644 --- a/include/utils/Logger.h +++ b/include/utils/Logger.h @@ -20,6 +20,7 @@ #define LOG_MESSAGE(severity, logger, ...) (logger)->Message(severity, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) +#define REPORT_TOKEN "" #define Debug(logger, ...) LOG_MESSAGE(Logger::DEBUG , logger, __VA_ARGS__) #define Info(logger, ...) LOG_MESSAGE(Logger::INFO , logger, __VA_ARGS__) #define Warning(logger, ...) LOG_MESSAGE(Logger::WARNING, logger, __VA_ARGS__) diff --git a/include/utils/PixelFormat.h b/include/utils/PixelFormat.h index 5e808b6c4..dca05a3b2 100644 --- a/include/utils/PixelFormat.h +++ b/include/utils/PixelFormat.h @@ -11,6 +11,7 @@ enum class PixelFormat { I420, NV12, MJPEG, + P010, NO_CHANGE }; @@ -42,6 +43,10 @@ inline PixelFormat parsePixelFormat(const QString& pixelFormat) { return PixelFormat::MJPEG; } + else if (format.compare("p010") == 0) + { + return PixelFormat::P010; + } return PixelFormat::NO_CHANGE; } @@ -73,6 +78,10 @@ inline QString pixelFormatToString(const PixelFormat& pixelFormat) { return "mjpeg"; } + else if (pixelFormat == PixelFormat::P010) + { + return "p010"; + } return "NO_CHANGE"; } diff --git a/include/utils/VideoBenchmark.h b/include/utils/VideoBenchmark.h new file mode 100644 index 000000000..2d777619e --- /dev/null +++ b/include/utils/VideoBenchmark.h @@ -0,0 +1,57 @@ +#pragma once + +/* VideoBenchmark.h +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. + */ + +#ifndef PCH_ENABLED +#include +#include +#endif + +#include +#include + +class VideoBenchmark : public QObject +{ + Q_OBJECT + + int _benchmarkStatus; + QString _benchmarkMessage; + bool _connected; + +public: + VideoBenchmark(QObject* parent); + +public slots: + void signalSetGlobalImageHandler(int priority, const Image& image, int timeout_ms, hyperhdr::Components origin, QString clientDescription); + void signalNewVideoImageHandler(const QString& name, const Image& image); + void benchmarkCapture(int status, QString message); + void newFrame(const Image& image); + +signals: + void SignalBenchmarkUpdate(int status, QString message); +}; diff --git a/sources/api/CallbackAPI.cpp b/sources/api/CallbackAPI.cpp index 8b219854f..e63e4d729 100644 --- a/sources/api/CallbackAPI.cpp +++ b/sources/api/CallbackAPI.cpp @@ -18,7 +18,6 @@ using namespace hyperhdr; CallbackAPI::CallbackAPI(Logger* log, bool localConnection, QObject* parent) : BaseAPI(log, localConnection, parent) { - _lutCalibrator = nullptr; _availableCommands << "components-update" << "performance-update" << "sessions-update" << "priorities-update" << "imageToLedMapping-update" << "grabberstate-update" << "lut-calibration-update" << "adjustment-update" << "leds-colors" << "live-video" << "videomodehdr-update" << "settings-update" << "leds-update" << "instance-update" << "token-update" << "benchmark-update"; } @@ -73,14 +72,7 @@ bool CallbackAPI::subscribeFor(const QString& type, bool unsubscribe) { if (unsubscribe) { - if (_lutCalibrator != nullptr) - disconnect(_lutCalibrator.get(), &LutCalibrator::SignalLutCalibrationUpdated, this, &CallbackAPI::lutCalibrationUpdateHandler); - _lutCalibrator = nullptr; - } - else - { - _lutCalibrator = std::unique_ptr(new LutCalibrator()); - connect(_lutCalibrator.get(), &LutCalibrator::SignalLutCalibrationUpdated, this, &CallbackAPI::lutCalibrationUpdateHandler, Qt::UniqueConnection); + _lutCalibratorThread = nullptr; } } @@ -185,12 +177,12 @@ bool CallbackAPI::subscribeFor(const QString& type, bool unsubscribe) connect(_accessManager.get(), &AccessManager::SignalTokenUpdated, this, &CallbackAPI::tokenChangeHandler, Qt::UniqueConnection); } - if (type == "benchmark-update" && grabberWrapper != nullptr) + if (type == "benchmark-update") { if (unsubscribe) - disconnect(grabberWrapper, &GrabberWrapper::SignalBenchmarkUpdate, this, &CallbackAPI::signalBenchmarkUpdateHandler); + disconnect(_instanceManager.get(), &HyperHdrManager::SignalBenchmarkUpdate, this, &CallbackAPI::signalBenchmarkUpdateHandler); else - connect(grabberWrapper, &GrabberWrapper::SignalBenchmarkUpdate, this, &CallbackAPI::signalBenchmarkUpdateHandler, Qt::UniqueConnection); + connect(_instanceManager.get(), &HyperHdrManager::SignalBenchmarkUpdate, this, &CallbackAPI::signalBenchmarkUpdateHandler, Qt::UniqueConnection); } return true; diff --git a/sources/api/HyperAPI.cpp b/sources/api/HyperAPI.cpp index 5475f8c2f..2fad75242 100644 --- a/sources/api/HyperAPI.cpp +++ b/sources/api/HyperAPI.cpp @@ -525,21 +525,10 @@ void HyperAPI::handleCropCommand(const QJsonObject& message, const QString& comm void HyperAPI::handleBenchmarkCommand(const QJsonObject& message, const QString& command, int tan) { - GrabberWrapper* grabberWrapper = (_videoGrabber != nullptr) ? _videoGrabber->grabberWrapper() : nullptr; const QString& subc = message["subcommand"].toString().trimmed(); int status = message["status"].toInt(); - - if (grabberWrapper != nullptr) - { - if (subc == "ping") - { - emit grabberWrapper->SignalBenchmarkUpdate(status, "pong"); - } - else - { - BLOCK_CALL_2(grabberWrapper, benchmarkCapture, int, status, QString, subc); - } - } + + emit _instanceManager->SignalBenchmarkCapture(status, subc); sendSuccessReply(command, tan); } @@ -1000,37 +989,36 @@ void HyperAPI::handleLutCalibrationCommand(const QJsonObject& message, const QSt { QString subcommand = message["subcommand"].toString(""); - if (_lutCalibrator == nullptr) + if (subcommand == "capture") + { + bool debug = message["debug"].toBool(false); + bool lchCorrection = message["lch_correction"].toBool(false); + + QThread* lutThread = new QThread(); + LutCalibrator* lutCalibrator = new LutCalibrator(_instanceManager->getRootPath(), getActiveComponent(), debug, lchCorrection); + lutCalibrator->moveToThread(lutThread); + connect(lutThread, &QThread::finished, lutCalibrator, &LutCalibrator::deleteLater); + connect(lutThread, &QThread::started, lutCalibrator, &LutCalibrator::startHandler); + connect(lutCalibrator, &LutCalibrator::SignalLutCalibrationUpdated, this, &CallbackAPI::lutCalibrationUpdateHandler); + + _lutCalibratorThread = std::unique_ptr>(lutThread, + [lutCalibrator](QThread* mqttThread) { + lutCalibrator->cancelCalibrationSafe(); + THREAD_REMOVER(QString("LutCalibrator"), mqttThread, lutCalibrator); + }); + _lutCalibratorThread->start(); + } + else if (_lutCalibratorThread != nullptr && subcommand == "stop") { - sendErrorReply("Please refresh the page and start again", command + "-" + subcommand, tan); + _lutCalibratorThread = nullptr; + } + else + { + sendErrorReply("The command does not have any effect", command + "-" + subcommand, tan); return; } - int checksum = message["checksum"].toInt(-1); - QJsonObject startColor = message["startColor"].toObject(); - QJsonObject endColor = message["endColor"].toObject(); - bool limitedRange = message["limitedRange"].toBool(false); - double saturation = message["saturation"].toDouble(1.0); - double luminance = message["luminance"].toDouble(1.0); - double gammaR = message["gammaR"].toDouble(1.0); - double gammaG = message["gammaG"].toDouble(1.0); - double gammaB = message["gammaB"].toDouble(1.0); - int coef = message["coef"].toInt(0); - ColorRgb _startColor, _endColor; - - _startColor.red = startColor["r"].toInt(128); - _startColor.green = startColor["g"].toInt(128); - _startColor.blue = startColor["b"].toInt(128); - _endColor.red = endColor["r"].toInt(255); - _endColor.green = endColor["g"].toInt(255); - _endColor.blue = endColor["b"].toInt(255); - sendSuccessReply(command, tan); - - if (subcommand == "capture") - _lutCalibrator->incomingCommand(_instanceManager->getRootPath(), (_videoGrabber != nullptr) ? _videoGrabber->grabberWrapper() : nullptr, getActiveComponent(), checksum, _startColor, _endColor, limitedRange, saturation, luminance, gammaR, gammaG, gammaB, coef); - else - _lutCalibrator->stopHandler(); } void HyperAPI::handleInstanceCommand(const QJsonObject& message, const QString& command, int tan) diff --git a/sources/api/JSONRPC_schema/schema-lut-calibration.json b/sources/api/JSONRPC_schema/schema-lut-calibration.json index 5a0da5908..305154d0d 100644 --- a/sources/api/JSONRPC_schema/schema-lut-calibration.json +++ b/sources/api/JSONRPC_schema/schema-lut-calibration.json @@ -15,51 +15,13 @@ "tan" : { "type" : "integer" }, - "checksum": { - "type" : "integer", - "required" : true + "debug": { + "type" : "boolean", + "required" : false }, - "coef": { - "type" : "integer", + "lch_correction": { + "type" : "boolean", "required" : true - }, - "limitedRange": { - "type" : "boolean" - }, - "saturation": { - "type" : "number" - }, - "luminance": { - "type" : "number" - }, - "gammaR": { - "type" : "number" - }, - "gammaG": { - "type" : "number" - }, - "gammaB": { - "type" : "number" - }, - "startColor": { - "type": "object", - "required": true, - "properties":{ - "r" : {"type" : "integer"}, - "g" : {"type" : "integer"}, - "b" : {"type" : "integer"} - }, - "additionalProperties": false - }, - "endColor": { - "type": "object", - "required": true, - "properties":{ - "r" : {"type" : "integer"}, - "g" : {"type" : "integer"}, - "b" : {"type" : "integer"} - }, - "additionalProperties": false } }, "additionalProperties": false diff --git a/sources/base/Grabber.cpp b/sources/base/Grabber.cpp index 4a0419757..858243496 100644 --- a/sources/base/Grabber.cpp +++ b/sources/base/Grabber.cpp @@ -74,13 +74,13 @@ Grabber::Grabber(const QString& configurationPath, const QString& grabberName) , _signalDetectionEnabled(false) , _signalAutoDetectionEnabled(false) , _synchro(1) - , _benchmarkStatus(-1) - , _benchmarkMessage("") { + connect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetLut, this, &Grabber::signalSetLutHandler, Qt::BlockingQueuedConnection); } Grabber::~Grabber() { + disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetLut, this, &Grabber::signalSetLutHandler); } bool sortDevicePropertiesItem(const Grabber::DevicePropertiesItem& v1, const Grabber::DevicePropertiesItem& v2) @@ -101,7 +101,24 @@ void Grabber::setEnabled(bool enable) void Grabber::setMonitorNits(int nits) { - _targetMonitorNits = nits; + if (static_cast(_targetMonitorNits) != nits) + { + _targetMonitorNits = nits; + + Debug(_log, "Set nits to %i", _targetMonitorNits); + + if (_initialized && !_blocked) + { + Debug(_log, "Restarting video grabber"); + uninit(); + start(); + } + else + { + Info(_log, "Delayed restart of the grabber due to change of monitor nits value"); + _restartNeeded = true; + } + } } void Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom) @@ -128,7 +145,24 @@ void Grabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTo void Grabber::enableHardwareAcceleration(bool hardware) { - _hardware = hardware; + if (_hardware != hardware) + { + _hardware = hardware; + + Debug(_log, "Set hardware acceleration to %s", _hardware ? "enabled" : "disabled"); + + if (_initialized && !_blocked) + { + Debug(_log, "Restarting video grabber"); + uninit(); + start(); + } + else + { + Info(_log, "Delayed restart of the grabber due to change of the hardware acceleration"); + _restartNeeded = true; + } + } } bool Grabber::trySetInput(int input) @@ -518,6 +552,32 @@ int Grabber::getTargetSystemFrameDimension(int& targetSizeX, int& targetSizeY) return divide; } +int Grabber::getTargetSystemFrameDimension(int actualWidth, int actualHeight, int& targetSizeX, int& targetSizeY) +{ + int realSizeX = actualWidth; + int realSizeY = actualHeight; + + if (realSizeX <= 16 || realSizeY <= 16) + { + realSizeX = actualWidth; + realSizeY = actualHeight; + } + + int checkWidth = realSizeX; + int divide = 1; + + while (checkWidth > _width) + { + divide++; + checkWidth = realSizeX / divide; + } + + targetSizeX = realSizeX / divide; + targetSizeY = realSizeY / divide; + + return divide; +} + void Grabber::processSystemFrameBGRA(uint8_t* source, int lineSize, bool useLut) { int targetSizeX, targetSizeY; @@ -822,34 +882,24 @@ void Grabber::handleNewFrame(unsigned int workerIndex, Image image, qu { if (checkSignalDetectionManual(image)) emit GlobalSignals::getInstance()->SignalNewVideoImage(_deviceName, image); - } - if (_benchmarkStatus >= 0) - { - ColorRgb pixel = image(image.width() / 2, image.height() / 2); - if ((_benchmarkMessage == "white" && pixel.red > 120 && pixel.green > 120 && pixel.blue > 120) || - (_benchmarkMessage == "red" && pixel.red > 120 && pixel.green < 30 && pixel.blue < 30) || - (_benchmarkMessage == "green" && pixel.red < 30 && pixel.green > 120 && pixel.blue < 30) || - (_benchmarkMessage == "blue" && pixel.red < 30 && pixel.green < 40 && pixel.blue > 120) || - (_benchmarkMessage == "black" && pixel.red < 30 && pixel.green < 30 && pixel.blue < 30)) - - { - emit SignalBenchmarkUpdate(_benchmarkStatus, _benchmarkMessage); - _benchmarkStatus = -1; - _benchmarkMessage = ""; - } - } + } } else frameStat.directAccess = true; } -void Grabber::benchmarkCapture(int status, QString message) +bool Grabber::isInitialized() { - _benchmarkStatus = status; - _benchmarkMessage = message; + return _initialized; } -bool Grabber::isInitialized() +void Grabber::signalSetLutHandler(MemoryBuffer* lut) { - return _initialized; + if (lut != nullptr && _lut.size() >= lut->size()) + { + memcpy(_lut.data(), lut->data(), lut->size()); + Info(_log, "The byte array loaded into LUT"); + } + else + Error(_log, "Could not set LUT: current size = %i, incoming size = %i", _lut.size(), (lut != nullptr) ? lut->size() : 0); } diff --git a/sources/base/GrabberWrapper.cpp b/sources/base/GrabberWrapper.cpp index ef10f3200..c6cbba6cb 100644 --- a/sources/base/GrabberWrapper.cpp +++ b/sources/base/GrabberWrapper.cpp @@ -412,14 +412,6 @@ void GrabberWrapper::revive() QTimer::singleShot(3000, _grabber.get(), &Grabber::revive); } -void GrabberWrapper::benchmarkCapture(int status, QString message) -{ - if (_grabber != nullptr) - { - _grabber->benchmarkCapture(status, message); - } -} - bool GrabberWrapper::getAutoResume() { return _autoResume; diff --git a/sources/base/HyperHdrManager.cpp b/sources/base/HyperHdrManager.cpp index 86f3bd970..ac39e0dd9 100644 --- a/sources/base/HyperHdrManager.cpp +++ b/sources/base/HyperHdrManager.cpp @@ -49,9 +49,24 @@ HyperHdrManager::HyperHdrManager(const QString& rootPath) , _instanceTable(new InstanceTable()) , _rootPath(rootPath) , _fireStarter(0) + , _videoBenchmark(this) { qRegisterMetaType("InstanceState"); connect(this, &HyperHdrManager::SignalInstanceStateChanged, this, &HyperHdrManager::handleInstanceStateChange); + + connect(&_videoBenchmark, &VideoBenchmark::SignalBenchmarkUpdate, this, &HyperHdrManager::SignalBenchmarkUpdate); + connect(this, &HyperHdrManager::SignalBenchmarkCapture, &_videoBenchmark, &VideoBenchmark::benchmarkCapture); + + connect(GlobalSignals::getInstance(), &GlobalSignals::SignalRequestComponent, this, &HyperHdrManager::handleRequestComponent); +} + +void HyperHdrManager::handleRequestComponent(hyperhdr::Components component, int hyperHdrInd, bool listen) +{ + if (component == hyperhdr::Components::COMP_VIDEOGRABBER && hyperHdrInd == -1) + { + Warning(_log, "Global request to %s USB grabber", (listen) ? "resume" : "pause"); + toggleGrabbersAllInstances(listen); + } } HyperHdrManager::~HyperHdrManager() diff --git a/sources/base/schema/schema-flatbufServer.json b/sources/base/schema/schema-flatbufServer.json index 1e3f13f46..e1aaeb5bc 100644 --- a/sources/base/schema/schema-flatbufServer.json +++ b/sources/base/schema/schema-flatbufServer.json @@ -42,21 +42,14 @@ "default" : false, "propertyOrder" : 4 }, - "hdrToneMappingMode" : + "quarterOfFrameMode" : { - "type" : "integer", - "title" : "edt_conf_fbs_hdrToneMappingMode_title", - "append" : "edt_append_mode", - "enum" : [1, 2], - "default" : 1, + "type" : "boolean", + "format": "checkbox", "required" : true, - "propertyOrder" : 5, - "options": { - "enum_titles": ["Fullscreen", "Light (border only)"], - "dependencies": { - "hdrToneMapping": true - } - } + "title" : "flatbuffers_nv12_quarter_of_frame_title", + "default" : false, + "propertyOrder" : 5 } }, "additionalProperties" : false diff --git a/sources/flatbuffers/server/FlatBuffersServer.cpp b/sources/flatbuffers/server/FlatBuffersServer.cpp index dd0b94322..c58523d7d 100644 --- a/sources/flatbuffers/server/FlatBuffersServer.cpp +++ b/sources/flatbuffers/server/FlatBuffersServer.cpp @@ -33,13 +33,34 @@ FlatBuffersServer::FlatBuffersServer(std::shared_ptr netOrigin, const , _userLutFile("") , _currentLutPixelFormat(PixelFormat::RGB24) , _flatbufferToneMappingMode(0) -{ + , _quarterOfFrameMode(false) + , _active(false) +{ + connect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetLut, this, &FlatBuffersServer::signalSetLutHandler, Qt::BlockingQueuedConnection); + connect(GlobalSignals::getInstance(), &GlobalSignals::SignalRequestComponent, this, &FlatBuffersServer::handleRequestComponent); +} + +void FlatBuffersServer::handleRequestComponent(hyperhdr::Components component, int hyperHdrInd, bool listen) +{ + if (component == hyperhdr::Components::COMP_FLATBUFSERVER && hyperHdrInd == -1 && _active) + { + Warning(_log, "Global request to %s FlatBuffersServer", (listen) ? "resume" : "pause"); + if (listen) + { + startServer(); + } + else + { + stopServer(); + } + } } FlatBuffersServer::~FlatBuffersServer() { Debug(_log, "Prepare to shutdown"); + disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetLut, this, &FlatBuffersServer::signalSetLutHandler); stopServer(); Debug(_log, "FlatBuffersServer instance is closed"); @@ -68,6 +89,8 @@ void FlatBuffersServer::signalRequestSourceHandler(hyperhdr::Components componen { _hdrToneMappingEnabled = (listen) ? _flatbufferToneMappingMode : 0; + Info(_log, "Tone mapping: %i", _hdrToneMappingEnabled); + if (_hdrToneMappingEnabled || _currentLutPixelFormat == PixelFormat::YUYV) loadLutFile(); else @@ -111,14 +134,27 @@ void FlatBuffersServer::handleSettingsUpdate(settings::type type, const QJsonDoc } // HDR tone mapping - _flatbufferToneMappingMode = obj["hdrToneMapping"].toBool(false) ? obj["hdrToneMappingMode"].toInt(1) : 0; + _flatbufferToneMappingMode = obj["hdrToneMapping"].toBool(false) ? 1 : 0; signalRequestSourceHandler(hyperhdr::Components::COMP_HDR, -1, _flatbufferToneMappingMode); // new timeout just for new connections _timeout = obj["timeout"].toInt(5000); // enable check - obj["enable"].toBool(true) ? startServer() : stopServer(); + _active = obj["enable"].toBool(true); + if (_active) + { + startServer(); + } + else + { + stopServer(); + } + + _quarterOfFrameMode = obj["quarterOfFrameMode"].toBool(false); + + Info(_log, "Tone mapping: %i", _flatbufferToneMappingMode); + Info(_log, "NV12 quarter of frame mode: %i", _quarterOfFrameMode); } } @@ -292,15 +328,20 @@ void FlatBuffersServer::handlerImageReceived(int priority, FlatBuffersParser::Fl if (flatImage->format == FlatBuffersParser::FLATBUFFERS_IMAGE_FORMAT::RGB) { - if (_currentLutPixelFormat != PixelFormat::RGB24 && _hdrToneMappingEnabled) + if (_currentLutPixelFormat != PixelFormat::RGB24) { - _currentLutPixelFormat = PixelFormat::RGB24; - loadLutFile(); + _currentLutPixelFormat = PixelFormat::RGB24; + if (_hdrToneMappingEnabled) + { + loadLutFile(); + } + + Debug(_log, "Received first RGB frame. Image size: %i (%i x %i)", flatImage->size, flatImage->width, flatImage->height); } - if (flatImage->size != flatImage->width * flatImage->height * 3) + if (flatImage->size != flatImage->width * flatImage->height * 3 || flatImage->size == 0) { - Error(_log, "The RGB image data size does not match the width and height"); + Error(_log, "The RGB image data size does not match the width and height or it's empty. Image size: %i (%i x %i)", flatImage->size, flatImage->width, flatImage->height); } else { @@ -330,9 +371,9 @@ void FlatBuffersServer::handlerImageReceived(int priority, FlatBuffersParser::Fl { Error(_log, "The LUT file is not loaded"); } - else if (flatImage->size != ((flatImage->width * flatImage->height * 3) / 2)) + else if (flatImage->size != ((flatImage->width * flatImage->height * 3) / 2) || flatImage->size == 0) { - Error(_log, "The NV12 image data size (%i) does not match the width and height (%i)", flatImage->size, ((flatImage->width * flatImage->height * 3) / 2)); + Error(_log, "The NV12 image data size (%i) does not match the width and height (%i) or it's empty", flatImage->size, ((flatImage->width * flatImage->height * 3) / 2)); } else if ((flatImage->firstPlane.stride != flatImage->secondPlane.stride) || (flatImage->firstPlane.stride != 0 && flatImage->firstPlane.stride != flatImage->width)) @@ -343,10 +384,18 @@ void FlatBuffersServer::handlerImageReceived(int priority, FlatBuffersParser::Fl else { Image image(flatImage->width, flatImage->height); - - FrameDecoder::processImage( - 0, 0, 0, 0, - flatImage->firstPlane.data, flatImage->secondPlane.data, flatImage->width, flatImage->height, flatImage->width, PixelFormat::NV12, _lut.data(), image); + + if (_quarterOfFrameMode) + { + FrameDecoder::processQImage( + flatImage->firstPlane.data, flatImage->secondPlane.data, flatImage->width, flatImage->height, flatImage->width, PixelFormat::NV12, _lut.data(), image); + } + else + { + FrameDecoder::processImage( + 0, 0, 0, 0, + flatImage->firstPlane.data, flatImage->secondPlane.data, flatImage->width, flatImage->height, flatImage->width, PixelFormat::NV12, _lut.data(), image); + } emit GlobalSignals::getInstance()->SignalSetGlobalImage(priority, image, timeout_ms, origin, clientDescription); } } @@ -356,3 +405,13 @@ void FlatBuffersServer::handlerImageReceived(int priority, FlatBuffersParser::Fl } } +void FlatBuffersServer::signalSetLutHandler(MemoryBuffer* lut) +{ + if (lut != nullptr && _lut.size() >= lut->size()) + { + memcpy(_lut.data(), lut->data(), lut->size()); + Info(_log, "The byte array loaded into LUT"); + } + else + Error(_log, "Could not set LUT: current size = %i, incoming size = %i", _lut.size(), (lut != nullptr) ? lut->size() : 0); +} diff --git a/sources/grabber/linux/v4l2/V4L2Grabber.cpp b/sources/grabber/linux/v4l2/V4L2Grabber.cpp index 1ab7434fc..56275355b 100644 --- a/sources/grabber/linux/v4l2/V4L2Grabber.cpp +++ b/sources/grabber/linux/v4l2/V4L2Grabber.cpp @@ -61,6 +61,21 @@ // some stuff for HDR tone mapping #define LUT_FILE_SIZE 50331648 +namespace +{ + #ifdef V4L2_PIX_FMT_P010 + #pragma message "P010 is supported on the build machine" + bool supportedP010 = true; + #else + #pragma message "P010 is NOT supported on the build machine" + bool supportedP010 = false; + #endif +}; + +#ifndef V4L2_PIX_FMT_P010 + #define V4L2_PIX_FMT_P010 v4l2_fourcc('P', '0', '1', '0') +#endif + static const V4L2Grabber::HyperHdrFormat supportedFormats[] = { { V4L2_PIX_FMT_YUYV, PixelFormat::YUYV }, @@ -68,7 +83,8 @@ static const V4L2Grabber::HyperHdrFormat supportedFormats[] = { V4L2_PIX_FMT_RGB24, PixelFormat::RGB24 }, { V4L2_PIX_FMT_YUV420, PixelFormat::I420 }, { V4L2_PIX_FMT_NV12, PixelFormat::NV12 }, - { V4L2_PIX_FMT_MJPEG, PixelFormat::MJPEG } + { V4L2_PIX_FMT_MJPEG, PixelFormat::MJPEG }, + { V4L2_PIX_FMT_P010, PixelFormat::P010 } }; @@ -81,6 +97,8 @@ V4L2Grabber::V4L2Grabber(const QString& device, const QString& configurationPath { // Refresh devices getV4L2devices(); + + Debug(_log, "P010 was %s on the build machine", (supportedP010) ? "supported" : "unsupported"); } QString V4L2Grabber::GetSharedLut() @@ -129,7 +147,8 @@ void V4L2Grabber::setHdrToneMappingEnabled(int mode) { Debug(_log, "setHdrToneMappingMode replacing LUT and restarting"); _V4L2WorkerManager.Stop(); - if ((_actualVideoFormat == PixelFormat::YUYV) || (_actualVideoFormat == PixelFormat::I420) || (_actualVideoFormat == PixelFormat::NV12) || (_actualVideoFormat == PixelFormat::MJPEG)) + if ((_actualVideoFormat == PixelFormat::YUYV) || (_actualVideoFormat == PixelFormat::I420) || (_actualVideoFormat == PixelFormat::NV12) + || (_actualVideoFormat == PixelFormat::P010) || (_actualVideoFormat == PixelFormat::MJPEG)) loadLutFile(PixelFormat::YUYV); else loadLutFile(PixelFormat::RGB24); @@ -982,6 +1001,16 @@ bool V4L2Grabber::init_device(QString selectedDeviceName, DevicePropertiesItem p } break; + case V4L2_PIX_FMT_P010: + { + loadLutFile(PixelFormat::YUYV); + _actualVideoFormat = PixelFormat::P010; + _frameByteSize = (props.x * props.y * 6) / 2; + _lineLength = props.x * 2; + Info(_log, "Video pixel format is set to: P010"); + } + break; + case V4L2_PIX_FMT_NV12: { loadLutFile(PixelFormat::YUYV); @@ -1186,7 +1215,7 @@ bool V4L2Grabber::process_image(v4l2_buffer* buf, const void* frameImageBuffer, loadLutFile(); } - bool directAccess = !(_signalAutoDetectionEnabled || _signalDetectionEnabled || isCalibrating() || (_benchmarkStatus >= 0)); + bool directAccess = !(_signalAutoDetectionEnabled || _signalDetectionEnabled || isCalibrating() ); _workerThread->setup( i, buf, diff --git a/sources/grabber/linux/v4l2/V4L2Worker.cpp b/sources/grabber/linux/v4l2/V4L2Worker.cpp index 33aeb3b42..3584c139b 100644 --- a/sources/grabber/linux/v4l2/V4L2Worker.cpp +++ b/sources/grabber/linux/v4l2/V4L2Worker.cpp @@ -201,7 +201,7 @@ void V4L2Worker::runMe() { Image image(_width >> 1, _height >> 1); FrameDecoder::processQImage( - _sharedData, _width, _height, _lineLength, _pixelFormat, _lutBuffer, image); + _sharedData, nullptr, _width, _height, _lineLength, _pixelFormat, _lutBuffer, image, _hdrToneMappingEnabled); image.setBufferCacheSize(); if (!_directAccess) @@ -222,7 +222,7 @@ void V4L2Worker::runMe() FrameDecoder::processImage( _cropLeft, _cropRight, _cropTop, _cropBottom, - _sharedData, nullptr, _width, _height, _lineLength, _pixelFormat, _lutBuffer, image); + _sharedData, nullptr, _width, _height, _lineLength, _pixelFormat, _lutBuffer, image, _hdrToneMappingEnabled); image.setBufferCacheSize(); if (!_directAccess) diff --git a/sources/grabber/linux/v4l2/V4L2Wrapper.cpp b/sources/grabber/linux/v4l2/V4L2Wrapper.cpp index 79897fbf5..5e510522b 100644 --- a/sources/grabber/linux/v4l2/V4L2Wrapper.cpp +++ b/sources/grabber/linux/v4l2/V4L2Wrapper.cpp @@ -28,13 +28,13 @@ #include #include #include +#include V4L2Wrapper::V4L2Wrapper(const QString& device, const QString& configurationPath) : GrabberWrapper("V4L2:" + device.left(14)) { _grabber = std::unique_ptr(new V4L2Grabber(device, configurationPath)); - connect(_grabber.get(), &Grabber::SignalBenchmarkUpdate, this, &GrabberWrapper::SignalBenchmarkUpdate); connect(_grabber.get(), &Grabber::SignalCapturingException, this, &GrabberWrapper::capturingExceptionHandler); connect(_grabber.get(), &Grabber::SignalSetNewComponentStateToAllInstances, this, &GrabberWrapper::SignalSetNewComponentStateToAllInstances); connect(_grabber.get(), &Grabber::SignalSaveCalibration, this, &GrabberWrapper::SignalSaveCalibration); diff --git a/sources/grabber/osx/AVF/AVFGrabber.mm b/sources/grabber/osx/AVF/AVFGrabber.mm index e987dfab6..838b213d2 100644 --- a/sources/grabber/osx/AVF/AVFGrabber.mm +++ b/sources/grabber/osx/AVF/AVFGrabber.mm @@ -810,7 +810,7 @@ - (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleB loadLutFile(); } - bool directAccess = !(_signalAutoDetectionEnabled || _signalDetectionEnabled || isCalibrating() || (_benchmarkStatus >= 0)); + bool directAccess = !(_signalAutoDetectionEnabled || _signalDetectionEnabled || isCalibrating()); _workerThread->setup( i, _actualVideoFormat, diff --git a/sources/grabber/osx/AVF/AVFWorker.cpp b/sources/grabber/osx/AVF/AVFWorker.cpp index e75b6fc9a..2d1438b73 100644 --- a/sources/grabber/osx/AVF/AVFWorker.cpp +++ b/sources/grabber/osx/AVF/AVFWorker.cpp @@ -193,7 +193,7 @@ void AVFWorker::runMe() { Image image(_width >> 1, _height >> 1); FrameDecoder::processQImage( - _localBuffer.data(), _width, _height, _lineLength, _pixelFormat, _lutBuffer, image); + _localBuffer.data(), nullptr, _width, _height, _lineLength, _pixelFormat, _lutBuffer, image); image.setBufferCacheSize(); if (!_directAccess) diff --git a/sources/grabber/osx/AVF/AVFWrapper.cpp b/sources/grabber/osx/AVF/AVFWrapper.cpp index 84296cf6c..25aad948f 100644 --- a/sources/grabber/osx/AVF/AVFWrapper.cpp +++ b/sources/grabber/osx/AVF/AVFWrapper.cpp @@ -28,6 +28,7 @@ #include #include #include +#include AVFWrapper::AVFWrapper(const QString& device, @@ -35,7 +36,6 @@ AVFWrapper::AVFWrapper(const QString& device, : GrabberWrapper("macOS AVF:" + device.left(14)) { _grabber = std::unique_ptr(new AVFGrabber(device, configurationPath)); - connect(_grabber.get(), &Grabber::SignalBenchmarkUpdate, this, &GrabberWrapper::SignalBenchmarkUpdate); connect(_grabber.get(), &Grabber::SignalCapturingException, this, &GrabberWrapper::capturingExceptionHandler); connect(_grabber.get(), &Grabber::SignalSetNewComponentStateToAllInstances, this, &GrabberWrapper::SignalSetNewComponentStateToAllInstances); connect(_grabber.get(), &Grabber::SignalSaveCalibration, this, &GrabberWrapper::SignalSaveCalibration); diff --git a/sources/grabber/windows/DX/DxGrabber.cpp b/sources/grabber/windows/DX/DxGrabber.cpp index a1d817795..b3fc5048a 100644 --- a/sources/grabber/windows/DX/DxGrabber.cpp +++ b/sources/grabber/windows/DX/DxGrabber.cpp @@ -56,40 +56,17 @@ #define CHECK(hr) SUCCEEDED(hr) #define CLEAR(x) memset(&(x), 0, sizeof(x)) -#define MAX_WARNINGS 6 - -template void SafeRelease(T** ppT) -{ - if (*ppT) - { - (*ppT)->Release(); - *ppT = NULL; - } -} DxGrabber::DxGrabber(const QString& device, const QString& configurationPath) : Grabber(configurationPath, "DX11_SYSTEM:" + device.left(14)) , _configurationPath(configurationPath) , _timer(new QTimer(this)) , _retryTimer(new QTimer(this)) - , _warningCounter(MAX_WARNINGS) - , _actualDivide(-1) - , _wideGamut(false) - + , _multiMonitor(false) , _dxRestartNow(false) , _d3dDevice(nullptr) , _d3dContext(nullptr) - , _d3dBuffer(nullptr) - , _d3dSampler(nullptr) - , _d3dVertexLayout(nullptr) - , _d3dVertexShader(nullptr) - , _d3dPixelShader(nullptr) - , _d3dSourceTexture(nullptr) - , _d3dConvertTexture(nullptr) - , _d3dConvertTextureView(nullptr) - , _d3dRenderTargetView(nullptr) - , _d3dDuplicate(nullptr) { _timer->setTimerType(Qt::PreciseTimer); connect(_timer, &QTimer::timeout, this, &DxGrabber::grabFrame); @@ -149,21 +126,10 @@ void DxGrabber::uninit() Debug(_log, "Uninit grabber: %s", QSTRING_CSTR(_deviceName)); } - SafeRelease(&_d3dRenderTargetView); - SafeRelease(&_d3dSourceTexture); - SafeRelease(&_d3dConvertTextureView); - SafeRelease(&_d3dConvertTexture); - SafeRelease(&_d3dVertexShader); - SafeRelease(&_d3dVertexLayout); - SafeRelease(&_d3dPixelShader); - SafeRelease(&_d3dSampler); - SafeRelease(&_d3dBuffer); - SafeRelease(&_d3dDuplicate); + _handles.clear(); SafeRelease(&_d3dContext); SafeRelease(&_d3dDevice); - _warningCounter = MAX_WARNINGS; - _actualDivide = -1; _initialized = false; if (_dxRestartNow) @@ -271,7 +237,8 @@ void DxGrabber::enumerateDevices(bool silent) pAdapter->GetDesc1(&pDesc); IDXGIOutput* pOutput; - for (UINT j = 0; pAdapter->EnumOutputs(j, &pOutput) != DXGI_ERROR_NOT_FOUND; j++) + UINT j = 0; + for (; pAdapter->EnumOutputs(j, &pOutput) != DXGI_ERROR_NOT_FOUND; j++) { DXGI_OUTPUT_DESC oDesc; pOutput->GetDesc(&oDesc); @@ -281,6 +248,12 @@ void DxGrabber::enumerateDevices(bool silent) SafeRelease(&pOutput); } + + if (j > 1) + { + _deviceProperties.insert(MULTI_MONITOR + "|" + QString::fromWCharArray(pDesc.Description), properties); + } + SafeRelease(&pAdapter); } SafeRelease(&pFactory); @@ -332,17 +305,25 @@ bool DxGrabber::initDirectX(QString selectedDeviceName) for (UINT i = 0; pFactory->EnumAdapters1(i, &pAdapter) != DXGI_ERROR_NOT_FOUND && !exitNow; i++) { DeviceProperties properties; - DXGI_ADAPTER_DESC1 pDesc; + DXGI_ADAPTER_DESC1 pDesc{}; pAdapter->GetDesc1(&pDesc); + QString multiName = MULTI_MONITOR + "|" + QString::fromWCharArray(pDesc.Description); + + _multiMonitor = (selectedDeviceName == multiName); + IDXGIOutput* pOutput; - for (UINT j = 0; pAdapter->EnumOutputs(j, &pOutput) != DXGI_ERROR_NOT_FOUND && !exitNow; j++) + + + for (UINT j = 0; pAdapter->EnumOutputs(j, &pOutput) != DXGI_ERROR_NOT_FOUND && (!exitNow || (_multiMonitor && result)); j++) { - DXGI_OUTPUT_DESC oDesc; + DXGI_OUTPUT_DESC oDesc{}; pOutput->GetDesc(&oDesc); - exitNow = (QString::fromWCharArray(oDesc.DeviceName) + "|" + QString::fromWCharArray(pDesc.Description)) == selectedDeviceName; + QString currentName = (QString::fromWCharArray(oDesc.DeviceName) + "|" + QString::fromWCharArray(pDesc.Description)); + + exitNow = (currentName == selectedDeviceName) || _multiMonitor; if (exitNow) { @@ -350,67 +331,78 @@ bool DxGrabber::initDirectX(QString selectedDeviceName) if (CHECK(pOutput->QueryInterface(__uuidof(IDXGIOutput6), reinterpret_cast(&pOutput6)))) { - HRESULT findDriver = E_FAIL; - D3D_FEATURE_LEVEL featureLevel; - std::vector driverTypes{ - D3D_DRIVER_TYPE_HARDWARE, - D3D_DRIVER_TYPE_WARP, - D3D_DRIVER_TYPE_REFERENCE, - D3D_DRIVER_TYPE_UNKNOWN - }; - - CLEAR(featureLevel); - - for (auto& driverType : driverTypes) + if (_d3dDevice == nullptr) { - findDriver = D3D11CreateDevice(pAdapter, driverType, - nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, - D3D11_SDK_VERSION, &_d3dDevice, &featureLevel, &_d3dContext); - - if (SUCCEEDED(findDriver)) + HRESULT findDriver = E_FAIL; + D3D_FEATURE_LEVEL featureLevel; + std::vector driverTypes{ + D3D_DRIVER_TYPE_HARDWARE, + D3D_DRIVER_TYPE_WARP, + D3D_DRIVER_TYPE_REFERENCE, + D3D_DRIVER_TYPE_UNKNOWN + }; + + CLEAR(featureLevel); + + for (auto& driverType : driverTypes) { - switch (driverType) + findDriver = D3D11CreateDevice(pAdapter, driverType, + nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, + D3D11_SDK_VERSION, &_d3dDevice, &featureLevel, &_d3dContext); + + if (SUCCEEDED(findDriver)) { - case D3D_DRIVER_TYPE_HARDWARE: Info(_log, "Selected D3D_DRIVER_TYPE_HARDWARE"); break; - case D3D_DRIVER_TYPE_WARP: Info(_log, "Selected D3D_DRIVER_TYPE_WARP"); break; - case D3D_DRIVER_TYPE_REFERENCE: Info(_log, "Selected D3D_DRIVER_TYPE_REFERENCE"); break; - case D3D_DRIVER_TYPE_UNKNOWN: Info(_log, "Selected D3D_DRIVER_TYPE_UNKNOWN"); break; + switch (driverType) + { + case D3D_DRIVER_TYPE_HARDWARE: Info(_log, "Selected D3D_DRIVER_TYPE_HARDWARE"); break; + case D3D_DRIVER_TYPE_WARP: Info(_log, "Selected D3D_DRIVER_TYPE_WARP"); break; + case D3D_DRIVER_TYPE_REFERENCE: Info(_log, "Selected D3D_DRIVER_TYPE_REFERENCE"); break; + case D3D_DRIVER_TYPE_UNKNOWN: Info(_log, "Selected D3D_DRIVER_TYPE_UNKNOWN"); break; + } + + break; } + } - break; + if (!SUCCEEDED(findDriver)) + { + _d3dContext = nullptr; } } - if (CHECK(findDriver) && _d3dDevice != nullptr) + if (_d3dDevice != nullptr) { HRESULT status = E_FAIL; DXGI_OUTPUT_DESC1 descGamut; + std::unique_ptr display = std::make_unique(); + display->name = currentName; + display->targetMonitorNits = _targetMonitorNits; + pOutput6->GetDesc1(&descGamut); - _wideGamut = descGamut.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; + display->wideGamut = descGamut.ColorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020; Info(_log, "Gamut: %s, min nits: %0.2f, max nits: %0.2f, max frame nits: %0.2f, white point: [%0.2f, %0.2f]", - (_wideGamut) ? "HDR" : "SDR", descGamut.MinLuminance, descGamut.MaxLuminance, descGamut.MaxFullFrameLuminance, + (display->wideGamut) ? "HDR" : "SDR", descGamut.MinLuminance, descGamut.MaxLuminance, descGamut.MaxFullFrameLuminance, descGamut.WhitePoint[0], descGamut.WhitePoint[1]); - if (_wideGamut && _targetMonitorNits == 0) + if (display->wideGamut && display->targetMonitorNits == 0) { - Warning(_log, "Target SDR brightness is set to %i nits. Disabling wide gamut.", _targetMonitorNits); - _wideGamut = false; + Warning(_log, "Target SDR brightness is set to %i nits. Disabling wide gamut.", display->targetMonitorNits); + display->wideGamut = false; } - - if (_hardware && _wideGamut) + if (_hardware && display->wideGamut) { - Info(_log, "Using wide gamut for HDR. Target SDR brightness: %i nits", _targetMonitorNits); + Info(_log, "Using wide gamut for HDR. Target SDR brightness: %i nits", display->targetMonitorNits); std::vector wideFormat({ DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R10G10B10A2_UNORM }); - status = pOutput6->DuplicateOutput1(_d3dDevice, 0, static_cast(wideFormat.size()), wideFormat.data(), &_d3dDuplicate); + status = pOutput6->DuplicateOutput1(_d3dDevice, 0, static_cast(wideFormat.size()), wideFormat.data(), &display->d3dDuplicate); if (!CHECK(status)) { Warning(_log, "No support for DXGI_FORMAT_R16G16B16A16_FLOAT/DXGI_FORMAT_R10G10B10A2_UNORM. Fallback to BGRA"); - _wideGamut = false; + display->wideGamut = false; } } @@ -419,41 +411,41 @@ bool DxGrabber::initDirectX(QString selectedDeviceName) Info(_log, "Using BGRA format"); DXGI_FORMAT rgbFormat = DXGI_FORMAT_B8G8R8A8_UNORM; - status = pOutput6->DuplicateOutput1(_d3dDevice, 0, 1, &rgbFormat, &_d3dDuplicate); + status = pOutput6->DuplicateOutput1(_d3dDevice, 0, 1, &rgbFormat, &display->d3dDuplicate); } if (CHECK(status)) { - CLEAR(_surfaceProperties); - _d3dDuplicate->GetDesc(&_surfaceProperties); + CLEAR(display->surfaceProperties); + display->d3dDuplicate->GetDesc(&display->surfaceProperties); - Info(_log, "Surface format: %i", _surfaceProperties.ModeDesc.Format); + Info(_log, "Surface format: %i", display->surfaceProperties.ModeDesc.Format); - int targetSizeX = _surfaceProperties.ModeDesc.Width, targetSizeY = _surfaceProperties.ModeDesc.Height; + int targetSizeX = display->surfaceProperties.ModeDesc.Width, targetSizeY = display->surfaceProperties.ModeDesc.Height; if (_hardware) { - _actualWidth = targetSizeX; - _actualHeight = targetSizeY; + display->actualWidth = targetSizeX; + display->actualHeight = targetSizeY; - if (!_wideGamut) + if (!display->wideGamut) { - int maxSize = std::max((_actualWidth - _cropLeft - _cropRight), (_actualHeight - _cropTop - _cropBottom)); + int maxSize = std::max(display->actualWidth, display->actualHeight); - _actualDivide = 0; + display->actualDivide = 0; while (maxSize > _width) { - _actualDivide++; + display->actualDivide++; maxSize >>= 1; } - targetSizeX = _surfaceProperties.ModeDesc.Width >> _actualDivide; - targetSizeY = _surfaceProperties.ModeDesc.Height >> _actualDivide; + targetSizeX = display->surfaceProperties.ModeDesc.Width >> display->actualDivide; + targetSizeY = display->surfaceProperties.ModeDesc.Height >> display->actualDivide; } else { - _actualDivide = -1; - getTargetSystemFrameDimension(targetSizeX, targetSizeY); + display->actualDivide = -1; + getTargetSystemFrameDimension(display->actualWidth, display->actualHeight, targetSizeX, targetSizeY); } } @@ -470,22 +462,23 @@ bool DxGrabber::initDirectX(QString selectedDeviceName) sourceTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; sourceTextureDesc.MiscFlags = 0; sourceTextureDesc.BindFlags = 0; - if (CHECK(_d3dDevice->CreateTexture2D(&sourceTextureDesc, NULL, &_d3dSourceTexture))) + if (CHECK(_d3dDevice->CreateTexture2D(&sourceTextureDesc, NULL, &display->d3dSourceTexture))) { _actualVideoFormat = PixelFormat::XRGB; - _actualWidth = sourceTextureDesc.Width; - _actualHeight = sourceTextureDesc.Height; + display->actualWidth = _actualWidth = sourceTextureDesc.Width; + display->actualHeight = _actualHeight = sourceTextureDesc.Height; loadLutFile(PixelFormat::RGB24); - _frameByteSize = _actualWidth * _actualHeight * 4; - _lineLength = _actualWidth * 4; + _frameByteSize = display->actualWidth * display->actualHeight * 4; + _lineLength = display->actualWidth * 4; if (_hardware) { - result = initShaders(); + result = initShaders(*display); if (result) { - Info(_log, "The DX11 device has been initialized. Hardware acceleration is enabled"); + Info(_log, "The DX11 device has been initialized. Hardware acceleration is enabled"); + _handles.emplace_back(std::move(display)); } else { @@ -496,6 +489,7 @@ bool DxGrabber::initDirectX(QString selectedDeviceName) else { result = true; + _handles.emplace_back(std::move(display)); Info(_log, "The DX11 device has been initialized. Hardware acceleration is disabled"); } } @@ -534,19 +528,24 @@ bool DxGrabber::initDirectX(QString selectedDeviceName) SafeRelease(&pFactory); + if (!result && _handles.size() > 0) + { + uninit(); + } + return result; } -bool DxGrabber::initShaders() +bool DxGrabber::initShaders(DisplayHandle& display) { HRESULT status; std::unique_ptr descConvert; - if (_actualDivide < 0) + if (display.actualDivide < 0) descConvert = std::make_unique( DXGI_FORMAT_B8G8R8A8_UNORM, - _actualWidth, _actualHeight, + display.actualWidth, display.actualHeight, 1, 1, D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE, @@ -556,9 +555,9 @@ bool DxGrabber::initShaders() else descConvert = std::make_unique( DXGI_FORMAT_B8G8R8A8_UNORM, - _surfaceProperties.ModeDesc.Width, _surfaceProperties.ModeDesc.Height, + display.surfaceProperties.ModeDesc.Width, display.surfaceProperties.ModeDesc.Height, 1, - _actualDivide + 1, + display.actualDivide + 1, D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE, D3D11_USAGE_DEFAULT, 0, @@ -566,19 +565,19 @@ bool DxGrabber::initShaders() 0, D3D11_RESOURCE_MISC_GENERATE_MIPS); - status = _d3dDevice->CreateTexture2D(descConvert.get(), nullptr, &_d3dConvertTexture); + status = _d3dDevice->CreateTexture2D(descConvert.get(), nullptr, &display.d3dConvertTexture); if (CHECK(status)) { - if (_actualDivide < 0) + if (display.actualDivide < 0) { CD3D11_RENDER_TARGET_VIEW_DESC rtvDesc( D3D11_RTV_DIMENSION_TEXTURE2D, DXGI_FORMAT_B8G8R8A8_UNORM); - status = _d3dDevice->CreateRenderTargetView(_d3dConvertTexture, &rtvDesc, &_d3dRenderTargetView); + status = _d3dDevice->CreateRenderTargetView(display.d3dConvertTexture, &rtvDesc, &display.d3dRenderTargetView); if (CHECK(status)) { - _d3dContext->OMSetRenderTargets(1, &_d3dRenderTargetView, NULL); + _d3dContext->OMSetRenderTargets(1, &display.d3dRenderTargetView, NULL); } else Error(_log, "CreateRenderTargetView failed. Reason: %x", status); @@ -592,7 +591,7 @@ bool DxGrabber::initShaders() shaderDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; shaderDesc.Texture2D.MipLevels = descConvert->MipLevels;; - status = _d3dDevice->CreateShaderResourceView(_d3dConvertTexture, &shaderDesc, &_d3dConvertTextureView); + status = _d3dDevice->CreateShaderResourceView(display.d3dConvertTexture, &shaderDesc, &display.d3dConvertTextureView); return CHECK(status); } } @@ -601,10 +600,10 @@ bool DxGrabber::initShaders() if (CHECK(status)) { - status = _d3dDevice->CreateVertexShader(g_VertexShaderHyperHDR, sizeof(g_VertexShaderHyperHDR), nullptr, &_d3dVertexShader); + status = _d3dDevice->CreateVertexShader(g_VertexShaderHyperHDR, sizeof(g_VertexShaderHyperHDR), nullptr, &display.d3dVertexShader); if (CHECK(status)) { - _d3dContext->VSSetShader(_d3dVertexShader, NULL, 0); + _d3dContext->VSSetShader(display.d3dVertexShader, NULL, 0); _d3dContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); } else @@ -613,10 +612,10 @@ bool DxGrabber::initShaders() if (CHECK(status)) { - status = _d3dDevice->CreatePixelShader(g_PixelShaderHyperHDR, sizeof(g_PixelShaderHyperHDR), nullptr, &_d3dPixelShader); + status = _d3dDevice->CreatePixelShader(g_PixelShaderHyperHDR, sizeof(g_PixelShaderHyperHDR), nullptr, &display.d3dPixelShader); if (CHECK(status)) { - _d3dContext->PSSetShader(_d3dPixelShader, NULL, 0); + _d3dContext->PSSetShader(display.d3dPixelShader, NULL, 0); } else Error(_log, "Could not create pixel shaders. Reason: %x", status); @@ -627,9 +626,9 @@ bool DxGrabber::initShaders() D3D11_INPUT_ELEMENT_DESC layout[] = { {"SV_Position", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, }; UINT numElements = ARRAYSIZE(layout); - status = _d3dDevice->CreateInputLayout(layout, numElements, g_VertexShaderHyperHDR, sizeof(g_VertexShaderHyperHDR), &_d3dVertexLayout); + status = _d3dDevice->CreateInputLayout(layout, numElements, g_VertexShaderHyperHDR, sizeof(g_VertexShaderHyperHDR), &display.d3dVertexLayout); if (CHECK(status)) - _d3dContext->IASetInputLayout(_d3dVertexLayout); + _d3dContext->IASetInputLayout(display.d3dVertexLayout); else Error(_log, "Could not create vertex layout. Reason: %x", status); } @@ -646,16 +645,16 @@ bool DxGrabber::initShaders() sDesc.ComparisonFunc = D3D11_COMPARISON_NEVER; sDesc.MinLOD = 0; sDesc.MaxLOD = D3D11_FLOAT32_MAX; - status = _d3dDevice->CreateSamplerState(&sDesc, &_d3dSampler); + status = _d3dDevice->CreateSamplerState(&sDesc, &display.d3dSampler); if (CHECK(status)) - _d3dContext->PSSetSamplers(0, 1, &_d3dSampler); + _d3dContext->PSSetSamplers(0, 1, &display.d3dSampler); else Error(_log, "Could not create the sampler. Reason: %x", status); } if (CHECK(status)) { - DirectX::XMFLOAT4 params = { static_cast(_targetMonitorNits), 18.8515625f - 18.6875f * _targetMonitorNits, 0.0, 0.0 }; + DirectX::XMFLOAT4 params = { static_cast(display.targetMonitorNits), 18.8515625f - 18.6875f * display.targetMonitorNits, 0.0, 0.0 }; D3D11_BUFFER_DESC cbDesc; CLEAR(cbDesc); @@ -668,11 +667,11 @@ bool DxGrabber::initShaders() CLEAR(initData); initData.pSysMem = ¶ms; - status = _d3dDevice->CreateBuffer(&cbDesc, &initData, &_d3dBuffer); + status = _d3dDevice->CreateBuffer(&cbDesc, &initData, &display.d3dBuffer); if (CHECK(status)) { - _d3dContext->VSSetConstantBuffers(0, 1, &_d3dBuffer); + _d3dContext->VSSetConstantBuffers(0, 1, &display.d3dBuffer); } else Error(_log, "Could not create constant buffer. Reason: %x", status); @@ -699,15 +698,15 @@ bool DxGrabber::initShaders() return false; } -HRESULT DxGrabber::deepScaledCopy(ID3D11Texture2D* source) +HRESULT DxGrabber::deepScaledCopy(DisplayHandle& display, ID3D11Texture2D* source) { HRESULT status = S_OK; - if (_actualDivide >= 0) + if (display.actualDivide >= 0) { - _d3dContext->CopySubresourceRegion(_d3dConvertTexture, 0, 0, 0, 0, source, 0, NULL); - _d3dContext->GenerateMips(_d3dConvertTextureView); - _d3dContext->CopySubresourceRegion(_d3dSourceTexture, 0, 0, 0, 0, _d3dConvertTexture, _actualDivide, NULL); + _d3dContext->CopySubresourceRegion(display.d3dConvertTexture, 0, 0, 0, 0, source, 0, NULL); + _d3dContext->GenerateMips(display.d3dConvertTextureView); + _d3dContext->CopySubresourceRegion(display.d3dSourceTexture, 0, 0, 0, 0, display.d3dConvertTexture, display.actualDivide, NULL); } else { @@ -732,9 +731,9 @@ HRESULT DxGrabber::deepScaledCopy(ID3D11Texture2D* source) if (!CHECK(status)) { - if (_warningCounter > 0) + if (display.warningCounter > 0) { - Error(_log, "CreateShaderResourceView failed (%i). Reason: %i", _warningCounter--, status); + Error(_log, "CreateShaderResourceView failed (%i). Reason: %i", display.warningCounter--, status); } return status; } @@ -742,18 +741,90 @@ HRESULT DxGrabber::deepScaledCopy(ID3D11Texture2D* source) _d3dContext->PSSetShaderResources(0, 1, &rv); _d3dContext->Draw(4, 0); - _d3dContext->CopyResource(_d3dSourceTexture, _d3dConvertTexture); + _d3dContext->CopyResource(display.d3dSourceTexture, display.d3dConvertTexture); SafeRelease(&rv); } return status; } + void DxGrabber::grabFrame() +{ + if (_handles.size() == 0) + return; + + if (_multiMonitor) + { + int width = 0; + int height = 0; + bool useCache = false; + std::list>> images; + + for (auto&& display : _handles) + { + Image image; + auto result = captureFrame(*display, image); + + if (result < 0) + return; + else if (result == 0) + { + useCache = true; + } + else + { + images.push_back(std::pair>(width, image)); + } + + int targetSizeX = 0, targetSizeY = 0; + int divide = getTargetSystemFrameDimension(display->actualWidth, display->actualHeight, targetSizeX, targetSizeY); + + width += targetSizeX; + height = std::max(targetSizeY, height); + } + + if (useCache && (_cacheImage.width() != width || _cacheImage.height() != height)) + { + Warning(_log, "Invalid cached image size. Cached: %i x %i vs new: %i x %i", _cacheImage.width(), _cacheImage.height(), width, height); + return; + } + + Image image(width, height); + + if (useCache) + { + memcpy(image.rawMem(), _cacheImage.rawMem(), image.size()); + } + else + { + memset(image.rawMem(), 0, image.size()); + } + + for (auto&& source : images) + { + image.insertHorizontal(source.first, source.second); + } + + if (_signalDetectionEnabled) + { + if (checkSignalDetectionManual(image)) + emit SignalNewCapturedFrame(image); + } + else + emit SignalNewCapturedFrame(image); + } + else + { + captureFrame(*_handles.front()); + } +} + +void DxGrabber::captureFrame(DisplayHandle& display) { DXGI_OUTDUPL_FRAME_INFO infoFrame{}; IDXGIResource* resourceDesktop = nullptr; - auto status = _d3dDuplicate->AcquireNextFrame(0, &infoFrame, &resourceDesktop); + auto status = display.d3dDuplicate->AcquireNextFrame(0, &infoFrame, &resourceDesktop); if (CHECK(status)) { @@ -768,33 +839,33 @@ void DxGrabber::grabFrame() { if (_hardware) { - status = deepScaledCopy(texDesktop); + status = deepScaledCopy(display, texDesktop); } else { - _d3dContext->CopyResource(_d3dSourceTexture, texDesktop); + _d3dContext->CopyResource(display.d3dSourceTexture, texDesktop); } - if (CHECK(status) && CHECK(_d3dContext->Map(_d3dSourceTexture, 0, D3D11_MAP_READ, 0, &internalMap))) + if (CHECK(status) && CHECK(_d3dContext->Map(display.d3dSourceTexture, 0, D3D11_MAP_READ, 0, &internalMap))) { - processSystemFrameBGRA((uint8_t*)internalMap.pData, (int)internalMap.RowPitch, !(_hardware && _wideGamut)); - _d3dContext->Unmap(_d3dSourceTexture, 0); + processSystemFrameBGRA((uint8_t*)internalMap.pData, (int)internalMap.RowPitch, !(_hardware && display.wideGamut)); + _d3dContext->Unmap(display.d3dSourceTexture, 0); } SafeRelease(&texDesktop); } - else if (_warningCounter > 0) + else if (display.warningCounter > 0) { Error(_log, "ResourceDesktop->QueryInterface failed. Reason: %i", status); - _warningCounter--; + display.warningCounter--; } } else if (status == DXGI_ERROR_WAIT_TIMEOUT) { - if (_warningCounter > 0) + if (display.warningCounter > 0) { Debug(_log, "AcquireNextFrame didn't return the frame. Just warning: the screen has not changed?"); - _warningCounter--; + display.warningCounter--; } if (_cacheImage.width() > 1) @@ -807,15 +878,15 @@ void DxGrabber::grabFrame() Error(_log, "Lost DirectX capture context. Stopping."); _dxRestartNow = true; } - else if (_warningCounter > 0) + else if (display.warningCounter > 0) { Error(_log, "AcquireNextFrame failed. Reason: %i", status); - _warningCounter--; + display.warningCounter--; } SafeRelease(&resourceDesktop); - _d3dDuplicate->ReleaseFrame(); + display.d3dDuplicate->ReleaseFrame(); if (_dxRestartNow) { @@ -823,6 +894,92 @@ void DxGrabber::grabFrame() } } +int DxGrabber::captureFrame(DisplayHandle& display, Image& image) +{ + int result = -1; + DXGI_OUTDUPL_FRAME_INFO infoFrame{}; + IDXGIResource* resourceDesktop = nullptr; + + auto status = display.d3dDuplicate->AcquireNextFrame(0, &infoFrame, &resourceDesktop); + + if (CHECK(status)) + { + D3D11_MAPPED_SUBRESOURCE internalMap{}; + ID3D11Texture2D* texDesktop = nullptr; + + CLEAR(internalMap); + + status = resourceDesktop->QueryInterface(__uuidof(ID3D11Texture2D), (void**)&texDesktop); + + if (CHECK(status) && texDesktop != nullptr) + { + if (_hardware) + { + status = deepScaledCopy(display, texDesktop); + } + else + { + _d3dContext->CopyResource(display.d3dSourceTexture, texDesktop); + } + + if (CHECK(status) && CHECK(_d3dContext->Map(display.d3dSourceTexture, 0, D3D11_MAP_READ, 0, &internalMap))) + { + int lineSize = (int)internalMap.RowPitch; + bool useLut = !(_hardware && display.wideGamut); + int targetSizeX, targetSizeY; + int divide = getTargetSystemFrameDimension(display.actualWidth, display.actualHeight, targetSizeX, targetSizeY); + + image = Image(targetSizeX, targetSizeY); + FrameDecoder::processSystemImageBGRA(image, targetSizeX, targetSizeY, 0, 0, (uint8_t*)internalMap.pData, display.actualWidth, display.actualHeight, divide, (_hdrToneMappingEnabled == 0 || !_lutBufferInit || !useLut) ? nullptr : _lut.data(), lineSize); + + result = 1; + _d3dContext->Unmap(display.d3dSourceTexture, 0); + } + + SafeRelease(&texDesktop); + } + else if (display.warningCounter > 0) + { + Error(_log, "ResourceDesktop->QueryInterface failed. Reason: %i", status); + display.warningCounter--; + } + } + else if (status == DXGI_ERROR_WAIT_TIMEOUT) + { + if (display.warningCounter > 0) + { + Debug(_log, "AcquireNextFrame didn't return the frame. Just warning: the screen has not changed?"); + display.warningCounter--; + } + + if (_cacheImage.width() > 1) + { + result = 0; + } + } + else if (status == DXGI_ERROR_ACCESS_LOST) + { + Error(_log, "Lost DirectX capture context. Stopping."); + _dxRestartNow = true; + } + else if (display.warningCounter > 0) + { + Error(_log, "AcquireNextFrame failed. Reason: %i", status); + display.warningCounter--; + } + + SafeRelease(&resourceDesktop); + + display.d3dDuplicate->ReleaseFrame(); + + if (_dxRestartNow) + { + uninit(); + } + + return result; +} + void DxGrabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom) { diff --git a/sources/grabber/windows/MF/MFGrabber.cpp b/sources/grabber/windows/MF/MFGrabber.cpp index c5eeee303..f46867ded 100644 --- a/sources/grabber/windows/MF/MFGrabber.cpp +++ b/sources/grabber/windows/MF/MFGrabber.cpp @@ -111,7 +111,7 @@ const static VideoFormat fmt_array[] = { MFVideoFormat_Y42T, "Y42T", PixelFormat::NO_CHANGE }, { MFVideoFormat_P210, "P210", PixelFormat::NO_CHANGE }, { MFVideoFormat_P216, "P216", PixelFormat::NO_CHANGE }, - { MFVideoFormat_P010, "P010", PixelFormat::NO_CHANGE }, + { MFVideoFormat_P010, "P010", PixelFormat::P010 }, { MFVideoFormat_P016, "P016", PixelFormat::NO_CHANGE }, { MFVideoFormat_v210, "v210", PixelFormat::NO_CHANGE }, { MFVideoFormat_v216, "v216", PixelFormat::NO_CHANGE }, @@ -189,7 +189,7 @@ void MFGrabber::setHdrToneMappingEnabled(int mode) { Debug(_log, "setHdrToneMappingMode replacing LUT and restarting"); _MFWorkerManager.Stop(); - if ((_actualVideoFormat == PixelFormat::YUYV) || (_actualVideoFormat == PixelFormat::I420) || (_actualVideoFormat == PixelFormat::NV12) || (_actualVideoFormat == PixelFormat::MJPEG)) + if ((_actualVideoFormat == PixelFormat::YUYV) || (_actualVideoFormat == PixelFormat::I420) || (_actualVideoFormat == PixelFormat::P010) || (_actualVideoFormat == PixelFormat::NV12) || (_actualVideoFormat == PixelFormat::MJPEG)) loadLutFile(PixelFormat::YUYV); else loadLutFile(PixelFormat::RGB24); @@ -869,6 +869,14 @@ bool MFGrabber::init_device(QString selectedDeviceName, DevicePropertiesItem pro } break; + case PixelFormat::P010: + { + loadLutFile(PixelFormat::YUYV); + _frameByteSize = (6 * props.x * props.y) / 2; + _lineLength = props.x * 2; + } + break; + case PixelFormat::RGB24: { loadLutFile(PixelFormat::RGB24); @@ -1005,7 +1013,7 @@ bool MFGrabber::process_image(const void* frameImageBuffer, int size) loadLutFile(); } - bool directAccess = !(_signalAutoDetectionEnabled || _signalDetectionEnabled || isCalibrating() || (_benchmarkStatus >= 0)); + bool directAccess = !(_signalAutoDetectionEnabled || _signalDetectionEnabled || isCalibrating()); _workerThread->setup( i, _actualVideoFormat, diff --git a/sources/grabber/windows/MF/MFWorker.cpp b/sources/grabber/windows/MF/MFWorker.cpp index a70bccb4e..abd453011 100644 --- a/sources/grabber/windows/MF/MFWorker.cpp +++ b/sources/grabber/windows/MF/MFWorker.cpp @@ -139,7 +139,8 @@ MFWorker::MFWorker() : _frameBegin(0), _hdrToneMappingEnabled(0), _lutBuffer(nullptr), - _qframe(false) + _qframe(false), + _directAccess(false) { } @@ -198,7 +199,7 @@ void MFWorker::runMe() { Image image(_width >> 1, _height >> 1); FrameDecoder::processQImage( - _localBuffer.data(), _width, _height, _lineLength, _pixelFormat, _lutBuffer, image); + _localBuffer.data(), nullptr, _width, _height, _lineLength, _pixelFormat, _lutBuffer, image, _hdrToneMappingEnabled); image.setBufferCacheSize(); if (!_directAccess) @@ -217,7 +218,7 @@ void MFWorker::runMe() FrameDecoder::processImage( _cropLeft, _cropRight, _cropTop, _cropBottom, - _localBuffer.data(), nullptr, _width, _height, _lineLength, _pixelFormat, _lutBuffer, image); + _localBuffer.data(), nullptr, _width, _height, _lineLength, _pixelFormat, _lutBuffer, image, _hdrToneMappingEnabled); image.setBufferCacheSize(); if (!_directAccess) diff --git a/sources/grabber/windows/MF/MFWrapper.cpp b/sources/grabber/windows/MF/MFWrapper.cpp index fbbfa51b2..59facf9ff 100644 --- a/sources/grabber/windows/MF/MFWrapper.cpp +++ b/sources/grabber/windows/MF/MFWrapper.cpp @@ -28,6 +28,7 @@ #include #include #include +#include MFWrapper::MFWrapper(const QString& device, @@ -35,7 +36,6 @@ MFWrapper::MFWrapper(const QString& device, : GrabberWrapper("Media Foundation:" + device.left(14)) { _grabber = std::unique_ptr(new MFGrabber(device, configurationPath)); - connect(_grabber.get(), &Grabber::SignalBenchmarkUpdate, this, &GrabberWrapper::SignalBenchmarkUpdate); connect(_grabber.get(), &Grabber::SignalCapturingException, this, &GrabberWrapper::capturingExceptionHandler); connect(_grabber.get(), &Grabber::SignalSetNewComponentStateToAllInstances, this, &GrabberWrapper::SignalSetNewComponentStateToAllInstances); connect(_grabber.get(), &Grabber::SignalSaveCalibration, this, &GrabberWrapper::SignalSaveCalibration); diff --git a/sources/image/Image.cpp b/sources/image/Image.cpp index ff590ff26..666f6f8ee 100644 --- a/sources/image/Image.cpp +++ b/sources/image/Image.cpp @@ -27,6 +27,9 @@ #include +#include +#include +#include template Image::Image() : @@ -36,13 +39,15 @@ Image::Image() : template Image::Image(unsigned width, unsigned height) : - _sharedData(new ImageData(width, height)) + _sharedData(new ImageData(width, height)), + _pixelFormat(PixelFormat::NO_CHANGE) { } template Image::Image(const Image& other) : - _sharedData(other._sharedData) + _sharedData(other._sharedData), + _pixelFormat(other._pixelFormat) { } @@ -50,12 +55,14 @@ template Image& Image::operator=(const Image& other) { _sharedData = other._sharedData; + _pixelFormat = other._pixelFormat; return *this; } template Image& Image::operator=(Image&& other) noexcept { + _pixelFormat = other._pixelFormat; _sharedData = std::move(other._sharedData); return *this; } @@ -96,6 +103,25 @@ void Image::gradientVBox(uint16_t x1, uint16_t y1, uint16_t x2, uint _sharedData->gradientVBox(x1, y1, x2, y2, r, g, b); } + +template +void Image::insertHorizontal(int x, Image& source) +{ + int copyX = ((x + source.width()) > width()) ? width() - x : source.width(); + int copyY = std::min(source.height(), height()); + uint8_t* dest = rawMem() + sizeof(ColorSpace) * x; + uint8_t* src = source.rawMem(); + auto destRowSize = sizeof(ColorSpace) * width(); + auto srcRowSize = sizeof(ColorSpace) * source.width(); + + for (int y = 0; y < copyY; y++) + { + memcpy(dest, src, copyX * sizeof(ColorSpace)); + dest += destRowSize; + src += srcRowSize; + } +} + template unsigned Image::width() const { @@ -150,4 +176,27 @@ void Image::clear() _sharedData->clear(); } +template +bool Image::save(const char* filename) const +{ + std::ofstream myfile; + myfile.open(filename, std::ios::trunc | std::ios::out); + if (!myfile.is_open()) + return false; + myfile.write(reinterpret_cast(rawMem()), size()); + return true; +} + +template +void Image::setOriginFormat(PixelFormat pf) +{ + _pixelFormat = pf; +} + +template +PixelFormat Image::getOriginFormat() const +{ + return _pixelFormat; +} + template class Image; diff --git a/sources/led-drivers/net/DriverNetYeelight.cpp b/sources/led-drivers/net/DriverNetYeelight.cpp index 718e66ca8..837882d51 100644 --- a/sources/led-drivers/net/DriverNetYeelight.cpp +++ b/sources/led-drivers/net/DriverNetYeelight.cpp @@ -104,6 +104,9 @@ YeelightLight::YeelightLight(Logger* log, const QString& hostname, quint16 port , _brightnessFactor(1.0) , _transitionEffectParam(API_PARAM_EFFECT_SMOOTH) , _waitTimeQuota(API_DEFAULT_QUOTA_WAIT_TIME) + , _colorRgbValue(0) + , _bright(0) + , _ct(0) , _isOn(false) , _isInMusicMode(false) { diff --git a/sources/led-drivers/schemas/schema-philipshue.json b/sources/led-drivers/schemas/schema-philipshue.json index ced29694e..5e88e2c3b 100644 --- a/sources/led-drivers/schemas/schema-philipshue.json +++ b/sources/led-drivers/schemas/schema-philipshue.json @@ -111,7 +111,7 @@ "type": "boolean", "format": "checkbox", "title":"edt_dev_spec_candyGamma_title", - "default" : true, + "default" : false, "propertyOrder" : 10 }, "restoreOriginalState": { diff --git a/sources/led-strip/ColorSpaceCalibration.cpp b/sources/led-strip/ColorSpaceCalibration.cpp index e5a36bcaf..a97ea75a6 100644 --- a/sources/led-strip/ColorSpaceCalibration.cpp +++ b/sources/led-strip/ColorSpaceCalibration.cpp @@ -308,8 +308,8 @@ void ColorSpaceCalibration::applyBacklight(uint8_t& red, uint8_t& green, uint8_t } else { - int avVal = (std::min(int(red), std::min(int(green), int(blue))) + - std::max(int(red), std::max(int(green), int(blue)))) / 2; + int avVal = (1 + std::min(red, std::min(green, blue)) + + std::max(red, std::max(green, blue))) / 2; if (avVal < int(_sumBrightnessRGBLow)) { red = _sumBrightnessRGBLow; diff --git a/sources/lut-calibrator/BoardUtils.cpp b/sources/lut-calibrator/BoardUtils.cpp new file mode 100644 index 000000000..231c3c2fa --- /dev/null +++ b/sources/lut-calibrator/BoardUtils.cpp @@ -0,0 +1,527 @@ +/* BoardUtils.cpp +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include + +// YUV444 +// ffmpeg -loop 1 -t 90 -framerate 1/3 -i table_%d.png -stream_loop -1 -i audio.ogg -shortest -map 0:v:0 -map 1:a:0 -vf fps=25,colorspace=space=bt709:primaries=bt709:range=pc:trc=iec61966-2-1:ispace=bt709:iprimaries=bt709:irange=pc:itrc=iec61966-2-1:format=yuv444p10:fast=0:dither=none -c:v libx265 -pix_fmt yuv444p10le -profile:v main444-10 -preset veryslow -x265-params keyint=75:min-keyint=75:bframes=0:scenecut=0:psy-rd=0:psy-rdoq=0:rdoq=0:sao=false:cutree=false:deblock=false:strong-intra-smoothing=0:lossless=1:colorprim=bt709:transfer=iec61966-2-1:colormatrix=bt709:range=full -f mp4 test_SDR_yuv444_high_quality.mp4 +// ffmpeg -loop 1 -t 90 -framerate 1/3 -i table_%d.png -stream_loop -1 -i audio.ogg -shortest -map 0:v:0 -map 1:a:0 -vf fps=25,zscale=m=2020_ncl:p=2020:t=smpte2084:r=full:min=709:pin=709:tin=iec61966-2-1:rin=full:c=topleft:npl=200,format=yuv420p10le -c:v libx265 -vtag hvc1 -pix_fmt yuv444p10le -profile:v main444-10 -preset veryslow -x265-params keyint=75:min-keyint=75:bframes=0:scenecut=0:psy-rd=0:psy-rdoq=0:rdoq=0:sao=false:cutree=false:deblock=false:strong-intra-smoothing=0:hdr10=1:lossless=1:colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:range=full -f mp4 test_HDR_yuv444_high_quality.mp4 +// YUV420 +// ffmpeg -loop 1 -t 90 -framerate 1/3 -i table_%d.png -stream_loop -1 -i audio.ogg -shortest -map 0:v:0 -map 1:a:0 -vf fps=25,colorspace=space=bt709:primaries=bt709:range=pc:trc=iec61966-2-1:ispace=bt709:iprimaries=bt709:irange=pc:itrc=iec61966-2-1:format=yuv444p12:fast=0:dither=none -c:v libx265 -pix_fmt yuv420p10le -profile:v main10 -preset veryslow -x265-params keyint=75:min-keyint=75:bframes=0:scenecut=0:psy-rd=0:psy-rdoq=0:rdoq=0:sao=false:cutree=false:deblock=false:strong-intra-smoothing=0:lossless=1:colorprim=bt709:transfer=iec61966-2-1:colormatrix=bt709:range=full -f mp4 test_SDR_yuv420_low_quality.mp4 +// ffmpeg -loop 1 -t 90 -framerate 1/3 -i table_%d.png -stream_loop -1 -i audio.ogg -shortest -map 0:v:0 -map 1:a:0 -vf fps=25,zscale=m=2020_ncl:p=2020:t=smpte2084:r=full:min=709:pin=709:tin=iec61966-2-1:rin=full:c=topleft:npl=200,format=yuv420p10le -c:v libx265 -vtag hvc1 -pix_fmt yuv420p10le -profile:v main10 -preset veryslow -x265-params keyint=75:min-keyint=75:bframes=0:scenecut=0:psy-rd=0:psy-rdoq=0:rdoq=0:sao=false:cutree=false:deblock=false:strong-intra-smoothing=0:hdr10=1:lossless=1:colorprim=bt2020:transfer=smpte2084:colormatrix=bt2020nc:range=full -f mp4 test_HDR_yuv420_low_quality.mp4 + +using namespace linalg; +using namespace aliases; + +namespace BoardUtils +{ + + int indexToColorAndPos(int index, byte3& color, int2& position) + { + int currentIndex = index % SCREEN_SAMPLES_PER_BOARD; + int boardIndex = index / SCREEN_SAMPLES_PER_BOARD; + + position = int2(0, 1); + position.y += currentIndex / (SCREEN_BLOCKS_X / 2); + position.x += (currentIndex % (SCREEN_BLOCKS_X / 2)) * 2 + ((position.y + boardIndex )% 2); + + int B = (index % SCREEN_COLOR_DIMENSION) * SCREEN_COLOR_STEP; + int G = ((index / (SCREEN_COLOR_DIMENSION)) % SCREEN_COLOR_DIMENSION) * SCREEN_COLOR_STEP; + int R = (index / (SCREEN_COLOR_DIMENSION * SCREEN_COLOR_DIMENSION)) * SCREEN_COLOR_STEP; + + color.x = std::min(R, 255); + color.y = std::min(G, 255); + color.z = std::min(B, 255); + + return boardIndex; + } + + CapturedColor readBlock(const Image& yuvImage, int2 position, byte3* _color) + { + const double2 delta(yuvImage.width() / (double)SCREEN_BLOCKS_X, yuvImage.height() / (double)SCREEN_BLOCKS_Y); + CapturedColor color; + + if (_color != nullptr) + color.setSourceRGB(*_color); + + const double2 positionF{ position }; + const double2 startF = positionF * delta; + const double2 endF = ((positionF + double2(1, 1)) * delta) - double2(1, 1); + const int2 middle{ (startF + endF) / 2 }; + + if (middle.x + 1 >= static_cast(yuvImage.width()) || middle.y + 1 >= static_cast(yuvImage.height())) + throw std::runtime_error("Incorrect image size"); + + for (int x = -1; x <= 1; x++) + for (int y = -1; y <= 1; y++) + { + auto pos = middle + int2(x, y); + color.addColor(yuvImage(pos.x, pos.y)); + } + if (!color.calculateFinalColor()) + throw std::runtime_error("Too much noice while reading the color"); + return color; + } + + void getWhiteBlackColorLevels(const Image& yuvImage, CapturedColor& white, CapturedColor& black, int& line) + { + auto top = readBlock(yuvImage, int2(0, 0)); + auto bottom = readBlock(yuvImage, int2(0, SCREEN_BLOCKS_Y - 1)); + if (top.y() > bottom.y()) + { + white = top; + black = bottom; + line = 0; + } + else + { + white = bottom; + black = top; + line = SCREEN_BLOCKS_Y - 1; + } + } + + bool verifyBlackColorPattern(const Image& yuvImage, bool isFirstWhite, CapturedColor& black) + { + try + { + for (int x = 0; x < SCREEN_BLOCKS_X; x++) + for (int y = 0; y < SCREEN_BLOCKS_Y; y++) + if ((x + isFirstWhite + y) % 2 == 0) + { + auto test = readBlock(yuvImage, int2(x, y)); + if (test.Y() > black.Y() + SCREEN_MAX_CRC_BRIGHTNESS_ERROR) + return false; + } + } + catch (std::exception& ex) + { + // too much noice or too low resolution + return false; + } + + return true; + } + + int readCrc(const Image& yuvImage, int line, CapturedColor& white) + { + int boardIndex = 0; + int x = 0; + + for (int i = 0; i < SCREEN_CRC_COUNT; i++, x += 2) + { + auto test = readBlock(yuvImage, int2(x, line)); + if (test.Y() + SCREEN_MAX_CRC_BRIGHTNESS_ERROR < white.Y()) + throw std::runtime_error("Invalid CRC header"); + } + + while (true && x < SCREEN_BLOCKS_X) + { + auto test = readBlock(yuvImage, int2(x, line)); + if (test.Y() + SCREEN_MAX_CRC_BRIGHTNESS_ERROR < white.Y()) + break; + boardIndex++; + x += 2; + } + + return boardIndex; + } + + bool parseBoard(Logger* _log, const Image& yuvImage, int& boardIndex, CapturedColors& allColors, bool multiFrame) + { + bool exit = (multiFrame) ? false : true; + + int line = 0; + CapturedColor white, black; + + try + { + getWhiteBlackColorLevels(yuvImage, white, black, line); + } + catch (std::exception& ex) + { + Error(_log, "Too much noice or too low resolution"); + return false; + } + + if (!verifyBlackColorPattern(yuvImage, (line == 0), black)) + { + Error(_log, "The black color pattern failed, too much noice or too low resolution"); + return false; + } + + try + { + boardIndex = readCrc(yuvImage, line, white); + if (boardIndex > SCREEN_LAST_BOARD_INDEX) + throw std::runtime_error("Unexpected board"); + + } + catch (std::exception& ex) + { + Error(_log, "Too much noice or too low resolution"); + return false; + } + + if (allColors.isCaptured(boardIndex)) + { + return true; + } + + int max1 = (boardIndex + 1) * SCREEN_SAMPLES_PER_BOARD; + int max2 = std::pow(SCREEN_COLOR_DIMENSION, 3); + for (int currentIndex = boardIndex * SCREEN_SAMPLES_PER_BOARD; currentIndex < max1 && currentIndex < max2; currentIndex++) + { + byte3 color; + int2 position; + indexToColorAndPos(currentIndex, color, position); + + int B = (currentIndex % SCREEN_COLOR_DIMENSION); + int G = ((currentIndex / (SCREEN_COLOR_DIMENSION)) % SCREEN_COLOR_DIMENSION); + int R = (currentIndex / (SCREEN_COLOR_DIMENSION * SCREEN_COLOR_DIMENSION)); + + try + { + auto capturedColor = readBlock(yuvImage, position, &color); + if (allColors.all[R][G][B].hasAnySample()) + { + allColors.all[R][G][B].importColors(capturedColor); + if (allColors.all[R][G][B].hasAllSamples()) + exit = true; + } + else + { + capturedColor.setSourceRGB(color); + allColors.all[R][G][B] = capturedColor; + } + } + catch (std::exception& ex) + { + Error(_log, "Could not read position [%i, %i]. Too much noice or too low resolution", position.x, position.y); + return false; + } + } + + if (black.Y() > SCREEN_YUV_RANGE_LIMIT || white.Y() < 255 - SCREEN_YUV_RANGE_LIMIT) + { + if (allColors.getRange() == YuvConverter::COLOR_RANGE::FULL) + Error(_log, "The YUV range is changing. Now is LIMITED."); + else if (allColors.getRange() == YuvConverter::COLOR_RANGE::UNKNOWN) + Info(_log, "The YUV range is LIMITED"); + allColors.setRange(YuvConverter::COLOR_RANGE::LIMITED); + } + else + { + if (allColors.getRange() == YuvConverter::COLOR_RANGE::LIMITED) + Error(_log, "The YUV range is changing. Now is FULL."); + else if (allColors.getRange() == YuvConverter::COLOR_RANGE::UNKNOWN) + Info(_log, "The YUV range is FULL"); + allColors.setRange(YuvConverter::COLOR_RANGE::FULL); + } + + if (multiFrame) + { + if (!exit) + Info(_log, "Successfully parsed image of test board no. %i. Waiting for another frame...", boardIndex); + else + Info(_log, "Successfully parsed final image of test board no. %i", boardIndex); + } + else + { + Info(_log, "Successfully parsed image of the %i test board", boardIndex); + } + + return exit; + } + + Image loadTestBoardAsYuv(const std::string& filename) + { + YuvConverter yuvConverter; + auto image = utils_image::load2image(filename); + for (int y = 0; y < static_cast(image.height()); y++) + for (int x = 0; x < static_cast(image.width()); x++) + { + ColorRgb& inputRgb = image(x, y); + const double3 scaledRgb = double3(inputRgb.red, inputRgb.green, inputRgb.blue) / 255.0; + const double3 outputYuv = yuvConverter.toYuvBT709(YuvConverter::COLOR_RANGE::FULL, scaledRgb) * 255.0; + const ColorRgb outputRgb = ColorRgb(ColorRgb::round(outputYuv.x), ColorRgb::round(outputYuv.y), ColorRgb::round(outputYuv.z)); + inputRgb = outputRgb; + } + return image; + } + + void createTestBoards(const char* pattern) + { + const int2 margin(3, 2); + int maxIndex = std::pow(SCREEN_COLOR_DIMENSION, 3); + int boardIndex = 0; + Image image(1920, 1080); + const int2 delta(image.width() / SCREEN_BLOCKS_X, image.height() / SCREEN_BLOCKS_Y); + + auto saveImage = [](Image& image, const int2& delta, int boardIndex, const char* pattern, int2 margin) + { + for (int line = 0; line < SCREEN_BLOCKS_Y; line += SCREEN_BLOCKS_Y - 1) + { + int2 position = int2((line + boardIndex) % 2, line); + + for (int x = 0; x < boardIndex + SCREEN_CRC_COUNT; x++, position.x += 2) + { + const int2 start = position * delta; + const int2 end = ((position + int2(1, 1)) * delta) - int2(1, 1); + image.fastBox(start.x + margin.x, start.y + margin.y, end.x - margin.x, end.y - margin.y, 255, 255, 255); + } + } + utils_image::savePng(QString(pattern).arg(QString::number(boardIndex)).toStdString(), image); + }; + + + image.clear(); + + if (256 % SCREEN_COLOR_STEP > 0 || image.width() % SCREEN_BLOCKS_X || image.height() % SCREEN_BLOCKS_Y) + return; + + for (int index = 0; index < maxIndex; index++) + { + byte3 color; + int2 position; + int currentBoard = indexToColorAndPos(index, color, position); + + if (currentBoard < 0) + return; + + if (boardIndex != currentBoard) + { + saveImage(image, delta, boardIndex, pattern, margin); + image.clear(); + boardIndex = currentBoard; + } + + const int2 start = position * delta; + const int2 end = ((position + int2(1, 1)) * delta) - int2(1, 1); + + image.fastBox(start.x + margin.x, start.y + margin.y, end.x - margin.x, end.y - margin.y, color.x, color.y, color.z); + } + saveImage(image, delta, boardIndex, pattern, margin); + } + + bool verifyTestBoards(Logger* _log, const char* pattern) + { + int boardIndex = -1; + CapturedColors captured; + + for (int i = 0; i <= SCREEN_LAST_BOARD_INDEX; i++) + { + QString file = QString(pattern).arg(QString::number(i)); + Image image; + + if (!QFile::exists(file)) + { + Error(_log, "LUT test board: %i. File %s does not exists", i, QSTRING_CSTR(file)); + return false; + } + + image = loadTestBoardAsYuv(file.toStdString()); + + if (image.width() == 1 || image.height() == 1) + { + Error(_log, "LUT test board: %i. Could load PNG: %s", i, QSTRING_CSTR(file)); + return false; + } + + if (!parseBoard(_log, image, boardIndex, captured)) + { + Error(_log, "LUT test board: %i. Could not parse: %s", i, QSTRING_CSTR(file)); + return false; + } + + if (boardIndex != i) + { + Error(_log, "LUT test board: %i. Could not parse: %s. Incorrect board index: %i", i, QSTRING_CSTR(file), boardIndex); + return false; + } + captured.setCaptured(boardIndex); + } + + if (!captured.areAllCaptured()) + { + Error(_log, "Not all test boards were loaded"); + return false; + } + + // verify colors + YuvConverter converter; + int maxError = 0, totalErrors = 0; + for (int r = 0; r < SCREEN_COLOR_DIMENSION; r++) + for (int g = 0; g < SCREEN_COLOR_DIMENSION; g++) + for (int b = 0; b < SCREEN_COLOR_DIMENSION; b++) + { + const auto& sample = captured.all[r][g][b]; + int3 sourceRgb = sample.getSourceRGB(); + auto result = converter.toRgb(YuvConverter::COLOR_RANGE::FULL, YuvConverter::YUV_COEFS::BT709, sample.yuv()) * 255.0; + int3 outputRgb = ColorSpaceMath::to_int3(ColorSpaceMath::to_byte3(result)); + int distance = linalg::distance(sourceRgb, outputRgb); + if (distance > maxError) + { + totalErrors++; + maxError = distance; + Warning(_log, "Current max error = %i for color (%i, %i, %i) received (%i, %i, %i)", maxError, + sourceRgb.x, sourceRgb.y, sourceRgb.z, + outputRgb.x, outputRgb.y, outputRgb.z); + } + else if (distance == maxError && distance > 0) + { + totalErrors++; + } + } + if (maxError > 1) + { + Error(_log, "Failed to verify colors. The error is too high (%i > 1)", maxError); + return false; + } + + // all is OK + Info(_log, "All [0 - %i] LUT test boards were tested successfully. Total small errors = %i", SCREEN_LAST_BOARD_INDEX, totalErrors); + return true; + } + + bool CapturedColors::isCaptured(int index) const + { + return ((_capturedFlag & (1 << index))); + } + + bool CapturedColors::areAllCaptured() + { + for (int i = 0; i <= BoardUtils::SCREEN_LAST_BOARD_INDEX; i++) + if (!isCaptured(i)) + return false; + + finilizeBoard(); + + return true; + } + + void CapturedColors::finilizeBoard() + { + // update stats + bool limited = (getRange() == YuvConverter::COLOR_RANGE::LIMITED); + _yRange = (limited) ? (235.0 - 16.0) / 255.0 : 1.0; + _yShift = (limited) ? (16.0 / 255.0) : 0; + _downYLimit = all[0][0][0].y(); + _upYLimit = all[SCREEN_COLOR_DIMENSION - 1][SCREEN_COLOR_DIMENSION - 1][SCREEN_COLOR_DIMENSION - 1].y(); + + for (int r = 0; r < SCREEN_COLOR_DIMENSION; r++) + for (int g = 0; g < SCREEN_COLOR_DIMENSION; g++) + for (int b = 0; b < SCREEN_COLOR_DIMENSION; b++) + { + all[r][g][b].setCoords(byte3(r, g, b)); + } + } + + void CapturedColors::correctYRange(double3& yuv, double yRange, double upYLimit, double downYLimit, double yShift) + { + yuv.x = ((yuv.x - downYLimit) / (upYLimit - downYLimit)) * yRange + yShift; + } + + void CapturedColors::getSignalParams(double& yRange, double& upYLimit, double& downYLimit, double& yShift) + { + yRange = _yRange; + upYLimit = _upYLimit; + downYLimit = _downYLimit; + yShift = _yShift; + } + + void CapturedColors::setCaptured(int index) + { + _capturedFlag |= (1 << index); + } + + void CapturedColors::setRange(YuvConverter::COLOR_RANGE range) + { + _range = range; + } + + YuvConverter::COLOR_RANGE CapturedColors::getRange() const + { + return _range; + } + + bool CapturedColors::saveResult(const char* filename, const std::string& result) + { + std::ofstream myfile; + myfile.open(filename, std::ios::trunc | std::ios::out); + + if (!myfile.is_open()) + return false; + + std::list> arrayMark = { {"{", "}"} }; // {"np.array([", "])"}, + + myfile << result << std::endl; + + for(const auto& currentArray : arrayMark) + { + myfile << "capturedData = " << currentArray.first; + for (int r = 0; r < SCREEN_COLOR_DIMENSION; r++) + { + for (int g = 0; g < SCREEN_COLOR_DIMENSION; g++) + { + myfile << std::endl << "\t"; + for (int b = 0; b < SCREEN_COLOR_DIMENSION; b++) + { + auto elems = all[r][g][b].getInputYUVColors(); + myfile << currentArray.first; + + for (const auto& elem : elems) + { + if (elem != elems.front()) + { + myfile << ","; + } + + myfile << " " << std::to_string(elem.second) << ","; + myfile << std::to_string((int)elem.first.x) << ","; + myfile << std::to_string((int)elem.first.y) << ","; + myfile << std::to_string((int)elem.first.z); + } + + myfile << currentArray.second << ((r + 1 < SCREEN_COLOR_DIMENSION || g + 1 < SCREEN_COLOR_DIMENSION || b + 1 < SCREEN_COLOR_DIMENSION) ? "," : ""); + } + } + } + myfile << std::endl << currentArray.second << ";" << std::endl; + } + + myfile.close(); + + return true; + } +} + diff --git a/sources/lut-calibrator/CMakeLists.txt b/sources/lut-calibrator/CMakeLists.txt index 1e56cc8db..890474492 100644 --- a/sources/lut-calibrator/CMakeLists.txt +++ b/sources/lut-calibrator/CMakeLists.txt @@ -11,6 +11,7 @@ add_library(lut-calibrator OBJECT ${lut-calibrator_SOURCES}) target_link_libraries(lut-calibrator Qt${Qt_VERSION}::Core Qt${Qt_VERSION}::Network + linalg ) if(USE_PRECOMPILED_HEADERS AND COMMAND target_precompile_headers) diff --git a/sources/lut-calibrator/CapturedColor.cpp b/sources/lut-calibrator/CapturedColor.cpp new file mode 100644 index 000000000..acdeeae0b --- /dev/null +++ b/sources/lut-calibrator/CapturedColor.cpp @@ -0,0 +1,350 @@ +/* CapturedColor.cpp +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + + +#include +#include + + +void CapturedColor::importColors(const CapturedColor& color) +{ + for (auto iter = color.inputColors.begin(); iter != color.inputColors.end(); ++iter) + { + for (int i = 0; i < (*iter).second; i++) + { + addColor((*iter).first); + } + } + calculateFinalColor(); +} + +bool CapturedColor::calculateFinalColor() +{ + if (inputColors.empty() || (linalg::maxelem(max - min) > BoardUtils::SCREEN_MAX_COLOR_NOISE_ERROR)) + return false; + + int count = 0; + color = double3{ 0,0,0 }; + + sortedInputYUVColors.clear(); + sortedInputYuvColors.clear(); + for (auto iter = inputColors.begin(); iter != inputColors.end(); ++iter) + { + color += ((*iter).first) * ((*iter).second); + count += ((*iter).second); + + // sort + bool inserted = false; + for (auto sorted = sortedInputYUVColors.begin(); sorted != sortedInputYUVColors.end(); ++sorted) + if (((*iter).second) > (*sorted).second) + { + sortedInputYUVColors.insert(sorted, std::pair((*iter).first, (*iter).second)); + inserted = true; + break; + } + + if (!inserted) + sortedInputYUVColors.push_back(std::pair((*iter).first, (*iter).second)); + } + + while(sortedInputYUVColors.size() > 3 || (sortedInputYUVColors.size() == 3 && sortedInputYUVColors.back().second < 6)) + sortedInputYUVColors.pop_back(); + + std::for_each(sortedInputYUVColors.begin(), sortedInputYUVColors.end(), [this](std::pair& m) { + + /*if (m.first.y >= 127 && m.first.y <= 129 && m.first.z >= 127 && m.first.z <= 129) + { + m.first.y = 128; + m.first.z = 128; + }*/ + + sortedInputYuvColors.push_back(std::pair(static_cast(m.first) / 255.0, m.second)); + }); + + + auto workColor = color / count; + + colorInt = ColorSpaceMath::to_byte3(workColor); + color /= (count * 255.0); + + if (sourceRGB.x == sourceRGB.y && sourceRGB.y == sourceRGB.z) + { + colorInt.y = colorInt.z = 128; + color.y = color.z = 128.0/255; + } + else + { + auto delta = workColor - colorInt; + + if (delta.y * delta.z < 0) + { + if (((fabs(delta.y) >= fabs(delta.z)) && delta.y > 0) || + ((fabs(delta.y) < fabs(delta.z)) && delta.z > 0)) + { + colorInt.y = std::floor(workColor.y); + colorInt.z = std::floor(workColor.z); + } + else if (((fabs(delta.y) >= fabs(delta.z)) && delta.y < 0) || + ((fabs(delta.y) < fabs(delta.z)) && delta.z < 0)) + { + colorInt.y = std::ceil(workColor.y); + colorInt.z = std::ceil(workColor.z); + } + } + } + + ColorSpaceMath::trim01(color); + + return true; +} + +bool CapturedColor::hasAllSamples() +{ + return totalSamples > 27; +} + +bool CapturedColor::hasAnySample() +{ + return totalSamples > 0; +} + + +void CapturedColor::addColor(ColorRgb i) +{ + addColor(byte3{ i.red, i.green, i.blue }); +} + +void CapturedColor::addColor(const byte3& i) +{ + bool empty = !hasAnySample(); + + if (empty || min.x > i.x) + min.x = i.x; + if (empty || min.y > i.y) + min.y = i.y; + if (empty || min.z > i.z) + min.z = i.z; + + if (empty || max.x < i.x) + max.x = i.x; + if (empty || max.y < i.y) + max.y = i.y; + if (empty || max.z < i.z) + max.z = i.z; + + auto findIter = std::find_if(inputColors.begin(), inputColors.end(), [&](auto& m) { + return m.first == i; + }); + + if (findIter == inputColors.end()) + { + inputColors.push_back(std::pair(i, 1)); + } + else + { + (*findIter).second++; + } + + + totalSamples++; +} + + +void CapturedColor::setCoords(const byte3& index) +{ + arrayCoords = index; + + int MAX_IND = BoardUtils::MAX_INDEX; + + if ((std::min(index.x, index.z) == 0 && std::max(index.x, index.z) == MAX_IND && (index.y % (MAX_IND / 2) == 0)) || + (std::min(index.x, index.y) == 0 && std::max(index.x, index.y) == MAX_IND && (index.z % (MAX_IND / 2) == 0)) || + (std::min(index.y, index.z) == 0 && std::max(index.y, index.z) == MAX_IND && (index.x % (MAX_IND / 2) == 0)) || + (index.x == MAX_IND && index.y == MAX_IND && index.z == MAX_IND / 2) || + (index.x == MAX_IND && index.y == MAX_IND / 2 && index.z == MAX_IND) + ) + { + lchPrimary = LchPrimaries::HIGH; + } + else + { + MAX_IND /= 2; + if (std::max(std::max(index.x, index.y), index.z) == MAX_IND && + ((std::min(index.x, index.z) == 0 && std::max(index.x, index.z) == MAX_IND && (index.y % (MAX_IND / 2) == 0)) || + (std::min(index.x, index.y) == 0 && std::max(index.x, index.y) == MAX_IND && (index.z % (MAX_IND / 2) == 0)) || + (std::min(index.y, index.z) == 0 && std::max(index.y, index.z) == MAX_IND && (index.x % (MAX_IND / 2) == 0)) || + (index.x == MAX_IND && index.y == MAX_IND && index.z == MAX_IND / 2) || + (index.x == MAX_IND && index.y == MAX_IND / 2 && index.z == MAX_IND) + )) + { + lchPrimary = LchPrimaries::LOW; + } + else + { + MAX_IND = (BoardUtils::MAX_INDEX * 3) / 4; + if (std::max(std::max(index.x, index.y), index.z) == MAX_IND && + ((std::min(index.x, index.z) == 0 && std::max(index.x, index.z) == MAX_IND && (index.y % (MAX_IND / 2) == 0)) || + (std::min(index.x, index.y) == 0 && std::max(index.x, index.y) == MAX_IND && (index.z % (MAX_IND / 2) == 0)) || + (std::min(index.y, index.z) == 0 && std::max(index.y, index.z) == MAX_IND && (index.x % (MAX_IND / 2) == 0)) || + (index.x == MAX_IND && index.y == MAX_IND && index.z == MAX_IND / 2) || + (index.x == MAX_IND && index.y == MAX_IND / 2 && index.z == MAX_IND) + )) + { + lchPrimary = LchPrimaries::MID; + } + else + { + lchPrimary = LchPrimaries::NONE; + } + } + } +} + +QString CapturedColor::toString() +{ + return QString("(%1, %2, %3)").arg(color.x).arg(color.y).arg(color.z); +} + +void CapturedColor::setSourceRGB(byte3 _color) +{ + sourceRGB = ColorSpaceMath::to_int3(_color); + + auto srgb = static_cast(sourceRGB) / 255.0; + sourceLch = ColorSpaceMath::xyz_to_lch(ColorSpaceMath::from_sRGB_to_XYZ(srgb) * 100.0); +} + +int3 CapturedColor::getSourceRGB() const +{ + return sourceRGB; +} + +void CapturedColor::setFinalRGB(byte3 input) +{ + bool found = (std::find(finalRGB.begin(), finalRGB.end(), input) != finalRGB.end()); + if (!found) + { + finalRGB.push_back(input); + } +} + +CapturedColor::LchPrimaries CapturedColor::isLchPrimary(double3* _lchCoords) const +{ + if (_lchCoords != nullptr) + { + *_lchCoords = sourceLch; + } + return lchPrimary; +} + +std::list CapturedColor::getFinalRGB() const +{ + return finalRGB; +} + +std::list> CapturedColor::getInputYUVColors() const +{ + return sortedInputYUVColors; +} + +std::list> CapturedColor::getInputYuvColors() const +{ + return sortedInputYuvColors; +} + +int CapturedColor::getSourceError(const int3& _color) const +{ + auto delta = linalg::abs( sourceRGB - _color); + int error = std::min((delta.x * delta.x + delta.y * delta.y + delta.z * delta.z), (int)BoardUtils::MAX_CALIBRATION_ERROR); + + if (sourceRGB.x == sourceRGB.y && sourceRGB.y == sourceRGB.z) + { + return error * 20; + } + else if ( + (arrayCoords.x == 0 || arrayCoords.x == BoardUtils::MAX_INDEX) && + (arrayCoords.y == 0 || arrayCoords.y == BoardUtils::MAX_INDEX) && + (arrayCoords.z == 0 || arrayCoords.z == BoardUtils::MAX_INDEX)) + { + if (arrayCoords.x != BoardUtils::MAX_INDEX) + delta.x = (delta.x * 3) / 4; + + if (arrayCoords.y != BoardUtils::MAX_INDEX) + delta.y = (delta.y * 3) / 4; + + if (arrayCoords.z != BoardUtils::MAX_INDEX) + delta.z = (delta.z * 3) / 4; + + return error * 5; + } + else if (sourceRGB.x == sourceRGB.y) + { + if (_color.x == _color.y) + { + return error / 4; + } + else if (_color.x > _color.y) + { + return error * 4 / 5; + } + else + { + return error * 6 / 5; + } + } + else if (sourceRGB.x == sourceRGB.z) + { + if (_color.z == _color.x) + { + return error / 4; + } + else if (_color.z > _color.x) + { + return error * 3 / 4; + } + else + { + return error * 5 / 4; + } + } + else if (sourceRGB.y == sourceRGB.z) + { + if (_color.z == _color.y) + { + return error / 4; + } + else if (_color.z > _color.y) + { + return error * 4 / 5; + } + else + { + return error * 6 / 5; + } + } + + + return error; +} diff --git a/sources/lut-calibrator/ColorSpace.cpp b/sources/lut-calibrator/ColorSpace.cpp new file mode 100644 index 000000000..445fedc3e --- /dev/null +++ b/sources/lut-calibrator/ColorSpace.cpp @@ -0,0 +1,624 @@ +/* ColorSpace.cpp +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#include +#include +#include + +using namespace linalg; +using namespace aliases; + +namespace ColorSpaceMath +{ + const std::map> knownPrimaries = { + { + PRIMARIES::SRGB, + { + { 0.6400, 0.3300 }, + { 0.3000, 0.6000 }, + { 0.1500, 0.0600 }, + { 0.3127, 0.3290 } + } + }, + { + PRIMARIES::BT_2020, + { + { 0.708, 0.292 }, + { 0.170, 0.797 }, + { 0.131, 0.046 }, + { 0.3127, 0.3290 } + } + }, + { + PRIMARIES::WIDE_GAMMUT, + { + { 0.7350, 0.2650 }, + { 0.1150, 0.8260 }, + { 0.1570, 0.0180 }, + { 0.3127, 0.3290 } + } + } + }; + + QString gammaToString(HDR_GAMMA gamma) + { + if (gamma == HDR_GAMMA::PQ) + return "PQ"; + else if (gamma == HDR_GAMMA::HLG) + return "HLG"; + else if (gamma == HDR_GAMMA::sRGB) + return "sRGB"; + else if (gamma == HDR_GAMMA::BT2020inSRGB) + return "BT2020 with sRGB TRC"; + else if (gamma == HDR_GAMMA::PQinSRGB) + return "PQ in SRGB"; + else if (gamma == HDR_GAMMA::P010) + return "P010"; + return "UNKNOWN"; + } + + double bt2020_nonlinear_to_linear(double input) + { + const double alpha = 1.09929682680944; + const double beta = 0.018053968510807; + + if (input < 0) return -bt2020_nonlinear_to_linear(-input); + if (input < beta * 4.5) + return input / 4.5; + else + return std::pow((input + alpha - 1) / alpha, 1 / 0.45); + } + + double bt2020_linear_to_nonlinear(double input) + { + const double alpha = 1.09929682680944; + const double beta = 0.018053968510807; + + if (input < 0) return -bt2020_linear_to_nonlinear(-input); + + if (input < beta) + return 4.5 * input; + else + return alpha * std::pow(input, 0.45) - (alpha - 1); + } + + double3 bt2020_nonlinear_to_linear(double3 input) + { + return double3(bt2020_nonlinear_to_linear(input[0]), + bt2020_nonlinear_to_linear(input[1]), + bt2020_nonlinear_to_linear(input[2])); + } + + double3 bt2020_linear_to_nonlinear(double3 input) + { + return double3(bt2020_linear_to_nonlinear(input[0]), + bt2020_linear_to_nonlinear(input[1]), + bt2020_linear_to_nonlinear(input[2])); + } + + double srgb_nonlinear_to_linear(double input) + { + constexpr float alpha = 1.055010718947587f; + constexpr float beta = 0.003041282560128f; + + input = std::max(input, 0.0); + + if (input < 12.92 * beta) + input = input / 12.92f; + else + input = std::pow((input + (alpha - 1.0)) / alpha, 2.4); + + return input; + } + + double3 srgb_nonlinear_to_linear(double3 input) + { + return double3(srgb_nonlinear_to_linear(input[0]), + srgb_nonlinear_to_linear(input[1]), + srgb_nonlinear_to_linear(input[2])); + } + + double srgb_linear_to_nonlinear(double input) + { + constexpr float alpha = 1.055010718947587f; + constexpr float beta = 0.003041282560128f; + + input = std::max(input, 0.0); + + if (input < beta) + input = input * 12.92; + else + input = alpha * std::pow(input, 1.0 / 2.4) - (alpha - 1.0); + + return input; + } + + double3 srgb_linear_to_nonlinear(double3 input) + { + return double3(srgb_linear_to_nonlinear(input[0]), + srgb_linear_to_nonlinear(input[1]), + srgb_linear_to_nonlinear(input[2])); + } + + double3 from_BT2020_to_BT709(double3 a) + { + double3 b; + b.x = 1.6605 * a.x - 0.5876 * a.y - 0.0728 * a.z; + b.y = -0.1246 * a.x + 1.1329 * a.y - 0.0083 * a.z; + b.z = -0.0182 * a.x - 0.1006 * a.y + 1.1187 * a.z; + return b; + } + + std::vector getPrimaries(PRIMARIES primary) + { + return knownPrimaries.at(primary); + } + + linalg::mat getPrimariesToXYZ(PRIMARIES primary) + { + const auto& primaries = knownPrimaries.at(primary); + return to_XYZ(primaries[0], primaries[1], primaries[2], primaries[3]); + } + + double PQ_ST2084(double scale, double nonlinear) + { + constexpr double M1 = (2610.0 / 16384.0); + constexpr double M2 = (2523.0 / 4096.0) * 128.0; + constexpr double C1 = (3424.0 / 4096.0); + constexpr double C2 = (2413.0 / 4096.0) * 32.0; + constexpr double C3 = (2392.0 / 4096.0) * 32.0; + + if (nonlinear > 0.0) + { + double xpow = std::pow(nonlinear, 1.0 / M2); + double num = std::max(xpow - C1, 0.0); + double den = std::max(C2 - C3 * xpow, DBL_MIN); + nonlinear = std::pow(num / den, 1.0 / M1); + } + else if (nonlinear < 0.0) + { + return -PQ_ST2084(scale, -nonlinear); + } + else + return 0; + + return scale * nonlinear; + } + + double3 PQ_ST2084(double scale, double3 nonlinear) + { + return double3(PQ_ST2084(scale, nonlinear[0]), + PQ_ST2084(scale, nonlinear[1]), + PQ_ST2084(scale, nonlinear[2])); + } + + double inverse_OETF_HLG(double t) + { + if (t < 0) return -inverse_OETF_HLG(-t); + + constexpr double a = 0.17883277; + constexpr double b = 0.28466892; + constexpr double c = 0.55991073; + constexpr double one_twelfth = 1.0 / 12.0; + + if (t < 0.5) return t * t / 3.0; + else return (std::exp((t - c) / a) + b) * one_twelfth; + }; + + double3 inverse_OETF_HLG(double3 input) + { + return double3(inverse_OETF_HLG(input[0]), + inverse_OETF_HLG(input[1]), + inverse_OETF_HLG(input[2])); + } + + double3 OOTF_HLG(double3 input, double gamma) + { + if (gamma == 0) + return input; + + double3 coefs{ 0.2627, 0.6780, 0.0593 }; + double luma = linalg::dot(input, coefs); + luma = linalg::pow(luma, gamma - 1.0); + + return input * luma; + } + + double3 OOTF_HLG(double _input, double gamma) + { + double3 input(_input); + + if (gamma == 0) + return input; + + return OOTF_HLG(input, gamma); + } + + double3 from_bt2020_to_XYZ(double3 x) + { + return mul(matrix_bt2020_to_XYZ, x); + } + + double3 from_XYZ_to_bt2020(double3 x) + { + constexpr double3x3 m = inverse(matrix_bt2020_to_XYZ); + return mul(m, x); + } + + double3 from_XYZ_to_sRGB(double3 x) + { + constexpr double3x3 m = inverse(matrix_sRgb_to_XYZ); + return mul(m, x); + } + + double3 from_sRGB_to_XYZ(double3 x) + { + return mul(matrix_sRgb_to_XYZ, x); + } + + double2 XYZ_to_xy(const double3& a) + { + double len = std::max(a.x + a.y + a.z, std::numeric_limits::epsilon()); + return { a.x / len, a.y / len }; + } + + + double3 xyz_to_lab(const double3& xyz) + { + double x = xyz.x / 95.047; + double y = xyz.y / 100.00; + double z = xyz.z / 108.883; + + x = (x > 0.008856) ? std::cbrt(x) : (7.787 * x + 16.0 / 116.0); + y = (y > 0.008856) ? std::cbrt(y) : (7.787 * y + 16.0 / 116.0); + z = (z > 0.008856) ? std::cbrt(z) : (7.787 * z + 16.0 / 116.0); + + return double3{ + (116.0 * y) - 16, + 500 * (x - y), + 200 * (y - z) + }; + } + + double3 lab_to_xyz(const double3& lab) + { + double y = (lab.x + 16.0) / 116.0; + double x = lab.y / 500.0 + y; + double z = y - lab.z / 200.0; + + double x3 = std::pow(x, 3); + double y3 = std::pow(y, 3); + double z3 = std::pow(z, 3); + + return double3{ + x = ((x3 > 0.008856) ? x3 : ((x - 16.0 / 116.0) / 7.787)) * 95.047, + y = ((y3 > 0.008856) ? y3 : ((y - 16.0 / 116.0) / 7.787)) * 100.0, + z = ((z3 > 0.008856) ? z3 : ((z - 16.0 / 116.0) / 7.787)) * 108.883 + }; + } + + double3 lab_to_lch(const double3& lab) + { + const auto& l = lab.x; + const auto& a = lab.y; + const auto& b = lab.z; + + const auto c = std::sqrt(std::pow(a, 2) + std::pow(b, 2)); + + auto h = std::atan2(b, a); + + if (h > 0) + { + h = (h / M_PI) * 180.0; + } + else + { + h = 360.0 - (std::abs(h) / M_PI) * 180.0; + } + + return double3{ l, c, h }; + } + + double3 lch_to_lab(double3 lch) + { + if (lch.z > 360.0) + lch.z -= 360.0; + else if (lch.z < 360.0) + lch.z += 360.0; + + double h = lch.z * M_PI / 180.0; + + return double3{ + lch.x, + std::cos(h) * lch.y, + std::sin(h) * lch.y }; + } + + double3 xyz_to_lch(const double3& xyz) + { + return lab_to_lch(xyz_to_lab(xyz)); + } + + double3 lch_to_xyz(const double3& lch) + { + return lab_to_xyz(lch_to_lab(lch)); + } + + byte3 to_byte3(const double3& v) + { + return byte3( + std::lround(std::max(std::min(v.x, 255.0), 0.0)), + std::lround(std::max(std::min(v.y, 255.0), 0.0)), + std::lround(std::max(std::min(v.z, 255.0), 0.0)) + ); + } + + int3 to_int3(const byte3& v) + { + return int3(v.x, v.y, v.z); + } + + int3 to_int3(const double3& v) + { + return int3(std::lround(v.x), std::lround(v.y), std::lround(v.z)); + } + + double3 to_double3(const byte3& v) + { + return double3(v.x, v.y, v.z); + } + + void trim01(double3& input) + { + input.x = std::max(0.0, std::min(input.x, 1.0)); + input.y = std::max(0.0, std::min(input.y, 1.0)); + input.z = std::max(0.0, std::min(input.z, 1.0)); + } + + QString vecToString(const double2& v) + { + return QString("[%1 %2]").arg(v[0], 7, 'f', 3).arg(v[1], 7, 'f', 3); + } + + QString vecToString(const double3& v) + { + return QString("[%1 %2 %3]").arg(v[0], 7, 'f', 3).arg(v[1], 7, 'f', 3).arg(v[2], 7, 'f', 3); + } + + QString vecToString(const double4& v) + { + return QString("[%1 %2 %3 %4]").arg(v[0], 6, 'f', 3).arg(v[1], 6, 'f', 3).arg(v[2], 6, 'f', 3).arg(v[3], 6, 'f', 3); + } + + QString vecToString(const byte3& v) + { + return QString("[%1 %2 %3]").arg(v[0], 3).arg(v[1], 3).arg(v[2], 3); + } + + QString vecToString(const int3& v) + { + return QString("[%1 %2 %3]").arg(v[0], 3).arg(v[1], 3).arg(v[2], 3); + } + + QString matToString(double4x4 m) + { + QStringList ret; + for (int d = 0; d < 4; d++) + { + ret.append(vecToString(m.row(d))); + } + return ret.join("\r\n"); + } + + QString matToString(double3x3 m) + { + QStringList ret; + for (int d = 0; d < 3; d++) + { + ret.append(vecToString(m.row(d))); + } + return ret.join("\r\n"); + } + + + constexpr double intersectSegments(const double2& p1, const double2& p2, const double2& p3, const double2& p4) + { + const double denominator = linalg::determinant( double2x2{ {p1.x - p2.x, p3.x - p4.x}, {p1.y - p2.y, p3.y - p4.y} }); + if (denominator == 0.0) + return DBL_MAX; + + const double t = linalg::determinant( double2x2{ {p1.x - p3.x, p3.x - p4.x}, {p1.y - p3.y, p3.y - p4.y} } ) / denominator; + if (t >= 0.0) + return t; + return DBL_MAX; + } + + double maxLenInColorspace + (const std::vector& primaries, + const double cos_angle, + const double sin_angle) + { + const double2& p1 = primaries[3]; + + const double2 p2 = double2{ p1.x + cos_angle, p1.y + sin_angle }; + + double distance_to_edge = DBL_MAX; + for (size_t i = 0; i < 3; i++) + { + const size_t nextPrimary = (i == 2) ? 0 : (i + 1); + const double2& p3 = primaries[i]; + const double2& p4 = primaries[nextPrimary]; + const float distance = intersectSegments(p1, p2, p3, p4); + if (distance < distance_to_edge) + distance_to_edge = distance; + } + + return distance_to_edge; + } + + double2 primaryRotateAndScale(const double2 primary, + const double scaling, + const double rotation, + const std::vector& primaries, + bool truncate) + { + const double2 d = primary - primaries[3]; + const double angle = std::atan2(d.y, d.x) + rotation; + const double cos_angle = std::cos(angle); + const double sin_angle = std::sin(angle); + const double2 dx = double2{ cos_angle, sin_angle } * scaling * ((truncate) ? maxLenInColorspace(primaries, cos_angle, sin_angle) : linalg::length(d)); + + return dx + primaries[3]; + } + + void serialize(std::stringstream& out, const double2& v) + { + out << "{" << v[0] << ", " << v[1] << "}"; + } + + void serialize(std::stringstream& out, const double3& v) + { + out << "{" << v[0] << ", " << v[1] << ", " << v[2] << "}"; + } + + void serialize(std::stringstream& out, const double4& v) + { + out << "{" << v[0] << ", " << v[1] << ", " << v[2] << ", " << v[3] << "}"; + } + + void serialize(std::stringstream& out, const double4x4& m) + { + out << "{"; + for (int d = 0; d < 4; d++) + { + if (d != 0) out << ", "; + serialize(out, m[d]); + } + out << "}"; + } + + void serialize(std::stringstream& out, const double3x3& m) + { + out << "{"; + for (int d = 0; d < 3; d++) + { + if (d != 0) out << ", "; + serialize(out, m[d]); + } + out << "}"; + } + + float3 rgb2hsv(float3 rgb) + { + float aspect = 0.0f; + + if (rgb.y < rgb.z) + { + std::swap(rgb.y, rgb.z); + aspect = -1.0f; + } + + if (rgb.x < rgb.y) + { + std::swap(rgb.x, rgb.y); + aspect = -2.0f / 6.0f - aspect; + } + + float chroma = rgb.x - std::min(rgb.y, rgb.z); + return float3{ + /*H*/ std::fabs(aspect + (rgb.y - rgb.z) / (6.0f * chroma + 1e-20f)), + /*S*/ chroma / (rgb.x + 1e-20f), + /*V*/ rgb.x + }; + } + + float3 hsv2rgb(float3 hsv) + { + int i = floor(hsv.x * 6); + float f = hsv.x * 6 - i; + float p = hsv.z * (1 - hsv.y); + float q = hsv.z * (1 - f * hsv.y); + float t = hsv.z * (1 - (1 - f) * hsv.y); + + switch (i % 6) { + case 0: return float3{ hsv.z, t, p }; break; + case 1: return float3{ q, hsv.z, p }; break; + case 2: return float3{ p, hsv.z, t }; break; + case 3: return float3{ p, q, hsv.z }; break; + case 4: return float3{ t, p, hsv.z }; break; + case 5: return float3{ hsv.z, p, q }; break; + } + + return float3(); + } + + double3 rgb2hsv(double3 rgb) + { + double aspect = 0.0; + + if (rgb.y < rgb.z) + { + std::swap(rgb.y, rgb.z); + aspect = -1.0; + } + + if (rgb.x < rgb.y) + { + std::swap(rgb.x, rgb.y); + aspect = -2.0 / 6.0 - aspect; + } + + double chroma = rgb.x - std::min(rgb.y, rgb.z); + return double3{ + /*H*/ std::abs(aspect + (rgb.y - rgb.z) / (6.0 * chroma + 1e-20)), + /*S*/ chroma / (rgb.x + 1e-20), + /*V*/ rgb.x + }; + } + + double3 hsv2rgb(double3 hsv) + { + int i = floor(hsv.x * 6); + double f = hsv.x * 6 - i; + double p = hsv.z * (1 - hsv.y); + double q = hsv.z * (1 - f * hsv.y); + double t = hsv.z * (1 - (1 - f) * hsv.y); + + switch (i % 6) { + case 0: return double3{ hsv.z, t, p }; break; + case 1: return double3{ q, hsv.z, p }; break; + case 2: return double3{ p, hsv.z, t }; break; + case 3: return double3{ p, q, hsv.z }; break; + case 4: return double3{ t, p, hsv.z }; break; + case 5: return double3{ hsv.z, p, q }; break; + } + + return double3(); + } + +}; + diff --git a/sources/lut-calibrator/LutCalibrator.cpp b/sources/lut-calibrator/LutCalibrator.cpp index dea3e4e1e..ce5366bd2 100644 --- a/sources/lut-calibrator/LutCalibrator.cpp +++ b/sources/lut-calibrator/LutCalibrator.cpp @@ -1,4 +1,4 @@ -/* LutCalibrator.cpp + /* LutCalibrator.cpp * * MIT License * @@ -34,211 +34,363 @@ #include #include - #include - - #include + #include #endif #define STRING_CSTR(x) (x.operator std::string()).c_str() #include - +#include +#include +#include #include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include -ColorRgb LutCalibrator::primeColors[] = { - ColorRgb(255, 0, 0), ColorRgb(0, 255, 0), ColorRgb(0, 0, 255), ColorRgb(255, 255, 0), - ColorRgb(255, 0, 255), ColorRgb(0, 255, 255),ColorRgb(255, 128, 0), ColorRgb(255, 0, 128), ColorRgb(0, 128, 255), - ColorRgb(128, 64, 0), ColorRgb(128, 0, 64), - ColorRgb(128, 0, 0),ColorRgb(0, 128, 0),ColorRgb(0, 0, 128), - ColorRgb(16, 16, 16), ColorRgb(32, 32, 32), ColorRgb(48, 48, 48), ColorRgb(64, 64, 64), ColorRgb(96, 96, 96), ColorRgb(120, 120, 120), ColorRgb(144, 144, 144), ColorRgb(172, 172, 172), ColorRgb(196, 196, 196), ColorRgb(220, 220, 220), - ColorRgb(255, 255, 255), - ColorRgb(0, 0, 0) -}; +using namespace linalg; +using namespace aliases; +using namespace ColorSpaceMath; +using namespace BoardUtils; #define LUT_FILE_SIZE 50331648 #define LUT_INDEX(y,u,v) ((y + (u<<8) + (v<<16))*3) -#define REC(x) (x == 2) ? "REC.601" : (x == 1) ? "REC.709" : "FCC" -LutCalibrator* LutCalibrator::instance = nullptr; +///////////////////////////////////////////////////////////////// +// HELPERS // +///////////////////////////////////////////////////////////////// +struct MappingPrime; -LutCalibrator::LutCalibrator() + +enum SampleColor { RED = 0, GREEN, BLUE, LOW_RED, LOW_GREEN, LOW_BLUE }; + +class CreateLutWorker : public QRunnable +{ + const int startV; + const int endV; + const int phase; + const YuvConverter* yuvConverter; + const BestResult* bestResult; + uint8_t* lut; +public: + CreateLutWorker(int _startV, int _endV, int _phase, YuvConverter* _yuvConverter, BestResult* _bestResult, uint8_t* _lut) : + startV(_startV), + endV(_endV), + phase(_phase), + yuvConverter(_yuvConverter), + bestResult(_bestResult), + lut(_lut) + { + }; + void run() override; +}; + +///////////////////////////////////////////////////////////////// +// LUT CALIBRATOR // +///////////////////////////////////////////////////////////////// + +LutCalibrator::LutCalibrator(QString rootpath, hyperhdr::Components defaultComp, bool debug, bool lchCorrection) { _log = Logger::getInstance("CALIBRATOR"); - _mjpegCalibration = false; - _finish = false; - _limitedRange = false; - _saturation = 1; - _luminance = 1; - _gammaR = 1; - _gammaG = 1; - _gammaB = 1; - _checksum = -1; - _currentCoef = 0; - std::fill_n(_coefsResult, sizeof(_coefsResult) / sizeof(double), 0.0); - _warningCRC = _warningMismatch = -1; - _startColor = ColorRgb(0, 0, 0); - _endColor = ColorRgb(0, 0, 0); - _minColor = ColorRgb(255, 255, 255); - for (capColors selector = capColors::Red; selector != capColors::None; selector = capColors(((int)selector) + 1)) - _colorBalance[(int)selector].reset(); - _maxColor = ColorRgb(0, 0, 0); - _timeStamp = 0; + _capturedColors = std::make_shared(); + _yuvConverter = std::make_shared(); + + + _rootPath = rootpath; + _debug = debug; + _lchCorrection = lchCorrection; + _defaultComp = defaultComp; + _forcedExit = false; } LutCalibrator::~LutCalibrator() -{ +{ + Info(_log, "The calibration object is deleted"); } -void LutCalibrator::incomingCommand(QString rootpath, GrabberWrapper* grabberWrapper, hyperhdr::Components defaultComp, int checksum, ColorRgb startColor, ColorRgb endColor, bool limitedRange, double saturation, double luminance, double gammaR, double gammaG, double gammaB, int coef) +static void unpackP010(double *y, double *u, double *v) { - _rootPath = rootpath; - - if (checksum == 0) + if (y !=nullptr) { - if (grabberWrapper != nullptr && !_mjpegCalibration) - { - if (grabberWrapper->getHdrToneMappingEnabled() != 0) - { - QJsonObject report; - stopHandler(); - Error(_log, "Please disable LUT tone mapping and run the test again"); - report["status"] = 1; - report["error"] = "Please disable LUT tone mapping and run the test again"; - SignalLutCalibrationUpdated(report); - return; - } + double val = FrameDecoderUtils::unpackLuminanceP010(*y); + *y = (16.0 + val * 219.0) / 255.0; + } + + for (auto chroma : { u, v }) + if (chroma != nullptr) + { + double val = (*chroma * 255.0 - 128.0) / 128.0; + double fVal = FrameDecoderUtils::unpackChromaP010(std::abs(val)); + *chroma = (128.0 + ((val < 0) ? -fVal : fVal) * 112.0) / 255.0; + } +}; - QString vidMode; +static void unpackP010(double3& yuv) +{ + unpackP010(&yuv.x, &yuv.y, &yuv.z); +}; - SAFE_CALL_0_RET(grabberWrapper, getVideoCurrentModeResolution, QString, vidMode); +void LutCalibrator::cancelCalibrationSafe() +{ + _forcedExit = true; + AUTO_CALL_0(this, stopHandler); +} - _mjpegCalibration = (vidMode.indexOf("mjpeg", 0, Qt::CaseInsensitive) >= 0); +void LutCalibrator::error(QString message) +{ + QJsonObject report; + stopHandler(); + Error(_log, QSTRING_CSTR(message)); + report["error"] = message; + SignalLutCalibrationUpdated(report); +} - if (_mjpegCalibration) +QString LutCalibrator::generateReport(bool full) +{ + const int SCALE = SCREEN_COLOR_DIMENSION - 1; + + const std::list> reportColors = { + { "White", int3(SCALE, SCALE, SCALE) }, + { "Red", int3(SCALE, 0, 0) }, + { "Green", int3(0, SCALE, 0) }, + { "Blue", int3(0, 0, SCALE) }, + { "UpperRed", int3(SCALE * 3/ 4, 0, 0) }, + { "UpperGreen", int3(0, SCALE * 3/ 4, 0) }, + { "UpperBlue", int3(0, 0, SCALE *3 / 4) }, + { "MiddleRed", int3(SCALE / 2, 0, 0) }, + { "MiddleGreen",int3(0, SCALE / 2, 0) }, + { "MiddleBlue", int3(0, 0, SCALE / 2) }, + { "LowRed", int3(SCALE / 4, 0, 0) }, + { "LowGreen", int3(0, SCALE / 4, 0) }, + { "LowBlue", int3(0, 0, SCALE / 4) }, + { "Yellow", int3(SCALE, SCALE, 0) }, + { "Magenta", int3(SCALE, 0, SCALE) }, + { "Cyan", int3(0, SCALE, SCALE) }, + { "Orange", int3(SCALE, SCALE / 2, 0) }, + { "LimeBlue", int3(0, SCALE, SCALE / 2) }, + { "Pink", int3(SCALE, 0, SCALE / 2) }, + { "LimeRed", int3(SCALE / 2, SCALE, 0) }, + { "Azure", int3(0, SCALE / 2, SCALE) }, + { "Violet", int3(SCALE / 2, 0, SCALE) }, + { "WHITE_0", int3(0, 0, 0) }, + { "WHITE_1", int3(1, 1, 1) }, + { "WHITE_2", int3(2, 2, 2) }, + { "WHITE_3", int3(3, 3, 3) }, + { "WHITE_4", int3(4, 4, 4) }, + { "WHITE_5", int3(5, 5, 5) }, + { "WHITE_6", int3(6, 6, 6) }, + { "WHITE_7", int3(7, 7, 7) }, + { "WHITE_8", int3(8, 8, 8) }, + { "WHITE_9", int3(9, 9, 9) }, + { "WHITE_10", int3(10, 10, 10) }, + { "WHITE_11", int3(11, 11, 11) }, + { "WHITE_12", int3(12, 12, 12) }, + { "WHITE_13", int3(13, 13, 13) }, + { "WHITE_14", int3(14, 14, 14) }, + { "WHITE_15", int3(15, 15, 15) }, + { "WHITE_16", int3(16, 16, 16) } + }; + + QStringList rep; + for (const auto& color : reportColors) + if (color.second.x < SCREEN_COLOR_DIMENSION && color.second.y < SCREEN_COLOR_DIMENSION && color.second.z < SCREEN_COLOR_DIMENSION) + { + const auto& testColor = _capturedColors->all[color.second.x][color.second.y][color.second.z]; + + + if (!full) { - Debug(_log, "Enabling pseudo-HDR mode for calibration to bypass TurboJPEG MJPEG to RGB processing"); + auto list = testColor.getInputYUVColors(); + + QStringList colors; + for (auto i = list.begin(); i != list.end(); i++) + { + const auto& yuv = *(i); - emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_HDR, -1, true); + auto rgbBT709 = _yuvConverter->toRgb(_capturedColors->getRange(), YuvConverter::BT709, static_cast(yuv.first) / 255.0) * 255.0; - int hdrEnabled = 0; - SAFE_CALL_0_RET(grabberWrapper, getHdrToneMappingEnabled, int, hdrEnabled); - Debug(_log, "HDR is %s", (hdrEnabled) ? "enabled" : "disabled"); + colors.append(QString("%1 (YUV: %2)") + .arg(vecToString(ColorSpaceMath::to_byte3(rgbBT709)), 12) + .arg(vecToString(yuv.first), 12) + ); + } - if (!hdrEnabled) + rep.append(QString("%1: %2 => %3 %4") + .arg(QString::fromStdString(color.first), 12) + .arg(vecToString(testColor.getSourceRGB()), 12) + .arg(colors.join(", ")) + .arg(((list.size() > 1) ? " [source noise detected]" : ""))); + } + else + { + auto list = testColor.getFinalRGB(); + + QStringList colors; + for (auto i = list.begin(); i != list.end(); i++) { - QJsonObject report; - stopHandler(); - Error(_log, "Unexpected HDR state. Aborting"); - report["status"] = 1; - report["error"] = "Unexpected HDR state. Aborting"; - SignalLutCalibrationUpdated(report); - return; + colors.append(QString("%1").arg(vecToString((*i)), 12)); } + rep.append(QString("%1: %2 => %3 %4") + .arg(QString::fromStdString(color.first), 12) + .arg(vecToString(testColor.getSourceRGB()), 12) + .arg(colors.join(", ")) + .arg(((list.size() > 1) ? "[corrected, source noise detected]" : "[corrected]"))); } - } - _finish = false; - _limitedRange = limitedRange; - _saturation = saturation; - _luminance = luminance; - _gammaR = gammaR; - _gammaG = gammaG; - _gammaB = gammaB; - _checksum = -1; - _currentCoef = coef % (sizeof(_coefsResult) / sizeof(double)); - _warningCRC = _warningMismatch = -1; - _minColor = ColorRgb(255, 255, 255); - _maxColor = ColorRgb(0, 0, 0); - for (capColors selector = capColors::Red; selector != capColors::None; selector = capColors(((int)selector) + 1)) - _colorBalance[(int)selector].reset(); + }; + return rep.join("\r\n"); +} - - _lut.resize(LUT_FILE_SIZE * 2); +void LutCalibrator::notifyCalibrationFinished() +{ + QJsonObject report; + report["finished"] = true; + emit SignalLutCalibrationUpdated(report); +} - if (_lut.data() != nullptr) - { - memset(_lut.data(), 0, LUT_FILE_SIZE * 2); +void LutCalibrator::notifyCalibrationMessage(QString message, bool started) +{ + QJsonObject report; + report["message"] = message; + if (started) + { + report["start"] = true; + } + emit SignalLutCalibrationUpdated(report); +} - finalize(true); - memset(_lut.data(), 0, LUT_FILE_SIZE * 2); - - if (grabberWrapper != nullptr) - { - _log->disable(); +bool LutCalibrator::set1to1LUT() +{ + _lut.resize(LUT_FILE_SIZE); - BLOCK_CALL_0(grabberWrapper, stop); - BLOCK_CALL_0(grabberWrapper, start); + if (_lut.data() != nullptr) + { + for (int y = 0; y < 256; y++) + for (int u = 0; u < 256; u++) + for (int v = 0; v < 256; v++) + { + uint32_t ind_lutd = LUT_INDEX(y, u, v); + _lut.data()[ind_lutd] = y; + _lut.data()[ind_lutd + 1] = u; + _lut.data()[ind_lutd + 2] = v; + } - QThread::msleep(2000); + emit GlobalSignals::getInstance()->SignalSetLut(&_lut); + QThread::msleep(500); - _log->enable(); - } - else - { - Debug(_log, "Reloading LUT 1/2"); - emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_HDR, -1, true); - QThread::msleep(2000); - Debug(_log, "Reloading LUT 2/2"); - emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_HDR, -1, false); - QThread::msleep(2000); - Debug(_log, "Finished reloading LUT"); - } + return true; + } - if (defaultComp == hyperhdr::COMP_VIDEOGRABBER) - { - Debug(_log, "Using video grabber as a source"); - connect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewVideoImage, this, &LutCalibrator::setVideoImage, Qt::ConnectionType::UniqueConnection); - } - else if (defaultComp == hyperhdr::COMP_SYSTEMGRABBER) - { - Debug(_log, "Using system grabber as a source"); - connect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewSystemImage, this, &LutCalibrator::setSystemImage, Qt::ConnectionType::UniqueConnection); - } - else - { - Debug(_log, "Using flatbuffers/protobuffers as a source"); - connect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetGlobalImage, this, &LutCalibrator::signalSetGlobalImageHandler, Qt::ConnectionType::UniqueConnection); - } - } - else + return false; +} + + +void LutCalibrator::sendReport(Logger* _log, QString report) +{ + int total = 0; + QStringList list; + + auto lines = report.split("\r\n"); + for (const auto& line : lines) + { + if (total + line.size() + 4 >= 1024) { - QJsonObject report; - stopHandler(); - Error(_log, "Could not allocated memory (~100MB) for internal temporary buffer. Stopped."); - report["status"] = 1; - report["error"] = "Could not allocated memory (~100MB) for internal temporary buffer. Stopped."; - SignalLutCalibrationUpdated(report); - return; + Debug(_log, REPORT_TOKEN "\r\n%s", QSTRING_CSTR(list.join("\r\n"))); + total = 4; + list.clear(); } + + total += line.size() + 4; + list.append(line); + } + + Debug(_log, REPORT_TOKEN "%s\r\n", QSTRING_CSTR(list.join("\r\n"))); +} + +void LutCalibrator::startHandler() +{ + stopHandler(); + + _capturedColors.reset(); + _capturedColors = std::make_shared(); + bestResult = std::make_shared(); + + if (setTestData()) + { + notifyCalibrationMessage("Start calibration using test data"); + QTimer::singleShot(1000, this, &LutCalibrator::calibrate); + return; + } + + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_HDR, -1, false); + QThread::msleep(1500); + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_HDR, -1, true); + QThread::msleep(1500); + + if (!set1to1LUT()) + { + error("Could not allocated memory (~50MB) for internal temporary buffer. Stopped."); + return; + } + + if (_defaultComp == hyperhdr::COMP_VIDEOGRABBER) + { + auto message = "Using video grabber as a source
Waiting for first captured test board.."; + notifyCalibrationMessage(message); + Debug(_log, message); + connect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewVideoImage, this, &LutCalibrator::setVideoImage, Qt::ConnectionType::UniqueConnection); + } + else if (_defaultComp == hyperhdr::COMP_SYSTEMGRABBER) + { + auto message = "Using system grabber as a source
Waiting for first captured test board.."; + notifyCalibrationMessage(message); + Debug(_log, message); + connect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewSystemImage, this, &LutCalibrator::setSystemImage, Qt::ConnectionType::UniqueConnection); + } + else if (_defaultComp == hyperhdr::COMP_FLATBUFSERVER) + { + auto message = "Using flatbuffers/protobuffers as a source
Waiting for first captured test board.."; + notifyCalibrationMessage(message); + Debug(_log, message); + connect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetGlobalImage, this, &LutCalibrator::signalSetGlobalImageHandler, Qt::ConnectionType::UniqueConnection); + } + else + { + error(QString("Cannot use '%1' as a video source for calibration").arg(hyperhdr::componentToString(_defaultComp))); } - _checksum = checksum; - _startColor = startColor; - _endColor = endColor; - _timeStamp = InternalClock::now(); - - if (_checksum % 19 == 1) - Debug(_log, "Requested section: %i, %s, %s, YUV: %s, Coef: %s, Saturation: %f, Luminance: %f, Gammas: (%f, %f, %f)", - _checksum, STRING_CSTR(_startColor), STRING_CSTR(_endColor), (_limitedRange) ? "LIMITED" : "FULL", - REC(_currentCoef), _saturation, _luminance, _gammaR, _gammaG, _gammaB); } void LutCalibrator::stopHandler() { + disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewSystemImage, this, &LutCalibrator::setSystemImage); disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewVideoImage, this, &LutCalibrator::setVideoImage); disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetGlobalImage, this, &LutCalibrator::signalSetGlobalImageHandler); - _mjpegCalibration = false; - _finish = false; - _checksum = -1; - _warningCRC = _warningMismatch = -1; - std::fill_n(_coefsResult, sizeof(_coefsResult) / sizeof(double), 0.0); - _lut.releaseMemory(); + + if (_forcedExit) + { + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_HDR, -1, false); + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_HDR, -1, true); + + if (_defaultComp == hyperhdr::COMP_VIDEOGRABBER) + { + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_VIDEOGRABBER, -1, true); + } + if (_defaultComp == hyperhdr::COMP_FLATBUFSERVER) + { + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_FLATBUFSERVER, -1, true); + } + } } void LutCalibrator::setVideoImage(const QString& name, const Image& image) @@ -257,1111 +409,1365 @@ void LutCalibrator::signalSetGlobalImageHandler(int priority, const Image& image) -{ - int validate = 0; - int diffColor = 0; - QJsonObject report; - QJsonArray colors; - ColorRgb white{ 128,128,128 }, black{ 16,16,16 }; - double scaleX = image.width() / 128.0; - double scaleY = image.height() / 72.0; +{ + ////////////////////////////////////////////////////////////////////////// + ///////////////////////// Verify source //////////////////////////////// + ////////////////////////////////////////////////////////////////////////// - if (_checksum < 0) + if (image.width() < 1280 || image.height() < 720) + { + //image.save(QSTRING_CSTR(QString("d:/testimage_%1_x_%2.yuv").arg(image.width()).arg(image.height()))); + error(QString("Too low resolution: 1280/720 is the minimum. Received video frame: %1x%2. Stopped.").arg(image.width()).arg(image.height())); return; + } - if (image.width() < 3 * 128 || image.height() < 3 * 72) + if (image.width() * 1080 != image.height() * 1920) { - stopHandler(); - Error(_log, "Too low resolution: 384x216 is the minimum. Received video frame: %ix%i. Stopped.", image.width(), image.height()); - report["status"] = 1; - report["error"] = "Too low resolution: 384x216 is the minimum. Received video frame: %ix%i. Stopped."; - SignalLutCalibrationUpdated(report); + error("Invalid resolution width/height ratio. Expected aspect: 1920/1080 (or the same 1280/720 etc). Stopped."); return; } - if (image.width() * 1080 != image.height() * 1920) + auto pixelFormat = image.getOriginFormat(); + if (pixelFormat != PixelFormat::NV12 && pixelFormat != PixelFormat::MJPEG && pixelFormat != PixelFormat::YUYV && pixelFormat != PixelFormat::P010) { - stopHandler(); - Error(_log, "Invalid resolution width/height ratio. Expected aspect: 1920/1080 (or the same 1280/720 etc). Stopped."); - report["status"] = 1; - report["error"] = "Invalid resolution width/height ratio. Expected aspect: 1920/1080 (or the same 1280/720 etc). Stopped."; - SignalLutCalibrationUpdated(report); + error("Only NV12/MJPEG/YUYV/P010 video format for the USB grabber and NV12 for the flatbuffers source are supported for the LUT calibration."); return; } + else if (pixelFormat == PixelFormat::P010) + { + bestResult->signal.isSourceP010 = true; + } + + int boardIndex = -1; + + if (!parseBoard(_log, image, boardIndex, (*_capturedColors.get()), true) || _capturedColors->isCaptured(boardIndex)) + { + return; + } + + _capturedColors->setCaptured(boardIndex); - for (int py = 0; py < 72;) + notifyCalibrationMessage(QString("Captured test board: %1
Waiting for the next one...").arg(boardIndex)); + + + if (_capturedColors->areAllCaptured()) { - for (int px = (py < 71 && py > 0) ? _checksum % 2 : 0; px < 128; px++) - { - ColorRgb color; - int sX = (qRound(px * scaleX) + qRound((px + 1) * scaleX)) / 2; - int sY = (qRound(py * scaleY) + qRound((py + 1) * scaleY)) / 2; - int cR = 0, cG = 0, cB = 0; + Info(_log, "All boards are captured. Starting calibration..."); + stopHandler(); + notifyCalibrationMessage(QString("All boards are captured
Processing...
This will take a lot of time"), true); + QTimer::singleShot(200, this, &LutCalibrator::calibrate); + } +} - for (int i = -1; i <= 1; i++) - for (int j = -1; j <= 1; j++) - { - ColorRgb cur = image(sX + i, sY + j); - cR += cur.red; - cG += cur.green; - cB += cur.blue; - } - color = ColorRgb((uint8_t)qMin(qRound(cR / 9.0), 255), (uint8_t)qMin(qRound(cG / 9.0), 255), (uint8_t)qMin(qRound(cB / 9.0), 255)); +struct MappingPrime { + byte3 prime{}; + CapturedColor* sample = nullptr; + double3 org{}; + double3 real{}; + double3 delta{}; +}; +/* +double3 acesToneMapping(double3 input) +{ + const double3x3 aces_input_matrix = + { + {0.59719f, 0.35458f, 0.04823f}, + {0.07600f, 0.90834f, 0.01566f}, + {0.02840f, 0.13383f, 0.83777f} + }; + const double3x3 aces_output_matrix = + { + {1.60475f, -0.53108f, -0.07367f}, + {-0.10208f, 1.10813f, -0.00605f}, + {-0.00327f, -0.07276f, 1.07602f} + }; - if (py < 71 && py > 0) - { - if (!_finish) - { - storeColor(_startColor, color); - _finish |= increaseColor(_startColor); - } - else - increaseColor(_startColor); - } - else - { - if (px == 0) - { - white = color; - validate = 0; - diffColor = qMax(white.red, qMax(white.green, white.blue)); - diffColor = qMax((diffColor * 5) / 100, 10); - } - else if (px == 1) - { - black = color; - } - else if (px >= 8 && px < 24) - { - bool isWhite = ((diffColor + color.red) >= white.red && - (diffColor + color.green) >= white.green && - (diffColor + color.blue) >= white.blue); + auto rtt_and_odt_fit = [](double3 v) + { + double3 a = v * (v + 0.0245786) - 0.000090537; + double3 b = v * (0.983729 * v + 0.4329510) + 0.238081; + return a / b; + }; + + input = mul(aces_input_matrix, input); + input = rtt_and_odt_fit(input); + return mul(aces_output_matrix, input); +} - bool isBlack = (color.red <= (black.red + diffColor) && - color.green <= (black.green + diffColor) && - color.blue <= (black.blue + diffColor)); +double3 uncharted2_filmic(double3 v) +{ + float exposure_bias = 2.0f; - if ((isWhite && isBlack) || (!isWhite && !isBlack)) - { - if (_warningCRC != _checksum && (InternalClock::now() - _timeStamp > 1000)) - { - _warningCRC = _checksum; - Warning(_log, "Invalid CRC at: %i. CurrentColor: %s, Black: %s, White: %s, StartColor: %s, EndColor: %s.", int(px - 8), - STRING_CSTR(color), STRING_CSTR(black), - STRING_CSTR(white), STRING_CSTR(_startColor), - STRING_CSTR(_endColor)); - } - return; - } + auto uncharted2_tonemap_partial = [](double3 x) + { + float A = 0.15f; + float B = 0.50f; + float C = 0.10f; + float D = 0.20f; + float E = 0.02f; + float F = 0.30f; + return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F; + }; + + double3 curr = uncharted2_tonemap_partial(v * exposure_bias); + + double3 W = double3(11.2f); + double3 white_scale = double3(1.0f) / uncharted2_tonemap_partial(W); + return curr * white_scale; +}*/ + +static void doToneMapping(const LchLists& m, double3& p) +{ + auto a = xyz_to_lch(from_sRGB_to_XYZ(p) * 100.0); - auto sh = (isWhite) ? 1 << (15 - (px - 8)) : 0; + if (a.y < 0.1) + return; - validate |= sh; - } - else if (px == 24) - { - if (validate != _checksum) - { - if (_warningMismatch != _checksum && (InternalClock::now() - _timeStamp > 1000)) - { - _warningMismatch = _checksum; - Warning(_log, "CRC does not match: %i but expected %i, StartColor: %s , EndColor: %s", validate, _checksum, - STRING_CSTR(_startColor), STRING_CSTR(_endColor)); - } - return; - } - } - } + + double3 correctionHigh{}; + auto iterHigh = m.high.begin(); + auto lastHigh = *(iterHigh++); + for (; iterHigh != m.high.end(); lastHigh = *(iterHigh++)) + if ((lastHigh.w >= a.z && a.z >= (*iterHigh).w)) + { + auto& current = (*iterHigh); + double lastAsp = lastHigh.w - a.z; + double curAsp = a.z - current.w; + double prop = 1 - (lastAsp / (lastAsp + curAsp)); + + if (lastHigh.x > 0.0 && current.x > 0.0) + correctionHigh.x = prop * lastHigh.x + (1.0 - prop) * current.x; + if (lastHigh.y > 0.0 && current.y > 0.0) + correctionHigh.y = prop * lastHigh.y + (1.0 - prop) * current.y; + correctionHigh.z += prop * lastHigh.z + (1.0 - prop) * current.z; + break; } - switch (py) + double3 correctionMid{}; + auto iterMid = m.mid.begin(); + auto lastMid = *(iterMid++); + for (; iterMid != m.mid.end(); lastMid = *(iterMid++)) + if ((lastMid.w >= a.z && a.z >= (*iterMid).w)) { - case(0): py = 71; break; - case(70): py = 72; break; - case(71): py = 1; break; - default: py++; break; + auto& current = (*iterMid); + double lastAsp = lastMid.w - a.z; + double curAsp = a.z - current.w; + double prop = 1 - (lastAsp / (lastAsp + curAsp)); + + if (lastMid.x > 0 && current.x > 0) + correctionMid.x = prop * lastMid.x + (1 - prop) * current.x; + if (lastMid.y > 0 && current.y > 0) + correctionMid.y = prop * lastMid.y + (1 - prop) * current.y; + correctionMid.z += prop * lastMid.z + (1 - prop) * current.z; + break; } - } - if (_startColor != _endColor) - { - stopHandler(); - Error(_log, "Unexpected color %s != %s", STRING_CSTR(_startColor), STRING_CSTR(_endColor)); - report["status"] = 1; - SignalLutCalibrationUpdated(report); - return; - } + double3 correctionLow{}; + auto iterLow = m.low.begin(); + auto lastLow = *(iterLow++); + for (; iterLow != m.low.end(); lastLow = *(iterLow++)) + if ((lastLow.w >= a.z && a.z >= (*iterLow).w)) + { + auto& current = (*iterLow); + double lastAsp = lastLow.w - a.z; + double curAsp = a.z - current.w; + double prop = 1 - (lastAsp / (lastAsp + curAsp)); + + if (lastLow.x > 0 && current.x > 0) + correctionLow.x = prop * lastLow.x + (1 - prop) * current.x; + if (lastLow.y > 0 && current.y > 0) + correctionLow.y = prop * lastLow.y + (1 - prop) * current.y; + correctionLow.z += prop * lastLow.z + (1 - prop) * current.z; + break; + } + + double3 aHigh = a; + if (correctionHigh.x > 0) + aHigh.x *= correctionHigh.x; + if (correctionHigh.y > 0) + aHigh.y *= correctionHigh.y; + aHigh.z += correctionHigh.z; + + double3 pHigh = from_XYZ_to_sRGB(lch_to_xyz(aHigh) / 100.0); + + double3 aLow = a; + if (correctionLow.x > 0) + aLow.x *= correctionLow.x; + if (correctionLow.y > 0) + aLow.y *= correctionLow.y; + aLow.z += correctionLow.z; - report["status"] = 0; - report["validate"] = validate; + double3 pLow = from_XYZ_to_sRGB(lch_to_xyz(aLow) / 100.0); + double max = std::max(linalg::maxelem(pLow), linalg::maxelem(pHigh)); - if (_checksum % 19 == 1) + if (128.0 / 255.0 >= max) { - Info(_log, "The video frame has been analyzed. Progress: %i/21 section", _checksum); + if (correctionLow.x > 0) + { + a.x *= correctionLow.x; + } + if (correctionLow.y > 0) + { + a.y *= correctionLow.y; + } + a.z += correctionLow.z; } + else if (192.0 / 255.0 >= max) + { + double lenLow = std::abs((128.0 / 255.0) - max); + double lenMid = std::abs((192.0 / 255.0) - max); + double aspectMid = (1.0 - lenMid / (lenLow + lenMid)); - _checksum = -1; - - if (_finish) + if (correctionLow.x > 0 && correctionMid.x > 0) + { + a.x *= correctionMid.x * aspectMid + correctionLow.x * (1.0 - aspectMid); + } + if (correctionLow.y > 0 && correctionMid.y > 0) + { + a.y *= correctionMid.y * aspectMid + correctionLow.y * (1.0 - aspectMid); + } + a.z += correctionMid.z * aspectMid + correctionLow.z * (1.0 - aspectMid); + } + else { - if (!correctionEnd()) - return; + double lenMid = std::abs((192.0 / 255.0) - max); + double lenHigh = std::abs(1.0 - max); + double aspectHigh = (1.0 - lenHigh / (lenMid + lenHigh)); - if (!finalize()) - report["status"] = 1; + if (correctionMid.x > 0 && correctionHigh.x > 0) + { + a.x *= correctionHigh.x * aspectHigh + correctionMid.x * (1.0 - aspectHigh); + } + if (correctionMid.y > 0 && correctionHigh.y > 0) + { + a.y *= correctionHigh.y * aspectHigh + correctionMid.y * (1.0 - aspectHigh); + } + a.z += correctionHigh.z * aspectHigh + correctionMid.z * (1.0 - aspectHigh); } - SignalLutCalibrationUpdated(report); + + p = from_XYZ_to_sRGB(lch_to_xyz(a) / 100.0); + } -void LutCalibrator::storeColor(const ColorRgb& inputColor, const ColorRgb& color) +void LutCalibrator::printReport() { + QStringList info; + + info.append("-------------------------------------------------------------------------------------------------"); + info.append(" Detailed results"); + info.append("-------------------------------------------------------------------------------------------------"); + for (int r = 0; r < SCREEN_COLOR_DIMENSION; r++) + for (int g = 0; g < SCREEN_COLOR_DIMENSION; g++) + for (int b = 0; b < SCREEN_COLOR_DIMENSION; b++) + if ((r % 4 == 0 && g % 4 == 0 && b % 4 == 0) || _debug) + { + const auto& sample = _capturedColors->all[r][g][b]; + auto list = sample.getFinalRGB(); - for (capColors selector = capColors::Red; selector != capColors::None; selector = capColors(((int)selector) + 1)) - if (inputColor == primeColors[(int)selector]) - { - _colorBalance[(int)selector].AddColor(color); - break; - } + QStringList colors; + for (auto i = list.begin(); i != list.end(); i++) + { + colors.append(QString("%1").arg(vecToString(*i), 12)); + } + info.append(QString("%1 => %2 %3") + .arg(vecToString(sample.getSourceRGB()), 12) + .arg(colors.join(", ")) + .arg(((list.size() > 1)?"[source noise detected]" : ""))); + } - if (inputColor.red == inputColor.blue && inputColor.green == inputColor.blue) - { - if (color.red > _maxColor.red) _maxColor.red = color.red; - if (color.green > _maxColor.green) _maxColor.green = color.green; - if (color.blue > _maxColor.blue) _maxColor.blue = color.blue; - if (color.red < _minColor.red) _minColor.red = color.red; - if (color.green < _minColor.green) _minColor.green = color.green; - if (color.blue < _minColor.blue) _minColor.blue = color.blue; - } + info.append("-------------------------------------------------------------------------------------------------"); + sendReport(_log, info.join("\r\n")); } -bool LutCalibrator::increaseColor(ColorRgb& color) -{ - if (color == primeColors[capColors::None]) - color = primeColors[0]; - else + +static double3 hdr_to_srgb(const YuvConverter* _yuvConverter, double3 yuv, const linalg::vec& UV, const double3& aspect, const double4x4& coefMatrix, ColorSpaceMath::HDR_GAMMA gamma, double gammaHLG, double nits, int altConvert, const double3x3& bt2020_to_sRgb, int tryBt2020Range, const BestResult::Signal& signal, int colorAspectMode, const std::pair& colorAspect) +{ + double3 srgb; + bool white = true; + + if (gamma == HDR_GAMMA::P010) { - for (capColors selector = capColors::Red; selector != capColors::None; selector = capColors(((int)selector) + 1)) - if (color == primeColors[(int)selector]) - { - color = primeColors[(int)capColors(((int)selector) + 1)]; - break; - } + unpackP010(yuv); } - return (_checksum > 20) ? true : false; -} + if (gamma == HDR_GAMMA::sRGB || gamma == HDR_GAMMA::BT2020inSRGB) + { + CapturedColors::correctYRange(yuv, signal.yRange, signal.upYLimit, signal.downYLimit, signal.yShift); + } -double LutCalibrator::eotf(double scale, double x) noexcept -{ - // https://github.com/sekrit-twc/zimg/blob/master/src/zimg/colorspace/gamma.cpp - - constexpr double ST2084_M1 = 0.1593017578125; - constexpr double ST2084_M2 = 78.84375; - constexpr double ST2084_C1 = 0.8359375; - constexpr double ST2084_C2 = 18.8515625; - constexpr double ST2084_C3 = 18.6875; - - if (x > 0.0) { - double xpow = std::pow(x, 1.0 / ST2084_M2); - double num = std::max(xpow - ST2084_C1, 0.0); - double den = std::max(ST2084_C2 - ST2084_C3 * xpow, DBL_MIN); - x = std::pow(num / den, 1.0 / ST2084_M1); + if (signal.range == YuvConverter::COLOR_RANGE::LIMITED) + { + const double low = 16.0 / 255.0; + yuv.x = (yuv.x < low) ? low : (yuv.x - low) * aspect.x + low; } - else { - x = 0.0; + else + { + yuv.x *= aspect.x; + } + if (UV.x != UV.y || UV.x != 128) + { + const double mid = 128.0 / 256.0; + yuv.y = (yuv.y - mid) * aspect.y + mid; + yuv.z = (yuv.z - mid) * aspect.z + mid; + white = false; } - return scale * x; -} + auto a = _yuvConverter->multiplyColorMatrix(coefMatrix, yuv); -double LutCalibrator::inverse_eotf(double x) noexcept -{ - // https://github.com/sekrit-twc/zimg/blob/master/src/zimg/colorspace/gamma.cpp - - constexpr double ST2084_M1 = 0.1593017578125; - constexpr double ST2084_M2 = 78.84375; - constexpr double ST2084_C1 = 0.8359375; - constexpr double ST2084_C2 = 18.8515625; - constexpr double ST2084_C3 = 18.6875; - - if (x > 0.0f) { - double xpow = std::pow(x, ST2084_M1); - double num = (ST2084_C1 - 1.0) + (ST2084_C2 - ST2084_C3) * xpow; - double den = 1.0 + ST2084_C3 * xpow; - x = std::pow(1.0 + num / den, ST2084_M2); + double3 e; + + if (gamma == HDR_GAMMA::PQ || gamma == HDR_GAMMA::P010) + { + e = PQ_ST2084(10000.0 / nits, a); + } + else if (gamma == HDR_GAMMA::HLG) + { + e = OOTF_HLG(inverse_OETF_HLG(a), gammaHLG) * nits; + } + else if (gamma == HDR_GAMMA::sRGB) + { + srgb = a; } - else { - x = 0.0f; + else if (gamma == HDR_GAMMA::BT2020inSRGB) + { + e = srgb_nonlinear_to_linear(a); + } + else if (gamma == HDR_GAMMA::PQinSRGB) + { + a = srgb_linear_to_nonlinear(a); + e = PQ_ST2084(10000.0 / nits, a); } - return x; -} -double LutCalibrator::ootf(double v) noexcept -{ - // https://github.com/sekrit-twc/zimg/blob/master/src/zimg/colorspace/gamma.cpp + if (gamma != HDR_GAMMA::sRGB) + { + + if (altConvert) + { + srgb = mul(bt2020_to_sRgb, e); + } + else + { + srgb = ColorSpaceMath::from_BT2020_to_BT709(e); + } + + srgb = srgb_linear_to_nonlinear(srgb); + } + + if (tryBt2020Range) + { + srgb = bt2020_nonlinear_to_linear(srgb); + srgb = srgb_linear_to_nonlinear(srgb); + } + + if (colorAspectMode) + { + if (colorAspectMode == 1) + { + if (!white) + { + srgb *= (colorAspect).first; + } + else + { + double av = ((colorAspect).first.x + (colorAspect).first.y + (colorAspect).first.z) / 3.0; + srgb *= av; + } + } + else if (colorAspectMode == 2) + { + if (!white) + { + srgb *= ((colorAspect).first + (colorAspect).second) * 0.5; + } + else + { + double av = ((colorAspect).first.x + (colorAspect).first.y + (colorAspect).first.z + (colorAspect).second.x + (colorAspect).second.y + (colorAspect).second.z) / 6.0; + srgb *= av; + } + } + else + { + if (!white) + { + srgb *= (colorAspect).second; + } + else + { + double av = ((colorAspect).second.x + (colorAspect).second.y + (colorAspect).second.z) / 3.0; + srgb *= av; + } + } - constexpr double SRGB_ALPHA = 1.055010718947587; - constexpr double SRGB_BETA = 0.003041282560128; + } - v = std::max(v, 0.0); + ColorSpaceMath::trim01(srgb); - if (v < SRGB_BETA) - return v * 12.92; - else - return SRGB_ALPHA * std::pow(v, 1.0 / 2.4) - (SRGB_ALPHA - 1.0); + return srgb; } -double LutCalibrator::inverse_gamma(double x) noexcept +static LchLists prepareLCH(std::list>> __lchPrimaries) { - // https://github.com/sekrit-twc/zimg/blob/master/src/zimg/colorspace/gamma.cpp + int index = 0; + LchLists ret; + + for (const auto& _lchPrimaries : __lchPrimaries) + { + std::list lchPrimaries; + for (const auto& c : _lchPrimaries) + { + const auto& org = c.first; + auto current = xyz_to_lch(from_sRGB_to_XYZ(c.second) * 100.0); + + double4 correctionZ; + correctionZ.x = (current.x != 0.0) ? org.x / current.x : 0.0; + correctionZ.y = (current.y != 0.0) ? org.y / current.y : 0.0; + correctionZ.z = org.z - current.z; + correctionZ.w = current.z; + lchPrimaries.push_back(correctionZ); + } - x = std::max(x, 0.0); + lchPrimaries.sort([](const double4& a, const double4& b) { return a.w > b.w; }); - x = std::pow(x, 1.0 / 2.4); + double4 loopEnd = lchPrimaries.front(); + double4 loopFront = lchPrimaries.back(); - return x; -} + loopEnd.w -= 360.0; + lchPrimaries.push_back(loopEnd); + loopFront.w += 360.0; + lchPrimaries.push_front(loopFront); -void LutCalibrator::fromBT2020toXYZ(double r, double g, double b, double& x, double& y, double& z) -{ - x = 0.636958 * r + 0.144617 * g + 0.168881 * b; - y = 0.262700 * r + 0.677998 * g + 0.059302 * b; - z = 0.000000 * r + 0.028073 * g + 1.060985 * b; -} + if (index == 0) + ret.low = lchPrimaries; + else if (index == 1) + ret.mid = lchPrimaries; + else if (index == 2) + ret.high = lchPrimaries; + index++; + } -void LutCalibrator::fromXYZtoBT709(double x, double y, double z, double& r, double& g, double& b) -{ - r = 3.240970 * x - 1.537383 * y - 0.498611 * z; - g = -0.969244 * x + 1.875968 * y + 0.041555 * z; - b = 0.055630 * x - 0.203977 * y + 1.056972 * z; + return ret; } -void LutCalibrator::fromBT2020toBT709(double x, double y, double z, double& r, double& g, double& b) +void CalibrationWorker::run() { - r = 1.6605 * x - 0.5876 * y - 0.0728 * z; - g = -0.1246 * x + 1.1329 * y - 0.0083 * z; - b = -0.0182 * x - 0.1006 * y + 1.1187 * z; -} + constexpr int MAX_HINT = std::numeric_limits::max() / 2.0; + constexpr double MAX_HDOUBLE = std::numeric_limits::max() / 2.0; -void LutCalibrator::toneMapping(double xHdr, double yHdr, double zHdr, double& xSdr, double& ySdr, double& zSdr) -{ - double mian = xHdr + yHdr + zHdr; + printf("Starter thread: %i. Range: [%i - %i)\n", id, krIndexStart, krIndexEnd); - if (mian < 0.000001) + for (int krIndex = krIndexStart; krIndex < std::min(krIndexEnd, (halfKDelta * 2) + 1); krIndex++) + for (int kbIndex = 0; kbIndex <= 2 * halfKDelta; kbIndex ++) { - xSdr = 0; - ySdr = 0; - zSdr = 0; - return; - } + if (!precise) + { + QString gammaString = ColorSpaceMath::gammaToString(HDR_GAMMA(gamma)); + QString coefString = yuvConverter->coefToString(YuvConverter::YUV_COEFS(coef)); + if (HDR_GAMMA(gamma) == ColorSpaceMath::HDR_GAMMA::HLG) + gammaString += QString(" %1").arg(gammaHLG); + emit notifyCalibrationMessage(QString("First phase progress
Gamma: %1
Coef: %2
%3/289").arg(gammaString).arg(coefString).arg(++progress), false); + } + else + { + emit notifyCalibrationMessage(QString("Second phase progress:
%1/1089").arg(++progress), false); + } - double k1 = 0.83802, k2 = 15.09968, k3 = 0.74204, k4 = 78.99439; - double check = 58.5 / k1; - double x = xHdr / mian; - double y = yHdr / mian; + double2 kDelta = double2(((krIndex <= halfKDelta) ? -krIndex : krIndex - halfKDelta), + ((kbIndex <= halfKDelta) ? -kbIndex : kbIndex - halfKDelta)) * ((precise) ? 0.002 : 0.004); - if (yHdr < check) - ySdr = qMax(k1 * yHdr, 0.0); - else - ySdr = qMax(k2 * std::log(yHdr / check - k3) + k4, 0.0); + auto coefValues = yuvConverter->getCoef(YuvConverter::YUV_COEFS(coef)) + kDelta; + auto coefMatrix = yuvConverter->create_yuv_to_rgb_matrix(bestResult.signal.range, coefValues.x, coefValues.y); + std::list coloredAspectModeList; - xSdr = (x / y) * ySdr; - zSdr = ((1 - x - y) / y) * ySdr; + if (!precise) + { + if (bestResult.signal.isSourceP010) + { + coloredAspectModeList = { 0 }; + } + else + { + coloredAspectModeList = { 0, 1, 2, 3 }; + } + } + else if (bestResult.coloredAspectMode != 0) + coloredAspectModeList = { 0, bestResult.coloredAspectMode }; + else + coloredAspectModeList = { 0}; + for (const int& coloredAspectMode : coloredAspectModeList) + for (int altConvert = (precise) ? bestResult.altConvert : 0; altConvert <= 1; altConvert += (precise) ? MAX_HINT : 1) + for (int tryBt2020Range = (precise) ? bestResult.bt2020Range : 0; tryBt2020Range <= 1; tryBt2020Range += (precise) ? MAX_HINT : 1) + for (double aspectX = 0.99; aspectX <= 1.0101; aspectX += ((precise) ? 0.0025 : 0.0025 * 2.0)) + { + for (double aspectYZ = 1.0; aspectYZ <= 1.2101; aspectYZ += ((precise) ? MAX_HDOUBLE : 0.005 * 2.0)) + for (double aspectY = bestResult.aspect.y - 0.02; aspectY <= bestResult.aspect.y + 0.021; aspectY += (precise) ? 0.005 : MAX_HDOUBLE) + for (double aspectZ = bestResult.aspect.z - 0.02; aspectZ <= bestResult.aspect.z + 0.021; aspectZ += (precise) ? 0.005 : MAX_HDOUBLE) + { + double3 aspect = (precise) ? double3{ aspectX, aspectY, aspectZ } : double3{ aspectX, aspectYZ, aspectYZ }; + + std::pair colorAspect = std::pair({ 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 }); + + std::list> selectedLchHighPrimaries, selectedLchMidPrimaries, selectedLchLowPrimaries; + + if (coloredAspectMode) + { + auto red = hdr_to_srgb(yuvConverter, sampleColors[SampleColor::RED].first, sampleColors[SampleColor::RED].second, aspect, coefMatrix, HDR_GAMMA(gamma), gammaHLG, NITS, altConvert, bt2020_to_sRgb, tryBt2020Range, bestResult.signal, 0, colorAspect); + auto green = hdr_to_srgb(yuvConverter, sampleColors[SampleColor::GREEN].first, sampleColors[SampleColor::GREEN].second, aspect, coefMatrix, HDR_GAMMA(gamma), gammaHLG, NITS, altConvert, bt2020_to_sRgb, tryBt2020Range, bestResult.signal, 0, colorAspect); + auto blue = hdr_to_srgb(yuvConverter, sampleColors[SampleColor::BLUE].first, sampleColors[SampleColor::BLUE].second, aspect, coefMatrix, HDR_GAMMA(gamma), gammaHLG, NITS, altConvert, bt2020_to_sRgb, tryBt2020Range, bestResult.signal, 0, colorAspect); + colorAspect.first = double3{ 1.0 / red.x, 1.0 / green.y, 1.0 / blue.z }; + + red = hdr_to_srgb(yuvConverter, sampleColors[SampleColor::LOW_RED].first, sampleColors[SampleColor::LOW_RED].second, aspect, coefMatrix, HDR_GAMMA(gamma), gammaHLG, NITS, altConvert, bt2020_to_sRgb, tryBt2020Range, bestResult.signal, 0, colorAspect); + green = hdr_to_srgb(yuvConverter, sampleColors[SampleColor::LOW_GREEN].first, sampleColors[SampleColor::LOW_GREEN].second, aspect, coefMatrix, HDR_GAMMA(gamma), gammaHLG, NITS, altConvert, bt2020_to_sRgb, tryBt2020Range, bestResult.signal, 0, colorAspect); + blue = hdr_to_srgb(yuvConverter, sampleColors[SampleColor::LOW_BLUE].first, sampleColors[SampleColor::LOW_BLUE].second, aspect, coefMatrix, HDR_GAMMA(gamma), gammaHLG, NITS, altConvert, bt2020_to_sRgb, tryBt2020Range, bestResult.signal, 0, colorAspect); + colorAspect.second = double3{ 0.5 / red.x, 0.5 / green.y, 0.5 / blue.z }; + } + + long long int currentError = 0; + + for (auto v = vertex.begin(); v != vertex.end(); ++v) + { + auto& sample = *(*v).first; + + auto minError = MAX_CALIBRATION_ERROR; + + if (sample.U() == 128 && sample.V() == 128) + { + (*v).second = hdr_to_srgb(yuvConverter, sample.yuv(), byte2{ sample.U(), sample.V() }, aspect, coefMatrix, HDR_GAMMA(gamma), gammaHLG, NITS, altConvert, bt2020_to_sRgb, tryBt2020Range, bestResult.signal, coloredAspectMode, colorAspect); + auto SRGB = to_int3((*v).second * 255.0); + minError = sample.getSourceError(SRGB); + } + else + { + auto sampleList = sample.getInputYuvColors(); + for (auto iter = sampleList.cbegin(); iter != sampleList.cend(); ++iter) + { + auto srgb = hdr_to_srgb(yuvConverter, (*iter).first, byte2{ sample.U(), sample.V() }, aspect, coefMatrix, HDR_GAMMA(gamma), gammaHLG, NITS, altConvert, bt2020_to_sRgb, tryBt2020Range, bestResult.signal, coloredAspectMode, colorAspect); + + auto SRGB = to_int3(srgb * 255.0); + + auto sampleError = sample.getSourceError(SRGB); + + if (sampleError < minError) + { + (*v).second = srgb; + minError = sampleError; + } + } + } + + currentError += minError; + + if (((!precise || !lchCorrection) && (currentError >= bestResult.minError || currentError > weakBestScore)) + || currentError >= MAX_CALIBRATION_ERROR) + { + currentError = MAX_CALIBRATION_ERROR; + break; + } + + if (precise) + { + double3 lchPrimaries; + auto res = (*v).first->isLchPrimary(&lchPrimaries); + if (res == CapturedColor::LchPrimaries::HIGH) + { + selectedLchHighPrimaries.push_back(std::pair(lchPrimaries, (*v).second)); + } + else if (res == CapturedColor::LchPrimaries::MID) + { + selectedLchMidPrimaries.push_back(std::pair(lchPrimaries, (*v).second)); + } + else if (res == CapturedColor::LchPrimaries::LOW) + { + selectedLchLowPrimaries.push_back(std::pair(lchPrimaries, (*v).second)); + } + } + + } + + bool lchFavour = false; + long long int lcHError = MAX_CALIBRATION_ERROR; + + LchLists selectedLchPrimaries; + if (precise && lchCorrection && currentError < MAX_CALIBRATION_ERROR) + { + lcHError = 0; + lchFavour = true; + + selectedLchPrimaries = prepareLCH({ selectedLchLowPrimaries, selectedLchMidPrimaries, selectedLchHighPrimaries }); + + for (auto sample = vertex.begin(); sample != vertex.end(); ++sample) + { + auto correctedRGB = (*sample).second; + + doToneMapping(selectedLchPrimaries, correctedRGB); + lcHError += (*sample).first->getSourceError((int3)(to_byte3(correctedRGB * 255.0))); + if (lcHError >= currentError || lcHError > weakBestScore) + { + lchFavour = false; + lcHError = MAX_CALIBRATION_ERROR; + break; + } + } + } + else + lcHError = MAX_CALIBRATION_ERROR; + + + if (currentError < bestResult.minError || lcHError < bestResult.minError) + { + bestResult.minError = (lchFavour) ? lcHError : currentError; + + if (weakBestScore > bestResult.minError) + weakBestScore = bestResult.minError; + + bestResult.coef = YuvConverter::YUV_COEFS(coef); + bestResult.coefDelta = kDelta; + bestResult.bt2020Range = tryBt2020Range; + bestResult.altConvert = altConvert; + bestResult.altPrimariesToSrgb = bt2020_to_sRgb; + bestResult.coloredAspectMode = coloredAspectMode; + bestResult.colorAspect = colorAspect; + bestResult.aspect = aspect; + bestResult.nits = NITS; + bestResult.gamma = HDR_GAMMA(gamma); + bestResult.gammaHLG = gammaHLG; + bestResult.coefMatrix = coefMatrix; + bestResult.lchEnabled = (lchFavour); + bestResult.lchPrimaries = selectedLchPrimaries; + printf("New local best score: %.3f (classic: %.3f, LCH: %.3f %s) for thread: %i. Gamma: %s, coef: %s, kr/kb: %s, yuvCorrection: %s\n", + bestResult.minError / 300.0, + currentError / 300.0, + lcHError / 300.0, + (bestResult.lchEnabled) ? "ON" : "OFF", + id, + QSTRING_CSTR(gammaToString(HDR_GAMMA(gamma))), + QSTRING_CSTR(yuvConverter->coefToString(YuvConverter::YUV_COEFS(coef))), + QSTRING_CSTR(vecToString(kDelta)), + QSTRING_CSTR(vecToString(aspect))); + } + } + if (forcedExit) + { + printf("User terminated thread: %i\n", id); + return; + } + } + } + + if (bestResult.minError < MAX_CALIBRATION_ERROR) + printf("Finished thread: %i. Score: %.3f\n", id, bestResult.minError / 300.0); + else + printf("Finished thread: %i. Could not find anything\n", id); + } -void LutCalibrator::colorCorrection(double& r, double& g, double& b) +void LutCalibrator::fineTune(bool precise) { - double hue, saturation, luminance; - - ColorSpaceCalibration::rgb2hsl_d(r, g, b, hue, saturation, luminance); - - double s = saturation * _saturation; - saturation = s; + constexpr auto MAX_IND = SCREEN_COLOR_DIMENSION - 1; + const auto white = _capturedColors->all[MAX_IND][MAX_IND][MAX_IND].Y(); + double NITS = 0.0; + double maxLevel = 0.0; + std::atomic weakBestScore = MAX_CALIBRATION_ERROR; + + // prepare vertexes + std::list vertex; + + for (int r = MAX_IND; r >= 0; r--) + for (int g = MAX_IND; g >= 0; g--) + for (int b = MAX_IND; b >= 0; b--) + { + + if ((r % 4 == 0 && g % 4 == 0 && b % 2 == 0) || (r == g * 2 && g > b) || (r <= 6 && g <= 6 && b <= 6) || (r == b && b == g) || (r == g && r > 0) || (r == b && r > 0) + || _capturedColors->all[r][g][b].isLchPrimary(nullptr) != CapturedColor::LchPrimaries::NONE + || (bestResult->signal.isSourceP010 && ((r - g > 0 && r - g <= 3 && b == 0) || (r > 0 && g == 0 && b == 0) || (r == 0 && g > 0 && b == 0) || (r == 0 && g == 0 && b > 0)))) + { - double l = luminance * _luminance; - luminance = l; + vertex.push_back(&_capturedColors->all[r][g][b]); + } + } - ColorSpaceCalibration::hsl2rgb_d(hue, saturation, luminance, r, g, b); -} + vertex.sort([](CapturedColor*& a, CapturedColor*& b) { return ((int)a->coords().x + a->coords().y + a->coords().z) > + ((int)b->coords().x + b->coords().y + b->coords().z); }); -void LutCalibrator::balanceGray(int r, int g, int b, double& _r, double& _g, double& _b) -{ - if ((_r == 0 && _g == 0 && _b == 0) || - (_r == 1 && _g == 1 && _b == 1)) - return; + if (!precise) + { + Info(_log, "The first phase starts"); + Info(_log, "Optimal thread count: %i", QThreadPool::globalInstance()->maxThreadCount()); + Info(_log, "Number of test vertexes: %i", vertex.size()); + } + else + { + Info(_log, "The second phase starts"); + } - int _R = qRound(_r * 255); - int _G = qRound(_g * 255); - int _B = qRound(_b * 255); + // set startup parameters (signal) + bestResult->signal.range = _capturedColors->getRange(); + _capturedColors->getSignalParams(bestResult->signal.yRange, bestResult->signal.upYLimit, bestResult->signal.downYLimit, bestResult->signal.yShift); - int max = qMax(_R, qMax(_G, _B)); - int min = qMin(_R, qMin(_G, _B)); + if (bestResult->signal.isSourceP010) + { + double up = bestResult->signal.upYLimit; + unpackP010(&up, nullptr, nullptr); + bestResult->signal.upYLimit = up; + double down = bestResult->signal.downYLimit; + unpackP010(&down, nullptr, nullptr); + bestResult->signal.downYLimit = down; + } - if (max - min < 30) + if (bestResult->signal.range == YuvConverter::COLOR_RANGE::LIMITED) { - for (capColors selector = capColors::LowestGray; selector != capColors::None; selector = capColors(((int)selector) + 1)) - { - double whiteR = r * _colorBalance[selector].scaledRed; - double whiteG = g * _colorBalance[selector].scaledGreen; - double whiteB = b * _colorBalance[selector].scaledBlue; - double error = 1; + maxLevel = (white - 16.0) / (235.0 - 16.0); + } + else + { + maxLevel = white / 255.0; + } + + std::vector> sampleColors(6); + const auto& sampleRed = _capturedColors->all[MAX_IND][0][0]; + const auto& sampleGreen = _capturedColors->all[0][MAX_IND][0]; + const auto& sampleBlue = _capturedColors->all[0][0][MAX_IND]; + const auto& sampleRedLow = _capturedColors->all[MAX_IND / 2][0][0]; + const auto& sampleGreenLow = _capturedColors->all[0][MAX_IND / 2][0]; + const auto& sampleBlueLow = _capturedColors->all[0][0][MAX_IND / 2]; + + sampleColors[SampleColor::RED] = (std::pair(sampleRed.getInputYuvColors().front().first, byte2{ sampleRed.U(), sampleRed.V() })); + sampleColors[SampleColor::GREEN] = (std::pair(sampleGreen.getInputYuvColors().front().first, byte2{ sampleGreen.U(), sampleGreen.V() })); + sampleColors[SampleColor::BLUE] = (std::pair(sampleBlue.getInputYuvColors().front().first, byte2{ sampleBlue.U(), sampleBlue.V() })); + + sampleColors[SampleColor::LOW_RED] = (std::pair(sampleRedLow.getInputYuvColors().front().first, byte2{ sampleRedLow.U(), sampleRedLow.V() })); + sampleColors[SampleColor::LOW_GREEN] = (std::pair(sampleGreenLow.getInputYuvColors().front().first, byte2{ sampleGreenLow.U(), sampleGreenLow.V() })); + sampleColors[SampleColor::LOW_BLUE] = (std::pair(sampleBlueLow.getInputYuvColors().front().first, byte2{ sampleBlueLow.U(), sampleBlueLow.V() })); + + for (int gamma = (precise) ? (bestResult->gamma) : ((bestResult->signal.isSourceP010) ? HDR_GAMMA::P010 : HDR_GAMMA::PQ); + gamma <= HDR_GAMMA::P010; gamma++) + { + std::vector gammasHLG; - if (qAbs(whiteR - whiteG) <= error && qAbs(whiteG - whiteB) <= error && qAbs(whiteB - whiteR) <= error) + if (gamma == HDR_GAMMA::P010 && !bestResult->signal.isSourceP010) + continue; + + if (gamma == HDR_GAMMA::HLG) + { + if (precise) + { + gammasHLG = { bestResult->gammaHLG }; + } + else { - _r = _g = _b = (_r + _g + _b) / 3.0; - return; + gammasHLG = { 0 , 1.2, 1.1 }; } } + else + gammasHLG = { 0 }; + + if (gamma == HDR_GAMMA::PQ) + { + NITS = 10000.0 * PQ_ST2084(1.0, maxLevel); + } + else if (gamma == HDR_GAMMA::P010) + { + double unpackWhite = white / 255.0; + unpackP010(&unpackWhite, nullptr, nullptr); + unpackWhite *= 255.0; + maxLevel = (unpackWhite - 16.0) / (235.0 - 16.0); + NITS = 10000.0 * PQ_ST2084(1.0, maxLevel); + } + else if (gamma == HDR_GAMMA::PQinSRGB) + { + NITS = 10000.0 * PQ_ST2084(1.0, srgb_linear_to_nonlinear(maxLevel)); + } - for (capColors selector = capColors::LowestGray; selector != capColors::None; selector = capColors(((int)selector) + 1)) + for (double gammaHLG : gammasHLG) { - double whiteR = r * _colorBalance[selector].scaledRed; - double whiteG = g * _colorBalance[selector].scaledGreen; - double whiteB = b * _colorBalance[selector].scaledBlue; - double error = 2; + if (gammaHLG == 1.1 && bestResult->gamma != HDR_GAMMA::HLG) + break; - if (qAbs(whiteR - whiteG) <= error && qAbs(whiteG - whiteB) <= error && qAbs(whiteB - whiteR) <= error) + if (gamma == HDR_GAMMA::HLG) { - double average = (_r + _g + _b) / 3.0; - _r = (_r + average) / 2; - _g = (_g + average) / 2; - _b = (_b + average) / 2; - return; + NITS = 1.0 / OOTF_HLG(inverse_OETF_HLG(maxLevel), gammaHLG).x; } + for (int coef = (precise) ? bestResult->coef : YuvConverter::YUV_COEFS::BT601; coef <= YuvConverter::YUV_COEFS::BT2020; coef++) + { + double3x3 convert_bt2020_to_XYZ; + double3x3 convert_XYZ_to_sRgb; + + capturedPrimariesCorrection(HDR_GAMMA(gamma), gammaHLG, NITS, coef, convert_bt2020_to_XYZ, convert_XYZ_to_sRgb); + auto bt2020_to_sRgb = mul(convert_XYZ_to_sRgb, convert_bt2020_to_XYZ); + + printf("Processing gamma: %s, gammaHLG: %f, coef: %s. Current best gamma: %s, gammaHLG: %f, coef: %s (d:%s). Score: %.3f\n", + QSTRING_CSTR(gammaToString(HDR_GAMMA(gamma))), gammaHLG, QSTRING_CSTR(_yuvConverter->coefToString(YuvConverter::YUV_COEFS(coef))), + QSTRING_CSTR(gammaToString(HDR_GAMMA(bestResult->gamma))), bestResult->gammaHLG, QSTRING_CSTR(_yuvConverter->coefToString(YuvConverter::YUV_COEFS(bestResult->coef))), + QSTRING_CSTR(vecToString(bestResult->coefDelta)),bestResult->minError / 300.0); + + const int halfKDelta = (precise) ? 16 : 8; + const int krDelta = std::ceil((halfKDelta * 2.0) / QThreadPool::globalInstance()->maxThreadCount()); + + QList workers; + int index = 0; + std::atomic progress = 0; + + for (int krIndexStart = 0; krIndexStart <= halfKDelta * 2; krIndexStart += krDelta) + { + auto worker = new CalibrationWorker(bestResult.get(), weakBestScore, _yuvConverter.get(), index++, krIndexStart, krIndexStart + krDelta, halfKDelta, precise, coef, sampleColors, gamma, gammaHLG, NITS, bt2020_to_sRgb, vertex, _lchCorrection, _forcedExit, progress); + workers.push_back(worker); + QThreadPool::globalInstance()->start(worker); + connect(worker, &CalibrationWorker::notifyCalibrationMessage, this, &LutCalibrator::notifyCalibrationMessage, Qt::DirectConnection); + } + QThreadPool::globalInstance()->waitForDone(); + + for (const auto& worker : workers) + { + worker->getBestResult(bestResult.get()); + } + + qDeleteAll(workers); + workers.clear(); + + if (precise || _forcedExit) + break; + } } - } - if (max <= 4 && max >= 1) - { - _r = qMax(_r, 1.0 / 255.0); - _g = qMax(_g, 1.0 / 255.0); - _b = qMax(_b, 1.0 / 255.0); + if (precise || _forcedExit) + break; } } +static void reportLCH(Logger* _log, std::vector>>* all); -QString LutCalibrator::colorToQStr(capColors index) -{ - int ind = (int)index; - - double floor = qMax(_minColor.red, qMax(_minColor.green, _minColor.blue)); - double ceiling = qMax(_maxColor.red, qMax(_maxColor.green, _maxColor.blue)) * 1.05; - double scale = (ceiling - floor); - ColorStat normalized = _colorBalance[ind], fNormalized = _colorBalance[ind], color = _colorBalance[ind]; - normalized /= scale; - fNormalized /= 255.0; - color.red = qRound(color.red); - color.green = qRound(color.green); - color.blue = qRound(color.blue); - QString retVal = QString("%1 => %2").arg(QString::fromStdString(primeColors[ind])).arg(color.toQString()); - - return retVal; -} -double LutCalibrator::getError(ColorRgb first, ColorStat second) +void LutCalibrator::calibration() { - double errorR = 0, errorG = 0, errorB = 0; - - if ((first.red == 255 || first.red == 128) && first.green == 0 && first.blue == 0) - { - if (second.red <= 1) - return std::pow(255, 2) * 3; + // calibration + auto totalTime = InternalClock::now(); - errorR = 0; - errorG = 100.0 * second.green / second.red; - errorB = 100.0 * second.blue / second.red; + fineTune(false); - if (first.red == 255) - errorR += (255 - second.red); - } - else if (first.red == 0 && (first.green == 255 || first.green == 128) && first.blue == 0) + if (_forcedExit) { - if (second.green <= 1) - return std::pow(255, 2) * 3; + error("User terminated calibration"); + return; + } - errorR = 100.0 * second.red / second.green; - errorG = 0; - errorB = 100.0 * second.blue / second.green; + totalTime = InternalClock::now() - totalTime; - if (first.green == 255) - errorG += (255 - second.green); - } - else if (first.red == 0 && first.green == 0 && (first.blue == 255 || first.blue == 128)) + if (bestResult->minError >= MAX_CALIBRATION_ERROR) { - if (second.blue <= 1) - return std::pow(255, 2) * 3; + error("The calibration failed. The error is too high."); + return; + } - errorR = 100.0 * second.red / second.blue; - errorG = 100.0 * second.green / second.blue; - errorB = 0; + // fine tuning + auto totalTime2 = InternalClock::now(); - if (first.blue == 255) - errorB += (255 - second.blue); - } - else if (first.red == 255 && first.green == 255 && first.blue == 0) - { - if (second.red <= 1) - return std::pow(255, 2) * 3; + fineTune(true); - errorR = 0; - errorG = 0; - errorB = 2 * 100.0 * second.blue / second.red; - } - else if (first.red == 255 && first.green == 0 && first.blue == 255) + if (_forcedExit) { - if (second.red <= 1) - return std::pow(255, 2) * 3; - - errorR = 0; - errorG = 2 * 100.0 * second.green / second.red; - errorB = 0; + error("User terminated calibration"); + return; } - else if (first.red == 0 && first.green == 255 && first.blue == 255) - { - if (second.blue <= 1) - return std::pow(255, 2) * 3; - errorR = 2 * 100.0 * second.red / second.blue; - errorG = 0; - errorB = 0; - } - else if (first.red == 255 && first.green == 0 && first.blue == 128) + totalTime2 = InternalClock::now() - totalTime2; + + // write result + Debug(_log, "Score: %.3f", bestResult->minError / 300.0); + Debug(_log, "LCH: %s", (bestResult->lchEnabled) ? "Enabled" : "Disabled"); + Debug(_log, "The first phase time: %.3fs", totalTime / 1000.0); + Debug(_log, "The second phase time: %.3fs", totalTime2 / 1000.0); + Debug(_log, "Selected coef: %s", QSTRING_CSTR( _yuvConverter->coefToString(bestResult->coef))); + Debug(_log, "Selected coef delta: %f %f", bestResult->coefDelta.x, bestResult->coefDelta.y); + Debug(_log, "Selected EOTF: %s", QSTRING_CSTR(ColorSpaceMath::gammaToString(bestResult->gamma))); + if (bestResult->gamma == HDR_GAMMA::HLG) { - if (second.red <= 1) - return std::pow(255, 2) * 3; - - errorR = 2 * 100.0 * qAbs(1 - 2 * second.blue / second.red); - errorG = 0; - errorB = 0; + Debug(_log, "Selected HLG gamma: %f", bestResult->gammaHLG); } - else if (first.red == 255 && first.green == 128 && first.blue == 0) + if (bestResult->gamma != HDR_GAMMA::sRGB && bestResult->gamma != HDR_GAMMA::BT2020inSRGB) { - if (second.red <= 1) - return std::pow(255, 2) * 3; - - errorR = 2 * 100.0 * qAbs(1 - 2 * second.green / second.red); - errorG = 0; - errorB = 0; + Debug(_log, "Selected nits: %f", (bestResult->gamma == HDR_GAMMA::HLG) ? 1000.0 * (1 / bestResult->nits) : bestResult->nits); } - else if (first.red == 0 && first.green == 128 && first.blue == 255) + Debug(_log, "Selected bt2020 gamma range: %s", (bestResult->bt2020Range) ? "yes" : "no"); + Debug(_log, "Selected alternative conversion of primaries: %s", (bestResult->altConvert) ? "yes" : "no"); + Debug(_log, "Selected aspect: %f %f %f", bestResult->aspect.x, bestResult->aspect.y, bestResult->aspect.z); + Debug(_log, "Selected color aspect mode: %i", bestResult->coloredAspectMode); + Debug(_log, "Selected color aspect: %s %s", QSTRING_CSTR(vecToString(bestResult->colorAspect.first)), QSTRING_CSTR(vecToString(bestResult->colorAspect.second))); + Debug(_log, "Selected source is P010: %s", (bestResult->signal.isSourceP010) ? "yes" : "no"); + + if (_debug) { - if (second.blue <= 1) - return std::pow(255, 2) * 3; + double3x3 convert_bt2020_to_XYZ; + double3x3 convert_XYZ_to_sRgb; - errorR = 2 * 100.0 * qAbs(1 - 2 * second.green / second.blue); - errorG = 0; - errorB = 0; + capturedPrimariesCorrection(HDR_GAMMA(bestResult->gamma), bestResult->gammaHLG, bestResult->nits, bestResult->coef, convert_bt2020_to_XYZ, convert_XYZ_to_sRgb, true); } - else if (first.red == 128 && first.green == 64 && first.blue == 0) - { - if (second.red <= 1) - return std::pow(255, 2) * 3; - errorR = 0; - errorG = 2 * 100.0 * qAbs(1 - 2 * second.green / second.red); - errorB = 0; - } - else if (first.red == 128 && first.green == 0 && first.blue == 64) + // write report (captured raw colors) + std::stringstream results; + bestResult->serialize(results); + QString fileLogName = QString("%1%2").arg(_rootPath).arg("/calibration_captured_yuv.txt"); + if (_capturedColors->saveResult(QSTRING_CSTR(fileLogName), results.str())) { - if (second.red <= 1) - return std::pow(255, 2) * 3; - - errorR = 0; - errorG = 0; - errorB = 2 * 100.0 * qAbs(1 - 2 * second.blue / second.red); + Info(_log, "Write captured colors to: %s", QSTRING_CSTR(fileLogName)); } - else if (first.red == first.green && first.green == first.blue) + else { - double max = qMax(second.red, qMax(second.green, second.blue)) - qMin(second.red, qMin(second.green, second.blue)); - - errorR = max; - errorG = max; - errorB = max; - - if (first.red == primeColors[capColors::White].red) - { - errorR += qMin(qMax(255 - second.red, 0.0), 10.0); - errorG += qMin(qMax(255 - second.green, 0.0), 10.0); - errorB += qMin(qMax(255 - second.blue, 0.0), 10.0); - } + Error(_log, "Could not save captured colors to: %s", QSTRING_CSTR(fileLogName)); } - return std::pow(errorR, 2) + std::pow(errorG, 2) + std::pow(errorB, 2); -} - + // create LUT + notifyCalibrationMessage("Writing final LUT..."); + _lut.releaseMemory(); -QString LutCalibrator::colorToQStr(ColorRgb color) -{ - double mian = qMax(color.red, qMax(color.green, color.blue)); - - return QString("(%1, %2, %3), Proportion: (%4, %5, %6)") - .arg(color.red).arg(color.green).arg(color.blue) - .arg(color.red / mian, 0, 'f', 3).arg(color.green / mian, 0, 'f', 3).arg(color.blue / mian, 0, 'f', 3); -} - -QString LutCalibrator::calColorToQStr(capColors index) -{ - ColorRgb color = primeColors[(int)index], finalColor; - ColorStat real = _colorBalance[(int)index]; - - uint32_t indexRgb = LUT_INDEX(qRound(real.red), qRound(real.green), qRound(real.blue)); - - finalColor.red = _lut.data()[indexRgb]; - finalColor.green = _lut.data()[indexRgb + 1]; - finalColor.blue = _lut.data()[indexRgb + 2]; - - return QString("%1 => %2").arg(QString::fromStdString(color)).arg(QString::fromStdString(finalColor)); -} - -void LutCalibrator::displayPreCalibrationInfo() -{ - Debug(_log, ""); - Debug(_log, "--------------------- Captured colors starts ---------------------"); - Debug(_log, "Red: %s", QSTRING_CSTR(colorToQStr(capColors::Red))); - Debug(_log, "Green: %s", QSTRING_CSTR(colorToQStr(capColors::Green))); - Debug(_log, "Blue: %s", QSTRING_CSTR(colorToQStr(capColors::Blue))); - Debug(_log, "Yellow: %s", QSTRING_CSTR(colorToQStr(capColors::Yellow))); - Debug(_log, "Magenta: %s", QSTRING_CSTR(colorToQStr(capColors::Magenta))); - Debug(_log, "Cyan: %s", QSTRING_CSTR(colorToQStr(capColors::Cyan))); - Debug(_log, "Orange: %s", QSTRING_CSTR(colorToQStr(capColors::Orange))); - Debug(_log, "Pink: %s", QSTRING_CSTR(colorToQStr(capColors::Pink))); - Debug(_log, "Azure: %s", QSTRING_CSTR(colorToQStr(capColors::Azure))); - Debug(_log, "Brown: %s", QSTRING_CSTR(colorToQStr(capColors::Brown))); - Debug(_log, "Purple: %s", QSTRING_CSTR(colorToQStr(capColors::Purple))); - Debug(_log, "Low red: %s", QSTRING_CSTR(colorToQStr(capColors::LowRed))); - Debug(_log, "Low green: %s", QSTRING_CSTR(colorToQStr(capColors::LowGreen))); - Debug(_log, "Low blue: %s", QSTRING_CSTR(colorToQStr(capColors::LowBlue))); - Debug(_log, "LowestGray: %s", QSTRING_CSTR(colorToQStr(capColors::LowestGray))); - Debug(_log, "Gray1: %s", QSTRING_CSTR(colorToQStr(capColors::Gray1))); - Debug(_log, "Gray2: %s", QSTRING_CSTR(colorToQStr(capColors::Gray2))); - Debug(_log, "Gray3: %s", QSTRING_CSTR(colorToQStr(capColors::Gray3))); - Debug(_log, "Gray4: %s", QSTRING_CSTR(colorToQStr(capColors::Gray4))); - Debug(_log, "Gray5: %s", QSTRING_CSTR(colorToQStr(capColors::Gray5))); - Debug(_log, "Gray6: %s", QSTRING_CSTR(colorToQStr(capColors::Gray6))); - Debug(_log, "Gray7: %s", QSTRING_CSTR(colorToQStr(capColors::Gray7))); - Debug(_log, "Gray8: %s", QSTRING_CSTR(colorToQStr(capColors::Gray8))); - Debug(_log, "HighestGray: %s", QSTRING_CSTR(colorToQStr(capColors::HighestGray))); - Debug(_log, "White: %s", QSTRING_CSTR(colorToQStr(capColors::White))); - Debug(_log, "------------------------------------------------------------------"); - Debug(_log, ""); -} - -void LutCalibrator::displayPostCalibrationInfo() -{ - Debug(_log, ""); - Debug(_log, "-------------------- Calibrated colors starts --------------------"); - Debug(_log, "Red: %s", QSTRING_CSTR(calColorToQStr(capColors::Red))); - Debug(_log, "Green: %s", QSTRING_CSTR(calColorToQStr(capColors::Green))); - Debug(_log, "Blue: %s", QSTRING_CSTR(calColorToQStr(capColors::Blue))); - Debug(_log, "Yellow: %s", QSTRING_CSTR(calColorToQStr(capColors::Yellow))); - Debug(_log, "Magenta: %s", QSTRING_CSTR(calColorToQStr(capColors::Magenta))); - Debug(_log, "Cyan: %s", QSTRING_CSTR(calColorToQStr(capColors::Cyan))); - Debug(_log, "Orange: %s", QSTRING_CSTR(calColorToQStr(capColors::Orange))); - Debug(_log, "Pink: %s", QSTRING_CSTR(calColorToQStr(capColors::Pink))); - Debug(_log, "Azure: %s", QSTRING_CSTR(calColorToQStr(capColors::Azure))); - Debug(_log, "Brown: %s", QSTRING_CSTR(calColorToQStr(capColors::Brown))); - Debug(_log, "Purple: %s", QSTRING_CSTR(calColorToQStr(capColors::Purple))); - Debug(_log, "Low red: %s", QSTRING_CSTR(calColorToQStr(capColors::LowRed))); - Debug(_log, "Low green: %s", QSTRING_CSTR(calColorToQStr(capColors::LowGreen))); - Debug(_log, "Low blue: %s", QSTRING_CSTR(calColorToQStr(capColors::LowBlue))); - Debug(_log, "LowestGray: %s", QSTRING_CSTR(calColorToQStr(capColors::LowestGray))); - Debug(_log, "Gray1: %s", QSTRING_CSTR(calColorToQStr(capColors::Gray1))); - Debug(_log, "Gray2: %s", QSTRING_CSTR(calColorToQStr(capColors::Gray2))); - Debug(_log, "Gray3: %s", QSTRING_CSTR(calColorToQStr(capColors::Gray3))); - Debug(_log, "Gray4: %s", QSTRING_CSTR(calColorToQStr(capColors::Gray4))); - Debug(_log, "Gray5: %s", QSTRING_CSTR(calColorToQStr(capColors::Gray5))); - Debug(_log, "Gray6: %s", QSTRING_CSTR(calColorToQStr(capColors::Gray6))); - Debug(_log, "Gray7: %s", QSTRING_CSTR(calColorToQStr(capColors::Gray7))); - Debug(_log, "Gray8: %s", QSTRING_CSTR(calColorToQStr(capColors::Gray8))); - Debug(_log, "HighestGray: %s", QSTRING_CSTR(calColorToQStr(capColors::HighestGray))); - Debug(_log, "White: %s", QSTRING_CSTR(calColorToQStr(capColors::White))); - Debug(_log, "------------------------------------------------------------------"); - Debug(_log, ""); -} - -bool LutCalibrator::correctionEnd() -{ + auto totalTime3 = InternalClock::now(); + QString errorMessage = CreateLutFile(_log, _rootPath, bestResult.get(), &(_capturedColors->all)); + totalTime3 = InternalClock::now() - totalTime3; - disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewVideoImage, this, &LutCalibrator::setVideoImage); - disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewSystemImage, this, &LutCalibrator::setSystemImage); - disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetGlobalImage, this, &LutCalibrator::signalSetGlobalImageHandler); + if (!errorMessage.isEmpty()) + { + error(errorMessage); + } + else + { + Debug(_log, "The LUT creation time: %.3fs", totalTime3 / 1000.0); + } - double floor = qMax(_minColor.red, qMax(_minColor.green, _minColor.blue)); - double ceiling = qMin(_maxColor.red, qMin(_maxColor.green, _maxColor.blue)); - double range = 0; - double scale = (ceiling - floor) / (161.0 / 255.0); - int strategy = 0; - int whiteIndex = capColors::White; + // LCH + reportLCH(_log, &(_capturedColors->all)); + // control score + long long int currentError = 0; + constexpr auto MAX_IND = SCREEN_COLOR_DIMENSION - 1; + for (int r = MAX_IND; r >= 0; r--) + for (int g = MAX_IND; g >= 0; g--) + for (int b = MAX_IND; b >= 0; b--) + { + if ((r % 4 == 0 && g % 4 == 0 && b % 2 == 0) || (r == g * 2 && g > b) || (r <= 6 && g <= 6 && b <= 6) || (r == b && b == g) || (r == g && r > 0) || (r == b && r > 0) + || _capturedColors->all[r][g][b].isLchPrimary(nullptr) != CapturedColor::LchPrimaries::NONE + || (bestResult->signal.isSourceP010 && ((r - g > 0 && r - g <= 3 && b == 0) || (r > 0 && g == 0 && b == 0) || (r == 0 && g > 0 && b == 0) || (r == 0 && g == 0 && b > 0)))) + { + auto sample = _capturedColors->all[r][g][b]; + auto sampleList = sample.getFinalRGB(); + long long int microError = MAX_CALIBRATION_ERROR; + for (auto iter = sampleList.cbegin(); iter != sampleList.cend(); ++iter) + { + auto c = sample.getSourceError((int3)*iter); + if (c < microError) + microError = c; + } + currentError += microError; + } + } + Debug(_log, "The control score: %.3f", currentError / 300.0); - for (int j = 0; j < (int)(sizeof(_colorBalance) / sizeof(ColorStat)); j++) - _colorBalance[j].calculateFinalColor();; + // reload LUT + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_HDR, -1, false); + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_HDR, -1, true); - // YUV check - if (floor >= 2 && !_limitedRange) + if (_defaultComp == hyperhdr::COMP_VIDEOGRABBER) { - Warning(_log, "YUV limited range detected (black level: %f, ceiling: %f). Restarting the calibration using limited range YUV.", floor, ceiling); - - QJsonObject report; - report["limited"] = 1; - SignalLutCalibrationUpdated(report); - - return false; + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_VIDEOGRABBER, -1, true); } - - // coef autodetection - int lastCoef = (sizeof(_coefsResult) / sizeof(double)) - 1; - bool finished = (_coefsResult[lastCoef] != 0); - - if (!finished) + if (_defaultComp == hyperhdr::COMP_FLATBUFSERVER) { - int nextIndex = _currentCoef; + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_FLATBUFSERVER, -1, true); + } +} - _coefsResult[_currentCoef] = fineTune(range, scale, whiteIndex, strategy); - // choose best - if (_currentCoef == lastCoef) - { - for (int i = 0, best = INT_MAX; i < (int)(sizeof(_coefsResult) / sizeof(double)); i++) +static void reportLCH(Logger* _log, std::vector>>* all) +{ + QStringList info, intro; + std::list mHigh; + std::list mMid; + std::list mLow; + + + constexpr auto MAX_IND = SCREEN_COLOR_DIMENSION - 1; + for (int r = MAX_IND; r >= 0; r--) + for (int g = MAX_IND; g >= 0; g--) + for (int b = MAX_IND; b >= 0; b--) { - Debug(_log, "Mean error for %s is: %f", REC(i), _coefsResult[i]); - if (_coefsResult[i] < best) + double3 org; + auto ret = (*all)[r][g][b].isLchPrimary(&org); + if (ret == CapturedColor::LchPrimaries::HIGH || ret == CapturedColor::LchPrimaries::MID || ret == CapturedColor::LchPrimaries::LOW) { - best = _coefsResult[i]; - nextIndex = i; + MappingPrime m; + m.org = org; + m.prime = byte3(r, g, b); + if (ret == CapturedColor::LchPrimaries::HIGH) + mHigh.push_back(m); + else if (ret == CapturedColor::LchPrimaries::MID) + mMid.push_back(m); + else + mLow.push_back(m); } } - Warning(_log, "Best coef is: %s", REC(nextIndex)); - } - else - nextIndex = _currentCoef + 1; - // request next if needed - if (nextIndex != _currentCoef) + + for (std::list*& m : std::list*>{ &mHigh, &mMid, &mLow }) + { + for (MappingPrime& c : *m) { - Warning(_log, "Requesting next coef for switching: %s", REC(nextIndex)); - - QJsonObject report; - report["coef"] = nextIndex; - SignalLutCalibrationUpdated(report); - - return false; + auto& sample = (*all)[c.prime.x][c.prime.y][c.prime.z]; + auto b = static_cast(sample.getFinalRGB().front()) / 255.0; + c.real = xyz_to_lch(from_sRGB_to_XYZ(b) * 100.0); + c.delta = c.org - c.real; } - } - else - _coefsResult[_currentCoef] = fineTune(range, scale, whiteIndex, strategy); - + m->sort([](const MappingPrime& a, const MappingPrime& b) { return a.real.z > b.real.z; }); + MappingPrime loopEnd = m->front(); + MappingPrime loopFront = m->back(); + loopEnd.org.z -= 360; + loopEnd.real.z -= 360; + m->push_back(loopEnd); - // display precalibrated colors - displayPreCalibrationInfo(); + loopFront.org.z += 360; + loopFront.real.z += 360; + m->push_front(loopFront); + } + + info.append("Primaries in LCH colorspace"); + info.append("RGB | RGB primary in LCH | captured primary in LCH | average LCH delta | LCH to RGB way back "); + info.append("--------------------------------------------------------------------------------------------------------------------------------------------------------"); + for (std::list*& m : std::list*>{ &mHigh, &mMid, &mLow }) + { + for (MappingPrime& c : *m) + { + auto& sample = (*all)[c.prime.x][c.prime.y][c.prime.z]; + auto aa = from_XYZ_to_sRGB(lch_to_xyz(c.org) / 100.0) * 255; + auto bb = from_XYZ_to_sRGB(lch_to_xyz(c.real) / 100.0) * 255; + info.append(QString("%1 | %2 | %3 | %4 | %5 %6").arg(vecToString(sample.getSourceRGB()), 12). + arg(vecToString(c.org)). + arg(vecToString(c.real)). + arg(vecToString(c.delta)). + arg(vecToString(to_byte3(aa))). + arg(vecToString(to_byte3(bb)))); - // display stats - ColorStat whiteBalance = _colorBalance[whiteIndex]; + } + info.append("--------------------------------------------------------------------------------------------------------------------------------------------------------"); + } - Debug(_log, "Optimal PQ multi => %f, strategy => %i, white index => %i", range, strategy, whiteIndex); - Debug(_log, "White correction: (%f, %f, %f)", whiteBalance.scaledRed, whiteBalance.scaledGreen, whiteBalance.scaledBlue); - Debug(_log, "Min RGB floor: %f, max RGB ceiling: %f, scale: %f", floor, ceiling, scale); - Debug(_log, "Min RGB range => %s", STRING_CSTR(_minColor)); - Debug(_log, "Max RGB range => %s", STRING_CSTR(_maxColor)); - Debug(_log, "YUV range: %s", (floor >= 2 || _limitedRange) ? "LIMITED" : "FULL"); - Debug(_log, "YUV coefs: %s", REC(_currentCoef)); + LutCalibrator::sendReport(_log, info.join("\r\n")); +} - // build LUT table - for (int g = 0; g <= 255; g++) - for (int b = 0; b <= 255; b++) - for (int r = 0; r <= 255; r++) +void CreateLutWorker::run() +{ + printf("Starting LUT creation thread for phase %i. V range is [ %i, %i)\n", phase, startV, endV); + for (int v = startV; v < endV; v++) + for (int u = 0; u <= 255; u++) + for (int y = 0; y <= 255; y++) { - double Ri = clampDouble((r * whiteBalance.scaledRed - floor) / scale, 0, 1.0); - double Gi = clampDouble((g * whiteBalance.scaledGreen - floor) / scale, 0, 1.0); - double Bi = clampDouble((b * whiteBalance.scaledBlue - floor) /scale, 0, 1.0); + byte3 YUV(y, u, v); + double3 yuv = to_double3(YUV) / 255.0; - // ootf - if (strategy == 1) + if (phase == 0) { - Ri = ootf(Ri); - Gi = ootf(Gi); - Bi = ootf(Bi); + yuv = yuvConverter->toYuv(bestResult->signal.range, bestResult->coef, yuv); + YUV = to_byte3(yuv * 255); } - // eotf - if (strategy == 0 || strategy == 1) + if (phase == 0 || phase == 1) { - Ri = eotf(range, Ri); - Gi = eotf(range, Gi); - Bi = eotf(range, Bi); - } + //if (YUV.y >= 127 && YUV.y <= 129 && YUV.z >= 127 && YUV.z <= 129) { YUV.y = 128;yuv.y = 128.0 / 255.0; YUV.z = 128;yuv.z = 128.0 / 255.0; } + yuv = hdr_to_srgb(yuvConverter, yuv, byte2(YUV.y, YUV.z), bestResult->aspect, bestResult->coefMatrix, bestResult->gamma, bestResult->gammaHLG, bestResult->nits, bestResult->altConvert, bestResult->altPrimariesToSrgb, bestResult->bt2020Range, bestResult->signal, bestResult->coloredAspectMode, bestResult->colorAspect); - // bt2020 - if (strategy == 0 || strategy == 1) - { - fromBT2020toBT709(Ri, Gi, Bi, Ri, Gi, Bi); + if (bestResult->lchEnabled) + { + //yuv *= 255.0; + doToneMapping(bestResult->lchPrimaries, yuv); + //yuv /= 255.0; + } } - - // ootf - if (strategy == 0 || strategy == 1) + else { - Ri = ootf(Ri); - Gi = ootf(Gi); - Bi = ootf(Bi); + yuv = yuvConverter->toRgb(bestResult->signal.range, bestResult->coef, yuv); } - double finalR = clampDouble(Ri, 0, 1.0); - double finalG = clampDouble(Gi, 0, 1.0); - double finalB = clampDouble(Bi, 0, 1.0); + byte3 result = to_byte3(yuv * 255.0); + uint32_t ind_lutd = LUT_INDEX(y, u, v); + lut[ind_lutd] = result.x; + lut[ind_lutd + 1] = result.y; + lut[ind_lutd + 2] = result.z; + } +} - balanceGray(r, g, b, finalR, finalG, finalB); +QString LutCalibrator::CreateLutFile(Logger* _log, QString _rootPath, BestResult* bestResult, std::vector>>* all) +{ + // write LUT table + QString fileName = QString("%1%2").arg(_rootPath).arg("/lut_lin_tables.3d"); + std::fstream file; + file.open(fileName.toStdString(), std::ios::trunc | std::ios::out | std::ios::binary); - if (_saturation != 1.0 || _luminance != 1.0) - colorCorrection(finalR, finalG, finalB); + if (!file.is_open()) + { + return QString("Could not open LUT file for writing: %1").arg(fileName); + } + else + { + MemoryBuffer _lut; + YuvConverter yuvConverter; - if (_gammaR != 1.0) - finalR = std::pow(finalR, 1 / _gammaR); + Info(_log, "Writing LUT file to: %s", QSTRING_CSTR(fileName)); - if (_gammaG != 1.0) - finalG = std::pow(finalG, 1 / _gammaG); + _lut.resize(LUT_FILE_SIZE); - if (_gammaB != 1.0) - finalB = std::pow(finalB, 1 / _gammaB); + for (int phase = 0; phase < 3; phase++) + { + const int vDelta = std::ceil(256.0 / QThreadPool::globalInstance()->maxThreadCount()); - // save it - uint32_t ind_lutd = LUT_INDEX(r, g, b); - _lut.data()[ind_lutd ] = clampToInt(((finalR) * 255), 0, 255); - _lut.data()[ind_lutd + 1] = clampToInt(((finalG) * 255), 0, 255); - _lut.data()[ind_lutd + 2] = clampToInt(((finalB) * 255), 0, 255); + for (int v = 0; v <= 255; v += vDelta) + { + auto worker = new CreateLutWorker(v, std::min(v + vDelta, 256), phase, &yuvConverter, bestResult, _lut.data()); + QThreadPool::globalInstance()->start(worker); } + QThreadPool::globalInstance()->waitForDone(); - // display final colors - displayPostCalibrationInfo(); - - return true; -} - -void LutCalibrator::applyFilter() -{ - uint8_t* _secondBuffer = &(_lut.data()[LUT_FILE_SIZE]); - - memset(_secondBuffer, 0, LUT_FILE_SIZE); - - for (int r = 0; r < 256; r++) - for (int g = 0; g < 256; g++) - for (int b = 0; b < 256; b++) + if (phase == 1 && all != nullptr) { - uint32_t avR = 0, avG = 0, avB = 0, avCount = 0; - uint32_t index = LUT_INDEX(r, g, b); - - for (int x = -1; x <= 1; x++) - for (int y = -1; y <= 1; y++) - for (int z = -1; z <= 1; z++) + for (int r = 0; r < SCREEN_COLOR_DIMENSION; r++) + for (int g = 0; g < SCREEN_COLOR_DIMENSION; g++) + for (int b = 0; b < SCREEN_COLOR_DIMENSION; b++) { - int X = r + x; - int Y = g + y; - int Z = b + z; + auto& sample = (*all)[r][g][b]; - if (X >= 0 && X <= 255 && Y >= 0 && Y <= 255 && Z >= 0 && Z <= 255) + auto list = sample.getInputYUVColors(); + for(auto item = list.begin(); item != list.end(); ++item) { - uint32_t ind = LUT_INDEX(X, Y, Z); - uint32_t scale = (x == 0 && y == 0 && z == 0) ? 13 : 1; - - uint32_t R = _lut.data()[ind]; - uint32_t G = _lut.data()[ind + 1]; - uint32_t B = _lut.data()[ind + 2]; + auto ind_lutd = LUT_INDEX(((uint32_t)(*item).first.x), ((uint32_t)(*item).first.y), ((uint32_t)(*item).first.z)); + (*item).first = byte3{ _lut.data()[ind_lutd], _lut.data()[ind_lutd + 1], _lut.data()[ind_lutd + 2] }; + (*item).second = sample.getSourceError(static_cast((*item).first)); + } - if (scale != 1 && R == G && G == B) - { - avR = avG = avB = avCount = 0; - x = y = z = SHRT_MAX; - } + list.sort([](const std::pair& a, const std::pair& b) { return a.second < b.second; }); - avR += R * scale; - avG += G * scale; - avB += B * scale; - avCount += scale; + for (auto item = list.begin(); item != list.end(); ++item) + { + sample.setFinalRGB((*item).first); } } - - _secondBuffer[index] = clampToInt(((avR / (double)avCount)), 0, 255); - _secondBuffer[index + 1] = clampToInt(((avG / (double)avCount)), 0, 255); - _secondBuffer[index + 2] = clampToInt(((avB / (double)avCount)), 0, 255); } - memcpy(_lut.data(), _secondBuffer, LUT_FILE_SIZE); + file.write(reinterpret_cast(_lut.data()), _lut.size()); + } + file.close(); + } + return QString(); } -double LutCalibrator::fineTune(double& optimalRange, double& optimalScale, int& optimalWhite, int& optimalStrategy) -{ - QString optimalColor; - //double floor = qMax(_minColor.red, qMax(_minColor.green, _minColor.blue)); - double ceiling = qMin(_maxColor.red, qMin(_maxColor.green, _maxColor.blue)); - capColors primaries[] = { capColors::HighestGray, capColors::LowestGray, capColors::Red, capColors::Green, capColors::Blue, capColors::LowRed, capColors::LowGreen, capColors::LowBlue, capColors::Yellow, capColors::Magenta, capColors::Cyan, capColors::Pink, capColors::Orange, capColors::Azure, capColors::Brown, capColors::Purple, - capColors::Gray1, capColors::Gray2, capColors::Gray3, capColors::Gray4, capColors::Gray5, capColors::Gray6, capColors::Gray7, capColors::Gray8, capColors::White }; - - double maxError = (double)LLONG_MAX; +void LutCalibrator::setupWhitePointCorrection() +{ + - optimalStrategy = 2; - optimalWhite = capColors::White; - optimalRange = ceiling; + //for (const auto& coeff : YuvConverter::knownCoeffs) + { + /* + QString selected; + double min = std::numeric_limits::max(); + for (int w = WHITE_POINT_D65; w < WHITE_POINT_XY.size(); w++) + { + const vec& TEST_WHITE = WHITE_POINT_XY[w]; + + auto convert_bt2020_to_XYZ = to_XYZ(PRIMARIES[w][0], PRIMARIES[w][1], PRIMARIES[w][2], TEST_WHITE); + auto white_XYZ = mul(convert_bt2020_to_XYZ, whiteLinRGB); + auto white_xy = from_XYZ_to_xy(white_XYZ); + auto difference = TEST_WHITE - white_xy; + auto distance = length2((TEST_WHITE - white_xy) * 1000000); + if (distance < min) + { + min = distance; + selected = yuvConverter.coefToString(YuvConverter::YUV_COEFS(coef)) + " => "; + selected += (w == WHITE_POINT_D65) ? "D65" : ((w == WHITE_POINT_DCI_P3) ? "DCI_P3" : "unknowm"); + selected += QString(" (x: %1, y: %2)").arg(TEST_WHITE.x, 0, 'f', 3).arg(TEST_WHITE.y, 0, 'f', 3); + calibration.inputBT2020toXYZ[coef] = convert_bt2020_to_XYZ; + } + } + Debug(_log, QSTRING_CSTR(selected)); + */ + } +} - double rangeStart = 20, rangeLimit = 150; - bool restart = false; +void LutCalibrator::calibrate() +{ + #ifndef NDEBUG + sendReport(_log, _yuvConverter->toString()); + #endif - for (int whiteIndex = capColors::Gray1; whiteIndex <= capColors::White; whiteIndex++) + if (_defaultComp == hyperhdr::COMP_VIDEOGRABBER) + { + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_VIDEOGRABBER, -1, false); + } + if (_defaultComp == hyperhdr::COMP_FLATBUFSERVER) { - ColorStat whiteBalance = _colorBalance[whiteIndex]; + emit GlobalSignals::getInstance()->SignalRequestComponent(hyperhdr::Components::COMP_FLATBUFSERVER, -1, false); + } + + _capturedColors->finilizeBoard(); - for (int scale = (qRound(ceiling) / 8) * 8, limitScale = 512; scale <= limitScale; scale = (scale == limitScale) ? limitScale + 1 : qMin(scale + 4, limitScale)) - for (int strategy = 0; strategy < 3; strategy++) - for (double range = rangeStart; range <= rangeLimit; range += (range < 5) ? 0.1 : 0.5) - if (strategy != 2 || range == rangeLimit) - { - double currentError = 0; - QList colors; - double lR = -1, lG = -1, lB = -1; - for (int ind : primaries) - { - ColorStat calculated, normalized = _colorBalance[ind]; - normalized /= (double)scale; + sendReport(_log, "Captured colors:\r\n" + + generateReport(false)); - normalized.red *= whiteBalance.scaledRed; - normalized.green *= whiteBalance.scaledGreen; - normalized.blue *= whiteBalance.scaledBlue; + calibration(); - // ootf - if (strategy == 1) - { - normalized.red = ootf(normalized.red); - normalized.green = ootf(normalized.green); - normalized.blue = ootf(normalized.blue); - } + if (_forcedExit) + return; - // eotf - if (strategy == 0 || strategy == 1) - { - normalized.red = eotf(range, normalized.red); - normalized.green = eotf(range, normalized.green); - normalized.blue = eotf(range, normalized.blue); - } + sendReport(_log, "Calibrated:\r\n" + + generateReport(true)); - // bt2020 - if (strategy == 0 || strategy == 1) - { - fromBT2020toBT709(normalized.red, normalized.green, normalized.blue, calculated.red, calculated.green, calculated.blue); - } + notifyCalibrationFinished(); - // ootf - if (strategy == 0 || strategy == 1) - { - calculated.red = ootf(calculated.red); - calculated.green = ootf(calculated.green); - calculated.blue = ootf(calculated.blue); - } - else - { - calculated.red = normalized.red; - calculated.green = normalized.green; - calculated.blue = normalized.blue; - } + printReport(); +} - calculated.red = clampDouble(calculated.red, 0, 1.0) * 255.0; - calculated.green = clampDouble(calculated.green, 0, 1.0) * 255.0; - calculated.blue = clampDouble(calculated.blue, 0, 1.0) * 255.0; - if ((ind != capColors::HighestGray || - ((calculated.red <= 250.0 && calculated.green <= 250.0 && calculated.blue <= 250.0) && (calculated.red >= 200.0 && calculated.green >= 200.0 && calculated.blue >= 200.0))) && - (ind != capColors::LowestGray || - ((calculated.red >= 4 && calculated.green >= 4 && calculated.blue >= 4) && (calculated.red <= 28 && calculated.green <= 28 && calculated.blue <= 28)))) - { - if (ind == capColors::LowRed) - lR = calculated.red; - if (ind == capColors::LowGreen) - lG = calculated.green; - if (ind == capColors::LowBlue) - lB = calculated.blue; - - currentError += getError(primeColors[ind], calculated); - colors.push_back(calculated.toQString()); - } - else - { - currentError = maxError +1; - break; - } - } +void LutCalibrator::capturedPrimariesCorrection(ColorSpaceMath::HDR_GAMMA gamma, double gammaHLG, double nits, int coef, linalg::mat& convert_bt2020_to_XYZ, linalg::mat& convert_XYZ_to_corrected, bool printDebug) +{ + std::vector capturedPrimaries{ + _capturedColors->all[SCREEN_COLOR_DIMENSION - 1][0][0], //red + _capturedColors->all[0][SCREEN_COLOR_DIMENSION - 1][0], // green + _capturedColors->all[0][0][SCREEN_COLOR_DIMENSION - 1], // blue + _capturedColors->all[SCREEN_COLOR_DIMENSION - 1][SCREEN_COLOR_DIMENSION - 1][SCREEN_COLOR_DIMENSION - 1] //white + }; + std::vector actualPrimaries; + + for (auto& c : capturedPrimaries) + { + auto yuv = c.yuv(); - if (lR >= 0 && lG >= 0 && lB >= 0) - { - double m = qMax(lR, qMax(lG, lB)); - double n = qMin(lR, qMin(lG, lB)); - currentError += 8 * std::pow(m - n, 2); - } + if (gamma == HDR_GAMMA::P010) + { + unpackP010(yuv); + } - if (maxError > currentError) - { - maxError = currentError; - optimalRange = range; - optimalStrategy = strategy; - optimalScale = scale; - optimalWhite = whiteIndex; - optimalColor = ""; - for (auto c : colors) - optimalColor += QString("%1 ,").arg(c); - optimalColor += QString(" range: %1, strategy: %2, scale: %3, white: %4, error: %5").arg(optimalRange).arg(optimalStrategy).arg(optimalScale).arg(optimalWhite).arg(currentError); - } - } + auto a = _yuvConverter->toRgb(_capturedColors->getRange(), YuvConverter::YUV_COEFS(coef), yuv); - if (whiteIndex == capColors::White && maxError == LLONG_MAX && !restart) + if (gamma == ColorSpaceMath::HDR_GAMMA::PQ || gamma == ColorSpaceMath::HDR_GAMMA::P010) + { + a = PQ_ST2084(10000.0 / nits, a); + } + else if (gamma == ColorSpaceMath::HDR_GAMMA::HLG) + { + a = OOTF_HLG(inverse_OETF_HLG(a), gammaHLG) * nits; + } + else if (gamma == ColorSpaceMath::HDR_GAMMA::BT2020inSRGB) { - restart = true; - whiteIndex = capColors::LowestGray; - rangeStart = 0.1; - rangeLimit = 20; - Debug(_log, "Restarting calculation"); + a = srgb_nonlinear_to_linear(a); } + else if (gamma == HDR_GAMMA::PQinSRGB) + { + a = srgb_linear_to_nonlinear(a); + a = PQ_ST2084(10000.0 / nits, a); + } + actualPrimaries.push_back(a); } - Debug(_log, "Best result => %s", QSTRING_CSTR(optimalColor)); + constexpr linalg::vec bt2020_red_xy(0.708, 0.292); + constexpr linalg::vec bt2020_green_xy(0.17, 0.797); + constexpr linalg::vec bt2020_blue_xy(0.131, 0.046); + constexpr linalg::vec bt2020_white_xy(0.3127, 0.3290); + constexpr double3x3 bt2020_to_XYZ = to_XYZ(bt2020_red_xy, bt2020_green_xy, bt2020_blue_xy, bt2020_white_xy); - return maxError + 1; -} + convert_bt2020_to_XYZ = bt2020_to_XYZ; -bool LutCalibrator::finalize(bool fastTrack) -{ - QString fileName = QString("%1%2").arg(_rootPath).arg("/lut_lin_tables.3d"); - QFile file(fileName); + double2 sRgb_red_xy = { 0.64f, 0.33f }; + double2 sRgb_green_xy = { 0.30f, 0.60f }; + double2 sRgb_blue_xy = { 0.15f, 0.06f }; + double2 sRgb_white_xy = { 0.3127f, 0.3290f }; - bool ok = true; + double3 actual_red_xy(actualPrimaries[0]); + actual_red_xy = linalg::mul(convert_bt2020_to_XYZ, actual_red_xy); + sRgb_red_xy = XYZ_to_xy(actual_red_xy); - if (!fastTrack) - { - disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewVideoImage, this, &LutCalibrator::setVideoImage); - disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewSystemImage, this, &LutCalibrator::setSystemImage); - disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetGlobalImage, this, &LutCalibrator::signalSetGlobalImageHandler); - } + double3 actual_green_xy(actualPrimaries[1]); + actual_green_xy = mul(convert_bt2020_to_XYZ, actual_green_xy); + sRgb_green_xy = XYZ_to_xy(actual_green_xy); - if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) - { - Error(_log, "Could not open: %s for writing (read-only file system or lack of rights)", QSTRING_CSTR(fileName)); - ok = false; - } - else - { - double floor = qMax(_minColor.red, qMax(_minColor.green, _minColor.blue)); - double ceil = qMin(_maxColor.red, qMin(_maxColor.green, _maxColor.blue)); - double delta = ceil - floor; - double Kr = _coefs[_currentCoef].red, Kg = _coefs[_currentCoef].green, Kb = _coefs[_currentCoef].blue; + double3 actual_blue_xy(actualPrimaries[2]); + actual_blue_xy = mul(convert_bt2020_to_XYZ, actual_blue_xy); + sRgb_blue_xy = XYZ_to_xy(actual_blue_xy); - // RGB HDR and INTRO - Debug(_log, "----------------- Preparing and saving LUT table --------------------"); - Debug(_log, "Initial mode: %s", (fastTrack) ? "YES" : "NO"); - Debug(_log, "Using YUV coefs: %s", REC(_currentCoef)); - Debug(_log, "YUV table range: %s", (_limitedRange) ? "LIMITED" : "FULL"); + double3 actual_white_xy(actualPrimaries[3]); + actual_white_xy = mul(convert_bt2020_to_XYZ, actual_white_xy); + sRgb_white_xy = XYZ_to_xy(actual_white_xy); - if (floor <= ceil) - { - Debug(_log, "Min RGB floor: %f, max RGB ceiling: %f", floor, ceil); - Debug(_log, "Delta RGB range => %f", delta); - Debug(_log, "Min RGB range => %s", STRING_CSTR(_minColor)); - Debug(_log, "Max RGB range => %s", STRING_CSTR(_maxColor)); - } + mat convert_sRgb_to_XYZ; + convert_sRgb_to_XYZ = to_XYZ(sRgb_red_xy, sRgb_green_xy, sRgb_blue_xy, sRgb_white_xy); - file.write((const char*)_lut.data(), LUT_FILE_SIZE); - Debug(_log, "LUT RGB HDR table (1/3) is ready"); + convert_XYZ_to_corrected = inverse(convert_sRgb_to_XYZ); - // YUV HDR - uint8_t* _yuvBuffer = &(_lut.data()[LUT_FILE_SIZE]); + if (printDebug) + { + double2 sRgbR = { 0.64f, 0.33f }; + double2 sRgbG = { 0.30f, 0.60f }; + double2 sRgbB = { 0.15f, 0.06f }; + double2 sRgbW = { 0.3127f, 0.3290f }; + + auto dr = linalg::angle(sRgb_red_xy - sRgb_white_xy, sRgbR - sRgbW); + auto dg = linalg::angle(sRgb_green_xy - sRgb_white_xy, sRgbG - sRgbW); + auto db = linalg::angle(sRgb_blue_xy - sRgb_white_xy, sRgbB - sRgbW); + + Debug(_log, "--------------------------------- Actual PQ primaries for YUV coefs: %s ---------------------------------", QSTRING_CSTR(_yuvConverter->coefToString(YuvConverter::YUV_COEFS(coef)))); + Debug(_log, "r: (%.3f, %.3f, a: %.3f) vs sRGB(%.3f, %.3f) vs bt2020(%.3f, %.3f) vs wide(%.3f, %.3f)", sRgb_red_xy.x, sRgb_red_xy.y, dr, 0.64f, 0.33f, 0.708f, 0.292f, 0.7350f, 0.2650f); + Debug(_log, "g: (%.3f, %.3f, a: %.3f) vs sRGB(%.3f, %.3f) vs bt2020(%.3f, %.3f) vs wide(%.3f, %.3f)", sRgb_green_xy.x, sRgb_green_xy.y, dg, 0.30f, 0.60f, 0.17f, 0.797f, 0.1150f, 0.8260f); + Debug(_log, "b: (%.3f, %.3f, a: %.3f) vs sRGB(%.3f, %.3f) vs bt2020(%.3f, %.3f) vs wide(%.3f, %.3f)", sRgb_blue_xy.x, sRgb_blue_xy.y, db, 0.15f, 0.06f, 0.131f, 0.046f, 0.1570f, 0.0180f); + Debug(_log, "w: (%.3f, %.3f) vs sRGB(%.3f, %.3f) vs bt2020(%.3f, %.3f) vs wide(%.3f, %.3f)", sRgb_white_xy.x, sRgb_white_xy.y, 0.3127f, 0.3290f, 0.3127f, 0.3290f, 0.3127f, 0.3290f); + } +} - memset(_yuvBuffer, 0, LUT_FILE_SIZE); - for (int y = 0; y < 256 && !fastTrack; y++) - for (int u = 0; u < 256; u++) - for (int v = 0; v < 256; v++) - { - double r, g, b; - if (_limitedRange) - { - r = (255.0 / 219.0) * y + (255.0 / 112) * v * (1 - Kr) - (255.0 * 16.0 / 219 + 255.0 * 128.0 / 112.0 * (1 - Kr)); - g = (255.0 / 219.0) * y - (255.0 / 112) * u * (1 - Kb) * Kb / Kg - (255.0 / 112.0) * v * (1 - Kr) * Kr / Kg - - (255.0 * 16.0 / 219.0 - 255.0 / 112.0 * 128.0 * (1 - Kb) * Kb / Kg - 255.0 / 112.0 * 128.0 * (1 - Kr) * Kr / Kg); - b = (255.0 / 219.0) * y + (255.0 / 112.0) * u * (1 - Kb) - (255.0 * 16 / 219.0 + 255.0 * 128.0 / 112.0 * (1 - Kb)); - } - else - { - r = y + 2 * (v - 128) * (1 - Kr); - g = y - 2 * (u - 128) * (1 - Kb) * Kb / Kg - 2 * (v - 128) * (1 - Kr) * Kr / Kg; - b = y + 2 * (u - 128) * (1 - Kb); - } +bool LutCalibrator::setTestData() +{ + std::vector> capturedData; - int _R = clampToInt(r, 0, 255); - int _G = clampToInt(g, 0, 255); - int _B = clampToInt(b, 0, 255); + // asssign your test data from calibration_captured_yuv.txt to testData here - uint32_t indexRgb = LUT_INDEX(_R, _G, _B); - uint32_t index = LUT_INDEX(y, u, v); - _yuvBuffer[index] = _lut.data()[indexRgb]; - _yuvBuffer[index + 1] = _lut.data()[indexRgb + 1]; - _yuvBuffer[index + 2] = _lut.data()[indexRgb + 2]; - } - file.write((const char*)_yuvBuffer, LUT_FILE_SIZE); - Debug(_log, "LUT YUV HDR table (2/3) is ready"); + // verify + if (capturedData.size() != SCREEN_COLOR_DIMENSION * SCREEN_COLOR_DIMENSION * SCREEN_COLOR_DIMENSION) + return false; - // YUV - for (int y = 0; y < 256; y++) - for (int u = 0; u < 256; u++) - for (int v = 0; v < 256; v++) - { - uint32_t ind_lutd = LUT_INDEX(y, u, v); - double r, g, b; + auto iter = capturedData.begin(); + for (int r = 0; r < SCREEN_COLOR_DIMENSION; r++) + for (int g = 0; g < SCREEN_COLOR_DIMENSION; g++) + for (int b = 0; b < SCREEN_COLOR_DIMENSION; b++, ++iter) + { + auto& sample = _capturedColors->all[r][g][b]; + int R = std::min(r * SCREEN_COLOR_STEP, 255); + int G = std::min(g * SCREEN_COLOR_STEP, 255); + int B = std::min(b * SCREEN_COLOR_STEP, 255); + sample.setSourceRGB(byte3(R, G, B)); - if (_limitedRange) - { - r = (255.0 / 219.0) * y + (255.0 / 112) * v * (1 - Kr) - (255.0 * 16.0 / 219 + 255.0 * 128.0 / 112.0 * (1 - Kr)); - g = (255.0 / 219.0) * y - (255.0 / 112) * u * (1 - Kb) * Kb / Kg - (255.0 / 112.0) * v * (1 - Kr) * Kr / Kg - - (255.0 * 16.0 / 219.0 - 255.0 / 112.0 * 128.0 * (1 - Kb) * Kb / Kg - 255.0 / 112.0 * 128.0 * (1 - Kr) * Kr / Kg); - b = (255.0 / 219.0) * y + (255.0 / 112.0) * u * (1 - Kb) - (255.0 * 16 / 219.0 + 255.0 * 128.0 / 112.0 * (1 - Kb)); - } - else + const auto& colors = (*iter); + + for (int i = 0; i < static_cast(colors.size()); i += 4) + { + for (int j = 0; j < colors[i]; j++) { - r = y + 2 * (v - 128) * (1 - Kr); - g = y - 2 * (u - 128) * (1 - Kb) * Kb / Kg - 2 * (v - 128) * (1 - Kr) * Kr / Kg; - b = y + 2 * (u - 128) * (1 - Kb); + sample.addColor(ColorRgb(colors[i+1], colors[i+2], colors[i+3])); } - - _lut.data()[ind_lutd] = clampToInt(r, 0, 255); - _lut.data()[ind_lutd + 1] = clampToInt(g, 0, 255); - _lut.data()[ind_lutd + 2] = clampToInt(b, 0, 255); - - } - file.write((const char*)_lut.data(), LUT_FILE_SIZE); - - if (_mjpegCalibration && fastTrack) - { - file.seek(LUT_FILE_SIZE); - file.write((const char*)_lut.data(), LUT_FILE_SIZE); - } - - file.flush(); - Debug(_log, "LUT YUV table (3/3) is ready"); - - // finish - file.close(); - Debug(_log, "Your new LUT file is saved as %s. Is ready for usage: %s.", QSTRING_CSTR(fileName), (fastTrack) ? "NO. It's temporary LUT table without HDR information." : "YES"); - Debug(_log, "---------------------- LUT table is saved -----------------------"); - Debug(_log, ""); + } + + sample.calculateFinalColor(); + } + if (_capturedColors->all[0][0][0].Y() > SCREEN_YUV_RANGE_LIMIT || _capturedColors->all[0][0][0].Y() < 255 - SCREEN_YUV_RANGE_LIMIT) + { + _capturedColors->setRange(YuvConverter::LIMITED); + } + else + { + _capturedColors->setRange(YuvConverter::FULL); } - if (!fastTrack) - stopHandler(); - - return ok; + return true; } + diff --git a/sources/lut-calibrator/YuvConverter.cpp b/sources/lut-calibrator/YuvConverter.cpp new file mode 100644 index 000000000..585a0523f --- /dev/null +++ b/sources/lut-calibrator/YuvConverter.cpp @@ -0,0 +1,204 @@ +/* YuvConverter.cpp +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#include +#include +#include +#include +#include + +namespace { + std::mutex yuvConverterMutex; +} + +double3 YuvConverter::toRgb(COLOR_RANGE range, YUV_COEFS coef, const double3& input) const +{ + double4 ret(input, 1); + ret = mul(yuv2rgb.at(range).at(coef), ret); + return double3(ret.x, ret.y, ret.z); +} + +double3 YuvConverter::toYuv(COLOR_RANGE range, YUV_COEFS coef, const double3& input) const +{ + double4 ret(input, 1); + ret = mul(rgb2yuv.at(range).at(coef), ret); + return double3(ret.x, ret.y, ret.z); +} + +double3 YuvConverter::multiplyColorMatrix(double4x4 matrix, const double3& input) const +{ + double4 ret(input, 1); + ret = mul(matrix, ret); + return double3(ret.x, ret.y, ret.z); +} + +double3 YuvConverter::toYuvBT709(COLOR_RANGE range, const double3& input) const +{ + double4 ret(input, 1); + ret = mul(rgb2yuvBT709.at(range), ret); + return double3(ret.x, ret.y, ret.z); +} + +QString YuvConverter::coefToString(YUV_COEFS cf) const +{ + switch (cf) + { + case(FCC): return "FCC"; break; + case(BT601): return "BT601"; break; + case(BT709): return "BT709"; break; + case(BT2020): return "BT2020"; break; + default: return "?"; + } +} + +YuvConverter::YuvConverter() +{ + for (const auto& coeff : knownCoeffs) + for (const COLOR_RANGE& range : { COLOR_RANGE::FULL, COLOR_RANGE::LIMITED }) + { + const double Kr = coeff.second.x; + const double Kb = coeff.second.y; + const double Kg = 1.0 - Kr - Kb; + const double Cr = 0.5 / (1.0 - Kb); + const double Cb = 0.5 / (1.0 - Kr); + + double scaleY = 1.0, addY = 0.0, scaleUV = 1.0, addUV = 128 / 255.0; + + if (range == COLOR_RANGE::LIMITED) + { + scaleY = 219 / 255.0; + addY = 16 / 255.0; + scaleUV = 224 / 255.0; + } + + double4 c1(Kr * scaleY, -Kr * Cr * scaleUV, (1 - Kr) * Cb * scaleUV, 0); + double4 c2(Kg * scaleY, -Kg * Cr * scaleUV, -Kg * Cb * scaleUV, 0); + double4 c3(Kb * scaleY, (1 - Kb) * Cr * scaleUV, -Kb * Cb * scaleUV, 0); + double4 c4(addY, addUV, addUV, 1); + + double4x4 rgb2yuvMatrix(c1, c2, c3, c4); + + double4x4 yuv2rgbMatrix = inverse(rgb2yuvMatrix); + + yuv2rgb[range][coeff.first] = yuv2rgbMatrix; + + rgb2yuv[range][coeff.first] = rgb2yuvMatrix; + + if (coeff.first == YUV_COEFS::BT709) + { + rgb2yuvBT709[range] = rgb2yuvMatrix; + } + } +} + +double2 YuvConverter::getCoef(YUV_COEFS cf) +{ + return knownCoeffs.at(cf); +} + +byte3 YuvConverter::yuv_to_rgb(YUV_COEFS coef, COLOR_RANGE range, const byte3& input) const +{ + byte3 output; + auto cf = knownCoeffs.at(coef); + + const double Kr = cf.x; + const double Kb = cf.y; + const double Kg = 1.0 - Kr - Kb; + + int x = std::max(std::min(235, (int) input.x), 16); + int y = std::max(std::min(240, (int) input.y), 16); + int z = std::max(std::min(240, (int) input.z), 16); + + if (range == COLOR_RANGE::LIMITED) + { + output.x = (255.0 / 219.0) * x + (255.0 / 112) * z * (1 - Kr) - (255.0 * 16.0 / 219 + 255.0 * 128.0 / 112.0 * (1 - Kr)); + output.y = (255.0 / 219.0) * x - (255.0 / 112) * y * (1 - Kb) * Kb / Kg - (255.0 / 112.0) * z * (1 - Kr) * Kr / Kg + - (255.0 * 16.0 / 219.0 - 255.0 / 112.0 * 128.0 * (1 - Kb) * Kb / Kg - 255.0 / 112.0 * 128.0 * (1 - Kr) * Kr / Kg); + output.z = (255.0 / 219.0) * x + (255.0 / 112.0) * y * (1 - Kb) - (255.0 * 16 / 219.0 + 255.0 * 128.0 / 112.0 * (1 - Kb)); + } + else + { + output.x = x + 2 * (z - 128) * (1 - Kr); + output.y = x - 2 * (y - 128) * (1 - Kb) * Kb / Kg - 2 * (z - 128) * (1 - Kr) * Kr / Kg; + output.z = x + 2 * (y - 128) * (1 - Kb); + } + return output; +} + +double4x4 YuvConverter::create_yuv_to_rgb_matrix(COLOR_RANGE range, double Kr, double Kb) const +{ + const double Kg = 1.0 - Kr - Kb; + const double Cr = 0.5 / (1.0 - Kb); + const double Cb = 0.5 / (1.0 - Kr); + + double scaleY = 1.0, addY = 0.0, scaleUV = 1.0, addUV = 128 / 255.0; + + if (range == COLOR_RANGE::LIMITED) + { + scaleY = 219 / 255.0; + addY = 16 / 255.0; + scaleUV = 224 / 255.0; + } + + double4 c1(Kr * scaleY, -Kr * Cr * scaleUV, (1 - Kr) * Cb * scaleUV, 0); + double4 c2(Kg * scaleY, -Kg * Cr * scaleUV, -Kg * Cb * scaleUV, 0); + double4 c3(Kb * scaleY, (1 - Kb) * Cr * scaleUV, -Kb * Cb * scaleUV, 0); + double4 c4(addY, addUV, addUV, 1); + + double4x4 rgb2yuvMatrix(c1, c2, c3, c4); + + double4x4 yuv2rgbMatrix = inverse(rgb2yuvMatrix); + + return yuv2rgbMatrix; +} + + +QString YuvConverter::toString() +{ + QStringList ret, report; + + for (const auto& coeff : knownCoeffs) + for (const COLOR_RANGE& range : { COLOR_RANGE::FULL, COLOR_RANGE::LIMITED }) + { + double4x4 matrix = yuv2rgb[range][coeff.first]; + ret.append(QString("YUV to RGB %1 (%2):").arg(coefToString(coeff.first), 6).arg((range == COLOR_RANGE::LIMITED) ? "Limited" : "Full")); + ret.append(ColorSpaceMath::matToString(matrix).split("\r\n")); + } + + for (const COLOR_RANGE& range : { COLOR_RANGE::FULL, COLOR_RANGE::LIMITED }) + { + ret.append(QString("RGB to YUV %1 (%2):").arg(coefToString(YUV_COEFS::BT709), 6).arg((range == COLOR_RANGE::LIMITED) ? "Limited" : "Full")); + ret.append(ColorSpaceMath::matToString(rgb2yuvBT709[range]).split("\r\n")); + } + + for (int i = 0; i + 5 < static_cast(ret.size()); i += 5) + for (int j = 0; j < 5; j++, i++) + if (i + 5 < static_cast(ret.size())) + report.append(QString("%1 %2").arg(ret[i], -32).arg(ret[i + 5], -32)); + + return "Supported YUV/RGB matrix transformation:\r\n\r\n" + report.join("\r\n"); +} diff --git a/sources/utils-image/utils-image.cpp b/sources/utils-image/utils-image.cpp index e9838c1ca..1a940e205 100644 --- a/sources/utils-image/utils-image.cpp +++ b/sources/utils-image/utils-image.cpp @@ -140,4 +140,27 @@ namespace utils_image tjDestroy(_jpegCompressor); tjFree(compressedImage); } + + bool _IMAGE_SHARED_API savePng(const std::string& filename, const Image& image) + { + return stbi_write_png(filename.c_str(), image.width(), image.height(), 3, image.rawMem(), 0); + } + + Image _IMAGE_SHARED_API load2image(const std::string& filename) + { + Image ret; + int w, h, comp; + + unsigned char* image = stbi_load(filename.c_str(), &w, &h, &comp, STBI_rgb); + + if (image != nullptr) + { + ret.resize(w, h); + memcpy(ret.rawMem(), image, 3ll * w * h); + } + + STBIW_FREE(image); + + return ret; + } }; diff --git a/sources/utils/CMakeLists.txt b/sources/utils/CMakeLists.txt index 6e9bac641..42fb9dc5d 100644 --- a/sources/utils/CMakeLists.txt +++ b/sources/utils/CMakeLists.txt @@ -15,6 +15,7 @@ add_library(hyperhdr-utils OBJECT ${Utils_SOURCES}) target_link_libraries(hyperhdr-utils Qt${Qt_VERSION}::Core Qt${Qt_VERSION}::Network + linalg ) if(USE_PRECOMPILED_HEADERS AND COMMAND target_precompile_headers) diff --git a/sources/utils/FrameDecoder.cpp b/sources/utils/FrameDecoder.cpp index 413832c6d..5e24c6fda 100644 --- a/sources/utils/FrameDecoder.cpp +++ b/sources/utils/FrameDecoder.cpp @@ -29,17 +29,163 @@ #include #include -//#define TAKE_SCREEN_SHOT +#include +#include +#include +#include + +using namespace linalg; +using namespace aliases; + +namespace FrameDecoderUtils +{ + std::atomic initialized(false); + std::vector lutP010_y; + std::vector lutP010_uv; + + constexpr double signalBreakP010 = 0.9; + constexpr double signalBreakChromaP010 = 0.75; + + static double packChromaP010(double x) + { + constexpr double pi2 = M_PI / 2.0; + if (x < 0.0) + { + return 0.0; + } + else if (x <= 0.5) + { + return x * 1.5; + } + else if (x <= 1) + { + return std::sin(pi2 * ((x - 0.5) / 0.5)) * (1 - signalBreakChromaP010) + signalBreakChromaP010; + } + return 1; + }; + + double unpackChromaP010(double x) + { + constexpr double pi2 = M_PI / 2.0; + if (x < 0.0) + { + return 0.0; + } + else if (x <= signalBreakChromaP010) + { + x /= 1.5; + return x; + } + else if (x <= 1) + { + x = (x - signalBreakChromaP010) / (1.0 - signalBreakChromaP010); + x = std::asin(x); + x = x * 0.5 / pi2 + 0.5; + return x; + } + + return 1; + }; + + static double packLuminanceP010(double x) + { + constexpr double pi2 = M_PI / 2.0; + if (x < 0.0) + { + return 0.0; + } + else if (x <= 0.6) + { + return x * 1.5; + } + else if (x <= 1) + { + return std::sin(pi2 * ((x - 0.6) / 0.4)) * (1 - signalBreakP010) + signalBreakP010; + } + return 1; + }; + + double unpackLuminanceP010(double x) + { + constexpr double pi2 = M_PI / 2.0; + if (x < 0.0) + { + return 0.0; + } + else if (x <= signalBreakP010) + { + return x / 1.5; + } + else if (x <= 1) + { + x = (x - signalBreakP010) / (1.0 - signalBreakP010); + x = std::asin(x); + x = x * 0.4 / pi2 + 0.6; + return x; + } + + return 1; + }; + + + static void initP010() + { + static std::mutex lockerP010; + std::lock_guard locker(lockerP010); + + if (FrameDecoderUtils::initialized) + return; + + lutP010_y.resize(1024); + lutP010_uv.resize(1024); + + for (int i = 0; i < static_cast(lutP010_y.size()); ++i) + { + constexpr int sourceRange = 940 - 64; + const double sourceValue = std::min(std::max(i - 64, 0), sourceRange)/static_cast(sourceRange); + double val = packLuminanceP010(sourceValue); + lutP010_y[i] = std::lround(val * 255.0); + + /* + double unpack = unpackLuminanceP010(val); + double delta = sourceValue - unpack; + if (std::abs(delta) > 0.0000001) + { + bool error = true; + } + */ + } + + + for (int i = 0; i < static_cast(lutP010_uv.size()); ++i) + { + constexpr int sourceRange = (960 - 64) / 2; + const int current = std::abs(i - 512); + const double sourceValue = std::min(current, sourceRange) / static_cast(sourceRange); + double val = packChromaP010(sourceValue); + lutP010_uv[i] = std::max(std::min(128 + std::lround(((i < 512) ? -val : val) * 128.0), 255l), 0l);; + + /* + double unpack = unpackChromaP010(val); + double delta = sourceValue - unpack; + if (std::abs(delta) > 0.0000001) + { + bool error = true; + } + */ + } + + FrameDecoderUtils::initialized = true; + }; +}; + +using namespace FrameDecoderUtils; -#ifdef TAKE_SCREEN_SHOT - #include - int screenShotTaken = 300; -#endif void FrameDecoder::processImage( int _cropLeft, int _cropRight, int _cropTop, int _cropBottom, const uint8_t* data, const uint8_t* dataUV, int width, int height, int lineLength, - const PixelFormat pixelFormat, const uint8_t* lutBuffer, Image& outputImage) + const PixelFormat pixelFormat, const uint8_t* lutBuffer, Image& outputImage, bool toneMapping) { uint32_t ind_lutd, ind_lutd2; uint8_t buffer[8]; @@ -47,7 +193,7 @@ void FrameDecoder::processImage( // validate format if (pixelFormat != PixelFormat::YUYV && pixelFormat != PixelFormat::XRGB && pixelFormat != PixelFormat::RGB24 && - pixelFormat != PixelFormat::I420 && pixelFormat != PixelFormat::NV12 && pixelFormat != PixelFormat::MJPEG) + pixelFormat != PixelFormat::I420 && pixelFormat != PixelFormat::NV12 && pixelFormat != PixelFormat::P010 && pixelFormat != PixelFormat::MJPEG) { Error(Logger::getInstance("FrameDecoder"), "Invalid pixel format given"); return; @@ -55,7 +201,7 @@ void FrameDecoder::processImage( // validate format LUT if ((pixelFormat == PixelFormat::YUYV || pixelFormat == PixelFormat::I420 || pixelFormat == PixelFormat::MJPEG || - pixelFormat == PixelFormat::NV12) && lutBuffer == NULL) + pixelFormat == PixelFormat::NV12 || pixelFormat == PixelFormat::P010) && lutBuffer == NULL) { Error(Logger::getInstance("FrameDecoder"), "Missing LUT table for YUV colorspace"); return; @@ -70,6 +216,7 @@ void FrameDecoder::processImage( int outputHeight = (height - _cropTop - _cropBottom); outputImage.resize(outputWidth, outputHeight); + outputImage.setOriginFormat(pixelFormat); uint8_t* destMemory = outputImage.rawMem(); int destLineSize = outputImage.width() * 3; @@ -98,14 +245,6 @@ void FrameDecoder::processImage( } } -#ifdef TAKE_SCREEN_SHOT - if (screenShotTaken > 0 && screenShotTaken-- == 1) - { - QImage jpgImage((const uint8_t*)outputImage.memptr(), outputImage.width(), outputImage.height(), 3 * outputImage.width(), QImage::Format_RGB888); - jpgImage.save("D:/grabber_yuv.png", "png"); - } -#endif - return; } @@ -238,6 +377,64 @@ void FrameDecoder::processImage( return; } + if (pixelFormat == PixelFormat::P010) + { + uint16_t p010[2] = {}; + + if (!FrameDecoderUtils::initialized) + { + initP010(); + } + + auto deltaUV = (dataUV != nullptr) ? (uint8_t*)dataUV : (uint8_t*)data + lineLength * height; + for (int yDest = 0, ySource = _cropTop; yDest < outputHeight; ++ySource, ++yDest) + { + uint8_t* currentDest = destMemory + ((uint64_t)destLineSize) * yDest; + uint8_t* endDest = currentDest + destLineSize; + uint8_t* currentSource = (uint8_t*)data + (((uint64_t)lineLength * ySource) + ((uint64_t)_cropLeft)); + uint8_t* currentSourceUV = deltaUV + (((uint64_t)ySource / 2) * lineLength) + ((uint64_t)_cropLeft); + + while (currentDest < endDest) + { + memcpy(((uint32_t*)&p010), ((uint32_t*)currentSource), 4); + if (toneMapping) + { + buffer[0] = lutP010_y[p010[0] >> 6]; + buffer[1] = lutP010_y[p010[1] >> 6]; + } + else + { + buffer[0] = p010[0] >> 8; + buffer[1] = p010[1] >> 8; + } + + currentSource += 4; + memcpy(((uint32_t*)&p010), ((uint32_t*)currentSourceUV), 4); + if (toneMapping) + { + buffer[2] = lutP010_uv[p010[0] >> 6]; + buffer[3] = lutP010_uv[p010[1] >> 6]; + } + else + { + buffer[2] = p010[0] >> 8; + buffer[3] = p010[1] >> 8; + } + + currentSourceUV += 4; + + ind_lutd = LUT_INDEX(buffer[0], buffer[2], buffer[3]); + ind_lutd2 = LUT_INDEX(buffer[1], buffer[2], buffer[3]); + + *((uint32_t*)currentDest) = *((uint32_t*)(&lutBuffer[ind_lutd])); + currentDest += 3; + *((uint32_t*)currentDest) = *((uint32_t*)(&lutBuffer[ind_lutd2])); + currentDest += 3; + } + } + return; + } + if (pixelFormat == PixelFormat::NV12) { auto deltaUV = (dataUV != nullptr) ? (uint8_t*)dataUV : (uint8_t*)data + lineLength * height; @@ -264,20 +461,14 @@ void FrameDecoder::processImage( currentDest += 3; } } -#ifdef TAKE_SCREEN_SHOT - if (screenShotTaken > 0 && screenShotTaken-- == 1) - { - QImage jpgImage((const uint8_t*)outputImage.memptr(), outputImage.width(), outputImage.height(), 3 * outputImage.width(), QImage::Format_RGB888); - jpgImage.save("D:/grabber_nv12.png", "png"); - } -#endif + return; } } void FrameDecoder::processQImage( - const uint8_t* data, int width, int height, int lineLength, - const PixelFormat pixelFormat, const uint8_t* lutBuffer, Image& outputImage) + const uint8_t* data, const uint8_t* dataUV, int width, int height, int lineLength, + const PixelFormat pixelFormat, const uint8_t* lutBuffer, Image& outputImage, bool toneMapping) { uint32_t ind_lutd; uint8_t buffer[8]; @@ -285,7 +476,7 @@ void FrameDecoder::processQImage( // validate format if (pixelFormat != PixelFormat::YUYV && pixelFormat != PixelFormat::XRGB && pixelFormat != PixelFormat::RGB24 && - pixelFormat != PixelFormat::I420 && pixelFormat != PixelFormat::NV12) + pixelFormat != PixelFormat::I420 && pixelFormat != PixelFormat::NV12 && pixelFormat != PixelFormat::P010) { Error(Logger::getInstance("FrameDecoder"), "Invalid pixel format given"); return; @@ -293,7 +484,7 @@ void FrameDecoder::processQImage( // validate format LUT if ((pixelFormat == PixelFormat::YUYV || pixelFormat == PixelFormat::I420 || - pixelFormat == PixelFormat::NV12) && lutBuffer == NULL) + pixelFormat == PixelFormat::NV12 || pixelFormat == PixelFormat::P010) && lutBuffer == NULL) { Error(Logger::getInstance("FrameDecoder"), "Missing LUT table for YUV colorspace"); return; @@ -426,15 +617,67 @@ void FrameDecoder::processQImage( return; } + if (pixelFormat == PixelFormat::P010) + { + uint16_t p010[2] = {}; + + if (!FrameDecoderUtils::initialized) + { + initP010(); + } + + uint8_t* deltaUV = (dataUV != nullptr) ? (uint8_t*)dataUV : (uint8_t*)data + lineLength * height; + for (int yDest = 0, ySource = 0; yDest < outputHeight; ySource += 2, ++yDest) + { + uint8_t* currentDest = destMemory + ((uint64_t)destLineSize) * yDest; + uint8_t* endDest = currentDest + destLineSize; + uint8_t* currentSource = (uint8_t*)data + (((uint64_t)lineLength * ySource)); + uint8_t* currentSourceU = deltaUV + (((uint64_t)ySource / 2) * lineLength); + + while (currentDest < endDest) + { + memcpy(((uint16_t*)&p010), ((uint16_t*)currentSource), 2); + if (toneMapping) + { + buffer[0] = lutP010_y[p010[0] >> 6]; + } + else + { + buffer[0] = p010[0] >> 8; + } + currentSource += 4; + memcpy(((uint32_t*)&p010), ((uint32_t*)currentSourceU), 4); + if (toneMapping) + { + buffer[2] = lutP010_uv[p010[0] >> 6]; + buffer[3] = lutP010_uv[p010[1] >> 6]; + } + else + { + buffer[2] = p010[0] >> 8; + buffer[3] = p010[1] >> 8; + } + + currentSourceU += 4; + + ind_lutd = LUT_INDEX(buffer[0], buffer[2], buffer[3]); + + *((uint32_t*)currentDest) = *((uint32_t*)(&lutBuffer[ind_lutd])); + currentDest += 3; + } + } + return; + } + if (pixelFormat == PixelFormat::NV12) { - int deltaU = lineLength * height; + uint8_t* deltaUV = (dataUV != nullptr) ? (uint8_t*)dataUV : (uint8_t*)data + lineLength * height; for (int yDest = 0, ySource = 0; yDest < outputHeight; ySource += 2, ++yDest) { uint8_t* currentDest = destMemory + ((uint64_t)destLineSize) * yDest; uint8_t* endDest = currentDest + destLineSize; uint8_t* currentSource = (uint8_t*)data + (((uint64_t)lineLength * ySource)); - uint8_t* currentSourceU = (uint8_t*)data + deltaU + (((uint64_t)ySource / 2) * lineLength); + uint8_t* currentSourceU = deltaUV + (((uint64_t)ySource / 2) * lineLength); while (currentDest < endDest) { @@ -497,13 +740,6 @@ void FrameDecoder::applyLUT(uint8_t* _source, unsigned int width, unsigned int h } } } -#ifdef TAKE_SCREEN_SHOT - if (screenShotTaken > 0 && screenShotTaken-- == 1) - { - QImage jpgImage((const uint8_t*)_source, width, height, 3 * width, QImage::Format_RGB888); - jpgImage.save("D:/grabber_mjpeg.png", "png"); - } -#endif } void FrameDecoder::processSystemImageBGRA(Image& image, int targetSizeX, int targetSizeY, diff --git a/sources/utils/VideoBenchmark.cpp b/sources/utils/VideoBenchmark.cpp new file mode 100644 index 000000000..e5d454eb1 --- /dev/null +++ b/sources/utils/VideoBenchmark.cpp @@ -0,0 +1,92 @@ +/* VideoBenchmark.cpp +* +* MIT License +* +* Copyright (c) 2020-2024 awawa-dev +* +* Project homesite: https://github.com/awawa-dev/HyperHDR +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in all +* copies or substantial portions of the Software. + +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +* SOFTWARE. +*/ + +#include +#include + +VideoBenchmark::VideoBenchmark(QObject *parent): QObject(parent), + _benchmarkStatus(-1), + _benchmarkMessage(""), + _connected(false) +{} + +void VideoBenchmark::signalSetGlobalImageHandler(int priority, const Image& image, int timeout_ms, hyperhdr::Components origin, QString clientDescription) +{ + newFrame(image); +} + + +void VideoBenchmark::signalNewVideoImageHandler(const QString& name, const Image& image) +{ + newFrame(image); +} + +void VideoBenchmark::benchmarkCapture(int status, QString message) +{ + if (message == "ping") + { + if (!_connected) + { + _connected = true; + connect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewVideoImage, this, &VideoBenchmark::signalNewVideoImageHandler, Qt::ConnectionType::UniqueConnection); + connect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetGlobalImage, this, &VideoBenchmark::signalSetGlobalImageHandler, Qt::ConnectionType::UniqueConnection); + } + emit SignalBenchmarkUpdate(status, "pong"); + } + else + { + _benchmarkStatus = status; + _benchmarkMessage = message; + + if (_benchmarkMessage == "stop" || _benchmarkStatus < 0) + { + _connected = false; + disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalNewVideoImage, this, &VideoBenchmark::signalNewVideoImageHandler); + disconnect(GlobalSignals::getInstance(), &GlobalSignals::SignalSetGlobalImage, this, &VideoBenchmark::signalSetGlobalImageHandler); + + } + } +} + +void VideoBenchmark::newFrame(const Image& image) +{ + if (_benchmarkStatus >= 0) + { + ColorRgb pixel = image(image.width() / 2, image.height() / 2); + if ((_benchmarkMessage == "white" && pixel.red > 120 && pixel.green > 120 && pixel.blue > 120) || + (_benchmarkMessage == "red" && pixel.red > 120 && pixel.green < 30 && pixel.blue < 30) || + (_benchmarkMessage == "green" && pixel.red < 30 && pixel.green > 120 && pixel.blue < 30) || + (_benchmarkMessage == "blue" && pixel.red < 30 && pixel.green < 40 && pixel.blue > 120) || + (_benchmarkMessage == "black" && pixel.red < 30 && pixel.green < 30 && pixel.blue < 30)) + + { + emit SignalBenchmarkUpdate(_benchmarkStatus, _benchmarkMessage); + _benchmarkStatus = -1; + _benchmarkMessage = ""; + } + } +} diff --git a/www/content/grabber_calibration.html b/www/content/grabber_calibration.html index b9e66938e..453c002c4 100644 --- a/www/content/grabber_calibration.html +++ b/www/content/grabber_calibration.html @@ -1,31 +1,86 @@
- -
-

-
+
-
- - -
-
-
- Saturation - Luminance + + + +
+
+
+
+
+
+
+ + +
+
+ + +
+
+ +
+
+
-
-
-
- Gamma Red - Gamma Green - Gamma Blue + + +
+
+ +
-
-
- -
+ +