custom compression is fun.
This commit is contained in:
@@ -13,7 +13,7 @@
|
|||||||
struct AnimationConfig {
|
struct AnimationConfig {
|
||||||
int width = 1024;
|
int width = 1024;
|
||||||
int height = 1024;
|
int height = 1024;
|
||||||
int totalFrames = 480;
|
int totalFrames = 4800;
|
||||||
float fps = 30.0f;
|
float fps = 30.0f;
|
||||||
int numSeeds = 8;
|
int numSeeds = 8;
|
||||||
};
|
};
|
||||||
@@ -70,13 +70,11 @@ void expandPixel(Grid2& grid, AnimationConfig config, std::vector<std::tuple<siz
|
|||||||
TIME_FUNCTION;
|
TIME_FUNCTION;
|
||||||
std::vector<std::tuple<size_t, Vec2, Vec4>> newseeds;
|
std::vector<std::tuple<size_t, Vec2, Vec4>> newseeds;
|
||||||
|
|
||||||
|
|
||||||
std::unordered_set<size_t> visitedThisFrame;
|
std::unordered_set<size_t> visitedThisFrame;
|
||||||
for (const auto& seed : seeds) {
|
for (const auto& seed : seeds) {
|
||||||
visitedThisFrame.insert(std::get<0>(seed));
|
visitedThisFrame.insert(std::get<0>(seed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
for (const std::tuple<size_t, Vec2, Vec4>& seed : seeds) {
|
for (const std::tuple<size_t, Vec2, Vec4>& seed : seeds) {
|
||||||
size_t id = std::get<0>(seed);
|
size_t id = std::get<0>(seed);
|
||||||
Vec2 seedPOS = std::get<1>(seed);
|
Vec2 seedPOS = std::get<1>(seed);
|
||||||
@@ -88,7 +86,6 @@ void expandPixel(Grid2& grid, AnimationConfig config, std::vector<std::tuple<siz
|
|||||||
}
|
}
|
||||||
visitedThisFrame.insert(neighbor);
|
visitedThisFrame.insert(neighbor);
|
||||||
|
|
||||||
|
|
||||||
Vec2 neipos = grid.getPositionID(neighbor);
|
Vec2 neipos = grid.getPositionID(neighbor);
|
||||||
Vec4 neighborColor = grid.getColor(neighbor);
|
Vec4 neighborColor = grid.getColor(neighbor);
|
||||||
float distance = seedPOS.distance(neipos);
|
float distance = seedPOS.distance(neipos);
|
||||||
@@ -121,8 +118,34 @@ bool exportavi(std::vector<frame> frames, AnimationConfig config) {
|
|||||||
std::string filename = "output/chromatic_transformation.avi";
|
std::string filename = "output/chromatic_transformation.avi";
|
||||||
|
|
||||||
std::cout << "Frame count: " << frames.size() << std::endl;
|
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<double>(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";
|
std::filesystem::path dir = "output";
|
||||||
if (!std::filesystem::exists(dir)) {
|
if (!std::filesystem::exists(dir)) {
|
||||||
@@ -132,14 +155,15 @@ bool exportavi(std::vector<frame> frames, AnimationConfig config) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = AVIWriter::saveAVIFromCompressedFrames(filename,frames,frames[0].getWidth()+1,frames[0].getHeight()+1, config.fps);
|
bool success = AVIWriter::saveAVIFromCompressedFrames(filename, frames, frames[0].getWidth(), frames[0].getHeight(), config.fps);
|
||||||
|
|
||||||
//bool success = AVIWriter::saveAVI(filename, frames, config.width+1, config.height+1, config.fps);
|
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
// Check if file actually exists
|
// Check if file actually exists
|
||||||
if (std::filesystem::exists(filename)) {
|
if (std::filesystem::exists(filename)) {
|
||||||
auto file_size = std::filesystem::file_size(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 {
|
} else {
|
||||||
std::cout << "Failed to save AVI file!" << std::endl;
|
std::cout << "Failed to save AVI file!" << std::endl;
|
||||||
@@ -152,20 +176,21 @@ int main() {
|
|||||||
AnimationConfig config;
|
AnimationConfig config;
|
||||||
|
|
||||||
Grid2 grid = setup(config);
|
Grid2 grid = setup(config);
|
||||||
//grid.updateNeighborMap();
|
|
||||||
Preview(grid);
|
Preview(grid);
|
||||||
std::vector<std::tuple<size_t, Vec2, Vec4>> seeds = pickSeeds(grid,config);
|
std::vector<std::tuple<size_t, Vec2, Vec4>> seeds = pickSeeds(grid,config);
|
||||||
//std::vector<std::vector<uint8_t>> frames;
|
|
||||||
std::vector<frame> frames;
|
std::vector<frame> frames;
|
||||||
|
|
||||||
for (int i = 0; i < config.totalFrames; ++i){
|
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);
|
expandPixel(grid,config,seeds);
|
||||||
int width;
|
|
||||||
int height;
|
|
||||||
//std::vector<uint8_t> bgrframe;
|
|
||||||
frame bgrframe = grid.getGridAsFrame(frame::colormap::BGR);
|
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);
|
frames.push_back(bgrframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -138,7 +138,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<size_t> queryRange(const Vec2& center, float radius) const {
|
std::vector<size_t> queryRange(const Vec2& center, float radius) const {
|
||||||
TIME_FUNCTION;
|
|
||||||
std::vector<size_t> results;
|
std::vector<size_t> results;
|
||||||
float radiusSq = radius * radius;
|
float radiusSq = radius * radius;
|
||||||
|
|
||||||
@@ -637,7 +636,6 @@ public:
|
|||||||
|
|
||||||
// Get region as frame (Grayscale format)
|
// Get region as frame (Grayscale format)
|
||||||
frame getGridRegionAsFrameGrayscale(const Vec2& minCorner, const Vec2& maxCorner) const {
|
frame getGridRegionAsFrameGrayscale(const Vec2& minCorner, const Vec2& maxCorner) const {
|
||||||
TIME_FUNCTION;
|
|
||||||
int width, height;
|
int width, height;
|
||||||
std::vector<uint8_t> rgbData;
|
std::vector<uint8_t> rgbData;
|
||||||
getGridRegionAsRGB(minCorner, maxCorner, width, height, rgbData);
|
getGridRegionAsRGB(minCorner, maxCorner, width, height, rgbData);
|
||||||
@@ -669,19 +667,26 @@ public:
|
|||||||
|
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case frame::colormap::RGB:
|
case frame::colormap::RGB:
|
||||||
Frame = getGridRegionAsFrameRGB(minCorner, maxCorner);
|
Frame = std::move(getGridRegionAsFrameRGB(minCorner, maxCorner));
|
||||||
|
break;
|
||||||
case frame::colormap::BGR:
|
case frame::colormap::BGR:
|
||||||
Frame = getGridRegionAsFrameBGR(minCorner, maxCorner);
|
Frame = std::move(getGridRegionAsFrameBGR(minCorner, maxCorner));
|
||||||
|
break;
|
||||||
case frame::colormap::RGBA:
|
case frame::colormap::RGBA:
|
||||||
Frame = getGridRegionAsFrameRGBA(minCorner, maxCorner);
|
Frame = std::move(getGridRegionAsFrameRGBA(minCorner, maxCorner));
|
||||||
|
break;
|
||||||
case frame::colormap::BGRA:
|
case frame::colormap::BGRA:
|
||||||
Frame = getGridRegionAsFrameBGRA(minCorner, maxCorner);
|
Frame = std::move(getGridRegionAsFrameBGRA(minCorner, maxCorner));
|
||||||
|
break;
|
||||||
case frame::colormap::B:
|
case frame::colormap::B:
|
||||||
Frame = getGridRegionAsFrameGrayscale(minCorner, maxCorner);
|
Frame = std::move(getGridRegionAsFrameGrayscale(minCorner, maxCorner));
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Frame = getGridRegionAsFrameRGB(minCorner, maxCorner);
|
Frame = std::move(getGridRegionAsFrameRGB(minCorner, maxCorner));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
Frame.compressFrameZigZagRLE();
|
//Frame.compressFrameDiff();
|
||||||
|
Frame.compressFrameRLE();
|
||||||
return Frame;
|
return Frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,96 +10,17 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
class frame {
|
class frame {
|
||||||
private:
|
private:
|
||||||
std::vector<uint8_t> _data;
|
std::vector<uint8_t> _data;
|
||||||
std::vector<uint8_t> _compressedData;
|
|
||||||
std::unordered_map<size_t, uint8_t> overheadmap;
|
std::unordered_map<size_t, uint8_t> overheadmap;
|
||||||
size_t width;
|
size_t ratio = 1;
|
||||||
size_t height;
|
size_t sourceSize = 0;
|
||||||
|
size_t width = 0;
|
||||||
// Huffman coding structures
|
size_t height = 0;
|
||||||
struct HuffmanNode {
|
|
||||||
uint8_t value;
|
|
||||||
int freq;
|
|
||||||
std::shared_ptr<HuffmanNode> left, right;
|
|
||||||
|
|
||||||
HuffmanNode(uint8_t val, int f) : value(val), freq(f), left(nullptr), right(nullptr) {}
|
|
||||||
HuffmanNode(int f, std::shared_ptr<HuffmanNode> l, std::shared_ptr<HuffmanNode> r)
|
|
||||||
: value(0), freq(f), left(l), right(r) {}
|
|
||||||
|
|
||||||
bool isLeaf() const { return !left && !right; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct HuffmanCompare {
|
|
||||||
bool operator()(const std::shared_ptr<HuffmanNode>& a, const std::shared_ptr<HuffmanNode>& b) {
|
|
||||||
return a->freq > b->freq;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void buildHuffmanCodes(const std::shared_ptr<HuffmanNode>& node, const std::string& code,
|
|
||||||
std::unordered_map<uint8_t, std::string>& 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<uint8_t> zigzagScan() {
|
|
||||||
if (width == 0 || height == 0) return _data;
|
|
||||||
|
|
||||||
std::vector<uint8_t> 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<uint8_t> inverseZigzagScan(const std::vector<uint8_t>& zigzagData) {
|
|
||||||
if (width == 0 || height == 0) return zigzagData;
|
|
||||||
|
|
||||||
std::vector<uint8_t> 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:
|
public:
|
||||||
enum class colormap {
|
enum class colormap {
|
||||||
@@ -112,18 +33,15 @@ public:
|
|||||||
|
|
||||||
enum class compresstype {
|
enum class compresstype {
|
||||||
RLE,
|
RLE,
|
||||||
ZIGZAG,
|
|
||||||
DIFF,
|
DIFF,
|
||||||
DIFFRLE,
|
DIFFRLE,
|
||||||
ZIGZAGRLE,
|
LZ78,
|
||||||
LZ77,
|
|
||||||
LZSS,
|
|
||||||
HUFFMAN,
|
HUFFMAN,
|
||||||
RAW
|
RAW
|
||||||
};
|
};
|
||||||
|
|
||||||
colormap colorFormat;
|
colormap colorFormat = colormap::RGB;
|
||||||
compresstype cformat;
|
compresstype cformat = compresstype::RAW;
|
||||||
|
|
||||||
size_t getWidth() {
|
size_t getWidth() {
|
||||||
return width;
|
return width;
|
||||||
@@ -134,7 +52,7 @@ public:
|
|||||||
frame() {};
|
frame() {};
|
||||||
frame(size_t w, size_t h, colormap format = colormap::RGB)
|
frame(size_t w, size_t h, colormap format = colormap::RGB)
|
||||||
: width(w), height(h), colorFormat(format), cformat(compresstype::RAW) {
|
: width(w), height(h), colorFormat(format), cformat(compresstype::RAW) {
|
||||||
size_t channels = 3; // Default for RGB
|
size_t channels = 3;
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case colormap::RGBA: channels = 4; break;
|
case colormap::RGBA: channels = 4; break;
|
||||||
case colormap::BGR: channels = 3; break;
|
case colormap::BGR: channels = 3; break;
|
||||||
@@ -147,7 +65,6 @@ public:
|
|||||||
|
|
||||||
void setData(const std::vector<uint8_t>& data) {
|
void setData(const std::vector<uint8_t>& data) {
|
||||||
_data = data;
|
_data = data;
|
||||||
_compressedData.clear();
|
|
||||||
cformat = compresstype::RAW;
|
cformat = compresstype::RAW;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,306 +72,86 @@ public:
|
|||||||
return _data;
|
return _data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<uint8_t>& getCompressedData() const {
|
|
||||||
return _compressedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run-Length Encoding (RLE) compression
|
// Run-Length Encoding (RLE) compression
|
||||||
frame& compressFrameRLE() {
|
frame& compressFrameRLE() {
|
||||||
|
TIME_FUNCTION;
|
||||||
if (_data.empty()) {
|
if (_data.empty()) {
|
||||||
_compressedData.clear();
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
if (cformat == compresstype::DIFF) {
|
||||||
if (cformat == compresstype::ZIGZAG) {
|
|
||||||
cformat = compresstype::ZIGZAGRLE;
|
|
||||||
} else if (cformat == compresstype::DIFF) {
|
|
||||||
cformat = compresstype::DIFFRLE;
|
cformat = compresstype::DIFFRLE;
|
||||||
|
} else if (cformat == compresstype::RLE) {
|
||||||
|
return *this;
|
||||||
} else {
|
} else {
|
||||||
cformat = compresstype::RLE;
|
cformat = compresstype::RLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
_compressedData.clear();
|
std::vector<uint8_t> compressedData;
|
||||||
_compressedData.reserve(_data.size() * 2);
|
compressedData.reserve(_data.size() * 2);
|
||||||
|
|
||||||
size_t i = 0;
|
size_t width = 1;
|
||||||
while (i < _data.size()) {
|
for (size_t i = 0; i < _data.size(); i++) {
|
||||||
uint8_t current = _data[i];
|
if (_data[i] == _data[i+1] && width < 255) {
|
||||||
size_t count = 1;
|
width++;
|
||||||
|
|
||||||
// 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<uint8_t>(count));
|
|
||||||
_compressedData.push_back(current);
|
|
||||||
i += count;
|
|
||||||
} else {
|
} else {
|
||||||
// Encode literal sequence
|
compressedData.push_back(width);
|
||||||
size_t literal_start = i;
|
compressedData.push_back(_data[i]);
|
||||||
while (i < _data.size() &&
|
width = 1;
|
||||||
(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<uint8_t>(literal_length));
|
|
||||||
|
|
||||||
for (size_t j = literal_start; j < i; ++j) {
|
|
||||||
_compressedData.push_back(_data[j]);
|
|
||||||
}
|
}
|
||||||
}
|
ratio = compressedData.size() - _data.size();
|
||||||
}
|
sourceSize = _data.size();
|
||||||
|
_data.clear();
|
||||||
// Store compression metadata in overheadmap
|
_data = compressedData;
|
||||||
overheadmap[0] = static_cast<uint8_t>(cformat);
|
|
||||||
overheadmap[1] = static_cast<uint8_t>(_compressedData.size() > 0 ? 1 : 0);
|
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
frame& decompressFrameRLE() {
|
frame& decompressFrameRLE() {
|
||||||
if (_compressedData.empty()) {
|
TIME_FUNCTION;
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<uint8_t> decompressed;
|
std::vector<uint8_t> decompressed;
|
||||||
decompressed.reserve(_data.size());
|
decompressed.reserve(sourceSize);
|
||||||
|
|
||||||
size_t i = 0;
|
if (_data.size() % 2 != 0) {
|
||||||
while (i < _compressedData.size()) {
|
throw std::runtime_error("something broke (decompressFrameRLE)");
|
||||||
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++]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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);
|
_data = std::move(decompressed);
|
||||||
cformat = compresstype::RAW;
|
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<uint8_t>(cformat);
|
|
||||||
overheadmap[1] = static_cast<uint8_t>(width);
|
|
||||||
overheadmap[2] = static_cast<uint8_t>(height);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
frame& decompressFrameZigZag() {
|
|
||||||
if (_compressedData.empty()) {
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = inverseZigzagScan(_compressedData);
|
|
||||||
cformat = compresstype::RAW;
|
|
||||||
overheadmap.clear();
|
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Differential compression
|
// Differential compression
|
||||||
frame& compressFrameDiff() {
|
frame& compressFrameDiff() {
|
||||||
if (cformat != compresstype::RAW) {
|
// TODO
|
||||||
throw std::runtime_error("Cannot apply diff to already compressed data");
|
std::logic_error("Function not yet implemented");
|
||||||
}
|
|
||||||
|
|
||||||
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<int16_t>(_data[i]) - static_cast<int16_t>(_data[i - 1]);
|
|
||||||
// Convert to unsigned with bias of 128
|
|
||||||
_compressedData.push_back(static_cast<uint8_t>((diff + 128) & 0xFF));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store metadata
|
|
||||||
overheadmap[0] = static_cast<uint8_t>(cformat);
|
|
||||||
overheadmap[1] = static_cast<uint8_t>(_data.size() > 0 ? 1 : 0);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
frame& decompressFrameDiff() {
|
frame& decompressFrameDiff() {
|
||||||
if (_compressedData.empty()) {
|
// TODO
|
||||||
return *this;
|
std::logic_error("Function not yet implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> 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<int16_t>(original[i - 1]) +
|
|
||||||
(static_cast<int16_t>(_compressedData[i]) - 128);
|
|
||||||
// Clamp to 0-255
|
|
||||||
reconstructed = std::max(0, std::min(255, static_cast<int>(reconstructed)));
|
|
||||||
original.push_back(static_cast<uint8_t>(reconstructed));
|
|
||||||
}
|
|
||||||
|
|
||||||
_data = std::move(original);
|
|
||||||
cformat = compresstype::RAW;
|
|
||||||
overheadmap.clear();
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Huffman compression
|
// Huffman compression
|
||||||
frame& compressFrameHuffman() {
|
frame& compressFrameHuffman() {
|
||||||
cformat = compresstype::HUFFMAN;
|
// TODO
|
||||||
_compressedData.clear();
|
std::logic_error("Function not yet implemented");
|
||||||
|
|
||||||
if (_data.empty()) {
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate frequency of each byte value
|
|
||||||
std::unordered_map<uint8_t, int> freq;
|
|
||||||
for (uint8_t byte : _data) {
|
|
||||||
freq[byte]++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build Huffman tree
|
|
||||||
std::priority_queue<std::shared_ptr<HuffmanNode>,
|
|
||||||
std::vector<std::shared_ptr<HuffmanNode>>,
|
|
||||||
HuffmanCompare> pq;
|
|
||||||
|
|
||||||
for (const auto& pair : freq) {
|
|
||||||
pq.push(std::make_shared<HuffmanNode>(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<HuffmanNode>(left->freq + right->freq, left, right);
|
|
||||||
pq.push(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto root = pq.top();
|
|
||||||
|
|
||||||
// Build codes
|
|
||||||
std::unordered_map<uint8_t, std::string> 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<uint8_t>(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<uint8_t>((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<uint8_t>(8 - bitCount));
|
|
||||||
} else {
|
|
||||||
_compressedData.push_back(0); // No padding
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store metadata
|
|
||||||
overheadmap[0] = static_cast<uint8_t>(cformat);
|
|
||||||
overheadmap[1] = static_cast<uint8_t>(freq.size());
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combined compression methods
|
// Combined compression methods
|
||||||
frame& compressFrameZigZagRLE() {
|
frame& compressFrameZigZagRLE() {
|
||||||
compressFrameZigZag();
|
// TODO
|
||||||
// Store intermediate zigzag data temporarily
|
std::logic_error("Function not yet implemented");
|
||||||
auto zigzagData = _compressedData;
|
|
||||||
_data = std::move(zigzagData);
|
|
||||||
cformat = compresstype::ZIGZAG;
|
|
||||||
return compressFrameRLE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
frame& compressFrameDiffRLE() {
|
frame& compressFrameDiffRLE() {
|
||||||
compressFrameDiff();
|
// TODO
|
||||||
// Store intermediate diff data temporarily
|
std::logic_error("Function not yet implemented");
|
||||||
auto diffData = _compressedData;
|
|
||||||
_data = std::move(diffData);
|
|
||||||
cformat = compresstype::DIFF;
|
|
||||||
return compressFrameRLE();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic decompression that detects compression type
|
// Generic decompression that detects compression type
|
||||||
@@ -462,35 +159,81 @@ public:
|
|||||||
switch (cformat) {
|
switch (cformat) {
|
||||||
case compresstype::RLE:
|
case compresstype::RLE:
|
||||||
return decompressFrameRLE();
|
return decompressFrameRLE();
|
||||||
case compresstype::ZIGZAG:
|
break;
|
||||||
return decompressFrameZigZag();
|
|
||||||
case compresstype::DIFF:
|
case compresstype::DIFF:
|
||||||
return decompressFrameDiff();
|
return decompressFrameDiff();
|
||||||
case compresstype::ZIGZAGRLE:
|
break;
|
||||||
case compresstype::DIFFRLE:
|
case compresstype::DIFFRLE:
|
||||||
// For combined methods, first decompress RLE then the base method
|
// For combined methods, first decompress RLE then the base method
|
||||||
decompressFrameRLE();
|
decompressFrameRLE();
|
||||||
// Now _data contains the intermediate compressed form
|
|
||||||
if (cformat == compresstype::ZIGZAGRLE) {
|
|
||||||
cformat = compresstype::ZIGZAG;
|
|
||||||
return decompressFrameZigZag();
|
|
||||||
} else {
|
|
||||||
cformat = compresstype::DIFF;
|
cformat = compresstype::DIFF;
|
||||||
return decompressFrameDiff();
|
return decompressFrameDiff();
|
||||||
}
|
break;
|
||||||
case compresstype::HUFFMAN:
|
case compresstype::HUFFMAN:
|
||||||
// Huffman decompression would be implemented here
|
// Huffman decompression would be implemented here
|
||||||
throw std::runtime_error("Huffman decompression not fully implemented");
|
throw std::runtime_error("Huffman decompression not fully implemented");
|
||||||
|
break;
|
||||||
case compresstype::RAW:
|
case compresstype::RAW:
|
||||||
default:
|
default:
|
||||||
return *this; // Already decompressed
|
return *this; // Already decompressed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get compression ratio
|
|
||||||
double getCompressionRatio() const {
|
double getCompressionRatio() const {
|
||||||
if (_data.empty() || _compressedData.empty()) return 0.0;
|
if (_data.empty() || sourceSize == 0) return 0.0;
|
||||||
return static_cast<double>(_data.size()) / _compressedData.size();
|
return static_cast<double>(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 {
|
compresstype getCompressionType() const {
|
||||||
@@ -502,7 +245,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isCompressed() const {
|
bool isCompressed() const {
|
||||||
return cformat != compresstype::RAW && !_compressedData.empty();
|
return cformat != compresstype::RAW;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user