From 19c8d96fbff1ab5e7e9f3fe34e78c903cd9983dc Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Tue, 10 Jun 2025 12:59:44 +0200 Subject: [PATCH 01/37] Add basics for handling of RGB565 format --- YUViewLib/src/decoder/decoderFFmpeg.cpp | 30 +- YUViewLib/src/video/rgb/ConversionRGB.cpp | 34 +- YUViewLib/src/video/rgb/PixelFormatRGB.cpp | 170 +++++++--- YUViewLib/src/video/rgb/PixelFormatRGB.h | 93 +++--- .../src/video/rgb/PixelFormatRGBGuess.cpp | 40 ++- YUViewLib/src/video/rgb/videoHandlerRGB.cpp | 149 +++++---- YUViewLib/src/video/rgb/videoHandlerRGB.h | 2 +- .../rgb/videoHandlerRGBCustomFormatDialog.cpp | 4 +- .../video/rgb/ConversionRGBTest.cpp | 24 +- YUViewUnitTest/video/rgb/CreateTestData.cpp | 14 +- .../video/rgb/GetPixelValueTest.cpp | 8 +- .../video/rgb/PixelFormatRGBGuessTest.cpp | 294 +++++++++--------- .../video/rgb/PixelFormatRGBTest.cpp | 9 +- 13 files changed, 484 insertions(+), 387 deletions(-) diff --git a/YUViewLib/src/decoder/decoderFFmpeg.cpp b/YUViewLib/src/decoder/decoderFFmpeg.cpp index d6d88de41..622e56ac6 100644 --- a/YUViewLib/src/decoder/decoderFFmpeg.cpp +++ b/YUViewLib/src/decoder/decoderFFmpeg.cpp @@ -204,14 +204,14 @@ void decoderFFmpeg::copyCurImageToBuffer() for (unsigned plane = 0; plane < pixFmt.getNrPlanes(); plane++) { const auto component = - (plane == 0) ? video::yuv::Component::Luma : video::yuv::Component::Chroma; + (plane == 0) ? video::yuv::Component::Luma : video::yuv::Component::Chroma; auto src = frame.getData(plane); const auto srcLinesize = frame.getLineSize(plane); auto dst = this->currentOutputBuffer.data(); if (plane > 0) dst += (nrBytesY + (plane - 1) * nrBytesC); const auto dstLinesize = - this->frameSize.width / pixFmt.getSubsamplingHor(component) * nrBytesPerSample; + this->frameSize.width / pixFmt.getSubsamplingHor(component) * nrBytesPerSample; const auto height = this->frameSize.height / pixFmt.getSubsamplingVer(component); for (unsigned y = 0; y < height; y++) { @@ -224,10 +224,10 @@ void decoderFFmpeg::copyCurImageToBuffer() else if (this->rawFormat == video::RawFormat::RGB) { const auto pixFmt = this->getRGBPixelFormat(); - const auto nrBytesPerSample = pixFmt.getBitsPerSample() <= 8 ? 1 : 2; + const auto nrBytesPerSample = pixFmt.getBitsPerComponent() <= 8 ? 1 : 2; const auto nrBytesPerComponent = - this->frameSize.width * this->frameSize.height * nrBytesPerSample; - const auto nrBytes = nrBytesPerComponent * pixFmt.nrChannels(); + this->frameSize.width * this->frameSize.height * nrBytesPerSample; + const auto nrBytes = nrBytesPerComponent * pixFmt.getNrChannels(); // Is the output big enough? if (auto c = functions::clipToUnsigned(this->currentOutputBuffer.capacity()); c < nrBytes) @@ -257,7 +257,7 @@ void decoderFFmpeg::copyCurImageToBuffer() else { // We only need to iterate over the image once and copy all values per line at once (RGB(A)) - const auto wDst = this->frameSize.width * nrBytesPerSample * pixFmt.nrChannels(); + const auto wDst = this->frameSize.width * nrBytesPerSample * pixFmt.getNrChannels(); auto src = frame.getData(0); const auto srcLinesize = frame.getLineSize(0); for (unsigned y = 0; y < hDst; y++) @@ -291,9 +291,9 @@ void decoderFFmpeg::cacheCurStatistics() const int16_t mvY = mvs.dst_y - mvs.src_y; this->statisticsData->at(mvs.source < 0 ? 0 : 1) - .addBlockValue(blockX, blockY, mvs.w, mvs.h, (int)mvs.source); + .addBlockValue(blockX, blockY, mvs.w, mvs.h, (int)mvs.source); this->statisticsData->at(mvs.source < 0 ? 2 : 3) - .addBlockVector(blockX, blockY, mvs.w, mvs.h, mvX, mvY); + .addBlockVector(blockX, blockY, mvs.w, mvs.h, mvX, mvY); } } } @@ -339,7 +339,7 @@ bool decoderFFmpeg::pushAVPacket(FFmpeg::AVPacketWrapper &pkt) if (this->flushing) { DEBUG_FFMPEG( - "decoderFFmpeg::pushAVPacket: Error no new packets should be pushed in flushing mode."); + "decoderFFmpeg::pushAVPacket: Error no new packets should be pushed in flushing mode."); return false; } @@ -351,7 +351,7 @@ bool decoderFFmpeg::pushAVPacket(FFmpeg::AVPacketWrapper &pkt) #if DECODERFFMPEG_DEBUG_OUTPUT { QString meaning = - QString("decoderFFmpeg::pushAVPacket: Error sending packet - err %1").arg(retPush); + QString("decoderFFmpeg::pushAVPacket: Error sending packet - err %1").arg(retPush); if (retPush == -1094995529) meaning += " INDA"; // Log the first bytes @@ -379,7 +379,7 @@ bool decoderFFmpeg::pushAVPacket(FFmpeg::AVPacketWrapper &pkt) { // Enough data pushed. Decode and retrieve frames now. DEBUG_FFMPEG( - "decoderFFmpeg::pushAVPacket: Enough data pushed. Decode and retrieve frames now."); + "decoderFFmpeg::pushAVPacket: Enough data pushed. Decode and retrieve frames now."); this->decoderState = DecoderState::RetrieveFrames; return false; } @@ -430,7 +430,7 @@ bool decoderFFmpeg::decodeFrame() void decoderFFmpeg::fillStatisticList(stats::StatisticsData &statisticsData) const { auto sourceColorMapper = - stats::color::ColorMapper({-2, 2}, stats::color::PredefinedType::Col3_bblg); + stats::color::ColorMapper({-2, 2}, stats::color::PredefinedType::Col3_bblg); statisticsData.addStatType(stats::StatisticsType(0, "Source -", sourceColorMapper)); statisticsData.addStatType(stats::StatisticsType(1, "Source +", sourceColorMapper)); @@ -454,7 +454,7 @@ bool decoderFFmpeg::createDecoder(FFmpeg::AVCodecIDWrapper codecID, this->decCtx = this->ff.allocDecoder(this->videoCodec); if (!this->decCtx) return this->setErrorB( - QStringLiteral("Could not allocate video decoder (avcodec_alloc_context3)")); + QStringLiteral("Could not allocate video decoder (avcodec_alloc_context3)")); if (codecpar && !this->ff.configureDecoder(decCtx, codecpar)) return this->setErrorB(QStringLiteral("Unable to configure decoder from codecpar")); @@ -474,13 +474,13 @@ bool decoderFFmpeg::createDecoder(FFmpeg::AVCodecIDWrapper codecID, int ret = this->ff.dictSet(opts, "flags2", "+export_mvs", 0); if (ret < 0) return this->setErrorB( - QStringLiteral("Could not request motion vector retrieval. Return code %1").arg(ret)); + QStringLiteral("Could not request motion vector retrieval. Return code %1").arg(ret)); // Open codec ret = this->ff.avcodecOpen2(decCtx, videoCodec, opts); if (ret < 0) return this->setErrorB( - QStringLiteral("Could not open the video codec (avcodec_open2). Return code %1.").arg(ret)); + QStringLiteral("Could not open the video codec (avcodec_open2). Return code %1.").arg(ret)); this->frame = ff.allocateFrame(); if (!this->frame) diff --git a/YUViewLib/src/video/rgb/ConversionRGB.cpp b/YUViewLib/src/video/rgb/ConversionRGB.cpp index 350357262..e1e160d26 100644 --- a/YUViewLib/src/video/rgb/ConversionRGB.cpp +++ b/YUViewLib/src/video/rgb/ConversionRGB.cpp @@ -81,9 +81,9 @@ void convertRGBToARGB(const QByteArray &sourceBuffer, const bool outputHasAlpha, const bool premultiplyAlpha) { - const int rightShift = bitDepth == 8 ? 0 : (srcPixelFormat.getBitsPerSample() - 8); + const int rightShift = bitDepth == 8 ? 0 : (srcPixelFormat.getBitsPerComponent() - 8); const auto offsetToNextValue = - srcPixelFormat.getDataLayout() == DataLayout::Planar ? 1 : srcPixelFormat.nrChannels(); + srcPixelFormat.getDataLayout() == DataLayout::Planar ? 1 : srcPixelFormat.getNrChannels(); using InValueType = UintValueType; const auto setAlpha = outputHasAlpha && srcPixelFormat.hasAlpha(); @@ -170,9 +170,9 @@ void convertRGBPlaneToARGB(const QByteArray &sourceBuffer, const bool invert, const bool limitedRange) { - const auto shiftTo8Bit = srcPixelFormat.getBitsPerSample() - 8; + const auto shiftTo8Bit = srcPixelFormat.getBitsPerComponent() - 8; const auto offsetToNextValue = - srcPixelFormat.getDataLayout() == DataLayout::Planar ? 1 : srcPixelFormat.nrChannels(); + srcPixelFormat.getDataLayout() == DataLayout::Planar ? 1 : srcPixelFormat.getNrChannels(); using InValueType = UintValueType; @@ -212,7 +212,7 @@ rgba_t getPixelValue(const QByteArray &sourceBuffer, const QPoint &pixelPos) { const auto offsetToNextValue = - srcPixelFormat.getDataLayout() == DataLayout::Planar ? 1 : srcPixelFormat.nrChannels(); + srcPixelFormat.getDataLayout() == DataLayout::Planar ? 1 : srcPixelFormat.getNrChannels(); const auto offsetPixelPos = frameSize.width * pixelPos.y() + pixelPos.x(); using InValueType = UintValueType; @@ -250,11 +250,11 @@ void convertInputRGBToARGB(const QByteArray &sourceBuffer, const bool outputHasAlpha, const bool premultiplyAlpha) { - const auto bitsPerSample = srcPixelFormat.getBitsPerSample(); - if (bitsPerSample < 8 || bitsPerSample > 32) + const auto bitsPerComponent = srcPixelFormat.getBitsPerComponent(); + if (bitsPerComponent < 8 || bitsPerComponent > 32) throw std::invalid_argument("Invalid bit depth in pixel format for conversion"); - if (bitsPerSample == 8) + if (bitsPerComponent == 8) convertRGBToARGB<8>(sourceBuffer, srcPixelFormat, targetBuffer, @@ -264,7 +264,7 @@ void convertInputRGBToARGB(const QByteArray &sourceBuffer, limitedRange, outputHasAlpha, premultiplyAlpha); - else if (bitsPerSample <= 16) + else if (bitsPerComponent <= 16) convertRGBToARGB<16>(sourceBuffer, srcPixelFormat, targetBuffer, @@ -295,11 +295,11 @@ void convertSinglePlaneOfRGBToGreyscaleARGB(const QByteArray &sourceBuffer, const bool invert, const bool limitedRange) { - const auto bitsPerSample = srcPixelFormat.getBitsPerSample(); - if (bitsPerSample < 8 || bitsPerSample > 32) + const auto bitsPerComponent = srcPixelFormat.getBitsPerComponent(); + if (bitsPerComponent < 8 || bitsPerComponent > 32) throw std::invalid_argument("Invalid bit depth in pixel format for conversion"); - if (bitsPerSample == 8) + if (bitsPerComponent == 8) convertRGBPlaneToARGB<8>(sourceBuffer, srcPixelFormat, targetBuffer, @@ -308,7 +308,7 @@ void convertSinglePlaneOfRGBToGreyscaleARGB(const QByteArray &sourceBuffer, scale, invert, limitedRange); - else if (bitsPerSample <= 16) + else if (bitsPerComponent <= 16) convertRGBPlaneToARGB<16>(sourceBuffer, srcPixelFormat, targetBuffer, @@ -333,13 +333,13 @@ rgba_t getPixelValueFromBuffer(const QByteArray &sourceBuffer, const Size frameSize, const QPoint &pixelPos) { - const auto bitsPerSample = srcPixelFormat.getBitsPerSample(); - if (bitsPerSample < 8 || bitsPerSample > 32) + const auto bitsPerComponent = srcPixelFormat.getBitsPerComponent(); + if (bitsPerComponent < 8 || bitsPerComponent > 32) throw std::invalid_argument("Invalid bit depth in pixel format for conversion"); - if (bitsPerSample == 8) + if (bitsPerComponent == 8) return getPixelValue<8>(sourceBuffer, srcPixelFormat, frameSize, pixelPos); - else if (bitsPerSample <= 16) + else if (bitsPerComponent <= 16) return getPixelValue<16>(sourceBuffer, srcPixelFormat, frameSize, pixelPos); else return getPixelValue<32>(sourceBuffer, srcPixelFormat, frameSize, pixelPos); diff --git a/YUViewLib/src/video/rgb/PixelFormatRGB.cpp b/YUViewLib/src/video/rgb/PixelFormatRGB.cpp index a65e999eb..59576f6f1 100644 --- a/YUViewLib/src/video/rgb/PixelFormatRGB.cpp +++ b/YUViewLib/src/video/rgb/PixelFormatRGB.cpp @@ -44,53 +44,67 @@ namespace video::rgb { -PixelFormatRGB::PixelFormatRGB(unsigned bitsPerSample, - DataLayout dataLayout, - ChannelOrder channelOrder, - AlphaMode alphaMode, - Endianness endianness) - : bitsPerSample(bitsPerSample), dataLayout(dataLayout), channelOrder(channelOrder), +PixelFormatRGB::PixelFormatRGB(const int bitsPerComponent, + const DataLayout dataLayout, + const ChannelOrder channelOrder, + const AlphaMode alphaMode, + const Endianness endianness) + : bitsPerComponent(bitsPerComponent), dataLayout(dataLayout), channelOrder(channelOrder), alphaMode(alphaMode), endianness(endianness) { } PixelFormatRGB::PixelFormatRGB(const std::string &name) { - if (name != "Unknown Pixel Format") + if (name == "Unknown Pixel Format") + return; + + if (name.substr(0, 8) == "RGB565BE") { - auto channelOrderString = name.substr(0, 3); - if (name[0] == 'a' || name[0] == 'A') - { - this->alphaMode = AlphaMode::First; - channelOrderString = name.substr(1, 3); - } - else if (name[3] == 'a' || name[3] == 'A') - { - this->alphaMode = AlphaMode::Last; - channelOrderString = name.substr(0, 3); - } - auto order = ChannelOrderMapper.getValue(channelOrderString); - if (order) - this->channelOrder = *order; - - auto bitIdx = name.find("bit"); - if (bitIdx != std::string::npos) - this->bitsPerSample = std::stoi(name.substr(bitIdx - 2, 2), nullptr); - if (name.find("planar") != std::string::npos) - this->dataLayout = DataLayout::Planar; - if (this->bitsPerSample > 8 && name.find("BE") != std::string::npos) - this->endianness = Endianness::Big; + this->predefinedPixelFormat = PredefinedPixelFormat::RGB565BE; + return; + } + if (name.substr(0, 6) == "RGB565") + { + this->predefinedPixelFormat = PredefinedPixelFormat::RGB565; + return; } + + auto channelOrderString = name.substr(0, 3); + if (name[0] == 'a' || name[0] == 'A') + { + this->alphaMode = AlphaMode::First; + channelOrderString = name.substr(1, 3); + } + else if (name[3] == 'a' || name[3] == 'A') + { + this->alphaMode = AlphaMode::Last; + channelOrderString = name.substr(0, 3); + } + auto order = ChannelOrderMapper.getValue(channelOrderString); + if (order) + this->channelOrder = *order; + + auto bitIdx = name.find("bit"); + if (bitIdx != std::string::npos) + this->bitsPerComponent = std::stoi(name.substr(bitIdx - 2, 2), nullptr); + if (name.find("planar") != std::string::npos) + this->dataLayout = DataLayout::Planar; + if (this->bitsPerComponent > 8 && name.find("BE") != std::string::npos) + this->endianness = Endianness::Big; } -bool PixelFormatRGB::isValid() const +PixelFormatRGB::PixelFormatRGB(const PredefinedPixelFormat predefinedPixelFormat) { - return this->bitsPerSample >= 8 && this->bitsPerSample <= 32; + this->predefinedPixelFormat = predefinedPixelFormat; } -unsigned PixelFormatRGB::nrChannels() const +bool PixelFormatRGB::isValid() const { - return this->alphaMode != AlphaMode::None ? 4 : 3; + if (this->predefinedPixelFormat) + return true; + + return this->bitsPerComponent >= 8 && this->bitsPerComponent <= 32; } bool PixelFormatRGB::hasAlpha() const @@ -103,6 +117,11 @@ std::string PixelFormatRGB::getName() const if (!this->isValid()) return "Unknown Pixel Format"; + if (this->predefinedPixelFormat == PredefinedPixelFormat::RGB565) + return "RGB565"; + if (this->predefinedPixelFormat == PredefinedPixelFormat::RGB565BE) + return "RGB565BE"; + std::string name; if (this->alphaMode == AlphaMode::First) name += "A"; @@ -110,30 +129,80 @@ std::string PixelFormatRGB::getName() const if (this->alphaMode == AlphaMode::Last) name += "A"; - name += " " + std::to_string(this->bitsPerSample) + "bit"; + name += " " + std::to_string(this->bitsPerComponent) + "bit"; if (this->dataLayout == DataLayout::Planar) name += " planar"; - if (this->bitsPerSample > 8 && this->endianness == Endianness::Big) + if (this->bitsPerComponent > 8 && this->endianness == Endianness::Big) name += " BE"; return name; } -/* Get the number of bytes for a frame with this RGB format and the given size - */ -std::size_t PixelFormatRGB::bytesPerFrame(Size frameSize) const +int PixelFormatRGB::getBitsPerComponent() const +{ + return this->bitsPerComponent; +} + +DataLayout PixelFormatRGB::getDataLayout() const +{ + return this->dataLayout; +} + +ChannelOrder PixelFormatRGB::getChannelOrder() const +{ + return this->channelOrder; +} + +AlphaMode PixelFormatRGB::getAlphaMode() const +{ + return this->alphaMode; +} + +Endianness PixelFormatRGB::getEndianess() const +{ + return this->endianness; +} + +std::optional PixelFormatRGB::getPredefinedPixelFormat() const +{ + return this->predefinedPixelFormat; +} + +int PixelFormatRGB::getNrChannels() const +{ + if (this->predefinedPixelFormat == PredefinedPixelFormat::RGB565 || + this->predefinedPixelFormat == PredefinedPixelFormat::RGB565BE) + return 3; + + return this->alphaMode != AlphaMode::None ? 4 : 3; +} + +int PixelFormatRGB::getBytesPerFrame(const Size frameSize) const { - const auto bpsValid = this->bitsPerSample >= 8 && this->bitsPerSample <= 32; - if (!bpsValid || !frameSize.isValid()) + if (!this->isValid() || !frameSize.isValid()) return 0; - auto numSamples = std::size_t(frameSize.height) * std::size_t(frameSize.width); - auto nrBytes = numSamples * this->nrChannels() * ((this->bitsPerSample + 7) / 8); - DEBUG_RGB_FORMAT("PixelFormatRGB::bytesPerFrame samples %d channels %d bytes %d", - int(numSamples), - this->nrChannels(), - nrBytes); - return nrBytes; + const auto numberSamples = std::size_t(frameSize.height) * std::size_t(frameSize.width); + + int numberBytesPerFrame; + if (this->predefinedPixelFormat == PredefinedPixelFormat::RGB565 || + this->predefinedPixelFormat == PredefinedPixelFormat::RGB565BE) + { + numberBytesPerFrame = numberSamples * 2; + } + else + { + const auto numberBytesPerComponent = ((this->bitsPerComponent + 7) / 8); + numberBytesPerFrame = numberSamples * numberBytesPerComponent * this->getNrChannels(); + } + + DEBUG_RGB_FORMAT( + "PixelFormatRGB::bytesPerFrame numberSamples %d numberSamples %d numberBytesPerFrame %d", + int(numberSamples), + this->nrChannels(), + numberBytesPerFrame); + + return numberBytesPerFrame; } int PixelFormatRGB::getChannelPosition(Channel channel) const @@ -229,4 +298,9 @@ Channel PixelFormatRGB::getChannelAtPosition(int position) const throw std::invalid_argument("Invalid argument for channel position"); } -} // namespace video::rgb \ No newline at end of file +void PrintTo(const PixelFormatRGB &pixelFormatRGB, std::ostream *os) +{ + *os << pixelFormatRGB.getName(); +} + +} // namespace video::rgb diff --git a/YUViewLib/src/video/rgb/PixelFormatRGB.h b/YUViewLib/src/video/rgb/PixelFormatRGB.h index 99720d163..76a189600 100644 --- a/YUViewLib/src/video/rgb/PixelFormatRGB.h +++ b/YUViewLib/src/video/rgb/PixelFormatRGB.h @@ -97,23 +97,28 @@ struct rgba_t }; }; -template -inline T convertBitness(T value, unsigned src_bitness, unsigned dst_bitness) { +template inline T convertBitness(T value, unsigned src_bitness, unsigned dst_bitness) +{ if (src_bitness > dst_bitness) return value >> (src_bitness - dst_bitness); else return value << (dst_bitness - src_bitness); } -inline rgba_t convertBitness(rgba_t value, unsigned src_bitness, unsigned dst_bitness) { - return rgba_t({ - convertBitness(value.R, src_bitness, dst_bitness), - convertBitness(value.G, src_bitness, dst_bitness), - convertBitness(value.B, src_bitness, dst_bitness), - convertBitness(value.A, src_bitness, dst_bitness) - }); +inline rgba_t convertBitness(rgba_t value, unsigned src_bitness, unsigned dst_bitness) +{ + return rgba_t({convertBitness(value.R, src_bitness, dst_bitness), + convertBitness(value.G, src_bitness, dst_bitness), + convertBitness(value.B, src_bitness, dst_bitness), + convertBitness(value.A, src_bitness, dst_bitness)}); } +enum class PredefinedPixelFormat +{ + RGB565, // 16 bits packed as R:5, G:6, B:5 + RGB565BE, // 16 bits packed as R:5, G:6, B:5 Big Endian +}; + enum class ChannelOrder { RGB, @@ -125,12 +130,12 @@ enum class ChannelOrder }; constexpr EnumMapper ChannelOrderMapper = { - std::make_pair(ChannelOrder::RGB, "RGB"), - std::make_pair(ChannelOrder::RBG, "RBG"), - std::make_pair(ChannelOrder::GRB, "GRB"), - std::make_pair(ChannelOrder::GBR, "GBR"), - std::make_pair(ChannelOrder::BRG, "BRG"), - std::make_pair(ChannelOrder::BGR, "BGR")}; + std::make_pair(ChannelOrder::RGB, "RGB"), + std::make_pair(ChannelOrder::RBG, "RBG"), + std::make_pair(ChannelOrder::GRB, "GRB"), + std::make_pair(ChannelOrder::GBR, "GBR"), + std::make_pair(ChannelOrder::BRG, "BRG"), + std::make_pair(ChannelOrder::BGR, "BGR")}; enum class AlphaMode { @@ -143,36 +148,34 @@ constexpr EnumMapper AlphaModeMapper = {std::make_pair(AlphaMode:: std::make_pair(AlphaMode::First, "First"), std::make_pair(AlphaMode::Last, "Last")}; -// This class defines a specific RGB format with all properties like order of R/G/B, bitsPerValue, -// planarity... class PixelFormatRGB { public: - // The default constructor (will create an "Unknown Pixel Format") + // The default constructed Pixel format will be invalid PixelFormatRGB() = default; PixelFormatRGB(const std::string &name); - PixelFormatRGB(unsigned bitsPerSample, - DataLayout dataLayout, - ChannelOrder channelOrder, - AlphaMode alphaMode = AlphaMode::None, - Endianness endianness = Endianness::Little); - - bool isValid() const; - unsigned nrChannels() const; - bool hasAlpha() const; - std::string getName() const; - - unsigned getBitsPerSample() const { return this->bitsPerSample; } - DataLayout getDataLayout() const { return this->dataLayout; } - ChannelOrder getChannelOrder() const { return this->channelOrder; } - Endianness getEndianess() const { return this->endianness; } - - void setBitsPerSample(unsigned bitsPerSample) { this->bitsPerSample = bitsPerSample; } - void setDataLayout(DataLayout dataLayout) { this->dataLayout = dataLayout; } - - std::size_t bytesPerFrame(Size frameSize) const; - int getChannelPosition(Channel channel) const; - Channel getChannelAtPosition(int position) const; + PixelFormatRGB(const int bitsPerComponent, + const DataLayout dataLayout, + const ChannelOrder channelOrder, + const AlphaMode alphaMode = AlphaMode::None, + const Endianness endianness = Endianness::Little); + PixelFormatRGB(const PredefinedPixelFormat predefinedPixelFormat); + + [[nodiscard]] bool isValid() const; + [[nodiscard]] bool hasAlpha() const; + [[nodiscard]] std::string getName() const; + + [[nodiscard]] int getBitsPerComponent() const; + [[nodiscard]] DataLayout getDataLayout() const; + [[nodiscard]] ChannelOrder getChannelOrder() const; + [[nodiscard]] AlphaMode getAlphaMode() const; + [[nodiscard]] Endianness getEndianess() const; + [[nodiscard]] std::optional getPredefinedPixelFormat() const; + + [[nodiscard]] int getNrChannels() const; + [[nodiscard]] int getBytesPerFrame(const Size frameSize) const; + [[nodiscard]] int getChannelPosition(const Channel channel) const; + [[nodiscard]] Channel getChannelAtPosition(const int position) const; bool operator==(const PixelFormatRGB &a) const { return getName() == a.getName(); } bool operator!=(const PixelFormatRGB &a) const { return getName() != a.getName(); } @@ -180,11 +183,19 @@ class PixelFormatRGB bool operator!=(const std::string &a) const { return getName() != a; } private: - unsigned bitsPerSample{0}; + // If this is set, the format is defined according to a specific standard and does not + // conform to the definition below (using + // dataLayout/bitsPerSample/ChannelOder/alphaMode/Endianess). If this is set, none of the values + // below matter. + std::optional predefinedPixelFormat; + + int bitsPerComponent{0}; DataLayout dataLayout{DataLayout::Packed}; ChannelOrder channelOrder{ChannelOrder::RGB}; AlphaMode alphaMode{AlphaMode::None}; Endianness endianness{Endianness::Little}; }; +void PrintTo(const PixelFormatRGB &point, std::ostream *os); + } // namespace video::rgb diff --git a/YUViewLib/src/video/rgb/PixelFormatRGBGuess.cpp b/YUViewLib/src/video/rgb/PixelFormatRGBGuess.cpp index 844e1b999..d34c1b6e5 100644 --- a/YUViewLib/src/video/rgb/PixelFormatRGBGuess.cpp +++ b/YUViewLib/src/video/rgb/PixelFormatRGBGuess.cpp @@ -71,7 +71,7 @@ bool doesPixelFormatMatchFileSize(const PixelFormatRGB &pixelFormat, if (!fileSize) return true; - const auto bytesPerFrame = pixelFormat.bytesPerFrame(frameSize); + const auto bytesPerFrame = pixelFormat.getBytesPerFrame(frameSize); if (bytesPerFrame <= 0) return false; @@ -82,7 +82,7 @@ bool doesPixelFormatMatchFileSize(const PixelFormatRGB &pixelFormat, } // namespace std::optional checkForPixelFormatIndicatorInName( - const std::string &filename, const Size &frameSize, const std::optional &fileSize) + const std::string &filename, const Size &frameSize, const std::optional &fileSize) { std::string matcher = "(?:_|\\.|-)("; @@ -115,7 +115,7 @@ std::optional checkForPixelFormatIndicatorInName( name += "a"; name += bitDepthString + endiannessName; stringToMatchingFormat[name] = - PixelFormatRGB(bitDepth, DataLayout::Packed, channelOrder, alphaMode, endianness); + PixelFormatRGB(bitDepth, DataLayout::Packed, channelOrder, alphaMode, endianness); matcher += name + "|"; } } @@ -137,15 +137,18 @@ std::optional checkForPixelFormatIndicatorInName( if (doesPixelFormatMatchFileSize(format, frameSize, fileSize)) { const auto dataLayout = findDataLayoutInName(filename); - format.setDataLayout(dataLayout); - return format; + return PixelFormatRGB(format.getBitsPerComponent(), + dataLayout, + format.getChannelOrder(), + format.getAlphaMode(), + format.getEndianess()); } return {}; } std::optional checkForPixelFormatIndicatorInFileExtension( - const std::string &filename, const Size &frameSize, const std::optional &fileSize) + const std::string &filename, const Size &frameSize, const std::optional &fileSize) { const auto fileExtension = std::filesystem::path(filename).extension(); @@ -157,8 +160,11 @@ std::optional checkForPixelFormatIndicatorInFileExtension( if (doesPixelFormatMatchFileSize(format, frameSize, fileSize)) { const auto dataLayout = findDataLayoutInName(filename); - format.setDataLayout(dataLayout); - return format; + return PixelFormatRGB(format.getBitsPerComponent(), + dataLayout, + format.getChannelOrder(), + format.getAlphaMode(), + format.getEndianess()); } } } @@ -166,7 +172,7 @@ std::optional checkForPixelFormatIndicatorInFileExtension( } std::optional checkSpecificFileExtensions( - const std::string &filename, const Size &frameSize, const std::optional &fileSize) + const std::string &filename, const Size &frameSize, const std::optional &fileSize) { const auto fileExtension = std::filesystem::path(filename).extension(); @@ -176,6 +182,18 @@ std::optional checkSpecificFileExtensions( if (doesPixelFormatMatchFileSize(format, frameSize, fileSize)) return format; } + if (fileExtension == ".rgb565") + { + const auto format = PixelFormatRGB(PredefinedPixelFormat::RGB565); + if (doesPixelFormatMatchFileSize(format, frameSize, fileSize)) + return format; + } + if (fileExtension == ".rgb565be") + { + const auto format = PixelFormatRGB(PredefinedPixelFormat::RGB565BE); + if (doesPixelFormatMatchFileSize(format, frameSize, fileSize)) + return format; + } return {}; } @@ -197,11 +215,11 @@ PixelFormatRGB guessPixelFormatFromSizeAndName(const GuessedFrameFormat &guessed return *pixelFormat; if (const auto pixelFormat = - checkForPixelFormatIndicatorInFileExtension(filename, frameSize, fileSize)) + checkForPixelFormatIndicatorInFileExtension(filename, frameSize, fileSize)) return *pixelFormat; if (const auto pixelFormat = checkForPixelFormatIndicatorInName( - functions::toLower(fileInfo.parentFolderName), frameSize, fileSize)) + functions::toLower(fileInfo.parentFolderName), frameSize, fileSize)) return *pixelFormat; if (guessedFrameFormat.frameSize) diff --git a/YUViewLib/src/video/rgb/videoHandlerRGB.cpp b/YUViewLib/src/video/rgb/videoHandlerRGB.cpp index a7f61283f..6d6ca3ac4 100644 --- a/YUViewLib/src/video/rgb/videoHandlerRGB.cpp +++ b/YUViewLib/src/video/rgb/videoHandlerRGB.cpp @@ -51,20 +51,20 @@ namespace { constexpr EnumMapper ComponentShowMapper = { - std::make_pair(ComponentDisplayMode::RGBA, "RGBA"), - std::make_pair(ComponentDisplayMode::RGB, "RGB"), - std::make_pair(ComponentDisplayMode::R, "R"), - std::make_pair(ComponentDisplayMode::G, "G"), - std::make_pair(ComponentDisplayMode::B, "B"), - std::make_pair(ComponentDisplayMode::A, "A")}; + std::make_pair(ComponentDisplayMode::RGBA, "RGBA"), + std::make_pair(ComponentDisplayMode::RGB, "RGB"), + std::make_pair(ComponentDisplayMode::R, "R"), + std::make_pair(ComponentDisplayMode::G, "G"), + std::make_pair(ComponentDisplayMode::B, "B"), + std::make_pair(ComponentDisplayMode::A, "A")}; constexpr EnumMapper ComponentShowMapperToDisplayText = { - std::make_pair(ComponentDisplayMode::RGBA, "RGBA"), - std::make_pair(ComponentDisplayMode::RGB, "RGB"), - std::make_pair(ComponentDisplayMode::R, "Red Only"), - std::make_pair(ComponentDisplayMode::G, "Green Only"), - std::make_pair(ComponentDisplayMode::B, "Blue Only"), - std::make_pair(ComponentDisplayMode::A, "Alpha Only")}; + std::make_pair(ComponentDisplayMode::RGBA, "RGBA"), + std::make_pair(ComponentDisplayMode::RGB, "RGB"), + std::make_pair(ComponentDisplayMode::R, "Red Only"), + std::make_pair(ComponentDisplayMode::G, "Green Only"), + std::make_pair(ComponentDisplayMode::B, "Blue Only"), + std::make_pair(ComponentDisplayMode::A, "Alpha Only")}; void addConversionInformationToInfoList(QList &differenceInfoList, const int width, @@ -76,15 +76,14 @@ void addConversionInformationToInfoList(QList &differenceInfoList, const auto nrPixels = static_cast(width * height); differenceInfoList.append( - InfoItem("MSE R", std::to_string(static_cast(mseAdd[0]) / nrPixels))); + InfoItem("MSE R", std::to_string(static_cast(mseAdd[0]) / nrPixels))); differenceInfoList.append( - InfoItem("MSE G", std::to_string(static_cast(mseAdd[1]) / nrPixels))); + InfoItem("MSE G", std::to_string(static_cast(mseAdd[1]) / nrPixels))); differenceInfoList.append( - InfoItem("MSE B", std::to_string(static_cast(mseAdd[2]) / nrPixels))); + InfoItem("MSE B", std::to_string(static_cast(mseAdd[2]) / nrPixels))); - differenceInfoList.append( - InfoItem("MSE All", - std::to_string(static_cast(mseAdd[0] + mseAdd[1] + mseAdd[2]) / nrPixels))); + differenceInfoList.append(InfoItem( + "MSE All", std::to_string(static_cast(mseAdd[0] + mseAdd[1] + mseAdd[2]) / nrPixels))); } } // namespace @@ -115,12 +114,12 @@ void addConversionInformationToInfoList(QList &differenceInfoList, #endif std::vector videoHandlerRGB::formatPresetList = { - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB), - PixelFormatRGB(10, DataLayout::Packed, ChannelOrder::RGB), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::First), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG), - PixelFormatRGB(10, DataLayout::Packed, ChannelOrder::BRG), - PixelFormatRGB(10, DataLayout::Planar, ChannelOrder::RGB)}; + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB), + PixelFormatRGB(10, DataLayout::Packed, ChannelOrder::RGB), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::First), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG), + PixelFormatRGB(10, DataLayout::Packed, ChannelOrder::BRG), + PixelFormatRGB(10, DataLayout::Planar, ChannelOrder::RGB)}; videoHandlerRGB::videoHandlerRGB() : videoHandler() { @@ -260,7 +259,7 @@ QLayout *videoHandlerRGB::createVideoHandlerControls(bool isSizeFixed) ui.rgbFormatComboBox->addItem(QString::fromStdString(format.getName())); const auto currentFormatInPresetList = - vectorContains(videoHandlerRGB::formatPresetList, this->srcPixelFormat); + vectorContains(videoHandlerRGB::formatPresetList, this->srcPixelFormat); if (!currentFormatInPresetList && this->srcPixelFormat.isValid()) { videoHandlerRGB::formatPresetList.push_back(this->srcPixelFormat); @@ -270,7 +269,7 @@ QLayout *videoHandlerRGB::createVideoHandlerControls(bool isSizeFixed) ui.rgbFormatComboBox->setEnabled(!isSizeFixed); if (const auto presetIndex = - vectorIndexOf(videoHandlerRGB::formatPresetList, this->srcPixelFormat)) + vectorIndexOf(videoHandlerRGB::formatPresetList, this->srcPixelFormat)) ui.rgbFormatComboBox->setCurrentIndex(static_cast(*presetIndex)); ui.RScaleSpinBox->setValue(componentScale[0]); @@ -390,9 +389,9 @@ void videoHandlerRGB::updateControlsForNewPixelFormat() this->componentDisplayMode = ComponentDisplayMode::RGBA; ui.colorComponentsComboBox->addItems( - functions::toQStringList(ComponentShowMapperToDisplayText.getNames())); + functions::toQStringList(ComponentShowMapperToDisplayText.getNames())); ui.colorComponentsComboBox->setCurrentText(QString::fromStdString( - std::string(ComponentShowMapperToDisplayText.getName(this->componentDisplayMode)))); + std::string(ComponentShowMapperToDisplayText.getName(this->componentDisplayMode)))); } } @@ -401,7 +400,7 @@ void videoHandlerRGB::slotRGBFormatControlChanged(int selectionIndex) const auto nrBytesOldFormat = getBytesPerFrame(); const auto customFormatSelected = - (selectionIndex == static_cast(videoHandlerRGB::formatPresetList.size())); + (selectionIndex == static_cast(videoHandlerRGB::formatPresetList.size())); if (customFormatSelected) { DEBUG_RGB("videoHandlerRGB::slotRGBFormatControlChanged custom format"); @@ -411,7 +410,7 @@ void videoHandlerRGB::slotRGBFormatControlChanged(int selectionIndex) this->srcPixelFormat = dialog.getSelectedRGBFormat(); const auto isInPresetList = - vectorContains(videoHandlerRGB::formatPresetList, this->srcPixelFormat); + vectorContains(videoHandlerRGB::formatPresetList, this->srcPixelFormat); if (!isInPresetList && this->srcPixelFormat.isValid()) { videoHandlerRGB::formatPresetList.push_back(this->srcPixelFormat); @@ -422,7 +421,7 @@ void videoHandlerRGB::slotRGBFormatControlChanged(int selectionIndex) } if (const auto presetIndex = - vectorIndexOf(videoHandlerRGB::formatPresetList, this->srcPixelFormat)) + vectorIndexOf(videoHandlerRGB::formatPresetList, this->srcPixelFormat)) { const QSignalBlocker blocker(this->ui.rgbFormatComboBox); selectionIndex = static_cast(*presetIndex); @@ -623,8 +622,8 @@ void videoHandlerRGB::convertRGBToImage(const QByteArray &sourceBuffer, QImage & return; } - const auto bps = this->srcPixelFormat.getBitsPerSample(); - if (bps < 8 || bps > 32) + const auto bpc = this->srcPixelFormat.getBitsPerComponent(); + if (bpc < 8 || bpc > 32) { DEBUG_RGB("Unsupported bit depth. 8-16 bit are supported."); return; @@ -663,7 +662,7 @@ void videoHandlerRGB::convertSourceToRGBA32Bit(const QByteArray &sourceBuffer, "The source buffer does not hold enough data."); const auto outputSupportsAlpha = - imageFormat == QImage::Format_ARGB32 || imageFormat == QImage::Format_ARGB32_Premultiplied; + imageFormat == QImage::Format_ARGB32 || imageFormat == QImage::Format_ARGB32_Premultiplied; const auto premultiplyAlpha = imageFormat == QImage::Format_ARGB32_Premultiplied; const auto inputHasAlpha = srcPixelFormat.hasAlpha(); @@ -695,10 +694,10 @@ void videoHandlerRGB::convertSourceToRGBA32Bit(const QByteArray &sourceBuffer, const auto invert = this->componentInvert[displayIndex]; auto componentToChannel = std::map( - {{ComponentDisplayMode::R, rgb::Channel::Red}, - {ComponentDisplayMode::G, rgb::Channel::Green}, - {ComponentDisplayMode::B, rgb::Channel::Blue}, - {ComponentDisplayMode::A, rgb::Channel::Alpha}}); + {{ComponentDisplayMode::R, rgb::Channel::Red}, + {ComponentDisplayMode::G, rgb::Channel::Green}, + {ComponentDisplayMode::B, rgb::Channel::Blue}, + {ComponentDisplayMode::A, rgb::Channel::Alpha}}); const auto displayChannel = componentToChannel[this->componentDisplayMode]; convertSinglePlaneOfRGBToGreyscaleARGB(sourceBuffer, @@ -715,12 +714,12 @@ void videoHandlerRGB::convertSourceToRGBA32Bit(const QByteArray &sourceBuffer, rgba_t videoHandlerRGB::getPixelValue(const QPoint &pixelPos) const { return getPixelValueFromBuffer( - this->currentFrameRawData, this->srcPixelFormat, this->frameSize, pixelPos); + this->currentFrameRawData, this->srcPixelFormat, this->frameSize, pixelPos); } void videoHandlerRGB::guessAndSetPixelFormat( - const filesource::frameFormatGuess::GuessedFrameFormat &frameFormat, - const filesource::frameFormatGuess::FileInfoForGuess &fileInfo) + const filesource::frameFormatGuess::GuessedFrameFormat &frameFormat, + const filesource::frameFormatGuess::FileInfoForGuess &fileInfo) { const auto pixelFormat = guessPixelFormatFromSizeAndName(frameFormat, fileInfo); if (pixelFormat.isValid()) @@ -760,7 +759,7 @@ void videoHandlerRGB::drawPixelValues(QPainter *painter, { // The second item is not a videoHandlerRGB item FrameHandler::drawPixelValues( - painter, frameIdx, videoRect, zoomFactor, item2, markDifference, frameIdxItem1); + painter, frameIdx, videoRect, zoomFactor, item2, markDifference, frameIdxItem1); return; } @@ -779,7 +778,7 @@ void videoHandlerRGB::drawPixelValues(QPainter *painter, // This QRect has the size of one pixel and is moved on top of each pixel to draw the text QRect pixelRect; pixelRect.setSize(QSize(zoomFactor, zoomFactor)); - const unsigned drawWhitLevel = 1 << (srcPixelFormat.getBitsPerSample() - 1); + const unsigned drawWhitLevel = 1 << (srcPixelFormat.getBitsPerComponent() - 1); for (int x = xMin; x <= xMax; x++) { for (int y = yMin; y <= yMax; y++) @@ -807,8 +806,8 @@ void videoHandlerRGB::drawPixelValues(QPainter *painter, if (markDifference) painter->setPen((R == 0 && G == 0 && B == 0 && (!srcPixelFormat.hasAlpha() || A == 0)) - ? Qt::white - : Qt::black); + ? Qt::white + : Qt::black); else painter->setPen((R < 0 && G < 0 && B < 0) ? Qt::white : Qt::black); @@ -825,19 +824,19 @@ void videoHandlerRGB::drawPixelValues(QPainter *painter, rgba_t value = getPixelValue(QPoint(x, y)); if (srcPixelFormat.hasAlpha()) valText = QString("R%1\nG%2\nB%3\nA%4") - .arg(value.R, 0, formatBase) - .arg(value.G, 0, formatBase) - .arg(value.B, 0, formatBase) - .arg(value.A, 0, formatBase); + .arg(value.R, 0, formatBase) + .arg(value.G, 0, formatBase) + .arg(value.B, 0, formatBase) + .arg(value.A, 0, formatBase); else valText = QString("R%1\nG%2\nB%3") - .arg(value.R, 0, formatBase) - .arg(value.G, 0, formatBase) - .arg(value.B, 0, formatBase); + .arg(value.R, 0, formatBase) + .arg(value.G, 0, formatBase) + .arg(value.B, 0, formatBase); painter->setPen( - (value.R < drawWhitLevel && value.G < drawWhitLevel && value.B < drawWhitLevel) - ? Qt::white - : Qt::black); + (value.R < drawWhitLevel && value.G < drawWhitLevel && value.B < drawWhitLevel) + ? Qt::white + : Qt::black); } painter->drawText(pixelRect, Qt::AlignCenter, valText); @@ -856,21 +855,13 @@ QImage videoHandlerRGB::calculateDifference(FrameHandler *item2, if (rgbItem2 == nullptr) // The given item is not a RGB source. We cannot compare raw RGB values to non raw RGB values. // Call the base class comparison function to compare the items using the RGB 888 values. - return videoHandler::calculateDifference(item2, - frameIdxItem0, - frameIdxItem1, - differenceInfoList, - amplificationFactor, - markDifference); - - if (srcPixelFormat.getBitsPerSample() != rgbItem2->srcPixelFormat.getBitsPerSample()) + return videoHandler::calculateDifference( + item2, frameIdxItem0, frameIdxItem1, differenceInfoList, amplificationFactor, markDifference); + + if (srcPixelFormat.getBitsPerComponent() != rgbItem2->srcPixelFormat.getBitsPerComponent()) // The two items have different bit depths. Compare RGB 888 values instead. - return videoHandler::calculateDifference(item2, - frameIdxItem0, - frameIdxItem1, - differenceInfoList, - amplificationFactor, - markDifference); + return videoHandler::calculateDifference( + item2, frameIdxItem0, frameIdxItem1, differenceInfoList, amplificationFactor, markDifference); const int width = std::min(frameSize.width, rgbItem2->frameSize.width); const int height = std::min(frameSize.height, rgbItem2->frameSize.height); @@ -892,12 +883,12 @@ QImage videoHandlerRGB::calculateDifference(FrameHandler *item2, // (each 8 bit). auto qFrameSize = QSize(this->frameSize.width, this->frameSize.height); auto outputImage = - QImage(qFrameSize, functionsGui::platformImageFormat(this->srcPixelFormat.hasAlpha())); + QImage(qFrameSize, functionsGui::platformImageFormat(this->srcPixelFormat.hasAlpha())); // We directly write the difference values into the QImage buffer in the right format (ABGR). unsigned char *restrict dst = outputImage.bits(); - const auto bitDepth = srcPixelFormat.getBitsPerSample(); + const auto bitDepth = srcPixelFormat.getBitsPerComponent(); const auto posR = srcPixelFormat.getChannelPosition(Channel::Red); const auto posG = srcPixelFormat.getChannelPosition(Channel::Green); const auto posB = srcPixelFormat.getChannelPosition(Channel::Blue); @@ -906,7 +897,7 @@ QImage videoHandlerRGB::calculateDifference(FrameHandler *item2, { // How many values do we have to skip in src to get to the next input value? // In case of 8 or less bits this is 1 byte per value, for 9 to 16 bits it is 2 bytes per value. - int offsetToNextValue = srcPixelFormat.nrChannels(); + int offsetToNextValue = srcPixelFormat.getNrChannels(); if (srcPixelFormat.getDataLayout() == DataLayout::Planar) offsetToNextValue = 1; @@ -996,12 +987,12 @@ QImage videoHandlerRGB::calculateDifference(FrameHandler *item2, unsigned char *srcR0, *srcG0, *srcB0; if (srcPixelFormat.getDataLayout() == DataLayout::Planar) { - srcR0 = (unsigned char *)currentFrameRawData.data() + - (posR * frameSize.width * frameSize.height); - srcG0 = (unsigned char *)currentFrameRawData.data() + - (posG * frameSize.width * frameSize.height); - srcB0 = (unsigned char *)currentFrameRawData.data() + - (posB * frameSize.width * frameSize.height); + srcR0 = + (unsigned char *)currentFrameRawData.data() + (posR * frameSize.width * frameSize.height); + srcG0 = + (unsigned char *)currentFrameRawData.data() + (posG * frameSize.width * frameSize.height); + srcB0 = + (unsigned char *)currentFrameRawData.data() + (posB * frameSize.width * frameSize.height); } else { @@ -1071,7 +1062,7 @@ QImage videoHandlerRGB::calculateDifference(FrameHandler *item2, } else Q_ASSERT_X( - false, Q_FUNC_INFO, "No RGB format with less than 8 or more than 16 bits supported yet."); + false, Q_FUNC_INFO, "No RGB format with less than 8 or more than 16 bits supported yet."); } addConversionInformationToInfoList(differenceInfoList, width, height, bitDepth, mseAdd); diff --git a/YUViewLib/src/video/rgb/videoHandlerRGB.h b/YUViewLib/src/video/rgb/videoHandlerRGB.h index c4c7953ee..4bf4e4320 100644 --- a/YUViewLib/src/video/rgb/videoHandlerRGB.h +++ b/YUViewLib/src/video/rgb/videoHandlerRGB.h @@ -80,7 +80,7 @@ class videoHandlerRGB : public videoHandler // Get the number of bytes for one RGB frame with the current format virtual int64_t getBytesPerFrame() const override { - return srcPixelFormat.bytesPerFrame(frameSize); + return srcPixelFormat.getBytesPerFrame(frameSize); } // Try to guess and set the format (frameSize/srcPixelFormat) from the raw RGB data. diff --git a/YUViewLib/src/video/rgb/videoHandlerRGBCustomFormatDialog.cpp b/YUViewLib/src/video/rgb/videoHandlerRGBCustomFormatDialog.cpp index c8547e8d0..e18f8cf3f 100644 --- a/YUViewLib/src/video/rgb/videoHandlerRGBCustomFormatDialog.cpp +++ b/YUViewLib/src/video/rgb/videoHandlerRGBCustomFormatDialog.cpp @@ -38,7 +38,7 @@ namespace video::rgb { videoHandlerRGBCustomFormatDialog::videoHandlerRGBCustomFormatDialog( - const PixelFormatRGB &rgbFormat) + const PixelFormatRGB &rgbFormat) { this->ui.setupUi(this); @@ -62,7 +62,7 @@ videoHandlerRGBCustomFormatDialog::videoHandlerRGBCustomFormatDialog( this->ui.rgbOrderComboBox->setCurrentIndex(int(index)); } - auto bitDepth = rgbFormat.getBitsPerSample(); + const auto bitDepth = rgbFormat.getBitsPerComponent(); this->ui.bitDepthSpinBox->setValue(bitDepth); this->ui.comboBoxEndianness->setEnabled(bitDepth > 8); this->ui.comboBoxEndianness->setCurrentIndex(rgbFormat.getEndianess() == Endianness::Big ? 0 : 1); diff --git a/YUViewUnitTest/video/rgb/ConversionRGBTest.cpp b/YUViewUnitTest/video/rgb/ConversionRGBTest.cpp index b6a848739..22d0466b2 100644 --- a/YUViewUnitTest/video/rgb/ConversionRGBTest.cpp +++ b/YUViewUnitTest/video/rgb/ConversionRGBTest.cpp @@ -110,13 +110,13 @@ void checkOutputValues(const UChaVector &data, auto expectedValue = TEST_VALUES_12BIT.at(i); expectedValue.R = - scaleShiftClipInvertValue(expectedValue.R, bitDepth, scaling[0], inversion[0]); + scaleShiftClipInvertValue(expectedValue.R, bitDepth, scaling[0], inversion[0]); expectedValue.G = - scaleShiftClipInvertValue(expectedValue.G, bitDepth, scaling[1], inversion[1]); + scaleShiftClipInvertValue(expectedValue.G, bitDepth, scaling[1], inversion[1]); expectedValue.B = - scaleShiftClipInvertValue(expectedValue.B, bitDepth, scaling[2], inversion[2]); + scaleShiftClipInvertValue(expectedValue.B, bitDepth, scaling[2], inversion[2]); expectedValue.A = - scaleShiftClipInvertValue(expectedValue.A, bitDepth, scaling[3], inversion[3]); + scaleShiftClipInvertValue(expectedValue.A, bitDepth, scaling[3], inversion[3]); if (limitedRange) { @@ -148,16 +148,16 @@ void checkOutputValuesForPlane(const UChaVector &data, auto expectedPlaneValue = TEST_VALUES_12BIT[i].at(channel); const auto channelIndex = ChannelMapper.indexOf(channel); - const auto bitDepth = pixelFormat.getBitsPerSample(); + const auto bitDepth = pixelFormat.getBitsPerComponent(); expectedPlaneValue = scaleShiftClipInvertValue( - expectedPlaneValue, bitDepth, scaling[channelIndex], inversion[channelIndex]); + expectedPlaneValue, bitDepth, scaling[channelIndex], inversion[channelIndex]); if (limitedRange) expectedPlaneValue = LimitedRangeToFullRange.at(expectedPlaneValue); const auto expectedValue = - rgba_t({expectedPlaneValue, expectedPlaneValue, expectedPlaneValue, 255}); + rgba_t({expectedPlaneValue, expectedPlaneValue, expectedPlaneValue, 255}); const auto actualValue = getARGBValueFromDataLittleEndian(data, i); @@ -188,7 +188,7 @@ void testConversionToRGBA(const QByteArray &sourceBuffer, const auto alphaShouldBeSet = (outputHasAlpha && srcPixelFormat.hasAlpha()); checkOutputValues(outputBuffer, - srcPixelFormat.getBitsPerSample(), + srcPixelFormat.getBitsPerComponent(), componentScale, limitedRange, inversion, @@ -222,7 +222,7 @@ void testConversionToRGBASinglePlane(const QByteArray &sourceBuffer, limitedRange); checkOutputValuesForPlane( - outputBuffer, srcPixelFormat, componentScale, limitedRange, inversion, channel); + outputBuffer, srcPixelFormat, componentScale, limitedRange, inversion, channel); } } @@ -253,7 +253,7 @@ void runTestForAllParameters(TestingFunction testingFunction) for (const auto &channelOrder : video::rgb::ChannelOrderMapper.getValues()) { const video::rgb::PixelFormatRGB format( - bitDepth, dataLayout, channelOrder, alphaMode, endianness); + bitDepth, dataLayout, channelOrder, alphaMode, endianness); const auto data = createRawRGBData(format); for (const auto outputHasAlpha : {false, true}) @@ -265,8 +265,8 @@ void runTestForAllParameters(TestingFunction testingFunction) for (const auto limitedRange : {false, true}) { EXPECT_NO_THROW(testingFunction( - data, format, inversion, componentScale, limitedRange, outputHasAlpha)) - << "parametersAsString"; + data, format, inversion, componentScale, limitedRange, outputHasAlpha)) + << "parametersAsString"; } } } diff --git a/YUViewUnitTest/video/rgb/CreateTestData.cpp b/YUViewUnitTest/video/rgb/CreateTestData.cpp index e13e5ec10..ee592b061 100644 --- a/YUViewUnitTest/video/rgb/CreateTestData.cpp +++ b/YUViewUnitTest/video/rgb/CreateTestData.cpp @@ -49,8 +49,8 @@ void scaleValueToBitDepthAndPushIntoArra(QByteArray &data, data.push_back(scaledValue); else if (bitDepth <= 16) { - const auto upperByte = ((scaledValue & 0xff00) >> 8); - const auto lowerByte = (scaledValue & 0xff); + const auto upperByte = ((scaledValue & 0xff00) >> 8); + const auto lowerByte = (scaledValue & 0xff); if (endianness == Endianness::Little) { data.push_back(lowerByte); @@ -61,7 +61,9 @@ void scaleValueToBitDepthAndPushIntoArra(QByteArray &data, data.push_back(upperByte); data.push_back(lowerByte); } - } else { + } + else + { if (endianness == Endianness::Little) { data.push_back((scaledValue >> 0) & 0xFF); @@ -84,14 +86,14 @@ void scaleValueToBitDepthAndPushIntoArra(QByteArray &data, auto createRawRGBData(const PixelFormatRGB &format) -> QByteArray { QByteArray data; - const auto bitDepth = format.getBitsPerSample(); + const auto bitDepth = format.getBitsPerComponent(); const auto endianness = format.getEndianess(); if (format.getDataLayout() == DataLayout::Packed) { for (auto value : TEST_VALUES_12BIT) { - for (int channelPosition = 0; channelPosition < static_cast(format.nrChannels()); + for (int channelPosition = 0; channelPosition < static_cast(format.getNrChannels()); channelPosition++) { const auto channel = format.getChannelAtPosition(channelPosition); @@ -101,7 +103,7 @@ auto createRawRGBData(const PixelFormatRGB &format) -> QByteArray } else { - for (int channelPosition = 0; channelPosition < static_cast(format.nrChannels()); + for (int channelPosition = 0; channelPosition < static_cast(format.getNrChannels()); channelPosition++) { const auto channel = format.getChannelAtPosition(channelPosition); diff --git a/YUViewUnitTest/video/rgb/GetPixelValueTest.cpp b/YUViewUnitTest/video/rgb/GetPixelValueTest.cpp index 8082c52fb..d564afb0c 100644 --- a/YUViewUnitTest/video/rgb/GetPixelValueTest.cpp +++ b/YUViewUnitTest/video/rgb/GetPixelValueTest.cpp @@ -47,7 +47,7 @@ namespace void testGetPixelValueFromBuffer(const QByteArray &sourceBuffer, const PixelFormatRGB &srcPixelFormat) { - const auto bitDepth = srcPixelFormat.getBitsPerSample(); + const auto bitDepth = srcPixelFormat.getBitsPerComponent(); int testValueIndex = 0; for (int y : {0, 1, 2, 3}) @@ -56,7 +56,7 @@ void testGetPixelValueFromBuffer(const QByteArray &sourceBuffer, { const QPoint pixelPos(x, y); const auto actualValue = - getPixelValueFromBuffer(sourceBuffer, srcPixelFormat, TEST_FRAME_SIZE, pixelPos); + getPixelValueFromBuffer(sourceBuffer, srcPixelFormat, TEST_FRAME_SIZE, pixelPos); const auto testValue = TEST_VALUES_12BIT[testValueIndex++]; auto expectedValue = convertBitness(testValue, 12, bitDepth); @@ -85,11 +85,11 @@ TEST(GetPixelValueTest, TestGetPixelValueFromBuffer) for (const auto &channelOrder : ChannelOrderMapper.getValues()) { const PixelFormatRGB pixelFormat( - bitDepth, dataLayout, channelOrder, alphaMode, endianness); + bitDepth, dataLayout, channelOrder, alphaMode, endianness); const auto data = createRawRGBData(pixelFormat); EXPECT_NO_THROW(testGetPixelValueFromBuffer(data, pixelFormat)) - << "Failed for pixel format " << pixelFormat.getName(); + << "Failed for pixel format " << pixelFormat.getName(); } } } diff --git a/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp b/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp index 4e12b6462..1b8767a98 100644 --- a/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp +++ b/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp @@ -55,7 +55,7 @@ std::string getTestName(const testing::TestParamInfo &testParame { const auto testParameters = testParametersInfo.param; return filesource::frameFormatGuess::test::formatFileInfoForGuessForTestName( - testParameters.fileInfoForGuess) + + testParameters.fileInfoForGuess) + "_" + yuviewTest::replaceNonSupportedCharacters(testParameters.expectedPixelFormat.getName()); } @@ -65,161 +65,159 @@ TEST_P(GuessRGBFormatFromFilenameFrameSizeAndFileSize, TestGuess) const auto parameters = GetParam(); const auto guessedFrameFormat = - filesource::frameFormatGuess::guessFrameFormat(parameters.fileInfoForGuess); + filesource::frameFormatGuess::guessFrameFormat(parameters.fileInfoForGuess); const auto guessedFormat = - video::rgb::guessPixelFormatFromSizeAndName(guessedFrameFormat, parameters.fileInfoForGuess); + video::rgb::guessPixelFormatFromSizeAndName(guessedFrameFormat, parameters.fileInfoForGuess); EXPECT_EQ(guessedFormat.isValid(), parameters.expectedPixelFormat.isValid()); EXPECT_EQ(guessedFormat, parameters.expectedPixelFormat); } -constexpr auto BytesNoAlpha = 1920u * 1080 * 12u * 3u; // 12 frames RGB -constexpr auto NotEnoughBytes = 22u; -constexpr auto UnfittingBytes = 1920u * 1080u * 5u; -constexpr auto BytesBayerFile = 512u * 768u * 4u * 12u; // 12 frames raw bayer +constexpr auto BytesNoAlpha = 1920u * 1080 * 12u * 3u; // 12 frames RGB +constexpr auto NotEnoughBytes = 22u; +constexpr auto UnfittingBytes = 1920u * 1080u * 5u; +constexpr auto BytesBayerFile = 512u * 768u * 4u * 12u; // 12 frames raw bayer +constexpr auto BytesRGB565File = 512u * 768u * 2u * 12u; // 12 frames 2 bytes per pixel INSTANTIATE_TEST_SUITE_P( - VideoRGBTest, - GuessRGBFormatFromFilenameFrameSizeAndFileSize, - Values( - // Cases that should not detect anything - TestParameters({FileInfoForGuess({"noIndicatorHere.yuv", "", 0}), - PixelFormatRGB()}), - TestParameters({FileInfoForGuess({"something_1920x1080.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), - - // No Alpha - TestParameters({FileInfoForGuess({"something_1920x1080_rgb.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rbg.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RBG)}), - TestParameters({FileInfoForGuess({"something_1920x1080_grb.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GRB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_gbr.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GBR)}), - TestParameters({FileInfoForGuess({"something_1920x1080_brg.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG)}), - TestParameters({FileInfoForGuess({"something_1920x1080_bgr.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BGR)}), - - // Alpha First - TestParameters( - {FileInfoForGuess({"something_1920x1080_argb.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::First)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_arbg.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RBG, AlphaMode::First)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_agrb.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GRB, AlphaMode::First)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_agbr.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GBR, AlphaMode::First)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_abrg.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG, AlphaMode::First)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_abgr.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BGR, AlphaMode::First)}), - - // Alpha Last - TestParameters({FileInfoForGuess({"something_1920x1080_rgba.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::Last)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rbga.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RBG, AlphaMode::Last)}), - TestParameters({FileInfoForGuess({"something_1920x1080_grba.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GRB, AlphaMode::Last)}), - TestParameters({FileInfoForGuess({"something_1920x1080_gbra.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GBR, AlphaMode::Last)}), - TestParameters({FileInfoForGuess({"something_1920x1080_brga.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG, AlphaMode::Last)}), - TestParameters({FileInfoForGuess({"something_1920x1080_bgra.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BGR, AlphaMode::Last)}), - - // Bit dephts - TestParameters({FileInfoForGuess({"something_1920x1080_rgb10.yuv", "", BytesNoAlpha}), - PixelFormatRGB(10, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb12.yuv", "", BytesNoAlpha}), - PixelFormatRGB(12, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb16.yuv", "", BytesNoAlpha}), - PixelFormatRGB(16, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb48.yuv", "", BytesNoAlpha}), - PixelFormatRGB(16, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb64.yuv", "", BytesNoAlpha}), - PixelFormatRGB(16, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb32.yuv", "", BytesNoAlpha}), - PixelFormatRGB(32, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb96.yuv", "", BytesNoAlpha}), - PixelFormatRGB(32, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb128.yuv", "", BytesNoAlpha}), - PixelFormatRGB(32, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb11.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), - - // Endianness - TestParameters({FileInfoForGuess({"something_1920x1080_rgb8le.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb8be.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb10le.yuv", "", BytesNoAlpha}), - PixelFormatRGB(10, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_rgb10be.yuv", "", BytesNoAlpha}), - PixelFormatRGB( - 10, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::None, Endianness::Big)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_rgb16be.yuv", "", BytesNoAlpha}), - PixelFormatRGB( - 16, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::None, Endianness::Big)}), - - // DataLayout - TestParameters({FileInfoForGuess({"something_1920x1080_rgb_packed.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb_planar.yuv", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Planar, ChannelOrder::RGB)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_rgb10le_planar.yuv", "", BytesNoAlpha}), - PixelFormatRGB(10, DataLayout::Planar, ChannelOrder::RGB)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_rgb10be_planar.yuv", "", BytesNoAlpha}), - PixelFormatRGB( - 10, DataLayout::Planar, ChannelOrder::RGB, AlphaMode::None, Endianness::Big)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_rgb16_planar.yuv", "", BytesNoAlpha}), - PixelFormatRGB(16, DataLayout::Planar, ChannelOrder::RGB)}), - TestParameters( - {FileInfoForGuess({"something_1920x1080_rgb16be_planar.yuv", "", BytesNoAlpha}), - PixelFormatRGB( - 16, DataLayout::Planar, ChannelOrder::RGB, AlphaMode::None, Endianness::Big)}), - - // File size check - TestParameters({FileInfoForGuess({"something_1920x1080_rgb10.yuv", "", NotEnoughBytes}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb16be.yuv", "", NotEnoughBytes}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080_rgb16be.yuv", "", UnfittingBytes}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), - - // Format from file extension - TestParameters({FileInfoForGuess({"something_1920x1080.rgb", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), - TestParameters({FileInfoForGuess({"something_1920x1080.rbg", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RBG)}), - TestParameters({FileInfoForGuess({"something_1920x1080.grb", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GRB)}), - TestParameters({FileInfoForGuess({"something_1920x1080.gbr", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GBR)}), - TestParameters({FileInfoForGuess({"something_1920x1080.brg", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG)}), - TestParameters({FileInfoForGuess({"something_1920x1080.bgr", "", BytesNoAlpha}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BGR)}), - - // CMYK file - TestParameters({FileInfoForGuess({"something_512x768.cmyk", "", BytesBayerFile}), - PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::Last)}) - - ), - getTestName); + VideoRGBTest, + GuessRGBFormatFromFilenameFrameSizeAndFileSize, + Values( + // Cases that should not detect anything + TestParameters({FileInfoForGuess({"noIndicatorHere.yuv", "", 0}), PixelFormatRGB()}), + TestParameters({FileInfoForGuess({"something_1920x1080.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), + + // No Alpha + TestParameters({FileInfoForGuess({"something_1920x1080_rgb.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rbg.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RBG)}), + TestParameters({FileInfoForGuess({"something_1920x1080_grb.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GRB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_gbr.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GBR)}), + TestParameters({FileInfoForGuess({"something_1920x1080_brg.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG)}), + TestParameters({FileInfoForGuess({"something_1920x1080_bgr.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BGR)}), + + // Alpha First + TestParameters({FileInfoForGuess({"something_1920x1080_argb.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::First)}), + TestParameters({FileInfoForGuess({"something_1920x1080_arbg.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RBG, AlphaMode::First)}), + TestParameters({FileInfoForGuess({"something_1920x1080_agrb.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GRB, AlphaMode::First)}), + TestParameters({FileInfoForGuess({"something_1920x1080_agbr.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GBR, AlphaMode::First)}), + TestParameters({FileInfoForGuess({"something_1920x1080_abrg.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG, AlphaMode::First)}), + TestParameters({FileInfoForGuess({"something_1920x1080_abgr.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BGR, AlphaMode::First)}), + + // Alpha Last + TestParameters({FileInfoForGuess({"something_1920x1080_rgba.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::Last)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rbga.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RBG, AlphaMode::Last)}), + TestParameters({FileInfoForGuess({"something_1920x1080_grba.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GRB, AlphaMode::Last)}), + TestParameters({FileInfoForGuess({"something_1920x1080_gbra.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GBR, AlphaMode::Last)}), + TestParameters({FileInfoForGuess({"something_1920x1080_brga.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG, AlphaMode::Last)}), + TestParameters({FileInfoForGuess({"something_1920x1080_bgra.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BGR, AlphaMode::Last)}), + + // Bit dephts + TestParameters({FileInfoForGuess({"something_1920x1080_rgb10.yuv", "", BytesNoAlpha}), + PixelFormatRGB(10, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb12.yuv", "", BytesNoAlpha}), + PixelFormatRGB(12, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb16.yuv", "", BytesNoAlpha}), + PixelFormatRGB(16, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb48.yuv", "", BytesNoAlpha}), + PixelFormatRGB(16, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb64.yuv", "", BytesNoAlpha}), + PixelFormatRGB(16, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb32.yuv", "", BytesNoAlpha}), + PixelFormatRGB(32, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb96.yuv", "", BytesNoAlpha}), + PixelFormatRGB(32, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb128.yuv", "", BytesNoAlpha}), + PixelFormatRGB(32, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb11.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), + + // Endianness + TestParameters({FileInfoForGuess({"something_1920x1080_rgb8le.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb8be.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb10le.yuv", "", BytesNoAlpha}), + PixelFormatRGB(10, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters( + {FileInfoForGuess({"something_1920x1080_rgb10be.yuv", "", BytesNoAlpha}), + PixelFormatRGB( + 10, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::None, Endianness::Big)}), + TestParameters( + {FileInfoForGuess({"something_1920x1080_rgb16be.yuv", "", BytesNoAlpha}), + PixelFormatRGB( + 16, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::None, Endianness::Big)}), + + // DataLayout + TestParameters({FileInfoForGuess({"something_1920x1080_rgb_packed.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb_planar.yuv", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Planar, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb10le_planar.yuv", "", BytesNoAlpha}), + PixelFormatRGB(10, DataLayout::Planar, ChannelOrder::RGB)}), + TestParameters( + {FileInfoForGuess({"something_1920x1080_rgb10be_planar.yuv", "", BytesNoAlpha}), + PixelFormatRGB( + 10, DataLayout::Planar, ChannelOrder::RGB, AlphaMode::None, Endianness::Big)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb16_planar.yuv", "", BytesNoAlpha}), + PixelFormatRGB(16, DataLayout::Planar, ChannelOrder::RGB)}), + TestParameters( + {FileInfoForGuess({"something_1920x1080_rgb16be_planar.yuv", "", BytesNoAlpha}), + PixelFormatRGB( + 16, DataLayout::Planar, ChannelOrder::RGB, AlphaMode::None, Endianness::Big)}), + + // File size check + TestParameters({FileInfoForGuess({"something_1920x1080_rgb10.yuv", "", NotEnoughBytes}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb16be.yuv", "", NotEnoughBytes}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080_rgb16be.yuv", "", UnfittingBytes}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), + + // Format from file extension + TestParameters({FileInfoForGuess({"something_1920x1080.rgb", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB)}), + TestParameters({FileInfoForGuess({"something_1920x1080.rbg", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RBG)}), + TestParameters({FileInfoForGuess({"something_1920x1080.grb", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GRB)}), + TestParameters({FileInfoForGuess({"something_1920x1080.gbr", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::GBR)}), + TestParameters({FileInfoForGuess({"something_1920x1080.brg", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG)}), + TestParameters({FileInfoForGuess({"something_1920x1080.bgr", "", BytesNoAlpha}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BGR)}), + + // CMYK file + TestParameters({FileInfoForGuess({"something_512x768.cmyk", "", BytesBayerFile}), + PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::Last)}), + + // RGB565 + TestParameters({FileInfoForGuess({"something_512x768.rgb565", "", BytesRGB565File}), + PixelFormatRGB(PredefinedPixelFormat::RGB565)}), + TestParameters({FileInfoForGuess({"something_512x768.rgb565be", "", BytesRGB565File}), + PixelFormatRGB(PredefinedPixelFormat::RGB565BE)}) + + ), + getTestName); } // namespace video::rgb::test diff --git a/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp b/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp index a815e6045..ac3186193 100644 --- a/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp +++ b/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp @@ -52,6 +52,9 @@ std::vector getAllFormats() allFormats.push_back( PixelFormatRGB(bitsPerPixel, dataLayout, channelOrder, alphaMode, endianness)); + allFormats.push_back(PixelFormatRGB(PredefinedPixelFormat::RGB565)); + allFormats.push_back(PixelFormatRGB(PredefinedPixelFormat::RGB565BE)); + return allFormats; } @@ -77,18 +80,18 @@ TEST(PixelFormatRGBTest, testFormatFromToString) << "Format " << name << " channel position B missmatch"; EXPECT_EQ(fmt.getChannelPosition(Channel::Alpha), fmtNew.getChannelPosition(Channel::Alpha)) << "Format " << name << " channel position A missmatch"; - EXPECT_EQ(fmt.getBitsPerSample(), fmtNew.getBitsPerSample()) + EXPECT_EQ(fmt.getBitsPerComponent(), fmtNew.getBitsPerComponent()) << "Format " << name << " bits per sample missmatch"; EXPECT_EQ(fmt.getDataLayout(), fmtNew.getDataLayout()) << "Format " << name << " data layout missmatch"; if (fmt.hasAlpha()) { - EXPECT_EQ(fmt.nrChannels(), 4u) << "Format " << name << " alpha channel indication wrong. "; + EXPECT_EQ(fmt.getNrChannels(), 4) << "Format " << name << " alpha channel indication wrong. "; } else { - EXPECT_EQ(fmt.nrChannels(), 3u) << "Format " << name << " alpha channel indication wrong. "; + EXPECT_EQ(fmt.getNrChannels(), 3) << "Format " << name << " alpha channel indication wrong. "; } } } From 0a9f3975341845b45eff46dd6b6207a936d8715f Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Wed, 11 Jun 2025 16:47:36 +0300 Subject: [PATCH 02/37] A bit more testing for the PixelFormatRGB --- YUViewLib/src/video/rgb/PixelFormatRGB.cpp | 43 ++++++++- YUViewLib/src/video/rgb/PixelFormatRGB.h | 8 +- .../video/rgb/PixelFormatRGBTest.cpp | 93 +++++++++++++++++-- 3 files changed, 128 insertions(+), 16 deletions(-) diff --git a/YUViewLib/src/video/rgb/PixelFormatRGB.cpp b/YUViewLib/src/video/rgb/PixelFormatRGB.cpp index 59576f6f1..124df4571 100644 --- a/YUViewLib/src/video/rgb/PixelFormatRGB.cpp +++ b/YUViewLib/src/video/rgb/PixelFormatRGB.cpp @@ -44,6 +44,8 @@ namespace video::rgb { +constexpr auto UNKNOWN_FORMAT_NAME = "Unknown Pixel Format"; + PixelFormatRGB::PixelFormatRGB(const int bitsPerComponent, const DataLayout dataLayout, const ChannelOrder channelOrder, @@ -56,7 +58,7 @@ PixelFormatRGB::PixelFormatRGB(const int bitsPerComponent, PixelFormatRGB::PixelFormatRGB(const std::string &name) { - if (name == "Unknown Pixel Format") + if (name == UNKNOWN_FORMAT_NAME) return; if (name.substr(0, 8) == "RGB565BE") @@ -104,6 +106,9 @@ bool PixelFormatRGB::isValid() const if (this->predefinedPixelFormat) return true; + if (this->bitsPerComponent == 8 && this->endianness == Endianness::Big) + return false; + return this->bitsPerComponent >= 8 && this->bitsPerComponent <= 32; } @@ -115,7 +120,7 @@ bool PixelFormatRGB::hasAlpha() const std::string PixelFormatRGB::getName() const { if (!this->isValid()) - return "Unknown Pixel Format"; + return UNKNOWN_FORMAT_NAME; if (this->predefinedPixelFormat == PredefinedPixelFormat::RGB565) return "RGB565"; @@ -298,6 +303,40 @@ Channel PixelFormatRGB::getChannelAtPosition(int position) const throw std::invalid_argument("Invalid argument for channel position"); } +bool PixelFormatRGB::operator==(const PixelFormatRGB &a) const +{ + if (!this->isValid() || !a.isValid()) + return false; + + if (this->predefinedPixelFormat) + return this->predefinedPixelFormat == a.predefinedPixelFormat; + + return this->bitsPerComponent == a.bitsPerComponent && this->dataLayout == a.dataLayout && + this->channelOrder == a.channelOrder && this->alphaMode == a.alphaMode && + this->endianness == a.endianness; +} + +bool PixelFormatRGB::operator!=(const PixelFormatRGB &a) const +{ + return !(*this == a); +} + +bool PixelFormatRGB::operator==(const std::string &a) const +{ + if (!this->isValid() || a == UNKNOWN_FORMAT_NAME) + return false; + + return this->getName() == a; +} + +bool PixelFormatRGB::operator!=(const std::string &a) const +{ + if (!this->isValid() || a == UNKNOWN_FORMAT_NAME) + return true; + + return this->getName() != a; +} + void PrintTo(const PixelFormatRGB &pixelFormatRGB, std::ostream *os) { *os << pixelFormatRGB.getName(); diff --git a/YUViewLib/src/video/rgb/PixelFormatRGB.h b/YUViewLib/src/video/rgb/PixelFormatRGB.h index 76a189600..423ed058f 100644 --- a/YUViewLib/src/video/rgb/PixelFormatRGB.h +++ b/YUViewLib/src/video/rgb/PixelFormatRGB.h @@ -177,10 +177,10 @@ class PixelFormatRGB [[nodiscard]] int getChannelPosition(const Channel channel) const; [[nodiscard]] Channel getChannelAtPosition(const int position) const; - bool operator==(const PixelFormatRGB &a) const { return getName() == a.getName(); } - bool operator!=(const PixelFormatRGB &a) const { return getName() != a.getName(); } - bool operator==(const std::string &a) const { return getName() == a; } - bool operator!=(const std::string &a) const { return getName() != a; } + bool operator==(const PixelFormatRGB &a) const; + bool operator!=(const PixelFormatRGB &a) const; + bool operator==(const std::string &a) const; + bool operator!=(const std::string &a) const; private: // If this is set, the format is defined according to a specific standard and does not diff --git a/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp b/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp index ac3186193..ffe5d193d 100644 --- a/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp +++ b/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp @@ -40,7 +40,7 @@ namespace video::rgb::test namespace { -std::vector getAllFormats() +std::vector getAllValidFormats() { std::vector allFormats; @@ -49,8 +49,13 @@ std::vector getAllFormats() for (auto channelOrder : ChannelOrderMapper.getValues()) for (auto alphaMode : AlphaModeMapper.getValues()) for (auto endianness : EndianessMapper.getValues()) + { + if (endianness == Endianness::Big && bitsPerPixel == 8) + continue; + allFormats.push_back( PixelFormatRGB(bitsPerPixel, dataLayout, channelOrder, alphaMode, endianness)); + } allFormats.push_back(PixelFormatRGB(PredefinedPixelFormat::RGB565)); allFormats.push_back(PixelFormatRGB(PredefinedPixelFormat::RGB565BE)); @@ -58,11 +63,34 @@ std::vector getAllFormats() return allFormats; } +std::vector getInvalidFormats() +{ + std::vector invalidFormats; + + invalidFormats.push_back(PixelFormatRGB()); + + // If the bitrate is < 8 or > 32, the format is invalid. We can not add all cases to the list + // though. + invalidFormats.push_back(PixelFormatRGB(0, video::DataLayout::Packed, ChannelOrder::RGB)); + invalidFormats.push_back(PixelFormatRGB(1, video::DataLayout::Packed, ChannelOrder::RGB)); + invalidFormats.push_back(PixelFormatRGB(7, video::DataLayout::Packed, ChannelOrder::RGB)); + invalidFormats.push_back(PixelFormatRGB(33, video::DataLayout::Packed, ChannelOrder::RGB)); + invalidFormats.push_back(PixelFormatRGB(200, video::DataLayout::Packed, ChannelOrder::RGB)); + + for (auto dataLayout : DataLayoutMapper.getValues()) + for (auto channelOrder : ChannelOrderMapper.getValues()) + for (auto alphaMode : AlphaModeMapper.getValues()) + invalidFormats.push_back( + PixelFormatRGB(8, dataLayout, channelOrder, alphaMode, Endianness::Big)); + + return invalidFormats; +} + } // namespace TEST(PixelFormatRGBTest, testFormatFromToString) { - for (auto fmt : getAllFormats()) + for (auto fmt : getAllValidFormats()) { const auto name = fmt.getName(); EXPECT_TRUE(fmt.isValid()) << "Format " << name << " is invalid."; @@ -98,15 +126,60 @@ TEST(PixelFormatRGBTest, testFormatFromToString) TEST(PixelFormatRGBTest, testInvalidFormats) { - std::vector invalidFormats; - invalidFormats.push_back(PixelFormatRGB(0, video::DataLayout::Packed, ChannelOrder::RGB)); - invalidFormats.push_back(PixelFormatRGB(1, video::DataLayout::Packed, ChannelOrder::RGB)); - invalidFormats.push_back(PixelFormatRGB(7, video::DataLayout::Packed, ChannelOrder::RGB)); - invalidFormats.push_back(PixelFormatRGB(33, video::DataLayout::Packed, ChannelOrder::RGB)); - invalidFormats.push_back(PixelFormatRGB(200, video::DataLayout::Packed, ChannelOrder::RGB)); + for (const auto &format : getInvalidFormats()) + EXPECT_FALSE(format.isValid()) << "Format " << format.getName() << " should be invalid."; +} + +TEST(PixelFormatRGBTest, testComparisonOperatorsForValidFormat) +{ + const auto allValidFormats = getAllValidFormats(); + + for (size_t i = 0; i < allValidFormats.size(); ++i) + for (size_t j = 0; j < allValidFormats.size(); ++j) + { + const auto shouldBeEqual = (i == j); + if (shouldBeEqual) + { + EXPECT_TRUE(allValidFormats.at(i) == allValidFormats.at(j)); + EXPECT_FALSE(allValidFormats.at(i) != allValidFormats.at(j)); + EXPECT_TRUE(allValidFormats.at(i) == allValidFormats.at(j).getName()); + EXPECT_FALSE(allValidFormats.at(i) != allValidFormats.at(j).getName()); + } + else + { + EXPECT_FALSE(allValidFormats.at(i) == allValidFormats.at(j)); + EXPECT_TRUE(allValidFormats.at(i) != allValidFormats.at(j)); + EXPECT_FALSE(allValidFormats.at(i) == allValidFormats.at(j).getName()); + EXPECT_TRUE(allValidFormats.at(i) != allValidFormats.at(j).getName()); + } + } +} + +TEST(PixelFormatRGBTest, testComparisonOperators_ComparingToInvalidFormat_shouldAlwaysBeUnequal) +{ + const PixelFormatRGB invalidFormat; + + for (const auto &format : getAllValidFormats()) + { + EXPECT_FALSE(format == invalidFormat); + EXPECT_TRUE(format != invalidFormat); + EXPECT_FALSE(format == invalidFormat.getName()); + EXPECT_TRUE(format != invalidFormat.getName()); + } +} - for (auto fmt : invalidFormats) - EXPECT_FALSE(fmt.isValid()) << "Format " << fmt.getName() << " should be invalid."; +TEST(PixelFormatRGBTest, testComparisonOperators_ComparingTwoInvalidFormats_shouldAlwaysBeUnequal) +{ + const auto invalidFormats = getInvalidFormats(); + + for (size_t i = 0; i < invalidFormats.size(); ++i) + for (size_t j = 0; j < invalidFormats.size(); ++j) + { + EXPECT_FALSE(invalidFormats.at(i) == invalidFormats.at(j)); + EXPECT_TRUE(invalidFormats.at(i) != invalidFormats.at(j)); + EXPECT_FALSE(invalidFormats.at(i) == invalidFormats.at(j).getName()); + EXPECT_TRUE(invalidFormats.at(i) != invalidFormats.at(j).getName()); + } } } // namespace video::rgb::test From f9bc50c4e0dcd796f31c4c83bb5da7b16134a74b Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Thu, 12 Jun 2025 10:51:03 +0300 Subject: [PATCH 03/37] Basic rendering of RGB565 values works. --- .../src/playlistitem/playlistItemRawFile.cpp | 24 ++-- YUViewLib/src/video/PixelFormat.h | 6 + YUViewLib/src/video/rgb/ConversionRGB.cpp | 125 ++++++++++++++++-- YUViewLib/src/video/rgb/PixelFormatRGB.cpp | 44 ++++-- YUViewLib/src/video/rgb/PixelFormatRGB.h | 13 +- YUViewLib/src/video/rgb/videoHandlerRGB.cpp | 40 +++--- YUViewLib/src/video/yuv/PixelFormatYUV.cpp | 28 ++-- 7 files changed, 205 insertions(+), 75 deletions(-) diff --git a/YUViewLib/src/playlistitem/playlistItemRawFile.cpp b/YUViewLib/src/playlistitem/playlistItemRawFile.cpp index 0cc974027..ed9092fbd 100644 --- a/YUViewLib/src/playlistitem/playlistItemRawFile.cpp +++ b/YUViewLib/src/playlistitem/playlistItemRawFile.cpp @@ -55,7 +55,7 @@ namespace { constexpr auto YUV_EXTENSIONS = {"yuv", "nv12", "y4m"}; -constexpr auto RGB_EXTENSIONS = {"rgb", "gbr", "bgr", "brg"}; +constexpr auto RGB_EXTENSIONS = {"rgb", "gbr", "bgr", "brg", "rgb565", "rgb565be"}; constexpr auto RGBA_EXTENSIONS = {"rgba", "gbra", "bgra", "brga", "argb", "agbr", "abgr", "abrg"}; constexpr auto RAW_BAYER_EXTENSIONS = {"raw"}; constexpr auto CMYK_EXTENSIONS = {"cmyk"}; @@ -63,9 +63,9 @@ constexpr auto CMYK_EXTENSIONS = {"cmyk"}; bool isInExtensions(const QString &testValue, const std::initializer_list &extensions) { const auto it = - std::find_if(extensions.begin(), - extensions.end(), - [testValue](const char *extension) { return QString(extension) == testValue; }); + std::find_if(extensions.begin(), + extensions.end(), + [testValue](const char *extension) { return QString(extension) == testValue; }); return it != extensions.end(); } @@ -206,7 +206,7 @@ InfoData playlistItemRawFile::getInfo() const info.items.append(infoItem); const auto nrFrames = - (this->properties().startEndRange.second - this->properties().startEndRange.first + 1); + (this->properties().startEndRange.second - this->properties().startEndRange.first + 1); info.items.append(InfoItem("Num Frames", std::to_string(nrFrames))); info.items.append(InfoItem("Bytes per Frame", std::to_string(this->video->getBytesPerFrame()))); @@ -221,7 +221,7 @@ InfoData playlistItemRawFile::getInfo() const { if ((*fileSize % bpf) != 0) info.items.append(InfoItem( - "Warning"sv, "The file size and the given video size and/or raw format do not match.")); + "Warning"sv, "The file size and the given video size and/or raw format do not match.")); } else info.items.append(InfoItem("Warning"sv, "Could not obtain file size from input.")); @@ -251,7 +251,7 @@ bool playlistItemRawFile::parseY4MFile() unsigned width = 0; unsigned height = 0; auto format = - video::yuv::PixelFormatYUV(video::yuv::Subsampling::YUV_420, 8, video::yuv::PlaneOrder::YUV); + video::yuv::PixelFormatYUV(video::yuv::Subsampling::YUV_420, 8, video::yuv::PlaneOrder::YUV); while (rawData.at(offset++) == ' ') { @@ -389,7 +389,7 @@ bool playlistItemRawFile::parseY4MFile() if (width == 0 || height == 0) return setError( - "Error parsing the Y4M header: The size could not be obtained from the header."); + "Error parsing the Y4M header: The size could not be obtained from the header."); // Next, all frames should follow. Each frame starts with the sequence 'FRAME', followed by a set // of paramters for the frame. The 'FRAME' indicator is terminated by a 0x0A. The list of @@ -452,7 +452,7 @@ bool playlistItemRawFile::parseY4MFile() void playlistItemRawFile::setFormatFromFileName() { const auto fileInfoForGuess = filesource::frameFormatGuess::getFileInfoForGuessFromPath( - this->dataSource.getAbsoluteFilePath()); + this->dataSource.getAbsoluteFilePath()); const auto frameFormat = filesource::frameFormatGuess::guessFrameFormat(fileInfoForGuess); @@ -496,7 +496,7 @@ void playlistItemRawFile::savePlaylist(QDomElement &root, const QDir &playlistDi QUrl fileURL(QString::fromStdString(dataSource.getAbsoluteFilePath())); fileURL.setScheme("file"); auto relativePath = - playlistDir.relativeFilePath(QString::fromStdString(dataSource.getAbsoluteFilePath())); + playlistDir.relativeFilePath(QString::fromStdString(dataSource.getAbsoluteFilePath())); auto d = YUViewDomElement(root.ownerDocument().createElement("playlistItemRawFile")); @@ -523,7 +523,7 @@ playlistItemRawFile *playlistItemRawFile::newplaylistItemRawFile(const YUViewDom // check if file with absolute path exists, otherwise check relative path const auto filePath = - functions::getAbsPathFromAbsAndRel(playlistFilePath, absolutePath, relativePath); + functions::getAbsPathFromAbsAndRel(playlistFilePath, absolutePath, relativePath); if (filePath.isEmpty()) return nullptr; @@ -583,7 +583,7 @@ void playlistItemRawFile::getSupportedFileExtensions(QStringList &allExtensions, allExtensions.append(QString(extension)); filters.append("Raw YUV File (*.yuv *.nv21)"); - filters.append("Raw RGB File (*.rgb *.rbg *.grb *.gbr *.brg *.bgr)"); + filters.append("Raw RGB File (*.rgb *.rbg *.grb *.gbr *.brg *.bgr, *.rgb565, *.rgb565be)"); filters.append("Raw RGBA File (*.rgba *.rbga *.grba *.gbra *.brga *.bgra *.argb *.arbg *.agrb " "*.agbr *.abrg *.abgr)"); filters.append("YUV4MPEG2 File (*.y4m)"); diff --git a/YUViewLib/src/video/PixelFormat.h b/YUViewLib/src/video/PixelFormat.h index 2997ee1a9..6533f914a 100644 --- a/YUViewLib/src/video/PixelFormat.h +++ b/YUViewLib/src/video/PixelFormat.h @@ -63,6 +63,12 @@ enum class DataLayout constexpr EnumMapper DataLayoutMapper = { std::make_pair(DataLayout::Packed, "Packed"), std::make_pair(DataLayout::Planar, "Planar")}; +enum class TextRendering +{ + White, + Black +}; + } // namespace video Q_DECLARE_METATYPE(video::DataLayout); diff --git a/YUViewLib/src/video/rgb/ConversionRGB.cpp b/YUViewLib/src/video/rgb/ConversionRGB.cpp index e1e160d26..62d8224d4 100644 --- a/YUViewLib/src/video/rgb/ConversionRGB.cpp +++ b/YUViewLib/src/video/rgb/ConversionRGB.cpp @@ -67,6 +67,77 @@ int getOffsetToFirstByteOfComponent(const Channel channel, return offset; } +using RGBTuple = std::tuple; +RGBTuple extractRGB565Value(const unsigned char *data, const Endianness endianess) +{ + int byte1 = *data; + int byte2 = *(data + 1); + + if (endianess == Endianness::Big) + std::swap(byte1, byte2); + + const auto value = byte1 + (byte2 << 8); + + int r = ((value & 0b00000000'00011111) << 3); + int g = ((value & 0b00000111'11100000) >> 3); + int b = ((value & 0b11111000'00000000) >> 8); + + return {r, g, b}; +} + +void convertRGB565ToARGB(const QByteArray &sourceBuffer, + const PixelFormatRGB &srcPixelFormat, + unsigned char *targetBuffer, + const Size frameSize, + const bool componentInvert[4], + const int componentScale[4], + const bool limitedRange) +{ + auto rawData = reinterpret_cast(sourceBuffer.data()); + + const auto endianness = srcPixelFormat.getEndianess(); + for (unsigned i = 0; i < frameSize.width * frameSize.height; i++) + { + int byte1 = *rawData; + int byte2 = *(rawData + 1); + + if (endianness == Endianness::Big) + std::swap(byte1, byte2); + + const auto value = byte1 + (byte2 << 8); + + int r = ((value & 0b00000000'00011111) << 3); + int g = ((value & 0b00000111'11100000) >> 3); + int b = ((value & 0b11111000'00000000) >> 8); + + r = functions::clip(r * componentScale[0], 0, 255); + g = functions::clip(g * componentScale[1], 0, 255); + b = functions::clip(b * componentScale[2], 0, 255); + + if (componentInvert[0]) + r = (255 - r); + if (componentInvert[1]) + g = (255 - g); + if (componentInvert[2]) + b = (255 - b); + + if (limitedRange) + { + r = LimitedRangeToFullRange.at(r); + g = LimitedRangeToFullRange.at(g); + b = LimitedRangeToFullRange.at(b); + } + + targetBuffer[0] = r; + targetBuffer[1] = g; + targetBuffer[2] = b; + targetBuffer[3] = 255; + + rawData += 2; + targetBuffer += 4; + } +} + // Convert the input format to the output RGBA format. Apply inversion, scaling, // limited range conversion and alpha multiplication. The input can be any supported // format. The output is always 8 bit ARGB little endian. @@ -103,10 +174,10 @@ void convertRGBToARGB(const QByteArray &sourceBuffer, srcA = ((InValueType)sourceBuffer.data()) + offsetA; } + const auto isBigEndian = bitDepth > 8 && srcPixelFormat.getEndianess() == Endianness::Big; for (unsigned i = 0; i < frameSize.width * frameSize.height; i++) { - const auto isBigEndian = bitDepth > 8 && srcPixelFormat.getEndianess() == Endianness::Big; - auto convertValue = + auto convertValue = [&isBigEndian, &rightShift](const InValueType sourceData, const int scale, const bool invert) { auto value = static_cast(sourceData[0]); @@ -205,6 +276,30 @@ void convertRGBPlaneToARGB(const QByteArray &sourceBuffer, } } +rgba_t getPixelValueForPredefiendFormat(const QByteArray &sourceBuffer, + const PixelFormatRGB &srcPixelFormat, + const Size frameSize, + const QPoint &pixelPos) +{ + const auto offsetPixelPos = frameSize.width * pixelPos.y() + pixelPos.x(); + const auto rawData = + reinterpret_cast(sourceBuffer.data() + offsetPixelPos * 2); + + int byte1 = *rawData; + int byte2 = *(rawData + 1); + + if (srcPixelFormat.getEndianess() == Endianness::Big) + std::swap(byte1, byte2); + + const auto value = byte1 + (byte2 << 8); + + int r = ((value & 0b00000000'00011111)); + int g = ((value & 0b00000111'11100000) >> 5); + int b = ((value & 0b11111000'00000000) >> 11); + + return {static_cast(r), static_cast(g), static_cast(b), 255}; +} + template rgba_t getPixelValue(const QByteArray &sourceBuffer, const PixelFormatRGB &srcPixelFormat, @@ -251,10 +346,16 @@ void convertInputRGBToARGB(const QByteArray &sourceBuffer, const bool premultiplyAlpha) { const auto bitsPerComponent = srcPixelFormat.getBitsPerComponent(); - if (bitsPerComponent < 8 || bitsPerComponent > 32) - throw std::invalid_argument("Invalid bit depth in pixel format for conversion"); - if (bitsPerComponent == 8) + if (srcPixelFormat.getPredefinedPixelFormat()) + convertRGB565ToARGB(sourceBuffer, + srcPixelFormat, + targetBuffer, + frameSize, + componentInvert, + componentScale, + limitedRange); + else if (bitsPerComponent == 8) convertRGBToARGB<8>(sourceBuffer, srcPixelFormat, targetBuffer, @@ -274,7 +375,7 @@ void convertInputRGBToARGB(const QByteArray &sourceBuffer, limitedRange, outputHasAlpha, premultiplyAlpha); - else + else if (bitsPerComponent <= 32) convertRGBToARGB<32>(sourceBuffer, srcPixelFormat, targetBuffer, @@ -284,6 +385,8 @@ void convertInputRGBToARGB(const QByteArray &sourceBuffer, limitedRange, outputHasAlpha, premultiplyAlpha); + else + throw std::invalid_argument("Unable to perform conversion"); } void convertSinglePlaneOfRGBToGreyscaleARGB(const QByteArray &sourceBuffer, @@ -334,15 +437,17 @@ rgba_t getPixelValueFromBuffer(const QByteArray &sourceBuffer, const QPoint &pixelPos) { const auto bitsPerComponent = srcPixelFormat.getBitsPerComponent(); - if (bitsPerComponent < 8 || bitsPerComponent > 32) - throw std::invalid_argument("Invalid bit depth in pixel format for conversion"); - if (bitsPerComponent == 8) + if (srcPixelFormat.getPredefinedPixelFormat()) + return getPixelValueForPredefiendFormat(sourceBuffer, srcPixelFormat, frameSize, pixelPos); + else if (bitsPerComponent == 8) return getPixelValue<8>(sourceBuffer, srcPixelFormat, frameSize, pixelPos); else if (bitsPerComponent <= 16) return getPixelValue<16>(sourceBuffer, srcPixelFormat, frameSize, pixelPos); - else + else if (bitsPerComponent <= 32) return getPixelValue<32>(sourceBuffer, srcPixelFormat, frameSize, pixelPos); + + throw std::invalid_argument("Unable to perform conversion"); } } // namespace video::rgb diff --git a/YUViewLib/src/video/rgb/PixelFormatRGB.cpp b/YUViewLib/src/video/rgb/PixelFormatRGB.cpp index 124df4571..aef1ea08d 100644 --- a/YUViewLib/src/video/rgb/PixelFormatRGB.cpp +++ b/YUViewLib/src/video/rgb/PixelFormatRGB.cpp @@ -61,16 +61,12 @@ PixelFormatRGB::PixelFormatRGB(const std::string &name) if (name == UNKNOWN_FORMAT_NAME) return; - if (name.substr(0, 8) == "RGB565BE") - { - this->predefinedPixelFormat = PredefinedPixelFormat::RGB565BE; - return; - } - if (name.substr(0, 6) == "RGB565") - { - this->predefinedPixelFormat = PredefinedPixelFormat::RGB565; - return; - } + for (const auto predefinedFormat : PredefinedPixelFormatMapper) + if (name == predefinedFormat.second) + { + this->predefinedPixelFormat = predefinedFormat.first; + return; + } auto channelOrderString = name.substr(0, 3); if (name[0] == 'a' || name[0] == 'A') @@ -122,10 +118,8 @@ std::string PixelFormatRGB::getName() const if (!this->isValid()) return UNKNOWN_FORMAT_NAME; - if (this->predefinedPixelFormat == PredefinedPixelFormat::RGB565) - return "RGB565"; - if (this->predefinedPixelFormat == PredefinedPixelFormat::RGB565BE) - return "RGB565BE"; + if (this->predefinedPixelFormat) + return std::string(PredefinedPixelFormatMapper.getName(*this->predefinedPixelFormat)); std::string name; if (this->alphaMode == AlphaMode::First) @@ -303,6 +297,28 @@ Channel PixelFormatRGB::getChannelAtPosition(int position) const throw std::invalid_argument("Invalid argument for channel position"); } +TextRendering PixelFormatRGB::getPixelValueTextRendering(rgba_t value) const +{ + // Shift the values to 8 bit + if (this->predefinedPixelFormat) + { + value.R = (value.R << 3); + value.G = (value.G << 2); + value.B = (value.B << 3); + } + else if (this->bitsPerComponent > 8) + { + const auto shift = (this->bitsPerComponent - 8); + value.R = (value.R >> shift); + value.G = (value.G >> shift); + value.B = (value.B >> shift); + } + + // Approximation of Y = 0.375 R + 0.5 G + 0.125 B to be closer to the percieved brightness. + const auto luminance = (3 * value.R + 4 * value.B + value.B) >> 3; + return luminance < 128 ? TextRendering::White : TextRendering::Black; +} + bool PixelFormatRGB::operator==(const PixelFormatRGB &a) const { if (!this->isValid() || !a.isValid()) diff --git a/YUViewLib/src/video/rgb/PixelFormatRGB.h b/YUViewLib/src/video/rgb/PixelFormatRGB.h index 423ed058f..2716ada88 100644 --- a/YUViewLib/src/video/rgb/PixelFormatRGB.h +++ b/YUViewLib/src/video/rgb/PixelFormatRGB.h @@ -119,6 +119,10 @@ enum class PredefinedPixelFormat RGB565BE, // 16 bits packed as R:5, G:6, B:5 Big Endian }; +constexpr EnumMapper PredefinedPixelFormatMapper = { + std::make_pair(PredefinedPixelFormat::RGB565, "RGB565"), + std::make_pair(PredefinedPixelFormat::RGB565BE, "RGB565BE")}; + enum class ChannelOrder { RGB, @@ -172,10 +176,11 @@ class PixelFormatRGB [[nodiscard]] Endianness getEndianess() const; [[nodiscard]] std::optional getPredefinedPixelFormat() const; - [[nodiscard]] int getNrChannels() const; - [[nodiscard]] int getBytesPerFrame(const Size frameSize) const; - [[nodiscard]] int getChannelPosition(const Channel channel) const; - [[nodiscard]] Channel getChannelAtPosition(const int position) const; + [[nodiscard]] int getNrChannels() const; + [[nodiscard]] int getBytesPerFrame(const Size frameSize) const; + [[nodiscard]] int getChannelPosition(const Channel channel) const; + [[nodiscard]] Channel getChannelAtPosition(const int position) const; + [[nodiscard]] TextRendering getPixelValueTextRendering(rgba_t value) const; bool operator==(const PixelFormatRGB &a) const; bool operator!=(const PixelFormatRGB &a) const; diff --git a/YUViewLib/src/video/rgb/videoHandlerRGB.cpp b/YUViewLib/src/video/rgb/videoHandlerRGB.cpp index 6d6ca3ac4..de2b4703a 100644 --- a/YUViewLib/src/video/rgb/videoHandlerRGB.cpp +++ b/YUViewLib/src/video/rgb/videoHandlerRGB.cpp @@ -119,7 +119,9 @@ std::vector videoHandlerRGB::formatPresetList = { PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::First), PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::BRG), PixelFormatRGB(10, DataLayout::Packed, ChannelOrder::BRG), - PixelFormatRGB(10, DataLayout::Planar, ChannelOrder::RGB)}; + PixelFormatRGB(10, DataLayout::Planar, ChannelOrder::RGB), + PixelFormatRGB(PredefinedPixelFormat::RGB565), + PixelFormatRGB(PredefinedPixelFormat::RGB565BE)}; videoHandlerRGB::videoHandlerRGB() : videoHandler() { @@ -622,10 +624,9 @@ void videoHandlerRGB::convertRGBToImage(const QByteArray &sourceBuffer, QImage & return; } - const auto bpc = this->srcPixelFormat.getBitsPerComponent(); - if (bpc < 8 || bpc > 32) + if (!srcPixelFormat.isValid()) { - DEBUG_RGB("Unsupported bit depth. 8-16 bit are supported."); + DEBUG_RGB("Invalid RGB pixel format"); return; } @@ -778,7 +779,7 @@ void videoHandlerRGB::drawPixelValues(QPainter *painter, // This QRect has the size of one pixel and is moved on top of each pixel to draw the text QRect pixelRect; pixelRect.setSize(QSize(zoomFactor, zoomFactor)); - const unsigned drawWhitLevel = 1 << (srcPixelFormat.getBitsPerComponent() - 1); + for (int x = xMin; x <= xMax; x++) { for (int y = yMin; y <= yMax; y++) @@ -796,32 +797,32 @@ void videoHandlerRGB::drawPixelValues(QPainter *painter, rgba_t valueThis = getPixelValue(QPoint(x, y)); rgba_t valueOther = rgbItem2->getPixelValue(QPoint(x, y)); - const int R = int(valueThis.R) - int(valueOther.R); - const int G = int(valueThis.G) - int(valueOther.G); - const int B = int(valueThis.B) - int(valueOther.B); - const int A = int(valueThis.A) - int(valueOther.A); - const QString RString = ((R < 0) ? "-" : "") + QString::number(std::abs(R), formatBase); - const QString GString = ((G < 0) ? "-" : "") + QString::number(std::abs(G), formatBase); - const QString BString = ((B < 0) ? "-" : "") + QString::number(std::abs(B), formatBase); + const int r = int(valueThis.R) - int(valueOther.R); + const int g = int(valueThis.G) - int(valueOther.G); + const int b = int(valueThis.B) - int(valueOther.B); + const int a = int(valueThis.A) - int(valueOther.A); + const QString rString = ((r < 0) ? "-" : "") + QString::number(std::abs(r), formatBase); + const QString gString = ((g < 0) ? "-" : "") + QString::number(std::abs(g), formatBase); + const QString bString = ((b < 0) ? "-" : "") + QString::number(std::abs(b), formatBase); if (markDifference) - painter->setPen((R == 0 && G == 0 && B == 0 && (!srcPixelFormat.hasAlpha() || A == 0)) + painter->setPen((r == 0 && g == 0 && b == 0 && (!srcPixelFormat.hasAlpha() || a == 0)) ? Qt::white : Qt::black); else - painter->setPen((R < 0 && G < 0 && B < 0) ? Qt::white : Qt::black); + painter->setPen((r < 0 && g < 0 && b < 0) ? Qt::white : Qt::black); if (srcPixelFormat.hasAlpha()) { - const QString AString = ((A < 0) ? "-" : "") + QString::number(std::abs(A), formatBase); - valText = QString("R%1\nG%2\nB%3\nA%4").arg(RString, GString, BString, AString); + const QString aString = ((a < 0) ? "-" : "") + QString::number(std::abs(a), formatBase); + valText = QString("R%1\nG%2\nB%3\nA%4").arg(rString, gString, bString, aString); } else - valText = QString("R%1\nG%2\nB%3").arg(RString, GString, BString); + valText = QString("R%1\nG%2\nB%3").arg(rString, gString, bString); } else { - rgba_t value = getPixelValue(QPoint(x, y)); + const auto value = getPixelValue(QPoint(x, y)); if (srcPixelFormat.hasAlpha()) valText = QString("R%1\nG%2\nB%3\nA%4") .arg(value.R, 0, formatBase) @@ -833,8 +834,9 @@ void videoHandlerRGB::drawPixelValues(QPainter *painter, .arg(value.R, 0, formatBase) .arg(value.G, 0, formatBase) .arg(value.B, 0, formatBase); + painter->setPen( - (value.R < drawWhitLevel && value.G < drawWhitLevel && value.B < drawWhitLevel) + (this->srcPixelFormat.getPixelValueTextRendering(value) == TextRendering::White) ? Qt::white : Qt::black); } diff --git a/YUViewLib/src/video/yuv/PixelFormatYUV.cpp b/YUViewLib/src/video/yuv/PixelFormatYUV.cpp index c429750e5..73dd67c2f 100644 --- a/YUViewLib/src/video/yuv/PixelFormatYUV.cpp +++ b/YUViewLib/src/video/yuv/PixelFormatYUV.cpp @@ -43,12 +43,12 @@ void getColorConversionCoefficients(ColorConversion colorConversion, int RGBConv // The first index is the index of the ColorConversion enum. The second index is [Y, cRV, cGU, // cGV, cBU]. const int yuvRgbConvCoeffs[6][5] = { - {76309, 117489, -13975, -34925, 138438}, // BT709_LimitedRange - {65536, 103206, -12276, -30679, 121608}, // BT709_FullRange - {76309, 104597, -25675, -53279, 132201}, // BT601_LimitedRange - {65536, 91881, -22553, -46802, 116129}, // BT601_FullRange - {76309, 110013, -12276, -42626, 140363}, // BT2020_LimitedRange - {65536, 96638, -10783, -37444, 123299} // BT2020_FullRange + {76309, 117489, -13975, -34925, 138438}, // BT709_LimitedRange + {65536, 103206, -12276, -30679, 121608}, // BT709_FullRange + {76309, 104597, -25675, -53279, 132201}, // BT601_LimitedRange + {65536, 91881, -22553, -46802, 116129}, // BT601_FullRange + {76309, 110013, -12276, -42626, 140363}, // BT2020_LimitedRange + {65536, 96638, -10783, -37444, 123299} // BT2020_FullRange }; const auto index = ColorConversionMapper.indexOf(colorConversion); for (unsigned i = 0; i < 5; i++) @@ -78,7 +78,7 @@ std::vector getSupportedPackingFormats(Subsampling subsampling) { if (subsampling == Subsampling::YUV_422) return std::vector( - {PackingOrder::UYVY, PackingOrder::VYUY, PackingOrder::YUYV, PackingOrder::YVYU}); + {PackingOrder::UYVY, PackingOrder::VYUY, PackingOrder::YUYV, PackingOrder::YVYU}); if (subsampling == Subsampling::YUV_444) return std::vector({PackingOrder::YUV, PackingOrder::YVU, @@ -129,8 +129,8 @@ PixelFormatYUV::PixelFormatYUV(const std::string &name) } std::regex strExpr( - "([YUVA]{3,6}(?:\\(IL\\))?) (4:[4210]{1}:[4210]{1}) ([0-9]{1,2})-bit[ ]?([BL]{1}E)?[ " - "]?(packed-B|packed)?[ ]?(Cx[0-9]+)?[ ]?(Cy[0-9]+)?"); + "([YUVA]{3,6}(?:\\(IL\\))?) (4:[4210]{1}:[4210]{1}) ([0-9]{1,2})-bit[ ]?([BL]{1}E)?[ " + "]?(packed-B|packed)?[ ]?(Cx[0-9]+)?[ ]?(Cy[0-9]+)?"); std::smatch sm; if (!std::regex_match(name, sm, strExpr)) @@ -394,8 +394,8 @@ int64_t PixelFormatYUV::bytesPerFrame(const Size &frameSize) const if (this->subsampling == Subsampling::YUV_444) bytes += frameSize.width * frameSize.height * bytesPerSample * 2; // U/V planes else if (this->subsampling == Subsampling::YUV_422 || this->subsampling == Subsampling::YUV_440) - bytes += (frameSize.width / 2) * frameSize.height * bytesPerSample * - 2; // U/V planes, half the width + bytes += + (frameSize.width / 2) * frameSize.height * bytesPerSample * 2; // U/V planes, half the width else if (this->subsampling == Subsampling::YUV_420) bytes += (frameSize.width / 2) * (frameSize.height / 2) * bytesPerSample * 2; // U/V planes, half the width and height @@ -462,11 +462,7 @@ std::string PixelFormatYUV::getName() const if (!this->isValid()) return "Invalid"; if (this->predefinedPixelFormat) - { - if (*this->predefinedPixelFormat == PredefinedPixelFormat::V210) - return "V210"; - return "Invalid"; - } + return std::string(PredefinedPixelFormatMapper.getName(*this->predefinedPixelFormat)); std::stringstream ss; From 4fe3a27a690edbdffa1e1c390cb593fe69e16318 Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Thu, 12 Jun 2025 10:58:42 +0300 Subject: [PATCH 04/37] Fix unit test --- YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp b/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp index 1b8767a98..b4cebe21a 100644 --- a/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp +++ b/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp @@ -71,7 +71,8 @@ TEST_P(GuessRGBFormatFromFilenameFrameSizeAndFileSize, TestGuess) video::rgb::guessPixelFormatFromSizeAndName(guessedFrameFormat, parameters.fileInfoForGuess); EXPECT_EQ(guessedFormat.isValid(), parameters.expectedPixelFormat.isValid()); - EXPECT_EQ(guessedFormat, parameters.expectedPixelFormat); + if (guessedFormat.isValid()) + EXPECT_EQ(guessedFormat, parameters.expectedPixelFormat); } constexpr auto BytesNoAlpha = 1920u * 1080 * 12u * 3u; // 12 frames RGB From c8285991aed1d6d8a03629ad8e64bc4e54827768 Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Thu, 12 Jun 2025 18:47:00 +0300 Subject: [PATCH 05/37] Add test for luminance function --- YUViewLib/src/video/rgb/PixelFormatRGB.cpp | 2 +- .../video/rgb/PixelFormatRGBTest.cpp | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/YUViewLib/src/video/rgb/PixelFormatRGB.cpp b/YUViewLib/src/video/rgb/PixelFormatRGB.cpp index aef1ea08d..8f4a51309 100644 --- a/YUViewLib/src/video/rgb/PixelFormatRGB.cpp +++ b/YUViewLib/src/video/rgb/PixelFormatRGB.cpp @@ -315,7 +315,7 @@ TextRendering PixelFormatRGB::getPixelValueTextRendering(rgba_t value) const } // Approximation of Y = 0.375 R + 0.5 G + 0.125 B to be closer to the percieved brightness. - const auto luminance = (3 * value.R + 4 * value.B + value.B) >> 3; + const auto luminance = (3 * value.R + 4 * value.G + value.B) >> 3; return luminance < 128 ? TextRendering::White : TextRendering::Black; } diff --git a/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp b/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp index ffe5d193d..401e1990b 100644 --- a/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp +++ b/YUViewUnitTest/video/rgb/PixelFormatRGBTest.cpp @@ -182,4 +182,37 @@ TEST(PixelFormatRGBTest, testComparisonOperators_ComparingTwoInvalidFormats_shou } } +TEST(PixelFormatRGBTest, testBrightnessCalculation) +{ + constexpr rgba_t black = {0, 0, 0, 255}; + constexpr rgba_t lowerLuminance = {126, 125, 124, 255}; + constexpr rgba_t higherLuminance = {130, 130, 130, 255}; + constexpr rgba_t white = {255, 255, 255, 255}; + + auto scaleRgbToPixelFormatBitDepth = [](const PixelFormatRGB &pixelFormat, rgba_t value) -> rgba_t + { + if (pixelFormat.getPredefinedPixelFormat()) + return {value.R >> 3, value.G << 2, value.B << 3, 255}; + + const auto shift = pixelFormat.getBitsPerComponent() - 8; + return {value.R >> shift, value.G >> shift, value.B >> shift, 255}; + }; + + for (const auto &format : getAllValidFormats()) + { + EXPECT_EQ(format.getPixelValueTextRendering(scaleRgbToPixelFormatBitDepth(format, black)), + TextRendering::White); + EXPECT_EQ( + format.getPixelValueTextRendering(scaleRgbToPixelFormatBitDepth(format, lowerLuminance)), + TextRendering::White); + EXPECT_EQ( + format.getPixelValueTextRendering(scaleRgbToPixelFormatBitDepth(format, higherLuminance)), + TextRendering::Black); + EXPECT_EQ(format.getPixelValueTextRendering(scaleRgbToPixelFormatBitDepth(format, white)), + TextRendering::Black); + + break; + } +} + } // namespace video::rgb::test From 2a0383cbbf12f36b8a878ae7511c5d4d6dff076d Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Thu, 12 Jun 2025 21:37:10 +0300 Subject: [PATCH 06/37] Redo the format detection from the filename --- YUViewLib/src/playlistitem/playlistItemRawFile.cpp | 4 ++-- YUViewLib/src/video/rgb/PixelFormatRGBGuess.cpp | 11 ++++++++++- YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp | 9 +++++++-- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/YUViewLib/src/playlistitem/playlistItemRawFile.cpp b/YUViewLib/src/playlistitem/playlistItemRawFile.cpp index ed9092fbd..a31277172 100644 --- a/YUViewLib/src/playlistitem/playlistItemRawFile.cpp +++ b/YUViewLib/src/playlistitem/playlistItemRawFile.cpp @@ -55,7 +55,7 @@ namespace { constexpr auto YUV_EXTENSIONS = {"yuv", "nv12", "y4m"}; -constexpr auto RGB_EXTENSIONS = {"rgb", "gbr", "bgr", "brg", "rgb565", "rgb565be"}; +constexpr auto RGB_EXTENSIONS = {"rgb", "gbr", "bgr", "brg"}; constexpr auto RGBA_EXTENSIONS = {"rgba", "gbra", "bgra", "brga", "argb", "agbr", "abgr", "abrg"}; constexpr auto RAW_BAYER_EXTENSIONS = {"raw"}; constexpr auto CMYK_EXTENSIONS = {"cmyk"}; @@ -583,7 +583,7 @@ void playlistItemRawFile::getSupportedFileExtensions(QStringList &allExtensions, allExtensions.append(QString(extension)); filters.append("Raw YUV File (*.yuv *.nv21)"); - filters.append("Raw RGB File (*.rgb *.rbg *.grb *.gbr *.brg *.bgr, *.rgb565, *.rgb565be)"); + filters.append("Raw RGB File (*.rgb *.rbg *.grb *.gbr *.brg *.bgr)"); filters.append("Raw RGBA File (*.rgba *.rbga *.grba *.gbra *.brga *.bgra *.argb *.arbg *.agrb " "*.agbr *.abrg *.abgr)"); filters.append("YUV4MPEG2 File (*.y4m)"); diff --git a/YUViewLib/src/video/rgb/PixelFormatRGBGuess.cpp b/YUViewLib/src/video/rgb/PixelFormatRGBGuess.cpp index d34c1b6e5..9337e77ec 100644 --- a/YUViewLib/src/video/rgb/PixelFormatRGBGuess.cpp +++ b/YUViewLib/src/video/rgb/PixelFormatRGBGuess.cpp @@ -122,7 +122,13 @@ std::optional checkForPixelFormatIndicatorInName( } } - matcher.pop_back(); // Remove last | + stringToMatchingFormat["rgb565"] = PixelFormatRGB(PredefinedPixelFormat::RGB565); + matcher += "rgb565|"; + stringToMatchingFormat["rgb565le"] = PixelFormatRGB(PredefinedPixelFormat::RGB565); + matcher += "rgb565le|"; + stringToMatchingFormat["rgb565be"] = PixelFormatRGB(PredefinedPixelFormat::RGB565BE); + matcher += "rgb565be"; + matcher += ")(?:_|\\.|-)"; std::smatch sm; @@ -136,6 +142,9 @@ std::optional checkForPixelFormatIndicatorInName( auto format = stringToMatchingFormat[matchName]; if (doesPixelFormatMatchFileSize(format, frameSize, fileSize)) { + if (format.getPredefinedPixelFormat()) + return format; + const auto dataLayout = findDataLayoutInName(filename); return PixelFormatRGB(format.getBitsPerComponent(), dataLayout, diff --git a/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp b/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp index b4cebe21a..db50dc2cb 100644 --- a/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp +++ b/YUViewUnitTest/video/rgb/PixelFormatRGBGuessTest.cpp @@ -213,9 +213,14 @@ INSTANTIATE_TEST_SUITE_P( PixelFormatRGB(8, DataLayout::Packed, ChannelOrder::RGB, AlphaMode::Last)}), // RGB565 - TestParameters({FileInfoForGuess({"something_512x768.rgb565", "", BytesRGB565File}), + TestParameters({FileInfoForGuess({"something_512x768_rgb565.rgb", "", BytesRGB565File}), PixelFormatRGB(PredefinedPixelFormat::RGB565)}), - TestParameters({FileInfoForGuess({"something_512x768.rgb565be", "", BytesRGB565File}), + TestParameters( + {FileInfoForGuess({"something_512x768_rgb565_something.rgb", "", BytesRGB565File}), + PixelFormatRGB(PredefinedPixelFormat::RGB565)}), + TestParameters({FileInfoForGuess({"something_512x768_rgb565le.rgb", "", BytesRGB565File}), + PixelFormatRGB(PredefinedPixelFormat::RGB565)}), + TestParameters({FileInfoForGuess({"something_512x768_rgb565be.rgb", "", BytesRGB565File}), PixelFormatRGB(PredefinedPixelFormat::RGB565BE)}) ), From df0e59d48e118c2dc43c1fdb1b66cc9eda9cb6fc Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Fri, 13 Jun 2025 10:22:54 +0200 Subject: [PATCH 07/37] Add conversion for RGB565 to single planes --- YUViewLib/src/video/rgb/ConversionRGB.cpp | 65 +++++++++++++++++++++-- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/YUViewLib/src/video/rgb/ConversionRGB.cpp b/YUViewLib/src/video/rgb/ConversionRGB.cpp index 62d8224d4..6ba7d9d29 100644 --- a/YUViewLib/src/video/rgb/ConversionRGB.cpp +++ b/YUViewLib/src/video/rgb/ConversionRGB.cpp @@ -229,6 +229,52 @@ void convertRGBToARGB(const QByteArray &sourceBuffer, } } +void convertPredefinedPixelFormatRGBPlaneToARGB(const QByteArray &sourceBuffer, + const PixelFormatRGB &srcPixelFormat, + unsigned char *targetBuffer, + const Size frameSize, + const Channel displayChannel, + const int scale, + const bool invert, + const bool limitedRange) +{ + auto rawData = reinterpret_cast(sourceBuffer.data()); + + const auto endianness = srcPixelFormat.getEndianess(); + for (unsigned i = 0; i < frameSize.width * frameSize.height; i++) + { + int byte1 = *rawData; + int byte2 = *(rawData + 1); + + if (endianness == Endianness::Big) + std::swap(byte1, byte2); + + const auto value = byte1 + (byte2 << 8); + + int greyscaleValue = 0; + if (displayChannel == Channel::Red) + greyscaleValue = ((value & 0b00000000'00011111) << 3); + else if (displayChannel == Channel::Green) + greyscaleValue = ((value & 0b00000111'11100000) >> 3); + else if (displayChannel == Channel::Blue) + greyscaleValue = ((value & 0b11111000'00000000) >> 8); + + greyscaleValue = functions::clip(greyscaleValue * scale, 0, 255); + if (invert) + greyscaleValue = (255 - greyscaleValue); + if (limitedRange) + greyscaleValue = LimitedRangeToFullRange.at(greyscaleValue); + + targetBuffer[0] = greyscaleValue; + targetBuffer[1] = greyscaleValue; + targetBuffer[2] = greyscaleValue; + targetBuffer[3] = 255; + + rawData += 2; + targetBuffer += 4; + } +} + // Convert one single plane of the input format to RGBA. This is used to visualize the individual // components. template @@ -399,10 +445,19 @@ void convertSinglePlaneOfRGBToGreyscaleARGB(const QByteArray &sourceBuffer, const bool limitedRange) { const auto bitsPerComponent = srcPixelFormat.getBitsPerComponent(); - if (bitsPerComponent < 8 || bitsPerComponent > 32) - throw std::invalid_argument("Invalid bit depth in pixel format for conversion"); - if (bitsPerComponent == 8) + if (srcPixelFormat.getPredefinedPixelFormat()) + { + convertPredefinedPixelFormatRGBPlaneToARGB(sourceBuffer, + srcPixelFormat, + targetBuffer, + frameSize, + displayChannel, + scale, + invert, + limitedRange); + } + else if (bitsPerComponent == 8) convertRGBPlaneToARGB<8>(sourceBuffer, srcPixelFormat, targetBuffer, @@ -420,7 +475,7 @@ void convertSinglePlaneOfRGBToGreyscaleARGB(const QByteArray &sourceBuffer, scale, invert, limitedRange); - else + else if (bitsPerComponent <= 32) convertRGBPlaneToARGB<32>(sourceBuffer, srcPixelFormat, targetBuffer, @@ -429,6 +484,8 @@ void convertSinglePlaneOfRGBToGreyscaleARGB(const QByteArray &sourceBuffer, scale, invert, limitedRange); + else + throw std::invalid_argument("Invalid bit depth in pixel format for conversion"); } rgba_t getPixelValueFromBuffer(const QByteArray &sourceBuffer, From eaf3a8386ef16270bf08c83759bdc903060886ea Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Fri, 13 Jun 2025 10:27:01 +0200 Subject: [PATCH 08/37] Debug windows build --- .github/workflows/Build.yml | 261 ++++++++++++++++++------------------ 1 file changed, 132 insertions(+), 129 deletions(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index bd9ef01f9..8b7f1971e 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -7,133 +7,133 @@ on: - created jobs: - build-unix-native: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-22.04, ubuntu-24.04] - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - run: git fetch --prune --unshallow - - name: Install Linux packages - run: | - sudo apt-get update - sudo apt-get install qt6-base-dev - - name: Build - run: | - cd $GITHUB_WORKSPACE - mkdir build - cd build - qmake6 CONFIG+=UNITTESTS .. - make -j$(nproc) - - name: Run Unittests - run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest - build-mac-native: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [macos-13, macos-15] - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - run: git fetch --prune --unshallow - - name: Install packages - run: brew install qt - - name: Build - run: | - cd $GITHUB_WORKSPACE - mkdir build - cd build - qmake6 CONFIG+=UNITTESTS .. - make -j $(sysctl -n hw.logicalcpu) - - name: Run Unittests - run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest - build-linux-mac: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-22.04, macos-13, macos-15] - steps: - - uses: actions/checkout@v4 - with: - submodules: true - - run: git fetch --prune --unshallow - - name: Set artifact name - id: artifacts - shell: bash - run: | - echo "qt=qtBase-6-9-0-${{ matrix.os }}" >> "$GITHUB_OUTPUT" - echo "outputZip=YUView-${{ matrix.os }}.zip" >> "$GITHUB_OUTPUT" - - name: Install Qt base - run: | - cd ../../ - mkdir -p YUViewQt/YUViewQt - cd YUViewQt/YUViewQt - curl -L https://github.com/ChristianFeldmann/YUViewQt/releases/download/QtBase-6.9.0/${{steps.artifacts.outputs.qt}}.zip -o Qt.zip - unzip -qa Qt.zip - echo "$GITHUB_WORKSPACE/../../YUViewQt/YUViewQt/Qt/bin" >> $GITHUB_PATH - shell: bash - - name: Install Linuxdeployqt - if: runner.os == 'Linux' - run: | - curl -L https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage -o linuxdeployqt-6-x86_64.AppImage - chmod a+x linuxdeployqt-6-x86_64.AppImage - - name: Install Linux packages - if: runner.os == 'Linux' - run: | - sudo apt-get update - sudo apt-get install libgl1-mesa-dev libxkbcommon-x11-0 libpcre2-16-0 '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libatspi2.0-dev libfuse2 - - name: Download libde265 (Linux) - if: runner.os == 'Linux' - run: curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.so -o libde265-internals.so - - name: Download libde265 (Mac) - if: runner.os == 'macOS' - run: curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.dylib -o libde265-internals.dylib - - name: Download libde265 license file - run: curl -L https://raw.githubusercontent.com/ChristianFeldmann/libde265/master/COPYING -o libde265License.txt - shell: bash - - name: Build (Qmake + Make) - run: | - cd $GITHUB_WORKSPACE - export PATH=$GITHUB_WORKSPACE/../../YUViewQt/YUViewQt/Qt/bin:$PATH - mkdir build - cd build - qmake CONFIG+=UNITTESTS .. - make -j 4 - - name: Run Unittests - run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest - - name: Build App (Mac) - if: runner.os == 'macOS' - run: | - macdeployqt build/YUViewApp/YUView.app -always-overwrite -verbose=2 - cp libde265-internals.dylib build/YUViewApp/YUView.app/Contents/MacOS/. - cd build/YUViewApp - # Zip - zip -r ${{steps.artifacts.outputs.outputZip}} YUView.app/ - mkdir $GITHUB_WORKSPACE/artifacts - cp ${{steps.artifacts.outputs.outputZip}} $GITHUB_WORKSPACE/artifacts/ - - name: Build Appimage (Linux) - if: runner.os == 'Linux' - run: | - cd build - make INSTALL_ROOT=appdir install - $GITHUB_WORKSPACE/linuxdeployqt-6-x86_64.AppImage YUViewApp/appdir/usr/local/share/applications/de.rwth_aachen.ient.YUView.desktop -appimage -bundle-non-qt-libs -verbose=2 - mv YUView-*.AppImage YUView.AppImage - mkdir $GITHUB_WORKSPACE/artifacts - cp YUView.AppImage $GITHUB_WORKSPACE/artifacts/ - - name: Upload Artifact - uses: actions/upload-artifact@v4 - with: - name: ${{steps.artifacts.outputs.outputZip}} - path: artifacts - - name: Release - uses: softprops/action-gh-release@v1 - if: startsWith(github.ref, 'refs/tags/') - with: - files: artifacts/${{steps.artifacts.outputs.outputZip}} + # build-unix-native: + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # os: [ubuntu-22.04, ubuntu-24.04] + # steps: + # - uses: actions/checkout@v4 + # with: + # submodules: true + # - run: git fetch --prune --unshallow + # - name: Install Linux packages + # run: | + # sudo apt-get update + # sudo apt-get install qt6-base-dev + # - name: Build + # run: | + # cd $GITHUB_WORKSPACE + # mkdir build + # cd build + # qmake6 CONFIG+=UNITTESTS .. + # make -j$(nproc) + # - name: Run Unittests + # run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest + # build-mac-native: + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # os: [macos-13, macos-15] + # steps: + # - uses: actions/checkout@v4 + # with: + # submodules: true + # - run: git fetch --prune --unshallow + # - name: Install packages + # run: brew install qt + # - name: Build + # run: | + # cd $GITHUB_WORKSPACE + # mkdir build + # cd build + # qmake6 CONFIG+=UNITTESTS .. + # make -j $(sysctl -n hw.logicalcpu) + # - name: Run Unittests + # run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest + # build-linux-mac: + # runs-on: ${{ matrix.os }} + # strategy: + # matrix: + # os: [ubuntu-22.04, macos-13, macos-15] + # steps: + # - uses: actions/checkout@v4 + # with: + # submodules: true + # - run: git fetch --prune --unshallow + # - name: Set artifact name + # id: artifacts + # shell: bash + # run: | + # echo "qt=qtBase-6-9-0-${{ matrix.os }}" >> "$GITHUB_OUTPUT" + # echo "outputZip=YUView-${{ matrix.os }}.zip" >> "$GITHUB_OUTPUT" + # - name: Install Qt base + # run: | + # cd ../../ + # mkdir -p YUViewQt/YUViewQt + # cd YUViewQt/YUViewQt + # curl -L https://github.com/ChristianFeldmann/YUViewQt/releases/download/QtBase-6.9.0/${{steps.artifacts.outputs.qt}}.zip -o Qt.zip + # unzip -qa Qt.zip + # echo "$GITHUB_WORKSPACE/../../YUViewQt/YUViewQt/Qt/bin" >> $GITHUB_PATH + # shell: bash + # - name: Install Linuxdeployqt + # if: runner.os == 'Linux' + # run: | + # curl -L https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage -o linuxdeployqt-6-x86_64.AppImage + # chmod a+x linuxdeployqt-6-x86_64.AppImage + # - name: Install Linux packages + # if: runner.os == 'Linux' + # run: | + # sudo apt-get update + # sudo apt-get install libgl1-mesa-dev libxkbcommon-x11-0 libpcre2-16-0 '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libatspi2.0-dev libfuse2 + # - name: Download libde265 (Linux) + # if: runner.os == 'Linux' + # run: curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.so -o libde265-internals.so + # - name: Download libde265 (Mac) + # if: runner.os == 'macOS' + # run: curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.dylib -o libde265-internals.dylib + # - name: Download libde265 license file + # run: curl -L https://raw.githubusercontent.com/ChristianFeldmann/libde265/master/COPYING -o libde265License.txt + # shell: bash + # - name: Build (Qmake + Make) + # run: | + # cd $GITHUB_WORKSPACE + # export PATH=$GITHUB_WORKSPACE/../../YUViewQt/YUViewQt/Qt/bin:$PATH + # mkdir build + # cd build + # qmake CONFIG+=UNITTESTS .. + # make -j 4 + # - name: Run Unittests + # run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest + # - name: Build App (Mac) + # if: runner.os == 'macOS' + # run: | + # macdeployqt build/YUViewApp/YUView.app -always-overwrite -verbose=2 + # cp libde265-internals.dylib build/YUViewApp/YUView.app/Contents/MacOS/. + # cd build/YUViewApp + # # Zip + # zip -r ${{steps.artifacts.outputs.outputZip}} YUView.app/ + # mkdir $GITHUB_WORKSPACE/artifacts + # cp ${{steps.artifacts.outputs.outputZip}} $GITHUB_WORKSPACE/artifacts/ + # - name: Build Appimage (Linux) + # if: runner.os == 'Linux' + # run: | + # cd build + # make INSTALL_ROOT=appdir install + # $GITHUB_WORKSPACE/linuxdeployqt-6-x86_64.AppImage YUViewApp/appdir/usr/local/share/applications/de.rwth_aachen.ient.YUView.desktop -appimage -bundle-non-qt-libs -verbose=2 + # mv YUView-*.AppImage YUView.AppImage + # mkdir $GITHUB_WORKSPACE/artifacts + # cp YUView.AppImage $GITHUB_WORKSPACE/artifacts/ + # - name: Upload Artifact + # uses: actions/upload-artifact@v4 + # with: + # name: ${{steps.artifacts.outputs.outputZip}} + # path: artifacts + # - name: Release + # uses: softprops/action-gh-release@v1 + # if: startsWith(github.ref, 'refs/tags/') + # with: + # files: artifacts/${{steps.artifacts.outputs.outputZip}} build-windows: runs-on: windows-2022 strategy: @@ -153,6 +153,9 @@ jobs: curl -L https://github.com/ChristianFeldmann/YUViewQt/releases/download/QtBase-6.9.0/qtBase-6-9-0-windows-2022.zip -o Qt.zip 7z x Qt.zip echo "${{ github.workspace }}\..\..\YUViewQt\YUViewQt\Qt\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + dir + cd Qt + dir - name: Install libde265 run: | curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.dll -o libde265.dll @@ -177,9 +180,9 @@ jobs: mkdir build cd build echo "Qmake Version:" - d:\a\YUViewQt\YUViewQt\Qt\bin\qmake --version + qmake --version echo "Executing qmake..." - d:\a\YUViewQt\YUViewQt\Qt\bin\qmake CONFIG+=UNITTESTS .. + qmake CONFIG+=UNITTESTS .. echo "Executing jom:" jom - name: Run Unittests From 3cd0bdc505a356a20fd1ef533ab4e0e52d3a3c3f Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Fri, 13 Jun 2025 10:40:55 +0200 Subject: [PATCH 09/37] Remove hardcoded paths in windows builds. These changed and we should not rely on them. --- .github/workflows/Build.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 8b7f1971e..c1e66fd36 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -153,9 +153,8 @@ jobs: curl -L https://github.com/ChristianFeldmann/YUViewQt/releases/download/QtBase-6.9.0/qtBase-6-9-0-windows-2022.zip -o Qt.zip 7z x Qt.zip echo "${{ github.workspace }}\..\..\YUViewQt\YUViewQt\Qt\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - dir - cd Qt - dir + echo "Qmake Version:" + qmake --version - name: Install libde265 run: | curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.dll -o libde265.dll @@ -179,20 +178,18 @@ jobs: echo "Creating Build dir and entering it" mkdir build cd build - echo "Qmake Version:" - qmake --version echo "Executing qmake..." qmake CONFIG+=UNITTESTS .. echo "Executing jom:" jom - name: Run Unittests - run: D:\a\YUView\YUView\build\YUViewUnitTest\YUViewUnitTest + run: ${{ github.workspace }}\build\YUViewUnitTest\YUViewUnitTest - name: WindeployQT run: | mkdir deploy cd deploy cp ../build/YUViewApp/YUView.exe . - d:\a\YUViewQt\YUViewQt\Qt\bin\windeployqt.exe --release --no-compiler-runtime YUView.exe + windeployqt --release --no-compiler-runtime YUView.exe cp ../openSSL/*.dll . mkdir decoder cp ..\libde265.dll decoder From 7d50e0c20f2d4d86a00eae96a6ae80a62df06c45 Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Fri, 13 Jun 2025 10:56:44 +0200 Subject: [PATCH 10/37] Test qmake in individual step --- .github/workflows/Build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index c1e66fd36..6cdea9aa7 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -153,8 +153,8 @@ jobs: curl -L https://github.com/ChristianFeldmann/YUViewQt/releases/download/QtBase-6.9.0/qtBase-6-9-0-windows-2022.zip -o Qt.zip 7z x Qt.zip echo "${{ github.workspace }}\..\..\YUViewQt\YUViewQt\Qt\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - echo "Qmake Version:" - qmake --version + - name: Test qmake + run : qmake --version - name: Install libde265 run: | curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.dll -o libde265.dll From 0c251870687c87882542811c97eb4077858fda4d Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Fri, 13 Jun 2025 11:14:20 +0200 Subject: [PATCH 11/37] Reenable all builds --- .github/workflows/Build.yml | 254 ++++++++++++++++++------------------ 1 file changed, 127 insertions(+), 127 deletions(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 6cdea9aa7..c7da9fe63 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -7,133 +7,133 @@ on: - created jobs: - # build-unix-native: - # runs-on: ${{ matrix.os }} - # strategy: - # matrix: - # os: [ubuntu-22.04, ubuntu-24.04] - # steps: - # - uses: actions/checkout@v4 - # with: - # submodules: true - # - run: git fetch --prune --unshallow - # - name: Install Linux packages - # run: | - # sudo apt-get update - # sudo apt-get install qt6-base-dev - # - name: Build - # run: | - # cd $GITHUB_WORKSPACE - # mkdir build - # cd build - # qmake6 CONFIG+=UNITTESTS .. - # make -j$(nproc) - # - name: Run Unittests - # run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest - # build-mac-native: - # runs-on: ${{ matrix.os }} - # strategy: - # matrix: - # os: [macos-13, macos-15] - # steps: - # - uses: actions/checkout@v4 - # with: - # submodules: true - # - run: git fetch --prune --unshallow - # - name: Install packages - # run: brew install qt - # - name: Build - # run: | - # cd $GITHUB_WORKSPACE - # mkdir build - # cd build - # qmake6 CONFIG+=UNITTESTS .. - # make -j $(sysctl -n hw.logicalcpu) - # - name: Run Unittests - # run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest - # build-linux-mac: - # runs-on: ${{ matrix.os }} - # strategy: - # matrix: - # os: [ubuntu-22.04, macos-13, macos-15] - # steps: - # - uses: actions/checkout@v4 - # with: - # submodules: true - # - run: git fetch --prune --unshallow - # - name: Set artifact name - # id: artifacts - # shell: bash - # run: | - # echo "qt=qtBase-6-9-0-${{ matrix.os }}" >> "$GITHUB_OUTPUT" - # echo "outputZip=YUView-${{ matrix.os }}.zip" >> "$GITHUB_OUTPUT" - # - name: Install Qt base - # run: | - # cd ../../ - # mkdir -p YUViewQt/YUViewQt - # cd YUViewQt/YUViewQt - # curl -L https://github.com/ChristianFeldmann/YUViewQt/releases/download/QtBase-6.9.0/${{steps.artifacts.outputs.qt}}.zip -o Qt.zip - # unzip -qa Qt.zip - # echo "$GITHUB_WORKSPACE/../../YUViewQt/YUViewQt/Qt/bin" >> $GITHUB_PATH - # shell: bash - # - name: Install Linuxdeployqt - # if: runner.os == 'Linux' - # run: | - # curl -L https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage -o linuxdeployqt-6-x86_64.AppImage - # chmod a+x linuxdeployqt-6-x86_64.AppImage - # - name: Install Linux packages - # if: runner.os == 'Linux' - # run: | - # sudo apt-get update - # sudo apt-get install libgl1-mesa-dev libxkbcommon-x11-0 libpcre2-16-0 '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libatspi2.0-dev libfuse2 - # - name: Download libde265 (Linux) - # if: runner.os == 'Linux' - # run: curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.so -o libde265-internals.so - # - name: Download libde265 (Mac) - # if: runner.os == 'macOS' - # run: curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.dylib -o libde265-internals.dylib - # - name: Download libde265 license file - # run: curl -L https://raw.githubusercontent.com/ChristianFeldmann/libde265/master/COPYING -o libde265License.txt - # shell: bash - # - name: Build (Qmake + Make) - # run: | - # cd $GITHUB_WORKSPACE - # export PATH=$GITHUB_WORKSPACE/../../YUViewQt/YUViewQt/Qt/bin:$PATH - # mkdir build - # cd build - # qmake CONFIG+=UNITTESTS .. - # make -j 4 - # - name: Run Unittests - # run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest - # - name: Build App (Mac) - # if: runner.os == 'macOS' - # run: | - # macdeployqt build/YUViewApp/YUView.app -always-overwrite -verbose=2 - # cp libde265-internals.dylib build/YUViewApp/YUView.app/Contents/MacOS/. - # cd build/YUViewApp - # # Zip - # zip -r ${{steps.artifacts.outputs.outputZip}} YUView.app/ - # mkdir $GITHUB_WORKSPACE/artifacts - # cp ${{steps.artifacts.outputs.outputZip}} $GITHUB_WORKSPACE/artifacts/ - # - name: Build Appimage (Linux) - # if: runner.os == 'Linux' - # run: | - # cd build - # make INSTALL_ROOT=appdir install - # $GITHUB_WORKSPACE/linuxdeployqt-6-x86_64.AppImage YUViewApp/appdir/usr/local/share/applications/de.rwth_aachen.ient.YUView.desktop -appimage -bundle-non-qt-libs -verbose=2 - # mv YUView-*.AppImage YUView.AppImage - # mkdir $GITHUB_WORKSPACE/artifacts - # cp YUView.AppImage $GITHUB_WORKSPACE/artifacts/ - # - name: Upload Artifact - # uses: actions/upload-artifact@v4 - # with: - # name: ${{steps.artifacts.outputs.outputZip}} - # path: artifacts - # - name: Release - # uses: softprops/action-gh-release@v1 - # if: startsWith(github.ref, 'refs/tags/') - # with: - # files: artifacts/${{steps.artifacts.outputs.outputZip}} + build-unix-native: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, ubuntu-24.04] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - run: git fetch --prune --unshallow + - name: Install Linux packages + run: | + sudo apt-get update + sudo apt-get install qt6-base-dev + - name: Build + run: | + cd $GITHUB_WORKSPACE + mkdir build + cd build + qmake6 CONFIG+=UNITTESTS .. + make -j$(nproc) + - name: Run Unittests + run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest + build-mac-native: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [macos-13, macos-15] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - run: git fetch --prune --unshallow + - name: Install packages + run: brew install qt + - name: Build + run: | + cd $GITHUB_WORKSPACE + mkdir build + cd build + qmake6 CONFIG+=UNITTESTS .. + make -j $(sysctl -n hw.logicalcpu) + - name: Run Unittests + run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest + build-linux-mac: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-22.04, macos-13, macos-15] + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - run: git fetch --prune --unshallow + - name: Set artifact name + id: artifacts + shell: bash + run: | + echo "qt=qtBase-6-9-0-${{ matrix.os }}" >> "$GITHUB_OUTPUT" + echo "outputZip=YUView-${{ matrix.os }}.zip" >> "$GITHUB_OUTPUT" + - name: Install Qt base + run: | + cd ../../ + mkdir -p YUViewQt/YUViewQt + cd YUViewQt/YUViewQt + curl -L https://github.com/ChristianFeldmann/YUViewQt/releases/download/QtBase-6.9.0/${{steps.artifacts.outputs.qt}}.zip -o Qt.zip + unzip -qa Qt.zip + echo "$GITHUB_WORKSPACE/../../YUViewQt/YUViewQt/Qt/bin" >> $GITHUB_PATH + shell: bash + - name: Install Linuxdeployqt + if: runner.os == 'Linux' + run: | + curl -L https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage -o linuxdeployqt-6-x86_64.AppImage + chmod a+x linuxdeployqt-6-x86_64.AppImage + - name: Install Linux packages + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install libgl1-mesa-dev libxkbcommon-x11-0 libpcre2-16-0 '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev libatspi2.0-dev libfuse2 + - name: Download libde265 (Linux) + if: runner.os == 'Linux' + run: curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.so -o libde265-internals.so + - name: Download libde265 (Mac) + if: runner.os == 'macOS' + run: curl -L https://github.com/ChristianFeldmann/libde265/releases/download/v1.1/libde265.dylib -o libde265-internals.dylib + - name: Download libde265 license file + run: curl -L https://raw.githubusercontent.com/ChristianFeldmann/libde265/master/COPYING -o libde265License.txt + shell: bash + - name: Build (Qmake + Make) + run: | + cd $GITHUB_WORKSPACE + export PATH=$GITHUB_WORKSPACE/../../YUViewQt/YUViewQt/Qt/bin:$PATH + mkdir build + cd build + qmake CONFIG+=UNITTESTS .. + make -j 4 + - name: Run Unittests + run: $GITHUB_WORKSPACE/build/YUViewUnitTest/YUViewUnitTest + - name: Build App (Mac) + if: runner.os == 'macOS' + run: | + macdeployqt build/YUViewApp/YUView.app -always-overwrite -verbose=2 + cp libde265-internals.dylib build/YUViewApp/YUView.app/Contents/MacOS/. + cd build/YUViewApp + # Zip + zip -r ${{steps.artifacts.outputs.outputZip}} YUView.app/ + mkdir $GITHUB_WORKSPACE/artifacts + cp ${{steps.artifacts.outputs.outputZip}} $GITHUB_WORKSPACE/artifacts/ + - name: Build Appimage (Linux) + if: runner.os == 'Linux' + run: | + cd build + make INSTALL_ROOT=appdir install + $GITHUB_WORKSPACE/linuxdeployqt-6-x86_64.AppImage YUViewApp/appdir/usr/local/share/applications/de.rwth_aachen.ient.YUView.desktop -appimage -bundle-non-qt-libs -verbose=2 + mv YUView-*.AppImage YUView.AppImage + mkdir $GITHUB_WORKSPACE/artifacts + cp YUView.AppImage $GITHUB_WORKSPACE/artifacts/ + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: ${{steps.artifacts.outputs.outputZip}} + path: artifacts + - name: Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + files: artifacts/${{steps.artifacts.outputs.outputZip}} build-windows: runs-on: windows-2022 strategy: From 4541a5b2203293564a10d981be7d5b4ce9844074 Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Sun, 15 Jun 2025 22:44:18 +0200 Subject: [PATCH 12/37] Fix issue with big endianness --- YUViewLib/src/video/rgb/ConversionRGB.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/YUViewLib/src/video/rgb/ConversionRGB.cpp b/YUViewLib/src/video/rgb/ConversionRGB.cpp index 6ba7d9d29..efdba5ca3 100644 --- a/YUViewLib/src/video/rgb/ConversionRGB.cpp +++ b/YUViewLib/src/video/rgb/ConversionRGB.cpp @@ -95,7 +95,10 @@ void convertRGB565ToARGB(const QByteArray &sourceBuffer, { auto rawData = reinterpret_cast(sourceBuffer.data()); - const auto endianness = srcPixelFormat.getEndianess(); + const auto endianness = + (srcPixelFormat.getPredefinedPixelFormat() == PredefinedPixelFormat::RGB565BE + ? Endianness::Big + : Endianness::Little); for (unsigned i = 0; i < frameSize.width * frameSize.height; i++) { int byte1 = *rawData; @@ -240,7 +243,10 @@ void convertPredefinedPixelFormatRGBPlaneToARGB(const QByteArray &sourceBuff { auto rawData = reinterpret_cast(sourceBuffer.data()); - const auto endianness = srcPixelFormat.getEndianess(); + const auto endianness = + (srcPixelFormat.getPredefinedPixelFormat() == PredefinedPixelFormat::RGB565BE + ? Endianness::Big + : Endianness::Little); for (unsigned i = 0; i < frameSize.width * frameSize.height; i++) { int byte1 = *rawData; @@ -334,7 +340,11 @@ rgba_t getPixelValueForPredefiendFormat(const QByteArray &sourceBuffer, int byte1 = *rawData; int byte2 = *(rawData + 1); - if (srcPixelFormat.getEndianess() == Endianness::Big) + const auto endianness = + (srcPixelFormat.getPredefinedPixelFormat() == PredefinedPixelFormat::RGB565BE + ? Endianness::Big + : Endianness::Little); + if (endianness == Endianness::Big) std::swap(byte1, byte2); const auto value = byte1 + (byte2 << 8); From f3f25cb20e8d166ea981988037774f49d45bc902 Mon Sep 17 00:00:00 2001 From: Christian Feldmann Date: Wed, 18 Jun 2025 11:02:53 +0200 Subject: [PATCH 13/37] Prepare to move out the RGB difference calculation from the video handler --- .../src/video/rgb/ConversionDifferenceRGB.cpp | 209 ++++++++++++++++++ .../src/video/rgb/ConversionDifferenceRGB.h | 62 ++++++ YUViewLib/src/video/rgb/videoHandlerRGB.cpp | 19 +- .../src/video/videoHandlerDifference.cpp | 38 ++-- 4 files changed, 300 insertions(+), 28 deletions(-) create mode 100644 YUViewLib/src/video/rgb/ConversionDifferenceRGB.cpp create mode 100644 YUViewLib/src/video/rgb/ConversionDifferenceRGB.h diff --git a/YUViewLib/src/video/rgb/ConversionDifferenceRGB.cpp b/YUViewLib/src/video/rgb/ConversionDifferenceRGB.cpp new file mode 100644 index 000000000..6c41697a4 --- /dev/null +++ b/YUViewLib/src/video/rgb/ConversionDifferenceRGB.cpp @@ -0,0 +1,209 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ConversionDifferenceRGB.h" + +#include + +// Restrict is basically a promise to the compiler that for the scope of the pointer, the target of +// the pointer will only be accessed through that pointer (and pointers copied from it). +#if __STDC__ != 1 +#define restrict __restrict /* use implementation __ format */ +#else +#ifndef __STDC_VERSION__ +#define restrict __restrict /* use implementation __ format */ +#else +#if __STDC_VERSION__ < 199901L +#define restrict __restrict /* use implementation __ format */ +#else +#/* all ok */ +#endif +#endif +#endif + +namespace video::rgb +{ + +namespace +{ + +template struct DataPointers +{ + T *r; + T *g; + T *b; +}; + +template +DataPointers calculatePointersToStartOfComponents(const InputFrameParameters frameParameters, + const PixelFormatRGB &pixelFormat) +{ + const auto posR = pixelFormat.getChannelPosition(Channel::Red); + const auto posG = pixelFormat.getChannelPosition(Channel::Green); + const auto posB = pixelFormat.getChannelPosition(Channel::Blue); + + const auto castDataPointer = reinterpret_cast(frameParameters.rawDataItem->data()); + + if (pixelFormat.getDataLayout() == DataLayout::Planar) + { + const auto offsetToNextPlane = + frameParameters.frameSize.width * frameParameters.frameSize.height; + + return DataPointers({.r = castDataPointer + (posR * offsetToNextPlane), + .g = castDataPointer + (posG * offsetToNextPlane), + .b = castDataPointer + (posB * offsetToNextPlane)}); + } + + return DataPointers( + {.r = castDataPointer + posR, .g = castDataPointer + posG, .b = castDataPointer + posB}); +} + +std::pair +calculateDifferencePredefinedPixelFormat(const InputFrameParameters &frame1, + const InputFrameParameters &frame2, + const PredefinedPixelFormat predefinedPixelFormat, + const int amplificationFactor, + const bool markDifference) +{ + if (predefinedPixelFormat != PredefinedPixelFormat::RGB565 && + predefinedPixelFormat != PredefinedPixelFormat::RGB565BE) + return {}; + + const auto frameSize = Size(std::min(frame1.frameSize.width, frame2.frameSize.width), + std::min(frame1.frameSize.height, frame2.frameSize.height)); + + auto outputImage = + QImage(QSize(frameSize.width, frameSize.height), functionsGui::platformImageFormat(false)); + + MSE mse; + + // Todo: Add code here! + + return {outputImage, mse}; +} + +template +std::pair calculateDifferenceAndMSE(const InputFrameParameters &frame1, + const InputFrameParameters &frame2, + const PixelFormatRGB &pixelFormat, + const int amplificationFactor, + const bool markDifference) +{ + static_assert(std::is_same_v || std::is_same_v || + std::is_same_v); + + const auto components1 = calculatePointersToStartOfComponents(frame1, pixelFormat); + const auto components2 = calculatePointersToStartOfComponents(frame2, pixelFormat); + + const auto frameSize = Size(std::min(frame1.frameSize.width, frame2.frameSize.width), + std::min(frame1.frameSize.height, frame2.frameSize.height)); + auto outputImage = QImage(QSize(frameSize.width, frameSize.height), + functionsGui::platformImageFormat(pixelFormat.hasAlpha())); + MSE mse; + + unsigned char *restrict dst = outputImage.bits(); + const auto offsetToNextValue = + (pixelFormat.getDataLayout() == DataLayout::Planar ? 1 : pixelFormat.getNrChannels()); + + for (int y = 0; y < frameSize.height; ++y) + { + for (int x = 0; x < frameSize.width; ++x) + { + const auto offsetCoordinate = frameSize.width * y + x; + + const auto r1 = static_cast(*(components1.r + offsetToNextValue * offsetCoordinate)); + const auto g1 = static_cast(*(components1.g + offsetToNextValue * offsetCoordinate)); + const auto b1 = static_cast(*(components1.b + offsetToNextValue * offsetCoordinate)); + + const auto r2 = static_cast(*(components2.r + offsetToNextValue * offsetCoordinate)); + const auto g2 = static_cast(*(components2.g + offsetToNextValue * offsetCoordinate)); + const auto b2 = static_cast(*(components2.b + offsetToNextValue * offsetCoordinate)); + + const auto deltaR = r1 - r2; + const auto deltaG = g1 - g2; + const auto deltaB = b1 - b2; + + mse.r += deltaR * deltaR; + mse.g += deltaG * deltaG; + mse.b += deltaB * deltaB; + + if (markDifference) + { + // Just mark if there is a difference + dst[0] = (deltaB == 0) ? 0 : 255; + dst[1] = (deltaG == 0) ? 0 : 255; + dst[2] = (deltaR == 0) ? 0 : 255; + } + else + { + // We want to see the difference + dst[0] = functions::clip(128 + deltaB * amplificationFactor, 0, 255); + dst[1] = functions::clip(128 + deltaG * amplificationFactor, 0, 255); + dst[2] = functions::clip(128 + deltaR * amplificationFactor, 0, 255); + } + + dst[3] = 255; + dst += 4; + } + } + + return {outputImage, mse}; +} + +} // namespace + +std::pair calculateDifferenceAndMSE(const InputFrameParameters &frame1, + const InputFrameParameters &frame2, + const PixelFormatRGB &pixelFormat, + const int amplificationFactor, + const bool markDifference) +{ + + if (pixelFormat.getPredefinedPixelFormat()) + return calculateDifferencePredefinedPixelFormat( + frame1, frame2, *pixelFormat.getPredefinedPixelFormat(), amplificationFactor, markDifference); + + const auto bitDepth = pixelFormat.getBitsPerComponent(); + + if (pixelFormat.getBitsPerComponent() == 8) + return calculateDifferenceAndMSE( + frame1, frame2, pixelFormat, amplificationFactor, markDifference); + + if (pixelFormat.getBitsPerComponent() <= 16) + return calculateDifferenceAndMSE( + frame1, frame2, pixelFormat, amplificationFactor, markDifference); + + return calculateDifferenceAndMSE( + frame1, frame2, pixelFormat, amplificationFactor, markDifference); +} + +} // namespace video::rgb diff --git a/YUViewLib/src/video/rgb/ConversionDifferenceRGB.h b/YUViewLib/src/video/rgb/ConversionDifferenceRGB.h new file mode 100644 index 000000000..a293e0ea4 --- /dev/null +++ b/YUViewLib/src/video/rgb/ConversionDifferenceRGB.h @@ -0,0 +1,62 @@ +/* This file is part of YUView - The YUV player with advanced analytics toolset + * + * Copyright (C) 2015 Institut für Nachrichtentechnik, RWTH Aachen University, GERMANY + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations including + * the two. + * + * You must obey the GNU General Public License in all respects for all + * of the code used other than OpenSSL. If you modify file(s) with this + * exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do + * so, delete this exception statement from your version. If you delete + * this exception statement from all source files in the program, then + * also delete it here. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include