@gisatcz/deckgl-geolib 2.4.0-dev.1 → 2.4.0-dev.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/index.js CHANGED
@@ -5243,88 +5243,62 @@ function scale(num, inMin, inMax, outMin, outMax) {
5243
5243
  }
5244
5244
 
5245
5245
  class BitmapGenerator {
5246
+ /**
5247
+ * Main entry point: Generates an ImageBitmap from raw raster data.
5248
+ */
5246
5249
  static async generate(input, options) {
5247
5250
  const optionsLocal = { ...options };
5248
5251
  const { rasters, width, height } = input;
5249
- const channels = rasters.length;
5252
+ // NOTE: As of 2026-03, only interleaved rasters (rasters.length === 1) are produced by the main COG tile path.
5253
+ // Planar (rasters.length > 1) is not currently supported in production, but this check is kept for future extension.
5254
+ const isInterleaved = rasters.length === 1;
5250
5255
  const canvas = document.createElement('canvas');
5251
5256
  canvas.width = width;
5252
5257
  canvas.height = height;
5253
5258
  const c = canvas.getContext('2d');
5254
5259
  const imageData = c.createImageData(width, height);
5255
- let r;
5256
- let g;
5257
- let b;
5258
- let a;
5259
5260
  const size = width * height * 4;
5260
5261
  const alpha255 = Math.floor(optionsLocal.alpha * 2.55);
5262
+ // Normalize colors using chroma
5261
5263
  optionsLocal.unidentifiedColor = this.getColorFromChromaType(optionsLocal.unidentifiedColor, alpha255);
5262
5264
  optionsLocal.nullColor = this.getColorFromChromaType(optionsLocal.nullColor, alpha255);
5263
5265
  optionsLocal.clippedColor = this.getColorFromChromaType(optionsLocal.clippedColor, alpha255);
5264
5266
  optionsLocal.color = this.getColorFromChromaType(optionsLocal.color, alpha255);
5265
5267
  optionsLocal.useChannelIndex ??= optionsLocal.useChannel == null ? null : optionsLocal.useChannel - 1;
5266
5268
  // Derive channel count from data if not provided
5267
- const numAvailableChannels = optionsLocal.numOfChannels ?? (rasters.length === 1 ? rasters[0].length / (width * height) : rasters.length);
5269
+ // If planar support is added, this logic must be updated to handle both layouts correctly.
5270
+ const numAvailableChannels = optionsLocal.numOfChannels ??
5271
+ (rasters.length === 1 ? rasters[0].length / (width * height) : rasters.length);
5268
5272
  if (optionsLocal.useChannelIndex == null) {
5269
- if (channels === 1) {
5270
- if (rasters[0].length / (width * height) === 1) {
5271
- const channel = rasters[0];
5272
- // AUTO RANGE
5273
+ if (isInterleaved) {
5274
+ const ratio = rasters[0].length / (width * height);
5275
+ if (ratio === 1) {
5273
5276
  if (optionsLocal.useAutoRange) {
5274
- optionsLocal.colorScaleValueRange = this.getMinMax(channel, optionsLocal);
5277
+ optionsLocal.colorScaleValueRange = this.getMinMax(rasters[0], optionsLocal);
5275
5278
  }
5276
- // SINGLE CHANNEL
5277
- const colorData = this.getColorValue(channel, optionsLocal, size);
5278
- imageData.data.set(colorData);
5279
- }
5280
- // RGB values in one channel
5281
- if (rasters[0].length / (width * height) === 3) {
5282
- let pixel = 0;
5283
- for (let idx = 0; idx < size; idx += 4) {
5284
- const rgbColor = [rasters[0][pixel], rasters[0][pixel + 1], rasters[0][pixel + 2]];
5285
- const rgbaColor = this.hasPixelsNoData(rgbColor, optionsLocal.noDataValue)
5286
- ? optionsLocal.nullColor
5287
- : [...rgbColor, Math.floor(optionsLocal.alpha * 2.55)];
5288
- [imageData.data[idx], imageData.data[idx + 1], imageData.data[idx + 2], imageData.data[idx + 3]] = rgbaColor;
5289
- pixel += 3;
5279
+ imageData.data.set(this.getColorValue(rasters[0], optionsLocal, size));
5280
+ } // 3 or 4-band RGB(A) imagery: use per-pixel loop for direct color assignment
5281
+ else if (ratio === 3 || ratio === 4) {
5282
+ let sampleIndex = 0;
5283
+ for (let i = 0; i < size; i += 4) {
5284
+ const rgbColor = [rasters[0][sampleIndex], rasters[0][sampleIndex + 1], rasters[0][sampleIndex + 2]];
5285
+ const isNoData = this.hasPixelsNoData(rgbColor, optionsLocal.noDataValue);
5286
+ imageData.data[i] = isNoData ? optionsLocal.nullColor[0] : rgbColor[0];
5287
+ imageData.data[i + 1] = isNoData ? optionsLocal.nullColor[1] : rgbColor[1];
5288
+ imageData.data[i + 2] = isNoData ? optionsLocal.nullColor[2] : rgbColor[2];
5289
+ imageData.data[i + 3] = isNoData ? optionsLocal.nullColor[3] : (ratio === 4 ? rasters[0][sampleIndex + 3] : alpha255);
5290
+ sampleIndex += ratio;
5290
5291
  }
5291
5292
  }
5292
- if (rasters[0].length / (width * height) === 4) {
5293
- rasters[0].forEach((value, index) => {
5294
- imageData.data[index] = value;
5295
- });
5296
- }
5297
- }
5298
- if (channels === 3) {
5299
- // RGB
5300
- let pixel = 0;
5301
- const alphaConst = Math.floor(optionsLocal.alpha * 2.55);
5302
- for (let i = 0; i < size; i += 4) {
5303
- r = rasters[0][pixel];
5304
- g = rasters[1][pixel];
5305
- b = rasters[2][pixel];
5306
- a = alphaConst;
5307
- imageData.data[i] = r;
5308
- imageData.data[i + 1] = g;
5309
- imageData.data[i + 2] = b;
5310
- imageData.data[i + 3] = a;
5311
- pixel += 1;
5312
- }
5313
5293
  }
5314
- if (channels === 4) {
5315
- // RGBA
5316
- let pixel = 0;
5317
- const alphaConst = Math.floor(optionsLocal.alpha * 2.55);
5294
+ else {
5295
+ let sampleIndex = 0;
5318
5296
  for (let i = 0; i < size; i += 4) {
5319
- r = rasters[0][pixel];
5320
- g = rasters[1][pixel];
5321
- b = rasters[2][pixel];
5322
- a = alphaConst;
5323
- imageData.data[i] = r;
5324
- imageData.data[i + 1] = g;
5325
- imageData.data[i + 2] = b;
5326
- imageData.data[i + 3] = a;
5327
- pixel += 1;
5297
+ imageData.data[i] = rasters[0][sampleIndex];
5298
+ imageData.data[i + 1] = rasters[1][sampleIndex];
5299
+ imageData.data[i + 2] = rasters[2][sampleIndex];
5300
+ imageData.data[i + 3] = rasters.length === 4 ? rasters[3][sampleIndex] : alpha255;
5301
+ sampleIndex++;
5328
5302
  }
5329
5303
  }
5330
5304
  }
@@ -5332,12 +5306,10 @@ class BitmapGenerator {
5332
5306
  const isInterleaved = rasters.length === 1 && numAvailableChannels > 1;
5333
5307
  const channel = isInterleaved ? rasters[0] : (rasters[optionsLocal.useChannelIndex] ?? rasters[0]);
5334
5308
  const samplesPerPixel = isInterleaved ? numAvailableChannels : 1;
5335
- // AUTO RANGE
5336
5309
  if (optionsLocal.useAutoRange) {
5337
5310
  optionsLocal.colorScaleValueRange = this.getMinMax(channel, optionsLocal, samplesPerPixel);
5338
5311
  }
5339
- const colorData = this.getColorValue(channel, optionsLocal, size, samplesPerPixel);
5340
- imageData.data.set(colorData);
5312
+ imageData.data.set(this.getColorValue(channel, optionsLocal, size, samplesPerPixel));
5341
5313
  }
5342
5314
  else {
5343
5315
  // if user defined channel does not exist
@@ -5353,173 +5325,130 @@ class BitmapGenerator {
5353
5325
  // Note: createImageBitmap(imageData) is cleaner, but using the canvas ensures broad compatibility
5354
5326
  c.putImageData(imageData, 0, 0);
5355
5327
  const map = await createImageBitmap(canvas);
5356
- return {
5357
- map,
5358
- // rasters[0] is the interleaved buffer on the CogTiles path (primary use case).
5359
- // For planar multi-band GeoTIFFs via GeoImage.getBitmap(), only the first band is exposed here.
5360
- // Full multi-band raw picking support is tracked in https://github.com/Gisat/deck.gl-geotiff/issues/98
5361
- raw: rasters[0],
5362
- width,
5363
- height
5364
- };
5365
- }
5366
- static getMinMax(array, options, samplesPerPixel = 1) {
5367
- let maxValue = -Infinity;
5368
- let minValue = Infinity;
5369
- let foundValid = false;
5370
- let pixel = samplesPerPixel === 1 ? 0 : (options.useChannelIndex ?? 0);
5371
- for (let idx = pixel; idx < array.length; idx += samplesPerPixel) {
5372
- if (options.noDataValue === undefined || array[idx] !== options.noDataValue) {
5373
- if (array[idx] > maxValue)
5374
- maxValue = array[idx];
5375
- if (array[idx] < minValue)
5376
- minValue = array[idx];
5377
- foundValid = true;
5378
- }
5379
- }
5380
- if (!foundValid) {
5381
- return options.colorScaleValueRange || [0, 255];
5382
- }
5383
- return [minValue, maxValue];
5328
+ // rasters[0] is the interleaved buffer on the CogTiles path (primary use case).
5329
+ // For planar multi-band GeoTIFFs via GeoImage.getBitmap(), only the first band is exposed here.
5330
+ // Full multi-band raw picking support is tracked in https://github.com/Gisat/deck.gl-geotiff/issues/98
5331
+ return { map, raw: rasters[0], width, height };
5384
5332
  }
5385
5333
  static getColorValue(dataArray, options, arrayLength, samplesPerPixel = 1) {
5386
- const colorScale = chroma.scale(options.colorScale).domain(options.colorScaleValueRange);
5387
- let pixel = samplesPerPixel === 1 ? 0 : (options.useChannelIndex ?? 0);
5334
+ // Normalize all colorScale entries for chroma.js compatibility
5335
+ const colorScale = chroma.scale(options.colorScale?.map(c => Array.isArray(c) ? chroma(c) : c)).domain(options.colorScaleValueRange);
5388
5336
  const colorsArray = new Uint8ClampedArray(arrayLength);
5389
- const cbvInput = options.colorsBasedOnValues ?? [];
5390
- const classesInput = options.colorClasses ?? [];
5391
- const optUseColorsBasedOnValues = options.useColorsBasedOnValues && cbvInput.length > 0;
5392
- const optUseColorClasses = options.useColorClasses && classesInput.length > 0;
5393
- const dataValues = optUseColorsBasedOnValues ? cbvInput.map(([first]) => first) : [];
5394
- const colorValues = optUseColorsBasedOnValues ? cbvInput.map(([, second]) => [...chroma(second).rgb(), Math.floor(options.alpha * 2.55)]) : [];
5395
- const colorClasses = optUseColorClasses ? classesInput.map(([color]) => [...chroma(color).rgb(), Math.floor(options.alpha * 2.55)]) : [];
5396
- const dataIntervals = optUseColorClasses ? classesInput.map(([, interval]) => interval) : [];
5397
- const dataIntervalBounds = optUseColorClasses ? classesInput.map(([, , bounds], index) => {
5398
- if (bounds !== undefined)
5399
- return bounds;
5400
- if (index === classesInput.length - 1)
5401
- return [true, true];
5402
- return [true, false];
5403
- }) : [];
5404
- // Pre-calculate Loop Variables to avoid object lookup in loop
5405
- const optNoData = options.noDataValue;
5406
- const optClipLow = options.clipLow;
5407
- const optClipHigh = options.clipHigh;
5408
- const optClippedColor = options.clippedColor;
5409
- const optUseHeatMap = options.useHeatMap;
5410
- const optUseSingleColor = options.useSingleColor;
5411
- const optUseDataForOpacity = options.useDataForOpacity;
5412
- const optColor = options.color;
5413
- const optUnidentifiedColor = options.unidentifiedColor;
5414
- const optNullColor = options.nullColor;
5415
5337
  const optAlpha = Math.floor(options.alpha * 2.55);
5416
5338
  const rangeMin = options.colorScaleValueRange[0];
5417
5339
  const rangeMax = options.colorScaleValueRange.slice(-1)[0];
5418
- // LOOKUP TABLE OPTIMIZATION (for 8-bit data)
5419
- // If the data is Uint8 (0-255), we can pre-calculate the result for every possible value.
5420
5340
  const is8Bit = dataArray instanceof Uint8Array || dataArray instanceof Uint8ClampedArray;
5421
- // The LUT optimization is only applied for 8-bit data when `useDataForOpacity` is false.
5422
- // `useDataForOpacity` is excluded because it requires the raw data value for
5423
- // dynamic opacity scaling. All other visualization modes (HeatMap, Categorical,
5424
- // Classes, Single Color) are pre-calculated into the LUT for maximum performance.
5425
- if (is8Bit && !optUseDataForOpacity) {
5426
- // Create LUT: 256 values * 4 channels (RGBA)
5341
+ const isFloatOrWide = !is8Bit && (dataArray instanceof Float32Array || dataArray instanceof Uint16Array || dataArray instanceof Int16Array);
5342
+ // 1. 8-BIT COMPREHENSIVE LUT
5343
+ // Single-band 8-bit (grayscale or indexed): use LUT for fast mapping
5344
+ if (is8Bit && !options.useDataForOpacity) {
5427
5345
  const lut = new Uint8ClampedArray(256 * 4);
5428
5346
  for (let i = 0; i < 256; i++) {
5429
- let r = optNullColor[0], g = optNullColor[1], b = optNullColor[2], a = optNullColor[3];
5430
- // Logic mirroring the pixel loop
5431
- if (optNoData === undefined || i !== optNoData) {
5432
- if ((optClipLow != null && i <= optClipLow) || (optClipHigh != null && i >= optClipHigh)) {
5433
- [r, g, b, a] = optClippedColor;
5434
- }
5435
- else {
5436
- let c = [r, g, b, a];
5437
- if (optUseHeatMap) {
5438
- const rgb = colorScale(i).rgb();
5439
- c = [rgb[0], rgb[1], rgb[2], optAlpha];
5440
- }
5441
- else if (optUseColorsBasedOnValues) {
5442
- const index = dataValues.indexOf(i);
5443
- c = (index > -1) ? colorValues[index] : optUnidentifiedColor;
5444
- }
5445
- else if (optUseColorClasses) {
5446
- const index = this.findClassIndex(i, dataIntervals, dataIntervalBounds);
5447
- c = (index > -1) ? colorClasses[index] : optUnidentifiedColor;
5448
- }
5449
- else if (optUseSingleColor) {
5450
- c = optColor;
5451
- }
5452
- [r, g, b, a] = c;
5453
- }
5347
+ if ((options.clipLow != null && i <= options.clipLow) ||
5348
+ (options.clipHigh != null && i >= options.clipHigh)) {
5349
+ lut.set(options.clippedColor, i * 4);
5454
5350
  }
5455
- lut[i * 4] = r;
5456
- lut[i * 4 + 1] = g;
5457
- lut[i * 4 + 2] = b;
5458
- lut[i * 4 + 3] = a;
5459
- }
5460
- // Fast Apply Loop
5461
- let outIdx = 0;
5462
- const numPixels = arrayLength / 4;
5463
- for (let i = 0; i < numPixels; i++) {
5464
- const val = dataArray[pixel];
5465
- const lutIdx = Math.min(255, Math.max(0, val)) * 4;
5466
- colorsArray[outIdx++] = lut[lutIdx];
5467
- colorsArray[outIdx++] = lut[lutIdx + 1];
5468
- colorsArray[outIdx++] = lut[lutIdx + 2];
5469
- colorsArray[outIdx++] = lut[lutIdx + 3];
5470
- pixel += samplesPerPixel;
5351
+ else {
5352
+ lut.set(this.calculateSingleColor(i, colorScale, options, optAlpha), i * 4);
5353
+ }
5354
+ }
5355
+ for (let i = 0, sampleIndex = (options.useChannelIndex ?? 0); i < arrayLength; i += 4, sampleIndex += samplesPerPixel) {
5356
+ const lutIdx = dataArray[sampleIndex] * 4;
5357
+ colorsArray[i] = lut[lutIdx];
5358
+ colorsArray[i + 1] = lut[lutIdx + 1];
5359
+ colorsArray[i + 2] = lut[lutIdx + 2];
5360
+ colorsArray[i + 3] = lut[lutIdx + 3];
5471
5361
  }
5472
5362
  return colorsArray;
5473
5363
  }
5474
- // Standard Loop (Float or non-optimized)
5475
- for (let i = 0; i < arrayLength; i += 4) {
5476
- let r = optNullColor[0], g = optNullColor[1], b = optNullColor[2], a = optNullColor[3];
5477
- const val = dataArray[pixel];
5478
- if ((!Number.isNaN(val)) && (optNoData === undefined || val !== optNoData)) {
5479
- if ((optClipLow != null && val <= optClipLow) || (optClipHigh != null && val >= optClipHigh)) {
5480
- [r, g, b, a] = optClippedColor;
5364
+ // 2. FLOAT / 16-BIT LUT (HEATMAP ONLY)
5365
+ if (isFloatOrWide && options.useHeatMap && !options.useDataForOpacity) {
5366
+ const LUT_SIZE = 1024;
5367
+ const lut = new Uint8ClampedArray(LUT_SIZE * 4);
5368
+ const rangeSpan = (rangeMax - rangeMin) || 1;
5369
+ for (let i = 0; i < LUT_SIZE; i++) {
5370
+ const domainVal = rangeMin + (i / (LUT_SIZE - 1)) * rangeSpan;
5371
+ if ((options.clipLow != null && domainVal <= options.clipLow) ||
5372
+ (options.clipHigh != null && domainVal >= options.clipHigh)) {
5373
+ lut.set(options.clippedColor, i * 4);
5481
5374
  }
5482
5375
  else {
5483
- let c;
5484
- if (optUseHeatMap) {
5485
- const rgb = colorScale(val).rgb();
5486
- c = [rgb[0], rgb[1], rgb[2], optAlpha];
5487
- }
5488
- else if (optUseColorsBasedOnValues) {
5489
- const index = dataValues.indexOf(val);
5490
- c = (index > -1) ? colorValues[index] : optUnidentifiedColor;
5491
- }
5492
- else if (optUseColorClasses) {
5493
- const index = this.findClassIndex(val, dataIntervals, dataIntervalBounds);
5494
- c = (index > -1) ? colorClasses[index] : optUnidentifiedColor;
5495
- }
5496
- else if (optUseSingleColor) {
5497
- c = optColor;
5498
- }
5499
- if (c) {
5500
- [r, g, b, a] = c;
5501
- }
5502
- if (optUseDataForOpacity) {
5503
- a = scale(val, rangeMin, rangeMax, 0, 255);
5504
- }
5376
+ const rgb = colorScale(domainVal).rgb();
5377
+ lut[i * 4] = rgb[0];
5378
+ lut[i * 4 + 1] = rgb[1];
5379
+ lut[i * 4 + 2] = rgb[2];
5380
+ lut[i * 4 + 3] = optAlpha;
5381
+ }
5382
+ }
5383
+ for (let i = 0, sampleIndex = (options.useChannelIndex ?? 0); i < arrayLength; i += 4, sampleIndex += samplesPerPixel) {
5384
+ const val = dataArray[sampleIndex];
5385
+ if (this.isInvalid(val, options)) {
5386
+ colorsArray.set(this.getInvalidColor(val, options), i);
5387
+ }
5388
+ else {
5389
+ const t = (val - rangeMin) / rangeSpan;
5390
+ const lutIdx = Math.min(LUT_SIZE - 1, Math.max(0, Math.floor(t * (LUT_SIZE - 1)))) * 4;
5391
+ colorsArray[i] = lut[lutIdx];
5392
+ colorsArray[i + 1] = lut[lutIdx + 1];
5393
+ colorsArray[i + 2] = lut[lutIdx + 2];
5394
+ colorsArray[i + 3] = lut[lutIdx + 3];
5505
5395
  }
5506
5396
  }
5507
- colorsArray[i] = r;
5508
- colorsArray[i + 1] = g;
5509
- colorsArray[i + 2] = b;
5510
- colorsArray[i + 3] = a;
5511
- pixel += samplesPerPixel;
5397
+ return colorsArray;
5398
+ }
5399
+ // 3. FALLBACK LOOP (Categorical Float, Opacity, or Single Color)
5400
+ let sampleIndex = options.useChannelIndex ?? 0;
5401
+ for (let i = 0; i < arrayLength; i += 4) {
5402
+ const val = dataArray[sampleIndex];
5403
+ let color;
5404
+ if ((options.clipLow != null && val <= options.clipLow) || (options.clipHigh != null && val >= options.clipHigh)) {
5405
+ color = options.clippedColor;
5406
+ }
5407
+ else {
5408
+ color = this.calculateSingleColor(val, colorScale, options, optAlpha);
5409
+ }
5410
+ if (options.useDataForOpacity && !this.isInvalid(val, options)) {
5411
+ color[3] = scale(val, rangeMin, rangeMax, 0, 255);
5412
+ }
5413
+ colorsArray.set(color, i);
5414
+ sampleIndex += samplesPerPixel;
5512
5415
  }
5513
5416
  return colorsArray;
5514
5417
  }
5515
- static findClassIndex(number, intervals, bounds) {
5516
- for (let idx = 0; idx < intervals.length; idx += 1) {
5517
- const [min, max] = intervals[idx];
5518
- const [includeEqualMin, includeEqualMax] = bounds[idx];
5519
- if ((includeEqualMin ? number >= min : number > min)
5520
- && (includeEqualMax ? number <= max : number < max)) {
5521
- return idx;
5522
- }
5418
+ static calculateSingleColor(val, colorScale, options, alpha) {
5419
+ if (this.isInvalid(val, options)) {
5420
+ return options.nullColor;
5421
+ }
5422
+ // Color mode priority (most specific wins):
5423
+ // 1. useSingleColor
5424
+ // 2. useColorClasses
5425
+ // 3. useColorsBasedOnValues
5426
+ // 4. useHeatMap
5427
+ // Only the first enabled mode is used.
5428
+ if (options.useSingleColor) {
5429
+ return options.color;
5430
+ }
5431
+ else if (options.useColorClasses) {
5432
+ const index = this.findClassIndex(val, options);
5433
+ return index > -1 ? [...chroma(Array.isArray(options.colorClasses[index][0]) ? chroma(options.colorClasses[index][0]) : options.colorClasses[index][0]).rgb(), alpha] : options.unidentifiedColor;
5434
+ }
5435
+ else if (options.useColorsBasedOnValues) {
5436
+ const match = options.colorsBasedOnValues?.find(([v]) => v === val);
5437
+ return match ? [...chroma(Array.isArray(match[1]) ? chroma(match[1]) : match[1]).rgb(), alpha] : options.unidentifiedColor;
5438
+ }
5439
+ else if (options.useHeatMap) {
5440
+ return [...colorScale(val).rgb(), alpha];
5441
+ }
5442
+ return options.unidentifiedColor;
5443
+ }
5444
+ static findClassIndex(val, options) {
5445
+ if (!options.colorClasses)
5446
+ return -1;
5447
+ for (let i = 0; i < options.colorClasses.length; i++) {
5448
+ const [, [min, max], bounds] = options.colorClasses[i];
5449
+ const [incMin, incMax] = bounds || (i === options.colorClasses.length - 1 ? [true, true] : [true, false]);
5450
+ if ((incMin ? val >= min : val > min) && (incMax ? val <= max : val < max))
5451
+ return i;
5523
5452
  }
5524
5453
  return -1;
5525
5454
  }
@@ -5530,14 +5459,30 @@ class BitmapGenerator {
5530
5459
  }
5531
5460
  return colorsArray;
5532
5461
  }
5533
- static getColorFromChromaType(colorDefinition, alpha = 255) {
5534
- if (!Array.isArray(colorDefinition) || colorDefinition.length !== 4) {
5535
- return [...chroma(colorDefinition).rgb(), alpha];
5462
+ static isInvalid(val, options) {
5463
+ return Number.isNaN(val) || (options.noDataValue !== undefined && val === options.noDataValue);
5464
+ }
5465
+ static getInvalidColor(val, options) {
5466
+ return options.nullColor;
5467
+ }
5468
+ static getMinMax(array, options, samplesPerPixel = 1) {
5469
+ let max = -Infinity, min = Infinity;
5470
+ for (let i = (options.useChannelIndex ?? 0); i < array.length; i += samplesPerPixel) {
5471
+ const val = array[i];
5472
+ if (!this.isInvalid(val, options)) {
5473
+ if (val > max)
5474
+ max = val;
5475
+ if (val < min)
5476
+ min = val;
5477
+ }
5536
5478
  }
5537
- return colorDefinition;
5479
+ return max === -Infinity ? (options.colorScaleValueRange || [0, 255]) : [min, max];
5480
+ }
5481
+ static getColorFromChromaType(color, alpha = 255) {
5482
+ return (!Array.isArray(color) || color.length !== 4) ? [...chroma(color).rgb(), alpha] : color;
5538
5483
  }
5539
- static hasPixelsNoData(pixels, noDataValue) {
5540
- return noDataValue !== undefined && pixels.every((pixel) => pixel === noDataValue);
5484
+ static hasPixelsNoData(pixels, noData) {
5485
+ return noData !== undefined && pixels.every(p => p === noData);
5541
5486
  }
5542
5487
  }
5543
5488