// -*- C++;indent-tabs-mode: t; tab-width: 4; c-basic-offset: 4; -*- /** * KImageIO Routines to read (and perhaps in the future, write) images * in the high dynamic range EXR format. * Copyright (c) 2003, Brad Hards * * This library is distributed under the conditions of the GNU LGPL. */ #include "exr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class K_IStream: public Imf::IStream { public: K_IStream( QIODevice *dev, const QByteArray& fileName ): IStream( fileName.data() ), m_dev ( dev ) {} virtual bool read( char c[], int n ); virtual Imf::Int64 tellg( ); virtual void seekg( Imf::Int64 pos ); virtual void clear( ); private: QIODevice *m_dev; }; bool K_IStream::read( char c[], int n ) { qint64 result = m_dev->read( c, n ); if ( result > 0 ) { return true; } else if ( result == 0 ) { throw Iex::InputExc( "Unexpected end of file" ); } else // negative value { Iex::throwErrnoExc( "Error in read", result ); return false; } Imf::Int64 K_IStream::tellg( ) { return m_dev->pos(); } void K_IStream::seekg( Imf::Int64 pos ) { m_dev->seek( pos ); } void K_IStream::clear( ) { // TODO } /* this does a conversion from the ILM Half (equal to Nvidia Half) * format into the normal 32 bit pixel format. Process is from the * ILM code. */ QRgb RgbaToQrgba(struct Imf::Rgba imagePixel) { float r,g,b,a; // 1) Compensate for fogging by subtracting defog // from the raw pixel values. // Response: We work with defog of 0.0, so this is a no-op // 2) Multiply the defogged pixel values by // 2^(exposure + 2.47393). // Response: We work with exposure of 0.0. // (2^2.47393) is 5.55555 r = imagePixel.r * 5.55555; g = imagePixel.g * 5.55555; b = imagePixel.b * 5.55555; a = imagePixel.a * 5.55555; // 3) Values, which are now 1.0, are called "middle gray". // If defog and exposure are both set to 0.0, then // middle gray corresponds to a raw pixel value of 0.18. // In step 6, middle gray values will be mapped to an // intensity 3.5 f-stops below the display's maximum // intensity. // Response: no apparent content. // 4) Apply a knee function. The knee function has two // parameters, kneeLow and kneeHigh. Pixel values // below 2^kneeLow are not changed by the knee // function. Pixel values above kneeLow are lowered // according to a logarithmic curve, such that the // value 2^kneeHigh is mapped to 2^3.5 (in step 6, // this value will be mapped to the display's // maximum intensity). // Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32) if (r > 1.0) r = 1.0 + Imath::Math::log ((r-1.0) * 0.184874 + 1) / 0.184874; if (g > 1.0) g = 1.0 + Imath::Math::log ((g-1.0) * 0.184874 + 1) / 0.184874; if (b > 1.0) b = 1.0 + Imath::Math::log ((b-1.0) * 0.184874 + 1) / 0.184874; if (a > 1.0) a = 1.0 + Imath::Math::log ((a-1.0) * 0.184874 + 1) / 0.184874; // // 5) Gamma-correct the pixel values, assuming that the // screen's gamma is 0.4545 (or 1/2.2). r = Imath::Math::pow (r, 0.4545); g = Imath::Math::pow (g, 0.4545); b = Imath::Math::pow (b, 0.4545); a = Imath::Math::pow (a, 0.4545); // 6) Scale the values such that pixels middle gray // pixels are mapped to 84.66 (or 3.5 f-stops below // the display's maximum intensity). // // 7) Clamp the values to [0, 255]. return qRgba( (unsigned char) (Imath::clamp ( r * 84.66f, 0.f, 255.f ) ), (unsigned char) (Imath::clamp ( g * 84.66f, 0.f, 255.f ) ), (unsigned char) (Imath::clamp ( b * 84.66f, 0.f, 255.f ) ), (unsigned char) (Imath::clamp ( a * 84.66f, 0.f, 255.f ) ) ); } EXRHandler::EXRHandler() { } bool EXRHandler::canRead() const { if (canRead(device())) { setFormat("exr"); return true; } return false; } QByteArray EXRHandler::name() const { // TODO return QByteArray("exr"); } bool EXRHandler::read( QImage *outImage ) { try { int width, height; K_IStream istr( device(), QByteArray() ); Imf::RgbaInputFile file( istr ); Imath::Box2i dw = file.dataWindow(); width = dw.max.x - dw.min.x + 1; height = dw.max.y - dw.min.y + 1; Imf::Array2D pixels; pixels.resizeErase (height, width); file.setFrameBuffer (&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width); file.readPixels (dw.min.y, dw.max.y); QImage image(width, height, QImage::Format_RGB32); if( image.isNull()) return false; // somehow copy pixels into image for ( int y=0; y < height; y++ ) { for ( int x=0; x < width; x++ ) { // copy pixels(x,y) into image(x,y) image.setPixel( x, y, RgbaToQrgba( pixels[y][x] ) ); } } *outImage = image; return true; } catch (const std::exception &exc) { kDebug() << exc.what(); return false; } } bool EXRHandler::write( const QImage &image ) { // TODO: stub Q_UNUSED( image ); return false; } bool EXRHandler::canRead(QIODevice *device) { if (!device) { qWarning("EXRHandler::canRead() called with no device"); return false; } const QByteArray head = device->peek(4); return Imf::isImfMagic( head.data() ); } /* --- Plugin --- */ QStringList EXRPlugin::keys() const { return QStringList() << "exr" << "EXR"; } QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const { if ( format == "exr" || format == "EXR" ) return Capabilities(CanRead); if ( !format.isEmpty() ) return 0; if ( !device->isOpen() ) return 0; Capabilities cap; if (device->isReadable() && EXRHandler::canRead(device)) cap |= CanRead; return cap; } QImageIOHandler *EXRPlugin::create(QIODevice *device, const QByteArray &format) const { QImageIOHandler *handler = new EXRHandler; handler->setDevice(device); handler->setFormat(format); return handler; } Q_EXPORT_STATIC_PLUGIN( EXRPlugin ) Q_EXPORT_PLUGIN2( exr, EXRPlugin )