From ebf4cfa3b57dfe01a914fa9b35d1eb5581fd7a37 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sun, 20 Oct 2019 15:34:00 +0900 Subject: [PATCH 001/197] Run workflow on push and pull_request. Format YAML with prettier. --- .github/workflows/nodejs.yml | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index b15baf3..89973ab 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -1,10 +1,9 @@ name: Node CI -on: [push] +on: [push, pull_request] jobs: build: - runs-on: ubuntu-latest strategy: @@ -12,15 +11,15 @@ jobs: node-version: [8.x, 10.x, 12.x] steps: - - uses: actions/checkout@v1 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 - with: - node-version: ${{ matrix.node-version }} - - name: npm install, build, and test - run: | - npm install - npm run build --if-present - npm test - env: - CI: true + - uses: actions/checkout@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: npm install, build, and test + run: | + npm install + npm run build --if-present + npm test + env: + CI: true From ae40880049220d8283abd880e3f95c53581548a0 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sun, 3 Nov 2019 10:20:22 +0900 Subject: [PATCH 002/197] Add GitHub Sponsors config. --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..048b1cf --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [blueimp] From 3ed1e0a30da5f63cd7670d22788628776e9e501a Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sat, 21 Dec 2019 21:41:09 +0900 Subject: [PATCH 003/197] Update dev dependencies. --- package-lock.json | 521 +++++++++++++++++++++++++--------------------- 1 file changed, 289 insertions(+), 232 deletions(-) diff --git a/package-lock.json b/package-lock.json index d0bd2b9..39e7529 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,21 +25,21 @@ } }, "@types/node": { - "version": "12.7.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.2.tgz", - "integrity": "sha512-dyYO+f6ihZEtNPDcWNR1fkoTDf3zAK3lAABDze3mz6POyIercH0lEUawUFXlG8xaQZmm1yEBON/4TsYv/laDYg==", + "version": "12.12.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.21.tgz", + "integrity": "sha512-8sRGhbpU+ck1n0PGAUgVrWrWdjSW2aqNeyC15W88GRsMpSwzv6RJGlLhE7s2RhVSOdyDmxbqlWSeThq4/7xqlA==", "dev": true }, "acorn": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz", - "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", "dev": true }, "acorn-jsx": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz", - "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", "dev": true }, "ajv": { @@ -55,10 +55,13 @@ } }, "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } }, "ansi-regex": { "version": "3.0.0", @@ -103,16 +106,13 @@ "dev": true }, "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", - "dev": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } }, "balanced-match": { "version": "1.0.0", @@ -120,6 +120,12 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, + "basic-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", + "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -190,26 +196,26 @@ "dev": true }, "chrome-launcher": { - "version": "0.10.7", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.10.7.tgz", - "integrity": "sha512-IoQLp64s2n8OQuvKZwt77CscVj3UlV2Dj7yZtd1EBMld9mSdGcsGy9fN5hd/r4vJuWZR09it78n1+A17gB+AIQ==", + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.11.2.tgz", + "integrity": "sha512-jx0kJDCXdB2ARcDMwNCtrf04oY1Up4rOmVu+fqJ5MTPOOIG8EhRcEU9NZfXZc6dMw9FU8o1r21PNp8V2M0zQ+g==", "dev": true, "requires": { "@types/node": "*", - "is-wsl": "^1.1.0", + "is-wsl": "^2.1.0", "lighthouse-logger": "^1.0.0", "mkdirp": "0.5.1", "rimraf": "^2.6.1" } }, "chrome-remote-interface": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.27.2.tgz", - "integrity": "sha512-pVLljQ29SAx8KIv5tSa9sIf8GrEsAZdPJoeWOmY3/nrIzFmE+EryNNHvDkddGod0cmAFTv+GmPG0uvzxi2NWsA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/chrome-remote-interface/-/chrome-remote-interface-0.28.1.tgz", + "integrity": "sha512-OnVjEOuZtPDImShaWSQPKPZMNnUnoZfLKhayeXUWOyqir3MT1OTqMzUDEnIVx1itPnsW7CiKgyNLLgvgdniJgQ==", "dev": true, "requires": { "commander": "2.11.x", - "ws": "^6.1.0" + "ws": "^7.2.0" } }, "chrome-unmirror": { @@ -219,12 +225,12 @@ "dev": true }, "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^3.1.0" } }, "cli-width": { @@ -266,9 +272,9 @@ "dev": true }, "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true }, "commander": { @@ -418,15 +424,15 @@ } }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "requires": { "once": "^1.4.0" @@ -448,9 +454,9 @@ "dev": true }, "eslint": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.2.2.tgz", - "integrity": "sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw==", + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -460,19 +466,19 @@ "debug": "^4.0.1", "doctrine": "^3.0.0", "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.2", + "eslint-utils": "^1.4.3", "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.1", + "espree": "^6.1.2", "esquery": "^1.0.1", "esutils": "^2.0.2", "file-entry-cache": "^5.0.1", "functional-red-black-tree": "^1.0.1", "glob-parent": "^5.0.0", - "globals": "^11.7.0", + "globals": "^12.1.0", "ignore": "^4.0.6", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", - "inquirer": "^6.4.1", + "inquirer": "^7.0.0", "is-glob": "^4.0.0", "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", @@ -481,7 +487,7 @@ "minimatch": "^3.0.4", "mkdirp": "^0.5.1", "natural-compare": "^1.4.0", - "optionator": "^0.8.2", + "optionator": "^0.8.3", "progress": "^2.0.0", "regexpp": "^2.0.1", "semver": "^6.1.2", @@ -522,32 +528,32 @@ "dev": true }, "eslint-config-prettier": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.1.0.tgz", - "integrity": "sha512-k9fny9sPjIBQ2ftFTesJV21Rg4R/7a7t7LCtZVrYQiHEp8Nnuk3EGaDmsKSAnsPj0BYcgB2zxzHa2NTkIxcOLg==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.7.0.tgz", + "integrity": "sha512-FamQVKM3jjUVwhG4hEMnbtsq7xOIDm+SY5iBPfR8gKsJoAB2IQnNF+bk1+8Fy44Nq7PPJaLvkRxILYdJWoguKQ==", "dev": true, "requires": { "get-stdin": "^6.0.0" } }, "eslint-plugin-jsdoc": { - "version": "15.8.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-15.8.3.tgz", - "integrity": "sha512-p2O6SYetuSD5gWV04HHevIfp2WfimXReYwINuB4iC33hm1jrYoP+t2XbJtCBYvjhoRjjw8w4NfnyZKixte1fug==", + "version": "15.12.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-15.12.2.tgz", + "integrity": "sha512-QHzPc3VKTEbTn369/HpqDjl/czv3fCei/bZg5NA5tu9Od10MfpTH4kc1xnRDobhQoDs3AMz9wuaI4coHWRzMQw==", "dev": true, "requires": { "comment-parser": "^0.6.2", "debug": "^4.1.1", - "jsdoctypeparser": "5.0.1", + "jsdoctypeparser": "^5.1.1", "lodash": "^4.17.15", "object.entries-ponyfill": "^1.0.1", "regextras": "^0.6.1" } }, "eslint-plugin-prettier": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.0.tgz", - "integrity": "sha512-XWX2yVuwVNLOUhQijAkXz+rMPPoCr7WFiAl8ig6I7Xn+pPVhDhzg4DxHpmbeb0iqjO9UronEA3Tb09ChnFVHHA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", + "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0" @@ -564,12 +570,12 @@ } }, "eslint-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz", - "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", "dev": true, "requires": { - "eslint-visitor-keys": "^1.0.0" + "eslint-visitor-keys": "^1.1.0" } }, "eslint-visitor-keys": { @@ -579,13 +585,13 @@ "dev": true }, "espree": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz", - "integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", "dev": true, "requires": { - "acorn": "^7.0.0", - "acorn-jsx": "^5.0.2", + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", "eslint-visitor-keys": "^1.1.0" } }, @@ -626,9 +632,9 @@ "dev": true }, "eventemitter3": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz", - "integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.0.tgz", + "integrity": "sha512-qerSRB0p+UDEssxTtm6EDKcE7W4OaoisfIMl4CngyEhjpYglocpNg6UEqCvemdGhosAsg4sO2dXJOdyBifPGCg==", "dev": true }, "execa": { @@ -670,9 +676,9 @@ "dev": true }, "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, "fast-levenshtein": { @@ -682,9 +688,9 @@ "dev": true }, "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", + "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", "dev": true, "requires": { "escape-string-regexp": "^1.0.5" @@ -726,12 +732,23 @@ "dev": true }, "follow-redirects": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.8.0.tgz", - "integrity": "sha512-eYyazyi+vwXZ6LfSQicvqFwaNEF5xTvnB/rpzRLuqwK45u7WbBEnQ/dDic66KD/A8IzTXFlj2ROAcaP0f2v4lg==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.9.0.tgz", + "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", "dev": true, "requires": { - "debug": "^4.1.1" + "debug": "^3.0.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } } }, "fs.realpath": { @@ -784,9 +801,9 @@ } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -798,24 +815,27 @@ } }, "glob-parent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz", - "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", "dev": true, "requires": { "is-glob": "^4.0.1" } }, "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", + "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } }, "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, "has-flag": { @@ -831,36 +851,38 @@ "dev": true }, "hosted-git-info": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", - "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "dev": true }, "http-proxy": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz", - "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.0.tgz", + "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", "dev": true, "requires": { - "eventemitter3": "^3.0.0", + "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" } }, "http-server": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.11.1.tgz", - "integrity": "sha512-6JeGDGoujJLmhjiRGlt8yK8Z9Kl0vnl/dQoQZlc4oeqaUoAKQg94NILLfrY3oWzSyFaQCVNTcKE5PZ3cH8VP9w==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.0.tgz", + "integrity": "sha512-imGLDSTT1BZ0QG1rBFnaZ6weK5jeisUnCxZQI1cpYTdz0luPUM5e3s+WU5zRWEkiI6DQxL2p54oeKrDlzO6bRw==", "dev": true, "requires": { - "colors": "1.0.3", - "corser": "~2.0.0", - "ecstatic": "^3.0.0", - "http-proxy": "^1.8.1", - "opener": "~1.4.0", - "optimist": "0.6.x", - "portfinder": "^1.0.13", - "union": "~0.4.3" + "basic-auth": "^1.0.3", + "colors": "^1.3.3", + "corser": "^2.0.1", + "ecstatic": "^3.3.2", + "http-proxy": "^1.17.0", + "opener": "^1.5.1", + "optimist": "~0.6.1", + "portfinder": "^1.0.20", + "secure-compare": "3.0.1", + "union": "~0.5.0" } }, "iconv-lite": { @@ -879,9 +901,9 @@ "dev": true }, "import-fresh": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz", - "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -927,32 +949,60 @@ "dev": true }, "inquirer": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", - "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.1.tgz", + "integrity": "sha512-V1FFQ3TIO15det8PijPLFR9M9baSlnRs9nL7zWu1MNVA2T9YVl9ZbrHJhYs7e9X8jeMZ3lr2JH/rdHFgNCBdYw==", "dev": true, "requires": { - "ansi-escapes": "^3.2.0", + "ansi-escapes": "^4.2.1", "chalk": "^2.4.2", - "cli-cursor": "^2.1.0", + "cli-cursor": "^3.1.0", "cli-width": "^2.0.0", "external-editor": "^3.0.3", - "figures": "^2.0.0", - "lodash": "^4.17.12", - "mute-stream": "0.0.7", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", "run-async": "^2.2.0", - "rxjs": "^6.4.0", - "string-width": "^2.1.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" }, "dependencies": { "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, "strip-ansi": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", @@ -960,6 +1010,14 @@ "dev": true, "requires": { "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } } } } @@ -1022,9 +1080,9 @@ "dev": true }, "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", + "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", "dev": true }, "isexe": { @@ -1050,9 +1108,9 @@ } }, "jsdoctypeparser": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-5.0.1.tgz", - "integrity": "sha512-dYwcK6TKzvq+ZKtbp4sbQSW9JMo6s+4YFfUs5D/K7bZsn3s1NhEhZ+jmIPzby0HbkbECBe+hNPEa6a+E21o94w==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-5.1.1.tgz", + "integrity": "sha512-APGygIJrT5bbz5lsVt8vyLJC0miEbQf/z9ZBfTr4RYvdia8AhWMRlYgivvwHG5zKD/VW3d6qpChCy64hpQET3A==", "dev": true }, "json-parse-better-errors": { @@ -1148,9 +1206,9 @@ "dev": true }, "loglevel": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.3.tgz", - "integrity": "sha512-LoEDv5pgpvWgPF4kNYuIp0qqSJVWak/dML0RY74xlzMZiT9w77teNAwKYKWBTYjlokMirg+o3jBwp+vlLrcfAA==", + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.6.tgz", + "integrity": "sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ==", "dev": true }, "loud-rejection": { @@ -1276,14 +1334,14 @@ } }, "mocha-chrome": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mocha-chrome/-/mocha-chrome-2.0.0.tgz", - "integrity": "sha512-Kq6W9jdXY3C2PhNHtSrk3GnDuoAKN+DbgJKCLfXtc5cql8oHB8+rUYlq9t1c8in6vQ6/X432E/U8h0pV5QlAug==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/mocha-chrome/-/mocha-chrome-2.2.0.tgz", + "integrity": "sha512-RXP6Q2mlM2X+eO2Z8gribmiH4J9x5zu/JcTZ3deQSwiC5260BzizOc0eD1NWP3JuypGCKRwReicv4KCNIFtTZQ==", "dev": true, "requires": { "chalk": "^2.0.1", - "chrome-launcher": "^0.10.2", - "chrome-remote-interface": "^0.27.0", + "chrome-launcher": "^0.11.2", + "chrome-remote-interface": "^0.28.0", "chrome-unmirror": "^0.1.0", "debug": "^4.1.1", "deep-assign": "^3.0.0", @@ -1300,9 +1358,9 @@ "dev": true }, "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, "nanoassert": { @@ -1396,26 +1454,18 @@ } }, "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", "dev": true, "requires": { - "mimic-fn": "^1.0.0" - }, - "dependencies": { - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - } + "mimic-fn": "^2.1.0" } }, "opener": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz", - "integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", + "integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==", "dev": true }, "optimist": { @@ -1426,28 +1476,20 @@ "requires": { "minimist": "~0.0.1", "wordwrap": "~0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } } }, "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, "requires": { "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", + "fast-levenshtein": "~2.0.6", "levn": "~0.3.0", "prelude-ls": "~1.1.2", "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "word-wrap": "~1.2.3" } }, "os-locale": { @@ -1577,30 +1619,24 @@ } }, "portfinder": { - "version": "1.0.23", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.23.tgz", - "integrity": "sha512-B729mL/uLklxtxuiJKfQ84WPxNw5a7Yhx3geQZdcA4GjNjZSTSSMMWyoennMVnTWSmAR0lMdzWYN0JLnHrg1KQ==", + "version": "1.0.25", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", + "integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", "dev": true, "requires": { - "async": "^1.5.2", - "debug": "^2.2.0", - "mkdirp": "0.5.x" + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.1" }, "dependencies": { "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true } } }, @@ -1611,9 +1647,9 @@ "dev": true }, "prettier": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.18.2.tgz", - "integrity": "sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==", + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", "dev": true }, "prettier-linter-helpers": { @@ -1648,9 +1684,9 @@ "dev": true }, "qs": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", - "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==", "dev": true }, "quick-lru": { @@ -1783,9 +1819,9 @@ "dev": true }, "resolve": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.14.1.tgz", + "integrity": "sha512-fn5Wobh4cxbLzuHaE+nphztHy43/b++4M6SsGFC2gB8uYwf0C8LcarfCz1un7UTW8OFQg9iNjZ4xpcFVGebDPg==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -1815,12 +1851,12 @@ "dev": true }, "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "requires": { - "onetime": "^2.0.0", + "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, @@ -1843,9 +1879,9 @@ } }, "rxjs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz", - "integrity": "sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz", + "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==", "dev": true, "requires": { "tslib": "^1.9.0" @@ -1857,6 +1893,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", + "dev": true + }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", @@ -2029,6 +2071,12 @@ "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -2073,9 +2121,9 @@ } }, "tree-kill": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.1.tgz", - "integrity": "sha512-4hjqbObwlh2dLyW4tcz0Ymw0ggoaVDMveUB9w8kFSQScdRLo0gxO9J7WFcUBo+W3C1TLdFIEwNOWebgZZ0RH9Q==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true }, "trim-newlines": { @@ -2099,31 +2147,37 @@ "prelude-ls": "~1.1.2" } }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.2.tgz", + "integrity": "sha512-uhRwZcANNWVLrxLfNFEdltoPNhECUR3lc+UdJoG9CBpMcSnKyWA94tc3eAujB1GcMY5Uwq8ZMp4qWpxWYDQmaA==", "dev": true, "requires": { - "commander": "~2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" }, "dependencies": { "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true } } }, "union": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/union/-/union-0.4.6.tgz", - "integrity": "sha1-GY+9rrolTniLDvy2MLwR8kopWeA=", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", "dev": true, "requires": { - "qs": "~2.3.3" + "qs": "^6.4.0" } }, "uri-js": { @@ -2172,10 +2226,16 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, "wrap-ansi": { @@ -2241,13 +2301,10 @@ } }, "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.1.tgz", + "integrity": "sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A==", + "dev": true }, "y18n": { "version": "4.0.0", From 9326d2ae5bbfd7e9c9cb7e00264030756746d389 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sat, 21 Dec 2019 21:42:15 +0900 Subject: [PATCH 004/197] Check if blob argument is a Blob instance. --- js/load-image.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/load-image.js b/js/load-image.js index 0119c63..2e158f5 100644 --- a/js/load-image.js +++ b/js/load-image.js @@ -37,7 +37,7 @@ loadImage.fetchBlob( file, function(blob) { - if (blob) { + if (blob && loadImage.isInstanceOf('Blob', blob)) { // eslint-disable-next-line no-param-reassign file = blob url = loadImage.createObjectURL(file) From f1a1080b82247bb1bf6a255125a4832101368b8d Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sat, 21 Dec 2019 21:46:24 +0900 Subject: [PATCH 005/197] Add XHR2 fallback if fetch API is not available. This adds support to parse meta data for images loaded via URL for Internet Explorer 10+. --- js/load-image-fetch.js | 35 +++++++++++++++++++++++++++++++++-- test/test.js | 5 ++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/js/load-image-fetch.js b/js/load-image-fetch.js index 5dcd119..ab64665 100644 --- a/js/load-image-fetch.js +++ b/js/load-image-fetch.js @@ -28,7 +28,7 @@ if (typeof fetch !== 'undefined' && typeof Request !== 'undefined') { loadImage.fetchBlob = function(url, callback, options) { if (loadImage.hasMetaOption(options)) { - return fetch(new Request(url, options)) + fetch(new Request(url, options)) .then(function(response) { return response.blob() }) @@ -37,8 +37,39 @@ console.log(err) // eslint-disable-line no-console callback() }) + } else { + callback() + } + } + } else if ( + // Check for XHR2 support: + typeof XMLHttpRequest !== 'undefined' && + typeof ProgressEvent !== 'undefined' + ) { + loadImage.fetchBlob = function(url, callback, options) { + if (loadImage.hasMetaOption(options)) { + // eslint-disable-next-line no-param-reassign + options = options || {} + var req = new XMLHttpRequest() + req.open(options.method || 'GET', url) + if (options.headers) { + Object.keys(options.headers).forEach(function(key) { + req.setRequestHeader(key, options.headers[key]) + }) + } + req.withCredentials = options.credentials === 'include' + req.responseType = 'blob' + req.onload = function() { + callback(req.response) + } + req.onerror = req.onabort = req.ontimeout = function(e) { + console.log(e) // eslint-disable-line no-console + callback() + } + req.send(options.body) + } else { + callback() } - callback() } } }) diff --git a/test/test.js b/test/test.js index 09f4cb3..fc1d1a7 100644 --- a/test/test.js +++ b/test/test.js @@ -875,7 +875,10 @@ }) }) - if ('fetch' in window && 'Request' in window) { + if ( + ('fetch' in window && 'Request' in window) || + ('XMLHttpRequest' in window && 'ProgressEvent' in window) + ) { describe('Fetch', function() { it('Should fetch image URL as blob if meta option is true', function(done) { expect( From e9343c4a00d052cb9c0bfd2c04527ade1a800896 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sat, 21 Dec 2019 21:47:41 +0900 Subject: [PATCH 006/197] Add loading images via URL support to the demo. --- index.html | 8 ++++++- js/demo/demo.js | 58 +++++++++++++++++++++++++++++-------------------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/index.html b/index.html index 2240e6e..3610e16 100644 --- a/index.html +++ b/index.html @@ -72,7 +72,13 @@

JavaScript Load Image Demo

  • © Sebastian Tschan
  • Select an image file

    -

    +

    + +

    +

    Or enter an image URL into the following field:

    +

    + +

    Or drag & drop an image file onto this webpage.

    Result

    Result

    diff --git a/js/demo/demo.js b/js/demo/demo.js index cd9b62d..6dc9fa3 100644 --- a/js/demo/demo.js +++ b/js/demo/demo.js @@ -29,12 +29,16 @@ $(function () { * @param {object} tags Tags object */ function displayTagData(node, tags) { - var table = node.find('table').empty() + var table = $('') var row = $('') var cell = $('') var prop for (prop in tags) { if (Object.prototype.hasOwnProperty.call(tags, prop)) { + if (typeof tags[prop] === 'object') { + displayTagData(node, tags[prop]) + continue + } table.append( row .clone() @@ -43,7 +47,7 @@ $(function () { ) } } - node.show() + node.append(table).show() } /** @@ -55,11 +59,15 @@ $(function () { */ function displayThumbnailImage(node, thumbnail, options) { if (thumbnail) { - thumbNode.empty() + var link = $('') + .attr('href', loadImage.createObjectURL(thumbnail)) + .attr('download', 'thumbnail.jpg') + .appendTo(node) loadImage( thumbnail, function (img) { - node.append(img).show() + link.append(img) + node.show() }, options ) @@ -136,9 +144,9 @@ $(function () { orientation: true, meta: true } - exifNode.hide() - iptcNode.hide() - thumbNode.hide() + exifNode.hide().find('table').remove() + iptcNode.hide().find('table').remove() + thumbNode.hide().empty() if (!loadImage(file, updateResults, options)) { result .children() diff --git a/js/load-image-exif-map.js b/js/load-image-exif-map.js index 68d90d3..9684ae8 100644 --- a/js/load-image-exif-map.js +++ b/js/load-image-exif-map.js @@ -28,15 +28,14 @@ })(function (loadImage) { 'use strict' - loadImage.ExifMap.prototype.tags = { + var ExifMapProto = loadImage.ExifMap.prototype + + ExifMapProto.tags = { // ================= // TIFF tags (IFD0): // ================= 0x0100: 'ImageWidth', 0x0101: 'ImageHeight', - 0x8769: 'ExifIFDPointer', - 0x8825: 'GPSInfoIFDPointer', - 0xa005: 'InteroperabilityIFDPointer', 0x0102: 'BitsPerSample', 0x0103: 'Compression', 0x0106: 'PhotometricInterpretation', @@ -65,116 +64,120 @@ 0x0131: 'Software', 0x013b: 'Artist', 0x8298: 'Copyright', - // ================== - // Exif Sub IFD tags: - // ================== - 0x9000: 'ExifVersion', // EXIF version - 0xa000: 'FlashpixVersion', // Flashpix format version - 0xa001: 'ColorSpace', // Color space information tag - 0xa002: 'PixelXDimension', // Valid width of meaningful image - 0xa003: 'PixelYDimension', // Valid height of meaningful image - 0xa500: 'Gamma', - 0x9101: 'ComponentsConfiguration', // Information about channels - 0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel - 0x927c: 'MakerNote', // Any desired information written by the manufacturer - 0x9286: 'UserComment', // Comments by user - 0xa004: 'RelatedSoundFile', // Name of related sound file - 0x9003: 'DateTimeOriginal', // Date and time when the original image was generated - 0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally - 0x9290: 'SubSecTime', // Fractions of seconds for DateTime - 0x9291: 'SubSecTimeOriginal', // Fractions of seconds for DateTimeOriginal - 0x9292: 'SubSecTimeDigitized', // Fractions of seconds for DateTimeDigitized - 0x829a: 'ExposureTime', // Exposure time (in seconds) - 0x829d: 'FNumber', - 0x8822: 'ExposureProgram', // Exposure program - 0x8824: 'SpectralSensitivity', // Spectral sensitivity - 0x8827: 'PhotographicSensitivity', // EXIF 2.3, ISOSpeedRatings in EXIF 2.2 - 0x8828: 'OECF', // Optoelectric conversion factor - 0x8830: 'SensitivityType', - 0x8831: 'StandardOutputSensitivity', - 0x8832: 'RecommendedExposureIndex', - 0x8833: 'ISOSpeed', - 0x8834: 'ISOSpeedLatitudeyyy', - 0x8835: 'ISOSpeedLatitudezzz', - 0x9201: 'ShutterSpeedValue', // Shutter speed - 0x9202: 'ApertureValue', // Lens aperture - 0x9203: 'BrightnessValue', // Value of brightness - 0x9204: 'ExposureBias', // Exposure bias - 0x9205: 'MaxApertureValue', // Smallest F number of lens - 0x9206: 'SubjectDistance', // Distance to subject in meters - 0x9207: 'MeteringMode', // Metering mode - 0x9208: 'LightSource', // Kind of light source - 0x9209: 'Flash', // Flash status - 0x9214: 'SubjectArea', // Location and area of main subject - 0x920a: 'FocalLength', // Focal length of the lens in mm - 0xa20b: 'FlashEnergy', // Strobe energy in BCPS - 0xa20c: 'SpatialFrequencyResponse', - 0xa20e: 'FocalPlaneXResolution', // Number of pixels in width direction per FPRUnit - 0xa20f: 'FocalPlaneYResolution', // Number of pixels in height direction per FPRUnit - 0xa210: 'FocalPlaneResolutionUnit', // Unit for measuring the focal plane resolution - 0xa214: 'SubjectLocation', // Location of subject in image - 0xa215: 'ExposureIndex', // Exposure index selected on camera - 0xa217: 'SensingMethod', // Image sensor type - 0xa300: 'FileSource', // Image source (3 == DSC) - 0xa301: 'SceneType', // Scene type (1 == directly photographed) - 0xa302: 'CFAPattern', // Color filter array geometric pattern - 0xa401: 'CustomRendered', // Special processing - 0xa402: 'ExposureMode', // Exposure mode - 0xa403: 'WhiteBalance', // 1 = auto white balance, 2 = manual - 0xa404: 'DigitalZoomRatio', // Digital zoom ratio - 0xa405: 'FocalLengthIn35mmFilm', - 0xa406: 'SceneCaptureType', // Type of scene - 0xa407: 'GainControl', // Degree of overall image gain adjustment - 0xa408: 'Contrast', // Direction of contrast processing applied by camera - 0xa409: 'Saturation', // Direction of saturation processing applied by camera - 0xa40a: 'Sharpness', // Direction of sharpness processing applied by camera - 0xa40b: 'DeviceSettingDescription', - 0xa40c: 'SubjectDistanceRange', // Distance to subject - 0xa420: 'ImageUniqueID', // Identifier assigned uniquely to each image - 0xa430: 'CameraOwnerName', - 0xa431: 'BodySerialNumber', - 0xa432: 'LensSpecification', - 0xa433: 'LensMake', - 0xa434: 'LensModel', - 0xa435: 'LensSerialNumber', - // ============== - // GPS Info tags: - // ============== - 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' + 0x8769: { + // ExifIFDPointer + 0x9000: 'ExifVersion', // EXIF version + 0xa000: 'FlashpixVersion', // Flashpix format version + 0xa001: 'ColorSpace', // Color space information tag + 0xa002: 'PixelXDimension', // Valid width of meaningful image + 0xa003: 'PixelYDimension', // Valid height of meaningful image + 0xa500: 'Gamma', + 0x9101: 'ComponentsConfiguration', // Information about channels + 0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel + 0x927c: 'MakerNote', // Any desired information written by the manufacturer + 0x9286: 'UserComment', // Comments by user + 0xa004: 'RelatedSoundFile', // Name of related sound file + 0x9003: 'DateTimeOriginal', // Date and time when the original image was generated + 0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally + 0x9290: 'SubSecTime', // Fractions of seconds for DateTime + 0x9291: 'SubSecTimeOriginal', // Fractions of seconds for DateTimeOriginal + 0x9292: 'SubSecTimeDigitized', // Fractions of seconds for DateTimeDigitized + 0x829a: 'ExposureTime', // Exposure time (in seconds) + 0x829d: 'FNumber', + 0x8822: 'ExposureProgram', // Exposure program + 0x8824: 'SpectralSensitivity', // Spectral sensitivity + 0x8827: 'PhotographicSensitivity', // EXIF 2.3, ISOSpeedRatings in EXIF 2.2 + 0x8828: 'OECF', // Optoelectric conversion factor + 0x8830: 'SensitivityType', + 0x8831: 'StandardOutputSensitivity', + 0x8832: 'RecommendedExposureIndex', + 0x8833: 'ISOSpeed', + 0x8834: 'ISOSpeedLatitudeyyy', + 0x8835: 'ISOSpeedLatitudezzz', + 0x9201: 'ShutterSpeedValue', // Shutter speed + 0x9202: 'ApertureValue', // Lens aperture + 0x9203: 'BrightnessValue', // Value of brightness + 0x9204: 'ExposureBias', // Exposure bias + 0x9205: 'MaxApertureValue', // Smallest F number of lens + 0x9206: 'SubjectDistance', // Distance to subject in meters + 0x9207: 'MeteringMode', // Metering mode + 0x9208: 'LightSource', // Kind of light source + 0x9209: 'Flash', // Flash status + 0x9214: 'SubjectArea', // Location and area of main subject + 0x920a: 'FocalLength', // Focal length of the lens in mm + 0xa20b: 'FlashEnergy', // Strobe energy in BCPS + 0xa20c: 'SpatialFrequencyResponse', + 0xa20e: 'FocalPlaneXResolution', // Number of pixels in width direction per FPRUnit + 0xa20f: 'FocalPlaneYResolution', // Number of pixels in height direction per FPRUnit + 0xa210: 'FocalPlaneResolutionUnit', // Unit for measuring the focal plane resolution + 0xa214: 'SubjectLocation', // Location of subject in image + 0xa215: 'ExposureIndex', // Exposure index selected on camera + 0xa217: 'SensingMethod', // Image sensor type + 0xa300: 'FileSource', // Image source (3 == DSC) + 0xa301: 'SceneType', // Scene type (1 == directly photographed) + 0xa302: 'CFAPattern', // Color filter array geometric pattern + 0xa401: 'CustomRendered', // Special processing + 0xa402: 'ExposureMode', // Exposure mode + 0xa403: 'WhiteBalance', // 1 = auto white balance, 2 = manual + 0xa404: 'DigitalZoomRatio', // Digital zoom ratio + 0xa405: 'FocalLengthIn35mmFilm', + 0xa406: 'SceneCaptureType', // Type of scene + 0xa407: 'GainControl', // Degree of overall image gain adjustment + 0xa408: 'Contrast', // Direction of contrast processing applied by camera + 0xa409: 'Saturation', // Direction of saturation processing applied by camera + 0xa40a: 'Sharpness', // Direction of sharpness processing applied by camera + 0xa40b: 'DeviceSettingDescription', + 0xa40c: 'SubjectDistanceRange', // Distance to subject + 0xa420: 'ImageUniqueID', // Identifier assigned uniquely to each image + 0xa430: 'CameraOwnerName', + 0xa431: 'BodySerialNumber', + 0xa432: 'LensSpecification', + 0xa433: 'LensMake', + 0xa434: 'LensModel', + 0xa435: 'LensSerialNumber' + }, + 0x8825: { + // GPSInfoIFDPointer + 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' + }, + 0xa005: { + // InteroperabilityIFDPointer + 0x0001: 'InteroperabilityIndex' + } } - loadImage.ExifMap.prototype.stringValues = { + ExifMapProto.stringValues = { ExposureProgram: { 0: 'Undefined', 1: 'Manual', @@ -321,9 +324,9 @@ } } - loadImage.ExifMap.prototype.getText = function (id) { - var value = this.get(id) - switch (id) { + ExifMapProto.getText = function (name) { + var value = this.get(name) + switch (name) { case 'LightSource': case 'Flash': case 'MeteringMode': @@ -340,7 +343,7 @@ case 'SubjectDistanceRange': case 'FileSource': case 'Orientation': - return this.stringValues[id][value] + return this.stringValues[name][value] case 'ExifVersion': case 'FlashpixVersion': if (!value) return @@ -348,10 +351,10 @@ case 'ComponentsConfiguration': if (!value) return return ( - this.stringValues[id][value[0]] + - this.stringValues[id][value[1]] + - this.stringValues[id][value[2]] + - this.stringValues[id][value[3]] + this.stringValues[name][value[0]] + + this.stringValues[name][value[1]] + + this.stringValues[name][value[2]] + + this.stringValues[name][value[3]] ) case 'GPSVersionID': if (!value) return @@ -359,30 +362,53 @@ } return String(value) } - ;(function (exifMapPrototype) { - var tags = exifMapPrototype.tags - var map = exifMapPrototype.map - var prop - // Map the tag names to tags: - for (prop in tags) { - if (Object.prototype.hasOwnProperty.call(tags, prop)) { - map[tags[prop]] = prop - } - } - })(loadImage.ExifMap.prototype) - loadImage.ExifMap.prototype.getAll = function () { + ExifMapProto.getAll = function () { var map = {} var prop - var id + var obj + var name for (prop in this) { if (Object.prototype.hasOwnProperty.call(this, prop)) { - id = this.tags[prop] - if (id) { - map[id] = this.getText(id) + obj = this[prop] + if (obj && obj.getAll) { + map[this.privateIFDs[prop].name] = obj.getAll() + } else { + name = this.tags[prop] + if (name) map[name] = this.getText(name) } } } return map } + + ExifMapProto.getName = function (tagCode) { + var name = this.tags[tagCode] + if (typeof name === 'object') return this.privateIFDs[tagCode].name + return name + } + + // Extend the map of tag names to tag codes: + ;(function () { + var tags = ExifMapProto.tags + var prop + var privateIFD + var subTags + // Map the tag names to tags: + for (prop in tags) { + if (Object.prototype.hasOwnProperty.call(tags, prop)) { + privateIFD = ExifMapProto.privateIFDs[prop] + if (privateIFD) { + subTags = tags[prop] + for (prop in subTags) { + if (Object.prototype.hasOwnProperty.call(subTags, prop)) { + privateIFD.map[subTags[prop]] = Number(prop) + } + } + } else { + ExifMapProto.map[tags[prop]] = Number(prop) + } + } + } + })() }) diff --git a/js/load-image-exif.js b/js/load-image-exif.js index 74c2632..7dff053 100644 --- a/js/load-image-exif.js +++ b/js/load-image-exif.js @@ -27,28 +27,67 @@ })(function (loadImage) { 'use strict' - loadImage.ExifMap = function () { - return this + /** + * Exif tag map + * + * @name ExifMap + * @class + * @param {number} tagCode Private IFD tag code + */ + function ExifMap(tagCode) { + if (tagCode) { + Object.defineProperty(this, 'map', { + value: this.privateIFDs[tagCode].map + }) + Object.defineProperty(this, 'tags', { + value: (this.tags && this.tags[tagCode]) || {} + }) + } } - loadImage.ExifMap.prototype.map = { + ExifMap.prototype.map = { Orientation: 0x0112, - Thumbnail: 0x0201 + Thumbnail: 0x0201, + Exif: 0x8769, + GPSInfo: 0x8825, + Interoperability: 0xa005 + } + + ExifMap.prototype.privateIFDs = { + 0x8769: { name: 'Exif', map: {} }, + 0x8825: { name: 'GPSInfo', map: {} }, + 0xa005: { name: 'Interoperability', map: {} } } - loadImage.ExifMap.prototype.get = function (id) { + /** + * Retrieves exif tag value + * + * @param {number|string} id Exif tag code or name + * @returns {object} Exif tag value + */ + ExifMap.prototype.get = function (id) { return this[id] || this[this.map[id]] } - loadImage.getExifThumbnail = function (dataView, offset, length) { + /** + * Returns the Exif Thumbnail data as Blob. + * + * @param {DataView} dataView Data view interface + * @param {number} offset Thumbnail data offset + * @param {number} length Thumbnail data length + * @returns {undefined|Blob} Returns the Thumbnail Blob or undefined + */ + function getExifThumbnail(dataView, offset, length) { if (!length || offset + length > dataView.byteLength) { console.log('Invalid Exif data: Invalid thumbnail data.') return } - return new Blob([dataView.buffer.slice(offset, offset + length)]) + return new Blob([dataView.buffer.slice(offset, offset + length)], { + type: 'image/jpeg' + }) } - loadImage.exifTagTypes = { + var ExifTagTypes = { // byte, 8-bit unsigned int: 1: { getValue: function (dataView, dataOffset) { @@ -107,9 +146,20 @@ } } // undefined, 8-bit byte, value depending on field: - loadImage.exifTagTypes[7] = loadImage.exifTagTypes[1] + ExifTagTypes[7] = ExifTagTypes[1] - loadImage.getExifValue = function ( + /** + * Returns Exif tag value. + * + * @param {DataView} dataView Data view interface + * @param {number} tiffOffset TIFF offset + * @param {number} offset Tag offset + * @param {number} type Tag type + * @param {number} length Tag length + * @param {boolean} littleEndian Little endian encoding + * @returns {object} Tag value + */ + function getExifValue( dataView, tiffOffset, offset, @@ -117,7 +167,7 @@ length, littleEndian ) { - var tagType = loadImage.exifTagTypes[type] + var tagType = ExifTagTypes[type] var tagSize var dataOffset var values @@ -166,33 +216,30 @@ return values } - loadImage.parseExifTag = function ( - dataView, - tiffOffset, - offset, - littleEndian - ) { - return { - number: dataView.getUint16(offset, littleEndian), - value: loadImage.getExifValue( - dataView, - tiffOffset, - offset, - dataView.getUint16(offset + 2, littleEndian), // tag type - dataView.getUint32(offset + 4, littleEndian), // tag length - littleEndian - ) - } - } - - loadImage.parseExifTags = function ( + /** + * Parses Exif tags. + * + * @param {DataView} dataView Data view interface + * @param {number} tiffOffset TIFF offset + * @param {number} dirOffset Directory offset + * @param {boolean} littleEndian Little endian encoding + * @param {ExifMap} tags Map to store parsed exif tags + * @param {ExifMap} tagOffsets Map to store parsed exif tag offsets + * @param {object} includeTags Map of tags to include + * @param {object} excludeTags Map of tags to exclude + * @returns {number} Next directory offset + */ + function parseExifTags( dataView, tiffOffset, dirOffset, littleEndian, - data + tags, + tagOffsets, + includeTags, + excludeTags ) { - var tagsNumber, dirEndOffset, i, tagOffset, tag + var tagsNumber, dirEndOffset, i, tagOffset, tagNumber, tagValue if (dirOffset + 6 > dataView.byteLength) { console.log('Invalid Exif data: Invalid directory offset.') return @@ -205,29 +252,80 @@ } for (i = 0; i < tagsNumber; i += 1) { tagOffset = dirOffset + 2 + 12 * i - tag = this.parseExifTag( + tagNumber = dataView.getUint16(tagOffset, littleEndian) + if (includeTags && !includeTags[tagNumber]) continue + if (excludeTags && excludeTags[tagNumber] === true) continue + tagValue = getExifValue( dataView, tiffOffset, tagOffset, - littleEndian, - data + dataView.getUint16(tagOffset + 2, littleEndian), // tag type + dataView.getUint32(tagOffset + 4, littleEndian), // tag length + littleEndian ) - data.exif[tag.number] = tag.value - if (data.exifOffsets) { - data.exifOffsets[tag.number] = tagOffset + tags[tagNumber] = tagValue + if (tagOffsets) { + tagOffsets[tagNumber] = tagOffset } } // Return the offset to the next directory: return dataView.getUint32(dirEndOffset, littleEndian) } + /** + * Parses Private IFD tags. + * + * @param {object} data Data object to store exif tags and offsets + * @param {number} tagCode Private IFD tag code + * @param {DataView} dataView Data view interface + * @param {number} tiffOffset TIFF offset + * @param {boolean} littleEndian Little endian encoding + * @param {object} includeTags Map of tags to include + * @param {object} excludeTags Map of tags to exclude + */ + function parseExifPrivateIFD( + data, + tagCode, + dataView, + tiffOffset, + littleEndian, + includeTags, + excludeTags + ) { + var dirOffset = data.exif[tagCode] + if (dirOffset) { + data.exif[tagCode] = new ExifMap(tagCode) + if (data.exifOffsets) { + data.exifOffsets[tagCode] = new ExifMap(tagCode) + } + parseExifTags( + dataView, + tiffOffset, + tiffOffset + dirOffset, + littleEndian, + data.exif[tagCode], + data.exifOffsets && data.exifOffsets[tagCode], + includeTags && includeTags[tagCode], + excludeTags && excludeTags[tagCode] + ) + } + } + loadImage.parseExifData = function (dataView, offset, length, data, options) { if (options.disableExif) { return } + var includeTags = options.includeExifTags + var excludeTags = options.excludeExifTags || { + 0x8769: { + // ExifIFDPointer + 0x927c: true // MakerNote + } + } var tiffOffset = offset + 10 var littleEndian var dirOffset + var privateIFDs // Check for the ASCII code for "Exif" (0x45786966): if (dataView.getUint32(offset + 4) !== 0x45786966) { // No Exif data, might be XMP data instead @@ -262,58 +360,56 @@ // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian) // Create the exif object to store the tags: - data.exif = new loadImage.ExifMap() + data.exif = new ExifMap() if (!options.disableExifOffsets) { - data.exifOffsets = new loadImage.ExifMap() + data.exifOffsets = new ExifMap() data.exifTiffOffset = tiffOffset data.exifLittleEndian = littleEndian } // Parse the tags of the main image directory and retrieve the // offset to the next directory, usually the thumbnail directory: - dirOffset = loadImage.parseExifTags( + dirOffset = parseExifTags( dataView, tiffOffset, tiffOffset + dirOffset, littleEndian, - data + data.exif, + data.exifOffsets, + includeTags, + excludeTags ) if (dirOffset && !options.disableExifThumbnail) { - dirOffset = loadImage.parseExifTags( + dirOffset = parseExifTags( dataView, tiffOffset, tiffOffset + dirOffset, littleEndian, - data + data.exif, + data.exifOffsets, + includeTags, + excludeTags ) // Check for JPEG Thumbnail offset: - if (data.exif[0x0201]) { - data.exif[0x0201] = loadImage.getExifThumbnail( + if (data.exif[0x0201] && data.exif[0x0202]) { + data.exif[0x0201] = getExifThumbnail( dataView, tiffOffset + data.exif[0x0201], data.exif[0x0202] // Thumbnail data length ) } } - // Check for Exif Sub IFD Pointer: - if (data.exif[0x8769] && !options.disableExifSub) { - loadImage.parseExifTags( - dataView, - tiffOffset, - tiffOffset + data.exif[0x8769], // directory offset - littleEndian, - data - ) - } - // Check for GPS Info IFD Pointer: - if (data.exif[0x8825] && !options.disableExifGps) { - loadImage.parseExifTags( + privateIFDs = Object.keys(data.exif.privateIFDs) + privateIFDs.forEach(function (tagCode) { + parseExifPrivateIFD( + data, + tagCode, dataView, tiffOffset, - tiffOffset + data.exif[0x8825], // directory offset littleEndian, - data + includeTags, + excludeTags ) - } + }) } // Registers the Exif parser for the APP1 JPEG meta data segment: @@ -332,16 +428,18 @@ loadImage.exifWriters[data.exif.map[id]](buffer, data, value) } + loadImage.ExifMap = ExifMap + // Adds the following properties to the parseMetaData callback data: - // * exif: The parsed Exif tags - // * exifOffsets: The parsed Exif tag offsets - // * exifTiffOffset: TIFF header offset (used for offset pointers) - // * exifLittleEndian: little endian order if true, big endian if false + // - exif: The parsed Exif tags + // - exifOffsets: The parsed Exif tag offsets + // - exifTiffOffset: TIFF header offset (used for offset pointers) + // - exifLittleEndian: little endian order if true, big endian if false // Adds the following options to the parseMetaData method: - // * disableExif: Disables Exif parsing. - // * disableExifThumbnail: Disables parsing of the Exif Thumbnail. - // * disableExifSub: Disables parsing of the Exif Sub IFD. - // * disableExifGps: Disables parsing of the Exif GPS Info IFD. - // * disableExifOffsets: Disables storing Exif tag offsets + // - disableExif: Disables Exif parsing when true. + // - disableExifThumbnail: Disables parsing of Thumbnail data when true. + // - disableExifOffsets: Disables storing Exif tag offsets when true. + // - includeExifTags: A map of Exif tags to include for parsing. + // - excludeExifTags: A map of Exif tags to exclude from parsing. }) diff --git a/js/load-image-iptc-map.js b/js/load-image-iptc-map.js index adffa21..165d17d 100644 --- a/js/load-image-iptc-map.js +++ b/js/load-image-iptc-map.js @@ -6,9 +6,8 @@ * Copyright 2018, Dave Bevan * * IPTC tags mapping based on - * https://github.com/jseidelin/exif-js * https://iptc.org/standards/photo-metadata - * http://www.iptc.org/std/IIM/4.1/specification/IIMV4.1.pdf + * https://exiftool.org/TagNames/IPTC.html * * Licensed under the MIT license: * https://opensource.org/licenses/MIT @@ -30,100 +29,141 @@ })(function (loadImage) { 'use strict' - loadImage.IptcMap.prototype.tags = { - // ========== - // IPTC tags: - // ========== - 0x03: 'ObjectType', - 0x04: 'ObjectAttribute', - 0x05: 'ObjectName', - 0x07: 'EditStatus', - 0x08: 'EditorialUpdate', - 0x0a: 'Urgency', - 0x0c: 'SubjectRef', - 0x0f: 'Category', - 0x14: 'SupplCategory', - 0x16: 'FixtureID', - 0x19: 'Keywords', - 0x1a: 'ContentLocCode', - 0x1b: 'ContentLocName', - 0x1e: 'ReleaseDate', - 0x23: 'ReleaseTime', - 0x25: 'ExpirationDate', - 0x26: 'ExpirationTime', - 0x28: 'SpecialInstructions', - 0x2a: 'ActionAdvised', - 0x2d: 'RefService', - 0x2f: 'RefDate', - 0x32: 'RefNumber', - 0x37: 'DateCreated', - 0x3c: 'TimeCreated', - 0x3e: 'DigitalCreationDate', - 0x3f: 'DigitalCreationTime', - 0x41: 'OriginatingProgram', - 0x46: 'ProgramVersion', - 0x4b: 'ObjectCycle', - 0x50: 'Byline', - 0x55: 'BylineTitle', - 0x5a: 'City', - 0x5c: 'Sublocation', - 0x5f: 'State', - 0x64: 'CountryCode', - 0x65: 'CountryName', - 0x67: 'OrigTransRef', - 0x69: 'Headline', - 0x6e: 'Credit', - 0x73: 'Source', - 0x74: 'CopyrightNotice', - 0x76: 'Contact', - 0x78: 'Caption', - 0x7a: 'WriterEditor', - 0x82: 'ImageType', - 0x83: 'ImageOrientation', - 0x87: 'LanguageID' + var IptcMapProto = loadImage.IptcMap.prototype - // We don't record these tags: - // - // 0x00: 'RecordVersion', - // 0x7d: 'RasterizedCaption', - // 0x96: 'AudioType', - // 0x97: 'AudioSamplingRate', - // 0x98: 'AudioSamplingRes', - // 0x99: 'AudioDuration', - // 0x9a: 'AudioOutcue', - // 0xc8: 'PreviewFileFormat', - // 0xc9: 'PreviewFileFormatVer', - // 0xca: 'PreviewData' + IptcMapProto.tags = { + 0: 'ApplicationRecordVersion', + 3: 'ObjectTypeReference', + 4: 'ObjectAttributeReference', + 5: 'ObjectName', + 7: 'EditStatus', + 8: 'EditorialUpdate', + 10: 'Urgency', + 12: 'SubjectReference', + 15: 'Category', + 20: 'SupplementalCategories', + 22: 'FixtureIdentifier', + 25: 'Keywords', + 26: 'ContentLocationCode', + 27: 'ContentLocationName', + 30: 'ReleaseDate', + 35: 'ReleaseTime', + 37: 'ExpirationDate', + 38: 'ExpirationTime', + 40: 'SpecialInstructions', + 42: 'ActionAdvised', + 45: 'ReferenceService', + 47: 'ReferenceDate', + 50: 'ReferenceNumber', + 55: 'DateCreated', + 60: 'TimeCreated', + 62: 'DigitalCreationDate', + 63: 'DigitalCreationTime', + 65: 'OriginatingProgram', + 70: 'ProgramVersion', + 75: 'ObjectCycle', + 80: 'Byline', + 85: 'BylineTitle', + 90: 'City', + 92: 'Sublocation', + 95: 'State', + 100: 'CountryCode', + 101: 'Country', + 103: 'OriginalTransmissionReference', + 105: 'Headline', + 110: 'Credit', + 115: 'Source', + 116: 'CopyrightNotice', + 118: 'Contact', + 120: 'Caption', + 121: 'LocalCaption', + 122: 'Writer', + 125: 'RasterizedCaption', + 130: 'ImageType', + 131: 'ImageOrientation', + 135: 'LanguageIdentifier', + 150: 'AudioType', + 151: 'AudioSamplingRate', + 152: 'AudioSamplingResolution', + 153: 'AudioDuration', + 154: 'AudioOutcue', + 184: 'JobID', + 185: 'MasterDocumentID', + 186: 'ShortDocumentID', + 187: 'UniqueDocumentID', + 188: 'OwnerID', + 200: 'ObjectPreviewFileFormat', + 201: 'ObjectPreviewFileVersion', + 202: 'ObjectPreviewData', + 221: 'Prefs', + 225: 'ClassifyState', + 228: 'SimilarityIndex', + 230: 'DocumentNotes', + 231: 'DocumentHistory', + 232: 'ExifCameraInfo', + 255: 'CatalogSets' } - loadImage.IptcMap.prototype.getText = function (id) { + IptcMapProto.stringValues = { + 10: { + 0: '0 (reserved)', + 1: '1 (most urgent)', + 2: '2', + 3: '3', + 4: '4', + 5: '5 (normal urgency)', + 6: '6', + 7: '7', + 8: '8 (least urgent)', + 9: '9 (user-defined priority)' + }, + 75: { + a: 'Morning', + b: 'Both Morning and Evening', + p: 'Evening' + }, + 131: { + L: 'Landscape', + P: 'Portrait', + S: 'Square' + } + } + + IptcMapProto.getText = function (id) { var value = this.get(id) + var tagCode = this.map[id] + var stringValue = this.stringValues[tagCode] + if (stringValue) return stringValue[value] return String(value) } - ;(function (iptcMapPrototype) { - var tags = iptcMapPrototype.tags - var map = iptcMapPrototype.map || {} - var prop - // Map the tag names to tags: - for (prop in tags) { - if (Object.prototype.hasOwnProperty.call(tags, prop)) { - map[tags[prop]] = prop - } - } - })(loadImage.IptcMap.prototype) - loadImage.IptcMap.prototype.getAll = function () { + IptcMapProto.getAll = function () { var map = {} var prop - var id + var name for (prop in this) { if (Object.prototype.hasOwnProperty.call(this, prop)) { - id = this.tags[prop] - if (id) { - map[id] = this.getText(id) - } + name = this.tags[prop] + if (name) map[name] = this.getText(name) } } return map } + + IptcMapProto.getName = function (tagCode) { + return this.tags[tagCode] + } + + // Extend the map of tag names to tag codes: + ;(function () { + var tags = IptcMapProto.tags + var map = IptcMapProto.map || {} + var prop + // Map the tag names to tags: + for (prop in tags) { + if (Object.prototype.hasOwnProperty.call(tags, prop)) { + map[tags[prop]] = Number(prop) + } + } + })() }) diff --git a/js/load-image-iptc.js b/js/load-image-iptc.js index c7ce7c1..9f6a900 100644 --- a/js/load-image-iptc.js +++ b/js/load-image-iptc.js @@ -10,7 +10,7 @@ * https://opensource.org/licenses/MIT */ -/* global define, module, require, Buffer */ +/* global define, module, require, DataView */ ;(function (factory) { 'use strict' @@ -26,128 +26,214 @@ })(function (loadImage) { 'use strict' - loadImage.IptcMap = function () { - return this + /** + * IPTC tag map + * + * @name IptcMap + * @class + */ + function IptcMap() {} + + IptcMap.prototype.map = { + ObjectName: 5 } - loadImage.IptcMap.prototype.map = { - ObjectName: 0x5 + IptcMap.prototype.types = { + 0: 'Uint16', // ApplicationRecordVersion + 200: 'Uint16', // ObjectPreviewFileFormat + 201: 'Uint16', // ObjectPreviewFileVersion + 202: 'binary' // ObjectPreviewData } - loadImage.IptcMap.prototype.get = function (id) { + /** + * Retrieves IPTC tag value + * + * @param {number|string} id IPTC tag code or name + * @returns {object} IPTC tag value + */ + IptcMap.prototype.get = function (id) { return this[id] || this[this.map[id]] } - loadImage.parseIptcTags = function ( + /** + * Retrieves string for the given DataView and range + * + * @param {DataView} dataView Data view interface + * @param {number} offset Offset start + * @param {number} length Offset length + * @returns {string} String value + */ + function getStringValue(dataView, offset, length) { + var outstr = '' + var end = offset + length + for (var n = offset; n < end; n += 1) { + outstr += String.fromCharCode(dataView.getUint8(n)) + } + return outstr + } + + /** + * Retrieves tag value for the given DataView and range + * + * @param {number} tagCode Private IFD tag code + * @param {IptcMap} map IPTC tag map + * @param {DataView} dataView Data view interface + * @param {number} offset Range start + * @param {number} length Range length + * @returns {object} Tag value + */ + function getTagValue(tagCode, map, dataView, offset, length) { + if (map.types[tagCode] === 'binary') { + return new Blob([dataView.buffer.slice(offset, offset + length)]) + } + if (map.types[tagCode] === 'Uint16') { + return dataView.getUint16(offset) + } + return getStringValue(dataView, offset, length) + } + + /** + * Combines IPTC value with existing ones. + * + * @param {object} value Existing IPTC field value + * @param {object} newValue New IPTC field value + * @returns {object} Resulting IPTC field value + */ + function combineTagValues(value, newValue) { + if (value === undefined) return newValue + if (value instanceof Array) { + value.push(newValue) + return value + } + return [value, newValue] + } + + /** + * Parses IPTC tags. + * + * @param {DataView} dataView Data view interface + * @param {number} segmentOffset Segment offset + * @param {number} segmentLength Segment length + * @param {object} data Data export object + * @param {object} includeTags Map of tags to include + * @param {object} excludeTags Map of tags to exclude + */ + function parseIptcTags( dataView, - startOffset, - sectionLength, - data + segmentOffset, + segmentLength, + data, + includeTags, + excludeTags ) { - /** - * Retrieves string for the given Buffer and range - * - * @param {Buffer} buffer IPTC buffer - * @param {number} start Range start - * @param {number} length Range length - * @returns {string} String value - */ - function getStringFromDB(buffer, start, length) { - var outstr = '' - for (var n = start; n < start + length; n++) { - outstr += String.fromCharCode(buffer.getUint8(n)) - } - return outstr - } - var fieldValue, dataSize, segmentType - var segmentStartPos = startOffset - while (segmentStartPos < startOffset + sectionLength) { - // we currently handle the 2: class of iptc tag + var value, tagSize, tagCode + var segmentEnd = segmentOffset + segmentLength + var offset = segmentOffset + while (offset < segmentEnd) { if ( - dataView.getUint8(segmentStartPos) === 0x1c && - dataView.getUint8(segmentStartPos + 1) === 0x02 + dataView.getUint8(offset) === 0x1c && // tag marker + dataView.getUint8(offset + 1) === 0x02 // record number, only handles v2 ) { - segmentType = dataView.getUint8(segmentStartPos + 2) - // only store data for known tags - if (segmentType in data.iptc.tags) { - dataSize = dataView.getInt16(segmentStartPos + 3) - fieldValue = getStringFromDB(dataView, segmentStartPos + 5, dataSize) - // Check if we already stored a value with this name - if (Object.prototype.hasOwnProperty.call(data.iptc, segmentType)) { - // Value already stored with this name, create multivalue field - if (data.iptc[segmentType] instanceof Array) { - data.iptc[segmentType].push(fieldValue) - } else { - data.iptc[segmentType] = [data.iptc[segmentType], fieldValue] - } - } else { - data.iptc[segmentType] = fieldValue + tagCode = dataView.getUint8(offset + 2) + if ( + (!includeTags || includeTags[tagCode]) && + (!excludeTags || !excludeTags[tagCode]) + ) { + tagSize = dataView.getInt16(offset + 3) + value = getTagValue(tagCode, data.iptc, dataView, offset + 5, tagSize) + data.iptc[tagCode] = combineTagValues(data.iptc[tagCode], value) + if (data.iptcOffsets) { + data.iptcOffsets[tagCode] = offset } } } - segmentStartPos++ + offset += 1 } } + /** + * Tests if field segment starts at offset. + * + * @param {DataView} dataView Data view interface + * @param {number} offset Segment offset + * @returns {boolean} True if '8BIM' exists at offset + */ + function isSegmentStart(dataView, offset) { + return ( + dataView.getUint32(offset) === 0x3842494d && // Photoshop segment start + dataView.getUint16(offset + 4) === 0x0404 // IPTC segment start + ) + } + + /** + * Returns header length. + * + * @param {DataView} dataView Data view interface + * @param {number} offset Segment offset + * @returns {number} Header length + */ + function getHeaderLength(dataView, offset) { + var length = dataView.getUint8(offset + 7) + if (length % 2 !== 0) length += 1 + // Check for pre photoshop 6 format + if (length === 0) { + // Always 4 + length = 4 + } + return length + } + loadImage.parseIptcData = function (dataView, offset, length, data, options) { if (options.disableIptc) { return } var markerLength = offset + length - // Found '8BIM' ? - var isFieldSegmentStart = function (dataView, offset) { - return ( - dataView.getUint32(offset) === 0x3842494d && - dataView.getUint16(offset + 4) === 0x0404 - ) - } - // Hunt forward, looking for the correct IPTC block signature: - // Reference: https://metacpan.org/pod/distribution/Image-MetaData-JPEG/lib/Image/MetaData/JPEG/Structures.pod#Structure-of-a-Photoshop-style-APP13-segment - // From https://github.com/exif-js/exif-js/blob/master/exif.js ~ line 474 on while (offset + 8 < markerLength) { - if (isFieldSegmentStart(dataView, offset)) { - var nameHeaderLength = dataView.getUint8(offset + 7) - if (nameHeaderLength % 2 !== 0) nameHeaderLength += 1 - // Check for pre photoshop 6 format - if (nameHeaderLength === 0) { - // Always 4 - nameHeaderLength = 4 - } - var startOffset = offset + 8 + nameHeaderLength - if (startOffset > markerLength) { + if (isSegmentStart(dataView, offset)) { + var headerLength = getHeaderLength(dataView, offset) + var segmentOffset = offset + 8 + headerLength + if (segmentOffset > markerLength) { // eslint-disable-next-line no-console console.log('Invalid IPTC data: Invalid segment offset.') break } - var sectionLength = dataView.getUint16(offset + 6 + nameHeaderLength) - if (offset + sectionLength > markerLength) { + var segmentLength = dataView.getUint16(offset + 6 + headerLength) + if (offset + segmentLength > markerLength) { // eslint-disable-next-line no-console console.log('Invalid IPTC data: Invalid segment size.') break } // Create the iptc object to store the tags: - data.iptc = new loadImage.IptcMap() - // Parse the tags - return loadImage.parseIptcTags( + data.iptc = new IptcMap() + if (!options.disableIptcOffsets) { + data.iptcOffsets = new IptcMap() + } + parseIptcTags( dataView, - startOffset, - sectionLength, - data + segmentOffset, + segmentLength, + data, + options.includeIptcTags, + options.excludeIptcTags || { 202: true } // ObjectPreviewData ) + return } // eslint-disable-next-line no-param-reassign - offset++ + offset += 1 } - // eslint-disable-next-line no-console - console.log('No IPTC data at this offset - could be XMP') } // Registers this IPTC parser for the APP13 JPEG meta data segment: loadImage.metaDataParsers.jpeg[0xffed].push(loadImage.parseIptcData) + loadImage.IptcMap = IptcMap + // Adds the following properties to the parseMetaData callback data: - // * iptc: The iptc tags, parsed by the parseIptcData method + // - iptc: The iptc tags, parsed by the parseIptcData method // Adds the following options to the parseMetaData method: - // * disableIptc: Disables IPTC parsing. + // - disableIptc: Disables IPTC parsing when true. + // - disableIptcOffsets: Disables storing IPTC tag offsets when true. + // - includeIptcTags: A map of IPTC tags to include for parsing. + // - excludeIptcTags: A map of IPTC tags to exclude from parsing. }) diff --git a/test/test.js b/test/test.js index e33843c..6f2b039 100644 --- a/test/test.js +++ b/test/test.js @@ -989,6 +989,17 @@ }) }) + it('Should not parse Exif tags if disabled', function (done) { + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.exif).to.be.undefined + done() + }, + { disableExif: true } + ) + }) + it('Should parse Exif tag offsets', function (done) { loadImage.parseMetaData(blobJPEG, function (data) { expect(data.exifOffsets).to.be.ok @@ -999,6 +1010,57 @@ }) }) + it('Should not parse Exif tag offsets if disabled', function (done) { + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.exifOffsets).to.be.undefined + done() + }, + { disableExifOffsets: true } + ) + }) + + it('Should only parse included Exif tags', function (done) { + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.exif).to.be.ok + expect(data.exif.get('Orientation')).to.equal(6) + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.exif).to.be.ok + expect(data.exif.get('Orientation')).to.be.undefined + done() + }, + { includeExifTags: { 0x0132: true } } // DateTime + ) + }, + { includeExifTags: { 0x0112: true } } // Orientation + ) + }) + + it('Should not parse excluded Exif tags', function (done) { + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.exif).to.be.ok + expect(data.exif.get('Orientation')).to.equal(6) + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.exif).to.be.ok + expect(data.exif.get('Orientation')).to.be.undefined + done() + }, + { excludeExifTags: { 0x0112: true } } // Orientation + ) + }, + { excludeExifTags: { 0x0132: true } } // DateTime + ) + }) + it('Should parse IPTC tags', function (done) { loadImage.parseMetaData(blobJPEG, function (data) { expect(data.iptc).to.be.ok @@ -1007,6 +1069,80 @@ }) }) + it('Should not parse IPTC tags if disabled', function (done) { + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.iptc).to.be.undefined + done() + }, + { disableIptc: true } + ) + }) + + it('Should parse IPTC tag offsets', function (done) { + loadImage.parseMetaData(blobJPEG, function (data) { + expect(data.iptcOffsets).to.be.ok + expect(data.iptcOffsets.get('ObjectName')).to.equal(0x44) + done() + }) + }) + + it('Should not parse IPTC tag offsets if disabled', function (done) { + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.iptcOffsets).to.be.undefined + done() + }, + { disableIptcOffsets: true } + ) + }) + + it('Should only parse included IPTC tags', function (done) { + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.iptc).to.be.ok + expect(data.iptc.get('ApplicationRecordVersion')).to.be.undefined + expect(data.iptc.get('ObjectName')).to.equal('blueimp.net') + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.iptc).to.be.ok + expect(data.iptc.get('ApplicationRecordVersion')).to.equal(4) + expect(data.iptc.get('ObjectName')).to.be.undefined + done() + }, + { includeIptcTags: { 0: true } } // ApplicationRecordVersion + ) + }, + { includeIptcTags: { 5: true } } // ObjectName + ) + }) + + it('Should not parse excluded IPTC tags', function (done) { + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.iptc).to.be.ok + expect(data.iptc.get('ApplicationRecordVersion')).to.equal(4) + expect(data.iptc.get('ObjectName')).to.be.undefined + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.iptc).to.be.ok + expect(data.iptc.get('ApplicationRecordVersion')).to.be.undefined + expect(data.iptc.get('ObjectName')).to.equal('blueimp.net') + done() + }, + { excludeIptcTags: { 0: true } } // Orientation + ) + }, + { excludeIptcTags: { 5: true } } // DateTime + ) + }) + it('Should parse the complete image head', function (done) { loadImage.parseMetaData(blobJPEG, function (data) { expect(data.imageHead).to.be.ok @@ -1021,6 +1157,17 @@ }) }) + it('Should not parse the complete image head if disabled', function (done) { + loadImage.parseMetaData( + blobJPEG, + function (data) { + expect(data.imageHead).to.be.undefined + done() + }, + { disableImageHead: true } + ) + }) + it('Should parse meta data automatically', function (done) { expect( loadImage( From 79fdc82d6fe5fdd4ce037d184202708dac87349c Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sat, 11 Apr 2020 23:54:11 +0900 Subject: [PATCH 035/197] Fix ObjectURL revoke tests for Edge Legacy and IE. --- test/test.js | 72 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/test/test.js b/test/test.js index 6f2b039..35f9eab 100644 --- a/test/test.js +++ b/test/test.js @@ -68,6 +68,25 @@ } } + /** + * Request helper function. + * + * @param {string} url URL to request + * @param {Function} callback Request callback + */ + function request(url, callback) { + var xhr = new XMLHttpRequest() + xhr.onload = callback + xhr.onerror = callback + try { + xhr.open('GET', url, true) + } catch (e) { + callback.call(xhr, e) + return + } + xhr.send() + } + describe('Loading', function () { it('Return an object with onload and onerror methods', function () { var img = loadImage(blobGIF, function () {}) @@ -116,32 +135,37 @@ ).to.be.ok }) - it('Keep object URL if noRevoke is true', function (done) { - expect( - loadImage( - blobGIF, - function (img) { - loadImage(img.src, function (img2) { - expect(img.width).to.equal(img2.width) - expect(img.height).to.equal(img2.height) + describe('ObjectURL revoke', function () { + // Using XMLHttpRequest via the request helper function to test Object + // URLs to work around Edge Legacy and IE caching image URLs. + if (!window.XMLHttpRequest) return + + it('Keep object URL if noRevoke is set to true', function (done) { + expect( + loadImage( + blobGIF, + function (img) { + request(img.src, function (event) { + expect(event.type).to.equal('load') + done() + }) + }, + { noRevoke: true } + ) + ).to.be.ok + }) + + it('Discard object URL if noRevoke is not set', function (done) { + expect( + loadImage(blobGIF, function (img) { + request(img.src, function (event) { + // IE throws an error that has no type property: + expect(event.type).to.be.oneOf(['error', undefined]) done() }) - }, - { noRevoke: true } - ) - ).to.be.ok - }) - - it('Discard object URL if noRevoke is undefined/false', function (done) { - expect( - loadImage(blobGIF, function (img) { - loadImage(img.src, function (img2) { - expect(img2).to.be.an.instanceOf(window.Event) - expect(img2.type).to.equal('error') - done() }) - }) - ).to.be.ok + ).to.be.ok + }) }) }) @@ -1232,7 +1256,7 @@ it('Should fetch image URL as blob if meta option is true', function (done) { expect( loadImage( - // IE11 does not allow XMLHttpRequest access to data URLs, + // IE does not allow XMLHttpRequest access to data URLs, // so we use an ObjectURL instead of imageUrlJPEG directly: loadImage.createObjectURL(blobJPEG), function (img, data) { From 95b46948e57a6c719d119db3ac1454320c0f2d02 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sat, 11 Apr 2020 23:57:56 +0900 Subject: [PATCH 036/197] Add url to load/error event handler params. --- js/load-image.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/js/load-image.js b/js/load-image.js index a93ea13..ea633e4 100644 --- a/js/load-image.js +++ b/js/load-image.js @@ -28,10 +28,10 @@ var img = document.createElement('img') var url img.onerror = function (event) { - return loadImage.onerror(img, event, file, callback, options) + return loadImage.onerror(img, event, file, url, callback, options) } img.onload = function (event) { - return loadImage.onload(img, event, file, callback, options) + return loadImage.onload(img, event, file, url, callback, options) } if (typeof file === 'string') { loadImage.fetchBlob( @@ -58,7 +58,7 @@ // (Firefox 3.6) support the File API but not Blobs: loadImage.isInstanceOf('File', file) ) { - url = img._objectURL = loadImage.createObjectURL(file) + url = loadImage.createObjectURL(file) if (url) { img.src = url return img @@ -83,13 +83,12 @@ /** * Helper function to revoke an object URL * - * @param {HTMLImageElement} img Image element + * @param {string} url Blob Object URL * @param {object} [options] Options object */ - function revokeHelper(img, options) { - if (img._objectURL && !(options && options.noRevoke)) { - loadImage.revokeObjectURL(img._objectURL) - delete img._objectURL + function revokeHelper(url, options) { + if (url && url.slice(0, 5) === 'blob:' && !(options && options.noRevoke)) { + loadImage.revokeObjectURL(url) } } @@ -109,15 +108,15 @@ callback(img, data) } - loadImage.onerror = function (img, event, file, callback, options) { - revokeHelper(img, options) + loadImage.onerror = function (img, event, file, url, callback, options) { + revokeHelper(url, options) if (callback) { callback.call(img, event) } } - loadImage.onload = function (img, event, file, callback, options) { - revokeHelper(img, options) + loadImage.onload = function (img, event, file, url, callback, options) { + revokeHelper(url, options) if (callback) { loadImage.transform(img, options, callback, file, { originalWidth: img.naturalWidth || img.width, From 6b38e52d6525415f08785ee571529f63b72cbc51 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sun, 12 Apr 2020 00:00:47 +0900 Subject: [PATCH 037/197] Remove the meta dependency from the fetch plugin. --- js/load-image-fetch.js | 64 ++++++++++++++++++------------------------ js/load-image-meta.js | 5 ---- js/load-image.js | 48 ++++++++++++++++++++----------- 3 files changed, 58 insertions(+), 59 deletions(-) diff --git a/js/load-image-fetch.js b/js/load-image-fetch.js index 83fbfd5..7aabda1 100644 --- a/js/load-image-fetch.js +++ b/js/load-image-fetch.js @@ -15,9 +15,9 @@ 'use strict' if (typeof define === 'function' && define.amd) { // Register as an anonymous AMD module: - define(['./load-image', './load-image-meta'], factory) + define(['./load-image'], factory) } else if (typeof module === 'object' && module.exports) { - factory(require('./load-image'), require('./load-image-meta')) + factory(require('./load-image')) } else { // Browser globals: factory(window.loadImage) @@ -27,19 +27,14 @@ if (typeof fetch !== 'undefined' && typeof Request !== 'undefined') { loadImage.fetchBlob = function (url, callback, options) { - if (loadImage.hasMetaOption(options)) { - fetch(new Request(url, options)) - .then(function (response) { - return response.blob() - }) - .then(callback) - .catch(function (err) { - console.log(err) // eslint-disable-line no-console - callback() - }) - } else { - callback() - } + fetch(new Request(url, options)) + .then(function (response) { + return response.blob() + }) + .then(callback) + .catch(function (err) { + callback(null, err) + }) } } else if ( // Check for XHR2 support: @@ -47,29 +42,24 @@ typeof ProgressEvent !== 'undefined' ) { loadImage.fetchBlob = function (url, callback, options) { - if (loadImage.hasMetaOption(options)) { - // eslint-disable-next-line no-param-reassign - options = options || {} - var req = new XMLHttpRequest() - req.open(options.method || 'GET', url) - if (options.headers) { - Object.keys(options.headers).forEach(function (key) { - req.setRequestHeader(key, options.headers[key]) - }) - } - req.withCredentials = options.credentials === 'include' - req.responseType = 'blob' - req.onload = function () { - callback(req.response) - } - req.onerror = req.onabort = req.ontimeout = function (e) { - console.log(e) // eslint-disable-line no-console - callback() - } - req.send(options.body) - } else { - callback() + // eslint-disable-next-line no-param-reassign + options = options || {} + var req = new XMLHttpRequest() + req.open(options.method || 'GET', url) + if (options.headers) { + Object.keys(options.headers).forEach(function (key) { + req.setRequestHeader(key, options.headers[key]) + }) + } + req.withCredentials = options.credentials === 'include' + req.responseType = 'blob' + req.onload = function () { + callback(req.response) + } + req.onerror = req.onabort = req.ontimeout = function (err) { + callback(null, err) } + req.send(options.body) } } }) diff --git a/js/load-image-meta.js b/js/load-image-meta.js index 2bcb8e6..ecdb077 100644 --- a/js/load-image-meta.js +++ b/js/load-image-meta.js @@ -179,11 +179,6 @@ ) } - // Determines if meta data should be loaded automatically: - loadImage.hasMetaOption = function (options) { - return options && options.meta - } - var originalTransform = loadImage.transform loadImage.transform = function (img, options, callback, file, data) { if (loadImage.hasMetaOption(options)) { diff --git a/js/load-image.js b/js/load-image.js index ea633e4..50a36cd 100644 --- a/js/load-image.js +++ b/js/load-image.js @@ -27,6 +27,26 @@ function loadImage(file, callback, options) { var img = document.createElement('img') var url + /** + * Callback for the fetchBlob call. + * + * @param {Blob} blob Blob object + * @param {Error} err Error object + */ + function fetchBlobCallback(blob, err) { + if (err) console.log(err) // eslint-disable-line no-console + if (blob && loadImage.isInstanceOf('Blob', blob)) { + // eslint-disable-next-line no-param-reassign + file = blob + url = loadImage.createObjectURL(file) + } else { + url = file + if (options && options.crossOrigin) { + img.crossOrigin = options.crossOrigin + } + } + img.src = url + } img.onerror = function (event) { return loadImage.onerror(img, event, file, url, callback, options) } @@ -34,23 +54,11 @@ return loadImage.onload(img, event, file, url, callback, options) } if (typeof file === 'string') { - loadImage.fetchBlob( - file, - function (blob) { - if (blob && loadImage.isInstanceOf('Blob', blob)) { - // eslint-disable-next-line no-param-reassign - file = blob - url = loadImage.createObjectURL(file) - } else { - url = file - if (options && options.crossOrigin) { - img.crossOrigin = options.crossOrigin - } - } - img.src = url - }, - options - ) + if (loadImage.hasMetaOption(options)) { + loadImage.fetchBlob(file, fetchBlobCallback, options) + } else { + fetchBlobCallback() + } return img } else if ( loadImage.isInstanceOf('Blob', file) || @@ -92,6 +100,12 @@ } } + // Determines if meta data should be loaded automatically. + // Requires the load image meta extension to load meta data. + loadImage.hasMetaOption = function (options) { + return options && options.meta + } + // If the callback given to this function returns a blob, it is used as image // source instead of the original url and overrides the file argument used in // the onload and onerror event callbacks: From ff22e11daa25d0cff328124abf1cca22ffaa4be5 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sun, 12 Apr 2020 00:09:31 +0900 Subject: [PATCH 038/197] 3.0.0 --- js/load-image.all.min.js | 2 +- js/load-image.all.min.js.map | 2 +- package-lock.json | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/js/load-image.all.min.js b/js/load-image.all.min.js index f09182f..a1522ed 100644 --- a/js/load-image.all.min.js +++ b/js/load-image.all.min.js @@ -1,2 +1,2 @@ -!function(o){"use strict";function r(t,i,a){var o,n=document.createElement("img");return n.onerror=function(e){return r.onerror(n,e,t,i,a)},n.onload=function(e){return r.onload(n,e,t,i,a)},"string"==typeof t?(r.fetchBlob(t,function(e){e&&r.isInstanceOf("Blob",e)?o=r.createObjectURL(t=e):(o=t,a&&a.crossOrigin&&(n.crossOrigin=a.crossOrigin)),n.src=o},a),n):r.isInstanceOf("Blob",t)||r.isInstanceOf("File",t)?(o=n._objectURL=r.createObjectURL(t))?(n.src=o,n):r.readFile(t,function(e){var t=e.target;t&&t.result?n.src=t.result:i&&i(e)}):void 0}var t=o.createObjectURL&&o||o.URL&&URL.revokeObjectURL&&URL||o.webkitURL&&webkitURL;function n(e,t){!e._objectURL||t&&t.noRevoke||(r.revokeObjectURL(e._objectURL),delete e._objectURL)}r.fetchBlob=function(e,t){t()},r.isInstanceOf=function(e,t){return Object.prototype.toString.call(t)==="[object "+e+"]"},r.transform=function(e,t,i,a,o){i(e,o)},r.onerror=function(e,t,i,a,o){n(e,o),a&&a.call(e,t)},r.onload=function(e,t,i,a,o){n(e,o),a&&r.transform(e,o,a,i,{originalWidth:e.naturalWidth||e.width,originalHeight:e.naturalHeight||e.height})},r.createObjectURL=function(e){return!!t&&t.createObjectURL(e)},r.revokeObjectURL=function(e){return!!t&&t.revokeObjectURL(e)},r.readFile=function(e,t,i){if(o.FileReader){var a=new FileReader;if(a.onload=a.onerror=t,a[i=i||"readAsDataURL"])return a[i](e),a}return!1},"function"==typeof define&&define.amd?define(function(){return r}):"object"==typeof module&&module.exports?module.exports=r:o.loadImage=r}("undefined"!=typeof window&&window||this),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image"],e):"object"==typeof module&&module.exports?e(require("./load-image")):e(window.loadImage)}(function(x){"use strict";var n=x.transform;x.transform=function(e,t,i,a,o){n.call(x,x.scale(e,t,o),t,i,a,o)},x.transformCoordinates=function(){},x.getTransformedOptions=function(e,t){var i,a,o,n,r=t.aspectRatio;if(!r)return t;for(a in i={},t)Object.prototype.hasOwnProperty.call(t,a)&&(i[a]=t[a]);return i.crop=!0,r<(o=e.naturalWidth||e.width)/(n=e.naturalHeight||e.height)?(i.maxWidth=n*r,i.maxHeight=n):(i.maxWidth=o,i.maxHeight=o/r),i},x.renderImageToCanvas=function(e,t,i,a,o,n,r,s,l,c,d){var u=e.getContext("2d");return!1===d.imageSmoothingEnabled?u.imageSmoothingEnabled=!1:d.imageSmoothingQuality&&(u.imageSmoothingQuality=d.imageSmoothingQuality),u.drawImage(t,i,a,o,n,r,s,l,c),e},x.hasCanvasOption=function(e){return e.canvas||e.crop||!!e.aspectRatio},x.scale=function(e,t,i){t=t||{};var a,o,n,r,s,l,c,d,u,f,g,p=document.createElement("canvas"),m=e.getContext||x.hasCanvasOption(t)&&p.getContext,h=e.naturalWidth||e.width,A=e.naturalHeight||e.height,b=h,y=A;function S(){var e=Math.max((n||b)/b,(r||y)/y);1r.byteLength){console.log("Invalid meta data: Invalid segment size.");break}if((a=p.metaDataParsers.jpeg[t])&&!u.disableMetaDataParsers)for(o=0;oe.byteLength))return g.createObjectURL(new Blob([e.buffer.slice(t,t+i)]));console.log("Invalid Exif data: Invalid thumbnail data.")},g.exifTagTypes={1:{getValue:function(e,t){return e.getUint8(t)},size:1},2:{getValue:function(e,t){return String.fromCharCode(e.getUint8(t))},size:1,ascii:!0},3:{getValue:function(e,t,i){return e.getUint16(t,i)},size:2},4:{getValue:function(e,t,i){return e.getUint32(t,i)},size:4},5:{getValue:function(e,t,i){return e.getUint32(t,i)/e.getUint32(t+4,i)},size:8},9:{getValue:function(e,t,i){return e.getInt32(t,i)},size:4},10:{getValue:function(e,t,i){return e.getInt32(t,i)/e.getInt32(t+4,i)},size:8}},g.exifTagTypes[7]=g.exifTagTypes[1],g.getExifValue=function(e,t,i,a,o,n){var r,s,l,c,d,u,f=g.exifTagTypes[a];if(f){if(!((s=4<(r=f.size*o)?t+e.getUint32(i+8,n):i+8)+r>e.byteLength)){if(1===o)return f.getValue(e,s,n);for(l=[],c=0;ce.byteLength)console.log("Invalid Exif data: Invalid directory offset.");else{if(!((r=i+2+12*(n=e.getUint16(i,a)))+4>e.byteLength)){for(s=0;se.byteLength)console.log("Invalid Exif data: Invalid segment size.");else if(0===e.getUint16(t+8)){switch(e.getUint16(l)){case 18761:n=!0;break;case 19789:n=!1;break;default:return void console.log("Invalid Exif data: Invalid byte alignment marker.")}42===e.getUint16(l+2,n)?(r=e.getUint32(l+4,n),a.exif=new g.ExifMap,o.disableExifOffsets||(a.exifOffsets=new g.ExifMap,a.exifTiffOffset=l,a.exifLittleEndian=n),(r=g.parseExifTags(e,l,l+r,n,a))&&!o.disableExifThumbnail&&(s={exif:{}},r=g.parseExifTags(e,l,l+r,n,s),s.exif[513]&&(a.exif.Thumbnail=g.getExifThumbnail(e,l+s.exif[513],s.exif[514]))),a.exif[34665]&&!o.disableExifSub&&g.parseExifTags(e,l,l+a.exif[34665],n,a),a.exif[34853]&&!o.disableExifGps&&g.parseExifTags(e,l,l+a.exif[34853],n,a)):console.log("Invalid Exif data: Missing TIFF marker.")}else console.log("Invalid Exif data: Missing byte alignment offset.")}},g.metaDataParsers.jpeg[65505].push(g.parseExifData),g.exifWriters={274:function(e,t,i){return new DataView(e,t.exifOffsets[274]+8,2).setUint16(0,i,t.exifLittleEndian),e}},g.writeExifData=function(e,t,i,a){g.exifWriters[t.exif.map[i]](e,t,a)}}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-exif"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-exif")):e(window.loadImage)}(function(e){"use strict";e.ExifMap.prototype.tags={256:"ImageWidth",257:"ImageHeight",34665:"ExifIFDPointer",34853:"GPSInfoIFDPointer",40965:"InteroperabilityIFDPointer",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",274:"Orientation",277:"SamplesPerPixel",284:"PlanarConfiguration",530:"YCbCrSubSampling",531:"YCbCrPositioning",282:"XResolution",283:"YResolution",296:"ResolutionUnit",273:"StripOffsets",278:"RowsPerStrip",279:"StripByteCounts",513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",301:"TransferFunction",318:"WhitePoint",319:"PrimaryChromaticities",529:"YCbCrCoefficients",532:"ReferenceBlackWhite",306:"DateTime",270:"ImageDescription",271:"Make",272:"Model",305:"Software",315:"Artist",33432:"Copyright",36864:"ExifVersion",40960:"FlashpixVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",42240:"Gamma",37121:"ComponentsConfiguration",37122:"CompressedBitsPerPixel",37500:"MakerNote",37510:"UserComment",40964:"RelatedSoundFile",36867:"DateTimeOriginal",36868:"DateTimeDigitized",37520:"SubSecTime",37521:"SubSecTimeOriginal",37522:"SubSecTimeDigitized",33434:"ExposureTime",33437:"FNumber",34850:"ExposureProgram",34852:"SpectralSensitivity",34855:"PhotographicSensitivity",34856:"OECF",34864:"SensitivityType",34865:"StandardOutputSensitivity",34866:"RecommendedExposureIndex",34867:"ISOSpeed",34868:"ISOSpeedLatitudeyyy",34869:"ISOSpeedLatitudezzz",37377:"ShutterSpeedValue",37378:"ApertureValue",37379:"BrightnessValue",37380:"ExposureBias",37381:"MaxApertureValue",37382:"SubjectDistance",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37396:"SubjectArea",37386:"FocalLength",41483:"FlashEnergy",41484:"SpatialFrequencyResponse",41486:"FocalPlaneXResolution",41487:"FocalPlaneYResolution",41488:"FocalPlaneResolutionUnit",41492:"SubjectLocation",41493:"ExposureIndex",41495:"SensingMethod",41728:"FileSource",41729:"SceneType",41730:"CFAPattern",41985:"CustomRendered",41986:"ExposureMode",41987:"WhiteBalance",41988:"DigitalZoomRatio",41989:"FocalLengthIn35mmFilm",41990:"SceneCaptureType",41991:"GainControl",41992:"Contrast",41993:"Saturation",41994:"Sharpness",41995:"DeviceSettingDescription",41996:"SubjectDistanceRange",42016:"ImageUniqueID",42032:"CameraOwnerName",42033:"BodySerialNumber",42034:"LensSpecification",42035:"LensMake",42036:"LensModel",42037:"LensSerialNumber",0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude",5:"GPSAltitudeRef",6:"GPSAltitude",7:"GPSTimeStamp",8:"GPSSatellites",9:"GPSStatus",10:"GPSMeasureMode",11:"GPSDOP",12:"GPSSpeedRef",13:"GPSSpeed",14:"GPSTrackRef",15:"GPSTrack",16:"GPSImgDirectionRef",17:"GPSImgDirection",18:"GPSMapDatum",19:"GPSDestLatitudeRef",20:"GPSDestLatitude",21:"GPSDestLongitudeRef",22:"GPSDestLongitude",23:"GPSDestBearingRef",24:"GPSDestBearing",25:"GPSDestDistanceRef",26:"GPSDestDistance",27:"GPSProcessingMethod",28:"GPSAreaInformation",29:"GPSDateStamp",30:"GPSDifferential",31:"GPSHPositioningError"},e.ExifMap.prototype.stringValues={ExposureProgram:{0:"Undefined",1:"Manual",2:"Normal program",3:"Aperture priority",4:"Shutter priority",5:"Creative program",6:"Action program",7:"Portrait mode",8:"Landscape mode"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{0:"Unknown",1:"Daylight",2:"Fluorescent",3:"Tungsten (incandescent light)",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 - 5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},SensingMethod:{1:"Undefined",2:"One-chip color area sensor",3:"Two-chip color area sensor",4:"Three-chip color area sensor",5:"Color sequential area sensor",7:"Trilinear sensor",8:"Color sequential linear sensor"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},SceneType:{1:"Directly photographed"},CustomRendered:{0:"Normal process",1:"Custom process"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},GainControl:{0:"None",1:"Low gain up",2:"High gain up",3:"Low gain down",4:"High gain down"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},SubjectDistanceRange:{0:"Unknown",1:"Macro",2:"Close view",3:"Distant view"},FileSource:{3:"DSC"},ComponentsConfiguration:{0:"",1:"Y",2:"Cb",3:"Cr",4:"R",5:"G",6:"B"},Orientation:{1:"top-left",2:"top-right",3:"bottom-right",4:"bottom-left",5:"left-top",6:"right-top",7:"right-bottom",8:"left-bottom"}},e.ExifMap.prototype.getText=function(e){var t=this.get(e);switch(e){case"LightSource":case"Flash":case"MeteringMode":case"ExposureProgram":case"SensingMethod":case"SceneCaptureType":case"SceneType":case"CustomRendered":case"WhiteBalance":case"GainControl":case"Contrast":case"Saturation":case"Sharpness":case"SubjectDistanceRange":case"FileSource":case"Orientation":return this.stringValues[e][t];case"ExifVersion":case"FlashpixVersion":if(!t)return;return String.fromCharCode(t[0],t[1],t[2],t[3]);case"ComponentsConfiguration":if(!t)return;return this.stringValues[e][t[0]]+this.stringValues[e][t[1]]+this.stringValues[e][t[2]]+this.stringValues[e][t[3]];case"GPSVersionID":if(!t)return;return t[0]+"."+t[1]+"."+t[2]+"."+t[3]}return String(t)},function(e){var t,i=e.tags,a=e.map;for(t in i)Object.prototype.hasOwnProperty.call(i,t)&&(a[i[t]]=t)}(e.ExifMap.prototype),e.ExifMap.prototype.getAll=function(){var e,t,i={};for(e in this)Object.prototype.hasOwnProperty.call(this,e)&&(t=this.tags[e])&&(i[t]=this.getText(t));return i}}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-meta"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-meta")):e(window.loadImage)}(function(u){"use strict";u.IptcMap=function(){return this},u.IptcMap.prototype.map={ObjectName:5},u.IptcMap.prototype.get=function(e){return this[e]||this[this.map[e]]},u.parseIptcTags=function(e,t,i,a){function o(e,t,i){for(var a="",o=t;or.byteLength){console.log("Invalid meta data: Invalid segment size.");break}if((n=m.metaDataParsers.jpeg[t])&&!u.disableMetaDataParsers)for(a=0;ae.byteLength)){if(1===a)return f.getValue(e,s,o);for(l=[],c=0;ce.byteLength)console.log("Invalid Exif data: Invalid directory offset.");else{if(!((c=i+2+12*(l=e.getUint16(i,n)))+4>e.byteLength)){for(d=0;dc.byteLength)console.log("Invalid Exif data: Invalid segment size.");else if(0===c.getUint16(e+8)){switch(c.getUint16(m)){case 18761:u=!0;break;case 19789:u=!1;break;default:return void console.log("Invalid Exif data: Invalid byte alignment marker.")}42===c.getUint16(m+2,u)?(n=c.getUint32(m+4,u),d.exif=new h,i.disableExifOffsets||(d.exifOffsets=new h,d.exifTiffOffset=m,d.exifLittleEndian=u),(n=p(c,m,m+n,u,d.exif,d.exifOffsets,f,g))&&!i.disableExifThumbnail&&(n=p(c,m,m+n,u,d.exif,d.exifOffsets,f,g),d.exif[513]&&d.exif[514]&&(d.exif[513]=function(e,t,i){if(i&&!(t+i>e.byteLength))return new Blob([e.buffer.slice(t,t+i)],{type:"image/jpeg"});console.log("Invalid Exif data: Invalid thumbnail data.")}(c,m+d.exif[513],d.exif[514]))),Object.keys(d.exif.privateIFDs).forEach(function(e){var t,i,n,a,o,r,s,l;i=e,n=c,a=m,o=u,r=f,s=g,(l=(t=d).exif[i])&&(t.exif[i]=new h(i),t.exifOffsets&&(t.exifOffsets[i]=new h(i)),p(n,a,a+l,o,t.exif[i],t.exifOffsets&&t.exifOffsets[i],r&&r[i],s&&s[i]))})):console.log("Invalid Exif data: Missing TIFF marker.")}else console.log("Invalid Exif data: Missing byte alignment offset.")}},a.metaDataParsers.jpeg[65505].push(a.parseExifData),a.exifWriters={274:function(e,t,i){return new DataView(e,t.exifOffsets[274]+8,2).setUint16(0,i,t.exifLittleEndian),e}},a.writeExifData=function(e,t,i,n){a.exifWriters[t.exif.map[i]](e,t,n)},a.ExifMap=h}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-exif"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-exif")):e(window.loadImage)}(function(e){"use strict";var a=e.ExifMap.prototype;a.tags={256:"ImageWidth",257:"ImageHeight",258:"BitsPerSample",259:"Compression",262:"PhotometricInterpretation",274:"Orientation",277:"SamplesPerPixel",284:"PlanarConfiguration",530:"YCbCrSubSampling",531:"YCbCrPositioning",282:"XResolution",283:"YResolution",296:"ResolutionUnit",273:"StripOffsets",278:"RowsPerStrip",279:"StripByteCounts",513:"JPEGInterchangeFormat",514:"JPEGInterchangeFormatLength",301:"TransferFunction",318:"WhitePoint",319:"PrimaryChromaticities",529:"YCbCrCoefficients",532:"ReferenceBlackWhite",306:"DateTime",270:"ImageDescription",271:"Make",272:"Model",305:"Software",315:"Artist",33432:"Copyright",34665:{36864:"ExifVersion",40960:"FlashpixVersion",40961:"ColorSpace",40962:"PixelXDimension",40963:"PixelYDimension",42240:"Gamma",37121:"ComponentsConfiguration",37122:"CompressedBitsPerPixel",37500:"MakerNote",37510:"UserComment",40964:"RelatedSoundFile",36867:"DateTimeOriginal",36868:"DateTimeDigitized",37520:"SubSecTime",37521:"SubSecTimeOriginal",37522:"SubSecTimeDigitized",33434:"ExposureTime",33437:"FNumber",34850:"ExposureProgram",34852:"SpectralSensitivity",34855:"PhotographicSensitivity",34856:"OECF",34864:"SensitivityType",34865:"StandardOutputSensitivity",34866:"RecommendedExposureIndex",34867:"ISOSpeed",34868:"ISOSpeedLatitudeyyy",34869:"ISOSpeedLatitudezzz",37377:"ShutterSpeedValue",37378:"ApertureValue",37379:"BrightnessValue",37380:"ExposureBias",37381:"MaxApertureValue",37382:"SubjectDistance",37383:"MeteringMode",37384:"LightSource",37385:"Flash",37396:"SubjectArea",37386:"FocalLength",41483:"FlashEnergy",41484:"SpatialFrequencyResponse",41486:"FocalPlaneXResolution",41487:"FocalPlaneYResolution",41488:"FocalPlaneResolutionUnit",41492:"SubjectLocation",41493:"ExposureIndex",41495:"SensingMethod",41728:"FileSource",41729:"SceneType",41730:"CFAPattern",41985:"CustomRendered",41986:"ExposureMode",41987:"WhiteBalance",41988:"DigitalZoomRatio",41989:"FocalLengthIn35mmFilm",41990:"SceneCaptureType",41991:"GainControl",41992:"Contrast",41993:"Saturation",41994:"Sharpness",41995:"DeviceSettingDescription",41996:"SubjectDistanceRange",42016:"ImageUniqueID",42032:"CameraOwnerName",42033:"BodySerialNumber",42034:"LensSpecification",42035:"LensMake",42036:"LensModel",42037:"LensSerialNumber"},34853:{0:"GPSVersionID",1:"GPSLatitudeRef",2:"GPSLatitude",3:"GPSLongitudeRef",4:"GPSLongitude",5:"GPSAltitudeRef",6:"GPSAltitude",7:"GPSTimeStamp",8:"GPSSatellites",9:"GPSStatus",10:"GPSMeasureMode",11:"GPSDOP",12:"GPSSpeedRef",13:"GPSSpeed",14:"GPSTrackRef",15:"GPSTrack",16:"GPSImgDirectionRef",17:"GPSImgDirection",18:"GPSMapDatum",19:"GPSDestLatitudeRef",20:"GPSDestLatitude",21:"GPSDestLongitudeRef",22:"GPSDestLongitude",23:"GPSDestBearingRef",24:"GPSDestBearing",25:"GPSDestDistanceRef",26:"GPSDestDistance",27:"GPSProcessingMethod",28:"GPSAreaInformation",29:"GPSDateStamp",30:"GPSDifferential",31:"GPSHPositioningError"},40965:{1:"InteroperabilityIndex"}},a.stringValues={ExposureProgram:{0:"Undefined",1:"Manual",2:"Normal program",3:"Aperture priority",4:"Shutter priority",5:"Creative program",6:"Action program",7:"Portrait mode",8:"Landscape mode"},MeteringMode:{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"},LightSource:{0:"Unknown",1:"Daylight",2:"Fluorescent",3:"Tungsten (incandescent light)",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 - 5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"},Flash:{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"},SensingMethod:{1:"Undefined",2:"One-chip color area sensor",3:"Two-chip color area sensor",4:"Three-chip color area sensor",5:"Color sequential area sensor",7:"Trilinear sensor",8:"Color sequential linear sensor"},SceneCaptureType:{0:"Standard",1:"Landscape",2:"Portrait",3:"Night scene"},SceneType:{1:"Directly photographed"},CustomRendered:{0:"Normal process",1:"Custom process"},WhiteBalance:{0:"Auto white balance",1:"Manual white balance"},GainControl:{0:"None",1:"Low gain up",2:"High gain up",3:"Low gain down",4:"High gain down"},Contrast:{0:"Normal",1:"Soft",2:"Hard"},Saturation:{0:"Normal",1:"Low saturation",2:"High saturation"},Sharpness:{0:"Normal",1:"Soft",2:"Hard"},SubjectDistanceRange:{0:"Unknown",1:"Macro",2:"Close view",3:"Distant view"},FileSource:{3:"DSC"},ComponentsConfiguration:{0:"",1:"Y",2:"Cb",3:"Cr",4:"R",5:"G",6:"B"},Orientation:{1:"top-left",2:"top-right",3:"bottom-right",4:"bottom-left",5:"left-top",6:"right-top",7:"right-bottom",8:"left-bottom"}},a.getText=function(e){var t=this.get(e);switch(e){case"LightSource":case"Flash":case"MeteringMode":case"ExposureProgram":case"SensingMethod":case"SceneCaptureType":case"SceneType":case"CustomRendered":case"WhiteBalance":case"GainControl":case"Contrast":case"Saturation":case"Sharpness":case"SubjectDistanceRange":case"FileSource":case"Orientation":return this.stringValues[e][t];case"ExifVersion":case"FlashpixVersion":if(!t)return;return String.fromCharCode(t[0],t[1],t[2],t[3]);case"ComponentsConfiguration":if(!t)return;return this.stringValues[e][t[0]]+this.stringValues[e][t[1]]+this.stringValues[e][t[2]]+this.stringValues[e][t[3]];case"GPSVersionID":if(!t)return;return t[0]+"."+t[1]+"."+t[2]+"."+t[3]}return String(t)},a.getAll=function(){var e,t,i,n={};for(e in this)Object.prototype.hasOwnProperty.call(this,e)&&((t=this[e])&&t.getAll?n[this.privateIFDs[e].name]=t.getAll():(i=this.tags[e])&&(n[i]=this.getText(i)));return n},a.getName=function(e){var t=this.tags[e];return"object"==typeof t?this.privateIFDs[e].name:t},function(){var e,t,i,n=a.tags;for(e in n)if(Object.prototype.hasOwnProperty.call(n,e))if(t=a.privateIFDs[e])for(e in i=n[e])Object.prototype.hasOwnProperty.call(i,e)&&(t.map[i[e]]=Number(e));else a.map[n[e]]=Number(e)}()}),function(e){"use strict";"function"==typeof define&&define.amd?define(["./load-image","./load-image-meta"],e):"object"==typeof module&&module.exports?e(require("./load-image"),require("./load-image-meta")):e(window.loadImage)}(function(e){"use strict";function g(){}function m(e,t,i,n,a){return"binary"===t.types[e]?new Blob([i.buffer.slice(n,n+a)]):"Uint16"===t.types[e]?i.getUint16(n):function(e,t,i){for(var n="",a=t+i,o=t;o Date: Mon, 20 Apr 2020 12:36:13 +0600 Subject: [PATCH 039/197] Compatibility with jQuery 3.5.0 --- js/demo/demo.js | 6 +++--- js/vendor/jquery.Jcrop.js | 20 ++++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/js/demo/demo.js b/js/demo/demo.js index 6dc9fa3..b42cdcd 100644 --- a/js/demo/demo.js +++ b/js/demo/demo.js @@ -29,7 +29,7 @@ $(function () { * @param {object} tags Tags object */ function displayTagData(node, tags) { - var table = $('
    ') + var table = $('
    ') var row = $('') var cell = $('') var prop @@ -59,7 +59,7 @@ $(function () { */ function displayThumbnailImage(node, thumbnail, options) { if (thumbnail) { - var link = $('') + var link = $('') .attr('href', loadImage.createObjectURL(thumbnail)) .attr('download', 'thumbnail.jpg') .appendTo(node) @@ -105,7 +105,7 @@ $(function () { result.children().replaceWith($('Loading image file failed')) return } - var content = $('').append(img) + var content = $('').append(img) result.children().replaceWith(content) if (data) { if (img.getContext) { diff --git a/js/vendor/jquery.Jcrop.js b/js/vendor/jquery.Jcrop.js index 41f80e6..a879391 100755 --- a/js/vendor/jquery.Jcrop.js +++ b/js/vendor/jquery.Jcrop.js @@ -329,7 +329,7 @@ boundy = $img.height(), - $div = $('
    ').width(boundx).height(boundy).addClass(cssClass('holder')).css({ + $div = $('
    ').width(boundx).height(boundy).addClass(cssClass('holder')).css({ position: 'relative', backgroundColor: options.bgColor }).insertAfter($origimg).append($img); @@ -338,19 +338,19 @@ $div.addClass(options.addClass); } - var $img2 = $('
    '), + var $img2 = $('
    '), - $img_holder = $('
    ') + $img_holder = $('
    ') .width('100%').height('100%').css({ zIndex: 310, position: 'absolute', overflow: 'hidden' }), - $hdl_holder = $('
    ') + $hdl_holder = $('
    ') .width('100%').height('100%').css('zIndex', 320), - $sel = $('
    ') + $sel = $('
    ') .css({ position: 'absolute', zIndex: 600 @@ -737,7 +737,7 @@ // Shade Module {{{ var Shade = (function() { var enabled = false, - holder = $('
    ').css({ + holder = $('
    ').css({ position: 'absolute', zIndex: 240, opacity: 0 @@ -779,7 +779,7 @@ }); } function createShade() { - return $('
    ').css({ + return $('
    ').css({ position: 'absolute', backgroundColor: options.shadeColor||options.bgColor }).appendTo(holder); @@ -863,7 +863,7 @@ // Private Methods function insertBorder(type) //{{{ { - var jq = $('
    ').css({ + var jq = $('
    ').css({ position: 'absolute', opacity: options.borderOpacity }).addClass(cssClass(type)); @@ -873,7 +873,7 @@ //}}} function dragDiv(ord, zi) //{{{ { - var jq = $('
    ').mousedown(createDragger(ord)).css({ + var jq = $('
    ').mousedown(createDragger(ord)).css({ cursor: ord + '-resize', position: 'absolute', zIndex: zi @@ -1226,7 +1226,7 @@ width: '12px' }).addClass('jcrop-keymgr'), - $keywrap = $('
    ').css({ + $keywrap = $('
    ').css({ position: 'absolute', overflow: 'hidden' }).append($keymgr); From 804fd90a10177d7a9ccdbdfde2a663046cb02a8e Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Mon, 20 Apr 2020 22:31:56 +0900 Subject: [PATCH 040/197] Remove unnecessary whitespace. --- README.md | 49 ++++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index cc067d4..3c7a154 100644 --- a/README.md +++ b/README.md @@ -240,100 +240,103 @@ Defines the minimum height of the img/canvas element. The width of the sub-rectangle of the source image to draw into the destination canvas. - Defaults to the source image width and requires `canvas: true`. +Defaults to the source image width and requires `canvas: true`. ### sourceHeight The height of the sub-rectangle of the source image to draw into the destination canvas. - Defaults to the source image height and requires `canvas: true`. +Defaults to the source image height and requires `canvas: true`. ### top The top margin of the sub-rectangle of the source image. - Defaults to `0` and requires `canvas: true`. +Defaults to `0` and requires `canvas: true`. ### right The right margin of the sub-rectangle of the source image. - Defaults to `0` and requires `canvas: true`. +Defaults to `0` and requires `canvas: true`. ### bottom The bottom margin of the sub-rectangle of the source image. - Defaults to `0` and requires `canvas: true`. +Defaults to `0` and requires `canvas: true`. ### left The left margin of the sub-rectangle of the source image. - Defaults to `0` and requires `canvas: true`. +Defaults to `0` and requires `canvas: true`. ### contain Scales the image up/down to contain it in the max dimensions if set to `true`. - This emulates the CSS feature [background-image: contain](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Scaling_background_images#contain). +This emulates the CSS feature +[background-image: contain](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Scaling_background_images#contain). ### cover Scales the image up/down to cover the max dimensions with the image dimensions if set to `true`. - This emulates the CSS feature [background-image: cover](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Scaling_background_images#cover). +This emulates the CSS feature +[background-image: cover](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Scaling_background_images#cover). ### aspectRatio Crops the image to the given aspect ratio (e.g. `16/9`). - Setting the `aspectRatio` also enables the `crop` option. +Setting the `aspectRatio` also enables the `crop` option. ### pixelRatio Defines the ratio of the canvas pixels to the physical image pixels on the screen. - Should be set to `window.devicePixelRatio` unless the scaled image is not rendered -on screen. - Defaults to `1` and requires `canvas: true`. +Should be set to `window.devicePixelRatio` unless the scaled image is not +rendered on screen. +Defaults to `1` and requires `canvas: true`. ### downsamplingRatio Defines the ratio in which the image is downsampled. - By default, images are downsampled in one step. With a ratio of `0.5`, each step +By default, images are downsampled in one step. With a ratio of `0.5`, each step scales the image to half the size, before reaching the target dimensions. - Requires `canvas: true`. +Requires `canvas: true`. ### imageSmoothingEnabled If set to `false`, [disables image smoothing](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingEnabled). - Defaults to `true` and requires `canvas: true`. +Defaults to `true` and requires `canvas: true`. ### imageSmoothingQuality Sets the [quality of image smoothing](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/imageSmoothingQuality). - Possible values: `'low'`, `'medium'`, `'high'` - Defaults to `'low'` and requires `canvas: true`. +Possible values: `'low'`, `'medium'`, `'high'` +Defaults to `'low'` and requires `canvas: true`. ### crop Crops the image to the maxWidth/maxHeight constraints if set to `true`. - Enabling the `crop` option also enables the `canvas` option. +Enabling the `crop` option also enables the `canvas` option. ### orientation Transform the canvas according to the specified Exif orientation, which can be an `integer` in the range of `1` to `8` or the boolean value `true`. - When set to `true`, it will set the orientation value based on the EXIF data of +When set to `true`, it will set the orientation value based on the EXIF data of the image, which will be parsed automatically if the exif library is available. Setting `orientation` to an integer in the range of `2` to `8` enables the `canvas` option. - Setting `orientation` to `true` enables the `canvas` and `meta` options, unless -the browser supports automatic image orientation (see [browser support for image-orientation](https://caniuse.com/#feat=css-image-orientation)). +Setting `orientation` to `true` enables the `canvas` and `meta` options, unless +the browser supports automatic image orientation (see +[browser support for image-orientation](https://caniuse.com/#feat=css-image-orientation)). ### meta Automatically parses the image meta data if set to `true`. - The meta data is passed to the callback as part of the second argument. - If the file is given as URL and the browser supports the +The meta data is passed to the callback as part of the second argument. +If the file is given as URL and the browser supports the [fetch API](https://developer.mozilla.org/en/docs/Web/API/Fetch_API), fetches the file as Blob to be able to parse the meta data. From 3f7b50eab933a339b1dac49b284581957b245c85 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Mon, 20 Apr 2020 22:43:51 +0900 Subject: [PATCH 041/197] Clarify meta option. --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3c7a154..8a1e09a 100644 --- a/README.md +++ b/README.md @@ -335,10 +335,13 @@ the browser supports automatic image orientation (see ### meta Automatically parses the image meta data if set to `true`. -The meta data is passed to the callback as part of the second argument. +If meta data has been found, the data object passed as second argument to the +callback function has additional properties (see +[meta data parsing](#meta-data-parsing)). If the file is given as URL and the browser supports the -[fetch API](https://developer.mozilla.org/en/docs/Web/API/Fetch_API), fetches -the file as Blob to be able to parse the meta data. +[fetch API](https://developer.mozilla.org/en/docs/Web/API/Fetch_API) or the XHR +[responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType) +`blob`, fetches the file as Blob to be able to parse the meta data. ### canvas From addca35c4769526606d303f44a5064557a1f420b Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Mon, 20 Apr 2020 22:51:22 +0900 Subject: [PATCH 042/197] Flash tag is not in the base exif map anymore. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a1e09a..0e334be 100644 --- a/README.md +++ b/README.md @@ -473,7 +473,7 @@ become available, as well as three additional methods: - `exif.getAll()` ```js -var flashText = data.exif.getText('Flash') // e.g.: 'Flash fired, auto mode', +var flashText = data.exif.getText('Orientation') // e.g. right-top for value 6 var name = data.exif.getName(0x0112) // Orientation From 2715acb296be9dbbd122fbf4131bb2958becb635 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Mon, 20 Apr 2020 22:56:47 +0900 Subject: [PATCH 043/197] Add headlines for the Exif/IPTC parser options. --- README.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0e334be..48c2d33 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,10 @@ - [Meta data parsing](#meta-data-parsing) - [Image head](#image-head) - [Exif parser](#exif-parser) + - [Exif parser options](#exif-parser-options) - [Exif writer](#exif-writer) - [IPTC parser](#iptc-parser) + - [IPTC parser options](#iptc-parser-options) - [License](#license) - [Credits](#credits) @@ -481,8 +483,9 @@ var name = data.exif.getName(0x0112) // Orientation var allTags = data.exif.getAll() ``` -The Exif parser also adds additional options for the parseMetaData method, to -disable certain aspects of the parser: +#### Exif parser options + +The Exif parser adds additional options: - `disableExif`: Disables Exif parsing when `true`. - `disableExifThumbnail`: Disables parsing of Thumbnail data when `true`. @@ -599,8 +602,9 @@ var name = data.iptc.getName(5) // ObjectName var allTags = data.iptc.getAll() ``` -The IPTC parser also adds additional options for the parseMetaData method, to -disable certain aspects of the parser: +#### IPTC parser options + +The IPTC parser adds additional options: - `disableIptc`: Disables IPTC parsing when true. - `disableIptcOffsets`: Disables storing IPTC tag offsets when `true`. From b96a82149f6fb13befc1764caec141647b1324e7 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Mon, 20 Apr 2020 23:35:53 +0900 Subject: [PATCH 044/197] Add examples for parsing Exif Thumbnail and IFDs. --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 87 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 48c2d33..865156c 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,10 @@ - [Meta data parsing](#meta-data-parsing) - [Image head](#image-head) - [Exif parser](#exif-parser) + - [Exif Thumbnail](#exif-thumbnail) + - [Exif IFD](#exif-ifd) + - [GPSInfo IFD](#gpsinfo-ifd) + - [Interoperability IFD](#interoperability-ifd) - [Exif parser options](#exif-parser-options) - [Exif writer](#exif-writer) - [IPTC parser](#iptc-parser) @@ -463,9 +467,9 @@ By default, only the following names are mapped: - `Orientation` - `Thumbnail` -- `Exif` -- `GPSInfo` -- `Interoperability` +- `Exif` (see [Exif IFD](#exif-ifd)) +- `GPSInfo` (see [GPSInfo IFD](#gpsinfo-ifd)) +- `Interoperability` (see [Interoperability IFD](#interoperability-ifd)) If you also include the Load Image Exif Map library, additional tag mappings become available, as well as three additional methods: @@ -483,6 +487,86 @@ var name = data.exif.getName(0x0112) // Orientation var allTags = data.exif.getAll() ``` +#### Exif Thumbnail + +Example code displaying a thumbnail image embedded into the Exif meta data: + +```js +loadImage( + fileOrBlobOrUrl, + function (img, data) { + var thumbBlob = data.exif && data.exif.get('Thumbnail') + if (thumbBlob) { + loadImage(thumbBlob, function (thumbImage) { + document.body.appendChild(thumbImage) + }) + } + }, + { meta: true } +) +``` + +#### Exif IFD + +Example code displaying data from the Exif IFD (Image File Directory) that +contains Exif specified TIFF tags: + +```js +loadImage( + fileOrBlobOrUrl, + function (img, data) { + var exifIFD = data.exif && data.exif.get('Exif') + if (exifIFD) { + // Map of all Exif IFD tags with their mapped names/text as keys/values: + console.log(exifIFD.getAll()) + // A specific Exif IFD tag value: + console.log(exifIFD.get('UserComment')) + } + }, + { meta: true } +) +``` + +#### GPSInfo IFD + +Example code displaying data from the Exif IFD (Image File Directory) that +contains [GPS](https://en.wikipedia.org/wiki/Global_Positioning_System) info: + +```js +loadImage( + fileOrBlobOrUrl, + function (img, data) { + var gpsInfo = data.exif && data.exif.get('GPSInfo') + if (gpsInfo) { + // Map of all GPSInfo tags with their mapped names/text as keys/values: + console.log(gpsInfo.getAll()) + // A specific GPSInfo tag value: + console.log(gpsInfo.get('GPSLatitude')) + } + }, + { meta: true } +) +``` + +#### Interoperability IFD + +Example code displaying data from the Exif IFD (Image File Directory) that +contains Interoperability data: + +```js +loadImage( + fileOrBlobOrUrl, + function (img, data) { + var interoperabilityData = data.exif && data.exif.get('Interoperability') + if (interoperabilityData) { + // The InteroperabilityIndex tag value: + console.log(interoperabilityData.get('InteroperabilityIndex')) + } + }, + { meta: true } +) +``` + #### Exif parser options The Exif parser adds additional options: From 6869b3b537e0e88f7e9cad04932aaca922708e05 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Tue, 21 Apr 2020 17:34:37 +0900 Subject: [PATCH 045/197] Remove unnecessary "Should" as test prefix. --- test/test.js | 66 ++++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/test/test.js b/test/test.js index 35f9eab..cb1cd8d 100644 --- a/test/test.js +++ b/test/test.js @@ -682,7 +682,7 @@ }) describe('Orientation', function () { - it('Should keep the orientation', function (done) { + it('Keep the orientation', function (done) { expect( loadImage( blobGIF, @@ -696,7 +696,7 @@ ).to.be.ok }) - it('Should rotate left', function (done) { + it('Rotate left', function (done) { expect( loadImage( blobGIF, @@ -710,7 +710,7 @@ ).to.be.ok }) - it('Should rotate right', function (done) { + it('Rotate right', function (done) { expect( loadImage( blobGIF, @@ -724,7 +724,7 @@ ).to.be.ok }) - it('Should adjust constraints to new coordinates', function (done) { + it('Adjust constraints to new coordinates', function (done) { expect( loadImage( blobGIF, @@ -738,7 +738,7 @@ ).to.be.ok }) - it('Should adjust left and top to new coordinates', function (done) { + it('Adjust left and top to new coordinates', function (done) { expect( loadImage( blobGIF, @@ -752,7 +752,7 @@ ).to.be.ok }) - it('Should adjust right and bottom to new coordinates', function (done) { + it('Adjust right and bottom to new coordinates', function (done) { expect( loadImage( blobGIF, @@ -766,7 +766,7 @@ ).to.be.ok }) - it('Should adjust left and bottom to new coordinates', function (done) { + it('Adjust left and bottom to new coordinates', function (done) { expect( loadImage( blobGIF, @@ -780,7 +780,7 @@ ).to.be.ok }) - it('Should adjust right and top to new coordinates', function (done) { + it('Adjust right and top to new coordinates', function (done) { expect( loadImage( blobGIF, @@ -794,7 +794,7 @@ ).to.be.ok }) - it('Should rotate left with the given pixelRatio', function (done) { + it('Rotate left with the given pixelRatio', function (done) { expect( loadImage( blobGIF, @@ -810,7 +810,7 @@ ).to.be.ok }) - it('Should rotate right with the given pixelRatio', function (done) { + it('Rotate right with the given pixelRatio', function (done) { expect( loadImage( blobGIF, @@ -826,7 +826,7 @@ ).to.be.ok }) - it('Should ignore too small orientation value', function (done) { + it('Ignore too small orientation value', function (done) { expect( loadImage( blobGIF, @@ -840,7 +840,7 @@ ).to.be.ok }) - it('Should ignore too large orientation value', function (done) { + it('Ignore too large orientation value', function (done) { expect( loadImage( blobGIF, @@ -854,7 +854,7 @@ ).to.be.ok }) - it('Should rotate right based on the exif orientation value', function (done) { + it('Rotate right based on the exif orientation value', function (done) { expect( loadImage( blobJPEG, @@ -906,7 +906,7 @@ ).to.be.ok }) - it('Should scale image after exif based orientation', function (done) { + it('Scale image after exif based orientation', function (done) { expect( loadImage( blobJPEG, @@ -923,7 +923,7 @@ describe('from-image', function () { if (!loadImage.orientation) return - it('Should use automatic browser image orientation', function (done) { + it('Use automatic browser image orientation', function (done) { expect( loadImage( blobJPEG, @@ -1005,7 +1005,7 @@ }) describe('Metadata', function () { - it('Should parse Exif tags', function (done) { + it('Parse Exif tags', function (done) { loadImage.parseMetaData(blobJPEG, function (data) { expect(data.exif).to.be.ok expect(data.exif.get('Orientation')).to.equal(6) @@ -1013,7 +1013,7 @@ }) }) - it('Should not parse Exif tags if disabled', function (done) { + it('Do not parse Exif tags if disabled', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1024,7 +1024,7 @@ ) }) - it('Should parse Exif tag offsets', function (done) { + it('Parse Exif tag offsets', function (done) { loadImage.parseMetaData(blobJPEG, function (data) { expect(data.exifOffsets).to.be.ok expect(data.exifOffsets.get('Orientation')).to.equal(0x16) @@ -1034,7 +1034,7 @@ }) }) - it('Should not parse Exif tag offsets if disabled', function (done) { + it('Do not parse Exif tag offsets if disabled', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1045,7 +1045,7 @@ ) }) - it('Should only parse included Exif tags', function (done) { + it('Only parse included Exif tags', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1065,7 +1065,7 @@ ) }) - it('Should not parse excluded Exif tags', function (done) { + it('Do not parse excluded Exif tags', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1085,7 +1085,7 @@ ) }) - it('Should parse IPTC tags', function (done) { + it('Parse IPTC tags', function (done) { loadImage.parseMetaData(blobJPEG, function (data) { expect(data.iptc).to.be.ok expect(data.iptc.get('ObjectName')).to.equal('blueimp.net') @@ -1093,7 +1093,7 @@ }) }) - it('Should not parse IPTC tags if disabled', function (done) { + it('Do not parse IPTC tags if disabled', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1104,7 +1104,7 @@ ) }) - it('Should parse IPTC tag offsets', function (done) { + it('Parse IPTC tag offsets', function (done) { loadImage.parseMetaData(blobJPEG, function (data) { expect(data.iptcOffsets).to.be.ok expect(data.iptcOffsets.get('ObjectName')).to.equal(0x44) @@ -1112,7 +1112,7 @@ }) }) - it('Should not parse IPTC tag offsets if disabled', function (done) { + it('Do not parse IPTC tag offsets if disabled', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1123,7 +1123,7 @@ ) }) - it('Should only parse included IPTC tags', function (done) { + it('Only parse included IPTC tags', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1145,7 +1145,7 @@ ) }) - it('Should not parse excluded IPTC tags', function (done) { + it('Do not parse excluded IPTC tags', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1167,7 +1167,7 @@ ) }) - it('Should parse the complete image head', function (done) { + it('Parse the complete image head', function (done) { loadImage.parseMetaData(blobJPEG, function (data) { expect(data.imageHead).to.be.ok loadImage.parseMetaData( @@ -1181,7 +1181,7 @@ }) }) - it('Should not parse the complete image head if disabled', function (done) { + it('Do not parse the complete image head if disabled', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1192,7 +1192,7 @@ ) }) - it('Should parse meta data automatically', function (done) { + it('Parse meta data automatically', function (done) { expect( loadImage( blobJPEG, @@ -1210,7 +1210,7 @@ ).to.be.ok }) - it('Should write Exif Orientation tag and replace image head', function (done) { + it('Write Exif Orientation tag and replace image head', function (done) { loadImage( blobJPEG, function (img, data) { @@ -1253,7 +1253,7 @@ return } - it('Should fetch image URL as blob if meta option is true', function (done) { + it('Fetch image URL as blob if meta option is true', function (done) { expect( loadImage( // IE does not allow XMLHttpRequest access to data URLs, @@ -1273,7 +1273,7 @@ ).to.be.ok }) - it('Should load image URL as img if meta option is false', function (done) { + it('Load image URL as img if meta option is false', function (done) { expect( loadImage(imageUrlJPEG, function (img, data) { expect(data.imageHead).to.be.undefined From 4931b411b0d1df37850e0c480ee521a10e2e20a6 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Tue, 21 Apr 2020 23:16:45 +0900 Subject: [PATCH 046/197] Always pass a data arg to getTransformedOptions. --- js/load-image-scale.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/load-image-scale.js b/js/load-image-scale.js index 57ac273..dbebf50 100644 --- a/js/load-image-scale.js +++ b/js/load-image-scale.js @@ -45,7 +45,7 @@ // Returns transformed options, allows to override e.g. // maxWidth, maxHeight and crop options based on the aspectRatio. - // gets img, options passed as arguments: + // gets img, options, data passed as arguments: loadImage.getTransformedOptions = function (img, options) { var aspectRatio = options.aspectRatio var newOptions @@ -168,7 +168,7 @@ } if (useCanvas) { // eslint-disable-next-line no-param-reassign - options = loadImage.getTransformedOptions(img, options, data) + options = loadImage.getTransformedOptions(img, options, data || {}) sourceX = options.left || 0 sourceY = options.top || 0 if (options.sourceWidth) { From 379922b53b38a601738dfa8e79d3a396d494e82d Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Tue, 21 Apr 2020 23:23:47 +0900 Subject: [PATCH 047/197] Ensure pixelRatio scaling is idempotent. --- js/load-image-scale.js | 16 +++++++++++----- test/test.js | 12 ++++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/js/load-image-scale.js b/js/load-image-scale.js index dbebf50..a662dde 100644 --- a/js/load-image-scale.js +++ b/js/load-image-scale.js @@ -225,11 +225,17 @@ if (useCanvas) { pixelRatio = options.pixelRatio if (pixelRatio > 1) { - canvas.style.width = destWidth + 'px' - canvas.style.height = destHeight + 'px' - destWidth *= pixelRatio - destHeight *= pixelRatio - canvas.getContext('2d').scale(pixelRatio, pixelRatio) + if (parseInt(img.style.width, 10) === width / pixelRatio) { + // Source image is already scaled according to device pixel ratio + canvas.style.width = destWidth / pixelRatio + 'px' + canvas.style.height = destHeight / pixelRatio + 'px' + } else { + canvas.style.width = destWidth + 'px' + canvas.style.height = destHeight + 'px' + destWidth *= pixelRatio + destHeight *= pixelRatio + canvas.getContext('2d').scale(pixelRatio, pixelRatio) + } } downsamplingRatio = options.downsamplingRatio if ( diff --git a/test/test.js b/test/test.js index cb1cd8d..1e688ae 100644 --- a/test/test.js +++ b/test/test.js @@ -300,6 +300,12 @@ expect(img.height).to.equal(160) expect(img.style.width).to.equal('120px') expect(img.style.height).to.equal('80px') + // Check if pixelRatio scaling is idempotent: + var img2 = loadImage.scale(img, { minWidth: 120, pixelRatio: 2 }) + expect(img2.width).to.equal(240) + expect(img2.height).to.equal(160) + expect(img2.style.width).to.equal('120px') + expect(img2.style.height).to.equal('80px') done() }, { minWidth: 120, canvas: true, pixelRatio: 2 } @@ -316,6 +322,12 @@ expect(img.height).to.equal(40) expect(img.style.width).to.equal('30px') expect(img.style.height).to.equal('20px') + // Check if pixelRatio scaling is idempotent: + var img2 = loadImage.scale(img, { minWidth: 30, pixelRatio: 2 }) + expect(img2.width).to.equal(60) + expect(img2.height).to.equal(40) + expect(img2.style.width).to.equal('30px') + expect(img2.style.height).to.equal('20px') done() }, { maxWidth: 30, canvas: true, pixelRatio: 2 } From 029ac424c3a9bb4c40951a1e12c73424c0808f00 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Tue, 21 Apr 2020 23:39:31 +0900 Subject: [PATCH 048/197] Document orientation values visually in source. --- js/load-image-orientation.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index 74a5be1..7527527 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -9,6 +9,20 @@ * https://opensource.org/licenses/MIT */ +/* + +Exif orientation values to correctly display the letter F: + + 1 2 3 4 5 6 7 8 + +██████ ██████ ██ ██ ██████████ ██ ██ ██████████ +██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ +████ ████ ████ ████ ██ ██████████ ██████████ ██ +██ ██ ██ ██ +██ ██ ██████ ██████ + +*/ + /* global define, module, require */ ;(function (factory) { From cda87fc468211987d808be08dc7ab06c059fa3e7 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Tue, 21 Apr 2020 23:41:32 +0900 Subject: [PATCH 049/197] Add missing degree character in source comments. --- js/load-image-orientation.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index 7527527..e48ebcd 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -119,7 +119,7 @@ Exif orientation values to correctly display the letter F: ctx.scale(1, -1) break case 5: - // vertical flip + 90 rotate right + // vertical flip + 90° rotate right ctx.rotate(0.5 * Math.PI) ctx.scale(1, -1) break @@ -129,7 +129,7 @@ Exif orientation values to correctly display the letter F: ctx.translate(0, -height) break case 7: - // horizontal flip + 90 rotate right + // horizontal flip + 90° rotate right ctx.rotate(0.5 * Math.PI) ctx.translate(width, -height) ctx.scale(-1, 1) @@ -185,7 +185,7 @@ Exif orientation values to correctly display the letter F: newOptions.bottom = options.top break case 5: - // vertical flip + 90 rotate right + // vertical flip + 90° rotate right newOptions.left = options.top newOptions.top = options.left newOptions.right = options.bottom @@ -199,7 +199,7 @@ Exif orientation values to correctly display the letter F: newOptions.bottom = options.left break case 7: - // horizontal flip + 90 rotate right + // horizontal flip + 90° rotate right newOptions.left = options.bottom newOptions.top = options.right newOptions.right = options.top From 61761388e0b9fff19acef3c4d5c1413c11b8c843 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Tue, 21 Apr 2020 23:45:39 +0900 Subject: [PATCH 050/197] Normalize original width+height dimensions. Switch reported original dimensions for browsers with automatic image orientation. --- js/load-image-orientation.js | 28 ++++++++++++++++++++++++++++ test/test.js | 14 ++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index e48ebcd..41ac1ee 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -43,6 +43,7 @@ Exif orientation values to correctly display the letter F: })(function (loadImage) { 'use strict' + var originalTransform = loadImage.transform var originalHasCanvasOption = loadImage.hasCanvasOption var originalHasMetaOption = loadImage.hasMetaOption var originalTransformCoordinates = loadImage.transformCoordinates @@ -66,6 +67,33 @@ Exif orientation values to correctly display the letter F: img.src = testImageURL })() + loadImage.transform = function (img, options, callback, file, data) { + originalTransform.call( + loadImage, + img, + options, + function (img, data) { + if (data) { + var exifOrientation = data.exif && data.exif.get('Orientation') + if ( + loadImage.orientation && + exifOrientation > 4 && + exifOrientation < 9 + ) { + // Automatic image orientation switched image dimensions + var originalWidth = data.originalWidth + var originalHeight = data.originalHeight + data.originalWidth = originalHeight + data.originalHeight = originalWidth + } + } + callback(img, data) + }, + file, + data + ) + } + // Determines if the target image should be a canvas element: loadImage.hasCanvasOption = function (options) { return ( diff --git a/test/test.js b/test/test.js index 1e688ae..84b4a4b 100644 --- a/test/test.js +++ b/test/test.js @@ -932,6 +932,20 @@ ).to.be.ok }) + it('Provide original image width+height from before orientation', function (done) { + expect( + loadImage( + blobJPEG, + function (img, data) { + expect(data.originalWidth).to.equal(3) + expect(data.originalHeight).to.equal(2) + done() + }, + { meta: true, minWidth: 20, minHeight: 30 } + ) + ).to.be.ok + }) + describe('from-image', function () { if (!loadImage.orientation) return From 2e72d13f9f4de50623d9b7e057d969e2121aa0ea Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Tue, 21 Apr 2020 23:46:48 +0900 Subject: [PATCH 051/197] Return early for invalid orientation values. --- js/load-image-orientation.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index 41ac1ee..71faf2f 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -111,19 +111,18 @@ Exif orientation values to correctly display the letter F: ) } - // Transform image orientation based on - // the given EXIF orientation option: + // Transform image orientation based on the given EXIF orientation option: loadImage.transformCoordinates = function (canvas, options) { originalTransformCoordinates.call(loadImage, canvas, options) + var orientation = options.orientation + if (!(orientation > 1 && orientation < 9)) { + return + } var ctx = canvas.getContext('2d') var width = canvas.width var height = canvas.height var styleWidth = canvas.style.width var styleHeight = canvas.style.height - var orientation = options.orientation - if (!(orientation > 1 && orientation < 9)) { - return - } if (orientation > 4) { canvas.width = height canvas.height = width From ae7013d4084a1b82a283be23c0543e92bd8ff2c2 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Tue, 21 Apr 2020 23:49:37 +0900 Subject: [PATCH 052/197] Move transformCoordinates declaration to the end. --- js/load-image-orientation.js | 116 +++++++++++++++++------------------ 1 file changed, 58 insertions(+), 58 deletions(-) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index 71faf2f..2f28171 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -111,64 +111,6 @@ Exif orientation values to correctly display the letter F: ) } - // Transform image orientation based on the given EXIF orientation option: - loadImage.transformCoordinates = function (canvas, options) { - originalTransformCoordinates.call(loadImage, canvas, options) - var orientation = options.orientation - if (!(orientation > 1 && orientation < 9)) { - return - } - var ctx = canvas.getContext('2d') - var width = canvas.width - var height = canvas.height - var styleWidth = canvas.style.width - var styleHeight = canvas.style.height - if (orientation > 4) { - canvas.width = height - canvas.height = width - canvas.style.width = styleHeight - canvas.style.height = styleWidth - } - switch (orientation) { - case 2: - // horizontal flip - ctx.translate(width, 0) - ctx.scale(-1, 1) - break - case 3: - // 180° rotate left - ctx.translate(width, height) - ctx.rotate(Math.PI) - break - case 4: - // vertical flip - ctx.translate(0, height) - ctx.scale(1, -1) - break - case 5: - // vertical flip + 90° rotate right - ctx.rotate(0.5 * Math.PI) - ctx.scale(1, -1) - break - case 6: - // 90° rotate right - ctx.rotate(0.5 * Math.PI) - ctx.translate(0, -height) - break - case 7: - // horizontal flip + 90° rotate right - ctx.rotate(0.5 * Math.PI) - ctx.translate(width, -height) - ctx.scale(-1, 1) - break - case 8: - // 90° rotate left - ctx.rotate(-0.5 * Math.PI) - ctx.translate(-width, 0) - break - } - } - // Transforms coordinate and dimension options // based on the given orientation option: loadImage.getTransformedOptions = function (img, opts, data) { @@ -250,4 +192,62 @@ Exif orientation values to correctly display the letter F: } return newOptions } + + // Transform image orientation based on the given EXIF orientation option: + loadImage.transformCoordinates = function (canvas, options) { + originalTransformCoordinates.call(loadImage, canvas, options) + var orientation = options.orientation + if (!(orientation > 1 && orientation < 9)) { + return + } + var ctx = canvas.getContext('2d') + var width = canvas.width + var height = canvas.height + var styleWidth = canvas.style.width + var styleHeight = canvas.style.height + if (orientation > 4) { + canvas.width = height + canvas.height = width + canvas.style.width = styleHeight + canvas.style.height = styleWidth + } + switch (orientation) { + case 2: + // horizontal flip + ctx.translate(width, 0) + ctx.scale(-1, 1) + break + case 3: + // 180° rotate left + ctx.translate(width, height) + ctx.rotate(Math.PI) + break + case 4: + // vertical flip + ctx.translate(0, height) + ctx.scale(1, -1) + break + case 5: + // vertical flip + 90° rotate right + ctx.rotate(0.5 * Math.PI) + ctx.scale(1, -1) + break + case 6: + // 90° rotate right + ctx.rotate(0.5 * Math.PI) + ctx.translate(0, -height) + break + case 7: + // horizontal flip + 90° rotate right + ctx.rotate(0.5 * Math.PI) + ctx.translate(width, -height) + ctx.scale(-1, 1) + break + case 8: + // 90° rotate left + ctx.rotate(-0.5 * Math.PI) + ctx.translate(-width, 0) + break + } + } }) From 83ace3efe41ffbf8221431f546365918311656aa Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Wed, 22 Apr 2020 00:07:26 +0900 Subject: [PATCH 053/197] Remove unnecessary double negation. --- js/load-image-orientation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index 2f28171..312f6ac 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -97,7 +97,7 @@ Exif orientation values to correctly display the letter F: // Determines if the target image should be a canvas element: loadImage.hasCanvasOption = function (options) { return ( - (!!options.orientation === true && !loadImage.orientation) || + (options.orientation === true && !loadImage.orientation) || (options.orientation > 1 && options.orientation < 9) || originalHasCanvasOption.call(loadImage, options) ) From 2abe1704e69886711b4b3b8bafe946822a57f7cc Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Wed, 22 Apr 2020 18:31:51 +0900 Subject: [PATCH 054/197] Pass data object to transformCoordinates function. --- js/load-image-scale.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/js/load-image-scale.js b/js/load-image-scale.js index a662dde..2f1fcc7 100644 --- a/js/load-image-scale.js +++ b/js/load-image-scale.js @@ -40,7 +40,7 @@ // Transform image coordinates, allows to override e.g. // the canvas orientation based on the orientation option, - // gets canvas, options passed as arguments: + // gets canvas, options and data passed as arguments: loadImage.transformCoordinates = function () {} // Returns transformed options, allows to override e.g. @@ -121,6 +121,8 @@ loadImage.scale = function (img, options, data) { // eslint-disable-next-line no-param-reassign options = options || {} + // eslint-disable-next-line no-param-reassign + data = data || {} var canvas = document.createElement('canvas') var useCanvas = img.getContext || @@ -168,7 +170,7 @@ } if (useCanvas) { // eslint-disable-next-line no-param-reassign - options = loadImage.getTransformedOptions(img, options, data || {}) + options = loadImage.getTransformedOptions(img, options, data) sourceX = options.left || 0 sourceY = options.top || 0 if (options.sourceWidth) { @@ -285,7 +287,7 @@ } canvas.width = destWidth canvas.height = destHeight - loadImage.transformCoordinates(canvas, options) + loadImage.transformCoordinates(canvas, options, data) return loadImage.renderImageToCanvas( canvas, img, From e52c67b9f77c18a9a7a6f0f22b04ea0a80ce63ae Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Wed, 22 Apr 2020 21:22:09 +0900 Subject: [PATCH 055/197] Rename functions for canvas and meta requirements. --- js/load-image-meta.js | 2 +- js/load-image-orientation.js | 12 ++++++------ js/load-image-scale.js | 10 +++------- js/load-image.js | 4 ++-- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/js/load-image-meta.js b/js/load-image-meta.js index ecdb077..63177d3 100644 --- a/js/load-image-meta.js +++ b/js/load-image-meta.js @@ -181,7 +181,7 @@ var originalTransform = loadImage.transform loadImage.transform = function (img, options, callback, file, data) { - if (loadImage.hasMetaOption(options)) { + if (loadImage.requiresMetaData(options)) { loadImage.parseMetaData( file, function (data) { diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index 312f6ac..ce0557f 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -44,8 +44,8 @@ Exif orientation values to correctly display the letter F: 'use strict' var originalTransform = loadImage.transform - var originalHasCanvasOption = loadImage.hasCanvasOption - var originalHasMetaOption = loadImage.hasMetaOption + var originalRequiresCanvas = loadImage.requiresCanvas + var originalRequiresMetaData = loadImage.requiresMetaData var originalTransformCoordinates = loadImage.transformCoordinates var originalGetTransformedOptions = loadImage.getTransformedOptions @@ -95,19 +95,19 @@ Exif orientation values to correctly display the letter F: } // Determines if the target image should be a canvas element: - loadImage.hasCanvasOption = function (options) { + loadImage.requiresCanvas = function (options) { return ( (options.orientation === true && !loadImage.orientation) || (options.orientation > 1 && options.orientation < 9) || - originalHasCanvasOption.call(loadImage, options) + originalRequiresCanvas.call(loadImage, options) ) } // Determines if meta data should be loaded automatically: - loadImage.hasMetaOption = function (options) { + loadImage.requiresMetaData = function (options) { return ( (options && options.orientation === true && !loadImage.orientation) || - originalHasMetaOption.call(loadImage, options) + originalRequiresMetaData.call(loadImage, options) ) } diff --git a/js/load-image-scale.js b/js/load-image-scale.js index 2f1fcc7..53fe5e6 100644 --- a/js/load-image-scale.js +++ b/js/load-image-scale.js @@ -109,15 +109,12 @@ } // Determines if the target image should be a canvas element: - loadImage.hasCanvasOption = function (options) { + loadImage.requiresCanvas = function (options) { return options.canvas || options.crop || !!options.aspectRatio } // Scales and/or crops the given image (img or canvas HTML element) - // using the given options. - // Returns a canvas object if the browser supports canvas - // and the hasCanvasOption method returns true or a canvas - // object is passed as image, else the scaled image: + // using the given options: loadImage.scale = function (img, options, data) { // eslint-disable-next-line no-param-reassign options = options || {} @@ -125,8 +122,7 @@ data = data || {} var canvas = document.createElement('canvas') var useCanvas = - img.getContext || - (loadImage.hasCanvasOption(options) && canvas.getContext) + img.getContext || (loadImage.requiresCanvas(options) && canvas.getContext) var width = img.naturalWidth || img.width var height = img.naturalHeight || img.height var destWidth = width diff --git a/js/load-image.js b/js/load-image.js index 50a36cd..865586d 100644 --- a/js/load-image.js +++ b/js/load-image.js @@ -54,7 +54,7 @@ return loadImage.onload(img, event, file, url, callback, options) } if (typeof file === 'string') { - if (loadImage.hasMetaOption(options)) { + if (loadImage.requiresMetaData(options)) { loadImage.fetchBlob(file, fetchBlobCallback, options) } else { fetchBlobCallback() @@ -102,7 +102,7 @@ // Determines if meta data should be loaded automatically. // Requires the load image meta extension to load meta data. - loadImage.hasMetaOption = function (options) { + loadImage.requiresMetaData = function (options) { return options && options.meta } From 0a0dc98c0103463de2e8911306c8726321c842e8 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Wed, 22 Apr 2020 21:43:28 +0900 Subject: [PATCH 056/197] Refactor canvas+meta requirement checks for reuse. --- js/load-image-orientation.js | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index ce0557f..6920d73 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -94,11 +94,31 @@ Exif orientation values to correctly display the letter F: ) } + /** + * Determines if the image requires orientation. + * + * @param {object} [options] Options object + * @param {boolean} [withMetaData] Is meta data required for orientation + * @returns {boolean} Returns true if the image requires orientation + */ + function requiresOrientation(options, withMetaData) { + var orientation = options && options.orientation + return ( + // Exif orientation for browsers without automatic image orientation: + (orientation === true && !loadImage.orientation) || + // Orientation reset for browsers with automatic image orientation: + (orientation === 1 && loadImage.orientation) || + // Orientation to defined value, requires meta data for orientation reset: + ((!withMetaData || loadImage.orientation) && + orientation > 1 && + orientation < 9) + ) + } + // Determines if the target image should be a canvas element: loadImage.requiresCanvas = function (options) { return ( - (options.orientation === true && !loadImage.orientation) || - (options.orientation > 1 && options.orientation < 9) || + requiresOrientation(options) || originalRequiresCanvas.call(loadImage, options) ) } @@ -106,7 +126,7 @@ Exif orientation values to correctly display the letter F: // Determines if meta data should be loaded automatically: loadImage.requiresMetaData = function (options) { return ( - (options && options.orientation === true && !loadImage.orientation) || + requiresOrientation(options, true) || originalRequiresMetaData.call(loadImage, options) ) } From a3eb4cda44a6b7d3327af93fe970151dea697f0c Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Wed, 22 Apr 2020 21:45:16 +0900 Subject: [PATCH 057/197] Move transform function below requirements checks. --- js/load-image-orientation.js | 54 ++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index 6920d73..edddb7c 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -67,33 +67,6 @@ Exif orientation values to correctly display the letter F: img.src = testImageURL })() - loadImage.transform = function (img, options, callback, file, data) { - originalTransform.call( - loadImage, - img, - options, - function (img, data) { - if (data) { - var exifOrientation = data.exif && data.exif.get('Orientation') - if ( - loadImage.orientation && - exifOrientation > 4 && - exifOrientation < 9 - ) { - // Automatic image orientation switched image dimensions - var originalWidth = data.originalWidth - var originalHeight = data.originalHeight - data.originalWidth = originalHeight - data.originalHeight = originalWidth - } - } - callback(img, data) - }, - file, - data - ) - } - /** * Determines if the image requires orientation. * @@ -131,6 +104,33 @@ Exif orientation values to correctly display the letter F: ) } + loadImage.transform = function (img, options, callback, file, data) { + originalTransform.call( + loadImage, + img, + options, + function (img, data) { + if (data) { + var exifOrientation = data.exif && data.exif.get('Orientation') + if ( + loadImage.orientation && + exifOrientation > 4 && + exifOrientation < 9 + ) { + // Automatic image orientation switched image dimensions + var originalWidth = data.originalWidth + var originalHeight = data.originalHeight + data.originalWidth = originalHeight + data.originalHeight = originalWidth + } + } + callback(img, data) + }, + file, + data + ) + } + // Transforms coordinate and dimension options // based on the given orientation option: loadImage.getTransformedOptions = function (img, opts, data) { From 212a3a3dcc138da248163795e2fa9221b3d4c614 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Wed, 22 Apr 2020 23:35:29 +0900 Subject: [PATCH 058/197] Arrange Exif orientation visualization vertically. --- js/load-image-orientation.js | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index edddb7c..f700df1 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -10,16 +10,31 @@ */ /* - Exif orientation values to correctly display the letter F: - 1 2 3 4 5 6 7 8 + 1 2 + ██████ ██████ + ██ ██ + ████ ████ + ██ ██ + ██ ██ + + 3 4 + ██ ██ + ██ ██ + ████ ████ + ██ ██ + ██████ ██████ + + 5 6 +██████████ ██ +██ ██ ██ ██ +██ ██████████ -██████ ██████ ██ ██ ██████████ ██ ██ ██████████ -██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ -████ ████ ████ ████ ██ ██████████ ██████████ ██ -██ ██ ██ ██ -██ ██ ██████ ██████ + 7 8 + ██ ██████████ + ██ ██ ██ ██ +██████████ ██ */ From 7e5f90b8c623bcdb2088715af89686f2ccb877b2 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Wed, 22 Apr 2020 23:36:38 +0900 Subject: [PATCH 059/197] Update orientation requirements for canvas/meta. --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 865156c..75d87c7 100644 --- a/README.md +++ b/README.md @@ -332,11 +332,15 @@ an `integer` in the range of `1` to `8` or the boolean value `true`. When set to `true`, it will set the orientation value based on the EXIF data of the image, which will be parsed automatically if the exif library is available. -Setting `orientation` to an integer in the range of `2` to `8` enables the -`canvas` option. Setting `orientation` to `true` enables the `canvas` and `meta` options, unless the browser supports automatic image orientation (see -[browser support for image-orientation](https://caniuse.com/#feat=css-image-orientation)). +[browser support for image-orientation](https://caniuse.com/#feat=css-image-orientation)). +Setting `orientation` to `1` enables the `canvas` and `meta` options if the +browser does support automatic image orientation (to allow reset of the +orientation). +Setting `orientation` to an integer in the range of `2` to `8` always enables +the `canvas` option and also enables the `meta` option if the browser supports +automatic image orientation (again to allow reset). ### meta From 0f65d7d1241dc0f0463a531e612a5b56c9e856da Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Wed, 22 Apr 2020 23:38:06 +0900 Subject: [PATCH 060/197] Add Exif orientation visualization to the README. --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index 75d87c7..a13ac05 100644 --- a/README.md +++ b/README.md @@ -332,6 +332,34 @@ an `integer` in the range of `1` to `8` or the boolean value `true`. When set to `true`, it will set the orientation value based on the EXIF data of the image, which will be parsed automatically if the exif library is available. +Exif orientation values to correctly display the letter F: + +``` + 1 2 + ██████ ██████ + ██ ██ + ████ ████ + ██ ██ + ██ ██ + + 3 4 + ██ ██ + ██ ██ + ████ ████ + ██ ██ + ██████ ██████ + + 5 6 +██████████ ██ +██ ██ ██ ██ +██ ██████████ + + 7 8 + ██ ██████████ + ██ ██ ██ ██ +██████████ ██ +``` + Setting `orientation` to `true` enables the `canvas` and `meta` options, unless the browser supports automatic image orientation (see [browser support for image-orientation](https://caniuse.com/#feat=css-image-orientation)). From 14b599e924979e43f8f5d75e3afde4752aea5ac9 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Thu, 23 Apr 2020 00:32:05 +0900 Subject: [PATCH 061/197] Simplify original dimensions normalization. --- js/load-image-orientation.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index f700df1..30833de 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -126,12 +126,9 @@ Exif orientation values to correctly display the letter F: options, function (img, data) { if (data) { - var exifOrientation = data.exif && data.exif.get('Orientation') - if ( - loadImage.orientation && - exifOrientation > 4 && - exifOrientation < 9 - ) { + var autoOrientation = + loadImage.orientation && data.exif && data.exif.get('Orientation') + if (autoOrientation > 4 && autoOrientation < 9) { // Automatic image orientation switched image dimensions var originalWidth = data.originalWidth var originalHeight = data.originalHeight From f088760e7b29c88f75e622e97b2a937491a2fc60 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sun, 26 Apr 2020 18:13:32 +0900 Subject: [PATCH 062/197] Normalize EXIF test names spelling. --- test/test.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test.js b/test/test.js index 84b4a4b..33ed5f8 100644 --- a/test/test.js +++ b/test/test.js @@ -1031,7 +1031,7 @@ }) describe('Metadata', function () { - it('Parse Exif tags', function (done) { + it('Parse EXIF tags', function (done) { loadImage.parseMetaData(blobJPEG, function (data) { expect(data.exif).to.be.ok expect(data.exif.get('Orientation')).to.equal(6) @@ -1039,7 +1039,7 @@ }) }) - it('Do not parse Exif tags if disabled', function (done) { + it('Do not parse EXIF tags if disabled', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1050,7 +1050,7 @@ ) }) - it('Parse Exif tag offsets', function (done) { + it('Parse EXIF tag offsets', function (done) { loadImage.parseMetaData(blobJPEG, function (data) { expect(data.exifOffsets).to.be.ok expect(data.exifOffsets.get('Orientation')).to.equal(0x16) @@ -1060,7 +1060,7 @@ }) }) - it('Do not parse Exif tag offsets if disabled', function (done) { + it('Do not parse EXIF tag offsets if disabled', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1071,7 +1071,7 @@ ) }) - it('Only parse included Exif tags', function (done) { + it('Only parse included EXIF tags', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1091,7 +1091,7 @@ ) }) - it('Do not parse excluded Exif tags', function (done) { + it('Do not parse excluded EXIF tags', function (done) { loadImage.parseMetaData( blobJPEG, function (data) { @@ -1236,7 +1236,7 @@ ).to.be.ok }) - it('Write Exif Orientation tag and replace image head', function (done) { + it('Write EXIF Orientation tag and replace image head', function (done) { loadImage( blobJPEG, function (img, data) { @@ -1245,7 +1245,7 @@ expect(data.exif.get('Orientation')).to.equal(6) expect(data.iptc).to.be.ok expect(data.iptc.get('ObjectName')).to.equal('blueimp.net') - // Reset Exif Orientation data: + // Reset EXIF Orientation data: loadImage.writeExifData(data.imageHead, data, 'Orientation', 1) img.toBlob(function (blob) { loadImage.replaceHead(blob, data.imageHead, function (newBlob) { From 001fe7fcf345d1fb7e5a8faa9545a7eb09da5c94 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sun, 26 Apr 2020 18:15:07 +0900 Subject: [PATCH 063/197] Test functions via instanceOf method. --- test/test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/test.js b/test/test.js index 33ed5f8..64899a2 100644 --- a/test/test.js +++ b/test/test.js @@ -91,8 +91,8 @@ it('Return an object with onload and onerror methods', function () { var img = loadImage(blobGIF, function () {}) expect(img).to.be.an.instanceOf(Object) - expect(img.onload).to.be.a('function') - expect(img.onerror).to.be.a('function') + expect(img.onload).to.be.an.instanceOf(Function) + expect(img.onerror).to.be.an.instanceOf(Function) }) it('Load image url', function (done) { @@ -984,7 +984,7 @@ loadImage( blobGIF, function (img) { - expect(img.getContext).to.be.ok + expect(img.getContext).to.be.an.instanceOf(Function) expect(img.nodeName.toLowerCase()).to.equal('canvas') done() }, @@ -998,7 +998,7 @@ loadImage( blobGIF, function (img) { - expect(img.getContext).to.be.ok + expect(img.getContext).to.be.an.instanceOf(Function) expect(img.nodeName.toLowerCase()).to.equal('canvas') expect(img.width).to.equal(30) expect(img.height).to.equal(20) @@ -1018,7 +1018,7 @@ img = loadImage.scale(img, { maxWidth: 30 }) - expect(img.getContext).to.be.ok + expect(img.getContext).to.be.an.instanceOf(Function) expect(img.nodeName.toLowerCase()).to.equal('canvas') expect(img.width).to.equal(30) expect(img.height).to.equal(20) From 996dbb1bcfc60238961619be7a1889d8d409e383 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sun, 26 Apr 2020 18:18:04 +0900 Subject: [PATCH 064/197] Remove unnecessary destX/destY arguments. --- js/load-image-scale.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/js/load-image-scale.js b/js/load-image-scale.js index 53fe5e6..4f6ab50 100644 --- a/js/load-image-scale.js +++ b/js/load-image-scale.js @@ -82,8 +82,6 @@ sourceY, sourceWidth, sourceHeight, - destX, - destY, destWidth, destHeight, options @@ -100,8 +98,8 @@ sourceY, sourceWidth, sourceHeight, - destX, - destY, + 0, + 0, destWidth, destHeight ) @@ -252,8 +250,6 @@ sourceY, sourceWidth, sourceHeight, - 0, - 0, canvas.width, canvas.height, options @@ -273,8 +269,6 @@ 0, sourceWidth, sourceHeight, - 0, - 0, sourceWidth, sourceHeight, options @@ -291,8 +285,6 @@ sourceY, sourceWidth, sourceHeight, - 0, - 0, destWidth, destHeight, options From 8776ebe52bbe9509ba4407a0ce873bda51639680 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sun, 26 Apr 2020 18:22:33 +0900 Subject: [PATCH 065/197] Limit pixel ratio handling to scaling library. --- js/load-image-orientation.js | 4 ---- js/load-image-scale.js | 24 ++++++++++++------------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/js/load-image-orientation.js b/js/load-image-orientation.js index 30833de..d5e215e 100644 --- a/js/load-image-orientation.js +++ b/js/load-image-orientation.js @@ -235,13 +235,9 @@ Exif orientation values to correctly display the letter F: var ctx = canvas.getContext('2d') var width = canvas.width var height = canvas.height - var styleWidth = canvas.style.width - var styleHeight = canvas.style.height if (orientation > 4) { canvas.width = height canvas.height = width - canvas.style.width = styleHeight - canvas.style.height = styleWidth } switch (orientation) { case 2: diff --git a/js/load-image-scale.js b/js/load-image-scale.js index 4f6ab50..e398b13 100644 --- a/js/load-image-scale.js +++ b/js/load-image-scale.js @@ -220,18 +220,14 @@ } if (useCanvas) { pixelRatio = options.pixelRatio - if (pixelRatio > 1) { - if (parseInt(img.style.width, 10) === width / pixelRatio) { - // Source image is already scaled according to device pixel ratio - canvas.style.width = destWidth / pixelRatio + 'px' - canvas.style.height = destHeight / pixelRatio + 'px' - } else { - canvas.style.width = destWidth + 'px' - canvas.style.height = destHeight + 'px' - destWidth *= pixelRatio - destHeight *= pixelRatio - canvas.getContext('2d').scale(pixelRatio, pixelRatio) - } + if ( + pixelRatio > 1 && + // Check if image has not yet device pixel ratio applied: + parseInt(img.style.width, 10) !== width / pixelRatio + ) { + destWidth *= pixelRatio + destHeight *= pixelRatio + canvas.getContext('2d').scale(pixelRatio, pixelRatio) } downsamplingRatio = options.downsamplingRatio if ( @@ -278,6 +274,10 @@ canvas.width = destWidth canvas.height = destHeight loadImage.transformCoordinates(canvas, options, data) + if (pixelRatio > 1) { + canvas.style.width = canvas.width / pixelRatio + 'px' + canvas.style.height = canvas.height / pixelRatio + 'px' + } return loadImage.renderImageToCanvas( canvas, img, From 4c79652b5357fdf7fd5ce0b3f45c386e4e732719 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Sun, 26 Apr 2020 22:24:44 +0900 Subject: [PATCH 066/197] Fix IE10 ArrayBuffer.slice workaround. To use imageHead as DataView argument (e.g. for writing EXIF data), it needs to be a buffer. --- js/load-image-meta.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/js/load-image-meta.js b/js/load-image-meta.js index 63177d3..21c9961 100644 --- a/js/load-image-meta.js +++ b/js/load-image-meta.js @@ -96,6 +96,8 @@ var markerLength var parsers var i + var arr1 + var arr2 // Check for the JPEG marker (0xffd8): if (dataView.getUint16(0) === 0xffd8) { while (offset < maxOffset) { @@ -144,9 +146,12 @@ if (buffer.slice) { data.imageHead = buffer.slice(0, headLength) } else { - // Workaround for IE10, which does not yet - // support ArrayBuffer.slice: - data.imageHead = new Uint8Array(buffer).subarray(0, headLength) + // Workaround for IE10, which does not support + // ArrayBuffer.slice: + arr1 = new Uint8Array(buffer, 0, headLength) + arr2 = new Uint8Array(headLength) + arr2.set(arr1) + data.imageHead = arr2.buffer } } } else { From 681143cfb196bfeade745252fcc67fa04bb87d80 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Tue, 28 Apr 2020 19:40:14 +0900 Subject: [PATCH 067/197] Demo: take pixelRatio into account when cropping. Use contain:true instead of specifying both minWidth and maxWidth. Remove unnecessary downsamplingRatio option for cropping. --- js/demo/demo.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/js/demo/demo.js b/js/demo/demo.js index b42cdcd..fe1984c 100644 --- a/js/demo/demo.js +++ b/js/demo/demo.js @@ -250,10 +250,9 @@ $(function () { top: coordinates.y * pixelRatio, sourceWidth: coordinates.w * pixelRatio, sourceHeight: coordinates.h * pixelRatio, - minWidth: result.width(), - maxWidth: result.width(), + maxWidth: result.width() * pixelRatio, + contain: true, pixelRatio: pixelRatio, - downsamplingRatio: 0.5 }) ) coordinates = null From b86c22f62000513963980180454bb62901fba592 Mon Sep 17 00:00:00 2001 From: Sebastian Tschan Date: Tue, 28 Apr 2020 19:41:03 +0900 Subject: [PATCH 068/197] Demo: add orientation selector. --- index.html | 18 ++++++++++++++++++ js/demo/demo.js | 14 +++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index bc619ec..7192a00 100644 --- a/index.html +++ b/index.html @@ -80,6 +80,24 @@

    Select an image file

    Or drag & drop an image file onto this webpage.

    +

    + + +

    Result

    Select an image file

    +

    + + +

    Result

    Result

    API.

    -