diff --git a/.gitmodules b/.gitmodules index 4294cea..883f334 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "include/args"] path = include/args url = https://github.com/Taywee/args.git +[submodule "include/dpx"] + path = include/dpx + url = https://github.com/malcolmhumphreys/dpx.git diff --git a/include/dpx b/include/dpx new file mode 160000 index 0000000..3ba0f19 --- /dev/null +++ b/include/dpx @@ -0,0 +1 @@ +Subproject commit 3ba0f19e8fbf4becf165d7deda2c8cfb334e6c8c diff --git a/include/dpx.hpp b/include/dpx.hpp index 169d6fb..fd21bef 100644 --- a/include/dpx.hpp +++ b/include/dpx.hpp @@ -1,3 +1,4 @@ +/* #ifndef DPX_HPP #define DPX_HPP @@ -12,55 +13,61 @@ using namespace cv; class DPX { public: - DPX(); + DPX(const std::string& filepath); + ~DPX() = default; + + // Get the loaded raw image data + cv::Mat getRawImage() const { return raw; } - bool read(const string& filename); - Mat getMat() const; - Mat getMatBitDepth(int targetBitDepth) const; + // Get image properties + int getBitDepth() const { return bitDepth; } + int getWidth() const { return width; } + int getHeight() const { return height; } - int getWidth() const; - int getHeight() const; - int getBitDepth() const; + // Convert to 8-bit or 10-bit representation + cv::Mat convertTo8Bit() const; + cv::Mat convertTo10Bit() const; private: struct DPXHeader { - uint32_t magic; // Magic number (0x53445058 "SDPX" or 0x58504453 "XPDS") + uint32_t magic; // Magic number (0x53445058 or 'SDPX') uint32_t imageOffset; // Offset to image data - uint16_t width; - uint16_t height; - uint8_t descriptor; // Image descriptor - uint8_t bitDepth; // Bits per component - uint8_t imageElements; // Number of image elements - bool isBigEndian; // File endianness + uint32_t fileVersion; // Version number of header format + uint32_t fileSize; // Total image file size in bytes + uint16_t dittoKey; // Image content flag + uint16_t headerSize; // Generic header length in bytes + uint32_t industrySize; // Industry specific header length + uint32_t userSize; // User defined data length + uint32_t encryptKey; // Encryption key }; - DPXHeader header; - Mat image; + struct DPXImageElement { + uint32_t dataSign; // Data sign (0 = unsigned, 1 = signed) + uint32_t lowData; // Reference low data code value + uint32_t lowQuantity; // Reference low quantity + uint32_t highData; // Reference high data code value + uint32_t highQuantity; // Reference high quantity + uint8_t descriptor; // Descriptor for image element + uint8_t transfer; // Transfer characteristics + uint8_t colorimetric; // Colorimetric specification + uint8_t bitDepth; // Bit depth + uint16_t packing; // Packing method + uint16_t encoding; // Encoding method + }; - bool readHeader(ifstream& file); - void readImageData(ifstream& file); - uint32_t swapEndian(uint32_t value); - uint16_t swapEndian(uint16_t value); + cv::Mat raw; // Raw image data + int bitDepth; // Image bit depth + int width; // Image width + int height; // Image height + + // Private helper methods + void readHeader(std::ifstream& file); + void readImageData(std::ifstream& file, uint32_t imageOffset); + void processImageData(std::vector& buffer); + void unpack10BitData(const std::vector& buffer, cv::Mat& output); + void swapEndianness(uint8_t* data, size_t size); + bool isLittleEndian() const; }; #endif - -/* Example usage -int main() { - try { - DPX dpx; - dpx.read("image.dpx"); - - // Get the original 16-bit Mat - Mat mat16 = dpx.getMat(); - - // Get 8-bit version - Mat mat8 = dpx.getMatBitDepth(8); - - return 0; - } - catch (const exception& e) { - cerr << "Error: " << e.what() << endl; - return 1; - } -}*/ \ No newline at end of file +*/ \ No newline at end of file diff --git a/include/image.hpp b/include/image.hpp index 206df6c..655cb3e 100644 --- a/include/image.hpp +++ b/include/image.hpp @@ -17,7 +17,7 @@ #include #include -#include "dpx.hpp" +#include "dpx/include/dpx.h" using namespace std; using namespace cv; diff --git a/src/dpx.cpp b/src/dpx.cpp index e8a7318..25e0e57 100644 --- a/src/dpx.cpp +++ b/src/dpx.cpp @@ -1,262 +1,133 @@ #include "dpx.hpp" -DPX::DPX() {} - -uint32_t DPX::swapEndian(uint32_t value) { - return ((value & 0xFF000000) >> 24) | - ((value & 0x00FF0000) >> 8) | - ((value & 0x0000FF00) << 8) | - ((value & 0x000000FF) << 24); -} - -uint16_t DPX::swapEndian(uint16_t value) { - return ((value & 0xFF00) >> 8) | - ((value & 0x00FF) << 8); -} - -bool DPX::readHeader(ifstream& file) { - // DPX Header Map for reference: - // 0x0000-0x0003: Magic number - // 0x0004-0x0007: Image data offset - // 0x0008-0x000B: Version number of header format - // 0x000C-0x000F: Total image file size - // 0x0010-0x0013: Ditto Key - // 0x0014-0x0017: Generic section header length - // 0x0018-0x001B: Industry specific header length - // 0x001C-0x001F: User defined header length - - // Read and check magic number - file.read(reinterpret_cast(&header.magic), sizeof(header.magic)); - - const uint32_t MAGIC_BE = 0x53445058; // "SDPX" - const uint32_t MAGIC_LE = 0x58504453; // "XPDS" - - cout << "{ \"dpx_magic_number\" : \"0x" << hex << header.magic << dec << "\" }" << endl; - - if (header.magic == MAGIC_BE) { - header.isBigEndian = true; - } else if (header.magic == MAGIC_LE) { - header.isBigEndian = false; - } else { - throw runtime_error("Not a valid DPX file - Invalid magic number"); - } - - // Read Core Header - uint32_t genericHeaderLength; - file.seekg(0x14); // Go to generic header length - file.read(reinterpret_cast(&genericHeaderLength), sizeof(genericHeaderLength)); - if (!header.isBigEndian) { - genericHeaderLength = swapEndian(genericHeaderLength); - } - cout << "{ \"dpx_header_length\" : " << genericHeaderLength << " }" << endl; - - // Image Information Header starts at 0x0200 (512) - file.seekg(0x0200); - - // Read orientation - uint32_t orientation; - file.read(reinterpret_cast(&orientation), sizeof(orientation)); - if (!header.isBigEndian) { - orientation = swapEndian(orientation); - } - - // Read number of image elements - uint16_t numberOfElements; - file.read(reinterpret_cast(&numberOfElements), sizeof(numberOfElements)); - if (!header.isBigEndian) { - numberOfElements = swapEndian(numberOfElements); - } - - // Read image dimensions - file.read(reinterpret_cast(&header.width), sizeof(header.width)); - file.read(reinterpret_cast(&header.height), sizeof(header.height)); - if (!header.isBigEndian) { - header.width = swapEndian(header.width); - header.height = swapEndian(header.height); - } - - cout << "{ \"dpx_width\" : " << header.width << ", \"dpx_height\" :" << header.height << " }" << endl; - - // Image Element Information starts at 0x0604 (1540) - file.seekg(0x0604); - - // Read data sign (0 = unsigned) - uint32_t dataSign; - file.read(reinterpret_cast(&dataSign), sizeof(dataSign)); - if (!header.isBigEndian) { - dataSign = swapEndian(dataSign); - } - - // Read bit depth - file.read(reinterpret_cast(&header.bitDepth), sizeof(header.bitDepth)); - cout << "{ \"dpx_bit_depth\" : " << static_cast(header.bitDepth) << " }"<< endl; - - // Read packing method - uint16_t packing; - file.read(reinterpret_cast(&packing), sizeof(packing)); - if (!header.isBigEndian) { - packing = swapEndian(packing); - } - cout << "{ \"dpx_packing\" : " << packing << " }" << endl; - - // Read image offset (from start of file) - file.seekg(0x04); - file.read(reinterpret_cast(&header.imageOffset), sizeof(header.imageOffset)); - if (!header.isBigEndian) { - header.imageOffset = swapEndian(header.imageOffset); - } - cout << "{ \"dpx_offset\" : " << header.imageOffset << " }" << endl; - - return true; -} - -void DPX::readImageData(ifstream& file) { - file.seekg(header.imageOffset); - - image = Mat(header.height, header.width, CV_16UC3); - - if (header.bitDepth == 10) { - size_t packedSize = ((header.width * header.height * 3 * 10 + 31) / 32) * 4; - vector buffer(packedSize); - - file.read(reinterpret_cast(buffer.data()), packedSize); - - uint16_t* imgPtr = reinterpret_cast(image.data); - size_t pixelCount = 0; - - for (size_t i = 0; i < packedSize - 3; i += 4) { - // Extract four bytes - uint32_t b0 = buffer[i]; - uint32_t b1 = buffer[i + 1]; - uint32_t b2 = buffer[i + 2]; - uint32_t b3 = buffer[i + 3]; - - // Combine bytes according to endianness - uint32_t packed; - if (header.isBigEndian) { - packed = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3; - } else { - packed = (b3 << 24) | (b2 << 16) | (b1 << 8) | b0; - } - - // Extract three 10-bit values - uint16_t pix1 = (packed >> 22) & 0x3FF; - uint16_t pix2 = (packed >> 12) & 0x3FF; - uint16_t pix3 = (packed >> 2) & 0x3FF; - - // Scale to 16-bit - pix1 <<= 6; - pix2 <<= 6; - pix3 <<= 6; - - if (pixelCount < header.width * header.height * 3) { - size_t row = (pixelCount / 3) / header.width; - size_t col = (pixelCount / 3) % header.width; - size_t channel = pixelCount % 3; - - size_t idx = row * header.width * 3 + col * 3 + (2 - channel); - imgPtr[idx] = pix1; - - if (pixelCount + 1 < header.width * header.height * 3) { - channel = (pixelCount + 1) % 3; - idx = ((pixelCount + 1) / 3) / header.width * header.width * 3 + - ((pixelCount + 1) / 3) % header.width * 3 + (2 - channel); - imgPtr[idx] = pix2; - } - - if (pixelCount + 2 < header.width * header.height * 3) { - channel = (pixelCount + 2) % 3; - idx = ((pixelCount + 2) / 3) / header.width * header.width * 3 + - ((pixelCount + 2) / 3) % header.width * 3 + (2 - channel); - imgPtr[idx] = pix3; - } - } - - pixelCount += 3; - } - } else { - // Handle other bit depths (8, 12, 16) - size_t bytesPerPixel = (header.bitDepth + 7) / 8; - vector buffer(header.width * header.height * 3 * bytesPerPixel); - - file.read(reinterpret_cast(buffer.data()), buffer.size()); - - uint16_t* imgPtr = reinterpret_cast(image.data); - - for (int y = 0; y < header.height; y++) { - for (int x = 0; x < header.width; x++) { - for (int c = 0; c < 3; c++) { - size_t idx = (y * header.width * 3 + x * 3 + c) * bytesPerPixel; - uint16_t pixel = 0; - - switch (header.bitDepth) { - case 8: - pixel = buffer[idx] << 8; - break; - case 12: - if (header.isBigEndian) { - pixel = ((uint16_t)buffer[idx] << 8) | buffer[idx + 1]; - } else { - pixel = ((uint16_t)buffer[idx + 1] << 8) | buffer[idx]; - } - pixel = pixel << 4; - break; - case 16: - if (header.isBigEndian) { - pixel = ((uint16_t)buffer[idx] << 8) | buffer[idx + 1]; - } else { - pixel = ((uint16_t)buffer[idx + 1] << 8) | buffer[idx]; - } - break; - } - - imgPtr[y * header.width * 3 + x * 3 + (2 - c)] = pixel; - } - } - } - } -} - -bool DPX::read(const string& filename) { - ifstream file(filename, ios::binary); +/* +DPX::DPX(const std::string& filepath) { + std::ifstream file(filepath, std::ios::binary); if (!file.is_open()) { - throw runtime_error("Could not open file: " + filename); + throw std::runtime_error("Failed to open DPX file: " + filepath); } - if (!readHeader(file)) { - return false; + readHeader(file); + readImageData(file, header.imageOffset); +} + +void DPX::readHeader(std::ifstream& file) { + DPXHeader header; + file.read(reinterpret_cast(&header), sizeof(DPXHeader)); + + // Check magic number (SDPX in ASCII) + if (header.magic != 0x53445058) { + if (header.magic == 0x58504453) { // XPDS - wrong endianness + swapEndianness(reinterpret_cast(&header), sizeof(DPXHeader)); + } else { + throw std::runtime_error("Invalid DPX file format"); + } } - readImageData(file); - return true; + // Read image information header (located after main header) + uint32_t orientation; + file.read(reinterpret_cast(&orientation), sizeof(uint32_t)); + file.read(reinterpret_cast(&width), sizeof(uint32_t)); + file.read(reinterpret_cast(&height), sizeof(uint32_t)); + + // Read image element information + DPXImageElement element; + file.read(reinterpret_cast(&element), sizeof(DPXImageElement)); + + bitDepth = element.bitDepth; } -Mat DPX::getMat() const { - return image; +void DPX::readImageData(std::ifstream& file, uint32_t imageOffset) { + // Seek to image data + file.seekg(imageOffset, std::ios::beg); + + // Calculate buffer size based on bit depth and dimensions + size_t bytesPerPixel = (bitDepth + 7) / 8; + size_t rowPadding = (width * 3 * bytesPerPixel) % 4 ? 4 - ((width * 3 * bytesPerPixel) % 4) : 0; + size_t bufferSize = (width * 3 * bytesPerPixel + rowPadding) * height; + + // Read image data into buffer + std::vector buffer(bufferSize); + file.read(reinterpret_cast(buffer.data()), bufferSize); + + processImageData(buffer); } -Mat DPX::getMatBitDepth(int targetBitDepth) const { - if (targetBitDepth != 8 && targetBitDepth != 10) { - throw invalid_argument("Target bit depth must be 8 or 10"); +void DPX::processImageData(std::vector& buffer) { + switch (bitDepth) { + case 10: + raw = cv::Mat(height, width, CV_16UC3); + unpack10BitData(buffer, raw); + break; + + case 16: + raw = cv::Mat(height, width, CV_16UC3); + // Handle 16-bit data + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + for (int c = 0; c < 3; c++) { + size_t idx = (y * width + x) * 6 + c * 2; + uint16_t value = (buffer[idx] << 8) | buffer[idx + 1]; + raw.at(y, x)[c] = value; + } + } + } + break; + + default: + throw std::runtime_error("Unsupported bit depth: " + std::to_string(bitDepth)); } +} - Mat result; - if (targetBitDepth == 8) { - image.convertTo(result, CV_8UC3, 1.0/256.0); - } else if (targetBitDepth == 10) { - image.convertTo(result, CV_16UC3, 1.0/64.0); +void DPX::unpack10BitData(const std::vector& buffer, cv::Mat& output) { + // Handle 10-bit packed data (DaVinci Resolve format) + // 3 pixels (9 bytes) -> 3 * 10-bit values + size_t pixelIdx = 0; + for (size_t i = 0; i < buffer.size() - 8; i += 9) { + int y = pixelIdx / width; + int x = pixelIdx % width; + + if (y >= height) break; + + // Unpack 3 10-bit values from 9 bytes + uint32_t val1 = ((buffer[i] << 2) | (buffer[i + 1] >> 6)) & 0x3FF; + uint32_t val2 = ((buffer[i + 1] << 4) | (buffer[i + 2] >> 4)) & 0x3FF; + uint32_t val3 = ((buffer[i + 2] << 6) | (buffer[i + 3] >> 2)) & 0x3FF; + + // Scale to 16-bit + output.at(y, x)[0] = static_cast(val1 << 6); + output.at(y, x)[1] = static_cast(val2 << 6); + output.at(y, x)[2] = static_cast(val3 << 6); + + pixelIdx++; } - return result; } -int DPX::getWidth() const { - return header.width; +cv::Mat DPX::convertTo8Bit() const { + cv::Mat output; + raw.convertTo(output, CV_8UC3, 1.0 / 256.0); + return output; } -int DPX::getHeight() const { - return header.height; +cv::Mat DPX::convertTo10Bit() const { + cv::Mat output; + if (bitDepth == 16) { + raw.convertTo(output, CV_16UC3, 1.0 / 64.0); + } else { + output = raw.clone(); + } + return output; } -int DPX::getBitDepth() const { - return header.bitDepth; -} \ No newline at end of file +void DPX::swapEndianness(uint8_t* data, size_t size) { + for (size_t i = 0; i < size/2; i++) { + std::swap(data[i], data[size - 1 - i]); + } +} + +bool DPX::isLittleEndian() const { + uint16_t number = 0x1; + uint8_t* ptr = reinterpret_cast(&number); + return (*ptr == 1); +} +*/ \ No newline at end of file diff --git a/src/image.cpp b/src/image.cpp index f24386b..d1950f2 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -16,9 +16,10 @@ Mat Image::loadImage (string& image_path, uint64_t& x, uint64_t& y, uint64_t& w, string ext = getExtLower(located_path); //cout << "{ \"image_extension\" : \"" << ext << "\" }" << endl; if (ext == "dpx") { - DPX dpx; - dpx.read(located_path); - loaded = dpx.getMatBitDepth(8); + DpxReader in; + //in.open(image_path); + //DPX dpx(located_path); + //loaded = dpx.convertTo8Bit(); cout << "{ \"dpx\" : \"" << image_path << "\" }" << endl; } else { loaded = imread(located_path, IMREAD_COLOR);