Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions packages/geotiff/src/colorinterp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Photometric } from "@cogeotiff/core";

Check failure on line 1 in packages/geotiff/src/colorinterp.ts

View workflow job for this annotation

GitHub Actions / Lint

format

File content differs from formatting output
import { ExtraSample } from "./ifd.js";

export enum ColorInterp {
UNDEFINED = "undefined",
GRAY = "gray",
RED = "red",
GREEN = "green",
BLUE = "blue",
ALPHA = "alpha",
PALETTE = "palette",
CYAN = "cyan",
MAGENTA = "magenta",
YELLOW = "yellow",
BLACK = "black",
Y = "Y",
Cb = "Cb",
Cr = "Cr",
}

export function inferColorInterpretation({
count,
photometric,
extraSamples,
}: {
count: number;
photometric: Photometric | null;
extraSamples: ExtraSample[] | null;
}): ColorInterp[] {
switch (photometric) {
case null:
return Array<ColorInterp>(count).fill(ColorInterp.UNDEFINED);

case Photometric.MinIsBlack:
return Array<ColorInterp>(count).fill(ColorInterp.GRAY);

case Photometric.Rgb: {
if (count < 3) {
throw new Error(
"RGB photometric interpretation with fewer than 3 bands is not supported.",
);
}
if (count === 3) {
return [ColorInterp.RED, ColorInterp.GREEN, ColorInterp.BLUE];
}
// count >= 4: map extra samples
const extras = (extraSamples ?? []).map((sample) =>
sample === ExtraSample.UnassociatedAlpha ? ColorInterp.ALPHA : ColorInterp.UNDEFINED,
);
return [ColorInterp.RED, ColorInterp.GREEN, ColorInterp.BLUE, ...extras];
}

case Photometric.Palette:
return [ColorInterp.PALETTE];

case Photometric.Separated:
return [ColorInterp.CYAN, ColorInterp.MAGENTA, ColorInterp.YELLOW, ColorInterp.BLACK];

case Photometric.Ycbcr:
return [ColorInterp.Y, ColorInterp.Cb, ColorInterp.Cr];

default:
throw new Error(
`Color interpretation not implemented for photometric: ${photometric}`,
);
}
}
11 changes: 11 additions & 0 deletions packages/geotiff/src/geotiff.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SourceCache, SourceChunk } from "@chunkd/middleware";

Check failure on line 1 in packages/geotiff/src/geotiff.ts

View workflow job for this annotation

GitHub Actions / Lint

assist/source/organizeImports

The imports and exports are not sorted.
import { SourceView } from "@chunkd/source";
import { SourceHttp } from "@chunkd/source-http";
import { SourceMemory } from "@chunkd/source-memory";
Expand All @@ -10,6 +10,8 @@
import { fetchTile } from "./fetch.js";
import type { BandStatistics, GDALMetadata } from "./gdal-metadata.js";
import { parseGDALMetadata } from "./gdal-metadata.js";
import type { ColorInterp } from "./colorinterp.js";
import { inferColorInterpretation } from "./colorinterp.js";
import type { CachedTags, GeoKeyDirectory } from "./ifd.js";
import { extractGeoKeyDirectory, prefetchTags } from "./ifd.js";
import { Overview } from "./overview.js";
Expand Down Expand Up @@ -330,6 +332,15 @@
return this.image.value(TiffTag.SamplesPerPixel) ?? 1;
}

/** The color interpretation of each band in index order. */
get colorInterp(): ColorInterp[] {
return inferColorInterpretation({
count: this.count,
photometric: this.cachedTags.photometric,
extraSamples: this.cachedTags.extraSamples,
});
}

/** Bounding box [minX, minY, maxX, maxY] in the CRS. */
get bbox(): [number, number, number, number] {
return this.image.bbox;
Expand Down
20 changes: 20 additions & 0 deletions packages/geotiff/src/ifd.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,28 @@
import type { TiffImage, TiffTagGeoType, TiffTagType } from "@cogeotiff/core";
import { Predictor, SampleFormat, TiffTag, TiffTagGeo } from "@cogeotiff/core";

/**
* Description of extra components.
*
* Specifies that each pixel has N extra components whose interpretation is
* defined by one of the values listed below. When this field is used, the
* SamplesPerPixel field has a value greater than the PhotometricInterpretation
* field suggests.
*
* @see https://web.archive.org/web/20240329145321/https://www.awaresystems.be/imaging/tiff/tifftags/extrasamples.html
*/
export enum ExtraSample {
Unspecified = 0,
AssociatedAlpha = 1,
UnassociatedAlpha = 2,
}

/** Subset of TIFF tags that we pre-fetch for easier visualization. */
export interface CachedTags {
bitsPerSample: Uint16Array;
colorMap?: Uint16Array; // TiffTagType[TiffTag.ColorMap];
compression: TiffTagType[TiffTag.Compression];
extraSamples: [ExtraSample] | null;
gdalMetadata: TiffTagType[TiffTag.GdalMetadata] | null;
lercParameters: TiffTagType[TiffTag.LercParameters] | null;
modelTiepoint: TiffTagType[TiffTag.ModelTiePoint] | null;
Expand Down Expand Up @@ -33,6 +50,7 @@ export async function prefetchTags(image: TiffImage): Promise<CachedTags> {
const [
bitsPerSample,
colorMap,
extraSamples,
gdalNoData,
gdalMetadata,
lercParameters,
Expand All @@ -49,6 +67,7 @@ export async function prefetchTags(image: TiffImage): Promise<CachedTags> {
] = await Promise.all([
image.fetch(TiffTag.BitsPerSample),
image.fetch(TiffTag.ColorMap),
image.fetch(TiffTag.ExtraSamples),
image.fetch(TiffTag.GdalNoData),
image.fetch(TiffTag.GdalMetadata),
image.fetch(TiffTag.LercParameters),
Expand Down Expand Up @@ -91,6 +110,7 @@ export async function prefetchTags(image: TiffImage): Promise<CachedTags> {
bitsPerSample: new Uint16Array(bitsPerSample),
colorMap: colorMap ? new Uint16Array(colorMap as number[]) : undefined,
compression,
extraSamples: (extraSamples as [ExtraSample] | null) ?? null,
gdalMetadata,
lercParameters,
modelTiepoint,
Expand Down
Loading