Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tile offsets / JPEG #26

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ npm i tiff
The library can currently decode greyscale and RGB images (8, 16 or 32 bits).
It supports LZW compression and images with an additional alpha channel.

### Extensions
#### [Adobe Photoshop extensions](./TIFFphotoshop.pdf)

Images compressed with Zlib/deflate algorithm are also supported.
Images compressed with the Zlib/deflate algorithm and JPEG are supported.

## API

Expand Down
Binary file added TIFFphotoshop.pdf
Binary file not shown.
Binary file added img/color8-jpeg.tif
Binary file not shown.
Binary file added img/tiled.tif
Binary file not shown.
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,16 @@
},
"dependencies": {
"iobuffer": "^5.0.2",
"jpeg-js": "^0.4.3",
"pako": "^2.0.3"
},
"devDependencies": {
"@types/jest": "^26.0.20",
"@types/node": "^14.14.20",
"@types/node": "^14.14.22",
"@types/pako": "^1.0.1",
"eslint": "^7.17.0",
"eslint": "^7.18.0",
"eslint-config-cheminfo-typescript": "^8.0.5",
"image-js": "^0.31.4",
"jest": "^26.6.3",
"prettier": "^2.2.1",
"rimraf": "^3.0.2",
Expand Down
14 changes: 14 additions & 0 deletions src/__tests__/decode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ const files: TiffFile[] = [
bitsPerSample: 8,
components: 3,
},
{
name: 'color8-jpeg.tif',
width: 160,
height: 120,
bitsPerSample: 8,
components: 3,
},
{
name: 'color8-alpha.tif',
width: 800,
Expand Down Expand Up @@ -129,6 +136,13 @@ const files: TiffFile[] = [
components: 4,
alpha: true,
},
{
name: 'tiled.tif',
width: 1,
height: 1,
bitsPerSample: 8,
components: 1,
},
];
const cases = files.map(
(file) => [file.name, file, readImage(file.name)] as const,
Expand Down
14 changes: 14 additions & 0 deletions src/colorConversion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import TiffIfd from './tiffIfd';

export function convertWhiteIsZero(ifd: TiffIfd) {
// WhiteIsZero: we invert the values
const bitDepth = ifd.bitsPerSample;
const maxValue = Math.pow(2, bitDepth) - 1;
for (let i = 0; i < ifd.data.length; i++) {
ifd.data[i] = maxValue - ifd.data[i];
}
}

export function convertYCbCr(ifd: TiffIfd) {
throw new Error('TODO: convert YCbCr');
}
24 changes: 24 additions & 0 deletions src/photoshopJpeg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { decode } from 'jpeg-js';

export function decompressPhotoshopJpeg(
stripData: DataView,
jpegTables: number[] | undefined,
): DataView {
console.log(jpegTables);
// if (jpegTables) {
// throw new Error('Images compressed with JPEGTables are not supported');
// }
const decoded = decode(
new Uint8Array(
stripData.buffer,
stripData.byteOffset,
stripData.byteLength,
),
{
colorTransform: false,
useTArray: true,
formatAsRGBA: false,
},
);
throw new Error('todo: decompress JPEG');
}
111 changes: 111 additions & 0 deletions src/readStripData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import type TIFFDecoder from './tiffDecoder';
import TiffIfd from './tiffIfd';
import { DataArray } from './types';
import { decompressData, getDataArray, unsupported } from './utils';

export function readStripData(decoder: TIFFDecoder, ifd: TiffIfd) {
const width = ifd.width;
const height = ifd.height;

const bitDepth = ifd.bitsPerSample;
const sampleFormat = ifd.sampleFormat;
const size = width * height * ifd.samplesPerPixel;
const data = getDataArray(size, bitDepth, sampleFormat);

const rowsPerStrip = ifd.rowsPerStrip as number;
const maxPixels = rowsPerStrip * width * ifd.samplesPerPixel;
const stripOffsets = ifd.stripOffsets as number[];
const stripByteCounts = ifd.stripByteCounts as number[];

let remainingPixels = size;
let pixel = 0;
for (let i = 0; i < stripOffsets.length; i++) {
const stripData = new DataView(
decoder.buffer,
stripOffsets[i],
stripByteCounts[i],
);

// Last strip can be smaller
const length = remainingPixels > maxPixels ? maxPixels : remainingPixels;
remainingPixels -= length;

const dataToFill = decompressData(stripData, ifd);

pixel = fillUncompressed(
decoder,
bitDepth,
sampleFormat,
data,
dataToFill,
pixel,
length,
);
}

ifd.data = data;
}

function fillUncompressed(
decoder: TIFFDecoder,
bitDepth: number,
sampleFormat: number,
data: DataArray,
stripData: DataView,
pixel: number,
length: number,
): number {
if (bitDepth === 8) {
return fill8bit(data, stripData, pixel, length);
} else if (bitDepth === 16) {
return fill16bit(data, stripData, pixel, length, decoder.isLittleEndian());
} else if (bitDepth === 32 && sampleFormat === 3) {
return fillFloat32(
data,
stripData,
pixel,
length,
decoder.isLittleEndian(),
);
} else {
throw unsupported('bitDepth', bitDepth);
}
}

function fill8bit(
dataTo: DataArray,
dataFrom: DataView,
index: number,
length: number,
): number {
for (let i = 0; i < length; i++) {
dataTo[index++] = dataFrom.getUint8(i);
}
return index;
}

function fill16bit(
dataTo: DataArray,
dataFrom: DataView,
index: number,
length: number,
littleEndian: boolean,
): number {
for (let i = 0; i < length * 2; i += 2) {
dataTo[index++] = dataFrom.getUint16(i, littleEndian);
}
return index;
}

function fillFloat32(
dataTo: DataArray,
dataFrom: DataView,
index: number,
length: number,
littleEndian: boolean,
): number {
for (let i = 0; i < length * 4; i += 4) {
dataTo[index++] = dataFrom.getFloat32(i, littleEndian);
}
return index;
}
31 changes: 31 additions & 0 deletions src/readTileData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type TIFFDecoder from './tiffDecoder';
import TiffIfd from './tiffIfd';
import { decompressData, getDataArray } from './utils';

export function readTileData(decoder: TIFFDecoder, ifd: TiffIfd) {
const width = ifd.width;
const height = ifd.height;

const bitDepth = ifd.bitsPerSample;
const sampleFormat = ifd.sampleFormat;
const size = width * height * ifd.samplesPerPixel;
const data = getDataArray(size, bitDepth, sampleFormat);

const tileLength = ifd.tileLength as number;
const tileWidth = ifd.tileWidth as number;
const tileByteCounts = ifd.tileByteCounts as number[];
const tileOffsets = ifd.tileOffsets as number[];

// Iterate on each tile
for (let i = 0; i < tileOffsets.length; i++) {
const tileData = new DataView(
decoder.buffer,
tileOffsets[i],
tileByteCounts[i],
);

const dataToFill = decompressData(tileData, ifd.compression);

console.log(dataToFill);
}
}
Loading