diff --git a/.vscode/settings.json b/.vscode/settings.json index 64836b7..e563d9d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -86,7 +86,11 @@ "variant": "cpp", "__nullptr": "cpp", "unordered_set": "cpp", - "queue": "cpp" + "queue": "cpp", + "__hash_table": "cpp", + "__split_buffer": "cpp", + "__tree": "cpp", + "stack": "cpp" }, "files.exclude": { "**/*.rpyc": true, diff --git a/tests/g2chromatic2.cpp b/tests/g2chromatic2.cpp index 3d21b60..acf0702 100644 --- a/tests/g2chromatic2.cpp +++ b/tests/g2chromatic2.cpp @@ -11,8 +11,8 @@ #include "../util/timing_decorator.cpp" struct AnimationConfig { - int width = 256; - int height = 256; + int width = 1024; + int height = 1024; int totalFrames = 480; float fps = 30.0f; int numSeeds = 8; @@ -115,12 +115,13 @@ void expandPixel(Grid2& grid, AnimationConfig config, std::vector> frames, AnimationConfig config) { +//bool exportavi(std::vector> frames, AnimationConfig config) { +bool exportavi(std::vector frames, AnimationConfig config) { TIME_FUNCTION; std::string filename = "output/chromatic_transformation.avi"; std::cout << "Frame count: " << frames.size() << std::endl; - std::cout << "Frame size: " << (frames.empty() ? 0 : frames[0].size()) << std::endl; + //std::cout << "Frame size: " << (frames.empty() ? 0 : frames[0].size()) << std::endl; std::cout << "Width: " << config.width << ", Height: " << config.height << std::endl; std::filesystem::path dir = "output"; @@ -131,7 +132,9 @@ bool exportavi(std::vector> 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) { // Check if file actually exists @@ -152,16 +155,18 @@ int main() { //grid.updateNeighborMap(); Preview(grid); std::vector> seeds = pickSeeds(grid,config); - std::vector> frames; + //std::vector> frames; + std::vector frames; 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); int width; int height; - std::vector frame; - grid.getGridAsBGR(width,height,frame); - frames.push_back(frame); + //std::vector bgrframe; + frame bgrframe = grid.getGridAsFrame(frame::colormap::BGR); + //grid.getGridAsBGR(width,height,bgrframe); + frames.push_back(bgrframe); } exportavi(frames,config); diff --git a/util/grid/grid22.hpp b/util/grid/grid22.hpp index 9e2868e..1a0479f 100644 --- a/util/grid/grid22.hpp +++ b/util/grid/grid22.hpp @@ -563,86 +563,155 @@ public: getBoundingBox(minCorner, maxCorner); getGridRegionAsBGR(minCorner, maxCorner, width, height, bgrData); } + - // Get region as frame with customizable channels - void getGridRegionAsFrame(const Vec2& minCorner, const Vec2& maxCorner, - int& width, int& height, frame& outputFrame, - const std::vector& channels = {'R', 'G', 'B'}) const { + //frame stuff + frame getGridRegionAsFrameRGB(const Vec2& minCorner, const Vec2& maxCorner) const { TIME_FUNCTION; - // Calculate dimensions - width = static_cast(maxCorner.x - minCorner.x); - height = static_cast(maxCorner.y - minCorner.y); + int width, height; + std::vector rgbData; + getGridRegionAsRGB(minCorner, maxCorner, width, height, rgbData); - 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(pos.x - minCorner.x); - int pixelY = static_cast(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(id) / Positions.size(); break; // Normalized ID - default: value = 0.0f; break; - } - outputFrame.at(pixelY, pixelX, channel_idx) = static_cast(value * 255); - } - } - } - } + frame resultFrame(width, height, frame::colormap::RGB); + resultFrame.setData(rgbData); + return resultFrame; } - // Get full grid as frame - void getGridAsFrame(frame& outputFrame, const std::vector& channels = {'R', 'G', 'B'}) { + // Get region as frame (BGR format) + frame getGridRegionAsFrameBGR(const Vec2& minCorner, const Vec2& maxCorner) const { + TIME_FUNCTION; int width, height; + std::vector 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 rgbData; + getGridRegionAsRGB(minCorner, maxCorner, width, height, rgbData); + + // Convert RGB to RGBA + std::vector 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 bgrData; + getGridRegionAsBGR(minCorner, maxCorner, width, height, bgrData); + + // Convert BGR to BGRA + std::vector 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 rgbData; + getGridRegionAsRGB(minCorner, maxCorner, width, height, rgbData); + + // Convert RGB to grayscale + std::vector 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(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; 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 - void getGridRegionAsRGBFrame(const Vec2& minCorner, const Vec2& maxCorner, - int& width, int& height, frame& outputFrame) { - getGridRegionAsFrame(minCorner, maxCorner, width, height, outputFrame, {'R', 'G', 'B'}); - } - - void getGridRegionAsBGRFrame(const Vec2& minCorner, const Vec2& maxCorner, - int& width, int& height, frame& outputFrame) { - getGridRegionAsFrame(minCorner, maxCorner, width, height, outputFrame, {'B', 'G', 'R'}); - } - - 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'}); + // Get compressed frame with specified compression + frame getGridAsCompressedFrame(frame::colormap format = frame::colormap::RGB, + frame::compresstype compression = frame::compresstype::RLE) { + TIME_FUNCTION; + frame gridFrame = getGridAsFrame(format); + + if (gridFrame.getData().empty()) { + return gridFrame; + } + + switch (compression) { + case frame::compresstype::RLE: + 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; + } } @@ -733,7 +802,7 @@ public: neighborRadius = radius; updateNeighborMap(); // Recompute all neighbors } - // spatial map + }; #endif \ No newline at end of file diff --git a/util/output/aviwriter.hpp b/util/output/aviwriter.hpp index 0f6ad9a..7731a06 100644 --- a/util/output/aviwriter.hpp +++ b/util/output/aviwriter.hpp @@ -8,8 +8,8 @@ #include #include #include +#include #include "frame.hpp" -#include "video.hpp" class AVIWriter { private: @@ -112,94 +112,83 @@ private: } } - // Helper function to convert frame to RGB format - static std::vector frameToRGB(const frame& frm) { - TIME_FUNCTION; - if (frm.empty()) { - return {}; - } + static std::vector prepareFrameData(const frame& frm, uint32_t width, uint32_t height, uint32_t rowSize) { + std::vector paddedFrame(rowSize * height, 0); - size_t width = frm.width(); - size_t height = frm.height(); - std::vector rgbData(width * height * 3); - - // Check if frame already has RGB channels - bool hasR = frm.has_channel('R') || frm.has_channel('r'); - bool hasG = frm.has_channel('G') || frm.has_channel('g'); - bool hasB = frm.has_channel('B') || frm.has_channel('b'); - - if (hasR && hasG && hasB) { - // Frame has RGB channels - extract them - std::vector rChannel = frm.has_channel('R') ? - frm.get_channel_data('R') : frm.get_channel_data('r'); - std::vector gChannel = frm.has_channel('G') ? - frm.get_channel_data('G') : frm.get_channel_data('g'); - std::vector 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 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 - } - } + // Get the frame data (decompress if necessary) + std::vector frameData; + if (frm.isCompressed()) { + // Create a copy and decompress + frame tempFrame = frm; + tempFrame.decompress(); + frameData = tempFrame.getData(); } else { - // Unsupported format - use first channel as grayscale - std::vector firstChannel = frm.get_channel_data(frm.channels()[0]); + frameData = frm.getData(); + } + + if (frameData.empty()) { + return paddedFrame; + } + + // 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); - for (size_t i = 0; i < width * height; ++i) { - uint8_t gray = firstChannel[i]; - rgbData[i * 3 + 0] = gray; // Blue - rgbData[i * 3 + 1] = gray; // Green - rgbData[i * 3 + 2] = gray; // Red + // 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: - // 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(vid.fps()); - - if (actualFps <= 0.0f) { - return false; - } - - // Get all frames from the video - std::vector frames = vid.get_all_frames(); - - // Use the existing frame-based implementation - return saveAVI(filename, frames, actualFps); - } - // Original method for vector of raw frame data static bool saveAVI(const std::string& filename, const std::vector>& frames, @@ -379,38 +368,6 @@ public: return true; } - // New overload for frame objects - static bool saveAVI(const std::string& filename, - const std::vector& 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(frames[0].width()); - int height = static_cast(frames[0].height()); - - for (const auto& frm : frames) { - if (frm.width() != static_cast(width) || - frm.height() != static_cast(height)) { - return false; - } - } - - // Convert frames to RGB format - std::vector> 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 static bool saveAVIFromFrames(const std::string& filename, const std::vector& frameFiles, @@ -452,6 +409,168 @@ public: 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& 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(frames.size()); + uint32_t microSecPerFrame = static_cast(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(file.tellp()); + file.write(reinterpret_cast(&riffHeader), sizeof(riffHeader)); + + // hdrl list + uint32_t hdrlListStart = static_cast(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(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(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(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(file.tellp()); + file.seekp(strlListStart + 4); + uint32_t strlListSize = strlListEnd - strlListStart - 8; + file.write(reinterpret_cast(&strlListSize), 4); + file.seekp(strlListEnd); + + // Update hdrl list size + uint32_t hdrlListEnd = static_cast(file.tellp()); + file.seekp(hdrlListStart + 4); + uint32_t hdrlListSize = hdrlListEnd - hdrlListStart - 8; + file.write(reinterpret_cast(&hdrlListSize), 4); + file.seekp(hdrlListEnd); + + // movi list + uint32_t moviListStart = static_cast(file.tellp()); + writeList(file, 0x69766F6D, nullptr, 0); // 'movi' - we'll fill size later + + std::vector indexEntries; + indexEntries.reserve(frameCount); + + // Write frames with streaming decompression + for (uint32_t i = 0; i < frameCount; ++i) { + uint32_t frameStart = static_cast(file.tellp()) - moviListStart - 4; + + // Prepare frame data (decompresses if necessary and converts to RGB) + std::vector 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(file.tellp()); + file.seekp(moviListStart + 4); + uint32_t moviListSize = moviListEnd - moviListStart - 8; + file.write(reinterpret_cast(&moviListSize), 4); + file.seekp(moviListEnd); + + // idx1 chunk - index + uint32_t idx1Size = static_cast(indexEntries.size() * sizeof(AVIIndexEntry)); + writeChunk(file, 0x31786469, indexEntries.data(), idx1Size); // 'idx1' + + // Update RIFF chunk size + uint32_t fileEnd = static_cast(file.tellp()); + file.seekp(riffStartPos + 4); + uint32_t riffSize = fileEnd - riffStartPos - 8; + file.write(reinterpret_cast(&riffSize), 4); + + return true; + } + }; #endif \ No newline at end of file diff --git a/util/output/frame.hpp b/util/output/frame.hpp index 8a5f7e2..f2945ce 100644 --- a/util/output/frame.hpp +++ b/util/output/frame.hpp @@ -5,13 +5,103 @@ #include #include #include +#include +#include +#include +#include +#include class frame { private: std::vector _data; + std::vector _compressedData; std::unordered_map overheadmap; size_t width; size_t height; + + // Huffman coding structures + struct HuffmanNode { + uint8_t value; + int freq; + std::shared_ptr left, right; + + HuffmanNode(uint8_t val, int f) : value(val), freq(f), left(nullptr), right(nullptr) {} + HuffmanNode(int f, std::shared_ptr l, std::shared_ptr r) + : value(0), freq(f), left(l), right(r) {} + + bool isLeaf() const { return !left && !right; } + }; + + struct HuffmanCompare { + bool operator()(const std::shared_ptr& a, const std::shared_ptr& b) { + return a->freq > b->freq; + } + }; + + void buildHuffmanCodes(const std::shared_ptr& node, const std::string& code, + std::unordered_map& codes) { + if (!node) return; + + if (node->isLeaf()) { + codes[node->value] = code; + return; + } + + buildHuffmanCodes(node->left, code + "0", codes); + buildHuffmanCodes(node->right, code + "1", codes); + } + + std::vector zigzagScan() { + if (width == 0 || height == 0) return _data; + + std::vector result; + result.reserve(_data.size()); + + for (size_t i = 0; i < width + height - 1; ++i) { + if (i % 2 == 0) { + // Even diagonal - go up + for (size_t row = std::min(i, height - 1); row != (size_t)-1 && i - row < width; --row) { + size_t col = i - row; + result.push_back(_data[row * width + col]); + } + } else { + // Odd diagonal - go down + for (size_t col = std::min(i, width - 1); col != (size_t)-1 && i - col < height; --col) { + size_t row = i - col; + result.push_back(_data[row * width + col]); + } + } + } + + return result; + } + + std::vector inverseZigzagScan(const std::vector& zigzagData) { + if (width == 0 || height == 0) return zigzagData; + + std::vector result(_data.size(), 0); + size_t idx = 0; + + for (size_t i = 0; i < width + height - 1; ++i) { + if (i % 2 == 0) { + // Even diagonal - go up + for (size_t row = std::min(i, height - 1); row != (size_t)-1 && i - row < width; --row) { + size_t col = i - row; + result[row * width + col] = zigzagData[idx++]; + } + } else { + // Odd diagonal - go down + for (size_t col = std::min(i, width - 1); col != (size_t)-1 && i - col < height; --col) { + size_t row = i - col; + result[row * width + col] = zigzagData[idx++]; + } + } + } + + return result; + } + +public: enum class colormap { RGB, RGBA, @@ -19,6 +109,7 @@ private: BGRA, B }; + enum class compresstype { RLE, ZIGZAG, @@ -33,44 +124,386 @@ private: colormap colorFormat; 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 - //convert to hex code instead and then run an option - //decompress to return original data - std::vector compressFrameRLE() { - if (cformat == compresstype::ZIGZAG){ + + size_t getWidth() { + return width; + } + 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& data) { + _data = data; + _compressedData.clear(); + cformat = compresstype::RAW; + } + + const std::vector& getData() const { + return _data; + } + + const std::vector& getCompressedData() const { + return _compressedData; + } + + // Run-Length Encoding (RLE) compression + frame& compressFrameRLE() { + if (_data.empty()) { + _compressedData.clear(); + return *this; + } + + if (cformat == compresstype::ZIGZAG) { cformat = compresstype::ZIGZAGRLE; - } else if (cformat == compresstype::DIFF){ + } else if (cformat == compresstype::DIFF) { cformat = compresstype::DIFFRLE; } else { cformat = compresstype::RLE; } - std::vector compressed; - if (_data.empty()) return compressed; + + _compressedData.clear(); + _compressedData.reserve(_data.size() * 2); + size_t i = 0; - while (i < _data.size()) { - + uint8_t current = _data[i]; + size_t count = 1; + + // Count consecutive identical bytes + while (i + count < _data.size() && _data[i + count] == current && count < 255) { + count++; + } + + if (count > 1) { + // Encode run: 0xFF marker, count, value + _compressedData.push_back(0xFF); + _compressedData.push_back(static_cast(count)); + _compressedData.push_back(current); + i += count; + } else { + // Encode literal sequence + size_t literal_start = i; + while (i < _data.size() && + (i + 1 >= _data.size() || _data[i] != _data[i + 1]) && + (i - literal_start) < 127) { + i++; + } + + size_t literal_length = i - literal_start; + _compressedData.push_back(static_cast(literal_length)); + + for (size_t j = literal_start; j < i; ++j) { + _compressedData.push_back(_data[j]); + } + } } + // Store compression metadata in overheadmap + overheadmap[0] = static_cast(cformat); + overheadmap[1] = static_cast(_compressedData.size() > 0 ? 1 : 0); + + return *this; } - std::vector compressFrameZigZag() { - if (cformat != compresstype::RAW) { - //FAIL + + frame& decompressFrameRLE() { + if (_compressedData.empty()) { + return *this; } + + std::vector 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) { + throw std::runtime_error("Cannot apply zigzag to already compressed data"); + } + cformat = compresstype::ZIGZAG; + _compressedData = zigzagScan(); + + // Store metadata + overheadmap[0] = static_cast(cformat); + overheadmap[1] = static_cast(width); + overheadmap[2] = static_cast(height); + + return *this; } - std::vector compressFrameDiff() { - if (cformat != compresstype::RAW) { - //FAIL should decompress and recompress or just return false? + + frame& decompressFrameZigZag() { + if (_compressedData.empty()) { + return *this; } + + _data = inverseZigzagScan(_compressedData); + cformat = compresstype::RAW; + overheadmap.clear(); + + return *this; + } + + // Differential compression + frame& compressFrameDiff() { + if (cformat != compresstype::RAW) { + throw std::runtime_error("Cannot apply diff to already compressed data"); + } + cformat = compresstype::DIFF; + _compressedData.clear(); + _compressedData.reserve(_data.size()); + + if (_data.empty()) { + return *this; + } + + // First value remains the same + _compressedData.push_back(_data[0]); + + // Subsequent values are differences + for (size_t i = 1; i < _data.size(); ++i) { + int16_t diff = static_cast(_data[i]) - static_cast(_data[i - 1]); + // Convert to unsigned with bias of 128 + _compressedData.push_back(static_cast((diff + 128) & 0xFF)); + } + + // Store metadata + overheadmap[0] = static_cast(cformat); + overheadmap[1] = static_cast(_data.size() > 0 ? 1 : 0); + + return *this; } - std::vector compressFrameHuffman() { - + frame& decompressFrameDiff() { + if (_compressedData.empty()) { + return *this; + } + + std::vector original; + original.reserve(_compressedData.size()); + + // First value is original + original.push_back(_compressedData[0]); + + // Reconstruct subsequent values + for (size_t i = 1; i < _compressedData.size(); ++i) { + int16_t reconstructed = static_cast(original[i - 1]) + + (static_cast(_compressedData[i]) - 128); + // Clamp to 0-255 + reconstructed = std::max(0, std::min(255, static_cast(reconstructed))); + original.push_back(static_cast(reconstructed)); + } + + _data = std::move(original); + cformat = compresstype::RAW; + overheadmap.clear(); + + return *this; + } + + // Huffman compression + frame& compressFrameHuffman() { + cformat = compresstype::HUFFMAN; + _compressedData.clear(); + + if (_data.empty()) { + return *this; + } + + // Calculate frequency of each byte value + std::unordered_map freq; + for (uint8_t byte : _data) { + freq[byte]++; + } + + // Build Huffman tree + std::priority_queue, + std::vector>, + HuffmanCompare> pq; + + for (const auto& pair : freq) { + pq.push(std::make_shared(pair.first, pair.second)); + } + + while (pq.size() > 1) { + auto left = pq.top(); pq.pop(); + auto right = pq.top(); pq.pop(); + + auto parent = std::make_shared(left->freq + right->freq, left, right); + pq.push(parent); + } + + auto root = pq.top(); + + // Build codes + std::unordered_map codes; + buildHuffmanCodes(root, "", codes); + + // Encode data + std::string bitString; + for (uint8_t byte : _data) { + bitString += codes[byte]; + } + + // Convert bit string to bytes + // Store frequency table size + _compressedData.push_back(static_cast(freq.size())); + + // Store frequency table + for (const auto& pair : freq) { + _compressedData.push_back(pair.first); + // Store frequency as 4 bytes + for (int i = 0; i < 4; ++i) { + _compressedData.push_back(static_cast((pair.second >> (i * 8)) & 0xFF)); + } + } + + // Store encoded data + uint8_t currentByte = 0; + int bitCount = 0; + + for (char bit : bitString) { + currentByte = (currentByte << 1) | (bit == '1' ? 1 : 0); + bitCount++; + + if (bitCount == 8) { + _compressedData.push_back(currentByte); + currentByte = 0; + bitCount = 0; + } + } + + // Pad last byte if necessary + if (bitCount > 0) { + currentByte <<= (8 - bitCount); + _compressedData.push_back(currentByte); + // Store number of padding bits + _compressedData.push_back(static_cast(8 - bitCount)); + } else { + _compressedData.push_back(0); // No padding + } + + // Store metadata + overheadmap[0] = static_cast(cformat); + overheadmap[1] = static_cast(freq.size()); + + return *this; + } + + // 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(_data.size()) / _compressedData.size(); + } + + compresstype getCompressionType() const { + return cformat; + } + + const std::unordered_map& getOverheadMap() const { + return overheadmap; + } + + bool isCompressed() const { + return cformat != compresstype::RAW && !_compressedData.empty(); } - //get compression ratio }; #endif \ No newline at end of file diff --git a/util/output/video.hpp b/util/output/video.hpp deleted file mode 100644 index 57584b9..0000000 --- a/util/output/video.hpp +++ /dev/null @@ -1,667 +0,0 @@ -#ifndef VIDEO_HPP -#define VIDEO_HPP - - -#include "frame.hpp" -#include -#include -#include -#include -#include -#include -#include -#include "../timing_decorator.hpp" - -class video { -private: - std::vector>> compressed_frames_; - std::unordered_map keyframe_indices_; // Maps frame index to keyframe index - size_t width_; - size_t height_; - std::vector channels_; - double fps_; - bool use_differential_encoding_; - size_t keyframe_interval_; - - // Compress frame using differential encoding - std::vector> 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 diff_data(current_frame.size()); - - const std::vector& current_data = current_frame.data(); - const std::vector& 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>& 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 reconstructed_data(diff_frame.size()); - const std::vector& diff_data = diff_frame.data(); - const std::vector& 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& 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 channels, - double fps = 30.0, bool use_differential = true, size_t keyframe_interval = 50) - : video(width, height, std::vector(channels), fps, use_differential, keyframe_interval) {} - - // Accessors - size_t width() const noexcept { return width_; } - size_t height() const noexcept { return height_; } - const std::vector& 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& 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 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 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 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); - } - 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(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 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 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(&value); - for (size_t i = 0; i < sizeof(double); ++i) { - result.push_back(bytes[i]); - } - }; - - // Write header - add_uint32(static_cast(width_)); - add_uint32(static_cast(height_)); - result.push_back(static_cast(channels_.size())); - for (char c : channels_) { - result.push_back(static_cast(c)); - } - add_double(fps_); - result.push_back(use_differential_encoding_ ? 1 : 0); - add_uint32(static_cast(keyframe_interval_)); - add_uint32(static_cast(compressed_frames_.size())); - - // Write keyframe indices - add_uint32(static_cast(keyframe_indices_.size())); - for (const auto& kv : keyframe_indices_) { - add_uint32(static_cast(kv.first)); - } - - // Write compressed frames - for (const auto& compressed_frame : compressed_frames_) { - add_uint32(static_cast(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& 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(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(&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 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(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> 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 \ No newline at end of file