working better
This commit is contained in:
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -86,7 +86,11 @@
|
|||||||
"variant": "cpp",
|
"variant": "cpp",
|
||||||
"__nullptr": "cpp",
|
"__nullptr": "cpp",
|
||||||
"unordered_set": "cpp",
|
"unordered_set": "cpp",
|
||||||
"queue": "cpp"
|
"queue": "cpp",
|
||||||
|
"__hash_table": "cpp",
|
||||||
|
"__split_buffer": "cpp",
|
||||||
|
"__tree": "cpp",
|
||||||
|
"stack": "cpp"
|
||||||
},
|
},
|
||||||
"files.exclude": {
|
"files.exclude": {
|
||||||
"**/*.rpyc": true,
|
"**/*.rpyc": true,
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
#include "../util/timing_decorator.cpp"
|
#include "../util/timing_decorator.cpp"
|
||||||
|
|
||||||
struct AnimationConfig {
|
struct AnimationConfig {
|
||||||
int width = 256;
|
int width = 1024;
|
||||||
int height = 256;
|
int height = 1024;
|
||||||
int totalFrames = 480;
|
int totalFrames = 480;
|
||||||
float fps = 30.0f;
|
float fps = 30.0f;
|
||||||
int numSeeds = 8;
|
int numSeeds = 8;
|
||||||
@@ -115,12 +115,13 @@ void expandPixel(Grid2& grid, AnimationConfig config, std::vector<std::tuple<siz
|
|||||||
seeds = std::move(newseeds);
|
seeds = std::move(newseeds);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool exportavi(std::vector<std::vector<uint8_t>> frames, AnimationConfig config) {
|
//bool exportavi(std::vector<std::vector<uint8_t>> frames, AnimationConfig config) {
|
||||||
|
bool exportavi(std::vector<frame> frames, AnimationConfig config) {
|
||||||
TIME_FUNCTION;
|
TIME_FUNCTION;
|
||||||
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 << "Frame size: " << (frames.empty() ? 0 : frames[0].size()) << std::endl;
|
||||||
std::cout << "Width: " << config.width << ", Height: " << config.height << std::endl;
|
std::cout << "Width: " << config.width << ", Height: " << config.height << std::endl;
|
||||||
|
|
||||||
std::filesystem::path dir = "output";
|
std::filesystem::path dir = "output";
|
||||||
@@ -131,7 +132,9 @@ bool exportavi(std::vector<std::vector<uint8_t>> frames, AnimationConfig config)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = AVIWriter::saveAVI(filename, frames, config.width+1, config.height+1, config.fps);
|
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);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
// Check if file actually exists
|
// Check if file actually exists
|
||||||
@@ -152,16 +155,18 @@ int main() {
|
|||||||
//grid.updateNeighborMap();
|
//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<std::vector<uint8_t>> frames;
|
||||||
|
std::vector<frame> frames;
|
||||||
|
|
||||||
for (int i = 0; i < config.totalFrames; ++i){
|
for (int i = 0; i < config.totalFrames; ++i){
|
||||||
std::cout << "Processing frame " << i + 1 << "/" << config.totalFrames << std::endl;
|
std::cout << "Processing bgrframe " << i + 1 << "/" << config.totalFrames << std::endl;
|
||||||
expandPixel(grid,config,seeds);
|
expandPixel(grid,config,seeds);
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
std::vector<uint8_t> frame;
|
//std::vector<uint8_t> bgrframe;
|
||||||
grid.getGridAsBGR(width,height,frame);
|
frame bgrframe = grid.getGridAsFrame(frame::colormap::BGR);
|
||||||
frames.push_back(frame);
|
//grid.getGridAsBGR(width,height,bgrframe);
|
||||||
|
frames.push_back(bgrframe);
|
||||||
}
|
}
|
||||||
|
|
||||||
exportavi(frames,config);
|
exportavi(frames,config);
|
||||||
|
|||||||
@@ -564,85 +564,154 @@ public:
|
|||||||
getGridRegionAsBGR(minCorner, maxCorner, width, height, bgrData);
|
getGridRegionAsBGR(minCorner, maxCorner, width, height, bgrData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get region as frame with customizable channels
|
|
||||||
void getGridRegionAsFrame(const Vec2& minCorner, const Vec2& maxCorner,
|
//frame stuff
|
||||||
int& width, int& height, frame& outputFrame,
|
frame getGridRegionAsFrameRGB(const Vec2& minCorner, const Vec2& maxCorner) const {
|
||||||
const std::vector<char>& channels = {'R', 'G', 'B'}) const {
|
|
||||||
TIME_FUNCTION;
|
TIME_FUNCTION;
|
||||||
// Calculate dimensions
|
|
||||||
width = static_cast<int>(maxCorner.x - minCorner.x);
|
|
||||||
height = static_cast<int>(maxCorner.y - minCorner.y);
|
|
||||||
|
|
||||||
if (width <= 0 || height <= 0) {
|
|
||||||
width = height = 0;
|
|
||||||
outputFrame.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize frame with specified channels
|
|
||||||
outputFrame.resize(width, height, channels);
|
|
||||||
// For each position in the grid, find the corresponding pixel
|
|
||||||
for (const auto& [id, pos] : Positions) {
|
|
||||||
if (pos.x >= minCorner.x && pos.x < maxCorner.x &&
|
|
||||||
pos.y >= minCorner.y && pos.y < maxCorner.y) {
|
|
||||||
|
|
||||||
// Calculate pixel coordinates
|
|
||||||
int pixelX = static_cast<int>(pos.x - minCorner.x);
|
|
||||||
int pixelY = static_cast<int>(pos.y - minCorner.y);
|
|
||||||
|
|
||||||
// Ensure within bounds
|
|
||||||
if (pixelX >= 0 && pixelX < width && pixelY >= 0 && pixelY < height) {
|
|
||||||
// Get color
|
|
||||||
const Vec4& color = Colors.at(id);
|
|
||||||
|
|
||||||
// Set pixel data based on requested channels
|
|
||||||
for (size_t channel_idx = 0; channel_idx < channels.size(); ++channel_idx) {
|
|
||||||
float value = 0.0f;
|
|
||||||
switch (channels[channel_idx]) {
|
|
||||||
case 'R': case 'r': value = color.r; break;
|
|
||||||
case 'G': case 'g': value = color.g; break;
|
|
||||||
case 'B': case 'b': value = color.b; break;
|
|
||||||
case 'A': case 'a': value = color.a; break;
|
|
||||||
case 'X': case 'x': value = pos.x - minCorner.x; break; // Normalized X
|
|
||||||
case 'Y': case 'y': value = pos.y - minCorner.y; break; // Normalized Y
|
|
||||||
case 'S': case 's': value = Sizes.at(id); break; // Size
|
|
||||||
case 'I': case 'i': value = static_cast<float>(id) / Positions.size(); break; // Normalized ID
|
|
||||||
default: value = 0.0f; break;
|
|
||||||
}
|
|
||||||
outputFrame.at(pixelY, pixelX, channel_idx) = static_cast<uint8_t>(value * 255);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get full grid as frame
|
|
||||||
void getGridAsFrame(frame& outputFrame, const std::vector<char>& channels = {'R', 'G', 'B'}) {
|
|
||||||
int width, height;
|
int width, height;
|
||||||
|
std::vector<uint8_t> rgbData;
|
||||||
|
getGridRegionAsRGB(minCorner, maxCorner, width, height, rgbData);
|
||||||
|
|
||||||
|
frame resultFrame(width, height, frame::colormap::RGB);
|
||||||
|
resultFrame.setData(rgbData);
|
||||||
|
return resultFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get region as frame (BGR format)
|
||||||
|
frame getGridRegionAsFrameBGR(const Vec2& minCorner, const Vec2& maxCorner) const {
|
||||||
|
TIME_FUNCTION;
|
||||||
|
int width, height;
|
||||||
|
std::vector<uint8_t> bgrData;
|
||||||
|
getGridRegionAsBGR(minCorner, maxCorner, width, height, bgrData);
|
||||||
|
|
||||||
|
frame resultFrame(width, height, frame::colormap::BGR);
|
||||||
|
resultFrame.setData(bgrData);
|
||||||
|
return resultFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get region as frame (RGBA format)
|
||||||
|
frame getGridRegionAsFrameRGBA(const Vec2& minCorner, const Vec2& maxCorner) const {
|
||||||
|
TIME_FUNCTION;
|
||||||
|
int width, height;
|
||||||
|
std::vector<uint8_t> rgbData;
|
||||||
|
getGridRegionAsRGB(minCorner, maxCorner, width, height, rgbData);
|
||||||
|
|
||||||
|
// Convert RGB to RGBA
|
||||||
|
std::vector<uint8_t> rgbaData;
|
||||||
|
rgbaData.reserve(width * height * 4);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < rgbData.size(); i += 3) {
|
||||||
|
rgbaData.push_back(rgbData[i]); // R
|
||||||
|
rgbaData.push_back(rgbData[i + 1]); // G
|
||||||
|
rgbaData.push_back(rgbData[i + 2]); // B
|
||||||
|
rgbaData.push_back(255); // A (fully opaque)
|
||||||
|
}
|
||||||
|
|
||||||
|
frame resultFrame(width, height, frame::colormap::RGBA);
|
||||||
|
resultFrame.setData(rgbaData);
|
||||||
|
return resultFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get region as frame (BGRA format)
|
||||||
|
frame getGridRegionAsFrameBGRA(const Vec2& minCorner, const Vec2& maxCorner) const {
|
||||||
|
TIME_FUNCTION;
|
||||||
|
int width, height;
|
||||||
|
std::vector<uint8_t> bgrData;
|
||||||
|
getGridRegionAsBGR(minCorner, maxCorner, width, height, bgrData);
|
||||||
|
|
||||||
|
// Convert BGR to BGRA
|
||||||
|
std::vector<uint8_t> bgraData;
|
||||||
|
bgraData.reserve(width * height * 4);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < bgrData.size(); i += 3) {
|
||||||
|
bgraData.push_back(bgrData[i]); // B
|
||||||
|
bgraData.push_back(bgrData[i + 1]); // G
|
||||||
|
bgraData.push_back(bgrData[i + 2]); // R
|
||||||
|
bgraData.push_back(255); // A (fully opaque)
|
||||||
|
}
|
||||||
|
|
||||||
|
frame resultFrame(width, height, frame::colormap::BGRA);
|
||||||
|
resultFrame.setData(bgraData);
|
||||||
|
return resultFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get region as frame (Grayscale format)
|
||||||
|
frame getGridRegionAsFrameGrayscale(const Vec2& minCorner, const Vec2& maxCorner) const {
|
||||||
|
TIME_FUNCTION;
|
||||||
|
int width, height;
|
||||||
|
std::vector<uint8_t> rgbData;
|
||||||
|
getGridRegionAsRGB(minCorner, maxCorner, width, height, rgbData);
|
||||||
|
|
||||||
|
// Convert RGB to grayscale
|
||||||
|
std::vector<uint8_t> grayData;
|
||||||
|
grayData.reserve(width * height);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < rgbData.size(); i += 3) {
|
||||||
|
uint8_t r = rgbData[i];
|
||||||
|
uint8_t g = rgbData[i + 1];
|
||||||
|
uint8_t b = rgbData[i + 2];
|
||||||
|
// Standard grayscale conversion formula
|
||||||
|
uint8_t gray = static_cast<uint8_t>(0.299 * r + 0.587 * g + 0.114 * b);
|
||||||
|
grayData.push_back(gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
frame resultFrame(width, height, frame::colormap::B); // B for single channel/grayscale
|
||||||
|
resultFrame.setData(grayData);
|
||||||
|
return resultFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get entire grid as frame with specified format
|
||||||
|
frame getGridAsFrame(frame::colormap format = frame::colormap::RGB) {
|
||||||
|
TIME_FUNCTION;
|
||||||
Vec2 minCorner, maxCorner;
|
Vec2 minCorner, maxCorner;
|
||||||
getBoundingBox(minCorner, maxCorner);
|
getBoundingBox(minCorner, maxCorner);
|
||||||
getGridRegionAsFrame(minCorner, maxCorner, width, height, outputFrame, channels);
|
frame Frame;
|
||||||
|
|
||||||
|
switch (format) {
|
||||||
|
case frame::colormap::RGB:
|
||||||
|
Frame = getGridRegionAsFrameRGB(minCorner, maxCorner);
|
||||||
|
case frame::colormap::BGR:
|
||||||
|
Frame = getGridRegionAsFrameBGR(minCorner, maxCorner);
|
||||||
|
case frame::colormap::RGBA:
|
||||||
|
Frame = getGridRegionAsFrameRGBA(minCorner, maxCorner);
|
||||||
|
case frame::colormap::BGRA:
|
||||||
|
Frame = getGridRegionAsFrameBGRA(minCorner, maxCorner);
|
||||||
|
case frame::colormap::B:
|
||||||
|
Frame = getGridRegionAsFrameGrayscale(minCorner, maxCorner);
|
||||||
|
default:
|
||||||
|
Frame = getGridRegionAsFrameRGB(minCorner, maxCorner);
|
||||||
|
}
|
||||||
|
Frame.compressFrameZigZagRLE();
|
||||||
|
return Frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get region as frame with common channel configurations
|
// Get compressed frame with specified compression
|
||||||
void getGridRegionAsRGBFrame(const Vec2& minCorner, const Vec2& maxCorner,
|
frame getGridAsCompressedFrame(frame::colormap format = frame::colormap::RGB,
|
||||||
int& width, int& height, frame& outputFrame) {
|
frame::compresstype compression = frame::compresstype::RLE) {
|
||||||
getGridRegionAsFrame(minCorner, maxCorner, width, height, outputFrame, {'R', 'G', 'B'});
|
TIME_FUNCTION;
|
||||||
|
frame gridFrame = getGridAsFrame(format);
|
||||||
|
|
||||||
|
if (gridFrame.getData().empty()) {
|
||||||
|
return gridFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getGridRegionAsBGRFrame(const Vec2& minCorner, const Vec2& maxCorner,
|
switch (compression) {
|
||||||
int& width, int& height, frame& outputFrame) {
|
case frame::compresstype::RLE:
|
||||||
getGridRegionAsFrame(minCorner, maxCorner, width, height, outputFrame, {'B', 'G', 'R'});
|
return gridFrame.compressFrameRLE();
|
||||||
|
case frame::compresstype::ZIGZAG:
|
||||||
|
return gridFrame.compressFrameZigZag();
|
||||||
|
case frame::compresstype::DIFF:
|
||||||
|
return gridFrame.compressFrameDiff();
|
||||||
|
case frame::compresstype::ZIGZAGRLE:
|
||||||
|
return gridFrame.compressFrameZigZagRLE();
|
||||||
|
case frame::compresstype::DIFFRLE:
|
||||||
|
return gridFrame.compressFrameDiffRLE();
|
||||||
|
case frame::compresstype::HUFFMAN:
|
||||||
|
return gridFrame.compressFrameHuffman();
|
||||||
|
case frame::compresstype::RAW:
|
||||||
|
default:
|
||||||
|
return gridFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
void getGridRegionAsRGBAFrame(const Vec2& minCorner, const Vec2& maxCorner,
|
|
||||||
int& width, int& height, frame& outputFrame) {
|
|
||||||
getGridRegionAsFrame(minCorner, maxCorner, width, height, outputFrame, {'R', 'G', 'B', 'A'});
|
|
||||||
}
|
|
||||||
|
|
||||||
void getGridRegionAsBGRAFrame(const Vec2& minCorner, const Vec2& maxCorner,
|
|
||||||
int& width, int& height, frame& outputFrame) {
|
|
||||||
getGridRegionAsFrame(minCorner, maxCorner, width, height, outputFrame, {'B', 'G', 'R', 'A'});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -733,7 +802,7 @@ public:
|
|||||||
neighborRadius = radius;
|
neighborRadius = radius;
|
||||||
updateNeighborMap(); // Recompute all neighbors
|
updateNeighborMap(); // Recompute all neighbors
|
||||||
}
|
}
|
||||||
// spatial map
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -8,8 +8,8 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <iostream>
|
||||||
#include "frame.hpp"
|
#include "frame.hpp"
|
||||||
#include "video.hpp"
|
|
||||||
|
|
||||||
class AVIWriter {
|
class AVIWriter {
|
||||||
private:
|
private:
|
||||||
@@ -112,94 +112,83 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to convert frame to RGB format
|
static std::vector<uint8_t> prepareFrameData(const frame& frm, uint32_t width, uint32_t height, uint32_t rowSize) {
|
||||||
static std::vector<uint8_t> frameToRGB(const frame& frm) {
|
std::vector<uint8_t> paddedFrame(rowSize * height, 0);
|
||||||
TIME_FUNCTION;
|
|
||||||
if (frm.empty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t width = frm.width();
|
// Get the frame data (decompress if necessary)
|
||||||
size_t height = frm.height();
|
std::vector<uint8_t> frameData;
|
||||||
std::vector<uint8_t> rgbData(width * height * 3);
|
if (frm.isCompressed()) {
|
||||||
|
// Create a copy and decompress
|
||||||
// Check if frame already has RGB channels
|
frame tempFrame = frm;
|
||||||
bool hasR = frm.has_channel('R') || frm.has_channel('r');
|
tempFrame.decompress();
|
||||||
bool hasG = frm.has_channel('G') || frm.has_channel('g');
|
frameData = tempFrame.getData();
|
||||||
bool hasB = frm.has_channel('B') || frm.has_channel('b');
|
|
||||||
|
|
||||||
if (hasR && hasG && hasB) {
|
|
||||||
// Frame has RGB channels - extract them
|
|
||||||
std::vector<uint8_t> rChannel = frm.has_channel('R') ?
|
|
||||||
frm.get_channel_data('R') : frm.get_channel_data('r');
|
|
||||||
std::vector<uint8_t> gChannel = frm.has_channel('G') ?
|
|
||||||
frm.get_channel_data('G') : frm.get_channel_data('g');
|
|
||||||
std::vector<uint8_t> bChannel = frm.has_channel('B') ?
|
|
||||||
frm.get_channel_data('B') : frm.get_channel_data('b');
|
|
||||||
|
|
||||||
// Convert to BGR format (required by AVI)
|
|
||||||
for (size_t i = 0; i < width * height; ++i) {
|
|
||||||
rgbData[i * 3 + 0] = bChannel[i]; // Blue
|
|
||||||
rgbData[i * 3 + 1] = gChannel[i]; // Green
|
|
||||||
rgbData[i * 3 + 2] = rChannel[i]; // Red
|
|
||||||
}
|
|
||||||
} else if (frm.channels_count() == 1) {
|
|
||||||
// Grayscale frame - convert to RGB
|
|
||||||
std::vector<uint8_t> grayChannel = frm.get_channel_data(frm.channels()[0]);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < width * height; ++i) {
|
|
||||||
uint8_t gray = grayChannel[i];
|
|
||||||
rgbData[i * 3 + 0] = gray; // Blue
|
|
||||||
rgbData[i * 3 + 1] = gray; // Green
|
|
||||||
rgbData[i * 3 + 2] = gray; // Red
|
|
||||||
}
|
|
||||||
} else if (frm.channels_count() == 3) {
|
|
||||||
// Assume the 3 channels are RGB (even if not named)
|
|
||||||
// Convert to BGR format
|
|
||||||
for (size_t y = 0; y < height; ++y) {
|
|
||||||
for (size_t x = 0; x < width; ++x) {
|
|
||||||
rgbData[(y * width + x) * 3 + 0] = frm.at(y, x, size_t(2)); // Blue
|
|
||||||
rgbData[(y * width + x) * 3 + 1] = frm.at(y, x, size_t(1)); // Green
|
|
||||||
rgbData[(y * width + x) * 3 + 2] = frm.at(y, x, size_t(0)); // Red
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Unsupported format - use first channel as grayscale
|
frameData = frm.getData();
|
||||||
std::vector<uint8_t> firstChannel = frm.get_channel_data(frm.channels()[0]);
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < width * height; ++i) {
|
if (frameData.empty()) {
|
||||||
uint8_t gray = firstChannel[i];
|
return paddedFrame;
|
||||||
rgbData[i * 3 + 0] = gray; // Blue
|
}
|
||||||
rgbData[i * 3 + 1] = gray; // Green
|
|
||||||
rgbData[i * 3 + 2] = gray; // Red
|
// Determine source format and convert to RGB
|
||||||
|
size_t srcChannels = 3; // Default
|
||||||
|
switch (frm.colorFormat) {
|
||||||
|
case frame::colormap::RGBA: srcChannels = 4; break;
|
||||||
|
case frame::colormap::BGR: srcChannels = 3; break;
|
||||||
|
case frame::colormap::BGRA: srcChannels = 4; break;
|
||||||
|
case frame::colormap::B: srcChannels = 1; break;
|
||||||
|
default: srcChannels = 3; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t srcRowSize = width * srcChannels;
|
||||||
|
uint32_t dstRowSize = width * 3; // RGB
|
||||||
|
|
||||||
|
// Convert and flip vertically for BMP format
|
||||||
|
for (uint32_t y = 0; y < height; ++y) {
|
||||||
|
uint32_t srcY = height - 1 - y; // Flip vertically
|
||||||
|
const uint8_t* srcRow = frameData.data() + (srcY * srcRowSize);
|
||||||
|
uint8_t* dstRow = paddedFrame.data() + (y * rowSize);
|
||||||
|
|
||||||
|
// Convert to RGB format
|
||||||
|
switch (frm.colorFormat) {
|
||||||
|
case frame::colormap::RGB:
|
||||||
|
memcpy(dstRow, srcRow, dstRowSize);
|
||||||
|
break;
|
||||||
|
case frame::colormap::RGBA:
|
||||||
|
for (uint32_t x = 0; x < width; ++x) {
|
||||||
|
dstRow[x * 3] = srcRow[x * 4]; // R
|
||||||
|
dstRow[x * 3 + 1] = srcRow[x * 4 + 1]; // G
|
||||||
|
dstRow[x * 3 + 2] = srcRow[x * 4 + 2]; // B
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case frame::colormap::BGR:
|
||||||
|
for (uint32_t x = 0; x < width; ++x) {
|
||||||
|
dstRow[x * 3] = srcRow[x * 3 + 2]; // R
|
||||||
|
dstRow[x * 3 + 1] = srcRow[x * 3 + 1]; // G
|
||||||
|
dstRow[x * 3 + 2] = srcRow[x * 3]; // B
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case frame::colormap::BGRA:
|
||||||
|
for (uint32_t x = 0; x < width; ++x) {
|
||||||
|
dstRow[x * 3] = srcRow[x * 4 + 2]; // R
|
||||||
|
dstRow[x * 3 + 1] = srcRow[x * 4 + 1]; // G
|
||||||
|
dstRow[x * 3 + 2] = srcRow[x * 4]; // B
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case frame::colormap::B:
|
||||||
|
for (uint32_t x = 0; x < width; ++x) {
|
||||||
|
uint8_t gray = srcRow[x];
|
||||||
|
dstRow[x * 3] = gray; // R
|
||||||
|
dstRow[x * 3 + 1] = gray; // G
|
||||||
|
dstRow[x * 3 + 2] = gray; // B
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return rgbData;
|
return paddedFrame;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
// New method for video objects
|
|
||||||
static bool saveAVI(const std::string& filename,const video& vid,float fps = 0.0f) {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (vid.empty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use video's FPS if not overridden, otherwise use provided FPS
|
|
||||||
float actualFps = (fps > 0.0f) ? fps : static_cast<float>(vid.fps());
|
|
||||||
|
|
||||||
if (actualFps <= 0.0f) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all frames from the video
|
|
||||||
std::vector<frame> frames = vid.get_all_frames();
|
|
||||||
|
|
||||||
// Use the existing frame-based implementation
|
|
||||||
return saveAVI(filename, frames, actualFps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Original method for vector of raw frame data
|
// Original method for vector of raw frame data
|
||||||
static bool saveAVI(const std::string& filename,
|
static bool saveAVI(const std::string& filename,
|
||||||
const std::vector<std::vector<uint8_t>>& frames,
|
const std::vector<std::vector<uint8_t>>& frames,
|
||||||
@@ -379,38 +368,6 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// New overload for frame objects
|
|
||||||
static bool saveAVI(const std::string& filename,
|
|
||||||
const std::vector<frame>& frames,
|
|
||||||
float fps = 30.0f) {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (frames.empty() || fps <= 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate that all frames have the same dimensions
|
|
||||||
int width = static_cast<int>(frames[0].width());
|
|
||||||
int height = static_cast<int>(frames[0].height());
|
|
||||||
|
|
||||||
for (const auto& frm : frames) {
|
|
||||||
if (frm.width() != static_cast<size_t>(width) ||
|
|
||||||
frm.height() != static_cast<size_t>(height)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert frames to RGB format
|
|
||||||
std::vector<std::vector<uint8_t>> rgbFrames;
|
|
||||||
rgbFrames.reserve(frames.size());
|
|
||||||
|
|
||||||
for (const auto& frm : frames) {
|
|
||||||
rgbFrames.push_back(frameToRGB(frm));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the existing implementation
|
|
||||||
return saveAVI(filename, rgbFrames, width, height, fps);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenience function to save from individual frame files
|
// Convenience function to save from individual frame files
|
||||||
static bool saveAVIFromFrames(const std::string& filename,
|
static bool saveAVIFromFrames(const std::string& filename,
|
||||||
const std::vector<std::string>& frameFiles,
|
const std::vector<std::string>& frameFiles,
|
||||||
@@ -452,6 +409,168 @@ public:
|
|||||||
|
|
||||||
return saveAVI(filename, frames, width, height, fps);
|
return saveAVI(filename, frames, width, height, fps);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New method for streaming decompression of frame objects
|
||||||
|
static bool saveAVIFromCompressedFrames(const std::string& filename,
|
||||||
|
const std::vector<frame>& frames,
|
||||||
|
int width, int height,
|
||||||
|
float fps = 30.0f) {
|
||||||
|
TIME_FUNCTION;
|
||||||
|
if (frames.empty() || width <= 0 || height <= 0 || fps <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create directory if needed
|
||||||
|
if (!createDirectoryIfNeeded(filename)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ofstream file(filename, std::ios::binary);
|
||||||
|
if (!file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t frameCount = static_cast<uint32_t>(frames.size());
|
||||||
|
uint32_t microSecPerFrame = static_cast<uint32_t>(1000000.0f / fps);
|
||||||
|
|
||||||
|
// Calculate padding for each frame (BMP-style row padding)
|
||||||
|
uint32_t rowSize = (width * 3 + 3) & ~3;
|
||||||
|
uint32_t frameSize = rowSize * height;
|
||||||
|
|
||||||
|
// RIFF AVI header
|
||||||
|
RIFFChunk riffHeader;
|
||||||
|
riffHeader.chunkId = 0x46464952; // 'RIFF'
|
||||||
|
riffHeader.format = 0x20495641; // 'AVI '
|
||||||
|
|
||||||
|
// We'll come back and write the size at the end
|
||||||
|
uint32_t riffStartPos = static_cast<uint32_t>(file.tellp());
|
||||||
|
file.write(reinterpret_cast<const char*>(&riffHeader), sizeof(riffHeader));
|
||||||
|
|
||||||
|
// hdrl list
|
||||||
|
uint32_t hdrlListStart = static_cast<uint32_t>(file.tellp());
|
||||||
|
writeList(file, 0x6C726468, nullptr, 0); // 'hdrl' - we'll fill size later
|
||||||
|
|
||||||
|
// avih chunk
|
||||||
|
AVIMainHeader mainHeader;
|
||||||
|
mainHeader.microSecPerFrame = microSecPerFrame;
|
||||||
|
mainHeader.maxBytesPerSec = frameSize * static_cast<uint32_t>(fps);
|
||||||
|
mainHeader.paddingGranularity = 0;
|
||||||
|
mainHeader.flags = 0x000010; // HASINDEX flag
|
||||||
|
mainHeader.totalFrames = frameCount;
|
||||||
|
mainHeader.initialFrames = 0;
|
||||||
|
mainHeader.streams = 1;
|
||||||
|
mainHeader.suggestedBufferSize = frameSize;
|
||||||
|
mainHeader.width = width;
|
||||||
|
mainHeader.height = height;
|
||||||
|
mainHeader.reserved[0] = 0;
|
||||||
|
mainHeader.reserved[1] = 0;
|
||||||
|
mainHeader.reserved[2] = 0;
|
||||||
|
mainHeader.reserved[3] = 0;
|
||||||
|
|
||||||
|
writeChunk(file, 0x68697661, &mainHeader, sizeof(mainHeader)); // 'avih'
|
||||||
|
|
||||||
|
// strl list
|
||||||
|
uint32_t strlListStart = static_cast<uint32_t>(file.tellp());
|
||||||
|
writeList(file, 0x6C727473, nullptr, 0); // 'strl' - we'll fill size later
|
||||||
|
|
||||||
|
// strh chunk
|
||||||
|
AVIStreamHeader streamHeader;
|
||||||
|
streamHeader.type = 0x73646976; // 'vids'
|
||||||
|
streamHeader.handler = 0x00000000; // Uncompressed
|
||||||
|
streamHeader.flags = 0;
|
||||||
|
streamHeader.priority = 0;
|
||||||
|
streamHeader.language = 0;
|
||||||
|
streamHeader.initialFrames = 0;
|
||||||
|
streamHeader.scale = 1;
|
||||||
|
streamHeader.rate = static_cast<uint32_t>(fps);
|
||||||
|
streamHeader.start = 0;
|
||||||
|
streamHeader.length = frameCount;
|
||||||
|
streamHeader.suggestedBufferSize = frameSize;
|
||||||
|
streamHeader.quality = 0xFFFFFFFF; // Default quality
|
||||||
|
streamHeader.sampleSize = 0;
|
||||||
|
streamHeader.rcFrame.left = 0;
|
||||||
|
streamHeader.rcFrame.top = 0;
|
||||||
|
streamHeader.rcFrame.right = width;
|
||||||
|
streamHeader.rcFrame.bottom = height;
|
||||||
|
|
||||||
|
writeChunk(file, 0x68727473, &streamHeader, sizeof(streamHeader)); // 'strh'
|
||||||
|
|
||||||
|
// strf chunk
|
||||||
|
BITMAPINFOHEADER bitmapInfo;
|
||||||
|
bitmapInfo.size = sizeof(BITMAPINFOHEADER);
|
||||||
|
bitmapInfo.width = width;
|
||||||
|
bitmapInfo.height = height;
|
||||||
|
bitmapInfo.planes = 1;
|
||||||
|
bitmapInfo.bitCount = 24;
|
||||||
|
bitmapInfo.compression = 0; // BI_RGB - uncompressed
|
||||||
|
bitmapInfo.sizeImage = frameSize;
|
||||||
|
bitmapInfo.xPelsPerMeter = 0;
|
||||||
|
bitmapInfo.yPelsPerMeter = 0;
|
||||||
|
bitmapInfo.clrUsed = 0;
|
||||||
|
bitmapInfo.clrImportant = 0;
|
||||||
|
|
||||||
|
writeChunk(file, 0x66727473, &bitmapInfo, sizeof(bitmapInfo)); // 'strf'
|
||||||
|
|
||||||
|
// Update strl list size
|
||||||
|
uint32_t strlListEnd = static_cast<uint32_t>(file.tellp());
|
||||||
|
file.seekp(strlListStart + 4);
|
||||||
|
uint32_t strlListSize = strlListEnd - strlListStart - 8;
|
||||||
|
file.write(reinterpret_cast<const char*>(&strlListSize), 4);
|
||||||
|
file.seekp(strlListEnd);
|
||||||
|
|
||||||
|
// Update hdrl list size
|
||||||
|
uint32_t hdrlListEnd = static_cast<uint32_t>(file.tellp());
|
||||||
|
file.seekp(hdrlListStart + 4);
|
||||||
|
uint32_t hdrlListSize = hdrlListEnd - hdrlListStart - 8;
|
||||||
|
file.write(reinterpret_cast<const char*>(&hdrlListSize), 4);
|
||||||
|
file.seekp(hdrlListEnd);
|
||||||
|
|
||||||
|
// movi list
|
||||||
|
uint32_t moviListStart = static_cast<uint32_t>(file.tellp());
|
||||||
|
writeList(file, 0x69766F6D, nullptr, 0); // 'movi' - we'll fill size later
|
||||||
|
|
||||||
|
std::vector<AVIIndexEntry> indexEntries;
|
||||||
|
indexEntries.reserve(frameCount);
|
||||||
|
|
||||||
|
// Write frames with streaming decompression
|
||||||
|
for (uint32_t i = 0; i < frameCount; ++i) {
|
||||||
|
uint32_t frameStart = static_cast<uint32_t>(file.tellp()) - moviListStart - 4;
|
||||||
|
|
||||||
|
// Prepare frame data (decompresses if necessary and converts to RGB)
|
||||||
|
std::vector<uint8_t> paddedFrame = prepareFrameData(frames[i], width, height, rowSize);
|
||||||
|
|
||||||
|
// Write frame as '00db' chunk
|
||||||
|
writeChunk(file, 0x62643030, paddedFrame.data(), frameSize); // '00db'
|
||||||
|
|
||||||
|
// Add to index
|
||||||
|
AVIIndexEntry entry;
|
||||||
|
entry.chunkId = 0x62643030; // '00db'
|
||||||
|
entry.flags = 0x00000010; // AVIIF_KEYFRAME
|
||||||
|
entry.offset = frameStart;
|
||||||
|
entry.size = frameSize;
|
||||||
|
indexEntries.push_back(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update movi list size
|
||||||
|
uint32_t moviListEnd = static_cast<uint32_t>(file.tellp());
|
||||||
|
file.seekp(moviListStart + 4);
|
||||||
|
uint32_t moviListSize = moviListEnd - moviListStart - 8;
|
||||||
|
file.write(reinterpret_cast<const char*>(&moviListSize), 4);
|
||||||
|
file.seekp(moviListEnd);
|
||||||
|
|
||||||
|
// idx1 chunk - index
|
||||||
|
uint32_t idx1Size = static_cast<uint32_t>(indexEntries.size() * sizeof(AVIIndexEntry));
|
||||||
|
writeChunk(file, 0x31786469, indexEntries.data(), idx1Size); // 'idx1'
|
||||||
|
|
||||||
|
// Update RIFF chunk size
|
||||||
|
uint32_t fileEnd = static_cast<uint32_t>(file.tellp());
|
||||||
|
file.seekp(riffStartPos + 4);
|
||||||
|
uint32_t riffSize = fileEnd - riffStartPos - 8;
|
||||||
|
file.write(reinterpret_cast<const char*>(&riffSize), 4);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -5,13 +5,103 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <queue>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
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 width;
|
||||||
size_t height;
|
size_t height;
|
||||||
|
|
||||||
|
// Huffman coding structures
|
||||||
|
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:
|
||||||
enum class colormap {
|
enum class colormap {
|
||||||
RGB,
|
RGB,
|
||||||
RGBA,
|
RGBA,
|
||||||
@@ -19,6 +109,7 @@ private:
|
|||||||
BGRA,
|
BGRA,
|
||||||
B
|
B
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class compresstype {
|
enum class compresstype {
|
||||||
RLE,
|
RLE,
|
||||||
ZIGZAG,
|
ZIGZAG,
|
||||||
@@ -33,11 +124,48 @@ private:
|
|||||||
|
|
||||||
colormap colorFormat;
|
colormap colorFormat;
|
||||||
compresstype cformat;
|
compresstype cformat;
|
||||||
public:
|
|
||||||
// to do: compress rle option, zigzag the frame and then rle, do a diff and then rle. should only support addition diff
|
size_t getWidth() {
|
||||||
//convert to hex code instead and then run an option
|
return width;
|
||||||
//decompress to return original data
|
}
|
||||||
std::vector<uint8_t> compressFrameRLE() {
|
size_t getHeight() {
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
switch (format) {
|
||||||
|
case colormap::RGBA: channels = 4; break;
|
||||||
|
case colormap::BGR: channels = 3; break;
|
||||||
|
case colormap::BGRA: channels = 4; break;
|
||||||
|
case colormap::B: channels = 1; break;
|
||||||
|
default: channels = 3; break;
|
||||||
|
}
|
||||||
|
_data.resize(width * height * channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setData(const std::vector<uint8_t>& data) {
|
||||||
|
_data = data;
|
||||||
|
_compressedData.clear();
|
||||||
|
cformat = compresstype::RAW;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<uint8_t>& getData() const {
|
||||||
|
return _data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<uint8_t>& getCompressedData() const {
|
||||||
|
return _compressedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run-Length Encoding (RLE) compression
|
||||||
|
frame& compressFrameRLE() {
|
||||||
|
if (_data.empty()) {
|
||||||
|
_compressedData.clear();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
if (cformat == compresstype::ZIGZAG) {
|
if (cformat == compresstype::ZIGZAG) {
|
||||||
cformat = compresstype::ZIGZAGRLE;
|
cformat = compresstype::ZIGZAGRLE;
|
||||||
} else if (cformat == compresstype::DIFF) {
|
} else if (cformat == compresstype::DIFF) {
|
||||||
@@ -45,32 +173,337 @@ public:
|
|||||||
} else {
|
} else {
|
||||||
cformat = compresstype::RLE;
|
cformat = compresstype::RLE;
|
||||||
}
|
}
|
||||||
std::vector<uint8_t> compressed;
|
|
||||||
if (_data.empty()) return compressed;
|
_compressedData.clear();
|
||||||
|
_compressedData.reserve(_data.size() * 2);
|
||||||
|
|
||||||
size_t i = 0;
|
size_t i = 0;
|
||||||
|
|
||||||
while (i < _data.size()) {
|
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<uint8_t>(count));
|
||||||
|
_compressedData.push_back(current);
|
||||||
|
i += count;
|
||||||
|
} 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++;
|
||||||
}
|
}
|
||||||
std::vector<uint8_t> compressFrameZigZag() {
|
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store compression metadata in overheadmap
|
||||||
|
overheadmap[0] = static_cast<uint8_t>(cformat);
|
||||||
|
overheadmap[1] = static_cast<uint8_t>(_compressedData.size() > 0 ? 1 : 0);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
frame& decompressFrameRLE() {
|
||||||
|
if (_compressedData.empty()) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> decompressed;
|
||||||
|
decompressed.reserve(_data.size());
|
||||||
|
|
||||||
|
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++]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_data = std::move(decompressed);
|
||||||
|
cformat = compresstype::RAW;
|
||||||
|
overheadmap.clear();
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zigzag compression
|
||||||
|
frame& compressFrameZigZag() {
|
||||||
if (cformat != compresstype::RAW) {
|
if (cformat != compresstype::RAW) {
|
||||||
//FAIL
|
throw std::runtime_error("Cannot apply zigzag to already compressed data");
|
||||||
}
|
}
|
||||||
|
|
||||||
cformat = compresstype::ZIGZAG;
|
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;
|
||||||
}
|
}
|
||||||
std::vector<uint8_t> compressFrameDiff() {
|
|
||||||
|
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) {
|
if (cformat != compresstype::RAW) {
|
||||||
//FAIL should decompress and recompress or just return false?
|
throw std::runtime_error("Cannot apply diff to already compressed data");
|
||||||
}
|
}
|
||||||
|
|
||||||
cformat = compresstype::DIFF;
|
cformat = compresstype::DIFF;
|
||||||
|
_compressedData.clear();
|
||||||
|
_compressedData.reserve(_data.size());
|
||||||
|
|
||||||
|
if (_data.empty()) {
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> compressFrameHuffman() {
|
// 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() {
|
||||||
|
if (_compressedData.empty()) {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
frame& compressFrameHuffman() {
|
||||||
|
cformat = compresstype::HUFFMAN;
|
||||||
|
_compressedData.clear();
|
||||||
|
|
||||||
|
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
|
||||||
|
frame& compressFrameZigZagRLE() {
|
||||||
|
compressFrameZigZag();
|
||||||
|
// Store intermediate zigzag data temporarily
|
||||||
|
auto zigzagData = _compressedData;
|
||||||
|
_data = std::move(zigzagData);
|
||||||
|
cformat = compresstype::ZIGZAG;
|
||||||
|
return compressFrameRLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
frame& compressFrameDiffRLE() {
|
||||||
|
compressFrameDiff();
|
||||||
|
// Store intermediate diff data temporarily
|
||||||
|
auto diffData = _compressedData;
|
||||||
|
_data = std::move(diffData);
|
||||||
|
cformat = compresstype::DIFF;
|
||||||
|
return compressFrameRLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic decompression that detects compression type
|
||||||
|
frame& decompress() {
|
||||||
|
switch (cformat) {
|
||||||
|
case compresstype::RLE:
|
||||||
|
return decompressFrameRLE();
|
||||||
|
case compresstype::ZIGZAG:
|
||||||
|
return decompressFrameZigZag();
|
||||||
|
case compresstype::DIFF:
|
||||||
|
return decompressFrameDiff();
|
||||||
|
case compresstype::ZIGZAGRLE:
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
case compresstype::HUFFMAN:
|
||||||
|
// Huffman decompression would be implemented here
|
||||||
|
throw std::runtime_error("Huffman decompression not fully implemented");
|
||||||
|
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<double>(_data.size()) / _compressedData.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
compresstype getCompressionType() const {
|
||||||
|
return cformat;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::unordered_map<size_t, uint8_t>& getOverheadMap() const {
|
||||||
|
return overheadmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isCompressed() const {
|
||||||
|
return cformat != compresstype::RAW && !_compressedData.empty();
|
||||||
}
|
}
|
||||||
//get compression ratio
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -1,667 +0,0 @@
|
|||||||
#ifndef VIDEO_HPP
|
|
||||||
#define VIDEO_HPP
|
|
||||||
|
|
||||||
|
|
||||||
#include "frame.hpp"
|
|
||||||
#include <vector>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <memory>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <iostream>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include "../timing_decorator.hpp"
|
|
||||||
|
|
||||||
class video {
|
|
||||||
private:
|
|
||||||
std::vector<std::vector<std::pair<uint8_t, uint32_t>>> compressed_frames_;
|
|
||||||
std::unordered_map<size_t, size_t> keyframe_indices_; // Maps frame index to keyframe index
|
|
||||||
size_t width_;
|
|
||||||
size_t height_;
|
|
||||||
std::vector<char> channels_;
|
|
||||||
double fps_;
|
|
||||||
bool use_differential_encoding_;
|
|
||||||
size_t keyframe_interval_;
|
|
||||||
|
|
||||||
// Compress frame using differential encoding
|
|
||||||
std::vector<std::pair<uint8_t, uint32_t>> compress_with_differential(
|
|
||||||
const frame& current_frame, const frame* previous_frame = nullptr) const {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
|
|
||||||
if (previous_frame == nullptr) {
|
|
||||||
// First frame or keyframe - compress normally
|
|
||||||
return current_frame.compress_rle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create differential frame
|
|
||||||
std::vector<uint8_t> diff_data(current_frame.size());
|
|
||||||
|
|
||||||
const std::vector<uint8_t>& current_data = current_frame.data();
|
|
||||||
const std::vector<uint8_t>& prev_data = previous_frame->data();
|
|
||||||
|
|
||||||
// Calculate difference between frames
|
|
||||||
for (size_t i = 0; i < current_data.size(); ++i) {
|
|
||||||
// Use modulo arithmetic to handle unsigned byte overflow
|
|
||||||
diff_data[i] = (current_data[i] - prev_data[i]) & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create temporary frame for differential data
|
|
||||||
frame diff_frame(diff_data, width_, height_, channels_);
|
|
||||||
|
|
||||||
// Compress the differential data
|
|
||||||
return diff_frame.compress_rle();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decompress differential frame
|
|
||||||
frame decompress_differential(const std::vector<std::pair<uint8_t, uint32_t>>& compressed_diff,
|
|
||||||
const frame& previous_frame) const {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
|
|
||||||
frame diff_frame;
|
|
||||||
diff_frame.decompress_rle(compressed_diff);
|
|
||||||
|
|
||||||
// Reconstruct original frame from differential
|
|
||||||
std::vector<uint8_t> reconstructed_data(diff_frame.size());
|
|
||||||
const std::vector<uint8_t>& diff_data = diff_frame.data();
|
|
||||||
const std::vector<uint8_t>& prev_data = previous_frame.data();
|
|
||||||
|
|
||||||
for (size_t i = 0; i < diff_data.size(); ++i) {
|
|
||||||
// Reverse the differential encoding
|
|
||||||
reconstructed_data[i] = (prev_data[i] + diff_data[i]) & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
return frame(reconstructed_data, width_, height_, channels_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the nearest keyframe index for a given frame index
|
|
||||||
size_t find_nearest_keyframe(size_t frame_index) const {
|
|
||||||
if (keyframe_indices_.empty()) return 0;
|
|
||||||
|
|
||||||
// Keyframes are stored at intervals, so we can calculate the nearest one
|
|
||||||
size_t keyframe_idx = (frame_index / keyframe_interval_) * keyframe_interval_;
|
|
||||||
|
|
||||||
// Make sure the keyframe exists
|
|
||||||
if (keyframe_idx >= compressed_frames_.size()) {
|
|
||||||
// Find the last available keyframe
|
|
||||||
for (size_t i = frame_index; i > 0; --i) {
|
|
||||||
if (keyframe_indices_.count(i)) {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return keyframe_idx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build keyframe indices (call this when frames change)
|
|
||||||
void rebuild_keyframe_indices() {
|
|
||||||
keyframe_indices_.clear();
|
|
||||||
for (size_t i = 0; i < compressed_frames_.size(); i += keyframe_interval_) {
|
|
||||||
if (i < compressed_frames_.size()) {
|
|
||||||
keyframe_indices_[i] = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Always ensure frame 0 is a keyframe
|
|
||||||
if (!compressed_frames_.empty() && !keyframe_indices_.count(0)) {
|
|
||||||
keyframe_indices_[0] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get frame with keyframe optimization - much faster for random access
|
|
||||||
frame get_frame_optimized(size_t index) const {
|
|
||||||
if (index >= compressed_frames_.size()) {
|
|
||||||
throw std::out_of_range("Frame index out of range");
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's a keyframe or we're not using differential encoding, decompress directly
|
|
||||||
if (keyframe_indices_.count(index) || !use_differential_encoding_) {
|
|
||||||
frame result;
|
|
||||||
result.decompress_rle(compressed_frames_[index]);
|
|
||||||
result.resize(width_, height_, channels_);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the nearest keyframe
|
|
||||||
size_t keyframe_idx = find_nearest_keyframe(index);
|
|
||||||
|
|
||||||
// Decompress the keyframe first
|
|
||||||
frame current_frame = get_frame_optimized(keyframe_idx);
|
|
||||||
|
|
||||||
// Then decompress all frames from keyframe to target frame
|
|
||||||
for (size_t i = keyframe_idx + 1; i <= index; ++i) {
|
|
||||||
current_frame = decompress_differential(compressed_frames_[i], current_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
return current_frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
// Default constructor
|
|
||||||
video() : width_(0), height_(0), fps_(30.0), use_differential_encoding_(true), keyframe_interval_(50) {}
|
|
||||||
|
|
||||||
// Constructor with dimensions and settings
|
|
||||||
video(size_t width, size_t height, const std::vector<char>& channels = {'\0'},
|
|
||||||
double fps = 30.0, bool use_differential = true, size_t keyframe_interval = 50)
|
|
||||||
: width_(width), height_(height), channels_(channels), fps_(fps),
|
|
||||||
use_differential_encoding_(use_differential), keyframe_interval_(keyframe_interval) {
|
|
||||||
|
|
||||||
if (width == 0 || height == 0) {
|
|
||||||
throw std::invalid_argument("Dimensions must be positive");
|
|
||||||
}
|
|
||||||
if (channels.empty()) {
|
|
||||||
throw std::invalid_argument("Channels list cannot be empty");
|
|
||||||
}
|
|
||||||
if (fps <= 0) {
|
|
||||||
throw std::invalid_argument("FPS must be positive");
|
|
||||||
}
|
|
||||||
if (keyframe_interval == 0) {
|
|
||||||
throw std::invalid_argument("Keyframe interval must be positive");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Constructor with initializer list for channels
|
|
||||||
video(size_t width, size_t height, std::initializer_list<char> channels,
|
|
||||||
double fps = 30.0, bool use_differential = true, size_t keyframe_interval = 50)
|
|
||||||
: video(width, height, std::vector<char>(channels), fps, use_differential, keyframe_interval) {}
|
|
||||||
|
|
||||||
// Accessors
|
|
||||||
size_t width() const noexcept { return width_; }
|
|
||||||
size_t height() const noexcept { return height_; }
|
|
||||||
const std::vector<char>& channels() const noexcept { return channels_; }
|
|
||||||
double fps() const noexcept { return fps_; }
|
|
||||||
bool use_differential_encoding() const noexcept { return use_differential_encoding_; }
|
|
||||||
size_t frame_count() const noexcept { return compressed_frames_.size(); }
|
|
||||||
size_t channels_count() const noexcept { return channels_.size(); }
|
|
||||||
size_t keyframe_interval() const noexcept { return keyframe_interval_; }
|
|
||||||
const std::unordered_map<size_t, size_t>& keyframe_indices() const noexcept { return keyframe_indices_; }
|
|
||||||
|
|
||||||
// Check if video is empty
|
|
||||||
bool empty() const noexcept {
|
|
||||||
return compressed_frames_.empty() || width_ == 0 || height_ == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a frame to the video sequence
|
|
||||||
void add_frame(const frame& new_frame) {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
// Validate frame dimensions and channels
|
|
||||||
if (new_frame.width() != width_ || new_frame.height() != height_) {
|
|
||||||
throw std::invalid_argument("Frame dimensions must match video dimensions");
|
|
||||||
}
|
|
||||||
if (new_frame.channels() != channels_) {
|
|
||||||
throw std::invalid_argument("Frame channels must match video channels");
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t new_index = compressed_frames_.size();
|
|
||||||
|
|
||||||
if (compressed_frames_.empty() || !use_differential_encoding_) {
|
|
||||||
// First frame or differential encoding disabled - compress normally
|
|
||||||
compressed_frames_.push_back(new_frame.compress_rle());
|
|
||||||
} else {
|
|
||||||
// Check if this should be a keyframe
|
|
||||||
bool is_keyframe = (new_index % keyframe_interval_ == 0);
|
|
||||||
|
|
||||||
if (is_keyframe) {
|
|
||||||
// Keyframe - compress normally
|
|
||||||
compressed_frames_.push_back(new_frame.compress_rle());
|
|
||||||
keyframe_indices_[new_index] = new_index;
|
|
||||||
} else {
|
|
||||||
// Regular frame - use differential encoding from previous frame
|
|
||||||
frame prev_frame = get_frame_optimized(new_index - 1);
|
|
||||||
compressed_frames_.push_back(compress_with_differential(new_frame, &prev_frame));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure we have keyframe at index 0
|
|
||||||
if (compressed_frames_.size() == 1) {
|
|
||||||
keyframe_indices_[0] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add frame with move semantics
|
|
||||||
void add_frame(frame&& new_frame) {
|
|
||||||
add_frame(new_frame); // Just call the const version
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a specific frame (uses optimized version with keyframes)
|
|
||||||
frame get_frame(size_t index) const {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (!use_differential_encoding_ || keyframe_indices_.empty()) {
|
|
||||||
// Fallback to original method if no optimization possible
|
|
||||||
if (index >= compressed_frames_.size()) {
|
|
||||||
throw std::out_of_range("Frame index out of range");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index == 0 || !use_differential_encoding_) {
|
|
||||||
frame result;
|
|
||||||
result.decompress_rle(compressed_frames_[index]);
|
|
||||||
result.resize(width_, height_, channels_);
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
frame prev_frame = get_frame(index - 1);
|
|
||||||
return decompress_differential(compressed_frames_[index], prev_frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return get_frame_optimized(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get multiple frames as a sequence (optimized for sequential access)
|
|
||||||
std::vector<frame> get_frames(size_t start_index, size_t count) const {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (start_index >= compressed_frames_.size()) {
|
|
||||||
throw std::out_of_range("Start index out of range");
|
|
||||||
}
|
|
||||||
|
|
||||||
count = std::min(count, compressed_frames_.size() - start_index);
|
|
||||||
std::vector<frame> frames;
|
|
||||||
frames.reserve(count);
|
|
||||||
|
|
||||||
if (!use_differential_encoding_ || keyframe_indices_.empty()) {
|
|
||||||
// Original sequential method
|
|
||||||
for (size_t i = start_index; i < start_index + count; ++i) {
|
|
||||||
frames.push_back(get_frame(i));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Optimized method: start from nearest keyframe
|
|
||||||
size_t current_index = start_index;
|
|
||||||
size_t keyframe_idx = find_nearest_keyframe(start_index);
|
|
||||||
|
|
||||||
// Get the keyframe
|
|
||||||
frame current_frame = get_frame_optimized(keyframe_idx);
|
|
||||||
|
|
||||||
// If we started before the keyframe (shouldn't happen), handle it
|
|
||||||
if (keyframe_idx > start_index) {
|
|
||||||
// This is a fallback - should not normally occur
|
|
||||||
current_frame = get_frame_optimized(start_index);
|
|
||||||
current_index = start_index + 1;
|
|
||||||
} else if (keyframe_idx < start_index) {
|
|
||||||
// Decode frames from keyframe to start_index
|
|
||||||
for (size_t i = keyframe_idx + 1; i < start_index; ++i) {
|
|
||||||
current_frame = decompress_differential(compressed_frames_[i], current_frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now add the requested frames
|
|
||||||
for (size_t i = start_index; i < start_index + count; ++i) {
|
|
||||||
if (i > keyframe_idx) {
|
|
||||||
current_frame = decompress_differential(compressed_frames_[i], current_frame);
|
|
||||||
}
|
|
||||||
frames.push_back(current_frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return frames;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all frames
|
|
||||||
std::vector<frame> get_all_frames() const {
|
|
||||||
return get_frames(0, compressed_frames_.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove a frame
|
|
||||||
void remove_frame(size_t index) {
|
|
||||||
if (index >= compressed_frames_.size()) {
|
|
||||||
throw std::out_of_range("Frame index out of range");
|
|
||||||
}
|
|
||||||
compressed_frames_.erase(compressed_frames_.begin() + index);
|
|
||||||
rebuild_keyframe_indices();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear all frames
|
|
||||||
void clear_frames() noexcept {
|
|
||||||
compressed_frames_.clear();
|
|
||||||
keyframe_indices_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace a frame
|
|
||||||
void replace_frame(size_t index, const frame& new_frame) {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (index >= compressed_frames_.size()) {
|
|
||||||
throw std::out_of_range("Frame index out of range");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate frame dimensions and channels
|
|
||||||
if (new_frame.width() != width_ || new_frame.height() != height_) {
|
|
||||||
throw std::invalid_argument("Frame dimensions must match video dimensions");
|
|
||||||
}
|
|
||||||
if (new_frame.channels() != channels_) {
|
|
||||||
throw std::invalid_argument("Frame channels must match video channels");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool was_keyframe = keyframe_indices_.count(index);
|
|
||||||
bool should_be_keyframe = (index % keyframe_interval_ == 0);
|
|
||||||
|
|
||||||
if (index == 0 || !use_differential_encoding_ || should_be_keyframe) {
|
|
||||||
// Keyframe or no differential encoding - compress normally
|
|
||||||
compressed_frames_[index] = new_frame.compress_rle();
|
|
||||||
if (should_be_keyframe) {
|
|
||||||
keyframe_indices_[index] = index;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Differential frame
|
|
||||||
frame prev_frame = get_frame_optimized(index - 1);
|
|
||||||
compressed_frames_[index] = compress_with_differential(new_frame, &prev_frame);
|
|
||||||
|
|
||||||
// Remove from keyframes if it was one but shouldn't be
|
|
||||||
if (was_keyframe && !should_be_keyframe) {
|
|
||||||
keyframe_indices_.erase(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this isn't the last frame, we need to update the next frame's differential encoding
|
|
||||||
if (use_differential_encoding_ && index + 1 < compressed_frames_.size()) {
|
|
||||||
frame current_frame = get_frame_optimized(index);
|
|
||||||
frame next_frame_original = get_frame_optimized(index + 1);
|
|
||||||
compressed_frames_[index + 1] = compress_with_differential(next_frame_original, ¤t_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild keyframe indices if we changed keyframe status
|
|
||||||
if (was_keyframe != should_be_keyframe) {
|
|
||||||
rebuild_keyframe_indices();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set FPS
|
|
||||||
void set_fps(double fps) {
|
|
||||||
if (fps <= 0) {
|
|
||||||
throw std::invalid_argument("FPS must be positive");
|
|
||||||
}
|
|
||||||
fps_ = fps;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable/disable differential encoding
|
|
||||||
void set_differential_encoding(bool enabled) {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (use_differential_encoding_ == enabled) {
|
|
||||||
return; // No change needed
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!compressed_frames_.empty() && enabled != use_differential_encoding_) {
|
|
||||||
// Need to recompress all frames with new encoding setting
|
|
||||||
auto original_frames = get_all_frames();
|
|
||||||
clear_frames();
|
|
||||||
use_differential_encoding_ = enabled;
|
|
||||||
|
|
||||||
for (const auto& f : original_frames) {
|
|
||||||
add_frame(f);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
use_differential_encoding_ = enabled;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set keyframe interval and rebuild indices
|
|
||||||
void set_keyframe_interval(size_t interval) {
|
|
||||||
if (interval == 0) {
|
|
||||||
throw std::invalid_argument("Keyframe interval must be positive");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interval != keyframe_interval_) {
|
|
||||||
keyframe_interval_ = interval;
|
|
||||||
if (!compressed_frames_.empty()) {
|
|
||||||
// Rebuild keyframe indices with new interval
|
|
||||||
rebuild_keyframe_indices();
|
|
||||||
|
|
||||||
// If we have frames, we may need to recompress some as keyframes
|
|
||||||
if (use_differential_encoding_) {
|
|
||||||
auto original_frames = get_all_frames();
|
|
||||||
clear_frames();
|
|
||||||
|
|
||||||
for (const auto& f : original_frames) {
|
|
||||||
add_frame(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force a specific frame to be a keyframe
|
|
||||||
void make_keyframe(size_t index) {
|
|
||||||
if (index >= compressed_frames_.size()) {
|
|
||||||
throw std::out_of_range("Frame index out of range");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!keyframe_indices_.count(index)) {
|
|
||||||
// Recompress this frame as a keyframe
|
|
||||||
frame original_frame = get_frame_optimized(index);
|
|
||||||
compressed_frames_[index] = original_frame.compress_rle();
|
|
||||||
keyframe_indices_[index] = index;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get video duration in seconds
|
|
||||||
double duration() const noexcept {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
return compressed_frames_.size() / fps_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate total compressed size in bytes
|
|
||||||
size_t total_compressed_size() const noexcept {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
size_t total = 0;
|
|
||||||
for (const auto& compressed_frame : compressed_frames_) {
|
|
||||||
total += compressed_frame.size() * sizeof(std::pair<uint8_t, uint32_t>);
|
|
||||||
}
|
|
||||||
return total;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate total uncompressed size in bytes
|
|
||||||
size_t total_uncompressed_size() const noexcept {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
return compressed_frames_.size() * width_ * height_ * channels_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate overall compression ratio
|
|
||||||
double overall_compression_ratio() const noexcept {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (empty()) {
|
|
||||||
return 1.0;
|
|
||||||
}
|
|
||||||
size_t uncompressed = total_uncompressed_size();
|
|
||||||
if (uncompressed == 0) {
|
|
||||||
return 1.0;
|
|
||||||
}
|
|
||||||
return static_cast<double>(uncompressed) / total_compressed_size();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate average frame compression ratio
|
|
||||||
double average_frame_compression_ratio() const {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (empty()) {
|
|
||||||
return 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
double total_ratio = 0.0;
|
|
||||||
for (size_t i = 0; i < compressed_frames_.size(); ++i) {
|
|
||||||
frame f = get_frame(i);
|
|
||||||
total_ratio += f.get_compression_ratio();
|
|
||||||
}
|
|
||||||
|
|
||||||
return total_ratio / compressed_frames_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get compression statistics
|
|
||||||
struct compression_stats {
|
|
||||||
size_t total_frames;
|
|
||||||
size_t total_compressed_bytes;
|
|
||||||
size_t total_uncompressed_bytes;
|
|
||||||
double overall_ratio;
|
|
||||||
double average_frame_ratio;
|
|
||||||
double video_duration;
|
|
||||||
size_t keyframe_count;
|
|
||||||
size_t keyframe_interval;
|
|
||||||
};
|
|
||||||
|
|
||||||
compression_stats get_compression_stats() const {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
compression_stats stats;
|
|
||||||
stats.total_frames = compressed_frames_.size();
|
|
||||||
stats.total_compressed_bytes = total_compressed_size();
|
|
||||||
stats.total_uncompressed_bytes = total_uncompressed_size();
|
|
||||||
stats.overall_ratio = overall_compression_ratio();
|
|
||||||
stats.average_frame_ratio = average_frame_compression_ratio();
|
|
||||||
stats.video_duration = duration();
|
|
||||||
stats.keyframe_count = keyframe_indices_.size();
|
|
||||||
stats.keyframe_interval = keyframe_interval_;
|
|
||||||
return stats;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract a sub-video
|
|
||||||
video subvideo(size_t start_frame, size_t frame_count) const {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (start_frame >= compressed_frames_.size()) {
|
|
||||||
throw std::out_of_range("Start frame out of range");
|
|
||||||
}
|
|
||||||
|
|
||||||
frame_count = std::min(frame_count, compressed_frames_.size() - start_frame);
|
|
||||||
video result(width_, height_, channels_, fps_, use_differential_encoding_, keyframe_interval_);
|
|
||||||
|
|
||||||
// Add frames one by one to maintain proper keyframe structure
|
|
||||||
for (size_t i = start_frame; i < start_frame + frame_count; ++i) {
|
|
||||||
result.add_frame(get_frame(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append another video (must have same dimensions and channels)
|
|
||||||
void append_video(const video& other) {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (other.width_ != width_ || other.height_ != height_ || other.channels_ != channels_) {
|
|
||||||
throw std::invalid_argument("Videos must have same dimensions and channels");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add frames one by one to maintain proper keyframe structure
|
|
||||||
auto other_frames = other.get_all_frames();
|
|
||||||
for (const auto& frame : other_frames) {
|
|
||||||
add_frame(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save/Load functionality (basic serialization) - updated for keyframes
|
|
||||||
std::vector<uint8_t> serialize() const {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
// Simple serialization format:
|
|
||||||
// [header][compressed_frame_data...]
|
|
||||||
// Header: width(4), height(4), channels_count(1), channels_data(n), fps(8),
|
|
||||||
// use_diff(1), keyframe_interval(4), frame_count(4), keyframe_count(4), keyframe_indices...
|
|
||||||
|
|
||||||
std::vector<uint8_t> result;
|
|
||||||
|
|
||||||
auto add_uint32 = [&result](uint32_t value) {
|
|
||||||
for (int i = 0; i < 4; ++i) {
|
|
||||||
result.push_back((value >> (i * 8)) & 0xFF);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto add_double = [&result](double value) {
|
|
||||||
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&value);
|
|
||||||
for (size_t i = 0; i < sizeof(double); ++i) {
|
|
||||||
result.push_back(bytes[i]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Write header
|
|
||||||
add_uint32(static_cast<uint32_t>(width_));
|
|
||||||
add_uint32(static_cast<uint32_t>(height_));
|
|
||||||
result.push_back(static_cast<uint8_t>(channels_.size()));
|
|
||||||
for (char c : channels_) {
|
|
||||||
result.push_back(static_cast<uint8_t>(c));
|
|
||||||
}
|
|
||||||
add_double(fps_);
|
|
||||||
result.push_back(use_differential_encoding_ ? 1 : 0);
|
|
||||||
add_uint32(static_cast<uint32_t>(keyframe_interval_));
|
|
||||||
add_uint32(static_cast<uint32_t>(compressed_frames_.size()));
|
|
||||||
|
|
||||||
// Write keyframe indices
|
|
||||||
add_uint32(static_cast<uint32_t>(keyframe_indices_.size()));
|
|
||||||
for (const auto& kv : keyframe_indices_) {
|
|
||||||
add_uint32(static_cast<uint32_t>(kv.first));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write compressed frames
|
|
||||||
for (const auto& compressed_frame : compressed_frames_) {
|
|
||||||
add_uint32(static_cast<uint32_t>(compressed_frame.size()));
|
|
||||||
for (const auto& run : compressed_frame) {
|
|
||||||
result.push_back(run.first);
|
|
||||||
add_uint32(run.second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deserialize from byte data
|
|
||||||
static video deserialize(const std::vector<uint8_t>& data) {
|
|
||||||
TIME_FUNCTION;
|
|
||||||
if (data.size() < 4 + 4 + 1 + 8 + 1 + 4 + 4 + 4) { // Minimum header size
|
|
||||||
throw std::invalid_argument("Invalid video data: too short");
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t pos = 0;
|
|
||||||
auto read_uint32 = [&data, &pos]() {
|
|
||||||
if (pos + 4 > data.size()) throw std::invalid_argument("Unexpected end of data");
|
|
||||||
uint32_t value = 0;
|
|
||||||
for (int i = 0; i < 4; ++i) {
|
|
||||||
value |= static_cast<uint32_t>(data[pos++]) << (i * 8);
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto read_double = [&data, &pos]() {
|
|
||||||
if (pos + sizeof(double) > data.size()) throw std::invalid_argument("Unexpected end of data");
|
|
||||||
double value;
|
|
||||||
uint8_t* bytes = reinterpret_cast<uint8_t*>(&value);
|
|
||||||
for (size_t i = 0; i < sizeof(double); ++i) {
|
|
||||||
bytes[i] = data[pos++];
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Read header
|
|
||||||
uint32_t width = read_uint32();
|
|
||||||
uint32_t height = read_uint32();
|
|
||||||
uint8_t channels_count = data[pos++];
|
|
||||||
|
|
||||||
std::vector<char> channels;
|
|
||||||
for (uint8_t i = 0; i < channels_count; ++i) {
|
|
||||||
if (pos >= data.size()) throw std::invalid_argument("Unexpected end of data");
|
|
||||||
channels.push_back(static_cast<char>(data[pos++]));
|
|
||||||
}
|
|
||||||
|
|
||||||
double fps = read_double();
|
|
||||||
bool use_diff = data[pos++] != 0;
|
|
||||||
uint32_t keyframe_interval = read_uint32();
|
|
||||||
uint32_t frame_count = read_uint32();
|
|
||||||
|
|
||||||
video result(width, height, channels, fps, use_diff, keyframe_interval);
|
|
||||||
|
|
||||||
// Read keyframe indices
|
|
||||||
uint32_t keyframe_count = read_uint32();
|
|
||||||
for (uint32_t i = 0; i < keyframe_count; ++i) {
|
|
||||||
uint32_t keyframe_index = read_uint32();
|
|
||||||
result.keyframe_indices_[keyframe_index] = keyframe_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read compressed frames
|
|
||||||
for (uint32_t i = 0; i < frame_count; ++i) {
|
|
||||||
if (pos + 4 > data.size()) throw std::invalid_argument("Unexpected end of data");
|
|
||||||
uint32_t runs_count = read_uint32();
|
|
||||||
|
|
||||||
std::vector<std::pair<uint8_t, uint32_t>> compressed_frame;
|
|
||||||
for (uint32_t j = 0; j < runs_count; ++j) {
|
|
||||||
if (pos + 5 > data.size()) throw std::invalid_argument("Unexpected end of data");
|
|
||||||
uint8_t value = data[pos++];
|
|
||||||
uint32_t count = read_uint32();
|
|
||||||
compressed_frame.emplace_back(value, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.compressed_frames_.push_back(compressed_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
Reference in New Issue
Block a user