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