// jpegReader.C // Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved. /* Reads Jpeg files (actually JFIF files) using the public domain libjpeg. This is an example of a file reader that is not a subclass of FileReader. Instead this uses the library's reader functions and a single lock so that multiple threads do not crash the library. */ #include "DDImage/Reader.h" #include "DDImage/Row.h" #include "DDImage/ARRAY.h" #include #include #include "jpeg.h" #include "DDImage/Thread.h" #include "DDImage/MetaData.h" #include "exifReader.h" #include "boost/foreach.hpp" using namespace DD::Image; namespace { struct MetaWrapper { void * rawData; int size; MetaWrapper( void * data, int dataSize ) : rawData( data ), size( dataSize ) { } }; } class JpegReader : public ExifReader { FILE* file; struct jpeg_decompress_struct cinfo; int depth; unsigned char** lines; int nextline; Lock lock; public: JpegReader(Read*, int fd); ~JpegReader() override; void engine(int y, int x, int r, ChannelMask, Row &) override; void open() override; static const Description d; const MetaData::Bundle& fetchMetaData(const char* key) override; MetaData::Bundle _meta; //! Gets information about the ideal planar image (bounding box, data type, actual channels, etc) for reading/decoding the //! specified channels, overriding the default implementation in Reader. The information in the returned PlanarReadInfo //! is guidance for the user code, which may legitimately decide to use different settings when actually reading/decoding, //! though any deviation from the returned settings must be done with caution - see the documentation of planarReadAndDecode() //! and planarDecodePass() for more details. PlanarReadInfo planarReadInfo(const ChannelSet& channels) override { bool isValid = true; // Specify the bounds rather than format, i.e., the data window rather than display window. // It's up to the caller to decide whether that's appropriate, it may want to set the bounds in // the passed GenericImagePlane to match the format (or some other range if it really wants). // NOTE: If the data and display windows don't match then the constructor will have set the // (bbox) bounds to be the data window plus a single pixel border. DD::Image::Box bounds(x(), y(), r(), t()); // This reader doesn't really care whether the destination image is packed or not, though there may be memory // access performance implications. Since Hiero is (currently) only interested in packed we'll just specify that. bool packed = true; DataTypeEnum dataType = eDataTypeUInt8; bool useClamps = true; int minValue = 0; int maxValue = 0xff; int whitePoint = 1; // Only report the channels that are common to both the requested list and those present in the file. ChannelSet commonChannels = channels; commonChannels &= this->channels(); int nComponents = commonChannels.size(); ImagePlaneDescriptor baseDesc(bounds, packed, commonChannels, nComponents); DataInfo dataInfo(dataType, useClamps, minValue, maxValue, whitePoint); // Assume linear, I'm not sure we can do anything else. ColorCurveEnum colorCurve = eColorCurveLinear; GenericImagePlaneDescriptor desc(baseDesc, dataInfo, colorCurve); // #rick: // The jpeg lib we're using doesn't seem to have an API for decoding from a memory buffer, // just a file. I'm not sure about this, and it may also be possible to use memory mapping, but // for now we won't use a separate read pass. Besides, the OS should cache the file in memory // once we access it anyway. size_t readPassBufferSize = 0; // #rick: // Not sure about the best way to implement multithreading support, or even whether it's worth it (might // even slow things down due to memory access). // See the assertions in planarDecodePass(). bool isDecodeThreadable = false; PlanarReadInfo planarInfo(desc, readPassBufferSize, isDecodeThreadable, isValid); return planarInfo; } //! Do a combined, sequential read and decode, i.e., providing no opportunity for the calling application to multithread //! this in any way. See planarDecodePass() for more details of the decode. void planarReadAndDecode(GenericImagePlane& image, const ChannelSet& channels) override { planarDecodePass(nullptr, image, channels, 0, 1); } //! The read pass doesn't do anything as the jpeg lib we're using works with a file rather than buffer. int planarReadPass(void* buffer, const ChannelSet& channels) override { return 0; } //! Do a planar decode of the image data into the GenericImagePlane, for the specified channels. // //! NOTE: In the current implementation srcBuffer is ignored as the data is decoded directly from the file. //! Therefore there's no need to call planarReadPass(). //! //! NOTE: There's currently no support for anything otehr than a single decode pass - the function asserts //! if threadIndex and nDecodeThreads are not passed as 0 and 1, respectively. //! See the setting of the multithreadable flag in planarReadInfo(const ChannelSet& channels). //! //! NOTE: There's a current restriction that the requested channels exactly match those in the source file, //! or are RGBA when the source file contains RGB. void planarDecodePass(void* srcBuffer, GenericImagePlane& image, const ChannelSet& channels, int threadIndex, int nDecodeThreads) override { mFnAssert(threadIndex >= 0); mFnAssert(threadIndex < nDecodeThreads); mFnAssert(nDecodeThreads > 0); // #rick: // For now, to keep this code simpler, don't support splitting the 'decode' into multiple passes. // A parallel version won't necessarily be quicker as very little processing is going on, it's mostly // about memory access. In fact, a parallel version might even be slower. Needs testing... // #mat: Indeed. We now decode multiple jpeg files on separate decode threads in Hiero and hence single file threaded decodes is useless to us. Honestly // that's a bad approach anyway as you don't want separate threads reading scan lines at random - single threaded sequential disk I/O is always faster than // random access I/O mFnAssert(threadIndex == 0); mFnAssert(nDecodeThreads == 1); // Assert that the image plane we're filling in is configured to hold the whole image, not a sub area of it, // as that's all we support at the moment and the SGI format doesn't provide anything complicated like itself. mFnAssert(image.desc().bounds().x() == 0); mFnAssert(image.desc().bounds().y() == 0); mFnAssert(image.desc().bounds().w() == w()); mFnAssert(image.desc().bounds().h() == h()); mFnAssert(image.desc().dataInfo().dataType() == eDataTypeUInt8); // #mat: Hiero should never hit this scenario if (nDecodeThreads != 1 || threadIndex != 0 || image.desc().dataInfo().dataType() != eDataTypeUInt8) { mFnAssert(false); return; } // #mat: Assuming single decode thread and that we're decoding the entire image, which is true for Hiero but not for Nuke as I understand it int yStart = 0; int yEnd = h() - 1; // See whether we're decoding from RGB source into an RGBA destination. bool rgbToRgba = ((channels == Mask_RGBA) && (this->channels() == Mask_RGB)); // Unless we're doing the RGB-to-RGBA decode, require the number of requested channels to match // the number we've got. if (!rgbToRgba && (channels.size() != this->channels().size())) { mFnAssert(false); // #rick: What should we really do? Throw? return; } const size_t bytesPerRow = depth * width(); const int kColumnStride = image.colStrideBytes(); // #mat: Optimisation - If we don't need 3 to 4 expansion and if the dest pixels are packed RGB then just decode directly into the destination buffer. // No temporary buffer allocation required and eliminates unnecessary copy if (!rgbToRgba && kColumnStride == 3) { for (int y = yStart; y <= yEnd; y++) { // #mat: Seems that Hiero needs the destination row to be inverted const int kDestY = h() - 1 - y; unsigned char* buffer = &image.writableAt(0, kDestY, 0); if (buffer != nullptr) { jpeg_read_scanlines(&cinfo, &buffer, 1); } } } else { // #mat: Copied from dpxReaderExtensions.cpp struct AutoBuffer { AutoBuffer(int bufferSize) { _buffer = new unsigned char[bufferSize]; } ~AutoBuffer() { delete [] _buffer; } unsigned char* _buffer; }; // #mat: Optimisation - only single buffer for the row. Previously we were allocating a separate buffer for each row which resulted in thousands of heap // allocations for each file and was murdering OpenGL performance due to heap contention AutoBuffer autoBuffer (bytesPerRow); unsigned char* buffer = autoBuffer._buffer; if (buffer == nullptr) { mFnAssert(false); return; } for (int y = yStart; y <= yEnd; y++) { jpeg_read_scanlines(&cinfo, &buffer, 1); // Copy the decoded line from the internal buffer to the destination image. // #mat: Seems that Hiero needs the destination row to be inverted const int kDestY = h() - 1 - y; const int kColumnStride = image.colStrideBytes(); if (rgbToRgba) { // We're converting an RGB source to RGBA output, filling in the alpha channel // according to the current autoAlpha setting on the Read operator invoking this Reader. mFnAssert(this->channels().size() == 3); mFnAssert(channels.size() == 4); unsigned char* srcAddr = buffer; unsigned char alphaValue = iop->autoAlpha() ? 0xff : 0; // We can't be sure the pixels are packed as RGBARGBA... in the destination buffer // so get the start address of each of channel then increment each by the column stride. unsigned char* destR = &image.writableAt(0, kDestY, 0); unsigned char* destG = &image.writableAt(0, kDestY, 1); unsigned char* destB = &image.writableAt(0, kDestY, 2); unsigned char* destA = &image.writableAt(0, kDestY, 3); // #rick: // It might be worth writing special case code for when the destination pixels are packed, // in which case we can use a single dest pointer, which might be a bit quicker. Maybe. for (int columnCount = 0; columnCount < w(); columnCount++) { *destR = *srcAddr++; destR += kColumnStride; *destG = *srcAddr++; destG += kColumnStride; *destB = *srcAddr++; destB += kColumnStride; *destA = alphaValue; destA += kColumnStride; } } // #mat: Old path for writing to RGB. Not sure when we'd want to come here and not write directly into the destination buffer - when the destination // pixels aren't packed RGB? else { // #rick: // This could probably be optimised for memory access. // We're not converting from RGB to RGBA so just decode the channels that are present. int numChannels = this->channels().size(); for (int channelCount = 0; channelCount < numChannels; channelCount++) { unsigned char* srcAddr = buffer + channelCount; unsigned char* destBuffer = &image.writableAt(0, kDestY, channelCount); for (int columnCount = 0; columnCount < w(); columnCount++) { *destBuffer = *srcAddr; srcAddr += numChannels; destBuffer += kColumnStride; } } } } } jpeg_destroy_decompress(&cinfo); fclose(file); file = nullptr; // #mat: I delete this here rather than the destructor as otherwise we'd need to set all entries to NULL to ensure that the destructor doesn't free // random memory when it loops over the array. Better to clean it up here as we're done reading anyway delete [] lines; lines = nullptr; } private: int getline(int y); }; const MetaData::Bundle& JpegReader::fetchMetaData(const char* key) { return _meta; } // I don't know what the official test for jpeg is. There appears to // be some complexity at the start, probably they assume you will use // a jpeg_decompress object. These 3 bytes are the only constant between // all the example files I have seen: static bool test(int fd, const unsigned char* block, int length) { return block[0] == 0xff && block[1] == 0xd8 && block[2] == 0xff; // return (!strcmp((char*)block+6, "JFIF") || // !strcmp((char*)block+6, "Exif")); } static Reader* build(Read* iop, int fd, const unsigned char* b, int n) { return new JpegReader(iop, fd); } const Reader::Description JpegReader::d("jpeg\0jpg\0", build, test); static struct jpeg_error_mgr jerr; JpegReader::JpegReader(Read* r, int fd) : ExifReader(r) , file(nullptr) , depth(-1) , lines(nullptr) , nextline(0) { file = fdopen(fd, "rb"); if (!file) { iop->internalError("Jpeg read error: %s", strerror(errno)); return; } fseek(file, 0L, SEEK_SET); cinfo.err = jpeg_std_error(&jerr); jpeg_create_decompress(&cinfo); jpeg_stdio_src(&cinfo, file); jpeg_save_markers(&cinfo, JPEG_APP0 + 1, 0xffff); jpeg_read_header(&cinfo, TRUE); jpeg_start_decompress(&cinfo); lines = new unsigned char*[cinfo.output_height]; double aspect = 0; if ( cinfo.X_density > 0 && cinfo.Y_density > 0 && cinfo.X_density != 0xffff && cinfo.Y_density != 0xffff && (cinfo.X_density != 1 || cinfo.Y_density != 1) ) aspect = (float)cinfo.X_density / (float)cinfo.Y_density; depth = cinfo.output_components; set_info(cinfo.output_width, cinfo.output_height, depth, aspect); info_.ydirection(-1); jpeg_saved_marker_ptr mptr = cinfo.marker_list; std::vector< MetaWrapper > metaList; while (mptr != nullptr) { if (mptr->marker == 0xe1) { MetaWrapper metaData( mptr->data, mptr->data_length ); metaList.push_back( metaData ); } mptr = mptr->next; } BOOST_FOREACH(MetaWrapper& mw, metaList) { fetchExifMetaData(_meta, mw.rawData, mw.size); } _meta.setData(MetaData::DEPTH, MetaData::DEPTH_FIXED(cinfo.data_precision)); _meta.setDataCopy(MetaData::FILE_CREATION_TIME, _meta.getData("exif/0/DateTime")); _meta.setDataCopy(MetaData::FOCAL_LENGTH, _meta.getData("exif/2/FocalLength")); _meta.setDataCopy(MetaData::FNUMBER, _meta.getData("exif/2/FNumber")); _meta.setDataCopy(MetaData::EXPOSURE, _meta.getData("exif/2/ExposureTime")); } // delay anything unneeded for info_ until this is called: void JpegReader::open() { Reader::open(); } JpegReader::~JpegReader() { if (file) { jpeg_destroy_decompress(&cinfo); fclose(file); } if (lines) { for (int n = 0; n < nextline; n++) { delete[] lines[n]; } delete[] lines; } } // The engine reads individual rows out of the input. void JpegReader::engine(int y, int x, int r, ChannelMask channels, Row& row) { unsigned char* buffer; int Y = height() - y - 1; lock.lock(); if (Y < nextline) { buffer = lines[Y]; } else { // Read forward until we get the line: for (; nextline <= Y; nextline++) { buffer = lines[nextline] = new unsigned char[depth * width()]; jpeg_read_scanlines(&cinfo, &buffer, 1); } // When we read the entire file we don't need the the cinfo anymore: if (nextline >= height()) { jpeg_destroy_decompress(&cinfo); fclose(file); file = nullptr; } } lock.unlock(); // Convert the necessary channels to floating point: foreach (z, channels) { from_byte(z, row.writable(z) + x, buffer + x * depth + z - 1, nullptr /*alpha*/, r - x, depth /*delta*/); } }