diff --git a/tests/g2chromatic2.cpp b/tests/g2chromatic2.cpp index acf0702..701fbbb 100644 --- a/tests/g2chromatic2.cpp +++ b/tests/g2chromatic2.cpp @@ -13,7 +13,7 @@ struct AnimationConfig { int width = 1024; int height = 1024; - int totalFrames = 480; + int totalFrames = 4800; float fps = 30.0f; int numSeeds = 8; }; @@ -70,13 +70,11 @@ void expandPixel(Grid2& grid, AnimationConfig config, std::vector> newseeds; - std::unordered_set visitedThisFrame; for (const auto& seed : seeds) { visitedThisFrame.insert(std::get<0>(seed)); } - for (const std::tuple& seed : seeds) { size_t id = std::get<0>(seed); Vec2 seedPOS = std::get<1>(seed); @@ -88,7 +86,6 @@ void expandPixel(Grid2& grid, AnimationConfig config, std::vector frames, AnimationConfig config) { std::string filename = "output/chromatic_transformation.avi"; std::cout << "Frame count: " << frames.size() << std::endl; - //std::cout << "Frame size: " << (frames.empty() ? 0 : frames[0].size()) << std::endl; - std::cout << "Width: " << config.width << ", Height: " << config.height << std::endl; + + // Log compression statistics for all frames + std::cout << "\n=== Frame Compression Statistics ===" << std::endl; + size_t totalOriginalSize = 0; + size_t totalCompressedSize = 0; + int compressedFrameCount = 0; + + for (int i = 0; i < frames.size(); ++i) { + totalOriginalSize += frames[i].getSourceSize(); + totalCompressedSize += frames[i].getCompressedSize(); + compressedFrameCount++; + } + + // Print summary + //if (compressedFrameCount > 0) { + double overallRatio = static_cast(totalOriginalSize) / totalCompressedSize; + double overallSavings = (1.0 - 1.0/overallRatio) * 100.0; + + std::cout << "\n=== Overall Compression Summary ===" << std::endl; + std::cout << "Total frames: " << frames.size() << std::endl; + std::cout << "Compressed frames: " << compressedFrameCount << std::endl; + std::cout << "Total original size: " << totalOriginalSize << " bytes (" + << std::fixed << std::setprecision(2) << (totalOriginalSize / (1024.0 * 1024.0)) << " MB)" << std::endl; + std::cout << "Total compressed size: " << totalCompressedSize << " bytes (" + << std::fixed << std::setprecision(2) << (totalCompressedSize / (1024.0 * 1024.0)) << " MB)" << std::endl; + std::cout << "Overall compression ratio: " << std::fixed << std::setprecision(2) << overallRatio << ":1" << std::endl; + std::cout << "Overall space savings: " << std::fixed << std::setprecision(1) << overallSavings << "%" << std::endl; + //} std::filesystem::path dir = "output"; if (!std::filesystem::exists(dir)) { @@ -132,14 +155,15 @@ bool exportavi(std::vector frames, AnimationConfig config) { } } - bool success = AVIWriter::saveAVIFromCompressedFrames(filename,frames,frames[0].getWidth()+1,frames[0].getHeight()+1, config.fps); - - //bool success = AVIWriter::saveAVI(filename, frames, config.width+1, config.height+1, config.fps); + bool success = AVIWriter::saveAVIFromCompressedFrames(filename, frames, frames[0].getWidth(), frames[0].getHeight(), config.fps); if (success) { // Check if file actually exists if (std::filesystem::exists(filename)) { auto file_size = std::filesystem::file_size(filename); + std::cout << "\nAVI file created successfully: " << filename + << " (" << file_size << " bytes, " + << std::fixed << std::setprecision(2) << (file_size / (1024.0 * 1024.0)) << " MB)" << std::endl; } } else { std::cout << "Failed to save AVI file!" << std::endl; @@ -152,20 +176,21 @@ int main() { AnimationConfig config; Grid2 grid = setup(config); - //grid.updateNeighborMap(); Preview(grid); std::vector> seeds = pickSeeds(grid,config); - //std::vector> frames; std::vector frames; for (int i = 0; i < config.totalFrames; ++i){ - std::cout << "Processing bgrframe " << i + 1 << "/" << config.totalFrames << std::endl; + std::cout << "Processing frame " << i + 1 << "/" << config.totalFrames << std::endl; expandPixel(grid,config,seeds); - int width; - int height; - //std::vector bgrframe; frame bgrframe = grid.getGridAsFrame(frame::colormap::BGR); - //grid.getGridAsBGR(width,height,bgrframe); + + // Print compression info for this frame + if (i % 10 == 0 ) { + bgrframe.printCompressionStats(); + //(bgrframe, i + 1); + } + frames.push_back(bgrframe); } diff --git a/util/grid/grid22.hpp b/util/grid/grid22.hpp index 1a0479f..26b07a8 100644 --- a/util/grid/grid22.hpp +++ b/util/grid/grid22.hpp @@ -138,7 +138,6 @@ public: } std::vector queryRange(const Vec2& center, float radius) const { - TIME_FUNCTION; std::vector results; float radiusSq = radius * radius; @@ -637,7 +636,6 @@ public: // Get region as frame (Grayscale format) frame getGridRegionAsFrameGrayscale(const Vec2& minCorner, const Vec2& maxCorner) const { - TIME_FUNCTION; int width, height; std::vector rgbData; getGridRegionAsRGB(minCorner, maxCorner, width, height, rgbData); @@ -669,19 +667,26 @@ public: switch (format) { case frame::colormap::RGB: - Frame = getGridRegionAsFrameRGB(minCorner, maxCorner); + Frame = std::move(getGridRegionAsFrameRGB(minCorner, maxCorner)); + break; case frame::colormap::BGR: - Frame = getGridRegionAsFrameBGR(minCorner, maxCorner); + Frame = std::move(getGridRegionAsFrameBGR(minCorner, maxCorner)); + break; case frame::colormap::RGBA: - Frame = getGridRegionAsFrameRGBA(minCorner, maxCorner); + Frame = std::move(getGridRegionAsFrameRGBA(minCorner, maxCorner)); + break; case frame::colormap::BGRA: - Frame = getGridRegionAsFrameBGRA(minCorner, maxCorner); + Frame = std::move(getGridRegionAsFrameBGRA(minCorner, maxCorner)); + break; case frame::colormap::B: - Frame = getGridRegionAsFrameGrayscale(minCorner, maxCorner); + Frame = std::move(getGridRegionAsFrameGrayscale(minCorner, maxCorner)); + break; default: - Frame = getGridRegionAsFrameRGB(minCorner, maxCorner); + Frame = std::move(getGridRegionAsFrameRGB(minCorner, maxCorner)); + break; } - Frame.compressFrameZigZagRLE(); + //Frame.compressFrameDiff(); + Frame.compressFrameRLE(); return Frame; } diff --git a/util/output/frame.hpp b/util/output/frame.hpp index f2945ce..d05a2d1 100644 --- a/util/output/frame.hpp +++ b/util/output/frame.hpp @@ -10,97 +10,18 @@ #include #include #include +#include +#include class frame { private: std::vector _data; - std::vector _compressedData; std::unordered_map overheadmap; - size_t width; - size_t height; + size_t ratio = 1; + size_t sourceSize = 0; + size_t width = 0; + size_t height = 0; - // Huffman coding structures - struct HuffmanNode { - uint8_t value; - int freq; - std::shared_ptr left, right; - - HuffmanNode(uint8_t val, int f) : value(val), freq(f), left(nullptr), right(nullptr) {} - HuffmanNode(int f, std::shared_ptr l, std::shared_ptr r) - : value(0), freq(f), left(l), right(r) {} - - bool isLeaf() const { return !left && !right; } - }; - - struct HuffmanCompare { - bool operator()(const std::shared_ptr& a, const std::shared_ptr& b) { - return a->freq > b->freq; - } - }; - - void buildHuffmanCodes(const std::shared_ptr& node, const std::string& code, - std::unordered_map& codes) { - if (!node) return; - - if (node->isLeaf()) { - codes[node->value] = code; - return; - } - - buildHuffmanCodes(node->left, code + "0", codes); - buildHuffmanCodes(node->right, code + "1", codes); - } - - std::vector zigzagScan() { - if (width == 0 || height == 0) return _data; - - std::vector result; - result.reserve(_data.size()); - - for (size_t i = 0; i < width + height - 1; ++i) { - if (i % 2 == 0) { - // Even diagonal - go up - for (size_t row = std::min(i, height - 1); row != (size_t)-1 && i - row < width; --row) { - size_t col = i - row; - result.push_back(_data[row * width + col]); - } - } else { - // Odd diagonal - go down - for (size_t col = std::min(i, width - 1); col != (size_t)-1 && i - col < height; --col) { - size_t row = i - col; - result.push_back(_data[row * width + col]); - } - } - } - - return result; - } - - std::vector inverseZigzagScan(const std::vector& zigzagData) { - if (width == 0 || height == 0) return zigzagData; - - std::vector result(_data.size(), 0); - size_t idx = 0; - - for (size_t i = 0; i < width + height - 1; ++i) { - if (i % 2 == 0) { - // Even diagonal - go up - for (size_t row = std::min(i, height - 1); row != (size_t)-1 && i - row < width; --row) { - size_t col = i - row; - result[row * width + col] = zigzagData[idx++]; - } - } else { - // Odd diagonal - go down - for (size_t col = std::min(i, width - 1); col != (size_t)-1 && i - col < height; --col) { - size_t row = i - col; - result[row * width + col] = zigzagData[idx++]; - } - } - } - - return result; - } - public: enum class colormap { RGB, @@ -112,18 +33,15 @@ public: enum class compresstype { RLE, - ZIGZAG, DIFF, DIFFRLE, - ZIGZAGRLE, - LZ77, - LZSS, + LZ78, HUFFMAN, RAW }; - colormap colorFormat; - compresstype cformat; + colormap colorFormat = colormap::RGB; + compresstype cformat = compresstype::RAW; size_t getWidth() { return width; @@ -134,7 +52,7 @@ public: frame() {}; frame(size_t w, size_t h, colormap format = colormap::RGB) : width(w), height(h), colorFormat(format), cformat(compresstype::RAW) { - size_t channels = 3; // Default for RGB + size_t channels = 3; switch (format) { case colormap::RGBA: channels = 4; break; case colormap::BGR: channels = 3; break; @@ -147,7 +65,6 @@ public: void setData(const std::vector& data) { _data = data; - _compressedData.clear(); cformat = compresstype::RAW; } @@ -155,306 +72,86 @@ public: return _data; } - const std::vector& getCompressedData() const { - return _compressedData; - } - // Run-Length Encoding (RLE) compression frame& compressFrameRLE() { + TIME_FUNCTION; if (_data.empty()) { - _compressedData.clear(); return *this; } - - if (cformat == compresstype::ZIGZAG) { - cformat = compresstype::ZIGZAGRLE; - } else if (cformat == compresstype::DIFF) { + if (cformat == compresstype::DIFF) { cformat = compresstype::DIFFRLE; + } else if (cformat == compresstype::RLE) { + return *this; } else { cformat = compresstype::RLE; } - _compressedData.clear(); - _compressedData.reserve(_data.size() * 2); + std::vector compressedData; + compressedData.reserve(_data.size() * 2); - size_t i = 0; - while (i < _data.size()) { - uint8_t current = _data[i]; - size_t count = 1; - - // Count consecutive identical bytes - while (i + count < _data.size() && _data[i + count] == current && count < 255) { - count++; - } - - if (count > 1) { - // Encode run: 0xFF marker, count, value - _compressedData.push_back(0xFF); - _compressedData.push_back(static_cast(count)); - _compressedData.push_back(current); - i += count; + size_t width = 1; + for (size_t i = 0; i < _data.size(); i++) { + if (_data[i] == _data[i+1] && width < 255) { + width++; } else { - // Encode literal sequence - size_t literal_start = i; - while (i < _data.size() && - (i + 1 >= _data.size() || _data[i] != _data[i + 1]) && - (i - literal_start) < 127) { - i++; - } - - size_t literal_length = i - literal_start; - _compressedData.push_back(static_cast(literal_length)); - - for (size_t j = literal_start; j < i; ++j) { - _compressedData.push_back(_data[j]); - } + compressedData.push_back(width); + compressedData.push_back(_data[i]); + width = 1; } + } - - // Store compression metadata in overheadmap - overheadmap[0] = static_cast(cformat); - overheadmap[1] = static_cast(_compressedData.size() > 0 ? 1 : 0); - + ratio = compressedData.size() - _data.size(); + sourceSize = _data.size(); + _data.clear(); + _data = compressedData; return *this; } frame& decompressFrameRLE() { - if (_compressedData.empty()) { - return *this; - } - + TIME_FUNCTION; std::vector decompressed; - decompressed.reserve(_data.size()); + decompressed.reserve(sourceSize); - size_t i = 0; - while (i < _compressedData.size()) { - uint8_t marker = _compressedData[i++]; - - if (marker == 0xFF) { - // Run sequence - if (i + 1 >= _compressedData.size()) { - throw std::runtime_error("Invalid RLE data"); - } - - uint8_t count = _compressedData[i++]; - uint8_t value = _compressedData[i++]; - - for (int j = 0; j < count; ++j) { - decompressed.push_back(value); - } - } else { - // Literal sequence - uint8_t length = marker; - if (i + length > _compressedData.size()) { - throw std::runtime_error("Invalid RLE data"); - } - - for (int j = 0; j < length; ++j) { - decompressed.push_back(_compressedData[i++]); - } - } + if (_data.size() % 2 != 0) { + throw std::runtime_error("something broke (decompressFrameRLE)"); + } + for (size_t i = 0; i < _data.size(); i+=2) { + uint8_t width = _data[i]; + uint8_t value = _data[i+1]; + decompressed.insert(decompressed.end(),width, value); } _data = std::move(decompressed); cformat = compresstype::RAW; - overheadmap.clear(); - - return *this; - } - - // Zigzag compression - frame& compressFrameZigZag() { - if (cformat != compresstype::RAW) { - throw std::runtime_error("Cannot apply zigzag to already compressed data"); - } - - cformat = compresstype::ZIGZAG; - _compressedData = zigzagScan(); - - // Store metadata - overheadmap[0] = static_cast(cformat); - overheadmap[1] = static_cast(width); - overheadmap[2] = static_cast(height); - - return *this; - } - - frame& decompressFrameZigZag() { - if (_compressedData.empty()) { - return *this; - } - - _data = inverseZigzagScan(_compressedData); - cformat = compresstype::RAW; - overheadmap.clear(); return *this; } // Differential compression frame& compressFrameDiff() { - if (cformat != compresstype::RAW) { - throw std::runtime_error("Cannot apply diff to already compressed data"); - } - - cformat = compresstype::DIFF; - _compressedData.clear(); - _compressedData.reserve(_data.size()); - - if (_data.empty()) { - return *this; - } - - // First value remains the same - _compressedData.push_back(_data[0]); - - // Subsequent values are differences - for (size_t i = 1; i < _data.size(); ++i) { - int16_t diff = static_cast(_data[i]) - static_cast(_data[i - 1]); - // Convert to unsigned with bias of 128 - _compressedData.push_back(static_cast((diff + 128) & 0xFF)); - } - - // Store metadata - overheadmap[0] = static_cast(cformat); - overheadmap[1] = static_cast(_data.size() > 0 ? 1 : 0); - - return *this; + // TODO + std::logic_error("Function not yet implemented"); } frame& decompressFrameDiff() { - if (_compressedData.empty()) { - return *this; - } - - std::vector original; - original.reserve(_compressedData.size()); - - // First value is original - original.push_back(_compressedData[0]); - - // Reconstruct subsequent values - for (size_t i = 1; i < _compressedData.size(); ++i) { - int16_t reconstructed = static_cast(original[i - 1]) + - (static_cast(_compressedData[i]) - 128); - // Clamp to 0-255 - reconstructed = std::max(0, std::min(255, static_cast(reconstructed))); - original.push_back(static_cast(reconstructed)); - } - - _data = std::move(original); - cformat = compresstype::RAW; - overheadmap.clear(); - - return *this; + // TODO + std::logic_error("Function not yet implemented"); } - // Huffman compression frame& compressFrameHuffman() { - cformat = compresstype::HUFFMAN; - _compressedData.clear(); - - if (_data.empty()) { - return *this; - } - - // Calculate frequency of each byte value - std::unordered_map freq; - for (uint8_t byte : _data) { - freq[byte]++; - } - - // Build Huffman tree - std::priority_queue, - std::vector>, - HuffmanCompare> pq; - - for (const auto& pair : freq) { - pq.push(std::make_shared(pair.first, pair.second)); - } - - while (pq.size() > 1) { - auto left = pq.top(); pq.pop(); - auto right = pq.top(); pq.pop(); - - auto parent = std::make_shared(left->freq + right->freq, left, right); - pq.push(parent); - } - - auto root = pq.top(); - - // Build codes - std::unordered_map codes; - buildHuffmanCodes(root, "", codes); - - // Encode data - std::string bitString; - for (uint8_t byte : _data) { - bitString += codes[byte]; - } - - // Convert bit string to bytes - // Store frequency table size - _compressedData.push_back(static_cast(freq.size())); - - // Store frequency table - for (const auto& pair : freq) { - _compressedData.push_back(pair.first); - // Store frequency as 4 bytes - for (int i = 0; i < 4; ++i) { - _compressedData.push_back(static_cast((pair.second >> (i * 8)) & 0xFF)); - } - } - - // Store encoded data - uint8_t currentByte = 0; - int bitCount = 0; - - for (char bit : bitString) { - currentByte = (currentByte << 1) | (bit == '1' ? 1 : 0); - bitCount++; - - if (bitCount == 8) { - _compressedData.push_back(currentByte); - currentByte = 0; - bitCount = 0; - } - } - - // Pad last byte if necessary - if (bitCount > 0) { - currentByte <<= (8 - bitCount); - _compressedData.push_back(currentByte); - // Store number of padding bits - _compressedData.push_back(static_cast(8 - bitCount)); - } else { - _compressedData.push_back(0); // No padding - } - - // Store metadata - overheadmap[0] = static_cast(cformat); - overheadmap[1] = static_cast(freq.size()); - - return *this; + // TODO + std::logic_error("Function not yet implemented"); } // Combined compression methods frame& compressFrameZigZagRLE() { - compressFrameZigZag(); - // Store intermediate zigzag data temporarily - auto zigzagData = _compressedData; - _data = std::move(zigzagData); - cformat = compresstype::ZIGZAG; - return compressFrameRLE(); + // TODO + std::logic_error("Function not yet implemented"); } frame& compressFrameDiffRLE() { - compressFrameDiff(); - // Store intermediate diff data temporarily - auto diffData = _compressedData; - _data = std::move(diffData); - cformat = compresstype::DIFF; - return compressFrameRLE(); + // TODO + std::logic_error("Function not yet implemented"); } // Generic decompression that detects compression type @@ -462,35 +159,81 @@ public: switch (cformat) { case compresstype::RLE: return decompressFrameRLE(); - case compresstype::ZIGZAG: - return decompressFrameZigZag(); + break; case compresstype::DIFF: return decompressFrameDiff(); - case compresstype::ZIGZAGRLE: + break; case compresstype::DIFFRLE: // For combined methods, first decompress RLE then the base method decompressFrameRLE(); - // Now _data contains the intermediate compressed form - if (cformat == compresstype::ZIGZAGRLE) { - cformat = compresstype::ZIGZAG; - return decompressFrameZigZag(); - } else { - cformat = compresstype::DIFF; - return decompressFrameDiff(); - } + cformat = compresstype::DIFF; + return decompressFrameDiff(); + break; case compresstype::HUFFMAN: // Huffman decompression would be implemented here throw std::runtime_error("Huffman decompression not fully implemented"); + break; case compresstype::RAW: default: return *this; // Already decompressed } } - // Get compression ratio double getCompressionRatio() const { - if (_data.empty() || _compressedData.empty()) return 0.0; - return static_cast(_data.size()) / _compressedData.size(); + if (_data.empty() || sourceSize == 0) return 0.0; + return static_cast(sourceSize) / _data.size(); + } + + // Get source size (uncompressed size) + size_t getSourceSize() const { + return sourceSize; + } + + // Get compressed size + size_t getCompressedSize() const { + return _data.size(); + } + + // Print compression information + void printCompressionInfo() const { + std::cout << "Compression Type: "; + switch (cformat) { + case compresstype::RLE: std::cout << "RLE"; break; + case compresstype::DIFF: std::cout << "DIFF"; break; + case compresstype::DIFFRLE: std::cout << "DIFF + RLE"; break; + case compresstype::HUFFMAN: std::cout << "HUFFMAN"; break; + case compresstype::RAW: std::cout << "RAW (uncompressed)"; break; + default: std::cout << "UNKNOWN"; break; + } + std::cout << std::endl; + + std::cout << "Source Size: " << getSourceSize() << " bytes" << std::endl; + std::cout << "Compressed Size: " << getCompressedSize() << " bytes" << std::endl; + std::cout << "Compression Ratio: " << getCompressionRatio() << ":1" << std::endl; + + if (getCompressionRatio() > 1.0) { + double savings = (1.0 - (1.0 / getCompressionRatio())) * 100.0; + std::cout << "Space Savings: " << savings << "%" << std::endl; + } + } + + // Print compression information in a compact format + void printCompressionStats() const { + std::cout << "[" << getCompressionTypeString() << "] " + << getSourceSize() << "B -> " << getCompressedSize() << "B " + << "(ratio: " << getCompressionRatio() << ":1)" << std::endl; + } + + // Get compression type as string + std::string getCompressionTypeString() const { + switch (cformat) { + case compresstype::RLE: return "RLE"; + case compresstype::DIFF: return "DIFF"; + case compresstype::DIFFRLE: return "DIFF+RLE"; + case compresstype::HUFFMAN: return "HUFFMAN"; + case compresstype::RAW: return "RAW"; + default: return "UNKNOWN"; + } } compresstype getCompressionType() const { @@ -502,7 +245,7 @@ public: } bool isCompressed() const { - return cformat != compresstype::RAW && !_compressedData.empty(); + return cformat != compresstype::RAW; } };