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