// dpxReader.C // Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved. #ifndef FN_EXAMPLE_PLUGIN #include "Base/fnThreadLocalStorage.h" #endif // ndef FN_EXAMPLE_PLUGIN #include "DDImage/FileReader.h" #include "DDImage/Row.h" #include "DDImage/DDMath.h" #include "DDImage/Knob.h" #include "DDImage/ARRAY.h" #include "DDImage/Matrix3.h" #include "DDImage/Memory.h" #include "DDImage/MemoryHolder.h" #include "DDImage/MemoryHolderFactory.h" #include "DDImage/LUT.h" #include "DPXimage.h" #include #include #include #include #include using namespace DD::Image; const float Kb = .0722f; //.114f; const float Kr = .2126f; //.299f; ///! Sanitizes the string by replacing any non-printable characters with ///! C-style escape sequences. inline std::string SanitizeString(const std::string& s) { std::ostringstream oss; for (size_t i = 0; i < s.length(); i++) { if (isprint(s[i])) oss << s[i]; else // double-cast here is intentional to (a) keep character in range 0-255 rather than -128-127 and // (b) then to print it to the stream as a number rather than as a char oss << "\\x" << std::setw(2) << std::hex << std::setfill('0') << (unsigned int)((unsigned char)s[i]); } return oss.str(); } struct Count { unsigned int value; Count() : value(0) {} }; #ifndef FN_EXAMPLE_PLUGIN typedef Foundry::Base::ThreadLocalStorage ThreadLocalCount; #endif //FN_EXAMPLE_PLUGIN #define kMaxDPXElements 8 // FileBuffer to hold data from the file for up to kMaxDPXElements DPX elements. // Derives from MemoryHolder to provide usage information to Nuke's memory manager. class FileBuffer : public MemoryHolder { public: static FileBuffer* create(Iop* iopOwner) { return MemoryHolderFactory::create(iopOwner); } ~FileBuffer() { deleteBuffers(); } void resizeBuffer(int index, size_t requestedSize) { // Resize the buffer if it is bigger or smaller than the requested size (so we have enough space, // but also don't keep hold of more space than we need). if (_bufferSize[index] != requestedSize) { if (_buffer[index] != NULL) { Memory::deallocate(_buffer[index]); } _buffer[index] = Memory::allocate(requestedSize); _bufferSize[index] = requestedSize; } } // Get and set the y-coordinate of the first and last lines stored in the buffer. void setYMin(int y) { _yMin = y; } int getYMin() const { return _yMin; } void setYMax(int y) { _yMax = y; } int getYMax() const { return _yMax; } void setYRange(int yMin, int yMax) { _yMin = yMin; _yMax = yMax; } // Get the buffer to use for the given element. uchar* getBuffer(int index) { mFnAssert(index >= 0 && index < kMaxDPXElements); return _buffer[index]; } // Get the buffer size for the given element. size_t getBufferSize(int index) const { mFnAssert(index >=0 && index < kMaxDPXElements); return _bufferSize[index]; } // Return the total amount of memory allocated across all elements. size_t getTotalBufferSize() const { size_t totalBufferSize = 0; for (unsigned int i = 0; i < kMaxDPXElements; i++) { totalBufferSize += _bufferSize[i]; } return totalBufferSize; } // Implementation of memoryFree from MemoryHolder. Frees memory, if the file buffer // is not currently being accessed by any thread. virtual bool memoryFree(size_t amount) { if (getTotalBufferSize() == 0) { // There's nothing to free - return immediately. return false; } // If the buffer is not currently in use, we will try to free it. if (getUserCount() == 0) { // Lock the buffer to flag to any new users that they should not try to use it, // but instead read any lines they need directly from the file. _locked = true; // Check for any new users that sneaked in before we managed to lock. if (getUserCount() > 0) { // Unlock - we can't free anything as the buffer is now in use. _locked = false; return false; } // The buffer's not in use - we can free it. deleteBuffers(); return true; } // The buffer was in use - we couldn't free anything. return false; } // Implementation of memoryInfo from MemoryHolder virtual void memoryInfo(Memory::MemoryInfoArray& output, const void* restrict_to) const { // Sum up the memory in all our buffers. unsigned int totalBytes = 0; for (unsigned int i = 0; i < kMaxDPXElements; i++) { totalBytes += _bufferSize[i]; } output.push_back(Memory::MemoryInfo(_iopOwner, totalBytes)); } // Implementation of memoryWeight from MemoryHolder virtual int memoryWeight() const { // getUserCount() provides a snapshot of whether any threads are currently accessing the // buffer. If it returns 0, it doesn't mean a thread might not be just about to access // the buffer, but we can't know one way or the other. Our best guess if that if the // buffer is currently in use, it would not be efficient to free it. If it's not in use, // we will always allow it to be freed, and any thread which was about to access it will // be forced to fall back to reading from the file instead. (This will still work, it // just might be slower.) if (getUserCount() > 0) { // The buffer is in use - prefer not to free it. return 1000; } else { // Nothing's using the buffer - it's probably OK to free it. return 0; } } // Increment the user count for the current thread. void addUser() { #ifndef FN_EXAMPLE_PLUGIN _bufferUsers.local().value++; #endif // FN_EXAMPLE_PLUGIN } // Decrement the user count for the current thread. void removeUser() { #ifndef FN_EXAMPLE_PLUGIN _bufferUsers.local().value--; #endif // FN_EXAMPLE_PLUGIN } // Get the total user count over all threads which have accessed the file buffer. unsigned int getUserCount() const { unsigned int totalUsers = 0; #ifndef FN_EXAMPLE_PLUGIN for (ThreadLocalCount::iterator it = _bufferUsers.begin(); it != _bufferUsers.end(); ++it) { totalUsers += it->value; } #endif // FN_EXAMPLE_PLUGIN return totalUsers; } // If the buffer is locked, you should not attempt to use it. bool locked() { return _locked; } protected: // Don't call this directly; use FileBuffer::create() instead, to register // the cache with Nuke's memory manager. FileBuffer(Iop* iopOwner) : _iopOwner(iopOwner) , _locked(false) { // Initialise file buffers for (unsigned int i = 0; i < kMaxDPXElements; i++) { _buffer[i] = NULL; _bufferSize[i] = 0; } } // Clear buffers and return whether or not any buffers were in use (used by memoryFree). bool deleteBuffers() { bool wasCleared = false; for (unsigned int i = 0; i < kMaxDPXElements; i++) { wasCleared |= clearBuffer(i); } return wasCleared; } // Clear buffer and return whether or not the buffer was in use (used by memoryFree). bool clearBuffer(int index) { bool wasCleared = false; if (_buffer[index] != NULL) { Memory::deallocate(_buffer[index]); _buffer[index] = NULL; wasCleared = true; } _bufferSize[index] = 0; return wasCleared; } private: uchar* _buffer[kMaxDPXElements]; //!< Internal file buffer to hold up to kMaxDPXElements DPX elements. size_t _bufferSize[kMaxDPXElements]; //!< The current size of the file buffer for each of the kMaxDPXElements DPX elements. int _yMin; //!< The y-coordinate of the first line stored in the buffer. int _yMax; //!< The y-coordinate of the last line stored in the buffer. Iop* _iopOwner; //!< The Iop that owns the thing that created this file buffer. For memory-tracking purposes. #ifndef FN_EXAMPLE_PLUGIN mutable ThreadLocalCount _bufferUsers; //!< Thread-local counter to keep track of whether or not the buffer is in use. #endif //FN_EXAMPLE_PLUGIN bool _locked; //!< Lock the buffer. }; // Scoped file buffer guard. Flag that we are using the buffer on construction, // and remove the flag on destruction. class FileBufferGuard { public: FileBufferGuard(FileBuffer* buffer) : _buffer(buffer) { _buffer->addUser(); } ~FileBufferGuard() { _buffer->removeUser(); } private: FileBuffer* _buffer; }; class dpxReader : public FileReader { // this is the parts of the header we keep: bool _flipEndian; bool ycbcr_hack; unsigned orientation; unsigned width; unsigned height; struct Element { U8 descriptor; U8 transfer; //!< The transfer characteristics used to transform the data when writing the file. U8 bits; bool is_nl_matte; // Set if channel contains a Northlight matte U16 packing; U32 dataOffset; U32 bytes; // bytes per line int components; // descriptor decoded into # of components ChannelSet channels; // which Nuke channels it supplies } element[kMaxDPXElements]; int _fileSize; //!< The size of the dpx file. int _numElements; //!< The number of elements with data. static const Description description; MetaData::Bundle meta; // Storage for whether or not we should read all lines at once. bool _readAllLines; // Internal file buffer controls. This buffer is used to store the entire contents of the file when // reading full frames (where possible) has been requested on the command line. FileBuffer* _requestedLinesPreloadBuffer; bool testFileBuffer() const { // Test returns true if we have a file buffer and it is not locked. return (_requestedLinesPreloadBuffer != NULL && !_requestedLinesPreloadBuffer->locked()); } // Whether we need to invert y when reading from the file. bool invertY() const { return !(orientation & 2); } void readAllLines() { // If we have a file buffer, and it hasn't been locked due to low memory, allocate // space for reading the requested lines into. if (testFileBuffer()) { ChannelSet remaining(info_.channels()); if (ycbcr_hack && (info_.channels() & Mask_RGB)) remaining += Mask_RGB; // Determine the first and last lines we need to read from the file. const Box& requestBox = iop->requestedBox(); const int yMin = invertY() ? height - requestBox.t() : requestBox.y(); const int yMax = invertY() ? height - requestBox.y() - 1 : requestBox.t(); // Guard to prevent memory being freed while we are trying to allocate it. FileBufferGuard guard(_requestedLinesPreloadBuffer); // Test again to make sure no memory is being freed. if (!_requestedLinesPreloadBuffer->locked()) { for (unsigned i = 0; i < kMaxDPXElements; i++) { if (element[i].channels & remaining) { // Calculate the buffer size required to read all requested lines for // the current element from the file. const unsigned int nLines = yMax - yMin + 1; const size_t bufferSize = element[i].bytes * nLines; // This will reallocate the buffer if the size has changed. _requestedLinesPreloadBuffer->resizeBuffer(i, bufferSize); _requestedLinesPreloadBuffer->setYRange(yMin, yMax); // Read all requested lines from the file into our internal buffer. read((void *)_requestedLinesPreloadBuffer->getBuffer(i), element[i].dataOffset + yMin * element[i].bytes, static_cast(bufferSize)); remaining -= element[i].channels; if (!remaining) break; } } } } } public: const MetaData::Bundle& fetchMetaData(const char* key) { return meta; } dpxReader(Read* iop, int fd, const unsigned char* block, int len) : FileReader(iop, fd, block, len) { _readAllLines = readAllLinesRequested(); if (_readAllLines) { // Use an internal file buffer to cache the requested lines from the file _requestedLinesPreloadBuffer = FileBuffer::create(iop); } else { _requestedLinesPreloadBuffer = NULL; } // #rick: Maybe this sort of thing should by moved to FileReader? // Find out how large the whole file is for use by planarReadPass(). struct stat statBuffer; fstat(fd, &statBuffer); _fileSize = statBuffer.st_size; DPXHeader header; // Copy the header chunk raw: read(&header, 0, int(sizeof(header))); // Put the header into native-endianess: if (header.file.magicNumber != DPX_MAGIC) { _flipEndian = true; flip(&header.file.magicNumber, 2); // magicNumber & offsetToImageData flip(&header.file.totalFileSize, 5); // totalFileSize thru userDataSize flip(&header.image.orientation, 2); // orientation & numberElements flip(&header.image.pixelsPerLine, 2); // pixelsPerLine & linesPerImage for (int i = 0; i < header.image.numberElements; i++) { flip(&header.image.element[i].dataSign, 5); // dataSign, low/high stuff flip(&header.image.element[i].packing, 2); // packing & encoding flip(&header.image.element[i].dataOffset, 3); // dataOffset, eol/imagePadding } flip((U32*)(&header.film.frameRate), 1); flip((U32*)(&header.film.framePosition), 1); flip((U32*)(&header.film.sequenceLen), 1); flip((U32*)(&header.film.heldCount), 1); flip((U32*)(&header.film.shutterAngle), 1); flip((U32*)(&header.film.frameId), 1); flip((U32*)(&header.video.horzSampleRate), 10); flip(&header.video.timeCode, 2); flip((U32*)(&header.orientation.pixelAspect), 2); } else { _flipEndian = false; } width = header.image.pixelsPerLine; height = header.image.linesPerImage; _numElements = header.image.numberElements; // Figure out the pixel aspect ratio. We recognize two possible // 'invalid' values -- all 0s or all 1s. // Equality is also invalid because Shake writes that for all images // Another bug version writes the image size as the pixel aspect, // ignore that. double pixel_aspect = 0; if ( header.orientation.pixelAspect[0] != 0 && header.orientation.pixelAspect[1] != 0 && header.orientation.pixelAspect[0] != 0xffffffff && header.orientation.pixelAspect[1] != 0xffffffff && header.orientation.pixelAspect[0] != header.orientation.pixelAspect[1] && (header.orientation.pixelAspect[0] != width || header.orientation.pixelAspect[1] != height)) { pixel_aspect = (double)header.orientation.pixelAspect[0] / (double)header.orientation.pixelAspect[1]; } // Set the image size. We will figure out the channels later from the elements, use rgb here: set_info(width, height, 3, pixel_aspect); #define DUMP_HEADER 0 #if DUMP_HEADER printf("%s:", header.file.imageFileName); if (_flipEndian) printf(" flipEndian"); printf("\n"); // printf(" size=%dx%dx, ", header.image.pixelsPerLine, // header.image.linesPerImage); // printf(" orientation=%x\n", header.image.orientation); #endif int bitdepth = 0; for (int i = 0; i < header.image.numberElements; i++) { if (header.image.element[i].bits > bitdepth) bitdepth = header.image.element[i].bits; } ycbcr_hack = false; info_.channels(Mask_None); for (int i = 0; i < header.image.numberElements; i++) { #if DUMP_HEADER printf(" %d: ", i); // printf(" lowData=%d\n", header.image.element[i].lowData); // printf(" lowQuantity=%f\n", header.image.element[i].lowQuantity); // printf(" highData=%d\n", header.image.element[i].highData); // printf(" highQuantity=%f\n", header.image.element[i].highQuantity); printf("d %d, ", header.image.element[i].descriptor); printf("t %d, ", header.image.element[i].transfer); printf("c %d, ", header.image.element[i].colorimetric); printf("%d bits, ", header.image.element[i].bits); if (header.image.element[i].dataSign) printf("signed, "); if (header.image.element[i].packing) printf("filled=%d, ", header.image.element[i].packing); if (header.image.element[i].encoding) printf("rle=%d, ", header.image.element[i].encoding); // printf(" dataOffset=%d\n", header.image.element[i].dataOffset); if (header.image.element[i].eolPadding) printf("eolPadding=%d, ", header.image.element[i].eolPadding); // printf(" eoImagePadding=%d\n", header.image.element[i].eoImagePadding); printf("\"%s\"\n", header.image.element[i].description); #endif element[i].descriptor = header.image.element[i].descriptor; switch (element[i].descriptor) { case DESCRIPTOR_R: element[i].channels = Mask_Red; element[i].components = 1; break; case DESCRIPTOR_G: element[i].channels = Mask_Green; element[i].components = 1; break; case DESCRIPTOR_B: element[i].channels = Mask_Blue; element[i].components = 1; break; case DESCRIPTOR_A: element[i].channels = Mask_Alpha; element[i].components = 1; break; default: printf("Unknown DPX element descriptor %d\n", element[i].descriptor); case DESCRIPTOR_Y: element[i].channels = Mask_RGB; element[i].components = 1; break; case DESCRIPTOR_CbCr: element[i].channels = ChannelSetInit(6); // blue+green element[i].components = 1; if (i && element[0].descriptor == DESCRIPTOR_Y) { element[0].channels = Mask_Red; ycbcr_hack = true; } break; case DESCRIPTOR_Z: element[i].channels = Mask_Z; element[i].components = 1; break; case DESCRIPTOR_RGB: element[i].channels = Mask_RGB; element[i].components = 3; break; case DESCRIPTOR_RGBA: element[i].channels = Mask_RGBA; element[i].components = 4; break; case DESCRIPTOR_ABGR: element[i].channels = Mask_RGBA; element[i].components = 4; break; case DESCRIPTOR_CbYCrY: element[i].channels = Mask_RGB; element[i].components = 2; break; case DESCRIPTOR_CbYACrYA: element[i].channels = Mask_RGBA; element[i].components = 3; break; case DESCRIPTOR_CbYCr: element[i].channels = Mask_RGB; element[i].components = 3; break; case DESCRIPTOR_CbYCrA: element[i].channels = Mask_RGBA; element[i].components = 4; break; case DESCRIPTOR_USER_2: element[i].channels = ChannelSetInit(3); // red+green element[i].components = 2; break; case DESCRIPTOR_USER_3: element[i].channels = Mask_RGB; element[i].components = 3; break; case DESCRIPTOR_USER_4: element[i].channels = Mask_RGBA; element[i].components = 4; break; case DESCRIPTOR_USER_5: element[i].channels = Mask_RGBA; element[i].components = 5; break; case DESCRIPTOR_USER_6: element[i].channels = Mask_RGBA; element[i].components = 6; break; case DESCRIPTOR_USER_7: element[i].channels = Mask_RGBA; element[i].components = 7; break; case DESCRIPTOR_USER_8: element[i].channels = Mask_RGBA; element[i].components = 8; break; } element[i].bits = header.image.element[i].bits; element[i].packing = header.image.element[i].packing; element[i].is_nl_matte = false; //element[i].encoding = header.image.element[i].encoding; element[i].dataOffset = header.image.element[i].dataOffset; element[i].transfer = header.image.element[i].transfer; switch (element[i].bits) { case 1: // Northlight mattes use 8-bit words if(!strcmp(header.image.element[i].description, "NL CLEAN MATTE")) { element[i].bytes = (width * element[i].components + 7) / 8; element[i].is_nl_matte = true; } else element[i].bytes = (width * element[i].components + 31) / 32 * 4; break; case 8: element[i].bytes = (width * element[i].components + 3) & - 4; break; case 10: if (element[i].packing) { element[i].bytes = (width * element[i].components + 2) / 3 * 4; // detect stupid writers that did the math wrong struct stat s; fstat(fd, &s); if (element[i].dataOffset + element[i].bytes * height > size_t(s.st_size)) element[i].bytes = (width * element[i].components) / 3 * 4; } else element[i].bytes = (width * element[i].components * 10 + 31) / 32 * 4; break; case 12: if (element[i].packing) element[i].bytes = (width * element[i].components) * 2; else element[i].bytes = (width * element[i].components * 12 + 31) / 32 * 4; break; case 16: element[i].bytes = (width * element[i].components) * 2; break; case 32: // no sample files available for this case 64: // no sample files available for this default: printf("Unhandled DPX number of bits %d\n", element[i].bits); element[i].channels = Mask_None; break; } meta.setDataIfNotEmpty(MetaData::ELEMENT_DESCRIPTION[i], header.image.element[i].description); if (header.image.element[i].eolPadding != 0xffffffff) { // Add the end-of-line padding value to the number of bytes per line. // Note: Some exporters write garbage to this, so only add the padding if won't cause the image // data to overlap the next element or go past the end of the file if this is the last element. We // could still have a small garbage offset that isn't detected here but at least it won't cause // the reader to crash. size_t maxAllowedEndOfImage = ((i + 1) < header.image.numberElements) ? header.image.element[i + 1].dataOffset: static_cast(_fileSize); size_t imageSizeWithEolPadding = (element[i].bytes + header.image.element[i].eolPadding) * height; size_t endOfImageWithEolPadding = element[i].dataOffset + imageSizeWithEolPadding; if (endOfImageWithEolPadding <= maxAllowedEndOfImage) element[i].bytes += header.image.element[i].eolPadding; } info_.turn_on(element[i].channels); } std::string edgecode; DPXFilmHeader* s = &(header.film); if ( s->filmManufacturingIdCode[0] && s->filmType[0] && s->perfsOffset[0] && s->prefix[0] && s->count[0] ) { char buffer[22]; sprintf( buffer, "%c%c %c%c %c%c%c%c%c%c %c%c%c%c %c%c", s->filmManufacturingIdCode[0], s->filmManufacturingIdCode[1], s->filmType[0], s->filmType[1], s->prefix[0], s->prefix[1], s->prefix[2], s->prefix[3], s->prefix[4], s->prefix[5], s->count[0], s->count[1], s->count[2], s->count[3], s->perfsOffset[0], s->perfsOffset[1] ); edgecode = buffer; sprintf( buffer, "%c%c %c%c %c%c %c%c%c%c %c%c%c%c %c%c", s->filmManufacturingIdCode[0], s->filmManufacturingIdCode[1], s->filmType[0], s->filmType[1], s->prefix[0], s->prefix[1], s->prefix[2], s->prefix[3], s->prefix[4], s->prefix[5], s->count[0], s->count[1], s->count[2], s->count[3], s->perfsOffset[0], s->perfsOffset[1] ); meta.setData( MetaData::EDGECODE, SanitizeString(buffer) ); } orientation = header.image.orientation; info_.ydirection((orientation & 2) ? 1 : -1); switch (header.image.element[0].transfer) { case TRANSFER_USER: // seems to be used by some log files case TRANSFER_DENSITY: case TRANSFER_LOG: lut_ = LUT::GetLut(LUT::LOG, this); meta.setData(MetaData::DPX::TRANSFER, "log"); break; case TRANSFER_CCIR_709_1: lut_ = LUT::Builtin("rec709", this); meta.setData(MetaData::DPX::TRANSFER, "rec709"); break; case TRANSFER_LINEAR: // unfortunatly too much software writes this for sRGB... default: lut_ = LUT::GetLut(header.image.element[0].bits <= 8 ? LUT::INT8 : LUT::INT16, this); meta.setData(MetaData::DPX::TRANSFER, "sRGB"); break; } // XXXX meta.setDataIfNotEmpty(MetaData::COPYRIGHT, header.file.copyright); meta.setDataIfNotEmpty(MetaData::DPX::FILE_NAME, header.orientation.fileName); meta.setDataIfNotEmpty(MetaData::DPX::CREATION_TIME, header.orientation.creationTime); meta.setDataIfNotEmpty(MetaData::DPX::INPUT_DEVICE, header.orientation.inputName); meta.setDataIfNotEmpty(MetaData::DPX::INPUT_SN, header.orientation.inputSN); const R32 filmFrameRate = header.film.frameRate; const U32 timecode = header.video.timeCode; if(DPXValid(timecode)) { // The timecode is encoded as 0xHHMMSSFF where the drop frame flag is also encoded into the FF part as 0x40 if enabled. char buffer[12]; U32 frames = timecode & 0xFF; U32 flags = 0x0; // We only support reading drop frames for 29.97fps footage. const bool supportsDropFrame = DPXImage::isDropFrameSupported(filmFrameRate); // Note on 59.94fps drop frame footage: Because of the way in which the drop frame flag is encoded into the 0x40 bit of the // frame number, this creates a problem as the frame number itself also requires the 0x40 bit for frames above 40.... // i.e. the dropframe timecode for frame 11 and the non-dropframe timecode for frame 51 are both 0x51. // For this reason we can't tell if a 59.94fps dpx is drop frame or not just from the header timecode. // Only 29.97 if (supportsDropFrame) { frames = timecode & 0x3F; flags = timecode & 0xC0; } const bool isDropFrame = 0 != (flags & DPXImage::kDropFrameFlag); // Use ';' for dropframe, else ':' const char* timecodeFormat = isDropFrame ? "%02x;%02x;%02x;%02x" : "%02x:%02x:%02x:%02x"; sprintf(buffer, timecodeFormat, timecode >> 24, (timecode >> 16) & 255, (timecode >> 8) & 255, frames); const std::string timecodeStr = buffer; meta.setData(MetaData::TIMECODE, timecodeStr); // Television if (timecode != 0) { meta.setData(MetaData::DPX::TIME_CODE, timecode); } } if (pixel_aspect != 0) { meta.setData(MetaData::PIXEL_ASPECT, pixel_aspect); } meta.setData(MetaData::DEPTH, MetaData::DEPTH_FIXED(bitdepth)); if ((header.video.userBits != 0) && (isfinite(header.video.userBits))) { meta.setData(MetaData::DPX::USER_BITS, header.video.userBits); } if ((header.video.interlace != 0) && (isfinite(header.video.interlace))) { meta.setData(MetaData::DPX::INTERLACE, header.video.interlace); } if ((header.video.fieldNumber != 0) && (isfinite(header.video.fieldNumber))) { meta.setData(MetaData::DPX::FIELD_NUMBER, header.video.fieldNumber); } if ((header.video.videoSignal != 0) && (isfinite(header.video.videoSignal))) { meta.setData(MetaData::DPX::VIDEO_SIGNAL, header.video.videoSignal); } if ((header.video.horzSampleRate != 0) && (isfinite(header.video.horzSampleRate))) { meta.setData(MetaData::DPX::HORIZ_SAMPLE, header.video.horzSampleRate); } if ((header.video.vertSampleRate != 0) && (isfinite(header.video.vertSampleRate))) { meta.setData(MetaData::DPX::VERT_SAMPLE, header.video.vertSampleRate); } if ((filmFrameRate > 0.f) && (isfinite(filmFrameRate))) { meta.setData(MetaData::FRAME_RATE, filmFrameRate); } if ((header.video.frameRate > 0.f) && (isfinite(header.video.frameRate))) { meta.setData(MetaData::DPX::FRAME_RATE, header.video.frameRate); } if ((header.video.timeOffset != 0) && (isfinite(header.video.timeOffset))) { meta.setData(MetaData::DPX::TIME_OFFSET, header.video.timeOffset); } if ((header.video.gamma != 0) && (isfinite(header.video.gamma))) { meta.setData(MetaData::DPX::GAMMA, header.video.gamma); } if ((header.video.blackLevel != 0) && (isfinite(header.video.blackLevel))) { meta.setData(MetaData::DPX::BLACK_LEVEL, header.video.blackLevel); } if ((header.video.blackGain != 0) && (isfinite(header.video.blackGain))) { meta.setData(MetaData::DPX::BLACK_GAIN, header.video.blackGain); } if ((header.video.breakpoint != 0) && (isfinite(header.video.breakpoint))) { meta.setData(MetaData::DPX::BREAK_POINT, header.video.breakpoint); } if ((header.video.whiteLevel != 0) && (isfinite(header.video.whiteLevel))) { meta.setData(MetaData::DPX::WHITE_LEVEL, header.video.whiteLevel); } if ((header.video.integrationTimes != 0) && (isfinite(header.video.integrationTimes))) { meta.setData(MetaData::DPX::INTEGRATION_TIMES, header.video.integrationTimes); } // if (header.film.framePosition != UNDEF_R32) { meta.setData(MetaData::DPX::FRAMEPOS, header.film.framePosition); // } if (header.film.sequenceLen != UNDEF_U32) { meta.setData(MetaData::DPX::SEQUENCE_LENGTH, header.film.sequenceLen); } if (header.film.heldCount != UNDEF_U32) { meta.setData(MetaData::DPX::HELD_COUNT, header.film.heldCount); } if (header.film.shutterAngle != UNDEF_R32) { meta.setData(MetaData::SHUTTER_ANGLE, header.film.shutterAngle); } meta.setDataIfNotEmpty(MetaData::DPX::FRAME_ID, header.film.frameId); meta.setDataIfNotEmpty(MetaData::SLATE_INFO, header.film.slateInfo); if (edgecode.length() && edgecode != "00 00 000000 0000 00") { meta.setData(MetaData::EDGECODE, edgecode); } meta.setTimeStamp(MetaData::FILE_CREATION_TIME, header.file.creationTime); meta.setDataIfNotEmpty(MetaData::CREATOR, header.file.creator); meta.setDataIfNotEmpty(MetaData::PROJECT, header.file.project); meta.setDataIfNotEmpty(MetaData::COPYRIGHT, header.file.copyright); meta.setDataIfNotEmpty(MetaData::FILENAME, header.file.imageFileName); } ~dpxReader() { delete _requestedLinesPreloadBuffer; } void CConvert(float* dest, const uchar* src, int x, int r, int delta) { const float m = 1.0f / 255; const float off = .5f - 0x80 * m; for (; x < r; x++) dest[x] = src[x * delta] * m + off; } void CConvert(float* dest, const U16* src, int x, int r, int delta, int bits) { const float m = 1.0f / ((1 << bits) - 1); const float off = .5f - (1 << (bits - 1)) * m; for (; x < r; x++) dest[x] = src[x * delta] * m + off; } void CbConvert(float* dest, const uchar* src, int x, int r, int delta) { const float m = 1.0f / 255; const float m2 = m / 2; const float off = .5f - 0x80 * m; if (!(r & 1) && r >= int(width)) { dest[r - 1] = src[(r - 2) * delta] * m + off; r--; } for (; x < r; x++) { if (x & 1) dest[x] = (src[(x - 1) * delta] + src[(x + 1) * delta]) * m2 + off; else dest[x] = src[x * delta] * m + off; } } void CbConvert(float* dest, const U16* src, int x, int r, int delta, int bits) { const float m = 1.0f / ((1 << bits) - 1); const float m2 = m / 2; const float off = .5f - (1 << (bits - 1)) * m; if (!(r & 1) && r >= int(width)) { dest[r - 1] = src[(r - 2) * delta] * m + off; r--; } for (; x < r; x++) { if (x & 1) dest[x] = (src[(x - 1) * delta] + src[(x + 1) * delta]) * m2 + off; else dest[x] = src[x * delta] * m + off; } } void CrConvert(float* dest, const uchar* src, int x, int r, int delta) { const float m = 1.0f / 255; const float m2 = m / 2; const float off = .5f - 0x80 * m; if (!x) { dest[x] = src[(x + 1) * delta] * m + off; x++; } for (; x < r; x++) { if (x & 1) dest[x] = src[x * delta] * m + off; else dest[x] = (src[(x - 1) * delta] + src[(x + 1) * delta]) * m2 + off; } } void CrConvert(float* dest, const U16* src, int x, int r, int delta, int bits) { const float m = 1.0f / ((1 << bits) - 1); const float m2 = m / 2; const float off = .5f - (1 << (bits - 1)) * m; if (!x) { dest[x] = src[(x + 1) * delta] * m + off; x++; } for (; x < r; x++) { if (x & 1) dest[x] = src[x * delta] * m + off; else dest[x] = (src[(x - 1) * delta] + src[(x + 1) * delta]) * m2 + off; } } void YConvert(float* dest, const uchar* src, int x, int r, int delta) { Linear::from_byte(dest + x, src + x * delta, r - x, delta); } void YConvert(float* dest, const U16* src, int x, int r, int delta, int bits) { Linear::from_short(dest + x, src + x * delta, r - x, bits, delta); } void AConvert(float* dest, const uchar* src, int x, int r, int delta) { Linear::from_byte(dest + x, src + x * delta, r - x, delta); } void AConvert(float* dest, const U16* src, int x, int r, int delta, int bits) { Linear::from_short(dest + x, src + x * delta, r - x, bits, delta); } void fixYCbCr(int x, int r, bool alpha, Row& row) { if (iop->raw()) return; float* R = row.writable(Chan_Red); float* G = row.writable(Chan_Green); float* B = row.writable(Chan_Blue); for (int X = x; X < r; X++) { float y = (R[X] - float(16 / 255.0f)) * float(255.0f / 219); float u = (G[X] - .5f) * float(255.0f / 224); float v = (B[X] - .5f) * float(255.0f / 224); R[X] = v * (2 - 2 * Kr) + y; G[X] = y - v * ((2 - 2 * Kr) * Kr / (1 - Kr - Kb)) - u * ((2 - 2 * Kb) * Kb / (1 - Kr - Kb)); B[X] = u * (2 - 2 * Kb) + y; } from_float(Chan_Red, R + x, R + x, alpha ? row[Chan_Alpha] + x : 0, r - x); from_float(Chan_Green, G + x, G + x, alpha ? row[Chan_Alpha] + x : 0, r - x); from_float(Chan_Blue, B + x, B + x, alpha ? row[Chan_Alpha] + x : 0, r - x); } // Read a line from the file, or from our internal buffer for the file, if the latter exists // and is unlocked. void get_line_from_file(void* destination, unsigned int elementIndex, unsigned int dataOffsetInFile, unsigned int lineSize, int y, size_t bytesToRead) { // If we have an internal file buffer, try to read the line from it. if (testFileBuffer()) { // Make sure the internal file buffer won't be freed while we are using it. FileBufferGuard guard(_requestedLinesPreloadBuffer); // Check again that nothing is trying to free memory. if (!_requestedLinesPreloadBuffer->locked()) { mFnAssertMsg(y >= _requestedLinesPreloadBuffer->getYMin() && y <= _requestedLinesPreloadBuffer->getYMax(), "dpxReader: out-of-bounds access to internal file buffer."); // Read the line from our internal frame buffer. const int lineOffsetInBuffer= y - _requestedLinesPreloadBuffer->getYMin(); memcpy(destination, _requestedLinesPreloadBuffer->getBuffer(elementIndex) + lineOffsetInBuffer * lineSize, bytesToRead); return; } } // Read the line from the file. read(destination, dataOffsetInFile + y * lineSize, static_cast(bytesToRead)); } void read_element8(const Element& e, unsigned int index, int y, int x, int r, Row& row) { // uncompress into an array of bytes: ARRAY(uchar, buf, width * e.components); const int yMin = info_.y(); if (e.bits == 1) { if (e.is_nl_matte) { ARRAY(U8, src, e.bytes); get_line_from_file(src, index, e.dataOffset, e.bytes, y, e.bytes); for (unsigned x = 0; x < width * e.components; x++) buf[x] = (src[x / 8] & (1 << (x & 7))) ? 255 : 0; } else { unsigned n = (e.bytes + 3) / 4; ARRAY(U32, src, n); get_line_from_file(src, index, e.dataOffset, e.bytes, y, e.bytes); if (_flipEndian) flip(src, n); for (unsigned x = 0; x < width * e.components; x++) buf[x] = (src[x / 32] & (1 << (x & 31))) ? 255 : 0; } } else { get_line_from_file(buf, index, e.dataOffset, e.bytes, y, width * e.components); } // now convert to rgb switch (e.descriptor) { case DESCRIPTOR_CbCr: // to actually get rgb we need the Y from the other element. NYI CbConvert(row.writable(Chan_Green), buf, x, r, 1); CrConvert(row.writable(Chan_Blue), buf, x, r, 1); if (ycbcr_hack) fixYCbCr(x, r, false, row); break; case DESCRIPTOR_RGBA: { const uchar* alpha = buf + x * 4 + 3; foreach(z, e.channels) from_byte(z, row.writable(z) + x, buf + x * 4 + (z - 1), alpha, r - x, 4); break; } case DESCRIPTOR_ABGR: { const uchar* alpha = buf + x * 4; foreach(z, e.channels) from_byte(z, row.writable(z) + x, buf + x * 4 + (4 - z), alpha, r - x, 4); break; } case DESCRIPTOR_CbYCrY: YConvert(row.writable(Chan_Red), buf + 1, x, r, 2); CbConvert(row.writable(Chan_Green), buf, x, r, 2); CrConvert(row.writable(Chan_Blue), buf, x, r, 2); fixYCbCr(x, r, false, row); break; case DESCRIPTOR_CbYACrYA: CbConvert(row.writable(Chan_Green), buf, x, r, 3); CrConvert(row.writable(Chan_Blue), buf, x, r, 3); YConvert(row.writable(Chan_Red), buf + 1, x, r, 3); AConvert(row.writable(Chan_Alpha), buf + 2, x, r, 3); fixYCbCr(x, r, true, row); break; case DESCRIPTOR_CbYCr: CConvert(row.writable(Chan_Green), buf, x, r, 3); YConvert(row.writable(Chan_Red), buf + 1, x, r, 3); CConvert(row.writable(Chan_Blue), buf + 2, x, r, 3); fixYCbCr(x, r, false, row); break; case DESCRIPTOR_CbYCrA: CConvert(row.writable(Chan_Green), buf, x, r, 4); YConvert(row.writable(Chan_Red), buf + 1, x, r, 4); CConvert(row.writable(Chan_Blue), buf + 2, x, r, 4); AConvert(row.writable(Chan_Alpha), buf + 3, x, r, 4); fixYCbCr(x, r, true, row); break; case DESCRIPTOR_Y: if (ycbcr_hack) { YConvert(row.writable(Chan_Red), buf, x, r, 1); break; } // else fall through: default: { int Z = 0; foreach(z, e.channels) { from_byte(z, row.writable(z) + x, buf + Z + x * e.components, 0 /*alpha*/, r - x, e.components); if (Z + 1 < e.components) Z++; } break; } } } void read_element16(const Element& e, unsigned int index, int y, int x, int r, Row& row) { ARRAY(U16, buf, width * e.components + 2); const int yMin = info_.y(); switch (e.bits) { case 10: { unsigned n = (e.bytes + 3) / 4; unsigned x; ARRAY(U32, src, n); get_line_from_file(src, index, e.dataOffset, e.bytes, y, e.bytes); if (_flipEndian) flip(src, n); switch (e.packing) { case 0: for (x = 0; x < width * e.components; x++) { unsigned a = (x * 10) / 32; unsigned b = (x * 10) % 32; if (b > 22) buf[x] = ((src[a + 1] << (32 - b)) + (src[a] >> b)) & 0x3ff; else buf[x] = (src[a] >> b) & 0x3ff; } break; case 1: for (x = 0; x < n; x++) { buf[3 * x + 0] = (src[x] >> 22) & 0x3ff; buf[3 * x + 1] = (src[x] >> 12) & 0x3ff; buf[3 * x + 2] = (src[x] >> 02) & 0x3ff; } break; case 2: for (x = 0; x < n; x++) { buf[3 * x + 0] = (src[x] >> 20) & 0x3ff; buf[3 * x + 1] = (src[x] >> 10) & 0x3ff; buf[3 * x + 2] = (src[x] >> 00) & 0x3ff; } break; } break; } case 12: switch (e.packing) { case 0: { unsigned n = (e.bytes + 3) / 4; ARRAY(U32, src, n); get_line_from_file(src, index, e.dataOffset, e.bytes, y, e.bytes); if (_flipEndian) flip(src, n); for (unsigned x = 0; x < width * e.components; x++) { unsigned a = (x * 12) / 32; unsigned b = (x * 12) % 32; if (b > 20) buf[x] = ((src[a + 1] << (32 - b)) + (src[a] >> b)) & 0xfff; else buf[x] = (src[a] >> b) & 0xfff; } break; } case 1: { unsigned n = width * e.components; get_line_from_file(buf, index, e.dataOffset, e.bytes, y, n * 2); if (_flipEndian) flip(buf, n); for (unsigned x = 0; x < n; x++) buf[x] >>= 4; break; } case 2: { unsigned n = width * e.components; get_line_from_file(buf, index, e.dataOffset, e.bytes, y, n * 2); if (_flipEndian) flip(buf, n); for (unsigned x = 0; x < n; x++) buf[x] &= 0xfff; break; } } break; case 16: get_line_from_file(buf, index, e.dataOffset, e.bytes, y, width * e.components * 2); if (_flipEndian) flip(buf, width * e.components); break; } // now convert to rgb switch (e.descriptor) { case DESCRIPTOR_CbCr: // to actually get rgb we need the Y from the other element. NYI CbConvert(row.writable(Chan_Green), buf, x, r, 1, e.bits); CrConvert(row.writable(Chan_Blue), buf, x, r, 1, e.bits); if (ycbcr_hack) fixYCbCr(x, r, false, row); break; case DESCRIPTOR_RGBA: { const U16* alpha = buf + x * 4 + 3; foreach(z, e.channels) from_short(z, row.writable(z) + x, buf + x * 4 + (z - 1), alpha, r - x, e.bits, 4); break; } case DESCRIPTOR_ABGR: { const U16* alpha = buf + x * 4; foreach(z, e.channels) from_short(z, row.writable(z) + x, buf + x * 4 + (4 - z), alpha, r - x, e.bits, 4); break; } case DESCRIPTOR_CbYCrY: YConvert(row.writable(Chan_Red), buf + 1, x, r, 2, e.bits); CbConvert(row.writable(Chan_Green), buf, x, r, 2, e.bits); CrConvert(row.writable(Chan_Blue), buf, x, r, 2, e.bits); fixYCbCr(x, r, false, row); break; case DESCRIPTOR_CbYACrYA: CbConvert(row.writable(Chan_Green), buf, x, r, 3, e.bits); CrConvert(row.writable(Chan_Blue), buf, x, r, 3, e.bits); YConvert(row.writable(Chan_Red), buf + 1, x, r, 3, e.bits); AConvert(row.writable(Chan_Alpha), buf + 2, x, r, 3, e.bits); fixYCbCr(x, r, true, row); break; case DESCRIPTOR_CbYCr: YConvert(row.writable(Chan_Red), buf + 1, x, r, 3, e.bits); if (e.bits == 10) { // The "flowers" 10-bit sample image has the Cr/Cb reversed. This may be // a mistake, and other small test images have them the other way. // However I am leaving this reversed as that is how previous versions // of Nuke read this file. CConvert(row.writable(Chan_Blue), buf, x, r, 3, e.bits); CConvert(row.writable(Chan_Green), buf + 2, x, r, 3, e.bits); } else { CConvert(row.writable(Chan_Green), buf, x, r, 3, e.bits); CConvert(row.writable(Chan_Blue), buf + 2, x, r, 3, e.bits); } fixYCbCr(x, r, false, row); break; case DESCRIPTOR_CbYCrA: CConvert(row.writable(Chan_Green), buf, x, r, 4, e.bits); YConvert(row.writable(Chan_Red), buf + 1, x, r, 4, e.bits); CConvert(row.writable(Chan_Blue), buf + 2, x, r, 4, e.bits); AConvert(row.writable(Chan_Alpha), buf + 3, x, r, 4, e.bits); fixYCbCr(x, r, true, row); break; case DESCRIPTOR_Y: if (ycbcr_hack) { YConvert(row.writable(Chan_Red), buf, x, r, 1, e.bits); break; } // else fall through: default: { int Z = 0; foreach(z, e.channels) { from_short(z, row.writable(z) + x, buf + Z + x * e.components, 0 /*alpha*/, r - x, e.bits, e.components); if (Z + 1 < e.components) Z++; } break; } } } void read_element(const Element& e, unsigned int index, int y, int x, int r, Row& row) { if (e.bits <= 8) read_element8(e, index, y, x, r, row); else read_element16(e, index, y, x, r, row); } void open() { FileReader::open(); if (_readAllLines) { mFnAssert(_requestedLinesPreloadBuffer); readAllLines(); } } void engine(int y, int x, int r, ChannelMask channels, Row& row) { if (invertY()) { y = height - y - 1; } ChannelSet remaining(channels); if (ycbcr_hack && (channels & Mask_RGB)) remaining += Mask_RGB; for (unsigned i = 0; i < kMaxDPXElements; i++) { if (element[i].channels & remaining) { read_element(element[i], i, y, x, r, row); remaining -= element[i].channels; if (!remaining) break; } } } //! Checks that the file size is large enough to contain the data the header claims is there. //! The file could still be corrupted in some way but this function can at least be used for some //! very basic error checking. bool checkFileSizeConsistency() { // It doens't make much sense to have no elements but the file would at least be big enough to hold them! if (_numElements == 0) return true; const Element& lastElement = element[_numElements - 1]; int expectedFileSize = lastElement.dataOffset + lastElement.bytes * h(); return _fileSize >= expectedFileSize; } #if FN_BUILD_WITH_READER_EXTENSIONS #include "ReaderExtensions/dpxReaderExtensions.cpp" #endif }; static bool test(int fd, const unsigned char* block, int length) { DPXHeader* header = (DPXHeader*)block; U32 m = header->file.magicNumber; if (m == DPX_MAGIC || m == DPX_MAGIC_FLIPPED) return true; return false; } static Reader* build(Read* iop, int fd, const unsigned char* b, int n) { return new dpxReader(iop, fd, b, n); } const Reader::Description dpxReader::description("dpx\0", build, test); // end dpxReader.C