hdr.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. import { PanoramaToCubeMapTools } from "./panoramaToCubemap.js";
  2. /**
  3. * This groups tools to convert HDR texture to native colors array.
  4. */
  5. export class HDRTools {
  6. static _Ldexp(mantissa, exponent) {
  7. if (exponent > 1023) {
  8. return mantissa * Math.pow(2, 1023) * Math.pow(2, exponent - 1023);
  9. }
  10. if (exponent < -1074) {
  11. return mantissa * Math.pow(2, -1074) * Math.pow(2, exponent + 1074);
  12. }
  13. return mantissa * Math.pow(2, exponent);
  14. }
  15. static _Rgbe2float(float32array, red, green, blue, exponent, index) {
  16. if (exponent > 0) {
  17. /*nonzero pixel*/
  18. exponent = this._Ldexp(1.0, exponent - (128 + 8));
  19. float32array[index + 0] = red * exponent;
  20. float32array[index + 1] = green * exponent;
  21. float32array[index + 2] = blue * exponent;
  22. }
  23. else {
  24. float32array[index + 0] = 0;
  25. float32array[index + 1] = 0;
  26. float32array[index + 2] = 0;
  27. }
  28. }
  29. static _ReadStringLine(uint8array, startIndex) {
  30. let line = "";
  31. let character = "";
  32. for (let i = startIndex; i < uint8array.length - startIndex; i++) {
  33. character = String.fromCharCode(uint8array[i]);
  34. if (character == "\n") {
  35. break;
  36. }
  37. line += character;
  38. }
  39. return line;
  40. }
  41. /**
  42. * Reads header information from an RGBE texture stored in a native array.
  43. * More information on this format are available here:
  44. * https://en.wikipedia.org/wiki/RGBE_image_format
  45. *
  46. * @param uint8array The binary file stored in native array.
  47. * @returns The header information.
  48. */
  49. // eslint-disable-next-line @typescript-eslint/naming-convention
  50. static RGBE_ReadHeader(uint8array) {
  51. let height = 0;
  52. let width = 0;
  53. let line = this._ReadStringLine(uint8array, 0);
  54. if (line[0] != "#" || line[1] != "?") {
  55. // eslint-disable-next-line no-throw-literal
  56. throw "Bad HDR Format.";
  57. }
  58. let endOfHeader = false;
  59. let findFormat = false;
  60. let lineIndex = 0;
  61. do {
  62. lineIndex += line.length + 1;
  63. line = this._ReadStringLine(uint8array, lineIndex);
  64. if (line == "FORMAT=32-bit_rle_rgbe") {
  65. findFormat = true;
  66. }
  67. else if (line.length == 0) {
  68. endOfHeader = true;
  69. }
  70. } while (!endOfHeader);
  71. if (!findFormat) {
  72. // eslint-disable-next-line no-throw-literal
  73. throw "HDR Bad header format, unsupported FORMAT";
  74. }
  75. lineIndex += line.length + 1;
  76. line = this._ReadStringLine(uint8array, lineIndex);
  77. const sizeRegexp = /^-Y (.*) \+X (.*)$/g;
  78. const match = sizeRegexp.exec(line);
  79. // TODO. Support +Y and -X if needed.
  80. if (!match || match.length < 3) {
  81. // eslint-disable-next-line no-throw-literal
  82. throw "HDR Bad header format, no size";
  83. }
  84. width = parseInt(match[2]);
  85. height = parseInt(match[1]);
  86. if (width < 8 || width > 0x7fff) {
  87. // eslint-disable-next-line no-throw-literal
  88. throw "HDR Bad header format, unsupported size";
  89. }
  90. lineIndex += line.length + 1;
  91. return {
  92. height: height,
  93. width: width,
  94. dataPosition: lineIndex,
  95. };
  96. }
  97. /**
  98. * Returns the cubemap information (each faces texture data) extracted from an RGBE texture.
  99. * This RGBE texture needs to store the information as a panorama.
  100. *
  101. * More information on this format are available here:
  102. * https://en.wikipedia.org/wiki/RGBE_image_format
  103. *
  104. * @param buffer The binary file stored in an array buffer.
  105. * @param size The expected size of the extracted cubemap.
  106. * @param supersample enable supersampling the cubemap (default: false)
  107. * @returns The Cube Map information.
  108. */
  109. static GetCubeMapTextureData(buffer, size, supersample = false) {
  110. const uint8array = new Uint8Array(buffer);
  111. const hdrInfo = this.RGBE_ReadHeader(uint8array);
  112. const data = this.RGBE_ReadPixels(uint8array, hdrInfo);
  113. const cubeMapData = PanoramaToCubeMapTools.ConvertPanoramaToCubemap(data, hdrInfo.width, hdrInfo.height, size, supersample);
  114. return cubeMapData;
  115. }
  116. /**
  117. * Returns the pixels data extracted from an RGBE texture.
  118. * This pixels will be stored left to right up to down in the R G B order in one array.
  119. *
  120. * More information on this format are available here:
  121. * https://en.wikipedia.org/wiki/RGBE_image_format
  122. *
  123. * @param uint8array The binary file stored in an array buffer.
  124. * @param hdrInfo The header information of the file.
  125. * @returns The pixels data in RGB right to left up to down order.
  126. */
  127. // eslint-disable-next-line @typescript-eslint/naming-convention
  128. static RGBE_ReadPixels(uint8array, hdrInfo) {
  129. return this._RGBEReadPixelsRLE(uint8array, hdrInfo);
  130. }
  131. static _RGBEReadPixelsRLE(uint8array, hdrInfo) {
  132. let num_scanlines = hdrInfo.height;
  133. const scanline_width = hdrInfo.width;
  134. let a, b, c, d, count;
  135. let dataIndex = hdrInfo.dataPosition;
  136. let index = 0, endIndex = 0, i = 0;
  137. const scanLineArrayBuffer = new ArrayBuffer(scanline_width * 4); // four channel R G B E
  138. const scanLineArray = new Uint8Array(scanLineArrayBuffer);
  139. // 3 channels of 4 bytes per pixel in float.
  140. const resultBuffer = new ArrayBuffer(hdrInfo.width * hdrInfo.height * 4 * 3);
  141. const resultArray = new Float32Array(resultBuffer);
  142. // read in each successive scanline
  143. while (num_scanlines > 0) {
  144. a = uint8array[dataIndex++];
  145. b = uint8array[dataIndex++];
  146. c = uint8array[dataIndex++];
  147. d = uint8array[dataIndex++];
  148. if (a != 2 || b != 2 || c & 0x80 || hdrInfo.width < 8 || hdrInfo.width > 32767) {
  149. return this._RGBEReadPixelsNOTRLE(uint8array, hdrInfo);
  150. }
  151. if (((c << 8) | d) != scanline_width) {
  152. // eslint-disable-next-line no-throw-literal
  153. throw "HDR Bad header format, wrong scan line width";
  154. }
  155. index = 0;
  156. // read each of the four channels for the scanline into the buffer
  157. for (i = 0; i < 4; i++) {
  158. endIndex = (i + 1) * scanline_width;
  159. while (index < endIndex) {
  160. a = uint8array[dataIndex++];
  161. b = uint8array[dataIndex++];
  162. if (a > 128) {
  163. // a run of the same value
  164. count = a - 128;
  165. if (count == 0 || count > endIndex - index) {
  166. // eslint-disable-next-line no-throw-literal
  167. throw "HDR Bad Format, bad scanline data (run)";
  168. }
  169. while (count-- > 0) {
  170. scanLineArray[index++] = b;
  171. }
  172. }
  173. else {
  174. // a non-run
  175. count = a;
  176. if (count == 0 || count > endIndex - index) {
  177. // eslint-disable-next-line no-throw-literal
  178. throw "HDR Bad Format, bad scanline data (non-run)";
  179. }
  180. scanLineArray[index++] = b;
  181. if (--count > 0) {
  182. for (let j = 0; j < count; j++) {
  183. scanLineArray[index++] = uint8array[dataIndex++];
  184. }
  185. }
  186. }
  187. }
  188. }
  189. // now convert data from buffer into floats
  190. for (i = 0; i < scanline_width; i++) {
  191. a = scanLineArray[i];
  192. b = scanLineArray[i + scanline_width];
  193. c = scanLineArray[i + 2 * scanline_width];
  194. d = scanLineArray[i + 3 * scanline_width];
  195. this._Rgbe2float(resultArray, a, b, c, d, (hdrInfo.height - num_scanlines) * scanline_width * 3 + i * 3);
  196. }
  197. num_scanlines--;
  198. }
  199. return resultArray;
  200. }
  201. static _RGBEReadPixelsNOTRLE(uint8array, hdrInfo) {
  202. // this file is not run length encoded
  203. // read values sequentially
  204. let num_scanlines = hdrInfo.height;
  205. const scanline_width = hdrInfo.width;
  206. let a, b, c, d, i;
  207. let dataIndex = hdrInfo.dataPosition;
  208. // 3 channels of 4 bytes per pixel in float.
  209. const resultBuffer = new ArrayBuffer(hdrInfo.width * hdrInfo.height * 4 * 3);
  210. const resultArray = new Float32Array(resultBuffer);
  211. // read in each successive scanline
  212. while (num_scanlines > 0) {
  213. for (i = 0; i < hdrInfo.width; i++) {
  214. a = uint8array[dataIndex++];
  215. b = uint8array[dataIndex++];
  216. c = uint8array[dataIndex++];
  217. d = uint8array[dataIndex++];
  218. this._Rgbe2float(resultArray, a, b, c, d, (hdrInfo.height - num_scanlines) * scanline_width * 3 + i * 3);
  219. }
  220. num_scanlines--;
  221. }
  222. return resultArray;
  223. }
  224. }
  225. //# sourceMappingURL=hdr.js.map