1 | // -*- C++;indent-tabs-mode: t; tab-width: 4; c-basic-offset: 4; -*- |
2 | |
3 | /** |
4 | * KImageIO Routines to read (and perhaps in the future, write) images |
5 | * in the high dynamic range EXR format. |
6 | * Copyright (c) 2003, Brad Hards <bradh@frogmouth.net> |
7 | * |
8 | * This library is distributed under the conditions of the GNU LGPL. |
9 | */ |
10 | |
11 | #include "exr.h" |
12 | |
13 | #include <config.h> |
14 | |
15 | #include <ImfRgbaFile.h> |
16 | #include <ImfStandardAttributes.h> |
17 | #include <ImathBox.h> |
18 | #include <ImfInputFile.h> |
19 | #include <ImfBoxAttribute.h> |
20 | #include <ImfChannelListAttribute.h> |
21 | #include <ImfCompressionAttribute.h> |
22 | #include <ImfFloatAttribute.h> |
23 | #include <ImfIntAttribute.h> |
24 | #include <ImfLineOrderAttribute.h> |
25 | #include <ImfStringAttribute.h> |
26 | #include <ImfVecAttribute.h> |
27 | #include <ImfArray.h> |
28 | #include <ImfConvert.h> |
29 | #include <ImfVersion.h> |
30 | #include <IexThrowErrnoExc.h> |
31 | |
32 | #include <iostream> |
33 | |
34 | #include <kdebug.h> |
35 | |
36 | |
37 | #include <QImage> |
38 | #include <QDataStream> |
39 | #include <QImageIOPlugin> |
40 | |
41 | class K_IStream: public Imf::IStream |
42 | { |
43 | public: |
44 | K_IStream( QIODevice *dev, const QByteArray& fileName ): |
45 | IStream( fileName.data() ), m_dev ( dev ) |
46 | {} |
47 | |
48 | virtual bool read( char c[], int n ); |
49 | virtual Imf::Int64 tellg( ); |
50 | virtual void seekg( Imf::Int64 pos ); |
51 | virtual void clear( ); |
52 | |
53 | private: |
54 | QIODevice *m_dev; |
55 | }; |
56 | |
57 | bool K_IStream::read( char c[], int n ) |
58 | { |
59 | qint64 result = m_dev->read( c, n ); |
60 | if ( result > 0 ) { |
61 | return true; |
62 | } else if ( result == 0 ) { |
63 | throw Iex::InputExc( "Unexpected end of file" ); |
64 | } else // negative value { |
65 | Iex::throwErrnoExc( "Error in read" , result ); |
66 | return false; |
67 | } |
68 | |
69 | Imf::Int64 K_IStream::tellg( ) |
70 | { |
71 | return m_dev->pos(); |
72 | } |
73 | |
74 | void K_IStream::seekg( Imf::Int64 pos ) |
75 | { |
76 | m_dev->seek( pos ); |
77 | } |
78 | |
79 | void K_IStream::clear( ) |
80 | { |
81 | // TODO |
82 | } |
83 | |
84 | /* this does a conversion from the ILM Half (equal to Nvidia Half) |
85 | * format into the normal 32 bit pixel format. Process is from the |
86 | * ILM code. |
87 | */ |
88 | QRgb RgbaToQrgba(struct Imf::Rgba imagePixel) |
89 | { |
90 | float r,g,b,a; |
91 | |
92 | // 1) Compensate for fogging by subtracting defog |
93 | // from the raw pixel values. |
94 | // Response: We work with defog of 0.0, so this is a no-op |
95 | |
96 | // 2) Multiply the defogged pixel values by |
97 | // 2^(exposure + 2.47393). |
98 | // Response: We work with exposure of 0.0. |
99 | // (2^2.47393) is 5.55555 |
100 | r = imagePixel.r * 5.55555; |
101 | g = imagePixel.g * 5.55555; |
102 | b = imagePixel.b * 5.55555; |
103 | a = imagePixel.a * 5.55555; |
104 | |
105 | // 3) Values, which are now 1.0, are called "middle gray". |
106 | // If defog and exposure are both set to 0.0, then |
107 | // middle gray corresponds to a raw pixel value of 0.18. |
108 | // In step 6, middle gray values will be mapped to an |
109 | // intensity 3.5 f-stops below the display's maximum |
110 | // intensity. |
111 | // Response: no apparent content. |
112 | |
113 | // 4) Apply a knee function. The knee function has two |
114 | // parameters, kneeLow and kneeHigh. Pixel values |
115 | // below 2^kneeLow are not changed by the knee |
116 | // function. Pixel values above kneeLow are lowered |
117 | // according to a logarithmic curve, such that the |
118 | // value 2^kneeHigh is mapped to 2^3.5 (in step 6, |
119 | // this value will be mapped to the display's |
120 | // maximum intensity). |
121 | // Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32) |
122 | if (r > 1.0) |
123 | r = 1.0 + Imath::Math<float>::log ((r-1.0) * 0.184874 + 1) / 0.184874; |
124 | if (g > 1.0) |
125 | g = 1.0 + Imath::Math<float>::log ((g-1.0) * 0.184874 + 1) / 0.184874; |
126 | if (b > 1.0) |
127 | b = 1.0 + Imath::Math<float>::log ((b-1.0) * 0.184874 + 1) / 0.184874; |
128 | if (a > 1.0) |
129 | a = 1.0 + Imath::Math<float>::log ((a-1.0) * 0.184874 + 1) / 0.184874; |
130 | // |
131 | // 5) Gamma-correct the pixel values, assuming that the |
132 | // screen's gamma is 0.4545 (or 1/2.2). |
133 | r = Imath::Math<float>::pow (r, 0.4545); |
134 | g = Imath::Math<float>::pow (g, 0.4545); |
135 | b = Imath::Math<float>::pow (b, 0.4545); |
136 | a = Imath::Math<float>::pow (a, 0.4545); |
137 | |
138 | // 6) Scale the values such that pixels middle gray |
139 | // pixels are mapped to 84.66 (or 3.5 f-stops below |
140 | // the display's maximum intensity). |
141 | // |
142 | // 7) Clamp the values to [0, 255]. |
143 | return qRgba( (unsigned char) (Imath::clamp ( r * 84.66f, 0.f, 255.f ) ), |
144 | (unsigned char) (Imath::clamp ( g * 84.66f, 0.f, 255.f ) ), |
145 | (unsigned char) (Imath::clamp ( b * 84.66f, 0.f, 255.f ) ), |
146 | (unsigned char) (Imath::clamp ( a * 84.66f, 0.f, 255.f ) ) ); |
147 | } |
148 | |
149 | EXRHandler::EXRHandler() |
150 | { |
151 | } |
152 | |
153 | bool EXRHandler::canRead() const |
154 | { |
155 | if (canRead(device())) { |
156 | setFormat("exr" ); |
157 | return true; |
158 | } |
159 | return false; |
160 | } |
161 | |
162 | QByteArray EXRHandler::name() const |
163 | { |
164 | // TODO |
165 | return QByteArray("exr" ); |
166 | } |
167 | |
168 | bool EXRHandler::read( QImage *outImage ) |
169 | { |
170 | try |
171 | { |
172 | int width, height; |
173 | |
174 | K_IStream istr( device(), QByteArray() ); |
175 | Imf::RgbaInputFile file( istr ); |
176 | Imath::Box2i dw = file.dataWindow(); |
177 | |
178 | width = dw.max.x - dw.min.x + 1; |
179 | height = dw.max.y - dw.min.y + 1; |
180 | |
181 | Imf::Array2D<Imf::Rgba> pixels; |
182 | pixels.resizeErase (height, width); |
183 | |
184 | file.setFrameBuffer (&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width); |
185 | file.readPixels (dw.min.y, dw.max.y); |
186 | |
187 | QImage image(width, height, QImage::Format_RGB32); |
188 | if( image.isNull()) |
189 | return false; |
190 | |
191 | // somehow copy pixels into image |
192 | for ( int y=0; y < height; y++ ) { |
193 | for ( int x=0; x < width; x++ ) { |
194 | // copy pixels(x,y) into image(x,y) |
195 | image.setPixel( x, y, RgbaToQrgba( pixels[y][x] ) ); |
196 | } |
197 | } |
198 | |
199 | *outImage = image; |
200 | |
201 | return true; |
202 | } |
203 | catch (const std::exception &exc) |
204 | { |
205 | kDebug() << exc.what(); |
206 | return false; |
207 | } |
208 | } |
209 | |
210 | |
211 | bool EXRHandler::write( const QImage &image ) |
212 | { |
213 | // TODO: stub |
214 | Q_UNUSED( image ); |
215 | return false; |
216 | } |
217 | |
218 | |
219 | bool EXRHandler::canRead(QIODevice *device) |
220 | { |
221 | if (!device) { |
222 | qWarning("EXRHandler::canRead() called with no device" ); |
223 | return false; |
224 | } |
225 | |
226 | const QByteArray head = device->peek(4); |
227 | |
228 | return Imf::isImfMagic( head.data() ); |
229 | } |
230 | |
231 | |
232 | /* --- Plugin --- */ |
233 | |
234 | QStringList EXRPlugin::keys() const |
235 | { |
236 | return QStringList() << "exr" << "EXR" ; |
237 | } |
238 | |
239 | |
240 | QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const |
241 | { |
242 | if ( format == "exr" || format == "EXR" ) |
243 | return Capabilities(CanRead); |
244 | if ( !format.isEmpty() ) |
245 | return 0; |
246 | if ( !device->isOpen() ) |
247 | return 0; |
248 | |
249 | Capabilities cap; |
250 | if (device->isReadable() && EXRHandler::canRead(device)) |
251 | cap |= CanRead; |
252 | return cap; |
253 | } |
254 | |
255 | QImageIOHandler *EXRPlugin::create(QIODevice *device, const QByteArray &format) const |
256 | { |
257 | QImageIOHandler *handler = new EXRHandler; |
258 | handler->setDevice(device); |
259 | handler->setFormat(format); |
260 | return handler; |
261 | } |
262 | |
263 | Q_EXPORT_STATIC_PLUGIN( EXRPlugin ) |
264 | Q_EXPORT_PLUGIN2( exr, EXRPlugin ) |
265 | |