Repository: image-js/tiff
Branch: main
Commit: a3a91a905c3e
Files: 76
Total size: 97.9 MB
Directory structure:
gitextract_4e2k9nc9/
├── .github/
│ └── workflows/
│ ├── nodejs.yml
│ ├── release.yml
│ └── typedoc.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── eslint.config.js
├── img/
│ ├── array-sample-format.tif
│ ├── black.tif
│ ├── bw1bit.tif
│ ├── bwCross.tif
│ ├── cells.tif
│ ├── color-1px.tif
│ ├── color-5x5-deflate.tif
│ ├── color-5x5-lzw.tif
│ ├── color-5x5.tif
│ ├── color-alpha-2x2.tif
│ ├── color-alpha-5x5-lzw.tif
│ ├── color-alpha-5x5.tif
│ ├── color16-lzw.tif
│ ├── color16-multi.tif
│ ├── color16.tif
│ ├── color8-alpha.tif
│ ├── color8-lzw.tif
│ ├── color8-multi.tif
│ ├── color8.tif
│ ├── crosshair_tiled.tif
│ ├── dog.tiff
│ ├── female.tiff
│ ├── float32.tif
│ ├── float64.tif
│ ├── grey16-multi.tif
│ ├── grey16.tif
│ ├── grey8-lzw.tif
│ ├── grey8-multi.tif
│ ├── grey8.tif
│ ├── greya16.tif
│ ├── greya32.tif
│ ├── image-deflate.tif
│ ├── image-lzw.tif
│ ├── image.tif
│ ├── jellybeans.tiff
│ ├── palette.tif
│ ├── stack.tif
│ ├── tile_rgb_deflate.tif
│ ├── tiled.tif
│ └── whiteIsZero.tif
├── package.json
├── src/
│ ├── .npmignore
│ ├── __tests__/
│ │ ├── data/
│ │ │ ├── 1.strip
│ │ │ ├── 173.strip
│ │ │ └── 174.strip
│ │ ├── decode.lzw.test.ts
│ │ ├── decode.test.ts
│ │ ├── is_multi_page.test.ts
│ │ ├── lzw.test.ts
│ │ └── page_count.test.ts
│ ├── hacks.ts
│ ├── horizontal_differencing.ts
│ ├── ifd.ts
│ ├── ifd_value.ts
│ ├── index.ts
│ ├── lzw.ts
│ ├── tags/
│ │ ├── exif.ts
│ │ ├── gps.ts
│ │ └── standard.ts
│ ├── tiff_decoder.ts
│ ├── tiff_ifd.ts
│ ├── types.ts
│ └── zlib.ts
├── tsconfig.build.json
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/workflows/nodejs.yml
================================================
name: Node.js CI
on:
push:
branches:
- main
pull_request:
jobs:
nodejs:
# Documentation: https://github.com/zakodium/workflows#nodejs-ci
uses: zakodium/workflows/.github/workflows/nodejs.yml@nodejs-v1
with:
lint-check-types: true
================================================
FILE: .github/workflows/release.yml
================================================
name: Release
on:
push:
branches:
- main
jobs:
release:
# Documentation: https://github.com/zakodium/workflows#release
uses: zakodium/workflows/.github/workflows/release.yml@release-v1
with:
npm: true
secrets:
github-token: ${{ secrets.BOT_TOKEN }}
npm-token: ${{ secrets.NPM_BOT_TOKEN }}
================================================
FILE: .github/workflows/typedoc.yml
================================================
name: TypeDoc
on:
workflow_dispatch:
release:
types: [published]
jobs:
typedoc:
# Documentation: https://github.com/zakodium/workflows#typedoc
uses: zakodium/workflows/.github/workflows/typedoc.yml@typedoc-v1
with:
entry: 'src/index.ts'
secrets:
github-token: ${{ secrets.BOT_TOKEN }}
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Locally Packged Module
*.tgz
# Optional REPL history
.node_repl_history
lib
lib-esm
.idea/
.DS_Store
================================================
FILE: .npmrc
================================================
ignore-scripts=true
================================================
FILE: .prettierignore
================================================
CHANGELOG.md
================================================
FILE: .prettierrc.json
================================================
{
"arrowParens": "always",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all"
}
================================================
FILE: CHANGELOG.md
================================================
# Changelog
## [7.1.3](https://github.com/image-js/tiff/compare/v7.1.2...v7.1.3) (2025-11-25)
### Bug Fixes
* use fflate instead of pako ([#81](https://github.com/image-js/tiff/issues/81)) ([a6ea694](https://github.com/image-js/tiff/commit/a6ea694a4eef06c8d3b856f2d9dce3d3e2510a0f))
## [7.1.2](https://github.com/image-js/tiff/compare/v7.1.1...v7.1.2) (2025-10-07)
### Bug Fixes
* fix bug with rowsPerStrip ([#79](https://github.com/image-js/tiff/issues/79)) ([7abbe97](https://github.com/image-js/tiff/commit/7abbe97dcba8ffcc581977c512bfeaeca000a79d))
## [7.1.1](https://github.com/image-js/tiff/compare/v7.1.0...v7.1.1) (2025-08-13)
### Bug Fixes
* exports ([#75](https://github.com/image-js/tiff/issues/75)) ([c1f2448](https://github.com/image-js/tiff/commit/c1f2448bf460aea86aca6b6c41414713ee5b50e4))
## [7.1.0](https://github.com/image-js/tiff/compare/v7.0.0...v7.1.0) (2025-07-23)
### Features
* add support for 1 bit depth bw files ([#73](https://github.com/image-js/tiff/issues/73)) ([f101c2b](https://github.com/image-js/tiff/commit/f101c2b477c92fd2c4542b5a2452ed4cc5361a65))
### Performance Improvements
* improve performance of data decoding ([#70](https://github.com/image-js/tiff/issues/70)) ([be54e07](https://github.com/image-js/tiff/commit/be54e0790216b87b269dbcfe4671c48ac512d465))
## [7.0.0](https://github.com/image-js/tiff/compare/v6.2.0...v7.0.0) (2025-06-15)
### ⚠ BREAKING CHANGES
* migrate to ESM ([#68](https://github.com/image-js/tiff/issues/68))
### Code Refactoring
* migrate to ESM ([#68](https://github.com/image-js/tiff/issues/68)) ([3345547](https://github.com/image-js/tiff/commit/3345547d0665da124fdf9f3a61cdf99442bd4040))
## [6.2.0](https://github.com/image-js/tiff/compare/v6.1.1...v6.2.0) (2025-03-06)
### Features
* add float64 support ([#59](https://github.com/image-js/tiff/issues/59)) ([a164636](https://github.com/image-js/tiff/commit/a1646369615fe775fc0f3bcd166cbf9ed816138b))
* add tiled image support ([#62](https://github.com/image-js/tiff/issues/62)) ([8b1c922](https://github.com/image-js/tiff/commit/8b1c9220343831305967a220b0a9d39e0855aca7))
## [6.1.1](https://github.com/image-js/tiff/compare/v6.1.0...v6.1.1) (2024-09-12)
### Bug Fixes
* support Deflate compression code ([#56](https://github.com/image-js/tiff/issues/56)) ([d965602](https://github.com/image-js/tiff/commit/d965602d6f9f2857a84732298947b5fbeb75009b))
## [6.1.0](https://github.com/image-js/tiff/compare/v6.0.0...v6.1.0) (2024-05-21)
### Features
* export map of tag ids to names ([#53](https://github.com/image-js/tiff/issues/53)) ([9774e80](https://github.com/image-js/tiff/commit/9774e80c7d9d9d9db4fc05795701ec20961ad46d))
## [6.0.0](https://github.com/image-js/tiff/compare/v5.0.3...v6.0.0) (2024-04-08)
### ⚠ BREAKING CHANGES
* removed `firstImage` option, use `pages` option instead.
### Features
* add `pages` option ([#47](https://github.com/image-js/tiff/issues/47)) ([f0f0bac](https://github.com/image-js/tiff/commit/f0f0bac57a1f8790a9866fb4e476ac0a6e86b345)), closes [#37](https://github.com/image-js/tiff/issues/37) [#46](https://github.com/image-js/tiff/issues/46)
### Bug Fixes
* Support array sample format ([#51](https://github.com/image-js/tiff/issues/51)) ([42d778b](https://github.com/image-js/tiff/commit/42d778b333764b6e1b74b9af5718416991623fda))
### [5.0.3](https://www.github.com/image-js/tiff/compare/v5.0.2...v5.0.3) (2021-11-05)
### Bug Fixes
* support some malformed images without StripByteCounts ([56058a9](https://www.github.com/image-js/tiff/commit/56058a99c9e0b1e7e129b4150e4a705061555e20))
### [5.0.2](https://www.github.com/image-js/tiff/compare/v5.0.1...v5.0.2) (2021-10-31)
### Bug Fixes
* correctly decode images from pooled buffers ([fcbbc34](https://www.github.com/image-js/tiff/commit/fcbbc348028b97ee6f99186628fe11c8135a6e6a))
* make LZW more robust ([72a6180](https://www.github.com/image-js/tiff/commit/72a61809b8399b4d30d3657a248de2e7a2d2cdd6))
### [5.0.1](https://www.github.com/image-js/tiff/compare/v5.0.0...v5.0.1) (2021-10-12)
### Bug Fixes
* set TypeScript target to ES2020 ([#33](https://www.github.com/image-js/tiff/issues/33)) ([30e18e9](https://www.github.com/image-js/tiff/commit/30e18e956859bf64dc836ae53da3204cfefb9851))
## [5.0.0](https://www.github.com/image-js/tiff/compare/v4.3.0...v5.0.0) (2021-07-06)
### ⚠ BREAKING CHANGES
* Removed support for Node.js 10.
### Miscellaneous Chores
* stop testing on Node.js 10 ([220822f](https://www.github.com/image-js/tiff/commit/220822f008a3b8c6b047f4f78d2f01b202cda8b0))
## [4.3.0](https://github.com/image-js/tiff/compare/v4.2.0...v4.3.0) (2020-12-03)
### Features
* add support for Zlib/deflate compression ([7d3a04c](https://github.com/image-js/tiff/commit/7d3a04c04c44d75373ccd6c7928cb69b3b725077))
# [4.2.0](https://github.com/image-js/tiff/compare/v4.1.3...v4.2.0) (2020-08-21)
### Features
* add support for alpha channel and compressed 16-bit images ([5f2e612](https://github.com/image-js/tiff/commit/5f2e6128ed7b096290c1ebe0b861a61d3849b255))
## [4.1.3](https://github.com/image-js/tiff/compare/v4.1.2...v4.1.3) (2020-08-07)
### Bug Fixes
* support images that do not define `samplesPerPixel` ([2c2587b](https://github.com/image-js/tiff/commit/2c2587b4307ef22225d38f5d24968c678bf6fa54))
## [4.1.2](https://github.com/image-js/tiff/compare/v4.1.1...v4.1.2) (2020-08-06)
### Bug Fixes
* really correct decoding of RGB images ([5595c53](https://github.com/image-js/tiff/commit/5595c539469dfd3e8e2b617511f8327b94c86a77))
## [4.1.1](https://github.com/image-js/tiff/compare/v4.1.0...v4.1.1) (2020-08-06)
### Bug Fixes
* correctly support RGB images ([d546610](https://github.com/image-js/tiff/commit/d5466101845fd90c8a5225857ff0d7216d51b88d))
# [4.1.0](https://github.com/image-js/tiff/compare/v4.0.0...v4.1.0) (2020-08-04)
### Features
* support LZW compression ([20fbb50](https://github.com/image-js/tiff/commit/20fbb501b8855489e91ae22f519760c2112aae68))
# [4.0.0](https://github.com/image-js/tiff/compare/v3.0.1...v4.0.0) (2020-01-23)
### chore
* stop supporting Node.js 6 and 8 ([1156f52](https://github.com/image-js/tiff/commit/1156f52aaa4210dfb9ee2fef052b775298b86b81))
### Features
* add support for palette images ([d31413b](https://github.com/image-js/tiff/commit/d31413b09ed8f589107f7c1ffae06d5ea2e22b49))
### BREAKING CHANGES
* Node.js 6 and 8 are no longer supported.
<a name="3.0.1"></a>
## [3.0.1](https://github.com/image-js/tiff/compare/v3.0.0...v3.0.1) (2018-09-12)
### Bug Fixes
* support WhiteIsZero ([bf7a4ca](https://github.com/image-js/tiff/commit/bf7a4ca)), closes [#14](https://github.com/image-js/tiff/issues/14)
<a name="3.0.0"></a>
# [3.0.0](https://github.com/image-js/tiff/compare/v2.1.0...v3.0.0) (2017-10-25)
### Chores
* remove Node 4 from travis ([5c743d2](https://github.com/image-js/tiff/commit/5c743d2))
### Features
* add pageCount and isMultiPage functions ([7dac89f](https://github.com/image-js/tiff/commit/7dac89f))
### BREAKING CHANGES
* Stop support for Node 4
<a name="2.1.0"></a>
# [2.1.0](https://github.com/image-js/tiff/compare/v2.0.1...v2.1.0) (2016-11-05)
### Features
* add support for uncompressed RGB data ([b3ffff7](https://github.com/image-js/tiff/commit/b3ffff7))
<a name="2.0.1"></a>
## [2.0.1](https://github.com/image-js/tiff/compare/v2.0.0...v2.0.1) (2016-09-20)
### Bug Fixes
* return decimal numbers for rational types ([c3bad6c](https://github.com/image-js/tiff/commit/c3bad6c))
<a name="2.0.0"></a>
# [2.0.0](https://github.com/image-js/tiff/compare/v1.1.1...v2.0.0) (2016-09-20)
### Code Refactoring
* hide the decoder class behind a decode function ([78603ff](https://github.com/image-js/tiff/commit/78603ff))
### Features
* add support for decoding EXIF and GPS IFDs ([b2766a5](https://github.com/image-js/tiff/commit/b2766a5))
* allow to pass iobuffer options to the decoder ([97f0f8e](https://github.com/image-js/tiff/commit/97f0f8e))
### BREAKING CHANGES
* The API has changed. Use `tiff.decode()` instead of `TIFFDecoder`.
<a name="1.1.1"></a>
## [1.1.1](https://github.com/image-js/tiff/compare/v1.1.0...v1.1.1) (2016-04-25)
### Bug Fixes
* default value for compression field is 1 ([14c13a4](https://github.com/image-js/tiff/commit/14c13a4))
<a name="1.1.0"></a>
# [1.1.0](https://github.com/image-js/tiff/compare/v1.0.0...v1.1.0) (2015-12-04)
<a name="1.0.0"></a>
# [1.0.0](https://github.com/image-js/tiff/compare/v0.0.2...v1.0.0) (2015-11-23)
<a name="0.0.2"></a>
## [0.0.2](https://github.com/image-js/tiff/compare/v0.0.1...v0.0.2) (2015-09-20)
<a name="0.0.1"></a>
## 0.0.1 (2015-09-19)
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 Michaël Zasso
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<h3 align="center">
<a href="https://www.zakodium.com">
<img src="https://www.zakodium.com/brand/zakodium-logo-white.svg" width="50" alt="Zakodium logo" />
</a>
<p>
Maintained by <a href="https://www.zakodium.com">Zakodium</a>
</p>
</h3>
# tiff
[](https://www.npmjs.com/package/tiff)
[](https://www.npmjs.com/package/tiff)
[](https://codecov.io/gh/image-js/tiff)
[](https://github.com/image-js/tiff/blob/main/LICENSE)
TIFF image decoder written entirely in JavaScript.
## Installation
```console
npm install tiff
```
## Compatibility
### [TIFF standard](./TIFF6.pdf)
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
Images compressed with Zlib/deflate algorithm are also supported.
## API
### [Complete API documentation](https://image-js.github.io/tiff/)
### `tiff.decode(data[, options])`
Decodes the file and returns TIFF IFDs.
#### IFD object
Each decoded image is stored in an `IFD`.
##### IFD#data
The `data` property is a Typed Array containing the pixel data. It is a
`Uint8Array` for 8bit images, a `Uint16Array` for 16bit images and a
`Float32Array` for 32bit images.
##### Other properties of IFD
- `size` - number of pixels
- `width` - number of columns
- `height` - number of rows
- `bitsPerSample` - bit depth
- `alpha` - `true` if the image has an additional alpha channel
- `xResolution`
- `yResolution`
- `resolutionUnit`
### `tiff.pageCount(data)`
Returns the number of IFDs (pages) in the file.
### `tiff.isMultiPage(data)`
Returns true if the file has 2 or more IFDs (pages) and false if it has 1.
This is slightly more efficient than calling `pageCount()` if all you need to
know is whether the file has multiple pages or not.
## License
[MIT](./LICENSE)
================================================
FILE: eslint.config.js
================================================
import { defineConfig, globalIgnores } from 'eslint/config';
import cheminfo from 'eslint-config-cheminfo-typescript';
export default defineConfig(globalIgnores(['coverage', 'lib']), cheminfo);
================================================
FILE: img/black.tif
================================================
[File too large to display: 47.2 MB]
================================================
FILE: img/image-deflate.tif
================================================
[File too large to display: 13.7 MB]
================================================
FILE: img/image.tif
================================================
[File too large to display: 22.7 MB]
================================================
FILE: img/tiled.tif
================================================
[File too large to display: 14.3 MB]
================================================
FILE: package.json
================================================
{
"name": "tiff",
"version": "7.1.3",
"license": "MIT",
"description": "TIFF image decoder written entirely in JavaScript",
"author": "Michaël Zasso",
"type": "module",
"exports": {
".": "./lib/index.js"
},
"files": [
"lib",
"src"
],
"scripts": {
"check-types": "tsc --noEmit",
"clean": "rimraf coverage lib",
"eslint": "eslint .",
"eslint-fix": "eslint . --fix",
"prepack": "npm run tsc",
"prettier": "prettier --check .",
"prettier-write": "prettier --write .",
"test": "npm run test-only && npm run check-types && npm run eslint && npm run prettier",
"test-only": "vitest run --coverage",
"tsc": "npm run clean && npm run tsc-build",
"tsc-build": "tsc --project tsconfig.build.json"
},
"dependencies": {
"fflate": "^0.8.2",
"iobuffer": "^6.0.1"
},
"devDependencies": {
"@types/node": "^25.0.3",
"@vitest/coverage-v8": "^4.0.16",
"@zakodium/tsconfig": "^1.0.2",
"eslint": "^9.39.2",
"eslint-config-cheminfo-typescript": "^21.0.1",
"prettier": "^3.7.4",
"rimraf": "^6.1.2",
"typescript": "^5.9.3",
"vitest": "^4.0.16"
},
"repository": {
"type": "git",
"url": "git+https://github.com/image-js/tiff.git"
},
"bugs": {
"url": "https://github.com/image-js/tiff/issues"
},
"homepage": "https://image-js.github.io/tiff/"
}
================================================
FILE: src/.npmignore
================================================
__tests__
.npmignore
================================================
FILE: src/__tests__/data/1.strip
================================================
8`@$
BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/LfS9m7NgS}?PhT:%GRiTe6OTjU:VWVkUv_XlV;%gZmVeo\nW;w^oW
================================================
FILE: src/__tests__/data/173.strip
================================================
8`@$
BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/H9>fÀ373%<RiTOTjQ:t,j:v_Ukv%1趘e/l\f*Ew۩W©4׀_F1.M|cnNG-'D0ќfIXژ;%C5k'
\vBxm;z^e<ז^ΣUyWY?wƗ쥟C9C/+ALl
================================================
FILE: src/__tests__/data/174.strip
================================================
8`@$
BaPd6DbQ8V-FcQv=HdR9$M'JeRd]/G9VfgSxt<Ϧ:%QTd^MTjRJ|F˪VSXlV;%gM-v:Z9t^hzU'nܯQUrRkC2r<Fg9b'MR=/Y"4=zp{˵hNGQgI2Θra JswXWS:.ry]ṇcW~_O
================================================
FILE: src/__tests__/decode.lzw.test.ts
================================================
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
import { decode } from '../index.ts';
// We can decompress images using 'convert' from imagemagick
// convert image-lzw.tif -define colorspace:auto-grayscale=false -type truecolor image.tif
describe('decode lzw', () => {
it('image', { timeout: 30_000 }, () => {
const lzwBuffer = readFileSync(
join(import.meta.dirname, '../../img/image-lzw.tif'),
);
const imageLzw = decode(lzwBuffer);
const buffer = readFileSync(
join(import.meta.dirname, '../../img/image.tif'),
);
const image = decode(buffer);
expect(
dataEqual(imageLzw[0].data as Uint8Array, image[0].data as Uint8Array),
).toBe(true);
});
it('color8', () => {
const lzwBuffer = readFileSync(
join(import.meta.dirname, '../../img/color8-lzw.tif'),
);
const imageLzw = decode(lzwBuffer);
const buffer = readFileSync(
join(import.meta.dirname, '../../img/color8.tif'),
);
const image = decode(buffer);
expect(
dataEqual(imageLzw[0].data as Uint8Array, image[0].data as Uint8Array),
).toBe(true);
});
});
function dataEqual(data1: Uint8Array, data2: Uint8Array): boolean {
if (data1.length !== data2.length) return false;
for (let i = 0; i < data1.length; i++) {
if (data1[i] !== data2[i]) {
return false;
}
}
return true;
}
================================================
FILE: src/__tests__/decode.test.ts
================================================
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { expect, test } from 'vitest';
import { decode } from '../index.ts';
function readImage(file: string): Buffer {
return readFileSync(join(import.meta.dirname, '../../img', file));
}
interface TiffFile {
name: string;
width: number;
height: number;
bitsPerSample: number;
components: number;
alpha?: boolean;
pages?: number;
}
const files: TiffFile[] = [
{
name: 'color-1px.tif',
width: 1,
height: 1,
bitsPerSample: 8,
components: 3,
},
{
name: 'image-lzw.tif',
width: 2590,
height: 3062,
bitsPerSample: 8,
components: 3,
},
{
name: 'image-deflate.tif',
width: 5100,
height: 3434,
bitsPerSample: 8,
components: 3,
},
{
name: 'color8.tif',
width: 160,
height: 120,
bitsPerSample: 8,
components: 3,
},
{
name: 'color8-lzw.tif',
width: 160,
height: 120,
bitsPerSample: 8,
components: 3,
},
{
name: 'color8-alpha.tif',
width: 800,
height: 600,
bitsPerSample: 8,
components: 4,
alpha: true,
},
{
name: 'color16.tif',
width: 160,
height: 120,
bitsPerSample: 16,
components: 3,
},
{
name: 'color16-lzw.tif',
width: 160,
height: 120,
bitsPerSample: 16,
components: 3,
},
{ name: 'grey8.tif', width: 30, height: 90, bitsPerSample: 8, components: 1 },
{
name: 'grey8-lzw.tif',
width: 30,
height: 90,
bitsPerSample: 8,
components: 1,
},
{
name: 'grey16.tif',
width: 30,
height: 90,
bitsPerSample: 16,
components: 1,
},
{
name: 'whiteIsZero.tif',
width: 1248,
height: 1248,
bitsPerSample: 16,
components: 1,
},
{
name: 'cells.tif',
width: 2048,
height: 2048,
bitsPerSample: 16,
components: 1,
},
{
name: 'color-5x5.tif',
width: 5,
height: 5,
bitsPerSample: 8,
components: 3,
},
{
name: 'color-5x5-lzw.tif',
width: 5,
height: 5,
bitsPerSample: 8,
components: 3,
},
{
name: 'color-5x5-deflate.tif',
width: 5,
height: 5,
bitsPerSample: 8,
components: 3,
},
{
name: 'color-alpha-2x2.tif',
width: 2,
height: 2,
bitsPerSample: 8,
components: 4,
alpha: true,
},
{
name: 'color-alpha-5x5.tif',
width: 5,
height: 5,
bitsPerSample: 8,
components: 4,
alpha: true,
},
{
name: 'color-alpha-5x5-lzw.tif',
width: 5,
height: 5,
bitsPerSample: 8,
components: 4,
alpha: true,
},
{
name: 'float32.tif',
width: 141,
height: 125,
bitsPerSample: 32,
components: 1,
alpha: false,
},
{
name: 'float64.tif',
width: 851,
height: 338,
bitsPerSample: 64,
components: 1,
alpha: false,
},
{
name: 'black.tif',
width: 9192,
height: 2690,
bitsPerSample: 16,
components: 1,
alpha: false,
},
{
name: 'array-sample-format.tif',
width: 2,
height: 2,
bitsPerSample: 32,
components: 3,
alpha: false,
},
{
name: 'tiled.tif',
width: 2501,
height: 2001,
bitsPerSample: 32,
components: 1,
alpha: false,
},
{
name: 'bw1bit.tif',
width: 2,
height: 2,
bitsPerSample: 1,
components: 1,
alpha: false,
},
{
name: 'bwCross.tif',
width: 10,
height: 10,
bitsPerSample: 1,
components: 1,
alpha: false,
},
{
name: 'jellybeans.tiff',
width: 256,
height: 256,
bitsPerSample: 8,
components: 3,
alpha: false,
},
{
name: 'female.tiff',
width: 256,
height: 256,
bitsPerSample: 8,
components: 3,
alpha: false,
},
// Checks only the first frame of the image.
{
name: 'dog.tiff',
width: 16,
height: 16,
bitsPerSample: 1,
components: 4,
alpha: true,
pages: 8,
},
];
const cases = files.map(
(file) => [file.name, file, readImage(file.name)] as const,
);
const stack = readImage('stack.tif');
test.each(cases)(
'should decode %s',
{ timeout: 30_000 },
(name, file, image) => {
const result = decode(image);
expect(result).toHaveLength(file.pages ?? 1);
const { data, bitsPerSample, width, height, components, alpha } = result[0];
expect(width).toBe(file.width);
expect(height).toBe(file.height);
expect(components).toBe(file.components);
expect(bitsPerSample).toBe(file.bitsPerSample);
const size = file.width * file.height * file.components;
expect(data).toHaveLength(size);
expect(alpha).toBe(Boolean(file.alpha));
},
);
// prettier-ignore
const expectedRgb8BitData = Uint8Array.from([
255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 0, 0, 0,
255, 0, 0, 0, 255, 0, 0, 0, 255, 128, 128, 128, 128, 128, 128,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
0, 255, 255, 255, 0, 255, 255, 255, 0, 128, 128, 128, 128, 128, 128,
0, 255, 255, 255, 0, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0,
]);
test('should decode RGB 8bit data', () => {
const [result] = decode(readImage('color-5x5.tif'));
expect(result.data).toStrictEqual(expectedRgb8BitData);
});
test('should decode RGB 8bit data with LZW compression', () => {
const [result] = decode(readImage('color-5x5-lzw.tif'));
expect(result.data).toStrictEqual(expectedRgb8BitData);
});
// prettier-ignore
const expectedRgb8BitAlphaData = Uint8Array.from([
0, 0, 0, 0, 0, 255, 0, 54, 0, 0, 255, 102, 0, 0, 0, 152, 0, 0, 0, 203,
255, 0, 0, 31, 0, 255, 0, 78, 0, 0, 255, 255, 128, 128, 128, 255, 128, 128, 128, 255,
255, 255, 255, 54, 255, 255, 255, 255, 255, 255, 255, 127, 255, 255, 255, 255, 255, 255, 255, 255,
0, 255, 255, 78, 255, 0, 255, 255, 255, 255, 0, 255, 128, 128, 128, 177, 128, 128, 128, 255,
0, 255, 255, 102, 255, 0, 255, 255, 255, 255, 0, 255, 0, 0, 0, 255, 0, 0, 0, 229
]);
test('should decode RGB 8bit data with pre-multiplied alpha', () => {
const [result] = decode(readImage('color-alpha-5x5.tif'));
expect(result.data).toStrictEqual(expectedRgb8BitAlphaData);
});
test('should decode RGB 8bit data with pre-multiplied alpha and LZW compression', () => {
const [result] = decode(readImage('color-alpha-5x5-lzw.tif'));
expect(result.data).toStrictEqual(expectedRgb8BitAlphaData);
});
test('should decode RGB 8bit data with pre-multiplied alpha and lost precision', () => {
// prettier-ignore
const expectedData = Uint8Array.from([
255, 0, 0, 6, 255, 0, 0, 6,
128, 0, 0, 6, 128, 0, 0, 6,
]);
const [result] = decode(readImage('color-alpha-2x2.tif'));
expect(result.data).toStrictEqual(expectedData);
});
test('should decode with onlyFirst', () => {
const result = decode(readImage('grey8.tif'), { pages: [0] });
expect(result[0]).toHaveProperty('data');
});
test('should omit data', () => {
const result = decode(readImage('grey8.tif'), { ignoreImageData: true });
expect(result[0].data).toStrictEqual(new Uint8Array());
});
test('should read exif data', () => {
const result = decode(readImage('grey8.tif'), {
pages: [0],
ignoreImageData: true,
});
// @ts-expect-error We know exif is defined.
expect(result[0].exif.map).toStrictEqual({
ColorSpace: 65535,
PixelXDimension: 30,
PixelYDimension: 90,
});
});
test('should decode stacks', () => {
const decoded = decode(stack);
expect(decoded).toHaveLength(10);
for (const image of decoded) {
expect(image.width).toBe(128);
expect(image.height).toBe(128);
}
});
test('specify pages to decode', () => {
const decoded = decode(stack, { pages: [0, 2, 4, 6, 8] });
expect(decoded).toHaveLength(5);
for (const image of decoded) {
expect(image.width).toBe(128);
expect(image.height).toBe(128);
}
});
test('should throw if pages invalid', () => {
expect(() => decode(stack, { pages: [-1] })).toThrowError(
'Index -1 is invalid. Must be a positive integer.',
);
expect(() => decode(stack, { pages: [0.5] })).toThrowError(
'Index 0.5 is invalid. Must be a positive integer.',
);
expect(() => decode(stack, { pages: [20] })).toThrowError(
'Index 20 is out of bounds. The stack only contains 10 images.',
);
});
test('should decode palette', () => {
const decoded = decode(readImage('palette.tif'));
expect(decoded).toHaveLength(1);
const { palette } = decoded[0];
expect(palette).toHaveLength(256);
// @ts-expect-error We know palette is defined.
expect(palette[0]).toStrictEqual([65535, 0, 0]);
});
test('should decode image compressed with deflate algorithm', () => {
const decoded = decode(readImage('tile_rgb_deflate.tif'));
expect(decoded).toHaveLength(1);
expect(decoded[0]).toMatchObject({
alpha: false,
bitsPerSample: 16,
components: 3,
compression: 8,
width: 128,
height: 128,
});
});
test('should decode basic 2x2 1-bit image', () => {
const decoded = decode(readImage('bw1bit.tif'));
expect(decoded).toHaveLength(1);
expect(decoded[0]).toMatchObject({
alpha: false,
bitsPerSample: 1,
components: 1,
compression: 1,
width: 2,
height: 2,
});
expect(decoded[0].data).toStrictEqual(new Uint8Array([1, 0, 0, 1]));
});
test('should decode 10x10 1-bit image as a cross', () => {
const decoded = decode(readImage('bwCross.tif'));
expect(decoded).toHaveLength(1);
expect(decoded[0]).toMatchObject({
alpha: false,
bitsPerSample: 1,
components: 1,
compression: 1,
width: 10,
height: 10,
});
expect(decoded[0].data).toStrictEqual(
new Uint8Array(
[
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 1, 0, 0, 0, 0],
].flat(),
),
);
});
test('should decode 15x15 image with tile data', () => {
const decoded = decode(readImage('crosshair_tiled.tif'));
expect(decoded).toHaveLength(1);
expect(decoded[0]).toMatchObject({
alpha: false,
bitsPerSample: 1,
components: 1,
compression: 1,
width: 15,
height: 15,
});
expect(decoded[0].data).toStrictEqual(
new Uint8Array(
[
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
].flat(),
),
);
});
test('should decode multiframe image', () => {
const decoded = decode(readImage('dog.tiff'));
expect(decoded).toHaveLength(8);
expect(decoded[0]).toMatchObject({
alpha: true,
bitsPerSample: 1,
components: 4,
compression: 1,
width: 16,
height: 16,
});
expect(decoded[0].imageWidth).toBe(16);
// last row of the first frame
const firstFrameLastRow = new Uint8Array([
1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1,
1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
]);
expect(decoded[1].imageLength).toBe(16);
expect(decoded[1].imageWidth).toBe(16);
expect(decoded[0].data.slice(960, 1024)).toStrictEqual(firstFrameLastRow);
// twelveth row of the second frame
const secondFrameTwelvethRow = new Uint8Array([
1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
0, 1, 0, 1, 0, 1, 0,
]);
expect(decoded[1].samplesPerPixel).toBe(2);
expect(decoded[1].data.slice(352, 384)).toStrictEqual(secondFrameTwelvethRow);
});
test('should decode image with undefined rowsPerStrip', () => {
const decodedFemale = decode(readImage('female.tiff'));
expect(decodedFemale[0].rowsPerStrip).toStrictEqual(2 ** 32 - 1);
expect(decodedFemale[0].data.slice(0, 10)).toStrictEqual(
new Uint8Array([94, 0, 115, 27, 61, 103, 26, 60, 104, 27]),
);
const decodedJellybeans = decode(readImage('jellybeans.tiff'));
expect(decodedJellybeans[0].rowsPerStrip).toStrictEqual(2 ** 32 - 1);
expect(decodedJellybeans[0].data.slice(0, 10)).toStrictEqual(
new Uint8Array([139, 132, 90, 141, 136, 92, 142, 131, 89, 143]),
);
});
================================================
FILE: src/__tests__/is_multi_page.test.ts
================================================
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { expect, test } from 'vitest';
import { isMultiPage } from '../index.ts';
const files = [
{ name: 'grey8.tif', pages: 1 },
{ name: 'grey16.tif', pages: 1 },
{ name: 'color8.tif', pages: 1 },
{ name: 'color16.tif', pages: 1 },
{ name: 'grey8-multi.tif', pages: 2 },
{ name: 'grey16-multi.tif', pages: 2 },
{ name: 'color8-multi.tif', pages: 2 },
{ name: 'color16-multi.tif', pages: 2 },
];
// const files = ['color8c.tif'];//'grey8.tif', 'grey16.tif', 'color8.tif', 'color16.tif'];
const contents = files.map((file) =>
readFileSync(join(import.meta.dirname, '../../img', file.name)),
);
test('TIFF isMultiPage', () => {
for (let i = 0; i < contents.length; i++) {
const result = isMultiPage(contents[i]);
expect(result).toBe(files[i].pages > 1);
}
});
================================================
FILE: src/__tests__/lzw.test.ts
================================================
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
import { decompressLzw } from '../lzw.ts';
describe('lzw', () => {
it('1', () => {
const buffer = readFileSync(join(import.meta.dirname, 'data/1.strip'));
const result = decompressLzw(
new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength),
);
expect(result.byteLength).toBe(7770);
expect(
new Uint8Array(
result.buffer,
result.byteOffset,
result.byteLength,
).reduce((sum, current) => sum + current, 0),
).toBe(675);
});
it('173', () => {
const buffer = readFileSync(join(import.meta.dirname, 'data/173.strip'));
const result = decompressLzw(
new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength),
);
expect(result.byteLength).toBe(7770);
expect(
new Uint8Array(
result.buffer,
result.byteOffset,
result.byteLength,
).reduce((sum, current) => sum + current, 0),
).toBe(38307);
});
it('174', () => {
const buffer = readFileSync(join(import.meta.dirname, 'data/174.strip'));
const result = decompressLzw(
new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength),
);
expect(result.byteLength).toBe(7770);
});
});
================================================
FILE: src/__tests__/page_count.test.ts
================================================
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { expect, test } from 'vitest';
import { pageCount } from '../index.ts';
const files = [
{ name: 'grey8.tif', pages: 1 },
{ name: 'grey16.tif', pages: 1 },
{ name: 'color8.tif', pages: 1 },
{ name: 'color16.tif', pages: 1 },
{ name: 'grey8-multi.tif', pages: 2 },
{ name: 'grey16-multi.tif', pages: 2 },
{ name: 'color8-multi.tif', pages: 2 },
{ name: 'color16-multi.tif', pages: 2 },
];
// const files = ['color8c.tif'];//'grey8.tif', 'grey16.tif', 'color8.tif', 'color16.tif'];
const contents = files.map((file) =>
readFileSync(join(import.meta.dirname, '../../img', file.name)),
);
test('TIFF pageCount', () => {
for (let i = 0; i < contents.length; i++) {
const result = pageCount(contents[i]);
expect(result).toBe(files[i].pages);
}
});
================================================
FILE: src/hacks.ts
================================================
import type TiffIfd from './tiff_ifd.ts';
export function guessStripByteCounts(ifd: TiffIfd): number[] {
if (ifd.compression !== 1) {
throw new Error(
'missing mandatory StripByteCounts field in compressed image',
);
}
const bytesPerStrip =
ifd.rowsPerStrip *
ifd.width *
ifd.samplesPerPixel *
(ifd.bitsPerSample / 8);
return new Array(ifd.stripOffsets.length).fill(bytesPerStrip);
}
================================================
FILE: src/horizontal_differencing.ts
================================================
// Section 14: Differencing Predictor (p. 64)
export function applyHorizontalDifferencing8Bit(
data: Uint8Array,
width: number,
components: number,
): void {
let i = 0;
while (i < data.length) {
for (let j = components; j < width * components; j += components) {
for (let k = 0; k < components; k++) {
data[i + j + k] =
(data[i + j + k] + data[i + j - (components - k)]) & 255;
}
}
i += width * components;
}
}
export function applyHorizontalDifferencing16Bit(
data: Uint16Array,
width: number,
components: number,
): void {
let i = 0;
while (i < data.length) {
for (let j = components; j < width * components; j += components) {
for (let k = 0; k < components; k++) {
data[i + j + k] =
(data[i + j + k] + data[i + j - (components - k)]) & 65535;
}
}
i += width * components;
}
}
================================================
FILE: src/ifd.ts
================================================
import * as exif from './tags/exif.ts';
import * as gps from './tags/gps.ts';
import * as standard from './tags/standard.ts';
import type { DataArray, IFDKind } from './types.ts';
const tags = {
standard,
exif,
gps,
};
export default class IFD {
public kind: IFDKind;
public data: DataArray;
public fields: Map<number, any>;
public exif: IFD | undefined;
public gps: IFD | undefined;
private _hasMap: boolean;
private _map: any;
public constructor(kind: IFDKind) {
if (!kind) {
throw new Error('missing kind');
}
this.data = new Uint8Array();
this.fields = new Map();
this.kind = kind;
this._hasMap = false;
this._map = {};
}
public get(tag: number | string): any {
if (typeof tag === 'number') {
return this.fields.get(tag);
} else if (typeof tag === 'string') {
return this.fields.get(tags[this.kind].tagsByName[tag]);
} else {
throw new Error('expected a number or string');
}
}
public get map(): Record<string, any> {
if (!this._hasMap) {
const taglist = tags[this.kind].tagsById;
for (const key of this.fields.keys()) {
if (taglist[key]) {
this._map[taglist[key]] = this.fields.get(key);
}
}
this._hasMap = true;
}
return this._map;
}
}
================================================
FILE: src/ifd_value.ts
================================================
import type TIFFDecoder from './tiff_decoder.ts';
const types = new Map<
number,
[number, (decoder: TIFFDecoder, count: number) => any]
>([
[1, [1, readByte]], // BYTE
[2, [1, readASCII]], // ASCII
[3, [2, readShort]], // SHORT
[4, [4, readLong]], // LONG
[5, [8, readRational]], // RATIONAL
[6, [1, readSByte]], // SBYTE
[7, [1, readByte]], // UNDEFINED
[8, [2, readSShort]], // SSHORT
[9, [4, readSLong]], // SLONG
[10, [8, readSRational]], // SRATIONAL
[11, [4, readFloat]], // FLOAT
[12, [8, readDouble]], // DOUBLE
]);
export function getByteLength(type: number, count: number): number {
const val = types.get(type);
if (!val) throw new Error(`type not found: ${type}`);
return val[0] * count;
}
export function readData(
decoder: TIFFDecoder,
type: number,
count: number,
): any {
const val = types.get(type);
if (!val) throw new Error(`type not found: ${type}`);
return val[1](decoder, count);
}
function readByte(decoder: TIFFDecoder, count: number): number | Uint8Array {
if (count === 1) return decoder.readUint8();
const array = new Uint8Array(count);
for (let i = 0; i < count; i++) {
array[i] = decoder.readUint8();
}
return array;
}
function readASCII(decoder: TIFFDecoder, count: number): string | string[] {
const strings = [];
let currentString = '';
for (let i = 0; i < count; i++) {
// eslint-disable-next-line unicorn/prefer-code-point
const char = String.fromCharCode(decoder.readUint8());
if (char === '\0') {
strings.push(currentString);
currentString = '';
} else {
currentString += char;
}
}
if (strings.length === 1) {
return strings[0];
} else {
return strings;
}
}
function readShort(decoder: TIFFDecoder, count: number): number | Uint16Array {
if (count === 1) return decoder.readUint16();
const array = new Uint16Array(count);
for (let i = 0; i < count; i++) {
array[i] = decoder.readUint16();
}
return array;
}
function readLong(decoder: TIFFDecoder, count: number): number | Uint32Array {
if (count === 1) return decoder.readUint32();
const array = new Uint32Array(count);
for (let i = 0; i < count; i++) {
array[i] = decoder.readUint32();
}
return array;
}
function readRational(decoder: TIFFDecoder, count: number): number | number[] {
if (count === 1) {
return decoder.readUint32() / decoder.readUint32();
}
const rationals = new Array(count);
for (let i = 0; i < count; i++) {
rationals[i] = decoder.readUint32() / decoder.readUint32();
}
return rationals;
}
function readSByte(decoder: TIFFDecoder, count: number): number | Int8Array {
if (count === 1) return decoder.readInt8();
const array = new Int8Array(count);
for (let i = 0; i < count; i++) {
array[i] = decoder.readInt8();
}
return array;
}
function readSShort(decoder: TIFFDecoder, count: number): number | Int16Array {
if (count === 1) return decoder.readInt16();
const array = new Int16Array(count);
for (let i = 0; i < count; i++) {
array[i] = decoder.readInt16();
}
return array;
}
function readSLong(decoder: TIFFDecoder, count: number): number | Int32Array {
if (count === 1) return decoder.readInt32();
const array = new Int32Array(count);
for (let i = 0; i < count; i++) {
array[i] = decoder.readInt32();
}
return array;
}
function readSRational(decoder: TIFFDecoder, count: number): number | number[] {
if (count === 1) {
return decoder.readInt32() / decoder.readInt32();
}
const rationals = new Array(count);
for (let i = 0; i < count; i++) {
rationals[i] = decoder.readInt32() / decoder.readInt32();
}
return rationals;
}
function readFloat(decoder: TIFFDecoder, count: number): number | Float32Array {
if (count === 1) return decoder.readFloat32();
const array = new Float32Array(count);
for (let i = 0; i < count; i++) {
array[i] = decoder.readFloat32();
}
return array;
}
function readDouble(
decoder: TIFFDecoder,
count: number,
): number | Float64Array {
if (count === 1) return decoder.readFloat64();
const array = new Float64Array(count);
for (let i = 0; i < count; i++) {
array[i] = decoder.readFloat64();
}
return array;
}
================================================
FILE: src/index.ts
================================================
import type { InputData } from 'iobuffer';
import { tagsById as exif } from './tags/exif.ts';
import { tagsById as gps } from './tags/gps.ts';
import { tagsById as standard } from './tags/standard.ts';
import TIFFDecoder from './tiff_decoder.ts';
import type TiffIfd from './tiff_ifd.ts';
import type { DecodeOptions } from './types.ts';
function decodeTIFF(data: InputData, options?: DecodeOptions): TiffIfd[] {
const decoder = new TIFFDecoder(data);
return decoder.decode(options);
}
function isMultiPage(data: InputData): boolean {
const decoder = new TIFFDecoder(data);
return decoder.isMultiPage;
}
function pageCount(data: InputData): number {
const decoder = new TIFFDecoder(data);
return decoder.pageCount;
}
const tagNames = {
exif,
gps,
standard,
};
export { decodeTIFF as decode, isMultiPage, pageCount, tagNames };
export { type DecodeOptions } from './types.ts';
export { default as TiffIfd } from './tiff_ifd.ts';
================================================
FILE: src/lzw.ts
================================================
import { IOBuffer } from 'iobuffer';
const CLEAR_CODE = 256;
const EOI_CODE = 257;
// 0-255 from the table + 256 for clear code + 257 for end of information code.
const TABLE_START = 258;
const MIN_BIT_LENGTH = 9;
let stringTable: number[][] = [];
function initializeStringTable() {
if (stringTable.length === 0) {
for (let i = 0; i < 256; i++) {
stringTable.push([i]);
}
// Fill the table with dummy data.
// Elements at indices > 257 will be replaced during decompression.
const dummyString: number[] = [];
for (let i = 256; i < 4096; i++) {
stringTable.push(dummyString);
}
}
}
const andTable = [511, 1023, 2047, 4095];
const bitJumps = [0, 0, 0, 0, 0, 0, 0, 0, 0, 511, 1023, 2047, 4095];
class LzwDecoder {
private stripArray: Uint8Array;
private nextData = 0;
private nextBits = 0;
private bytePointer = 0;
private tableLength = TABLE_START;
private currentBitLength = MIN_BIT_LENGTH;
private outData: IOBuffer;
public constructor(data: DataView) {
this.stripArray = new Uint8Array(
data.buffer,
data.byteOffset,
data.byteLength,
);
this.outData = new IOBuffer(data.byteLength);
this.initializeTable();
}
public decode(): DataView {
let code = 0;
let oldCode = 0;
while ((code = this.getNextCode()) !== EOI_CODE) {
if (code === CLEAR_CODE) {
this.initializeTable();
code = this.getNextCode();
if (code === EOI_CODE) {
break;
}
this.writeString(this.stringFromCode(code));
oldCode = code;
} else if (this.isInTable(code)) {
this.writeString(this.stringFromCode(code));
this.addStringToTable(
this.stringFromCode(oldCode).concat(this.stringFromCode(code)[0]),
);
oldCode = code;
} else {
const outString = this.stringFromCode(oldCode).concat(
this.stringFromCode(oldCode)[0],
);
this.writeString(outString);
this.addStringToTable(outString);
oldCode = code;
}
}
const outArray = this.outData.toArray();
return new DataView(
outArray.buffer,
outArray.byteOffset,
outArray.byteLength,
);
}
private initializeTable(): void {
initializeStringTable();
this.tableLength = TABLE_START;
this.currentBitLength = MIN_BIT_LENGTH;
}
private writeString(string: number[]): void {
this.outData.writeBytes(string);
}
private stringFromCode(code: number): number[] {
// At this point, `code` must be defined in the table.
return stringTable[code];
}
private isInTable(code: number): boolean {
return code < this.tableLength;
}
private addStringToTable(string: number[]): void {
stringTable[this.tableLength++] = string;
if (stringTable.length > 4096) {
stringTable = [];
throw new Error(
'LZW decoding error. Please open an issue at https://github.com/image-js/tiff/issues/new/choose (include a test image).',
);
}
if (this.tableLength === bitJumps[this.currentBitLength]) {
this.currentBitLength++;
}
}
private getNextCode(): number {
this.nextData =
(this.nextData << 8) | (this.stripArray[this.bytePointer++] & 0xff);
this.nextBits += 8;
if (this.nextBits < this.currentBitLength) {
this.nextData =
(this.nextData << 8) | (this.stripArray[this.bytePointer++] & 0xff);
this.nextBits += 8;
}
const code =
(this.nextData >> (this.nextBits - this.currentBitLength)) &
andTable[this.currentBitLength - 9];
this.nextBits -= this.currentBitLength;
// This should not really happen but is present in other codes as well.
// See: https://github.com/sugark/Tiffus/blob/15a60123813d1612f4ae9e4fab964f9f7d71cf63/src/org/eclipse/swt/internal/image/TIFFLZWDecoder.java
if (this.bytePointer > this.stripArray.length) {
return 257;
}
return code;
}
}
export function decompressLzw(stripData: DataView): DataView {
return new LzwDecoder(stripData).decode();
}
================================================
FILE: src/tags/exif.ts
================================================
const tagsById: Record<number, string> = {
0x829a: 'ExposureTime',
0x829d: 'FNumber',
0x8822: 'ExposureProgram',
0x8824: 'SpectralSensitivity',
0x8827: 'ISOSpeedRatings',
0x8828: 'OECF',
0x8830: 'SensitivityType',
0x8831: 'StandardOutputSensitivity',
0x8832: 'RecommendedExposureIndex',
0x8833: 'ISOSpeed',
0x8834: 'ISOSpeedLatitudeyyy',
0x8835: 'ISOSpeedLatitudezzz',
0x9000: 'ExifVersion',
0x9003: 'DateTimeOriginal',
0x9004: 'DateTimeDigitized',
0x9101: 'ComponentsConfiguration',
0x9102: 'CompressedBitsPerPixel',
0x9201: 'ShutterSpeedValue',
0x9202: 'ApertureValue',
0x9203: 'BrightnessValue',
0x9204: 'ExposureBiasValue',
0x9205: 'MaxApertureValue',
0x9206: 'SubjectDistance',
0x9207: 'MeteringMode',
0x9208: 'LightSource',
0x9209: 'Flash',
0x920a: 'FocalLength',
0x9214: 'SubjectArea',
0x927c: 'MakerNote',
0x9286: 'UserComment',
0x9290: 'SubsecTime',
0x9291: 'SubsecTimeOriginal',
0x9292: 'SubsecTimeDigitized',
0xa000: 'FlashpixVersion',
0xa001: 'ColorSpace',
0xa002: 'PixelXDimension',
0xa003: 'PixelYDimension',
0xa004: 'RelatedSoundFile',
0xa20b: 'FlashEnergy',
0xa20c: 'SpatialFrequencyResponse',
0xa20e: 'FocalPlaneXResolution',
0xa20f: 'FocalPlaneYResolution',
0xa210: 'FocalPlaneResolutionUnit',
0xa214: 'SubjectLocation',
0xa215: 'ExposureIndex',
0xa217: 'SensingMethod',
0xa300: 'FileSource',
0xa301: 'SceneType',
0xa302: 'CFAPattern',
0xa401: 'CustomRendered',
0xa402: 'ExposureMode',
0xa403: 'WhiteBalance',
0xa404: 'DigitalZoomRatio',
0xa405: 'FocalLengthIn35mmFilm',
0xa406: 'SceneCaptureType',
0xa407: 'GainControl',
0xa408: 'Contrast',
0xa409: 'Saturation',
0xa40a: 'Sharpness',
0xa40b: 'DeviceSettingDescription',
0xa40c: 'SubjectDistanceRange',
0xa420: 'ImageUniqueID',
0xa430: 'CameraOwnerName',
0xa431: 'BodySerialNumber',
0xa432: 'LensSpecification',
0xa433: 'LensMake',
0xa434: 'LensModel',
0xa435: 'LensSerialNumber',
0xa500: 'Gamma',
};
const tagsByName: Record<string, number> = {};
for (const i in tagsById) {
tagsByName[tagsById[i]] = Number(i);
}
export { tagsById, tagsByName };
================================================
FILE: src/tags/gps.ts
================================================
const tagsById: Record<number, string> = {
0x0000: 'GPSVersionID',
0x0001: 'GPSLatitudeRef',
0x0002: 'GPSLatitude',
0x0003: 'GPSLongitudeRef',
0x0004: 'GPSLongitude',
0x0005: 'GPSAltitudeRef',
0x0006: 'GPSAltitude',
0x0007: 'GPSTimeStamp',
0x0008: 'GPSSatellites',
0x0009: 'GPSStatus',
0x000a: 'GPSMeasureMode',
0x000b: 'GPSDOP',
0x000c: 'GPSSpeedRef',
0x000d: 'GPSSpeed',
0x000e: 'GPSTrackRef',
0x000f: 'GPSTrack',
0x0010: 'GPSImgDirectionRef',
0x0011: 'GPSImgDirection',
0x0012: 'GPSMapDatum',
0x0013: 'GPSDestLatitudeRef',
0x0014: 'GPSDestLatitude',
0x0015: 'GPSDestLongitudeRef',
0x0016: 'GPSDestLongitude',
0x0017: 'GPSDestBearingRef',
0x0018: 'GPSDestBearing',
0x0019: 'GPSDestDistanceRef',
0x001a: 'GPSDestDistance',
0x001b: 'GPSProcessingMethod',
0x001c: 'GPSAreaInformation',
0x001d: 'GPSDateStamp',
0x001e: 'GPSDifferential',
0x001f: 'GPSHPositioningError',
};
const tagsByName: Record<string, number> = {};
for (const i in tagsById) {
tagsByName[tagsById[i]] = Number(i);
}
export { tagsById, tagsByName };
================================================
FILE: src/tags/standard.ts
================================================
const tagsById: Record<number, string> = {
// Baseline tags
0x00fe: 'NewSubfileType',
0x00ff: 'SubfileType',
0x0100: 'ImageWidth',
0x0101: 'ImageLength',
0x0102: 'BitsPerSample',
0x0103: 'Compression',
0x0106: 'PhotometricInterpretation',
0x0107: 'Threshholding',
0x0108: 'CellWidth',
0x0109: 'CellLength',
0x010a: 'FillOrder',
0x010e: 'ImageDescription',
0x010f: 'Make',
0x0110: 'Model',
0x0111: 'StripOffsets',
0x0112: 'Orientation',
0x0115: 'SamplesPerPixel',
0x0116: 'RowsPerStrip',
0x0117: 'StripByteCounts',
0x0118: 'MinSampleValue',
0x0119: 'MaxSampleValue',
0x011a: 'XResolution',
0x011b: 'YResolution',
0x011c: 'PlanarConfiguration',
0x0120: 'FreeOffsets',
0x0121: 'FreeByteCounts',
0x0122: 'GrayResponseUnit',
0x0123: 'GrayResponseCurve',
0x0128: 'ResolutionUnit',
0x0131: 'Software',
0x0132: 'DateTime',
0x013b: 'Artist',
0x013c: 'HostComputer',
0x0140: 'ColorMap',
0x0152: 'ExtraSamples',
0x8298: 'Copyright',
// Extension tags
0x010d: 'DocumentName',
0x011d: 'PageName',
0x011e: 'XPosition',
0x011f: 'YPosition',
0x0124: 'T4Options',
0x0125: 'T6Options',
0x0129: 'PageNumber',
0x012d: 'TransferFunction',
0x013d: 'Predictor',
0x013e: 'WhitePoint',
0x013f: 'PrimaryChromaticities',
0x0141: 'HalftoneHints',
0x0142: 'TileWidth',
0x0143: 'TileLength',
0x0144: 'TileOffsets',
0x0145: 'TileByteCounts',
0x0146: 'BadFaxLines',
0x0147: 'CleanFaxData',
0x0148: 'ConsecutiveBadFaxLines',
0x014a: 'SubIFDs',
0x014c: 'InkSet',
0x014d: 'InkNames',
0x014e: 'NumberOfInks',
0x0150: 'DotRange',
0x0151: 'TargetPrinter',
0x0153: 'SampleFormat',
0x0154: 'SMinSampleValue',
0x0155: 'SMaxSampleValue',
0x0156: 'TransferRange',
0x0157: 'ClipPath',
0x0158: 'XClipPathUnits',
0x0159: 'YClipPathUnits',
0x015a: 'Indexed',
0x015b: 'JPEGTables',
0x015f: 'OPIProxy',
0x0190: 'GlobalParametersIFD',
0x0191: 'ProfileType',
0x0192: 'FaxProfile',
0x0193: 'CodingMethods',
0x0194: 'VersionYear',
0x0195: 'ModeNumber',
0x01b1: 'Decode',
0x01b2: 'DefaultImageColor',
0x0200: 'JPEGProc',
0x0201: 'JPEGInterchangeFormat',
0x0202: 'JPEGInterchangeFormatLength',
0x0203: 'JPEGRestartInterval',
0x0205: 'JPEGLosslessPredictors',
0x0206: 'JPEGPointTransforms',
0x0207: 'JPEGQTables',
0x0208: 'JPEGDCTables',
0x0209: 'JPEGACTables',
0x0211: 'YCbCrCoefficients',
0x0212: 'YCbCrSubSampling',
0x0213: 'YCbCrPositioning',
0x0214: 'ReferenceBlackWhite',
0x022f: 'StripRowCounts',
0x02bc: 'XMP',
0x800d: 'ImageID',
0x87ac: 'ImageLayer',
// Private tags
0x80a4: 'WangAnnotatio',
0x82a5: 'MDFileTag',
0x82a6: 'MDScalePixel',
0x82a7: 'MDColorTable',
0x82a8: 'MDLabName',
0x82a9: 'MDSampleInfo',
0x82aa: 'MDPrepDate',
0x82ab: 'MDPrepTime',
0x82ac: 'MDFileUnits',
0x830e: 'ModelPixelScaleTag',
0x83bb: 'IPTC',
0x847e: 'INGRPacketDataTag',
0x847f: 'INGRFlagRegisters',
0x8480: 'IrasBTransformationMatrix',
0x8482: 'ModelTiepointTag',
0x85d8: 'ModelTransformationTag',
0x8649: 'Photoshop',
0x8769: 'ExifIFD',
0x8773: 'ICCProfile',
0x87af: 'GeoKeyDirectoryTag',
0x87b0: 'GeoDoubleParamsTag',
0x87b1: 'GeoAsciiParamsTag',
0x8825: 'GPSIFD',
0x885c: 'HylaFAXFaxRecvParams',
0x885d: 'HylaFAXFaxSubAddress',
0x885e: 'HylaFAXFaxRecvTime',
0x935c: 'ImageSourceData',
0xa005: 'InteroperabilityIFD',
0xa480: 'GDAL_METADATA',
0xa481: 'GDAL_NODATA',
0xc427: 'OceScanjobDescription',
0xc428: 'OceApplicationSelector',
0xc429: 'OceIdentificationNumber',
0xc42a: 'OceImageLogicCharacteristics',
0xc612: 'DNGVersion',
0xc613: 'DNGBackwardVersion',
0xc614: 'UniqueCameraModel',
0xc615: 'LocalizedCameraModel',
0xc616: 'CFAPlaneColor',
0xc617: 'CFALayout',
0xc618: 'LinearizationTable',
0xc619: 'BlackLevelRepeatDim',
0xc61a: 'BlackLevel',
0xc61b: 'BlackLevelDeltaH',
0xc61c: 'BlackLevelDeltaV',
0xc61d: 'WhiteLevel',
0xc61e: 'DefaultScale',
0xc61f: 'DefaultCropOrigin',
0xc620: 'DefaultCropSize',
0xc621: 'ColorMatrix1',
0xc622: 'ColorMatrix2',
0xc623: 'CameraCalibration1',
0xc624: 'CameraCalibration2',
0xc625: 'ReductionMatrix1',
0xc626: 'ReductionMatrix2',
0xc627: 'AnalogBalance',
0xc628: 'AsShotNeutral',
0xc629: 'AsShotWhiteXY',
0xc62a: 'BaselineExposure',
0xc62b: 'BaselineNoise',
0xc62c: 'BaselineSharpness',
0xc62d: 'BayerGreenSplit',
0xc62e: 'LinearResponseLimit',
0xc62f: 'CameraSerialNumber',
0xc630: 'LensInfo',
0xc631: 'ChromaBlurRadius',
0xc632: 'AntiAliasStrength',
0xc634: 'DNGPrivateData',
0xc635: 'MakerNoteSafety',
0xc65a: 'CalibrationIlluminant1',
0xc65b: 'CalibrationIlluminant2',
0xc65c: 'BestQualityScale',
0xc660: 'AliasLayerMetadata',
};
const tagsByName: Record<string, number> = {};
for (const i in tagsById) {
tagsByName[tagsById[i]] = Number(i);
}
export { tagsById, tagsByName };
================================================
FILE: src/tiff_decoder.ts
================================================
import type { InputData } from 'iobuffer';
import { IOBuffer } from 'iobuffer';
import { guessStripByteCounts } from './hacks.ts';
import {
applyHorizontalDifferencing16Bit,
applyHorizontalDifferencing8Bit,
} from './horizontal_differencing.ts';
import IFD from './ifd.ts';
import { getByteLength, readData } from './ifd_value.ts';
import { decompressLzw } from './lzw.ts';
import TiffIfd from './tiff_ifd.ts';
import type { DataArray, DecodeOptions, IFDKind } from './types.ts';
import { decompressZlib } from './zlib.ts';
const defaultOptions: DecodeOptions = {
ignoreImageData: false,
};
interface InternalOptions extends DecodeOptions {
kind?: IFDKind;
}
export default class TIFFDecoder extends IOBuffer {
private _nextIFD: number;
public constructor(data: InputData) {
super(data);
this._nextIFD = 0;
}
public get isMultiPage(): boolean {
let c = 0;
this.decodeHeader();
while (this._nextIFD) {
c++;
this.decodeIFD({ ignoreImageData: true }, true);
if (c === 2) {
return true;
}
}
if (c === 1) {
return false;
}
throw unsupported('ifdCount', c);
}
public get pageCount(): number {
let c = 0;
this.decodeHeader();
while (this._nextIFD) {
c++;
this.decodeIFD({ ignoreImageData: true }, true);
}
if (c > 0) {
return c;
}
throw unsupported('ifdCount', c);
}
public decode(options: DecodeOptions = {}): TiffIfd[] {
const { pages } = options;
checkPages(pages);
const maxIndex = pages ? Math.max(...pages) : Infinity;
options = { ...defaultOptions, ...options };
const result = [];
this.decodeHeader();
let index = 0;
while (this._nextIFD) {
if (pages) {
if (pages.includes(index)) {
result.push(this.decodeIFD(options, true));
} else {
this.decodeIFD({ ignoreImageData: true }, true);
}
if (index === maxIndex) {
break;
}
} else {
result.push(this.decodeIFD(options, true));
}
index++;
}
if (index < maxIndex && maxIndex !== Infinity) {
throw new RangeError(
`Index ${maxIndex} is out of bounds. The stack only contains ${index} images.`,
);
}
return result;
}
private decodeHeader(): void {
// Byte offset
const value = this.readUint16();
if (value === 0x4949) {
this.setLittleEndian();
} else if (value === 0x4d4d) {
this.setBigEndian();
} else {
throw new Error(`invalid byte order: 0x${value.toString(16)}`);
}
// Magic number
if (this.readUint16() !== 42) {
throw new Error('not a TIFF file');
}
// Offset of the first IFD
this._nextIFD = this.readUint32();
}
private decodeIFD(options: InternalOptions, tiff: true): TiffIfd;
private decodeIFD(options: InternalOptions, tiff: false): IFD;
private decodeIFD(options: InternalOptions, tiff: boolean): TiffIfd | IFD {
this.seek(this._nextIFD);
let ifd: TiffIfd | IFD;
if (tiff) {
ifd = new TiffIfd();
} else {
if (!options.kind) {
throw new Error(`kind is missing`);
}
ifd = new IFD(options.kind);
}
const numEntries = this.readUint16();
for (let i = 0; i < numEntries; i++) {
this.decodeIFDEntry(ifd);
}
if (!options.ignoreImageData) {
if (!(ifd instanceof TiffIfd)) {
throw new Error('must be a tiff ifd');
}
this.decodeImageData(ifd);
}
this._nextIFD = this.readUint32();
return ifd;
}
private decodeIFDEntry(ifd: IFD): void {
const offset = this.offset;
const tag = this.readUint16();
const type = this.readUint16();
const numValues = this.readUint32();
if (type < 1 || type > 12) {
this.skip(4); // unknown type, skip this value
return;
}
const valueByteLength = getByteLength(type, numValues);
if (valueByteLength > 4) {
this.seek(this.readUint32());
}
const value = readData(this, type, numValues);
ifd.fields.set(tag, value);
// Read sub-IFDs
if (tag === 0x8769 || tag === 0x8825) {
const currentOffset = this.offset;
let kind: IFDKind = 'exif';
if (tag === 0x8769) {
kind = 'exif';
} else if (tag === 0x8825) {
kind = 'gps';
}
this._nextIFD = value;
ifd[kind] = this.decodeIFD(
{
kind,
ignoreImageData: true,
},
false,
);
this.offset = currentOffset;
}
// go to the next entry
this.seek(offset);
this.skip(12);
}
private decodeImageData(ifd: TiffIfd): void {
const orientation = ifd.orientation;
if (orientation && orientation !== 1) {
throw unsupported('orientation', orientation);
}
switch (ifd.type) {
case 0: // WhiteIsZero
case 1: // BlackIsZero
case 2: // RGB
case 3: // Palette color
if (ifd.tiled) {
this.readTileData(ifd);
} else {
this.readStripData(ifd);
}
break;
default:
throw unsupported('image type', ifd.type);
}
this.applyPredictor(ifd);
this.convertAlpha(ifd);
if (ifd.bitsPerSample === 1) {
this.split1BitData(ifd);
}
if (ifd.type === 0) {
// WhiteIsZero: we invert the values
const bitDepth = ifd.bitsPerSample;
const maxValue = 2 ** bitDepth - 1;
for (let i = 0; i < ifd.data.length; i++) {
ifd.data[i] = maxValue - ifd.data[i];
}
}
}
private split1BitData(ifd: TiffIfd) {
const { imageWidth, imageLength, samplesPerPixel } = ifd;
const data = new Uint8Array(imageLength * imageWidth * samplesPerPixel);
const bytesPerRow = Math.ceil((imageWidth * samplesPerPixel) / 8);
let dataIndex = 0;
for (let row = 0; row < imageLength; row++) {
const rowStartByte = row * bytesPerRow;
for (let col = 0; col < imageWidth * samplesPerPixel; col++) {
const byteIndex = rowStartByte + Math.floor(col / 8);
const bitIndex = 7 - (col % 8);
const bit = (ifd.data[byteIndex] >> bitIndex) & 1;
data[dataIndex++] = bit;
}
}
ifd.data = data;
}
private static uncompress(data: DataView, compression = 1): DataView {
switch (compression) {
// No compression, nothing to do
case 1: {
return data;
}
// LZW compression
case 5: {
return decompressLzw(data);
}
// Zlib and Deflate compressions. They are identical.
case 8:
case 32946: {
return decompressZlib(data);
}
case 2: // CCITT Group 3 1-Dimensional Modified Huffman run length encoding
throw unsupported('Compression', 'CCITT Group 3');
case 32773: // PackBits compression
throw unsupported('Compression', 'PackBits');
default:
throw unsupported('Compression', compression);
}
}
private createSampleReader(
sampleFormat: number,
bitDepth: number,
littleEndian: boolean,
): (data: DataView, index: number) => number {
if (bitDepth === 8 || bitDepth === 1) {
return (data: DataView, index: number) => data.getUint8(index);
} else if (bitDepth === 16) {
return (data: DataView, index: number) =>
data.getUint16(2 * index, littleEndian);
} else if (bitDepth === 32 && sampleFormat === 3) {
return (data: DataView, index: number) =>
data.getFloat32(4 * index, littleEndian);
} else if (bitDepth === 64 && sampleFormat === 3) {
return (data: DataView, index: number) =>
data.getFloat64(8 * index, littleEndian);
} else {
throw unsupported('bitDepth', bitDepth);
}
}
private readStripData(ifd: TiffIfd): void {
// General Image Dimensions
const width = ifd.width;
const height = ifd.height;
const size =
ifd.bitsPerSample !== 1
? width * ifd.samplesPerPixel * height
: Math.ceil((width * ifd.samplesPerPixel) / 8) * height;
// Compressed Strip Layout
const stripOffsets = ifd.stripOffsets;
const stripByteCounts = ifd.stripByteCounts || guessStripByteCounts(ifd);
const littleEndian = this.isLittleEndian();
// For 1-bit images, calculate pixels per strip correctly
const stripLength =
ifd.bitsPerSample !== 1
? width * ifd.samplesPerPixel * ifd.rowsPerStrip
: Math.ceil((width * ifd.samplesPerPixel) / 8) * ifd.rowsPerStrip;
const readSamples = this.createSampleReader(
ifd.sampleFormat,
ifd.bitsPerSample,
littleEndian,
);
// Output Data Buffer
const output = getDataArray(size, ifd.bitsPerSample, ifd.sampleFormat);
// Iterate over Number of Strips
let start = 0;
for (let i = 0; i < stripOffsets.length; i++) {
// Extract Strip Data, Uncompress
const stripData = new DataView(
this.buffer,
this.byteOffset + stripOffsets[i],
stripByteCounts[i],
);
const uncompressed = TIFFDecoder.uncompress(stripData, ifd.compression);
// Last strip can be smaller
const length = Math.min(stripLength, size - start);
// Write Uncompressed Strip Data to Output (Linear Layout)
for (let index = 0; index < length; ++index) {
const value = readSamples(uncompressed, index);
output[start + index] = value;
}
start += length;
}
ifd.data = output;
// For 1-bit images, we need to convert the data to bits
}
private readTileData(ifd: TiffIfd): void {
if (!ifd.tileWidth || !ifd.tileHeight) {
return;
}
const width = ifd.width;
const height = ifd.height;
const size =
ifd.bitsPerSample !== 1
? width * height * ifd.samplesPerPixel
: Math.ceil((width * ifd.samplesPerPixel) / 8) * height;
const twidth = ifd.tileWidth;
const theight = ifd.tileHeight;
const nwidth = Math.ceil(width / twidth);
const nheight = Math.ceil(height / theight);
const tileOffsets = ifd.tileOffsets;
const tileByteCounts = ifd.tileByteCounts;
const littleEndian = this.isLittleEndian();
const readSamples = this.createSampleReader(
ifd.sampleFormat,
ifd.bitsPerSample,
littleEndian,
);
const output = getDataArray(size, ifd.bitsPerSample, ifd.sampleFormat);
for (let nx = 0; nx < nwidth; ++nx) {
for (let ny = 0; ny < nheight; ++ny) {
const nind = ny * nwidth + nx;
const tileData = new DataView(
this.buffer,
this.byteOffset + tileOffsets[nind],
tileByteCounts[nind],
);
const uncompressed = TIFFDecoder.uncompress(tileData, ifd.compression);
if (ifd.bitsPerSample === 1) {
// For 1-bit: read sequentially by bytes
const bytesPerRow = Math.ceil(width / 8);
const tileBytesPerRow = Math.ceil(twidth / 8);
for (let ty = 0; ty < theight && ny * theight + ty < height; ty++) {
const iy = ny * theight + ty;
const srcStart = ty * tileBytesPerRow;
const dstStart = iy * bytesPerRow + Math.floor((nx * twidth) / 8);
// Copy the row of bytes from tile to output
const bytesToCopy = Math.min(
tileBytesPerRow,
bytesPerRow - Math.floor((nx * twidth) / 8),
);
for (let b = 0; b < bytesToCopy; b++) {
output[dstStart + b] = readSamples(uncompressed, srcStart + b);
}
}
} else {
// For 8/16/32-bit: read by pixels
for (let ty = 0; ty < theight; ty++) {
for (let tx = 0; tx < twidth; tx++) {
const ix = nx * twidth + tx;
const iy = ny * theight + ty;
if (ix >= width || iy >= height) continue;
const tilePixelIndex = ty * twidth + tx;
const value = readSamples(uncompressed, tilePixelIndex);
const outputPixelIndex = (iy * width + ix) * ifd.samplesPerPixel;
output[outputPixelIndex] = value;
}
}
}
}
}
ifd.data = output;
}
private applyPredictor(ifd: TiffIfd): void {
const bitDepth = ifd.bitsPerSample;
switch (ifd.predictor) {
case 1: {
// No prediction scheme, nothing to do
break;
}
case 2: {
if (bitDepth === 8) {
applyHorizontalDifferencing8Bit(
ifd.data as Uint8Array,
ifd.width,
ifd.components,
);
} else if (bitDepth === 16) {
applyHorizontalDifferencing16Bit(
ifd.data as Uint16Array,
ifd.width,
ifd.components,
);
} else {
throw new Error(
`Horizontal differencing is only supported for images with a bit depth of ${bitDepth}`,
);
}
break;
}
default:
throw new Error(`invalid predictor: ${ifd.predictor}`);
}
}
private convertAlpha(ifd: TiffIfd): void {
if (ifd.alpha && ifd.associatedAlpha) {
const { data, components, maxSampleValue } = ifd;
for (let i = 0; i < data.length; i += components) {
const alphaValue = data[i + components - 1];
for (let j = 0; j < components - 1; j++) {
data[i + j] = Math.round((data[i + j] * maxSampleValue) / alphaValue);
}
}
}
}
}
function getDataArray(
size: number,
bitDepth: number,
sampleFormat: number,
): DataArray {
if (bitDepth === 8 || bitDepth === 1) {
return new Uint8Array(size);
} else if (bitDepth === 16) {
return new Uint16Array(size);
} else if (bitDepth === 32 && sampleFormat === 3) {
return new Float32Array(size);
} else if (bitDepth === 64 && sampleFormat === 3) {
return new Float64Array(size);
} else {
throw unsupported(
'bit depth / sample format',
`${bitDepth} / ${sampleFormat}`,
);
}
}
function unsupported(type: string, value: any): Error {
return new Error(`Unsupported ${type}: ${value}`);
}
function checkPages(pages: number[] | undefined) {
if (pages) {
for (const page of pages) {
if (page < 0 || !Number.isInteger(page)) {
throw new RangeError(
`Index ${page} is invalid. Must be a positive integer.`,
);
}
}
}
}
================================================
FILE: src/tiff_ifd.ts
================================================
import Ifd from './ifd.ts';
// eslint-disable-next-line prefer-named-capture-group
const dateTimeRegex = /^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$/;
export default class TiffIfd extends Ifd {
public constructor() {
super('standard');
}
// Custom fields
public get size(): number {
return this.width * this.height;
}
public get width(): number {
return this.imageWidth;
}
public get height(): number {
return this.imageLength;
}
public get components(): number {
return this.samplesPerPixel;
}
public get date(): Date {
const date = new Date();
const result = dateTimeRegex.exec(this.dateTime);
if (result === null) {
throw new Error(`invalid dateTime: ${this.dateTime}`);
}
date.setFullYear(
Number(result[1]),
Number(result[2]) - 1,
Number(result[3]),
);
date.setHours(Number(result[4]), Number(result[5]), Number(result[6]));
return date;
}
// IFD fields
public get newSubfileType(): number {
return this.get('NewSubfileType');
}
public get imageWidth(): number {
return this.get('ImageWidth');
}
public get imageLength(): number {
return this.get('ImageLength');
}
public get bitsPerSample(): number {
const data = this.get('BitsPerSample');
if (data && typeof data !== 'number') {
return data[0];
}
return data;
}
public get alpha(): boolean {
const extraSamples = this.extraSamples;
if (!extraSamples) return false;
return extraSamples[0] !== 0;
}
public get associatedAlpha(): boolean {
const extraSamples = this.extraSamples;
if (!extraSamples) return false;
return extraSamples[0] === 1;
}
public get extraSamples(): number[] | undefined {
return alwaysArray(this.get('ExtraSamples'));
}
public get compression(): number {
return this.get('Compression') || 1;
}
public get type(): number {
return this.get('PhotometricInterpretation');
}
public get fillOrder(): number {
return this.get('FillOrder') || 1;
}
public get documentName(): string | undefined {
return this.get('DocumentName');
}
public get imageDescription(): string | undefined {
return this.get('ImageDescription');
}
public get stripOffsets(): number[] {
return alwaysArray(this.get('StripOffsets'));
}
public get orientation(): number {
return this.get('Orientation');
}
public get samplesPerPixel(): number {
return this.get('SamplesPerPixel') || 1;
}
public get rowsPerStrip(): number {
return this.get('RowsPerStrip') || 2 ** 32 - 1;
}
public get stripByteCounts(): number[] {
return alwaysArray(this.get('StripByteCounts'));
}
public get minSampleValue(): number {
return this.get('MinSampleValue') || 0;
}
public get maxSampleValue(): number {
return this.get('MaxSampleValue') || 2 ** this.bitsPerSample - 1;
}
public get xResolution(): number {
return this.get('XResolution');
}
public get yResolution(): number {
return this.get('YResolution');
}
public get planarConfiguration(): number {
return this.get('PlanarConfiguration') || 1;
}
public get resolutionUnit(): number {
return this.get('ResolutionUnit') || 2;
}
public get dateTime(): string {
return this.get('DateTime');
}
public get predictor(): number {
return this.get('Predictor') || 1;
}
public get sampleFormat(): number {
const data = alwaysArray(this.get('SampleFormat') || 1);
return data[0];
}
public get sMinSampleValue(): number {
return this.get('SMinSampleValue') || this.minSampleValue;
}
public get sMaxSampleValue(): number {
return this.get('SMaxSampleValue') || this.maxSampleValue;
}
public get palette(): Array<[number, number, number]> | undefined {
const totalColors = 2 ** this.bitsPerSample;
const colorMap: number[] = this.get('ColorMap');
if (!colorMap) return undefined;
if (colorMap.length !== 3 * totalColors) {
throw new Error(`ColorMap size must be ${totalColors}`);
}
const palette: Array<[number, number, number]> = [];
for (let i = 0; i < totalColors; i++) {
palette.push([
colorMap[i],
colorMap[i + totalColors],
colorMap[i + 2 * totalColors],
]);
}
return palette;
}
public get tileWidth(): number | undefined {
return this.get('TileWidth');
}
public get tileHeight(): number | undefined {
return this.get('TileLength');
}
public get tileOffsets(): number[] {
return alwaysArray(this.get('TileOffsets'));
}
public get tileByteCounts(): number[] {
return alwaysArray(this.get('TileByteCounts'));
}
public get tiled(): boolean {
return (
this.tileWidth !== undefined &&
this.tileHeight !== undefined &&
this.tileOffsets !== undefined &&
this.tileByteCounts !== undefined
);
}
}
function alwaysArray(value: number | number[]): number[] {
if (typeof value === 'number') return [value];
return value;
}
================================================
FILE: src/types.ts
================================================
export interface DecodeOptions {
ignoreImageData?: boolean;
/**
* Specify the indices of the pages to decode in case of a multi-page TIFF.
*/
pages?: number[];
}
export type IFDKind = 'standard' | 'exif' | 'gps';
export type DataArray = Uint8Array | Uint16Array | Float32Array | Float64Array;
================================================
FILE: src/zlib.ts
================================================
import { decompressSync } from 'fflate';
export function decompressZlib(stripData: DataView): DataView {
const stripUint8 = new Uint8Array(
stripData.buffer,
stripData.byteOffset,
stripData.byteLength,
);
const inflated = decompressSync(stripUint8);
return new DataView(
inflated.buffer,
inflated.byteOffset,
inflated.byteLength,
);
}
================================================
FILE: tsconfig.build.json
================================================
{
"extends": "./tsconfig.json",
"include": ["src"],
"exclude": ["**/__tests__", "**/*.test.ts"]
}
================================================
FILE: tsconfig.json
================================================
{
"extends": "@zakodium/tsconfig",
"compilerOptions": {
"noUncheckedIndexedAccess": false,
"types": ["node"],
"outDir": "lib"
},
"include": ["src", "vite*.ts"]
}
gitextract_4e2k9nc9/ ├── .github/ │ └── workflows/ │ ├── nodejs.yml │ ├── release.yml │ └── typedoc.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── eslint.config.js ├── img/ │ ├── array-sample-format.tif │ ├── black.tif │ ├── bw1bit.tif │ ├── bwCross.tif │ ├── cells.tif │ ├── color-1px.tif │ ├── color-5x5-deflate.tif │ ├── color-5x5-lzw.tif │ ├── color-5x5.tif │ ├── color-alpha-2x2.tif │ ├── color-alpha-5x5-lzw.tif │ ├── color-alpha-5x5.tif │ ├── color16-lzw.tif │ ├── color16-multi.tif │ ├── color16.tif │ ├── color8-alpha.tif │ ├── color8-lzw.tif │ ├── color8-multi.tif │ ├── color8.tif │ ├── crosshair_tiled.tif │ ├── dog.tiff │ ├── female.tiff │ ├── float32.tif │ ├── float64.tif │ ├── grey16-multi.tif │ ├── grey16.tif │ ├── grey8-lzw.tif │ ├── grey8-multi.tif │ ├── grey8.tif │ ├── greya16.tif │ ├── greya32.tif │ ├── image-deflate.tif │ ├── image-lzw.tif │ ├── image.tif │ ├── jellybeans.tiff │ ├── palette.tif │ ├── stack.tif │ ├── tile_rgb_deflate.tif │ ├── tiled.tif │ └── whiteIsZero.tif ├── package.json ├── src/ │ ├── .npmignore │ ├── __tests__/ │ │ ├── data/ │ │ │ ├── 1.strip │ │ │ ├── 173.strip │ │ │ └── 174.strip │ │ ├── decode.lzw.test.ts │ │ ├── decode.test.ts │ │ ├── is_multi_page.test.ts │ │ ├── lzw.test.ts │ │ └── page_count.test.ts │ ├── hacks.ts │ ├── horizontal_differencing.ts │ ├── ifd.ts │ ├── ifd_value.ts │ ├── index.ts │ ├── lzw.ts │ ├── tags/ │ │ ├── exif.ts │ │ ├── gps.ts │ │ └── standard.ts │ ├── tiff_decoder.ts │ ├── tiff_ifd.ts │ ├── types.ts │ └── zlib.ts ├── tsconfig.build.json └── tsconfig.json
SYMBOL INDEX (107 symbols across 12 files)
FILE: src/__tests__/decode.lzw.test.ts
function dataEqual (line 43) | function dataEqual(data1: Uint8Array, data2: Uint8Array): boolean {
FILE: src/__tests__/decode.test.ts
function readImage (line 8) | function readImage(file: string): Buffer {
type TiffFile (line 12) | interface TiffFile {
FILE: src/hacks.ts
function guessStripByteCounts (line 3) | function guessStripByteCounts(ifd: TiffIfd): number[] {
FILE: src/horizontal_differencing.ts
function applyHorizontalDifferencing8Bit (line 3) | function applyHorizontalDifferencing8Bit(
function applyHorizontalDifferencing16Bit (line 20) | function applyHorizontalDifferencing16Bit(
FILE: src/ifd.ts
class IFD (line 12) | class IFD {
method constructor (line 22) | public constructor(kind: IFDKind) {
method get (line 33) | public get(tag: number | string): any {
method map (line 43) | public get map(): Record<string, any> {
FILE: src/ifd_value.ts
function getByteLength (line 21) | function getByteLength(type: number, count: number): number {
function readData (line 27) | function readData(
function readByte (line 37) | function readByte(decoder: TIFFDecoder, count: number): number | Uint8Ar...
function readASCII (line 46) | function readASCII(decoder: TIFFDecoder, count: number): string | string...
function readShort (line 66) | function readShort(decoder: TIFFDecoder, count: number): number | Uint16...
function readLong (line 75) | function readLong(decoder: TIFFDecoder, count: number): number | Uint32A...
function readRational (line 84) | function readRational(decoder: TIFFDecoder, count: number): number | num...
function readSByte (line 95) | function readSByte(decoder: TIFFDecoder, count: number): number | Int8Ar...
function readSShort (line 104) | function readSShort(decoder: TIFFDecoder, count: number): number | Int16...
function readSLong (line 113) | function readSLong(decoder: TIFFDecoder, count: number): number | Int32A...
function readSRational (line 122) | function readSRational(decoder: TIFFDecoder, count: number): number | nu...
function readFloat (line 133) | function readFloat(decoder: TIFFDecoder, count: number): number | Float3...
function readDouble (line 142) | function readDouble(
FILE: src/index.ts
function decodeTIFF (line 10) | function decodeTIFF(data: InputData, options?: DecodeOptions): TiffIfd[] {
function isMultiPage (line 15) | function isMultiPage(data: InputData): boolean {
function pageCount (line 20) | function pageCount(data: InputData): number {
FILE: src/lzw.ts
constant CLEAR_CODE (line 3) | const CLEAR_CODE = 256;
constant EOI_CODE (line 4) | const EOI_CODE = 257;
constant TABLE_START (line 6) | const TABLE_START = 258;
constant MIN_BIT_LENGTH (line 7) | const MIN_BIT_LENGTH = 9;
function initializeStringTable (line 10) | function initializeStringTable() {
class LzwDecoder (line 27) | class LzwDecoder {
method constructor (line 36) | public constructor(data: DataView) {
method decode (line 46) | public decode(): DataView {
method initializeTable (line 82) | private initializeTable(): void {
method writeString (line 88) | private writeString(string: number[]): void {
method stringFromCode (line 92) | private stringFromCode(code: number): number[] {
method isInTable (line 97) | private isInTable(code: number): boolean {
method addStringToTable (line 101) | private addStringToTable(string: number[]): void {
method getNextCode (line 114) | private getNextCode(): number {
function decompressLzw (line 140) | function decompressLzw(stripData: DataView): DataView {
FILE: src/tiff_decoder.ts
type InternalOptions (line 20) | interface InternalOptions extends DecodeOptions {
class TIFFDecoder (line 24) | class TIFFDecoder extends IOBuffer {
method constructor (line 27) | public constructor(data: InputData) {
method isMultiPage (line 32) | public get isMultiPage(): boolean {
method pageCount (line 48) | public get pageCount(): number {
method decode (line 61) | public decode(options: DecodeOptions = {}): TiffIfd[] {
method decodeHeader (line 94) | private decodeHeader(): void {
method decodeIFD (line 116) | private decodeIFD(options: InternalOptions, tiff: boolean): TiffIfd | ...
method decodeIFDEntry (line 143) | private decodeIFDEntry(ifd: IFD): void {
method decodeImageData (line 187) | private decodeImageData(ifd: TiffIfd): void {
method split1BitData (line 222) | private split1BitData(ifd: TiffIfd) {
method uncompress (line 243) | private static uncompress(data: DataView, compression = 1): DataView {
method createSampleReader (line 267) | private createSampleReader(
method readStripData (line 288) | private readStripData(ifd: TiffIfd): void {
method readTileData (line 341) | private readTileData(ifd: TiffIfd): void {
method applyPredictor (line 421) | private applyPredictor(ifd: TiffIfd): void {
method convertAlpha (line 453) | private convertAlpha(ifd: TiffIfd): void {
function getDataArray (line 466) | function getDataArray(
function unsupported (line 487) | function unsupported(type: string, value: any): Error {
function checkPages (line 490) | function checkPages(pages: number[] | undefined) {
FILE: src/tiff_ifd.ts
class TiffIfd (line 6) | class TiffIfd extends Ifd {
method constructor (line 7) | public constructor() {
method size (line 12) | public get size(): number {
method width (line 15) | public get width(): number {
method height (line 18) | public get height(): number {
method components (line 21) | public get components(): number {
method date (line 24) | public get date(): Date {
method newSubfileType (line 40) | public get newSubfileType(): number {
method imageWidth (line 43) | public get imageWidth(): number {
method imageLength (line 46) | public get imageLength(): number {
method bitsPerSample (line 49) | public get bitsPerSample(): number {
method alpha (line 56) | public get alpha(): boolean {
method associatedAlpha (line 61) | public get associatedAlpha(): boolean {
method extraSamples (line 66) | public get extraSamples(): number[] | undefined {
method compression (line 69) | public get compression(): number {
method type (line 72) | public get type(): number {
method fillOrder (line 75) | public get fillOrder(): number {
method documentName (line 78) | public get documentName(): string | undefined {
method imageDescription (line 81) | public get imageDescription(): string | undefined {
method stripOffsets (line 84) | public get stripOffsets(): number[] {
method orientation (line 87) | public get orientation(): number {
method samplesPerPixel (line 90) | public get samplesPerPixel(): number {
method rowsPerStrip (line 93) | public get rowsPerStrip(): number {
method stripByteCounts (line 96) | public get stripByteCounts(): number[] {
method minSampleValue (line 99) | public get minSampleValue(): number {
method maxSampleValue (line 102) | public get maxSampleValue(): number {
method xResolution (line 105) | public get xResolution(): number {
method yResolution (line 108) | public get yResolution(): number {
method planarConfiguration (line 111) | public get planarConfiguration(): number {
method resolutionUnit (line 114) | public get resolutionUnit(): number {
method dateTime (line 117) | public get dateTime(): string {
method predictor (line 120) | public get predictor(): number {
method sampleFormat (line 123) | public get sampleFormat(): number {
method sMinSampleValue (line 127) | public get sMinSampleValue(): number {
method sMaxSampleValue (line 130) | public get sMaxSampleValue(): number {
method palette (line 133) | public get palette(): Array<[number, number, number]> | undefined {
method tileWidth (line 150) | public get tileWidth(): number | undefined {
method tileHeight (line 153) | public get tileHeight(): number | undefined {
method tileOffsets (line 156) | public get tileOffsets(): number[] {
method tileByteCounts (line 159) | public get tileByteCounts(): number[] {
method tiled (line 162) | public get tiled(): boolean {
function alwaysArray (line 172) | function alwaysArray(value: number | number[]): number[] {
FILE: src/types.ts
type DecodeOptions (line 1) | interface DecodeOptions {
type IFDKind (line 9) | type IFDKind = 'standard' | 'exif' | 'gps';
type DataArray (line 11) | type DataArray = Uint8Array | Uint16Array | Float32Array | Float64Array;
FILE: src/zlib.ts
function decompressZlib (line 3) | function decompressZlib(stripData: DataView): DataView {
Condensed preview — 76 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (78K chars).
[
{
"path": ".github/workflows/nodejs.yml",
"chars": 266,
"preview": "name: Node.js CI\n\non:\n push:\n branches:\n - main\n pull_request:\n\njobs:\n nodejs:\n # Documentation: https://g"
},
{
"path": ".github/workflows/release.yml",
"chars": 339,
"preview": "name: Release\n\non:\n push:\n branches:\n - main\n\njobs:\n release:\n # Documentation: https://github.com/zakodium"
},
{
"path": ".github/workflows/typedoc.yml",
"chars": 325,
"preview": "name: TypeDoc\n\non:\n workflow_dispatch:\n release:\n types: [published]\n\njobs:\n typedoc:\n # Documentation: https:/"
},
{
"path": ".gitignore",
"chars": 640,
"preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscov"
},
{
"path": ".npmrc",
"chars": 20,
"preview": "ignore-scripts=true\n"
},
{
"path": ".prettierignore",
"chars": 13,
"preview": "CHANGELOG.md\n"
},
{
"path": ".prettierrc.json",
"chars": 112,
"preview": "{\n \"arrowParens\": \"always\",\n \"semi\": true,\n \"singleQuote\": true,\n \"tabWidth\": 2,\n \"trailingComma\": \"all\"\n}\n"
},
{
"path": "CHANGELOG.md",
"chars": 8656,
"preview": "# Changelog\n\n## [7.1.3](https://github.com/image-js/tiff/compare/v7.1.2...v7.1.3) (2025-11-25)\n\n\n### Bug Fixes\n\n* use ff"
},
{
"path": "LICENSE",
"chars": 1081,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 Michaël Zasso\n\nPermission is hereby granted, free of charge, to any person obt"
},
{
"path": "README.md",
"chars": 2067,
"preview": "<h3 align=\"center\">\n <a href=\"https://www.zakodium.com\">\n <img src=\"https://www.zakodium.com/brand/zakodium-logo-whi"
},
{
"path": "eslint.config.js",
"chars": 195,
"preview": "import { defineConfig, globalIgnores } from 'eslint/config';\nimport cheminfo from 'eslint-config-cheminfo-typescript';\n\n"
},
{
"path": "package.json",
"chars": 1371,
"preview": "{\n \"name\": \"tiff\",\n \"version\": \"7.1.3\",\n \"license\": \"MIT\",\n \"description\": \"TIFF image decoder written entirely in J"
},
{
"path": "src/.npmignore",
"chars": 21,
"preview": "__tests__\n.npmignore\n"
},
{
"path": "src/__tests__/data/1.strip",
"chars": 96,
"preview": "8`@\b$\u0016\r\u0007BaPd6\u001d\u000fDbQ8V-\u0017FcQv=\u001fHdR9$M'JeRd]/LfS9m7NgS}?PhT:%\u0016GRiTe6OTjU:VWVkUv_XlV;%gZmVeo\\nW;w^oW\u0013"
},
{
"path": "src/__tests__/data/173.strip",
"chars": 164,
"preview": "8`@\b$\u0016\r\u0007BaPd6\u001d\u000fDbQ8V-\u0017FcQv=\u001fHdR9$M'JeRd]/H9>fÀ3\u000b73%\u0016<RiT\u0002OTjQ:t,j:v_Ukv\u000b%1趘e/l\\f*Ew۩W©4׀\u0006\u000e_F1\u0013.M|cnNG-'D0ќ\u001efI\u0019Xژ;%C5\u0005k'\u0007"
},
{
"path": "src/__tests__/data/174.strip",
"chars": 166,
"preview": "8`@\b$\u0016\r\u0007BaPd6\u001d\u000fDbQ8V-\u0017FcQv=\u001fHdR9$M'JeRd]/G\u001f9VfgSxt\u0003<Ϧ:%\u0016\u0013QTd^MTjRJ|F\u001b˪VSXlV;%gM-v:Z9\u0004t^hzU'nܯQ\u0005\u0004\u001cU\u0007\u000f\u0006rRkC\u00052r<Fg9b'\u0012\u000eMR"
},
{
"path": "src/__tests__/decode.lzw.test.ts",
"chars": 1438,
"preview": "import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { describe, expect, it } from 'vitest'"
},
{
"path": "src/__tests__/decode.test.ts",
"chars": 12964,
"preview": "import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { expect, test } from 'vitest';\n\nimpor"
},
{
"path": "src/__tests__/is_multi_page.test.ts",
"chars": 868,
"preview": "import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { expect, test } from 'vitest';\n\nimpor"
},
{
"path": "src/__tests__/lzw.test.ts",
"chars": 1338,
"preview": "import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { describe, expect, it } from 'vitest'"
},
{
"path": "src/__tests__/page_count.test.ts",
"chars": 858,
"preview": "import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { expect, test } from 'vitest';\n\nimpor"
},
{
"path": "src/hacks.ts",
"chars": 423,
"preview": "import type TiffIfd from './tiff_ifd.ts';\n\nexport function guessStripByteCounts(ifd: TiffIfd): number[] {\n if (ifd.comp"
},
{
"path": "src/horizontal_differencing.ts",
"chars": 890,
"preview": "// Section 14: Differencing Predictor (p. 64)\n\nexport function applyHorizontalDifferencing8Bit(\n data: Uint8Array,\n wi"
},
{
"path": "src/ifd.ts",
"chars": 1310,
"preview": "import * as exif from './tags/exif.ts';\nimport * as gps from './tags/gps.ts';\nimport * as standard from './tags/standard"
},
{
"path": "src/ifd_value.ts",
"chars": 4215,
"preview": "import type TIFFDecoder from './tiff_decoder.ts';\n\nconst types = new Map<\n number,\n [number, (decoder: TIFFDecoder, co"
},
{
"path": "src/index.ts",
"chars": 953,
"preview": "import type { InputData } from 'iobuffer';\n\nimport { tagsById as exif } from './tags/exif.ts';\nimport { tagsById as gps "
},
{
"path": "src/lzw.ts",
"chars": 4065,
"preview": "import { IOBuffer } from 'iobuffer';\n\nconst CLEAR_CODE = 256;\nconst EOI_CODE = 257;\n// 0-255 from the table + 256 for cl"
},
{
"path": "src/tags/exif.ts",
"chars": 2158,
"preview": "const tagsById: Record<number, string> = {\n 0x829a: 'ExposureTime',\n 0x829d: 'FNumber',\n 0x8822: 'ExposureProgram',\n "
},
{
"path": "src/tags/gps.ts",
"chars": 1086,
"preview": "const tagsById: Record<number, string> = {\n 0x0000: 'GPSVersionID',\n 0x0001: 'GPSLatitudeRef',\n 0x0002: 'GPSLatitude'"
},
{
"path": "src/tags/standard.ts",
"chars": 4905,
"preview": "const tagsById: Record<number, string> = {\n // Baseline tags\n 0x00fe: 'NewSubfileType',\n 0x00ff: 'SubfileType',\n 0x0"
},
{
"path": "src/tiff_decoder.ts",
"chars": 14345,
"preview": "import type { InputData } from 'iobuffer';\nimport { IOBuffer } from 'iobuffer';\n\nimport { guessStripByteCounts } from '."
},
{
"path": "src/tiff_ifd.ts",
"chars": 5008,
"preview": "import Ifd from './ifd.ts';\n\n// eslint-disable-next-line prefer-named-capture-group\nconst dateTimeRegex = /^(\\d{4}):(\\d{"
},
{
"path": "src/types.ts",
"chars": 307,
"preview": "export interface DecodeOptions {\n ignoreImageData?: boolean;\n /**\n * Specify the indices of the pages to decode in c"
},
{
"path": "src/zlib.ts",
"chars": 371,
"preview": "import { decompressSync } from 'fflate';\n\nexport function decompressZlib(stripData: DataView): DataView {\n const stripU"
},
{
"path": "tsconfig.build.json",
"chars": 104,
"preview": "{\n \"extends\": \"./tsconfig.json\",\n \"include\": [\"src\"],\n \"exclude\": [\"**/__tests__\", \"**/*.test.ts\"]\n}\n"
},
{
"path": "tsconfig.json",
"chars": 182,
"preview": "{\n \"extends\": \"@zakodium/tsconfig\",\n \"compilerOptions\": {\n \"noUncheckedIndexedAccess\": false,\n \"types\": [\"node\"]"
}
]
// ... and 40 more files (download for full content)
About this extraction
This page contains the full source code of the image-js/tiff GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 76 files (97.9 MB), approximately 25.5k tokens, and a symbol index with 107 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.