Skip to content

Commit 7f03347

Browse files
committed
Always outputs XYZ format, doing the conversion from RGB internally if necessary
1 parent 5e63314 commit 7f03347

File tree

5 files changed

+122
-81
lines changed

5 files changed

+122
-81
lines changed

README.md

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# HDR
22

3-
A minimal streaming HDR image (ie. Radiance .pic format) library for node.js.
3+
A very minimal streaming HDR image (ie. Radiance .pic format) library for node.js. Always outputs XYZ color space regardless of the format of the HDR file.
44

55
## Contents
66

@@ -43,7 +43,7 @@ hdrloader.on('load', function() {
4343
//-- this.width - image width in pixels
4444
//-- this.height - image height in pixels
4545
//-- this.data - Float32Array of pixel colors with length = width*height*3
46-
//-- in non-planar [R, G, B, R, G, B, ...] pixel layout
46+
//-- in non-planar [X, Y, Z, X, Y, Z, ...] pixel layout
4747
});
4848

4949
//-- Start piping in image data from filesystem/http request/ect.:
@@ -55,7 +55,10 @@ This library does the minimum required to read HDR files. It is up to the consum
5555

5656
## Versions
5757

58-
* [v0.5.0](https://github.com/imbcmdth/hdr/archive/v1.0.0.zip) Initial public release
58+
* [v0.6.0](https://github.com/imbcmdth/hdr/archive/v0.6.0.zip) Output is now always in XYZ color format (doing the conversion automatically if the HDR file is in RGB format) and broke out color handling and pixel format into their own repositories/packages
59+
* [v0.5.1](https://github.com/imbcmdth/hdr/archive/v0.5.1.zip) Made internal functions more easily extensible
60+
* [v0.5.0](https://github.com/imbcmdth/hdr/archive/v0.5.0.zip) Initial public release
61+
5962

6063
## License - MIT
6164

lib/color.js

-37
This file was deleted.

lib/hdr-load.js

+107-37
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,31 @@
11
'use strict';
22

3-
var stream = require('stream'),
4-
util = require('util'),
5-
colorUtil = require('./color');
3+
var stream = require('stream');
4+
var util = require('util');
5+
var floatUtils = require('mmme-encoding');
6+
var xyzUtils = require('xyz-utils');
67

78
module.exports = HDRLoader;
89

9-
var MINLEN = 8,
10-
MAXLEN = 0x7fff,
11-
HEADER_REGEX = /([\#\?]+)?([^=\n\r]+)?=?([^=\n\r]*)?([\n\r]+)/gi,
12-
DIMENSION_REGEX = /([+\-])([XY])\s(\d+)\s([+\-])([XY])\s(\d+)/i,
13-
HEADER_PREFIXES = {
14-
'#?': 'FILETYPE',
15-
'#': 'COMMENT',
16-
'undefined': 'HEADER'
17-
},
18-
R = 0,
19-
G = 1,
20-
B = 2,
21-
E = 3;
10+
var MINLEN = 8;
11+
var MAXLEN = 0x7fff;
12+
var HEADER_REGEX = /([\#\?]+)?([^=\n\r]+)?=?([^=\n\r]*)?([\n\r]+)/gi;
13+
var DIMENSION_REGEX = /^([+\-])([XY])\s(\d+)\s([+\-])([XY])\s(\d+)/i;
14+
var HEADER_PREFIXES = {
15+
'#?': 'FILETYPE',
16+
'#': 'COMMENT',
17+
'undefined': 'HEADER'
18+
};
19+
var DEFAULT_HEADERS = {
20+
'EXPOSURE': 1,
21+
'COLORCORR': [1, 1, 1],
22+
'PIXASPECT': 1,
23+
'PRIMARIES': [0.640, 0.330, 0.290, 0.600, 0.150, 0.060, 0.333, 0.333]
24+
};
25+
var X = 0;
26+
var Y = 1;
27+
var Z = 2;
28+
var E = 3;
2229

2330
function HDRLoader (options) {
2431
if (!options) options = {};
@@ -37,6 +44,9 @@ function HDRLoader (options) {
3744
this._error = false;
3845
this._row_major = true;
3946
this._scanlineSize = -1;
47+
this._isRGBE = true;
48+
this._hasExposure = false;
49+
this._exposureVals = [1, 1, 1];
4050
}
4151

4252
util.inherits(HDRLoader, stream.Writable);
@@ -100,17 +110,18 @@ HDRLoader.prototype._readScanline = function (chunk) {
100110
var firstPixel = [],
101111
scanline;
102112

103-
firstPixel[R] = chunk.readUInt8(R);
104-
firstPixel[G] = chunk.readUInt8(G);
105-
firstPixel[B] = chunk.readUInt8(B);
113+
firstPixel[X] = chunk.readUInt8(X);
114+
firstPixel[Y] = chunk.readUInt8(Y);
115+
firstPixel[Z] = chunk.readUInt8(Z);
106116
firstPixel[E] = chunk.readUInt8(E);
107117

108118
if (this._isOldRLE(firstPixel)) {
109119
scanline = this._readOldRLE(chunk);
110120
} else {
111-
if ((firstPixel[B] << 8 | firstPixel[E]) !== this._getScanlinePixels()) {
121+
if ((firstPixel[Z] << 8 | firstPixel[E]) !== this._getScanlinePixels()) {
112122
this._lastChunk = null;
113123
this.data = null;
124+
console.log(this, chunk)
114125
this._error = true;
115126
this.emit('error');
116127
return;
@@ -123,14 +134,24 @@ HDRLoader.prototype._readScanline = function (chunk) {
123134
}
124135

125136
HDRLoader.prototype._processScanline = function (scanline) {
137+
var tempValues;
138+
var tempFloats;
139+
var tempXYZ;
140+
126141
if (this._row_major) {
127142
for (var i = this._start_x; i !== this._end_x; i += this._inc_x) {
128-
this._writePixel(i, this._current_y, colorUtil.toFloats.apply(null, scanline.shift()));
143+
tempValues = scanline.shift();
144+
tempFloats = floatUtils.toFloats(tempValues[0], tempValues[1], tempValues[2], tempValues[3]);
145+
tempXYZ = this._processPixel(tempFloats);
146+
this._writePixel(i, this._current_y, tempXYZ);
129147
}
130148
this._current_y += this._inc_y;
131149
} else {
132150
for (var i = this._start_y; i !== this._end_y; i += this._inc_y) {
133-
this._writePixel(this._current_x, i, colorUtil.toFloats.apply(null, scanline.shift()));
151+
tempValues = scanline.shift();
152+
tempFloats = floatUtils.toFloats(tempValues[0], tempValues[1], tempValues[2], tempValues[3]);
153+
tempXYZ = this._processPixel(tempFloats);
154+
this._writePixel(this._current_x, i, tempXYZ);
134155
}
135156
this._current_x += this._inc_x;
136157
}
@@ -145,19 +166,19 @@ HDRLoader.prototype._readOldRLE = function (chunk) {
145166

146167
while (len > 0) {
147168
scanline[writePos] = [];
148-
scanline[writePos][R] = chunk.readUInt8(offset++);
149-
scanline[writePos][G] = chunk.readUInt8(offset++);
150-
scanline[writePos][B] = chunk.readUInt8(offset++);
169+
scanline[writePos][X] = chunk.readUInt8(offset++);
170+
scanline[writePos][Y] = chunk.readUInt8(offset++);
171+
scanline[writePos][Z] = chunk.readUInt8(offset++);
151172
scanline[writePos][E] = chunk.readUInt8(offset++);
152173

153-
if (scanline[writePos][R] === 1
154-
&& scanline[writePos][G] === 1
155-
&& scanline[writePos][B] === 1) {
174+
if (scanline[writePos][X] === 1
175+
&& scanline[writePos][Y] === 1
176+
&& scanline[writePos][Z] === 1) {
156177
for (i = scanline[writePos][E] << rshift; i > 0; i--) {
157178
scanline[writePos] = [];
158-
scanline[writePos][R] = scanline[writePos - 1][R];
159-
scanline[writePos][G] = scanline[writePos - 1][G];
160-
scanline[writePos][B] = scanline[writePos - 1][B];
179+
scanline[writePos][X] = scanline[writePos - 1][X];
180+
scanline[writePos][Y] = scanline[writePos - 1][Y];
181+
scanline[writePos][X] = scanline[writePos - 1][Z];
161182
scanline[writePos][E] = scanline[writePos - 1][E];
162183
writePos++;
163184
len--;
@@ -214,12 +235,32 @@ HDRLoader.prototype._trimRemainingBuffer = function (chunk, consumed) {
214235
this._lastChunk = chunk.slice(consumed);
215236
}
216237

238+
HDRLoader.prototype._processPixel = function (pixelData) {
239+
var XYZ = pixelData;
240+
241+
// First adjust the pixel data according to exposure settings
242+
pixelData[0] /= this._exposureVals[0];
243+
pixelData[1] /= this._exposureVals[1];
244+
pixelData[2] /= this._exposureVals[2];
245+
246+
// If the pixel format is RGB we convert to XYZ because of the high
247+
// likelyhood that any further HDR processing will be done in XYZ
248+
// or a similar color space and XYZ offers the best intermediate for
249+
// conversion between colorspaces
250+
if (this._isRGBE) {
251+
// TODO: Use white point for conversion if supplied in headers
252+
XYZ = xyzUtils.fromRGB(pixelData);
253+
}
254+
255+
return XYZ;
256+
}
257+
217258
HDRLoader.prototype._writePixel = function (x, y, pixelData) {
218259
var offset = (x + y * this.width) * 3;
219260

220-
this.data[offset++] = pixelData[R];
221-
this.data[offset++] = pixelData[G];
222-
this.data[offset++] = pixelData[B];
261+
this.data[offset++] = pixelData[X];
262+
this.data[offset++] = pixelData[Y];
263+
this.data[offset++] = pixelData[Z];
223264
}
224265

225266
HDRLoader.prototype._readHeader = function (chunk) {
@@ -242,6 +283,20 @@ HDRLoader.prototype._readHeader = function (chunk) {
242283
}
243284

244285
this._headerFinished = true;
286+
287+
if (this._hasExposure) {
288+
if (this.headers['EXPOSURE']) {
289+
var t = this.headers['EXPOSURE'];
290+
this._exposureVals = [t, t, t];
291+
}
292+
if (this.headers['COLORCORR']) {
293+
var t = this.headers['COLORCORR'];
294+
this._exposureVals[0] *= t[0];
295+
this._exposureVals[1] *= t[1];
296+
this._exposureVals[2] *= t[2];
297+
}
298+
}
299+
245300
break;
246301
} else {
247302
switch (HEADER_PREFIXES[String(headerData[1])]) {
@@ -267,19 +322,34 @@ HDRLoader.prototype._readHeader = function (chunk) {
267322
this._trimRemainingBuffer(chunk, sliceOffset);
268323
}
269324

325+
// We only attempt to parse the most common headers
270326
HDRLoader.prototype._processHeader = function (headerName, headerValue) {
271327
switch (headerName.toUpperCase()) {
272328
case 'EXPOSURE':
329+
this._hasExposure = true;
273330
case 'PIXASPECT':
274331
var val = parseFloat(headerValue);
275332
return this.headers[headerName] ? this.headers[headerName] * val : val;
276333
case 'PRIMARIES':
277334
var vals = headerValue.split(/\s+/);
278335
return vals.map(parseFloat);
279336
case 'COLORCORR':
337+
this._hasExposure = true;
280338
var vals = headerValue.split(/\s+/);
281339
vals = vals.map(parseFloat);
282340
return this.headers[headerName] ? this.headers[headerName].map(mults(vals)) : vals;
341+
case 'FORMAT':
342+
if (headerValue === '32-bit_rle_rgbe') {
343+
this._isRGBE = true;
344+
return 'RGBE';
345+
} else if (headerValue === '32-bit_rle_xyze') {
346+
this._isRGBE = false;
347+
return 'XYZE';
348+
} else {
349+
// Must be a parse error
350+
this._error = true;
351+
this.emit('error');
352+
}
283353
default:
284354
return headerValue;
285355
}
@@ -290,9 +360,9 @@ HDRLoader.prototype._isOldRLE = function (pixel) {
290360

291361
if (len < MINLEN || len > MAXLEN) return true;
292362

293-
if (pixel[R] !== 2) return true;
363+
if (pixel[X] !== 2) return true;
294364

295-
if (pixel[G] !== 2 || pixel[B] & 128) return true;
365+
if (pixel[Y] !== 2 || pixel[Z] & 128) return true;
296366

297367
return false;
298368
}
@@ -378,4 +448,4 @@ function mults (m) {
378448
return function (v, i) {
379449
return m[i] * v;
380450
};
381-
}
451+
}

lib/hdr.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
module.exports = {
2-
loader: require('./hdr-load.js')
3-
}
2+
loader: require('./hdr-load.js'),
3+
utils: require('mmme-encoding')
4+
}

package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "hdr",
33
"description": "Module for reading HDR image files",
4-
"version": "0.5.1",
4+
"version": "0.6.0",
55
"homepage": "https://github.com/imbcmdth/hdr",
66
"repository": {
77
"type": "git",
@@ -22,11 +22,15 @@
2222
"xyze"
2323
],
2424
"main": "./lib/hdr",
25+
"dependencies": {
26+
"mmme-encoding": "0.1.0",
27+
"xyz-utils": "0.1.0"
28+
},
2529
"devDependencies": {
2630
"mocha": "1.8.1"
2731
},
2832
"scripts": {
2933
"test": "./node_modules/mocha/bin/mocha test"
3034
},
3135
"license": "MIT"
32-
}
36+
}

0 commit comments

Comments
 (0)