resample.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. /**
  2. * @module resample
  3. */
  4. function copyNewSize(array, width, height, samplesPerPixel = 1) {
  5. return new (Object.getPrototypeOf(array).constructor)(width * height * samplesPerPixel);
  6. }
  7. /**
  8. * Resample the input arrays using nearest neighbor value selection.
  9. * @param {TypedArray[]} valueArrays The input arrays to resample
  10. * @param {number} inWidth The width of the input rasters
  11. * @param {number} inHeight The height of the input rasters
  12. * @param {number} outWidth The desired width of the output rasters
  13. * @param {number} outHeight The desired height of the output rasters
  14. * @returns {TypedArray[]} The resampled rasters
  15. */
  16. export function resampleNearest(valueArrays, inWidth, inHeight, outWidth, outHeight) {
  17. const relX = inWidth / outWidth;
  18. const relY = inHeight / outHeight;
  19. return valueArrays.map((array) => {
  20. const newArray = copyNewSize(array, outWidth, outHeight);
  21. for (let y = 0; y < outHeight; ++y) {
  22. const cy = Math.min(Math.round(relY * y), inHeight - 1);
  23. for (let x = 0; x < outWidth; ++x) {
  24. const cx = Math.min(Math.round(relX * x), inWidth - 1);
  25. const value = array[(cy * inWidth) + cx];
  26. newArray[(y * outWidth) + x] = value;
  27. }
  28. }
  29. return newArray;
  30. });
  31. }
  32. // simple linear interpolation, code from:
  33. // https://en.wikipedia.org/wiki/Linear_interpolation#Programming_language_support
  34. function lerp(v0, v1, t) {
  35. return ((1 - t) * v0) + (t * v1);
  36. }
  37. /**
  38. * Resample the input arrays using bilinear interpolation.
  39. * @param {TypedArray[]} valueArrays The input arrays to resample
  40. * @param {number} inWidth The width of the input rasters
  41. * @param {number} inHeight The height of the input rasters
  42. * @param {number} outWidth The desired width of the output rasters
  43. * @param {number} outHeight The desired height of the output rasters
  44. * @returns {TypedArray[]} The resampled rasters
  45. */
  46. export function resampleBilinear(valueArrays, inWidth, inHeight, outWidth, outHeight) {
  47. const relX = inWidth / outWidth;
  48. const relY = inHeight / outHeight;
  49. return valueArrays.map((array) => {
  50. const newArray = copyNewSize(array, outWidth, outHeight);
  51. for (let y = 0; y < outHeight; ++y) {
  52. const rawY = relY * y;
  53. const yl = Math.floor(rawY);
  54. const yh = Math.min(Math.ceil(rawY), (inHeight - 1));
  55. for (let x = 0; x < outWidth; ++x) {
  56. const rawX = relX * x;
  57. const tx = rawX % 1;
  58. const xl = Math.floor(rawX);
  59. const xh = Math.min(Math.ceil(rawX), (inWidth - 1));
  60. const ll = array[(yl * inWidth) + xl];
  61. const hl = array[(yl * inWidth) + xh];
  62. const lh = array[(yh * inWidth) + xl];
  63. const hh = array[(yh * inWidth) + xh];
  64. const value = lerp(
  65. lerp(ll, hl, tx),
  66. lerp(lh, hh, tx),
  67. rawY % 1,
  68. );
  69. newArray[(y * outWidth) + x] = value;
  70. }
  71. }
  72. return newArray;
  73. });
  74. }
  75. /**
  76. * Resample the input arrays using the selected resampling method.
  77. * @param {TypedArray[]} valueArrays The input arrays to resample
  78. * @param {number} inWidth The width of the input rasters
  79. * @param {number} inHeight The height of the input rasters
  80. * @param {number} outWidth The desired width of the output rasters
  81. * @param {number} outHeight The desired height of the output rasters
  82. * @param {string} [method = 'nearest'] The desired resampling method
  83. * @returns {TypedArray[]} The resampled rasters
  84. */
  85. export function resample(valueArrays, inWidth, inHeight, outWidth, outHeight, method = 'nearest') {
  86. switch (method.toLowerCase()) {
  87. case 'nearest':
  88. return resampleNearest(valueArrays, inWidth, inHeight, outWidth, outHeight);
  89. case 'bilinear':
  90. case 'linear':
  91. return resampleBilinear(valueArrays, inWidth, inHeight, outWidth, outHeight);
  92. default:
  93. throw new Error(`Unsupported resampling method: '${method}'`);
  94. }
  95. }
  96. /**
  97. * Resample the pixel interleaved input array using nearest neighbor value selection.
  98. * @param {TypedArray} valueArrays The input arrays to resample
  99. * @param {number} inWidth The width of the input rasters
  100. * @param {number} inHeight The height of the input rasters
  101. * @param {number} outWidth The desired width of the output rasters
  102. * @param {number} outHeight The desired height of the output rasters
  103. * @param {number} samples The number of samples per pixel for pixel
  104. * interleaved data
  105. * @returns {TypedArray} The resampled raster
  106. */
  107. export function resampleNearestInterleaved(
  108. valueArray, inWidth, inHeight, outWidth, outHeight, samples) {
  109. const relX = inWidth / outWidth;
  110. const relY = inHeight / outHeight;
  111. const newArray = copyNewSize(valueArray, outWidth, outHeight, samples);
  112. for (let y = 0; y < outHeight; ++y) {
  113. const cy = Math.min(Math.round(relY * y), inHeight - 1);
  114. for (let x = 0; x < outWidth; ++x) {
  115. const cx = Math.min(Math.round(relX * x), inWidth - 1);
  116. for (let i = 0; i < samples; ++i) {
  117. const value = valueArray[(cy * inWidth * samples) + (cx * samples) + i];
  118. newArray[(y * outWidth * samples) + (x * samples) + i] = value;
  119. }
  120. }
  121. }
  122. return newArray;
  123. }
  124. /**
  125. * Resample the pixel interleaved input array using bilinear interpolation.
  126. * @param {TypedArray} valueArrays The input arrays to resample
  127. * @param {number} inWidth The width of the input rasters
  128. * @param {number} inHeight The height of the input rasters
  129. * @param {number} outWidth The desired width of the output rasters
  130. * @param {number} outHeight The desired height of the output rasters
  131. * @param {number} samples The number of samples per pixel for pixel
  132. * interleaved data
  133. * @returns {TypedArray} The resampled raster
  134. */
  135. export function resampleBilinearInterleaved(
  136. valueArray, inWidth, inHeight, outWidth, outHeight, samples) {
  137. const relX = inWidth / outWidth;
  138. const relY = inHeight / outHeight;
  139. const newArray = copyNewSize(valueArray, outWidth, outHeight, samples);
  140. for (let y = 0; y < outHeight; ++y) {
  141. const rawY = relY * y;
  142. const yl = Math.floor(rawY);
  143. const yh = Math.min(Math.ceil(rawY), (inHeight - 1));
  144. for (let x = 0; x < outWidth; ++x) {
  145. const rawX = relX * x;
  146. const tx = rawX % 1;
  147. const xl = Math.floor(rawX);
  148. const xh = Math.min(Math.ceil(rawX), (inWidth - 1));
  149. for (let i = 0; i < samples; ++i) {
  150. const ll = valueArray[(yl * inWidth * samples) + (xl * samples) + i];
  151. const hl = valueArray[(yl * inWidth * samples) + (xh * samples) + i];
  152. const lh = valueArray[(yh * inWidth * samples) + (xl * samples) + i];
  153. const hh = valueArray[(yh * inWidth * samples) + (xh * samples) + i];
  154. const value = lerp(
  155. lerp(ll, hl, tx),
  156. lerp(lh, hh, tx),
  157. rawY % 1,
  158. );
  159. newArray[(y * outWidth * samples) + (x * samples) + i] = value;
  160. }
  161. }
  162. }
  163. return newArray;
  164. }
  165. /**
  166. * Resample the pixel interleaved input array using the selected resampling method.
  167. * @param {TypedArray} valueArray The input array to resample
  168. * @param {number} inWidth The width of the input rasters
  169. * @param {number} inHeight The height of the input rasters
  170. * @param {number} outWidth The desired width of the output rasters
  171. * @param {number} outHeight The desired height of the output rasters
  172. * @param {number} samples The number of samples per pixel for pixel
  173. * interleaved data
  174. * @param {string} [method = 'nearest'] The desired resampling method
  175. * @returns {TypedArray} The resampled rasters
  176. */
  177. export function resampleInterleaved(valueArray, inWidth, inHeight, outWidth, outHeight, samples, method = 'nearest') {
  178. switch (method.toLowerCase()) {
  179. case 'nearest':
  180. return resampleNearestInterleaved(
  181. valueArray, inWidth, inHeight, outWidth, outHeight, samples,
  182. );
  183. case 'bilinear':
  184. case 'linear':
  185. return resampleBilinearInterleaved(
  186. valueArray, inWidth, inHeight, outWidth, outHeight, samples,
  187. );
  188. default:
  189. throw new Error(`Unsupported resampling method: '${method}'`);
  190. }
  191. }