From aa11b4f4e3ea86616b40768e97be8073feb2d147 Mon Sep 17 00:00:00 2001 From: Christian Heimlich Date: Sun, 29 Sep 2024 17:37:42 -0400 Subject: [PATCH] Consolidate Frame into Canvas This simplifies data sharing within the lib's implementation and streamlines the overall procedure for both encoding and decoding. Also switch to polling for PxAccess buffer cycling checks, instead of using a callback function since the condition is simple. Finally, use ordinal values for Encoding enum instead of arbitrarily assigned ones. --- lib/CMakeLists.txt | 10 +- lib/include/pxcrypt/stat.h | 3 + lib/src/art_io/measure.cpp | 6 +- lib/src/art_io/measure.h | 4 +- lib/src/codec/standard_decoder.cpp | 23 ++- lib/src/codec/standard_encoder.cpp | 23 ++- lib/src/medium_io/canvas.cpp | 46 +++-- lib/src/medium_io/canvas.h | 31 ++- lib/src/medium_io/frame.cpp | 93 --------- lib/src/medium_io/frame.h | 73 ------- lib/src/medium_io/operate/basic_px_access.cpp | 61 ------ lib/src/medium_io/operate/basic_px_access.h | 47 ----- lib/src/medium_io/operate/data_translator.cpp | 9 +- lib/src/medium_io/operate/meta_access.cpp | 104 ++++++++++ lib/src/medium_io/operate/meta_access.h | 77 +++++++ lib/src/medium_io/operate/px_access.cpp | 48 +++-- lib/src/medium_io/operate/px_access.h | 19 +- .../medium_io/traverse/canvas_traverser.cpp | 192 +++++++++--------- lib/src/medium_io/traverse/canvas_traverser.h | 96 ++++----- .../traverse/canvas_traverser_prime.cpp | 47 +++++ .../traverse/canvas_traverser_prime.h | 40 ++++ .../medium_io/traverse/frame_traverser.cpp | 97 --------- lib/src/medium_io/traverse/frame_traverser.h | 82 -------- lib/src/stat.cpp | 65 +++++- 24 files changed, 606 insertions(+), 690 deletions(-) delete mode 100644 lib/src/medium_io/frame.cpp delete mode 100644 lib/src/medium_io/frame.h delete mode 100644 lib/src/medium_io/operate/basic_px_access.cpp delete mode 100644 lib/src/medium_io/operate/basic_px_access.h create mode 100644 lib/src/medium_io/operate/meta_access.cpp create mode 100644 lib/src/medium_io/operate/meta_access.h create mode 100644 lib/src/medium_io/traverse/canvas_traverser_prime.cpp create mode 100644 lib/src/medium_io/traverse/canvas_traverser_prime.h delete mode 100644 lib/src/medium_io/traverse/frame_traverser.cpp delete mode 100644 lib/src/medium_io/traverse/frame_traverser.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d760fcf..5637ae7 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -33,12 +33,10 @@ ob_add_standard_library(${LIB_TARGET_NAME} codec/standard_encoder.cpp medium_io/canvas.h medium_io/canvas.cpp - medium_io/frame.h - medium_io/frame.cpp - medium_io/operate/basic_px_access.h - medium_io/operate/basic_px_access.cpp medium_io/operate/data_translator.h medium_io/operate/data_translator.cpp + medium_io/operate/meta_access.h + medium_io/operate/meta_access.cpp medium_io/operate/px_access.h medium_io/operate/px_access.cpp medium_io/sequence/ch_sequence_generator.h @@ -47,8 +45,8 @@ ob_add_standard_library(${LIB_TARGET_NAME} medium_io/sequence/px_sequence_generator.cpp medium_io/traverse/canvas_traverser.h medium_io/traverse/canvas_traverser.cpp - medium_io/traverse/frame_traverser.h - medium_io/traverse/frame_traverser.cpp + medium_io/traverse/canvas_traverser_prime.h + medium_io/traverse/canvas_traverser_prime.cpp LINKS PRIVATE $ diff --git a/lib/include/pxcrypt/stat.h b/lib/include/pxcrypt/stat.h index b694374..7499313 100644 --- a/lib/include/pxcrypt/stat.h +++ b/lib/include/pxcrypt/stat.h @@ -30,6 +30,7 @@ class PXCRYPT_CODEC_EXPORT Stat //-Constructor--------------------------------------------------------------------------------------------------------- public: Stat(const QImage& image); + Stat(const QSize& size); //-Destructor--------------------------------------------------------------------------------------------------- public: @@ -38,6 +39,8 @@ class PXCRYPT_CODEC_EXPORT Stat //-Instance Functions---------------------------------------------------------------------------------------------- public: Capacity capacity(quint8 bpc) const; + bool fitsMetadata() const; + quint8 minimumDensity(quint64 bytes) const; }; } diff --git a/lib/src/art_io/measure.cpp b/lib/src/art_io/measure.cpp index f90ccda..fa02890 100644 --- a/lib/src/art_io/measure.cpp +++ b/lib/src/art_io/measure.cpp @@ -1,5 +1,9 @@ +// Unit Include +#include "measure.h" + // Project Includes #include "art_io/artwork.h" +#include "pxcrypt/stat.h" namespace PxCryptPrivate { @@ -15,6 +19,6 @@ IMeasure::IMeasure() {} //-Instance Functions---------------------------------------------------------------------------------------------- //Public: quint64 IMeasure::size() const { return IArtwork::size(renditionSize()); } -Frame::metavalue_t IMeasure::minimumBpc(const QSize& dim) const { return Frame::minimumBpc(dim, size()); } +Canvas::metavalue_t IMeasure::minimumBpc(const QSize& dim) const { return PxCrypt::Stat(dim).minimumDensity(size()); } } diff --git a/lib/src/art_io/measure.h b/lib/src/art_io/measure.h index f99ff6c..7089abf 100644 --- a/lib/src/art_io/measure.h +++ b/lib/src/art_io/measure.h @@ -5,7 +5,7 @@ #include // Project Includes -#include "medium_io/frame.h" +#include "medium_io/canvas.h" namespace PxCryptPrivate { @@ -22,7 +22,7 @@ class IMeasure public: quint64 size() const; - Frame::metavalue_t minimumBpc(const QSize& dim) const; + Canvas::metavalue_t minimumBpc(const QSize& dim) const; }; template diff --git a/lib/src/codec/standard_decoder.cpp b/lib/src/codec/standard_decoder.cpp index 25d3e6b..abff9cb 100644 --- a/lib/src/codec/standard_decoder.cpp +++ b/lib/src/codec/standard_decoder.cpp @@ -5,9 +5,9 @@ #include "pxcrypt/codec/encoder.h" #include "codec/decoder_p.h" #include "codec/encdec.h" -#include "medium_io/frame.h" #include "medium_io/canvas.h" #include "art_io/works/standard.h" +#include "pxcrypt/stat.h" namespace PxCrypt { @@ -109,35 +109,37 @@ StandardDecoder::Error StandardDecoder::decode(QByteArray& decoded, const QImage if(encoded.isNull()) return Error(Error::InvalidSource); + // Get image stats + Stat encStat(encoded); + // Ensure image meets bare minimum space for meta pixels - if(!Frame::meetsSizeMinimum(encoded)) + if(!encStat.fitsMetadata()) return Error(Error::NotLargeEnough); // Ensure standard pixel format QImage encStd = standardizeImage(encoded); - // Setup frame - Frame frame(&encStd, d->mPsk); + // Setup canvas + Canvas canvas(encStd, d->mPsk); // Ensure BPC is valid - quint8 bpc = frame.bpc(); + quint8 bpc = canvas.bpc(); if(bpc < BPC_MIN || bpc > BPC_MAX) return Error(Error::InvalidMeta); // Ensure encoding is valid - Encoder::Encoding encoding = frame.encoding(); + Encoder::Encoding encoding = canvas.encoding(); if(!magic_enum::enum_contains(encoding)) return Error(Error::InvalidMeta); // Bare minimum size check - Frame::Capacity capacity = frame.capacity(); + Stat::Capacity capacity = encStat.capacity(bpc); quint64 minSize = StandardWork::Measure().size(); if(capacity.bytes < minSize) return Error(Error::NotLargeEnough); // Ensure medium image is valid if applicable QImage mediumStd; - const QImage* mediumStdPtr = nullptr; if(encoding == Encoder::Relative) { if(medium.isNull()) @@ -147,11 +149,10 @@ StandardDecoder::Error StandardDecoder::decode(QByteArray& decoded, const QImage return Error(Error::DimensionMismatch); mediumStd = standardizeImage(medium); - mediumStdPtr = &mediumStd; + canvas.setReference(&mediumStd); } - // Setup IO - Canvas canvas(&encStd, frame, mediumStdPtr); + // Prepare for IO canvas.open(QIODevice::ReadOnly); // Closes upon destruction // Read diff --git a/lib/src/codec/standard_encoder.cpp b/lib/src/codec/standard_encoder.cpp index 553ea08..b5fdf65 100644 --- a/lib/src/codec/standard_encoder.cpp +++ b/lib/src/codec/standard_encoder.cpp @@ -5,9 +5,9 @@ #include "codec/encdec.h" #include "codec/encoder_p.h" #include "codec/encdec.h" -#include "medium_io/frame.h" #include "medium_io/canvas.h" #include "art_io/works/standard.h" +#include "pxcrypt/stat.h" namespace PxCrypt { @@ -87,8 +87,8 @@ StandardEncoder::StandardEncoder() : Encoder(std::make_uniquemTag.size(), payload.size()); if(d->mBpc == 0)// Determine BPC if auto @@ -187,13 +188,13 @@ StandardEncoder::Error StandardEncoder::encode(QImage& encoded, QByteArrayView p if(d->mBpc == 0) { // Check how short at max density - quint64 max = Frame::capacity(medium.size(), BPC_MAX).bytes; + quint64 max = mediumStat.capacity(BPC_MAX).bytes; return Error(Error::WontFit, u"(%1 KiB short)."_s.arg((measurement.size() - max)/1024.0, 0, 'f', 3)); } } else // Ensure data will fit with fixed BPC { - quint64 max = Frame::capacity(medium.size(), d->mBpc).bytes; + quint64 max = mediumStat.capacity(d->mBpc).bytes; if(static_cast(measurement.size()) > max) return Error(Error::WontFit, u"(%1 KiB short)."_s.arg((measurement.size() - max)/1024.0, 0, 'f', 3)); } @@ -201,13 +202,13 @@ StandardEncoder::Error StandardEncoder::encode(QImage& encoded, QByteArrayView p // Copy base image, normalize to standard format QImage workspace = standardizeImage(medium); - // Setup frame, mark meta pixels - Frame frame(&workspace, d->mPsk); - frame.setBpc(d->mBpc); - frame.setEncoding(d->mEncoding); + // Setup canvas, mark meta pixels, use self as reference if using relative encoding + Canvas canvas(workspace, d->mPsk); + canvas.setBpc(d->mBpc); + canvas.setEncoding(d->mEncoding); + canvas.setReference( d->mEncoding == Relative ? &workspace : nullptr); - // Setup IO, use self to denote relative encoding if applicable - Canvas canvas(&workspace, frame, d->mEncoding == Relative ? &workspace : nullptr); + // Prepare for IO canvas.open(QIODevice::WriteOnly); // Closes upon destruction // Write diff --git a/lib/src/medium_io/canvas.cpp b/lib/src/medium_io/canvas.cpp index 72be00f..a981c38 100644 --- a/lib/src/medium_io/canvas.cpp +++ b/lib/src/medium_io/canvas.cpp @@ -10,12 +10,12 @@ namespace PxCryptPrivate //-Constructor--------------------------------------------------------------------------------------------------------- //Public: -Canvas::Canvas(QImage* image, const Frame& frame, const QImage* refImage) : - mAccess(image, frame.traverser(), frame.bpc(), refImage), - mTranslator(mAccess) -{ - Q_ASSERT(image && !image->isNull()); -} +Canvas::Canvas(QImage& image, const QByteArray& psk) : + mSize(image.size()), + mMetaAccess(image, !psk.isEmpty() ? psk : DEFAULT_SEED), + mPxAccess(image, mMetaAccess), + mTranslator(mPxAccess) +{} //-Destructor--------------------------------------------------------------------------------------------------- //Public: @@ -23,22 +23,22 @@ Canvas::~Canvas() { close(); } //-Instance Functions-------------------------------------------------------------------------------------------- //Private: -void Canvas::_reset() { mAccess.reset(); } +void Canvas::_reset() { mPxAccess.reset(); } //Protected: qint64 Canvas::readData(char* data, qint64 maxlen) { - if(maxlen == 0) // QIODevice doc's say this input can be used to perform "post-reading operations" - return mAccess.atEnd() ? -1 : 0; + if(maxlen == 0) // QIODevice doc's say this input (0) can be used to perform "post-reading operations" + return mPxAccess.atEnd() ? -1 : 0; - if(mAccess.atEnd()) + if(mPxAccess.atEnd()) { qWarning("Attempt to read at end of canvas!"); return -1; } qint64 i; - for(i = 0; i < maxlen && !mAccess.atEnd(); i++) + for(i = 0; i < maxlen && !mPxAccess.atEnd(); i++) { char& byte = data[i]; if(!mTranslator.skimByte(reinterpret_cast(byte))) @@ -51,19 +51,19 @@ qint64 Canvas::readData(char* data, qint64 maxlen) qint64 Canvas::skipData(qint64 maxSize) { Q_ASSERT(maxSize >= 0); - return mAccess.skip(maxSize); + return mPxAccess.skip(maxSize); } qint64 Canvas::writeData(const char* data, qint64 len) { - if(mAccess.atEnd()) + if(mPxAccess.atEnd()) { qWarning("Attempt to write at end of canvas!"); return -1; } qint64 i; - for(i = 0; i < len && !mAccess.atEnd(); i++) + for(i = 0; i < len && !mPxAccess.atEnd(); i++) { quint8 byte = data[i]; if(!mTranslator.weaveByte(byte)) @@ -72,7 +72,7 @@ qint64 Canvas::writeData(const char* data, qint64 len) // Always ensure data is current if Unbuffered is used if(openMode().testFlag(QIODevice::Unbuffered)) - mAccess.flush(); + mPxAccess.flush(); return i; } @@ -87,6 +87,11 @@ bool Canvas::open(OpenMode mode) qCritical("Unsupported open mode!"); // Prepare for access + Encoding e = static_cast(mMetaAccess.enc()); + if(e == Encoding::Relative) + Q_ASSERT(mPxAccess.hasReferenceImage()); + else + mPxAccess.setReferenceImage(nullptr); // Force-clear reference when it's not needed _reset(); // Base implementation @@ -95,10 +100,17 @@ bool Canvas::open(OpenMode mode) void Canvas::close() { - mAccess.flush(); + mPxAccess.flush(); return QIODevice::close(); } -bool Canvas::atEnd() const { return mAccess.atEnd(); } +bool Canvas::atEnd() const { return mPxAccess.atEnd(); } + +Canvas::metavalue_t Canvas::bpc() const { return mMetaAccess.bpc(); } +Canvas::Encoding Canvas::encoding() const { return static_cast(mMetaAccess.enc()); } + +void Canvas::setBpc(metavalue_t bpc) { mMetaAccess.setBpc(bpc); } +void Canvas::setEncoding(Encoding enc) { mMetaAccess.setEnc(enc); } +void Canvas::setReference(const QImage* ref) { mPxAccess.setReferenceImage(ref); } } diff --git a/lib/src/medium_io/canvas.h b/lib/src/medium_io/canvas.h index d108615..f11b8ac 100644 --- a/lib/src/medium_io/canvas.h +++ b/lib/src/medium_io/canvas.h @@ -6,23 +6,39 @@ #include // Project Includes -#include "medium_io/frame.h" +#include "pxcrypt/codec/encoder.h" +#include "medium_io/operate/meta_access.h" #include "medium_io/operate/px_access.h" #include "medium_io/operate/data_translator.h" +using namespace Qt::StringLiterals; + namespace PxCryptPrivate { class Canvas final : public QIODevice { +//-Aliases---------------------------------------------------------------------------------------------------------- +private: + using Encoding = PxCrypt::Encoder::Encoding; + +public: + using metavalue_t = quint8; + +//-Class Variables---------------------------------------------------------------------------------------------- +private: + static inline const QByteArray DEFAULT_SEED = "The best and most secure seed that is possible to exist!"_ba; + //-Instance Variables---------------------------------------------------------------------------------------------- private: - PxAccess mAccess; + QSize mSize; + MetaAccess mMetaAccess; + PxAccess mPxAccess; DataTranslator mTranslator; //-Constructor--------------------------------------------------------------------------------------------------- public: - Canvas(QImage* image, const Frame& frame, const QImage* refImage = nullptr); + Canvas(QImage& image, const QByteArray& psk = {}); //-Destructor--------------------------------------------------------------------------------------------------- public: @@ -38,10 +54,19 @@ class Canvas final : public QIODevice qint64 writeData(const char* data, qint64 len) override; public: + // IO bool isSequential() const override; bool open(OpenMode mode) override; void close() override; bool atEnd() const override; + + // Other + metavalue_t bpc() const; + Encoding encoding() const; + + void setBpc(metavalue_t bpc); + void setEncoding(Encoding enc); + void setReference(const QImage* ref = nullptr); }; } diff --git a/lib/src/medium_io/frame.cpp b/lib/src/medium_io/frame.cpp deleted file mode 100644 index 8324740..0000000 --- a/lib/src/medium_io/frame.cpp +++ /dev/null @@ -1,93 +0,0 @@ -// Unit Include -#include "frame.h" - -namespace PxCryptPrivate -{ - -//=============================================================================================================== -// Frame -//=============================================================================================================== - -//-Constructor--------------------------------------------------------------------------------------------------------- -//Public: -Frame::Frame(QImage* image, const QByteArray& psk) : - mImage(image), - mAccess(image, !psk.isEmpty() ? psk : DEFAULT_SEED), - mBpcPixelRef({mAccess++, mAccess++, mAccess++}), - mEncPixelRef({mAccess++, mAccess++, mAccess++}), - mBpcValue(readMetaValue(mBpcPixelRef)), - mEncValue(static_cast(readMetaValue(mEncPixelRef))) -{ - //Q_ASSERT(image); Too late to check -} - -//-Class Functions---------------------------------------------------------------------------------------------- -//Private: -Frame::metavalue_t Frame::readMetaValue(const MetaPixelRef& pixel) -{ - quint8 v = 0; - for(int i = 0; i < 3; i++) - v |= (pixel[i].get() & 0b1) << i; - return v; -} - -void Frame::writeMetaValue(const MetaPixelRef& pixel, metavalue_t value) -{ - for(int i = 0; i < 3; i++) - { - if((value >> i) & 0b1) - pixel[i].get() |= 0b1; - else - pixel[i].get() &= ~0b1; - } -} - -//Public: -bool Frame::meetsSizeMinimum(const QImage& image) { return image.size().width() * image.size().height() >= META_PIXEL_COUNT; } - -Frame::metavalue_t Frame::minimumBpc(const QSize& dim, quint64 bytes) -{ - // Returns the minimum BPC required to store `bytes`, or 0 if they will not fit - if(dim.width() == 0 || dim.height() == 0) - return 0; - - double bits = bytes * 8.0; - double chunks = (dim.width() * dim.height() - META_PIXEL_COUNT) * 3.0; - double bpc = std::ceil(bits/chunks); - - return bpc < 8 ? bpc : 0; -} - -Frame::Capacity Frame::capacity(const QSize& dim, metavalue_t bpc) -{ - quint64 usablePixels = dim.width() * dim.height() - META_PIXEL_COUNT; - quint64 usableChanels = usablePixels * 3; - quint64 useableBits = usableChanels * bpc; - quint64 useableBytes = useableBits / 8; - quint8 leftover = useableBits % 8; - - return {.bytes = useableBytes, .leftoverBits = leftover}; -} - -int Frame::metalPixelCount() { return META_PIXEL_COUNT; } - -//-Instance Functions-------------------------------------------------------------------------------------------- -//Public: -Frame::metavalue_t Frame::bpc() const { return mBpcValue; } -Frame::Encoding Frame::encoding() const { return mEncValue; } -const FrameTraverser& Frame::traverser() const { return mAccess.traverser(); } -Frame::Capacity Frame::capacity() const { return capacity(mImage->size(), mBpcValue); } - -void Frame::setBpc(metavalue_t bpc) -{ - mBpcValue = bpc; - writeMetaValue(mBpcPixelRef, mBpcValue); -} - -void Frame::setEncoding(Encoding enc) -{ - mEncValue = enc; - writeMetaValue(mEncPixelRef, enc); -} - -} diff --git a/lib/src/medium_io/frame.h b/lib/src/medium_io/frame.h deleted file mode 100644 index 5fdd8bf..0000000 --- a/lib/src/medium_io/frame.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef FRAME_H -#define FRAME_H - -// Project Includes -#include "pxcrypt/codec/encoder.h" -#include "medium_io/operate/basic_px_access.h" - -using namespace Qt::Literals; - -namespace PxCryptPrivate -{ - -class Frame -{ -//-Aliases---------------------------------------------------------------------------------------------------------- -private: - using MetaPixelRef = std::array, 3>; - using Encoding = PxCrypt::Encoder::Encoding; - -public: - using metavalue_t = quint8; - -//-Structs------------------------------------------------------------------------------------------------------ -public: - struct Capacity - { - quint64 bytes; - quint8 leftoverBits; - }; - -//-Class Variables---------------------------------------------------------------------------------------------- -private: - static const int META_PIXEL_COUNT = 2; // BPC + EncType - static inline const QByteArray DEFAULT_SEED = "The best and most secure seed that is possible to exist!"_ba; - -//-Instance Variables---------------------------------------------------------------------------------------------- -private: - const QImage* mImage; - BasicPxAccess mAccess; - MetaPixelRef mBpcPixelRef; - MetaPixelRef mEncPixelRef; - metavalue_t mBpcValue; - Encoding mEncValue; - -//-Constructor--------------------------------------------------------------------------------------------------- -public: - Frame(QImage* canvas, const QByteArray& psk = {}); // TODO: Make a way for this and related classes to take const image - -//-Class Functions---------------------------------------------------------------------------------------------- -private: - static metavalue_t readMetaValue(const MetaPixelRef& pixel); - static void writeMetaValue(const MetaPixelRef& pixel, metavalue_t value); - -public: - static int metalPixelCount(); - static bool meetsSizeMinimum(const QImage& image); - static metavalue_t minimumBpc(const QSize& dim, quint64 bytes); - static Capacity capacity(const QSize& dim, metavalue_t bpc); - -//-Instance Functions---------------------------------------------------------------------------------------------- -public: - metavalue_t bpc() const; - Encoding encoding() const; - const FrameTraverser& traverser() const; - Capacity capacity() const; - - void setBpc(metavalue_t bpc); - void setEncoding(Encoding enc); -}; - -} - -#endif // FRAME_H diff --git a/lib/src/medium_io/operate/basic_px_access.cpp b/lib/src/medium_io/operate/basic_px_access.cpp deleted file mode 100644 index 07ef70a..0000000 --- a/lib/src/medium_io/operate/basic_px_access.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Unit Include -#include "basic_px_access.h" - -// Project Includes -#include "medium_io/frame.h" - -namespace PxCryptPrivate -{ - -//=============================================================================================================== -// PxAccess -//=============================================================================================================== - -//-Constructor--------------------------------------------------------------------------------------------------------- -//Public: -BasicPxAccess::BasicPxAccess(QImage* image, const QByteArray& psk) : - mPixels(reinterpret_cast(image->bits())), - mTraverser(*image, psk) -{ - Q_ASSERT(image->width() * image->height() >= Frame::metalPixelCount()); // Practically this is too small, but it is the minimum for this class to work -} - -//-Class Functions------------------------------------------------------------------------------------------------ -//Private: -template - requires std::same_as, QRgb> -ChT& BasicPxAccess::channelRef(RgbT& pixel, Channel ch) -{ - // As of Qt 6.7, QRgb is brazenly defined as 'unsigned int', despite that not being guaranteed to be 32-bit, so make sure - static_assert(sizeof(pixel) == 4); - - // Dirty direct memory access, but meh, base it on system endianness and we're good - ChT* chPtr = reinterpret_cast(&pixel); - if constexpr(QSysInfo::ByteOrder == QSysInfo::BigEndian) // 0xAARRGGBB - chPtr += ch; - else // 0xBBGGRRAA - chPtr += (3 - ch); - - return *chPtr; -} -//-Instance Functions-------------------------------------------------------------------------------------------- -//Private: -QRgb& BasicPxAccess::framePixel() { return mPixels[mTraverser.pixelIndex()]; } -const QRgb& BasicPxAccess::constFramePixel() const { return mPixels[mTraverser.pixelIndex()]; } - -//Public: -quint8 BasicPxAccess::constValue() const { return channelRef(constFramePixel(), mTraverser.channel()); } -quint8& BasicPxAccess::value() { return channelRef(framePixel(), mTraverser.channel()); } -const FrameTraverser& BasicPxAccess::traverser() const { return mTraverser; } -void BasicPxAccess::nextChannel() { mTraverser.nextChannel(); } - -//-Operators---------------------------------------------------------------------------------------------------------------- -//Public: -quint8& BasicPxAccess::operator++(int) -{ - quint8& v = value(); - nextChannel(); - return v; -} - -} diff --git a/lib/src/medium_io/operate/basic_px_access.h b/lib/src/medium_io/operate/basic_px_access.h deleted file mode 100644 index 44fc19e..0000000 --- a/lib/src/medium_io/operate/basic_px_access.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef BASIC_PX_ACCESS_H -#define BASIC_PX_ACCESS_H - -// Project Includes -#include "medium_io/traverse/frame_traverser.h" - -namespace PxCryptPrivate -{ - -class BasicPxAccess -{ -//-Instance Variables------------------------------------------------------------------------------------------------------ -private: - QRgb* mPixels; - FrameTraverser mTraverser; - -//-Constructor--------------------------------------------------------------------------------------------------------- -public: - BasicPxAccess(QImage* image, const QByteArray& psk); - -//-Class Functions---------------------------------------------------------------------------------------------- -private: - // Right now this is only used with non-const references, but the flexibility is there for the future - template, const quint8, quint8>> - requires std::same_as, QRgb> - static ChT& channelRef(RgbT& pixel, Channel ch); - -//-Instance Functions---------------------------------------------------------------------------------------------- -private: - const QRgb& constFramePixel() const; - QRgb& framePixel(); - -public: - quint8 constValue() const; - quint8& value(); - const FrameTraverser& traverser() const; - - void nextChannel(); - -//-Operators---------------------------------------------------------------------------------------------------------------- -public: - quint8& operator++(int); // value() + nextChannel() -}; - -} - -#endif // BASIC_PX_ACCESS_H diff --git a/lib/src/medium_io/operate/data_translator.cpp b/lib/src/medium_io/operate/data_translator.cpp index 38a287b..25a30ec 100644 --- a/lib/src/medium_io/operate/data_translator.cpp +++ b/lib/src/medium_io/operate/data_translator.cpp @@ -3,7 +3,7 @@ namespace PxCryptPrivate { -/*! @cond */ //TODO: Doxygen bug, this should be needed because namespace is excluded +/*! @cond */ //TODO: Doxygen bug, this shouldn't be needed because namespace is excluded //=============================================================================================================== // DataTranslator //=============================================================================================================== @@ -21,13 +21,12 @@ template bool DataTranslator::translate(F procedure) { int byteBitIdx = 0; - quint8 bpc = mAccess.bpc(); while(byteBitIdx < 8 && !mAccess.atEnd()) { // Determine how many bits can be used int remaining = 8 - byteBitIdx; - int available = bpc - mAccess.bitIndex(); + int available = mAccess.availableBits(); int processing = std::min(remaining, available); // Perform procedure @@ -52,7 +51,7 @@ void DataTranslator::weaveBits(quint8 bits, int count) // Update bits quint8& val = mAccess.bufferedValue(); - if(mAccess.hasReferenceCanvas()) // Relative method + if(mAccess.hasReferenceImage()) // Relative method { if(mAccess.originalValue() > 127) val -= bits; @@ -72,7 +71,7 @@ quint8 DataTranslator::skimBits(int count) quint8 keepMask = ((0b1 << count) - 1) << chBitIdx; quint8 bits; - if(mAccess.hasReferenceCanvas()) // Relative method + if(mAccess.hasReferenceImage()) // Relative method bits = Qx::distance(mAccess.referenceValue(), mAccess.constBufferedValue()) & keepMask; else // Absolute method bits = mAccess.constBufferedValue() & keepMask; diff --git a/lib/src/medium_io/operate/meta_access.cpp b/lib/src/medium_io/operate/meta_access.cpp new file mode 100644 index 0000000..606a668 --- /dev/null +++ b/lib/src/medium_io/operate/meta_access.cpp @@ -0,0 +1,104 @@ +// Unit Include +#include "meta_access.h" + +namespace PxCryptPrivate +{ + +//=============================================================================================================== +// MetaRef +//=============================================================================================================== + +//-Constructor--------------------------------------------------------------------------------------------------------- +//Private: +MetaRef::MetaRef(const DisectedPixelRef& pixelRef) : + mPixelRef(pixelRef) +{} + +//-Operators---------------------------------------------------------------------------------------------- +//Private: +quint8 MetaRef::operator*() const +{ + quint8 v = 0; + for(int i = 0; i < 3; i++) + v |= (*mPixelRef[i] & 0b1) << i; + return v; +} + +void MetaRef::operator=(quint8 value) +{ + for(int i = 0; i < 3; i++) + { + if((value >> i) & 0b1) + *mPixelRef[i] |= 0b1; + else + *mPixelRef[i] &= ~0b1; + } +} + +//=============================================================================================================== +// MetaAccess +//=============================================================================================================== + +//-Constructor--------------------------------------------------------------------------------------------------------- +//Public: +MetaAccess::MetaAccess(QImage& image, const QByteArray& psk) : + mTraverser(image, psk), + mPixels(reinterpret_cast(image.bits())), + mBpcRef({&ncr(), &ncr(), &ncr()}), + mBpcCache(*mBpcRef), + mEncRef({&ncr(), &ncr(), &ncr()}), + mEncCache(*mEncRef) +{ + // TODO: Have this check or a more complete one in Canvas cause this occurs too late due to initializers + //Q_ASSERT(image.width() * image.height() >= META_PIXEL_COUNT); +} + +//-Class Functions------------------------------------------------------------------------------------------------ +//Private: +quint8& MetaAccess::channelRef(QRgb& pixel, Channel ch) +{ + // As of Qt 6.7, QRgb is brazenly defined as 'unsigned int', despite that not being guaranteed to be 32-bit, so make sure + static_assert(sizeof(pixel) == 4); + + // Dirty direct memory access, but meh, base it on system endianness and we're good + quint8* chPtr = reinterpret_cast(&pixel); + if constexpr(QSysInfo::ByteOrder == QSysInfo::BigEndian) // 0xAARRGGBB + chPtr += ch; + else // 0xBBGGRRAA + chPtr += (3 - ch); + + return *chPtr; +} + +//Public: +int MetaAccess::metaPixelCount() { return META_PIXEL_COUNT; } + +//-Instance Functions-------------------------------------------------------------------------------------------- +//Private: +QRgb& MetaAccess::currentPixelRef() { return mPixels[mTraverser.pixelIndex()]; } +quint8& MetaAccess::currentChannelRef() { return channelRef(currentPixelRef(), mTraverser.channel()); } +quint8& MetaAccess::ncr() +{ + quint8& v = currentChannelRef(); + mTraverser.nextChannel(); + return v; +} + +//Public: +void MetaAccess::setBpc(quint8 bpc) +{ + mBpcCache = bpc; + mBpcRef = bpc; +} + +void MetaAccess::setEnc(quint8 enc) +{ + mEncCache = enc; + mEncRef = enc; +} + +quint8 MetaAccess::bpc() const { return mBpcCache; } +quint8 MetaAccess::enc() const { return mEncCache; } +CanvasTraverserPrime& MetaAccess::surrenderTraverser() { return mTraverser; } + +} diff --git a/lib/src/medium_io/operate/meta_access.h b/lib/src/medium_io/operate/meta_access.h new file mode 100644 index 0000000..dc1b7fa --- /dev/null +++ b/lib/src/medium_io/operate/meta_access.h @@ -0,0 +1,77 @@ +#ifndef META_ACCESS_H +#define META_ACCESS_H + +// Project Includes +#include "medium_io/traverse/canvas_traverser_prime.h" + +namespace PxCryptPrivate +{ + +class MetaRef +{ + friend class MetaAccess; +//-Alias------------------------------------------------------------------------------------------------------ +private: + using ChannelRef = quint8*; + using DisectedPixelRef = std::array; + +//-Instance Variables------------------------------------------------------------------------------------------------------ +private: + DisectedPixelRef mPixelRef; + +//-Constructor--------------------------------------------------------------------------------------------------------- +private: + MetaRef(const DisectedPixelRef& pixelRef); + +//-Operators---------------------------------------------------------------------------------------------- +public: + quint8 operator*() const; + void operator=(quint8 value); +}; + +class MetaAccess +{ +//-Class Variables------------------------------------------------------------------------------------------------------ +private: + static const int META_PIXEL_COUNT = 2; // BPC + EncType + +//-Instance Variables------------------------------------------------------------------------------------------------------ +private: + CanvasTraverserPrime mTraverser; + QRgb* mPixels; + MetaRef mBpcRef; + quint8 mBpcCache; + MetaRef mEncRef; + quint8 mEncCache; + +//-Constructor--------------------------------------------------------------------------------------------------------- +public: + MetaAccess(QImage& image, const QByteArray& psk); + +//-Class Functions---------------------------------------------------------------------------------------------- +private: + static quint8& channelRef(QRgb& pixel, Channel ch); + +public: + static int metaPixelCount(); + +//-Instance Functions---------------------------------------------------------------------------------------------- +private: + QRgb& currentPixelRef(); + quint8& currentChannelRef(); + quint8& ncr(); // Get next channel reference + +public: + void setBpc(quint8 bpc); + void setEnc(quint8 enc); + quint8 bpc() const; + quint8 enc() const; + + CanvasTraverserPrime& surrenderTraverser(); +}; + + + +} + +#endif // META_ACCESS_H diff --git a/lib/src/medium_io/operate/px_access.cpp b/lib/src/medium_io/operate/px_access.cpp index 9e987d2..8861832 100644 --- a/lib/src/medium_io/operate/px_access.cpp +++ b/lib/src/medium_io/operate/px_access.cpp @@ -1,6 +1,9 @@ // Unit Include #include "px_access.h" +// Project Includes +#include "medium_io/operate/meta_access.h" + namespace PxCryptPrivate { @@ -10,15 +13,13 @@ namespace PxCryptPrivate //-Constructor--------------------------------------------------------------------------------------------------------- //Public: -PxAccess::PxAccess(QImage* canvas, const FrameTraverser& frameTraverser, quint8 bpc, const QImage* refCanvas) : - mPixels(reinterpret_cast(canvas->bits())), - mRefPixels(refCanvas ? reinterpret_cast(refCanvas->bits()) : nullptr), - mTraverser(frameTraverser, bpc), - mInitTravState(mTraverser.state()), +PxAccess::PxAccess(QImage& canvas, MetaAccess& metaAccess) : + mPixels(reinterpret_cast(canvas.bits())), + mRefPixels(nullptr), + mTraverser(metaAccess), mNeedFlush(false) { - Q_ASSERT(canvas && !canvas->isNull()); - reset(); + Q_ASSERT(!canvas.isNull()); } //-Instance Functions-------------------------------------------------------------------------------------------- @@ -30,7 +31,7 @@ quint8 PxAccess::canvasGreen() const { return qGreen(constCanvasPixel()); } quint8 PxAccess::canvasBlue() const { return qBlue(constCanvasPixel()); } quint8 PxAccess::canvasAlpha() const { return qAlpha(constCanvasPixel()); } -const QRgb& PxAccess::referencePixel() const{ Q_ASSERT(mRefPixels); return mRefPixels[mTraverser.pixelIndex()]; } +const QRgb& PxAccess::referencePixel() const{ return mRefPixels[mTraverser.pixelIndex()]; } quint8 PxAccess::referenceRed() const { return qRed(referencePixel()); } quint8 PxAccess::referenceGreen() const { return qGreen(referencePixel()); } quint8 PxAccess::referenceBlue() const { return qBlue(referencePixel()); } @@ -58,23 +59,23 @@ void PxAccess::flushBuffer() } //Public: -quint8 PxAccess::bpc() const { return mTraverser.bpc(); } -int PxAccess::bitIndex() const { return mTraverser.channelBitIndex(); } -bool PxAccess::hasReferenceCanvas() const { return mRefPixels; } +bool PxAccess::hasReferenceImage() const { return mRefPixels; } +int PxAccess::availableBits() const { return mTraverser.remainingChannelBits(); } +int PxAccess::bitIndex() const { return mTraverser.channelBitIndex(); }; bool PxAccess::atEnd() const { return mTraverser.atEnd(); } +void PxAccess::setReferenceImage(const QImage* ref) +{ + mRefPixels = ref ? reinterpret_cast(ref->bits()) : nullptr; +} + void PxAccess::reset() { // Ensure canvas is current flushBuffer(); - // Restart generator sequence to start (just before first non-meta-pixel) - if(mTraverser != mInitTravState) - mTraverser = CanvasTraverser(mInitTravState); - - // Ensure callbacks are set - mTraverser.setPrePixelChange([this]{ flushBuffer(); }); - mTraverser.setPostPixelChange([this]{ fillBuffer(); }); + // Re-initialize canvas traverser + mTraverser.init(); // Fill fillBuffer(); @@ -95,7 +96,16 @@ qint64 PxAccess::skip(qint64 bytes) return skipped; } -void PxAccess::advanceBits(int bitCount) { mTraverser.advanceBits(bitCount); } +void PxAccess::advanceBits(int bitCount) +{ + bool cycleBuffer = mTraverser.bitAdvanceWillChangePixel(bitCount); + if(cycleBuffer) + flushBuffer(); + mTraverser.advanceBits(bitCount); + if(cycleBuffer && !atEnd()) + fillBuffer(); +} + void PxAccess::flush() { flushBuffer(); } quint8& PxAccess::bufferedValue() diff --git a/lib/src/medium_io/operate/px_access.h b/lib/src/medium_io/operate/px_access.h index b1b5113..94c1f3e 100644 --- a/lib/src/medium_io/operate/px_access.h +++ b/lib/src/medium_io/operate/px_access.h @@ -7,30 +7,22 @@ namespace PxCryptPrivate { +class MetaAccess; + class PxAccess { -//-Inner Struct----------------------------------------------------------------------------------------------------------- -private: - struct Pos - { - quint64 px; - int ch; - int bit; - }; - //-Instance Variables------------------------------------------------------------------------------------------------------ private: QRgb* mPixels; const QRgb* mRefPixels; CanvasTraverser mTraverser; - CanvasTraverser::State mInitTravState; std::array mBuffer; bool mNeedFlush; //-Constructor--------------------------------------------------------------------------------------------------------- public: - PxAccess(QImage* canvas, const FrameTraverser& frameTraverser, quint8 bpc, const QImage* refCanvas); + PxAccess(QImage& canvas, MetaAccess& metaAccess); //-Instance Functions---------------------------------------------------------------------------------------------- private: @@ -54,12 +46,13 @@ class PxAccess public: // Stat - quint8 bpc() const; - bool hasReferenceCanvas() const; + bool hasReferenceImage() const; + int availableBits() const; int bitIndex() const; bool atEnd() const; // Manipulation + void setReferenceImage(const QImage* ref); void reset(); qint64 skip(qint64 bytes); void advanceBits(int bitCount); diff --git a/lib/src/medium_io/traverse/canvas_traverser.cpp b/lib/src/medium_io/traverse/canvas_traverser.cpp index 05d5667..565fb54 100644 --- a/lib/src/medium_io/traverse/canvas_traverser.cpp +++ b/lib/src/medium_io/traverse/canvas_traverser.cpp @@ -2,7 +2,7 @@ #include "canvas_traverser.h" // Project Includes -#include "medium_io/frame.h" +#include "medium_io/operate/meta_access.h" namespace PxCryptPrivate { @@ -13,88 +13,116 @@ namespace PxCryptPrivate //-Constructor--------------------------------------------------------------------------------------------------------- //Public: -CanvasTraverser::CanvasTraverser(const FrameTraverser& frameTraverser, quint8 bpc) : - mFrameTraverser(frameTraverser), - mBpc(bpc), - mChBitIdx(0) +CanvasTraverser::CanvasTraverser(MetaAccess& meta) : + mMeta(meta), + mLinearPosition{0, 0, 0} // Ignore meta pixels { - // Should be created at first non-meta-pixel, start of byte boundary, much of the implementation relies on this - Q_ASSERT(mFrameTraverser.pixelStep() == Frame::metalPixelCount() && mFrameTraverser.channelStep() == 0); - //TODO: See if frame can just be merged into canvas, or if not, if canvas traverser can properly cut off the - // first two pixels consumed by frametraverser so that here we dont even need to account for the meta pixels + CanvasTraverserPrime& prime = mMeta.surrenderTraverser(); + mPxSequence = prime.surrenderPxSequence(); + mChSequence = prime.surrenderChSequence(); + mCurrentSelection = Selection{prime.pixelIndex(), prime.channel()}; - /* Determine end position + mInitialState = std::make_unique(state()); +} + +//-Instance Functions-------------------------------------------------------------------------------------------- +//Private: +void CanvasTraverser::restoreState(const State& state) +{ + mPxSequence = std::make_unique(state.pxState); + mChSequence = std::make_unique(state.chState); + mLinearPosition = state.linearPosition; + mCurrentSelection = state.currentSelection; + +} +void CanvasTraverser::calculateEnd() +{ + /* Determine end Position * - * We determine the number of available bits, starting from the first non-meta pixels - * in order to determine the last byte boundary correctly (since this depends strictly - * on the starting pos and BPC), but then add two to the pixel portion of the pos to - * account for the meta pixels that were skipped + * We determine the number of available bits, based on the pixel-sequence's remaining + * pixels (which handles ignoring meta-pixels) in order to determine the last byte boundary + * correctly (since this depends strictly on the starting Position and BPC). */ - uint writeablePixels = mFrameTraverser.remainingPixels() + 1; // +1 to include current - quint64 bitsAvailable = (writeablePixels * 3 * mBpc); + + quint64 writeablePixels = Qx::length(mPxSequence->pixelCoverage(), mPxSequence->pixelTotal()); + quint64 bitsAvailable = (writeablePixels * 3 * mMeta.bpc()); bitsAvailable &= ~7; // Same as (x / 8) * 8, only count whole bytes - mEnd = Pos::fromBitPos(bitsAvailable, mBpc); - mEnd.px += Frame::metalPixelCount(); + mLinearEnd = Position::fromBits(bitsAvailable, mMeta.bpc()); } -CanvasTraverser::CanvasTraverser(const State& state) : - mFrameTraverser(state.frameState()), - mBpc(state.bpc()), - mChBitIdx(state.chBitIndex()), - mEnd(state.end()) -{} +void CanvasTraverser::advanceChannel() +{ + Q_ASSERT(!mPxSequence->atEnd()); // Shouldn't happen with current implementation -//-Instance Functions-------------------------------------------------------------------------------------------- -//Private: -CanvasTraverser::Pos CanvasTraverser::currentPos() const + if(mChSequence->pixelExhausted()) + advancePixel(); + else + { + mLinearPosition.ch++; + mCurrentSelection.ch = mChSequence->next(); + } +} + +void CanvasTraverser::advancePixel() { - return {.px = mFrameTraverser.pixelStep(), .ch = mFrameTraverser.channelStep(), .bit = mChBitIdx}; + mLinearPosition.px += 1; + mCurrentSelection.px = mPxSequence->next(); + mLinearPosition.ch = 0; + mCurrentSelection.ch = mChSequence->next(); } //Public: -CanvasTraverser::State CanvasTraverser::state() const +void CanvasTraverser::init() { - return State{mFrameTraverser.state(), mBpc, mChBitIdx, mEnd}; + if(*this != *mInitialState) + restoreState(*mInitialState); + calculateEnd(); } -quint8 CanvasTraverser::bpc() const { return mBpc; } +CanvasTraverser::State CanvasTraverser::state() const +{ + return State{ + mPxSequence->state(), + mChSequence->state(), + mLinearPosition, + mCurrentSelection + }; +} bool CanvasTraverser::atEnd() const { - Pos cp = currentPos(); - Q_ASSERT(currentPos() <= mEnd); - bool reached = cp >= mEnd; - Q_ASSERT(reached || !mFrameTraverser.atEnd()); - return reached; + Q_ASSERT(mLinearPosition <= mLinearEnd); // Debug check to ensure end is never passed + return mLinearPosition >= mLinearEnd; // Release fallback to check for at or over end } -quint64 CanvasTraverser::pixelIndex() const { return mFrameTraverser.pixelIndex(); } -Channel CanvasTraverser::channel() const { return mFrameTraverser.channel(); } -int CanvasTraverser::channelBitIndex() const { return mChBitIdx; } +quint64 CanvasTraverser::pixelIndex() const { return mCurrentSelection.px; } +Channel CanvasTraverser::channel() const { return mCurrentSelection.ch; } +int CanvasTraverser::channelBitIndex() const { return mLinearPosition.bit; } +int CanvasTraverser::remainingChannelBits() const { return mMeta.bpc() - channelBitIndex(); } void CanvasTraverser::advanceBits(int bitCount) -{ + { if(Q_UNLIKELY(atEnd())) { qCritical("Attempted to advance past end!"); return; } - mChBitIdx+= bitCount; - Q_ASSERT(mChBitIdx <= mBpc); // Current design dictates that advancement is capped at the number of bits left on the current channel + mLinearPosition.bit += bitCount; + Q_ASSERT(mLinearPosition.bit <= mMeta.bpc()); // Current design dictates that advancement is capped at the number of bits left on the current channel - if(mChBitIdx == mBpc) + if(mLinearPosition.bit == mMeta.bpc()) { - mChBitIdx = 0; - bool changingPx = mFrameTraverser.pixelExhausted(); - if(changingPx && mPrePxChange) - mPrePxChange(); - mFrameTraverser.nextChannel(); - if(changingPx && !atEnd() && mPostPxChange) - mPostPxChange(); + mLinearPosition.bit = 0; + advanceChannel(); } - Q_ASSERT(currentPos() <= mEnd); // Never should be possible to go past end + Q_ASSERT(mLinearPosition <= mLinearEnd); // Never should be possible to go past end +} + +bool CanvasTraverser::bitAdvanceWillChangePixel(int bitCount) +{ + return mLinearPosition.bit + bitCount >= mMeta.bpc() && mChSequence->pixelExhausted(); } qint64 CanvasTraverser::skip(qint64 bytes) @@ -103,78 +131,54 @@ qint64 CanvasTraverser::skip(qint64 bytes) return -1; quint64 bitsToSkip = (static_cast(bytes) * 8); - Pos pos = currentPos(); - quint64 bitPos = pos.toBitPos(mBpc); - Pos newPos = Pos::fromBitPos(bitPos + bitsToSkip, mBpc); - if(newPos > mEnd) - newPos = mEnd; + quint64 bitPos = mLinearPosition.toBits(mMeta.bpc()); + Position newPos = Position::fromBits(bitPos + bitsToSkip, mMeta.bpc()); + if(newPos > mLinearEnd) + newPos = mLinearEnd; /* Have to walk through each channel in order for the pixel sequence to be properly generated, * but channel bit index can be set directly since we manage it here. */ - quint64 chDist = Pos::channelsBeteween(pos, newPos); + quint64 chDist = Position::channelsBeteween(mLinearPosition, newPos); for(quint64 skip = 0; skip < chDist; skip++) - mFrameTraverser.nextChannel(); - mChBitIdx = newPos.bit; + advanceChannel(); + mLinearPosition.bit = newPos.bit; // Return actual bytes skipped - return (newPos.toBitPos(mBpc) - bitPos)/8; + return (newPos.toBits(mMeta.bpc()) - bitPos)/8; } -void CanvasTraverser::setPrePixelChange(const std::function& ppc) { mPrePxChange = ppc; } -void CanvasTraverser::setPostPixelChange(const std::function& ppc) { mPostPxChange = ppc; } - //-Operators---------------------------------------------------------------------------------------------------------------- //Public: bool CanvasTraverser::operator==(const State& state) const { - return mFrameTraverser == state.frameState() && - mBpc == state.bpc() && - mChBitIdx == state.chBitIndex() && - mEnd == state.end(); + return *mPxSequence == state.pxState && + *mChSequence == state.chState && + mLinearPosition == state.linearPosition && + mCurrentSelection == state.currentSelection; } //=============================================================================================================== -// CanvasTraverser::Pos +// CanvasTraverser::Position //=============================================================================================================== -quint64 CanvasTraverser::Pos::channelsBeteween(const Pos& a, const Pos& b) +quint64 CanvasTraverser::Position::channelsBeteween(const Position& a, const Position& b) { return (b.px - a.px)*3 + (b.ch - a.ch); } -CanvasTraverser::Pos CanvasTraverser::Pos::fromBitPos(quint64 bitPos, quint8 bpc) +CanvasTraverser::Position CanvasTraverser::Position::fromBits(quint64 bitPosistion, quint8 bpc) { return { - .px = bitPos / (bpc * 3), - .ch = static_cast((bitPos / bpc) % 3), - .bit = static_cast(bitPos % bpc) + .px = bitPosistion / (bpc * 3), + .ch = static_cast((bitPosistion / bpc) % 3), + .bit = static_cast(bitPosistion % bpc) }; } -quint64 CanvasTraverser::Pos::toBitPos(quint8 bpc) const +quint64 CanvasTraverser::Position::toBits(quint8 bpc) const { return (((px * 3) + ch) * bpc) + bit; } -//=============================================================================================================== -// CanvasTraverser::State -//=============================================================================================================== - -//-Constructor--------------------------------------------------------------------------------------------------------- -//Public: -CanvasTraverser::State::State(const FrameTraverser::State& frameState, quint8 bpc, int chBitIdx, Pos end) : - mFrameState(frameState), - mBpc(bpc), - mChBitIdx(chBitIdx), - mEnd(end) -{} - -//-Instance Functions-------------------------------------------------------------------------------------------- -//Public: -FrameTraverser::State CanvasTraverser::State::frameState() const { return mFrameState; } -quint8 CanvasTraverser::State::bpc() const { return mBpc; } -int CanvasTraverser::State::chBitIndex() const { return mChBitIdx; } -CanvasTraverser::Pos CanvasTraverser::State::end() const { return mEnd; } - } diff --git a/lib/src/medium_io/traverse/canvas_traverser.h b/lib/src/medium_io/traverse/canvas_traverser.h index 6ef5ac7..bbb88aa 100644 --- a/lib/src/medium_io/traverse/canvas_traverser.h +++ b/lib/src/medium_io/traverse/canvas_traverser.h @@ -2,94 +2,98 @@ #define CANVAS_TRAVERSER_H // Project Includes -#include "medium_io/traverse/frame_traverser.h" +#include "medium_io/sequence/px_sequence_generator.h" +#include "medium_io/sequence/ch_sequence_generator.h" namespace PxCryptPrivate { +class MetaAccess; + class CanvasTraverser { -//-Inner Class------------------------------------------------------------------------------------------------------ -public: - class State; - //-Inner Struct----------------------------------------------------------------------------------------------------------- private: - struct Pos + struct Position { quint64 px; int ch; int bit; - static quint64 channelsBeteween(const Pos& a, const Pos& b); - static Pos fromBitPos(quint64 bitPos, quint8 bpc); - quint64 toBitPos(quint8 bpc) const; - bool operator==(const Pos& other) const = default; - auto operator<=>(const Pos& other) const noexcept = default; + static quint64 channelsBeteween(const Position& a, const Position& b); + static Position fromBits(quint64 bitPos, quint8 bpc); + quint64 toBits(quint8 bpc) const; + bool operator==(const Position& other) const = default; + auto operator<=>(const Position& other) const noexcept = default; + }; + + struct Selection + { + qint64 px; + Channel ch; + //int bit; Always the same as linear position bit so just share that + + bool operator==(const Selection& other) const = default; + }; + + struct State + { + PxSequenceGenerator::State pxState; + ChSequenceGenerator::State chState; + Position linearPosition; + Selection currentSelection; }; //-Instance Variables------------------------------------------------------------------------------------------------------ private: - FrameTraverser mFrameTraverser; - quint8 mBpc; - int mChBitIdx; - Pos mEnd; - std::function mPrePxChange; // Not saved as part of state - std::function mPostPxChange; // Not saved as part of state + // Meta + MetaAccess& mMeta; + + // Generator + std::unique_ptr mPxSequence; + std::unique_ptr mChSequence; + + // Location + Position mLinearPosition; + Selection mCurrentSelection; + Position mLinearEnd; + + // State + std::unique_ptr mInitialState; //-Constructor--------------------------------------------------------------------------------------------------------- public: - CanvasTraverser(const FrameTraverser& frameTraverser, quint8 bpc); - CanvasTraverser(const State& state); + CanvasTraverser(MetaAccess& meta); //-Instance Functions---------------------------------------------------------------------------------------------- private: - Pos currentPos() const; + void restoreState(const State& state); + void calculateEnd(); + void advanceChannel(); + void advancePixel(); public: + void init(); + // Stat State state() const; - quint8 bpc() const; bool atEnd() const; quint64 pixelIndex() const; Channel channel() const; int channelBitIndex() const; + int remainingChannelBits() const; // Manipulation void advanceBits(int bitCount); + bool bitAdvanceWillChangePixel(int bitCount); qint64 skip(qint64 bytes); - // Callback - void setPrePixelChange(const std::function& ppc); // Callback over signal to avoid use of QObject for something so simple. - void setPostPixelChange(const std::function& ppc); // Callback over signal to avoid use of QObject for something so simple. - //-Operators---------------------------------------------------------------------------------------------------------------- public: bool operator==(const State& state) const; }; -class CanvasTraverser::State -{ -//-Instance Variables------------------------------------------------------------------------------------------------------ -private: - FrameTraverser::State mFrameState; - quint8 mBpc; - int mChBitIdx; - Pos mEnd; - -//-Constructor------------------------------------------------------------------------------------------------------------- -public: - State(const FrameTraverser::State& frameState, quint8 bpc, int chBitIdx, Pos end); - -//-Instance Functions------------------------------------------------------------------------------------------------------ -public: - FrameTraverser::State frameState() const; - quint8 bpc() const; - int chBitIndex() const; - Pos end() const; -}; - } #endif // CANVAS_TRAVERSER_H diff --git a/lib/src/medium_io/traverse/canvas_traverser_prime.cpp b/lib/src/medium_io/traverse/canvas_traverser_prime.cpp new file mode 100644 index 0000000..e25c57f --- /dev/null +++ b/lib/src/medium_io/traverse/canvas_traverser_prime.cpp @@ -0,0 +1,47 @@ +// Unit Include +#include "canvas_traverser_prime.h" + +namespace PxCryptPrivate +{ + +//=============================================================================================================== +// CanvasTraverserPrime +//=============================================================================================================== + +//-Constructor--------------------------------------------------------------------------------------------------------- +//Public: +CanvasTraverserPrime::CanvasTraverserPrime(const QImage& image, const QByteArray& seed) : + mPxSequence(std::make_unique(image.size(), seed)), + mChSequence(std::make_unique(seed)) +{ + Q_ASSERT(!image.isNull()); + mCurrentIndex = mPxSequence->next(); + mCurrentChannel = mChSequence->next(); +} + +//-Instance Functions-------------------------------------------------------------------------------------------- +//Private: +void CanvasTraverserPrime::advancePixel() +{ + mCurrentIndex = mPxSequence->next(); + mCurrentChannel = mChSequence->next(); +} + +//Public: +qint64 CanvasTraverserPrime::pixelIndex() const { return mCurrentIndex; } +Channel CanvasTraverserPrime::channel() const { return mCurrentChannel; } + +void CanvasTraverserPrime::nextChannel() +{ + Q_ASSERT(!mPxSequence->atEnd()); // Shouldn't happen with current implementation + + if(mChSequence->pixelExhausted()) + advancePixel(); + else + mCurrentChannel = mChSequence->next(); +} + +std::unique_ptr CanvasTraverserPrime::surrenderPxSequence() { return std::move(mPxSequence); } +std::unique_ptr CanvasTraverserPrime::surrenderChSequence() { return std::move(mChSequence); } + +} diff --git a/lib/src/medium_io/traverse/canvas_traverser_prime.h b/lib/src/medium_io/traverse/canvas_traverser_prime.h new file mode 100644 index 0000000..5e66c2e --- /dev/null +++ b/lib/src/medium_io/traverse/canvas_traverser_prime.h @@ -0,0 +1,40 @@ +#ifndef CANVAS_TRAVERSER_PRIME_H +#define CANVAS_TRAVERSER_PRIME_H + +// Project Includes +#include "medium_io/sequence/px_sequence_generator.h" +#include "medium_io/sequence/ch_sequence_generator.h" + +namespace PxCryptPrivate +{ + +class CanvasTraverserPrime +{ +//-Instance Variables------------------------------------------------------------------------------------------------------ +private: + std::unique_ptr mPxSequence; + std::unique_ptr mChSequence; + + qint64 mCurrentIndex; + Channel mCurrentChannel; + +//-Constructor--------------------------------------------------------------------------------------------------------- +public: + CanvasTraverserPrime(const QImage& image, const QByteArray& seed); + +//-Instance Functions---------------------------------------------------------------------------------------------- +private: + void advancePixel(); + +public: + qint64 pixelIndex() const; + Channel channel() const; + void nextChannel(); + + std::unique_ptr surrenderPxSequence(); + std::unique_ptr surrenderChSequence(); +}; + +} + +#endif // CANVAS_TRAVERSER_PRIME_H diff --git a/lib/src/medium_io/traverse/frame_traverser.cpp b/lib/src/medium_io/traverse/frame_traverser.cpp deleted file mode 100644 index ac302f1..0000000 --- a/lib/src/medium_io/traverse/frame_traverser.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// Unit Include -#include "frame_traverser.h" - -// Qx Includes -#include - -namespace PxCryptPrivate -{ - -//=============================================================================================================== -// FrameTraverser -//=============================================================================================================== - -//-Constructor--------------------------------------------------------------------------------------------------------- -//Public: -FrameTraverser::FrameTraverser(const QImage& image, const QByteArray& seed) : - mPxSequence(image.size(), seed), - mChSequence(seed), - mCurrentIndex(mPxSequence.next()), - mCurrentChannel(mChSequence.next()) -{ - Q_ASSERT(!image.isNull()); -} - -FrameTraverser::FrameTraverser(const State& state) : - mPxSequence(state.pxState()), - mChSequence(state.chState()), - mCurrentIndex(state.index()), - mCurrentChannel(state.channel()) -{} - -//-Instance Functions-------------------------------------------------------------------------------------------- -//Private: -void FrameTraverser::advancePixel() -{ - mCurrentIndex = mPxSequence.next(); - mCurrentChannel = mChSequence.next(); -} - -//Public: -FrameTraverser::State FrameTraverser::state() const -{ - return State{mPxSequence.state(), mChSequence.state(), mCurrentIndex, mCurrentChannel}; -} - -bool FrameTraverser::atEnd() const { return mPxSequence.atEnd(); } - -bool FrameTraverser::pixelExhausted() const { return mChSequence.pixelExhausted(); } -quint64 FrameTraverser::pixelStep() const { return (mPxSequence.pixelCoverage() - 1) + mPxSequence.atEnd(); } // Handle end is "1 past" -quint64 FrameTraverser::remainingPixels() const { return mPxSequence.pixelTotal() - mPxSequence.pixelCoverage(); } -quint64 FrameTraverser::totalPixels() const { return mPxSequence.pixelTotal(); } -qint64 FrameTraverser::pixelIndex() const { return mCurrentIndex; } -Channel FrameTraverser::channel() const { return mCurrentChannel; } -int FrameTraverser::channelStep() const { return mChSequence.step(); } - -void FrameTraverser::nextChannel() -{ - Q_ASSERT(!atEnd()); // Shouldn't happen with current implementation - - if(mChSequence.pixelExhausted()) - advancePixel(); - else - mCurrentChannel = mChSequence.next(); -} - - -//-Operators---------------------------------------------------------------------------------------------------------------- -//Public: -bool FrameTraverser::operator==(const State& state) const -{ - return mPxSequence == state.pxState() && - mChSequence == state.chState() && - mCurrentIndex == state.index() && - mCurrentChannel == state.channel(); -} - -//=============================================================================================================== -// FrameTraverser::State -//=============================================================================================================== - -//-Constructor--------------------------------------------------------------------------------------------------------- -//Public: -FrameTraverser::State::State(const PxSequenceGenerator::State& pxState, const ChSequenceGenerator::State& chState, qint64 index, Channel channel) : - mPxState(pxState), - mChState(chState), - mIndex(index), - mChannel(channel) -{} - -//-Instance Functions-------------------------------------------------------------------------------------------- -//Public: -PxSequenceGenerator::State FrameTraverser::State::pxState() const { return mPxState; } -ChSequenceGenerator::State FrameTraverser::State::chState() const { return mChState; } -qint64 FrameTraverser::State::index() const { return mIndex; } -Channel FrameTraverser::State::channel() const { return mChannel; } - -} diff --git a/lib/src/medium_io/traverse/frame_traverser.h b/lib/src/medium_io/traverse/frame_traverser.h deleted file mode 100644 index 1076a6c..0000000 --- a/lib/src/medium_io/traverse/frame_traverser.h +++ /dev/null @@ -1,82 +0,0 @@ -#ifndef FRAME_TRAVERSER_H -#define FRAME_TRAVERSER_H - -// Qt Includes -#include - -// Project Includes -#include "medium_io/sequence/px_sequence_generator.h" -#include "medium_io/sequence/ch_sequence_generator.h" - -namespace PxCryptPrivate -{ - -class FrameTraverser -{ -//-Inner Class------------------------------------------------------------------------------------------------------ -public: - class State; - -//-Instance Variables------------------------------------------------------------------------------------------------------ -private: - PxSequenceGenerator mPxSequence; - ChSequenceGenerator mChSequence; - - qint64 mCurrentIndex; - Channel mCurrentChannel; - -//-Constructor--------------------------------------------------------------------------------------------------------- -public: - FrameTraverser(const QImage& image, const QByteArray& seed); - FrameTraverser(const State& state); - -//-Instance Functions---------------------------------------------------------------------------------------------- -private: - void advancePixel(); - -public: - // Stat - State state() const; - bool atEnd() const; - bool pixelExhausted() const; - - quint64 pixelStep() const; - quint64 remainingPixels() const; - quint64 totalPixels() const; - - qint64 pixelIndex() const; - Channel channel() const; - int channelStep() const; - - // Manipulation - void nextChannel(); - -//-Operators---------------------------------------------------------------------------------------------------------------- -public: - bool operator==(const State& state) const; -}; - -class FrameTraverser::State -{ -//-Instance Variables------------------------------------------------------------------------------------------------------ -private: - PxSequenceGenerator::State mPxState; - ChSequenceGenerator::State mChState; - qint64 mIndex; - Channel mChannel; - -//-Constructor------------------------------------------------------------------------------------------------------------- -public: - State(const PxSequenceGenerator::State& pxState, const ChSequenceGenerator::State& chState, qint64 index, Channel channel); - -//-Instance Functions------------------------------------------------------------------------------------------------------ -public: - PxSequenceGenerator::State pxState() const; - ChSequenceGenerator::State chState() const; - qint64 index() const; - Channel channel() const; -}; - -} - -#endif // FRAME_TRAVERSER_H diff --git a/lib/src/stat.cpp b/lib/src/stat.cpp index 82a7fc2..ae182bf 100644 --- a/lib/src/stat.cpp +++ b/lib/src/stat.cpp @@ -2,13 +2,13 @@ #include "pxcrypt/stat.h" // Project Includes -#include "medium_io/frame.h" +#include "medium_io/operate/meta_access.h" -/* TODO: In the long run it might make more sense to expose Frame (or canvas if it's combined with Frame) - * to the user directly, but with PIMPL. Hide most of its function in the private class and just have - * the few curiosity methods (like capacity) in the public one, and otherwise just use it to pass to - * encoders/decoders for use (similar to QFile and IO classes). It can also have an isValid() method so - * if the user wishes they can test if the frame meets the size minimum immediately after construction. +/* TODO: In the long run it might make more sense to expose Canvas to the user directly, but with PIMPL. + * Hide most of its function in the private class and just have the few curiosity methods (like capacity) + * in the public one, and otherwise just use it to pass to encoders/decoders for use (similar to QFile + * and IO classes). It can also have an isValid() method so if the user wishes they can test if the + * frame meets the size minimum immediately after construction. * * This change would likely make this class unnecessary. */ @@ -62,6 +62,16 @@ Stat::Stat(const QImage& image) : d->mDim = image.size(); } +/*! + * Constructs a statistics generator for an image of size @a size. + */ +Stat::Stat(const QSize& size) : + d_ptr(std::make_unique()) +{ + Q_D(Stat); + d->mDim = size; +} + //-Destructor--------------------------------------------------------------------------------------------------- //Public: @@ -70,6 +80,8 @@ Stat::Stat(const QImage& image) : */ Stat::~Stat() {} +//-Instance Functions------------------------------------------------------------------------------------------- +//Public: /*! * Returns capacity information of the image, calculated at @a bpc bits-per-channel. */ @@ -77,9 +89,44 @@ Stat::Capacity Stat::capacity(quint8 bpc) const { Q_D(const Stat); - // Shamelessly mirror Frame::Capacity - PxCryptPrivate::Frame::Capacity c = PxCryptPrivate::Frame::capacity(d->mDim, bpc); - return {.bytes = c.bytes, .leftoverBits = c.leftoverBits}; + quint64 usablePixels = d->mDim.width() * d->mDim.height() - PxCryptPrivate::MetaAccess::metaPixelCount(); + quint64 usableChanels = usablePixels * 3; + quint64 useableBits = usableChanels * bpc; + quint64 useableBytes = useableBits / 8; + quint8 leftover = useableBits % 8; + + return {.bytes = useableBytes, .leftoverBits = leftover}; +} + +/*! + * Returns @c true if the image is large enough to fit the standard metadata required by every type of + * PxCrypt image; otherwise, returns @c false. + */ +bool Stat::fitsMetadata() const +{ + Q_D(const Stat); + + return d->mDim.width() * d->mDim.height() >= PxCryptPrivate::MetaAccess::metaPixelCount(); +} +/*! + * Returns @c the smallest density (bits-per-channel) requires to store @a bytes total bytes within + * the image, regardless of encoder underlying storage format, or @c 0 if @a bytes exceeds the capacity + * of the image. + * + * @sa capacity(). + */ +quint8 Stat::minimumDensity(quint64 bytes) const +{ + Q_D(const Stat); + + if(d->mDim.width() == 0 || d->mDim.height() == 0) + return 0; + + double bits = bytes * 8.0; + double chunks = (d->mDim.width() * d->mDim.height() - PxCryptPrivate::MetaAccess::metaPixelCount()) * 3.0; + double bpc = std::ceil(bits/chunks); + + return bpc < 8 ? bpc : 0; } //===============================================================================================================