From a95adc4c899534ccaefad21f1ecf4758bdc52852 Mon Sep 17 00:00:00 2001 From: egordidenko Date: Fri, 9 Feb 2024 16:29:06 +0300 Subject: [PATCH 1/8] feat: implemented stable image shrink --- .github/workflows/checks.yml | 2 + package-lock.json | 53 ++--------- package.json | 4 +- packages/image-shrink/.gitignore | 1 + packages/image-shrink/README.md | 0 packages/image-shrink/jest.config.js | 5 + packages/image-shrink/package.json | 49 ++++++++++ packages/image-shrink/rollup.config.js | 12 +++ packages/image-shrink/src/.eslintrc.json | 3 + .../image-shrink/src/constans/allowLayers.ts | 4 + packages/image-shrink/src/constans/index.ts | 3 + packages/image-shrink/src/constans/markers.ts | 15 +++ packages/image-shrink/src/constans/sizes.ts | 45 +++++++++ packages/image-shrink/src/helper/memoize.ts | 38 ++++++++ packages/image-shrink/src/index.ts | 1 + .../src/utils/IccProfile/getIccProfile.ts | 24 +++++ .../src/utils/IccProfile/replaceIccProfile.ts | 13 +++ .../src/utils/IccProfile/stripIccProfile.ts | 24 +++++ .../src/utils/canvas/canvasResize.ts | 22 +++++ .../src/utils/canvas/canvasTest.ts | 57 ++++++++++++ .../src/utils/canvas/canvasToBlob.ts | 8 ++ .../src/utils/canvas/createCanvas.ts | 9 ++ .../src/utils/canvas/hasTransparency.ts | 28 ++++++ .../src/utils/canvas/testCanvasSize.ts | 38 ++++++++ .../image-shrink/src/utils/devices/mobile.ts | 16 ++++ .../src/utils/exif/findExifOrientation.ts | 33 +++++++ .../image-shrink/src/utils/exif/getExif.ts | 27 ++++++ .../src/utils/exif/isBrowserApplyExif.ts | 25 +++++ .../src/utils/exif/replaceExif.ts | 19 ++++ .../src/utils/image/JPEG/readJpegChunks.ts | 90 ++++++++++++++++++ .../src/utils/image/JPEG/replaceJpegChunk.ts | 49 ++++++++++ .../src/utils/image/imageLoader.ts | 27 ++++++ .../image-shrink/src/utils/render/fallback.ts | 41 +++++++++ .../image-shrink/src/utils/render/index.ts | 0 .../image-shrink/src/utils/render/native.ts | 4 + .../src/utils/shouldSkipShrink.ts | 23 +++++ packages/image-shrink/src/utils/shrinkFile.ts | 91 +++++++++++++++++++ .../image-shrink/src/utils/shrinkImage.ts | 41 +++++++++ packages/image-shrink/tsconfig.build.json | 5 + packages/image-shrink/tsconfig.dts.json | 4 + packages/image-shrink/tsconfig.json | 3 + 41 files changed, 909 insertions(+), 47 deletions(-) create mode 100644 packages/image-shrink/.gitignore create mode 100644 packages/image-shrink/README.md create mode 100644 packages/image-shrink/jest.config.js create mode 100644 packages/image-shrink/package.json create mode 100644 packages/image-shrink/rollup.config.js create mode 100644 packages/image-shrink/src/.eslintrc.json create mode 100644 packages/image-shrink/src/constans/allowLayers.ts create mode 100644 packages/image-shrink/src/constans/index.ts create mode 100644 packages/image-shrink/src/constans/markers.ts create mode 100644 packages/image-shrink/src/constans/sizes.ts create mode 100644 packages/image-shrink/src/helper/memoize.ts create mode 100644 packages/image-shrink/src/index.ts create mode 100644 packages/image-shrink/src/utils/IccProfile/getIccProfile.ts create mode 100644 packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts create mode 100644 packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts create mode 100644 packages/image-shrink/src/utils/canvas/canvasResize.ts create mode 100644 packages/image-shrink/src/utils/canvas/canvasTest.ts create mode 100644 packages/image-shrink/src/utils/canvas/canvasToBlob.ts create mode 100644 packages/image-shrink/src/utils/canvas/createCanvas.ts create mode 100644 packages/image-shrink/src/utils/canvas/hasTransparency.ts create mode 100644 packages/image-shrink/src/utils/canvas/testCanvasSize.ts create mode 100644 packages/image-shrink/src/utils/devices/mobile.ts create mode 100644 packages/image-shrink/src/utils/exif/findExifOrientation.ts create mode 100644 packages/image-shrink/src/utils/exif/getExif.ts create mode 100644 packages/image-shrink/src/utils/exif/isBrowserApplyExif.ts create mode 100644 packages/image-shrink/src/utils/exif/replaceExif.ts create mode 100644 packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts create mode 100644 packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts create mode 100644 packages/image-shrink/src/utils/image/imageLoader.ts create mode 100644 packages/image-shrink/src/utils/render/fallback.ts create mode 100644 packages/image-shrink/src/utils/render/index.ts create mode 100644 packages/image-shrink/src/utils/render/native.ts create mode 100644 packages/image-shrink/src/utils/shouldSkipShrink.ts create mode 100644 packages/image-shrink/src/utils/shrinkFile.ts create mode 100644 packages/image-shrink/src/utils/shrinkImage.ts create mode 100644 packages/image-shrink/tsconfig.build.json create mode 100644 packages/image-shrink/tsconfig.dts.json create mode 100644 packages/image-shrink/tsconfig.json diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 385e2b9cb..14c1a44d3 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -32,6 +32,8 @@ jobs: - 'packages/api-client-utils/**' signed-uploads: - 'packages/signed-uploads/**' + image-shrink: + - 'packages/image-shrink/**' - name: Install dependencies working-directory: ./ run: npm i diff --git a/package-lock.json b/package-lock.json index f19219422..a254feb0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1307,22 +1307,6 @@ "@octokit/types": "^6.0.3" } }, - "node_modules/@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "dev": true, - "peer": true, - "dependencies": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, "node_modules/@octokit/endpoint": { "version": "6.0.12", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", @@ -13253,22 +13237,6 @@ "@octokit/types": "^6.0.3" } }, - "@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "dev": true, - "peer": true, - "requires": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, "@octokit/endpoint": { "version": "6.0.12", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", @@ -13310,8 +13278,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "dev": true, - "requires": {} + "dev": true }, "@octokit/plugin-rest-endpoint-methods": { "version": "3.17.0", @@ -14181,8 +14148,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "acorn-walk": { "version": "8.2.0", @@ -15716,8 +15682,7 @@ "version": "8.8.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", - "dev": true, - "requires": {} + "dev": true }, "eslint-import-resolver-node": { "version": "0.3.7", @@ -17501,8 +17466,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "29.4.3", @@ -19455,8 +19419,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/prettier-config-standard/-/prettier-config-standard-5.0.0.tgz", "integrity": "sha512-QK252QwCxlsak8Zx+rPKZU31UdbRcu9iUk9X1ONYtLSO221OgvV9TlKoTf6iPDZtvF3vE2mkgzFIEgSUcGELSQ==", - "dev": true, - "requires": {} + "dev": true }, "prettier-plugin-jsdoc": { "version": "0.4.2", @@ -19943,8 +19906,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/rollup-plugin-node-externals/-/rollup-plugin-node-externals-5.1.2.tgz", "integrity": "sha512-M32v8yPeVT0dYOYHfd6SNyl0X1xskB15jYFlwUPzIIVpLQ200KVlilbFsoNMUho4SnQuT7Di3s/aLm79bnP48w==", - "dev": true, - "requires": {} + "dev": true }, "run-async": { "version": "2.4.1", @@ -21373,8 +21335,7 @@ "ws": { "version": "8.11.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "requires": {} + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==" }, "xml-name-validator": { "version": "4.0.0", diff --git a/package.json b/package.json index 2421b728d..d2f70f3b0 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "packages/api-client-utils", "packages/upload-client", "packages/rest-client", - "packages/signed-uploads" + "packages/signed-uploads", + "packages/image-shrink" ], "engines": { "node": ">=16" @@ -17,6 +18,7 @@ "build:docs:upload-client": "typedoc --out docs/upload-client packages/upload-client/src/index.ts --readme packages/upload-client/README.md --options ./typedoc.json", "build:docs:rest-client": "typedoc --out docs/rest-client packages/rest-client/src/index.ts --readme packages/rest-client/README.md --options ./typedoc.json", "build:docs:signed-uploads": "typedoc --out docs/signed-uploads packages/signed-uploads/src/index.ts --readme packages/signed-uploads/README.md --options ./typedoc.json", + "build:docs:image-shrink": "typedoc --out docs/image-shrink packages/image-shrink/src/index.ts --readme packages/image-shrink/README.md --options ./typedoc.json", "build:docs": "rimraf docs && run-s build:docs:* && touch docs/.nojekyll", "build": "npm run build --workspaces", "lint": "run-s eslint prettier", diff --git a/packages/image-shrink/.gitignore b/packages/image-shrink/.gitignore new file mode 100644 index 000000000..6b1d0bfab --- /dev/null +++ b/packages/image-shrink/.gitignore @@ -0,0 +1 @@ +LICENSE diff --git a/packages/image-shrink/README.md b/packages/image-shrink/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/image-shrink/jest.config.js b/packages/image-shrink/jest.config.js new file mode 100644 index 000000000..d4c926445 --- /dev/null +++ b/packages/image-shrink/jest.config.js @@ -0,0 +1,5 @@ +import baseConfig from '../../jest.config.js' + +export default { + ...baseConfig +} diff --git a/packages/image-shrink/package.json b/packages/image-shrink/package.json new file mode 100644 index 000000000..abd875c4c --- /dev/null +++ b/packages/image-shrink/package.json @@ -0,0 +1,49 @@ +{ + "name": "@uploadcare/image-shrink", + "version": "6.10.0", + "description": "Library for work with Uploadcare image shrink", + "type": "module", + "main": "./dist/cjs/index.browser.cjs", + "module": "./dist/esm/index.browser.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/esm/index.browser.mjs", + "require": "./dist/cjs/index.browser.cjs" + } + }, + "sideEffects": false, + "files": [ + "dist/*" + ], + "scripts": { + "prepack": "cp ../../LICENSE ./LICENSE", + "clean": "rimraf dist", + "test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js", + "prebuild": "npm run clean", + "build": "npm run build:types && npm run build:compile", + "build:types": "dts-bundle-generator --project tsconfig.dts.json -o dist/index.d.ts src/index.ts", + "build:compile": "rollup -c" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/uploadcare/uploadcare-js-api-clients.git" + }, + "author": "Uploadcare", + "license": "MIT", + "bugs": { + "url": "https://github.com/uploadcare/uploadcare-js-api-clients/issues" + }, + "homepage": "https://github.com/uploadcare/uploadcare-js-api-clients#readme", + "keywords": [ + "uploadcare", + "signed", + "uploads", + "secure", + "signature" + ], + "devDependencies": { + "ts-node": "^10.8.1" + }, + "dependencies": {} +} diff --git a/packages/image-shrink/rollup.config.js b/packages/image-shrink/rollup.config.js new file mode 100644 index 000000000..68b2bf908 --- /dev/null +++ b/packages/image-shrink/rollup.config.js @@ -0,0 +1,12 @@ +import { + createRollupConfig, + RollupTargetEnv +} from '../../createRollupConfig.js' +import * as url from 'url' + +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)) + +const config = ({ targetEnv }) => + createRollupConfig({ targetEnv, cwd: __dirname }) + +export default [...config({ targetEnv: RollupTargetEnv.BROWSER })] diff --git a/packages/image-shrink/src/.eslintrc.json b/packages/image-shrink/src/.eslintrc.json new file mode 100644 index 000000000..baf36aedc --- /dev/null +++ b/packages/image-shrink/src/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "env": {"es6": true, "node": true, "browser": true} +} diff --git a/packages/image-shrink/src/constans/allowLayers.ts b/packages/image-shrink/src/constans/allowLayers.ts new file mode 100644 index 000000000..dc15b2fb5 --- /dev/null +++ b/packages/image-shrink/src/constans/allowLayers.ts @@ -0,0 +1,4 @@ +export const allowLayers = [ + 1, // L (black-white) + 3 // RGB +] diff --git a/packages/image-shrink/src/constans/index.ts b/packages/image-shrink/src/constans/index.ts new file mode 100644 index 000000000..5c381c78c --- /dev/null +++ b/packages/image-shrink/src/constans/index.ts @@ -0,0 +1,3 @@ +export { allowLayers } from './allowLayers' +export { markers } from './markers' +export { sizes } from './sizes' diff --git a/packages/image-shrink/src/constans/markers.ts b/packages/image-shrink/src/constans/markers.ts new file mode 100644 index 000000000..d5cec6f2c --- /dev/null +++ b/packages/image-shrink/src/constans/markers.ts @@ -0,0 +1,15 @@ +export const markers = [ + 0xc0, // ("SOF0", "Baseline DCT", SOF) + 0xc1, // ("SOF1", "Extended Sequential DCT", SOF) + 0xc2, // ("SOF2", "Progressive DCT", SOF) + 0xc3, // ("SOF3", "Spatial lossless", SOF) + 0xc5, // ("SOF5", "Differential sequential DCT", SOF) + 0xc6, // ("SOF6", "Differential progressive DCT", SOF) + 0xc7, // ("SOF7", "Differential spatial", SOF) + 0xc9, // ("SOF9", "Extended sequential DCT (AC)", SOF) + 0xca, // ("SOF10", "Progressive DCT (AC)", SOF) + 0xcb, // ("SOF11", "Spatial lossless DCT (AC)", SOF) + 0xcd, // ("SOF13", "Differential sequential DCT (AC)", SOF) + 0xce, // ("SOF14", "Differential progressive DCT (AC)", SOF) + 0xcf // ("SOF15", "Differential spatial (AC)", SOF) +] diff --git a/packages/image-shrink/src/constans/sizes.ts b/packages/image-shrink/src/constans/sizes.ts new file mode 100644 index 000000000..c89ade8d0 --- /dev/null +++ b/packages/image-shrink/src/constans/sizes.ts @@ -0,0 +1,45 @@ +export const sizes = { + squareSide: [ + // Safari (iOS < 9, ram >= 256) + // We are supported mobile safari < 9 since widget v2, by 5 Mpx limit + // so it's better to continue support despite the absence of this browser in the support table + Math.floor(Math.sqrt(5 * 1000 * 1000)), + // IE Mobile (Windows Phone 8.x) + // Safari (iOS >= 9) + 4096, + // IE 9 (Win) + 8192, + // Firefox 63 (Mac, Win) + 11180, + // Chrome 68 (Android 6) + 10836, + // Chrome 68 (Android 5) + 11402, + // Chrome 68 (Android 7.1-9) + 14188, + // Chrome 70 (Mac, Win) + // Chrome 68 (Android 4.4) + // Edge 17 (Win) + // Safari 7-12 (Mac) + 16384 + ], + dimension: [ + // IE Mobile (Windows Phone 8.x) + 4096, + // IE 9 (Win) + 8192, + // Edge 17 (Win) + // IE11 (Win) + 16384, + // Chrome 70 (Mac, Win) + // Chrome 68 (Android 4.4-9) + // Firefox 63 (Mac, Win) + 32767, + // Chrome 83 (Mac, Win) + // Safari 7-12 (Mac) + // Safari (iOS 9-12) + // Actually Safari has a much bigger limits - 4194303 of width and 8388607 of height, + // but we will not use them + 65535 + ] +} diff --git a/packages/image-shrink/src/helper/memoize.ts b/packages/image-shrink/src/helper/memoize.ts new file mode 100644 index 000000000..54b0b925c --- /dev/null +++ b/packages/image-shrink/src/helper/memoize.ts @@ -0,0 +1,38 @@ +export const memoize = (fn, serializer) => { + const cache = {} + return (...args) => { + const key = serializer(args, cache) + return key in cache ? cache[key] : (cache[key] = fn(...args)) + } +} + +/** + * Memoization key serealizer, that prevents unnecessary canvas tests. No need + * to make test if we know that: + * + * - Browser supports higher canvas size + * - Browser doesn't support lower canvas size + */ +export const memoKeySerializer = (args, cache) => { + const [w] = args + const cachedWidths = Object.keys(cache) + .map((val) => parseInt(val, 10)) + .sort((a, b) => a - b) + + for (let i = 0; i < cachedWidths.length; i++) { + const cachedWidth = cachedWidths[i] + const isSupported = !!cache[cachedWidth] + // higher supported canvas size, return it + if (cachedWidth > w && isSupported) { + return cachedWidth + } + // lower unsupported canvas size, return it + if (cachedWidth < w && !isSupported) { + return cachedWidth + } + } + + // use canvas width as the key, + // because we're doing dimension test by width - [dimension, 1] + return w +} diff --git a/packages/image-shrink/src/index.ts b/packages/image-shrink/src/index.ts new file mode 100644 index 000000000..b206b750e --- /dev/null +++ b/packages/image-shrink/src/index.ts @@ -0,0 +1 @@ +export { shrinkFile, type TSetting } from './utils/shrinkFile' diff --git a/packages/image-shrink/src/utils/IccProfile/getIccProfile.ts b/packages/image-shrink/src/utils/IccProfile/getIccProfile.ts new file mode 100644 index 000000000..3f8a8d97f --- /dev/null +++ b/packages/image-shrink/src/utils/IccProfile/getIccProfile.ts @@ -0,0 +1,24 @@ +import { readJpegChunks } from '../image/JPEG/readJpegChunks' + +export const getIccProfile = async (file: File) => { + const iccProfile: DataView[] = [] + const { promiseReadJpegChunks, stack } = readJpegChunks() + + return await promiseReadJpegChunks(file) + .then(() => { + stack.forEach(({ marker, view }) => { + if (marker === 0xe2) { + if ( + // check for "ICC_PROFILE\0" + view.getUint32(0) === 0x4943435f && + view.getUint32(4) === 0x50524f46 && + view.getUint32(8) === 0x494c4500 + ) { + iccProfile.push(view) + } + } + }) + return iccProfile + }) + .catch(() => iccProfile) +} diff --git a/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts b/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts new file mode 100644 index 000000000..81abc8794 --- /dev/null +++ b/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts @@ -0,0 +1,13 @@ +import { replaceJpegChunk } from '../image/JPEG/replaceJpegChunk' + +export const MARKER = 0xe2 +export const replaceIccProfile = ( + blob: Blob | File, + iccProfiles: DataView[] +) => { + return replaceJpegChunk( + blob, + MARKER, + iccProfiles.map((chunk) => chunk.buffer) + ) +} diff --git a/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts b/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts new file mode 100644 index 000000000..cc2e5d4f5 --- /dev/null +++ b/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts @@ -0,0 +1,24 @@ +import { replaceIccProfile } from './replaceIccProfile' +import { imageLoader } from '../image/imageLoader' + +export const stripIccProfile = (inputFile: File): Promise => { + return new Promise((resolve, reject) => { + replaceIccProfile(inputFile, []) + .then((file: File) => { + imageLoader(URL.createObjectURL(file)) + .then((img) => { + resolve(img) + return img + }) + .then((img) => { + URL.revokeObjectURL(img.src) + }) + .catch(() => { + reject('Failed to load image') + }) + }) + .catch(() => { + reject('Failed to strip ICC profile and not image') + }) + }) +} diff --git a/packages/image-shrink/src/utils/canvas/canvasResize.ts b/packages/image-shrink/src/utils/canvas/canvasResize.ts new file mode 100644 index 000000000..4e61ba468 --- /dev/null +++ b/packages/image-shrink/src/utils/canvas/canvasResize.ts @@ -0,0 +1,22 @@ +import { createCanvas } from './createCanvas' + +export const canvasResize = (img, w, h) => { + return new Promise((resolve, reject) => { + try { + const { ctx, canvas } = createCanvas() + + canvas.width = w + canvas.height = h + + ctx.imageSmoothingQuality = 'high' + ctx.drawImage(img, 0, 0, w, h) + + img.src = '//:0' // for image + img.width = img.height = 1 // for canvas + + resolve(canvas) + } catch (e) { + reject(`Failed to resize image. ${e}`) + } + }) +} diff --git a/packages/image-shrink/src/utils/canvas/canvasTest.ts b/packages/image-shrink/src/utils/canvas/canvasTest.ts new file mode 100644 index 000000000..ef954d29f --- /dev/null +++ b/packages/image-shrink/src/utils/canvas/canvasTest.ts @@ -0,0 +1,57 @@ +import { createCanvas } from './createCanvas' + +// add constans +const TestPixel = { + R: 55, + G: 110, + B: 165, + A: 255 +} + +const FILL_STYLE = `rgba(${TestPixel.R}, ${TestPixel.G}, ${TestPixel.B}, ${ + TestPixel.A / 255 +})` +export const canvasTest = (width, height) => { + try { + const fill = [width - 1, height - 1, 1, 1] // x, y, width, height + + const { canvas: cropCvs, ctx: cropCtx } = createCanvas() + cropCvs.width = 1 + cropCvs.height = 1 + + const { canvas: testCvs, ctx: testCtx } = createCanvas() + testCvs.width = width + testCvs.height = height + + if (testCtx) { + testCtx.fillStyle = FILL_STYLE + testCtx.fillRect.apply(testCtx, fill) + + // Render the test pixel in the bottom-right corner of the + // test canvas in the top-left of the 1x1 crop canvas. This + // dramatically reducing the time for getImageData to complete. + cropCtx.drawImage(testCvs, width - 1, height - 1, 1, 1, 0, 0, 1, 1) + } + + const imageData = cropCtx && cropCtx.getImageData(0, 0, 1, 1).data + let isTestPass = false + + if (imageData) { + // On IE10, imageData have type CanvasPixelArray, not Uint8ClampedArray. + // CanvasPixelArray supports index access operations only. + // Array buffers can't be destructuredd and compared with JSON.stringify + isTestPass = + imageData[0] === TestPixel.R && + imageData[1] === TestPixel.G && + imageData[2] === TestPixel.B && + imageData[3] === TestPixel.A + } + + testCvs.width = testCvs.height = 1 + + return isTestPass + } catch (e) { + new Error(`Failed to test for max canvas size of ${width}x${height}.`) + return false + } +} diff --git a/packages/image-shrink/src/utils/canvas/canvasToBlob.ts b/packages/image-shrink/src/utils/canvas/canvasToBlob.ts new file mode 100644 index 000000000..8faea1800 --- /dev/null +++ b/packages/image-shrink/src/utils/canvas/canvasToBlob.ts @@ -0,0 +1,8 @@ +export const canvasToBlob = ( + canvas: HTMLCanvasElement, + type: string, + quality: number | undefined, + callback +): void => { + return canvas.toBlob(callback, type, quality) +} diff --git a/packages/image-shrink/src/utils/canvas/createCanvas.ts b/packages/image-shrink/src/utils/canvas/createCanvas.ts new file mode 100644 index 000000000..a64648401 --- /dev/null +++ b/packages/image-shrink/src/utils/canvas/createCanvas.ts @@ -0,0 +1,9 @@ +export const createCanvas = () => { + const canvas = document.createElement('canvas') as HTMLCanvasElement + const ctx = canvas.getContext('2d') as CanvasRenderingContext2D + + return { + canvas, + ctx + } +} diff --git a/packages/image-shrink/src/utils/canvas/hasTransparency.ts b/packages/image-shrink/src/utils/canvas/hasTransparency.ts new file mode 100644 index 000000000..ad3826b6b --- /dev/null +++ b/packages/image-shrink/src/utils/canvas/hasTransparency.ts @@ -0,0 +1,28 @@ +import { createCanvas } from "./createCanvas"; + +export const hasTransparency = (img) => { + const canvasSize = 50; + + // Create a canvas element and get 2D rendering context + const { ctx, canvas } = createCanvas(); + canvas.width = canvas.height = canvasSize; + + // Draw the image onto the canvas + ctx.drawImage(img, 0, 0, canvasSize, canvasSize); + + // Get the image data + const imageData = ctx.getImageData(0, 0, canvasSize, canvasSize).data; + + // Reset the canvas dimensions + canvas.width = canvas.height = 1; + + // Check for transparency in the alpha channel + for (let i = 3; i < imageData.length; i += 4) { + if (imageData[i] < 254) { + return true; + } + } + + // No transparency found + return false; +}; diff --git a/packages/image-shrink/src/utils/canvas/testCanvasSize.ts b/packages/image-shrink/src/utils/canvas/testCanvasSize.ts new file mode 100644 index 000000000..0f965eb72 --- /dev/null +++ b/packages/image-shrink/src/utils/canvas/testCanvasSize.ts @@ -0,0 +1,38 @@ +import { sizes } from '../../constans' +import { memoize, memoKeySerializer } from '../../helper/memoize' +import { canvasTest } from './canvasTest' + +function wrapAsync(fn) { + return (...args) => { + return new Promise((resolve) => { + setTimeout(() => { + const result = fn(...args) + resolve(result) + }, 0) + }) + } +} + +const squareTest = wrapAsync(memoize(canvasTest, memoKeySerializer)) +const dimensionTest = wrapAsync(memoize(canvasTest, memoKeySerializer)) + +export const testCanvasSize = (w, h) => { + return new Promise(async (resolve, reject) => { + const testSquareSide = sizes.squareSide.find((side) => side * side >= w * h) + const testDimension = sizes.dimension.find((side) => side >= w && side >= h) + + if (!testSquareSide || !testDimension) { + reject() + return + } + + const squareSupported = await squareTest(testSquareSide, testSquareSide) + const dimensionSupported = await dimensionTest(testDimension, 1) + + if (squareSupported && dimensionSupported) { + resolve(true) + } else { + reject() + } + }) +} diff --git a/packages/image-shrink/src/utils/devices/mobile.ts b/packages/image-shrink/src/utils/devices/mobile.ts new file mode 100644 index 000000000..1e7b45343 --- /dev/null +++ b/packages/image-shrink/src/utils/devices/mobile.ts @@ -0,0 +1,16 @@ +export const isIOS = () => { + if (/iPad|iPhone|iPod/.test(navigator.platform)) { + return true + } else { + return ( + navigator.maxTouchPoints && + navigator.maxTouchPoints > 2 && + /MacIntel/.test(navigator.platform) + ) + } +} + +export const isIpadOS = + navigator.maxTouchPoints && + navigator.maxTouchPoints > 2 && + /MacIntel/.test(navigator.platform) diff --git a/packages/image-shrink/src/utils/exif/findExifOrientation.ts b/packages/image-shrink/src/utils/exif/findExifOrientation.ts new file mode 100644 index 000000000..ea2eaedea --- /dev/null +++ b/packages/image-shrink/src/utils/exif/findExifOrientation.ts @@ -0,0 +1,33 @@ +export const findExifOrientation = (exif: DataView, exifCallback) => { + let count, j, little, offset, ref + if ( + !exif || + exif.byteLength < 14 || + exif.getUint32(0) !== 0x45786966 || + exif.getUint16(4) !== 0 + ) { + return null + } + if (exif.getUint16(6) === 0x4949) { + little = true + } else if (exif.getUint16(6) === 0x4d4d) { + little = false + } else { + return null + } + if (exif.getUint16(8, little) !== 0x002a) { + return null + } + offset = 8 + exif.getUint32(10, little) + count = exif.getUint16(offset - 2, little) + for (j = 0, ref = count; ref >= 0 ? j < ref : j > ref; ref >= 0 ? ++j : --j) { + if (exif.byteLength < offset + 10) { + return null + } + if (exif.getUint16(offset, little) === 0x0112) { + return exifCallback(offset + 8, little) + } + offset += 12 + } + return null +} diff --git a/packages/image-shrink/src/utils/exif/getExif.ts b/packages/image-shrink/src/utils/exif/getExif.ts new file mode 100644 index 000000000..716a0fb7c --- /dev/null +++ b/packages/image-shrink/src/utils/exif/getExif.ts @@ -0,0 +1,27 @@ +import { readJpegChunks } from '../image/JPEG/readJpegChunks' + +export const getExif = async (file: File) => { + let isExif: DataView | null = null + + const { promiseReadJpegChunks, stack } = readJpegChunks() + return promiseReadJpegChunks(file) + .then(() => { + stack.forEach(({ marker, view }) => { + if (!isExif && marker === 0xe1) { + if (view.byteLength >= 14) { + if ( + // check for "Exif\0" + view.getUint32(0) === 0x45786966 && + view.getUint16(4) === 0 + ) { + isExif = view + return isExif + } + } + } + + return isExif + }) + }) + .catch(() => isExif) +} diff --git a/packages/image-shrink/src/utils/exif/isBrowserApplyExif.ts b/packages/image-shrink/src/utils/exif/isBrowserApplyExif.ts new file mode 100644 index 000000000..cd2fbc8e2 --- /dev/null +++ b/packages/image-shrink/src/utils/exif/isBrowserApplyExif.ts @@ -0,0 +1,25 @@ +const base64ImageSrc = + 'data:image/jpg;base64,' + + '/9j/4AAQSkZJRgABAQEASABIAAD/4QA6RXhpZgAATU0AKgAAAAgAAwESAAMAAAABAAYAAAEo' + + 'AAMAAAABAAIAAAITAAMAAAABAAEAAAAAAAD/2wBDAP//////////////////////////////' + + '////////////////////////////////////////////////////////wAALCAABAAIBASIA' + + '/8QAJgABAAAAAAAAAAAAAAAAAAAAAxABAAAAAAAAAAAAAAAAAAAAAP/aAAgBAQAAPwBH/9k=' + +let isApplied +export const isBrowserApplyExif = () => { + return new Promise((resolve, reject) => { + if (isApplied !== undefined) { + resolve(isApplied) + } else { + const image = new Image() + + image.addEventListener('load', () => { + isApplied = image.naturalWidth < image.naturalHeight + image.src = '//:0' + resolve(isApplied) + }) + + image.src = base64ImageSrc + } + }) +} diff --git a/packages/image-shrink/src/utils/exif/replaceExif.ts b/packages/image-shrink/src/utils/exif/replaceExif.ts new file mode 100644 index 000000000..e692ef422 --- /dev/null +++ b/packages/image-shrink/src/utils/exif/replaceExif.ts @@ -0,0 +1,19 @@ +import { replaceJpegChunk } from '../image/JPEG/replaceJpegChunk' +import { findExifOrientation } from './findExifOrientation' + +export const setExifOrientation = (exif, orientation) => { + findExifOrientation(exif, (offset, little) => + exif.setUint16(offset, orientation, little) + ) +} +export const replaceExif = async ( + file: File, + exif: DataView, + isExifApplied: boolean | unknown +) => { + if (isExifApplied) { + setExifOrientation(exif, 1) + } + + return replaceJpegChunk(file, 0xe1, [exif.buffer]) +} diff --git a/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts b/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts new file mode 100644 index 000000000..10a775d79 --- /dev/null +++ b/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts @@ -0,0 +1,90 @@ +type TChunk = { + startPos: number + length: number + marker: number + view: DataView +} + +export const readJpegChunks = () => { + let stack: TChunk[] = [] + const promiseReadJpegChunks = (file) => + new Promise((resolve, reject) => { + let pos + const readToView = (file, cb) => { + const reader = new FileReader() + + reader.addEventListener('load', () => { + cb(new DataView(reader.result as ArrayBuffer)) + }) + + reader.addEventListener('error', (e) => { + reject(`Reader error: ${e}`) + }) + + reader.readAsArrayBuffer(file) + } + + const readNext = () => + readToView(file.slice(pos, pos + 128), (view) => { + let i, j, ref + for ( + i = j = 0, ref = view.byteLength; + ref >= 0 ? j < ref : j > ref; + i = ref >= 0 ? ++j : --j + ) { + if (view.getUint8(i) === 0xff) { + pos += i + break + } + } + + return readNextChunk() + }) + + const readNextChunk = () => { + let startPos = pos + + return readToView(file.slice(pos, (pos += 4)), (view) => { + let length, marker + + if (view.byteLength !== 4 || view.getUint8(0) !== 0xff) { + return reject('Corrupted') + } + + marker = view?.getUint8(1) + + if (marker === 0xda) { + return resolve(true) + } + + length = view.getUint16(2) - 2 + return readToView(file.slice(pos, (pos += length)), (view) => { + if (view.byteLength !== length) { + return reject('Corrupted') + } + + stack.push({ startPos, length, marker, view }) + return readNext() + }) + }) + } + + if (!(FileReader && DataView)) { + reject('Not Support') + } + + pos = 2 + readToView(file.slice(0, 2), function (view) { + if (view.getUint16(0) !== 0xffd8) { + reject('Not jpeg') + } + + return readNext() + }) + }) + + return { + stack, + promiseReadJpegChunks + } +} diff --git a/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts b/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts new file mode 100644 index 000000000..19d79656b --- /dev/null +++ b/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts @@ -0,0 +1,49 @@ +// @ts-nocheck +import { readJpegChunks } from './readJpegChunks' + +export const replaceJpegChunk = (blob, marker, chunks) => { + return new Promise(async (resolve, reject) => { + const oldChunkPos = [] + const oldChunkLength = [] + + const { promiseReadJpegChunks, stack } = readJpegChunks() + + return await promiseReadJpegChunks(blob) + .then(() => { + stack.forEach((chunk) => { + if (chunk.marker === marker) { + oldChunkPos.push(chunk.startPos) + return oldChunkLength.push(chunk.length) + } + }) + }) + .then(() => { + const newChunks = [blob.slice(0, 2)] + + for (const chunk of chunks) { + const intro = new DataView(new ArrayBuffer(4)) + intro.setUint16(0, 0xff00 + marker) + intro.setUint16(2, chunk.byteLength + 2) + newChunks.push(intro.buffer) + newChunks.push(chunk) + } + + let pos = 2 + for (let i = 0; i < oldChunkPos.length; i++) { + if (oldChunkPos[i] > pos) { + newChunks.push(blob.slice(pos, oldChunkPos[i])) + } + pos = oldChunkPos[i] + oldChunkLength[i] + 4 + } + + newChunks.push(blob.slice(pos, blob.size)) + + const newBlob = new Blob(newChunks, { + type: blob.type + }) + + resolve(newBlob) + }) + .catch(() => reject(blob)) + }).catch(() => blob) +} diff --git a/packages/image-shrink/src/utils/image/imageLoader.ts b/packages/image-shrink/src/utils/image/imageLoader.ts new file mode 100644 index 000000000..c57e0464b --- /dev/null +++ b/packages/image-shrink/src/utils/image/imageLoader.ts @@ -0,0 +1,27 @@ +// @ts-nocheck +export const processImage = (image: HTMLImageElement, src?: string) => { + return new Promise((resolve, reject) => { + if (src) { + image.src = src + } + + if (image.complete) { + resolve(image) + } else { + image.addEventListener('load', () => { + resolve(image) + }) + image.addEventListener('error', () => { + reject(image) + }) + } + }) +} + +export const imageLoader = (image: unknown) => { + if (image.src) { + return processImage(image) + } + + return processImage(new Image(), image) +} diff --git a/packages/image-shrink/src/utils/render/fallback.ts b/packages/image-shrink/src/utils/render/fallback.ts new file mode 100644 index 000000000..74c172751 --- /dev/null +++ b/packages/image-shrink/src/utils/render/fallback.ts @@ -0,0 +1,41 @@ +// @ts-nocheck +import { testCanvasSize } from '../canvas/testCanvasSize' +import { canvasResize } from '../canvas/canvasResize' + +const calcShrinkSteps = function (sourceW, targetW, targetH, step) { + const steps = [] + let sW = targetW + let sH = targetH + + // result should include at least one target step, + // even if abs(source - target) < step * source + // just to be sure nothing will break + // if the original resolution / target resolution condition changes + do { + steps.push([sW, sH]) + sW = Math.round(sW / step) + sH = Math.round(sH / step) + } while (sW < sourceW * step) + + return steps.reverse() +} + +export const fallback = ({ img, sourceW, targetW, targetH, step }) => { + const steps = calcShrinkSteps(sourceW, targetW, targetH, step) + + return steps + .reduce((chain, [w, h]) => { + return chain + .then((canvas) => { + return testCanvasSize(w, h) + .then(() => canvas) + .catch(() => canvasResize(canvas, w, h)) + }) + .then((canvas) => { + const progress = (sourceW - w) / (sourceW - targetW) + return { canvas, progress } + }) + }, Promise.resolve(img)) + .then(({ canvas }) => canvas) + .catch((error) => Promise.reject(error)) +} diff --git a/packages/image-shrink/src/utils/render/index.ts b/packages/image-shrink/src/utils/render/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/image-shrink/src/utils/render/native.ts b/packages/image-shrink/src/utils/render/native.ts new file mode 100644 index 000000000..c778ed6f5 --- /dev/null +++ b/packages/image-shrink/src/utils/render/native.ts @@ -0,0 +1,4 @@ +import { canvasResize } from '../canvas/canvasResize' + +export const native = ({ img, targetW, targetH }) => + canvasResize(img, targetW, targetH) diff --git a/packages/image-shrink/src/utils/shouldSkipShrink.ts b/packages/image-shrink/src/utils/shouldSkipShrink.ts new file mode 100644 index 000000000..8e525d889 --- /dev/null +++ b/packages/image-shrink/src/utils/shouldSkipShrink.ts @@ -0,0 +1,23 @@ +import { readJpegChunks } from './image/JPEG/readJpegChunks' +import { allowLayers, markers } from '../constans' + +export const shouldSkipShrink = async (file: File) => { + let skip = false + + const { promiseReadJpegChunks, stack } = readJpegChunks() + + return await promiseReadJpegChunks(file) + .then(() => { + stack.forEach(({ marker, view }) => { + if (!skip && markers.indexOf(marker) >= 0) { + const layer = view.getUint8(5) + if (allowLayers.indexOf(layer) < 0) { + skip = true + } + } + }) + + return skip + }) + .catch(() => skip) +} diff --git a/packages/image-shrink/src/utils/shrinkFile.ts b/packages/image-shrink/src/utils/shrinkFile.ts new file mode 100644 index 000000000..803752e08 --- /dev/null +++ b/packages/image-shrink/src/utils/shrinkFile.ts @@ -0,0 +1,91 @@ +import { shrinkImage } from './shrinkImage' +import { stripIccProfile } from './IccProfile/stripIccProfile' +import { shouldSkipShrink } from './shouldSkipShrink' +import { canvasToBlob } from './canvas/canvasToBlob' +import { hasTransparency } from './canvas/hasTransparency' +import { isBrowserApplyExif } from './exif/isBrowserApplyExif' +import { getExif } from './exif/getExif' +import { getIccProfile } from './IccProfile/getIccProfile' +import { replaceExif } from './exif/replaceExif' +import { replaceIccProfile } from './IccProfile/replaceIccProfile' + +export type TSetting = { + size: number + quality?: number +} + +export const shrinkFile = (file: File, settings: TSetting): Promise => { + return new Promise(async (resolve, reject) => { + if (!(URL && DataView && Blob)) { + reject('Not support') + } + + try { + const image = await shouldSkipShrink(file) + .then((shouldSkip) => { + if (shouldSkip) { + return reject('Should skipped') + } + }) + .then(() => { + return stripIccProfile(file).catch(() => { + reject('Failed to strip ICC profile and not image') + }) + }) + + const exifList = Promise.allSettled([ + getExif(file), + isBrowserApplyExif(), + getIccProfile(file) + ]) + + exifList.then(async (results) => { + const isRejected = results.some( + (result) => result.status === 'rejected' + ) + + const [exif, isExifApplied, iccProfile] = results as { + value: any + status: string + }[] + const isJPEG = !isRejected + + return shrinkImage(image as HTMLImageElement, settings) + .then(async (canvas) => { + let format = 'image/jpeg' + let quality: number | undefined = settings?.quality || 0.8 + + if (!isJPEG && hasTransparency(canvas)) { + format = 'image/png' + quality = undefined + } + + canvasToBlob(canvas, format, quality, (blob) => { + canvas.width = canvas.height = 1 + + let replaceChain = Promise.resolve(blob) + + if (exif.value) { + replaceChain = replaceChain + .then((blob) => + replaceExif(blob, exif.value, isExifApplied.value) + ) + .catch(() => blob) + } + + if (iccProfile?.value?.length > 0) { + replaceChain = replaceChain + .then((blob) => replaceIccProfile(blob, iccProfile.value)) + .catch(() => blob) + } + + replaceChain.then(resolve).catch(() => resolve(blob)) + }) + }) + .catch(() => reject(file)) + }) + } catch (e) { + reject(`Failed to shrink image: ${e}`) + } + }) +} diff --git a/packages/image-shrink/src/utils/shrinkImage.ts b/packages/image-shrink/src/utils/shrinkImage.ts new file mode 100644 index 000000000..a203c4d46 --- /dev/null +++ b/packages/image-shrink/src/utils/shrinkImage.ts @@ -0,0 +1,41 @@ +import { testCanvasSize } from './canvas/testCanvasSize' +import { createCanvas } from './canvas/createCanvas' +import { native } from './render/native' +import { fallback } from './render/fallback' +import { isIOS, isIpadOS } from './devices/mobile' +import { TSetting } from './shrinkFile' + +export const STEP = 0.71 // should be > sqrt(0.5) + +export const shrinkImage = ( + img: HTMLImageElement, + settings: TSetting +): Promise => { + return new Promise((resolve, reject) => { + if (img.width * STEP * img.height * STEP < settings.size) { + reject('Not required') + } + + const sourceW = img.width + const sourceH = img.height + const ratio = sourceW / sourceH + + // target size shouldn't be greater than settings.size in any case + const targetW = Math.floor(Math.sqrt(settings.size * ratio)) + const targetH = Math.floor(settings.size / Math.sqrt(settings.size * ratio)) + + return testCanvasSize(targetW, targetH) + .then(() => { + const { ctx } = createCanvas() + const supportNative = 'imageSmoothingQuality' in ctx + + const useNativeScaling = supportNative && !isIOS() && !isIpadOS + + return useNativeScaling + ? native({ img, targetW, targetH }) + : fallback({ img, sourceW, targetW, targetH, step: STEP }) + }) + .then((canvas) => resolve(canvas)) + .catch(() => reject('Not supported')) + }) +} diff --git a/packages/image-shrink/tsconfig.build.json b/packages/image-shrink/tsconfig.build.json new file mode 100644 index 000000000..680cd691d --- /dev/null +++ b/packages/image-shrink/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.build.json", + "include": ["src"], + "exclude": ["node_modules", "**/*.test.ts"], +} diff --git a/packages/image-shrink/tsconfig.dts.json b/packages/image-shrink/tsconfig.dts.json new file mode 100644 index 000000000..ca53c1d96 --- /dev/null +++ b/packages/image-shrink/tsconfig.dts.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.dts.json", + "include": ["src"], +} diff --git a/packages/image-shrink/tsconfig.json b/packages/image-shrink/tsconfig.json new file mode 100644 index 000000000..6f83eb665 --- /dev/null +++ b/packages/image-shrink/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json", +} From 03ac60056270f948cbfcb27aa61208256a7c225f Mon Sep 17 00:00:00 2001 From: egordidenko Date: Mon, 12 Feb 2024 12:03:51 +0300 Subject: [PATCH 2/8] feat: add import/export for docs --- packages/image-shrink/src/index.ts | 37 +++++++++++++++++++ .../src/utils/IccProfile/replaceIccProfile.ts | 2 +- .../src/utils/IccProfile/stripIccProfile.ts | 3 +- .../src/utils/canvas/canvasTest.ts | 4 +- .../image-shrink/src/utils/canvas/index.ts | 6 +++ .../src/utils/image/JPEG/replaceJpegChunk.ts | 12 +++--- .../src/utils/image/imageLoader.ts | 10 ++++- .../image-shrink/src/utils/render/index.ts | 2 + 8 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 packages/image-shrink/src/utils/canvas/index.ts diff --git a/packages/image-shrink/src/index.ts b/packages/image-shrink/src/index.ts index b206b750e..856a0b478 100644 --- a/packages/image-shrink/src/index.ts +++ b/packages/image-shrink/src/index.ts @@ -1 +1,38 @@ +// Path: packages/image-shrink/src/constans +export { allowLayers, markers, sizes } from './constans' + +// Path: packages/image-shrink/src/helper +export { memoize, memoKeySerializer } from './helper/memoize' + export { shrinkFile, type TSetting } from './utils/shrinkFile' + +export { shrinkImage, STEP } from './utils/shrinkImage' + +export { shouldSkipShrink } from './utils/shouldSkipShrink' + +export { + canvasResize, + canvasTest, + testCanvasSize, + createCanvas, + canvasToBlob, + hasTransparency +} from './utils/canvas' + +export { isIOS, isIpadOS } from './utils/devices/mobile' + +export { findExifOrientation } from './utils/exif/findExifOrientation' +export { getExif } from './utils/exif/getExif' +export { isBrowserApplyExif } from './utils/exif/isBrowserApplyExif' +export { replaceExif } from './utils/exif/replaceExif' + +export { getIccProfile } from './utils/IccProfile/getIccProfile' +export { replaceIccProfile } from './utils/IccProfile/replaceIccProfile' +export { stripIccProfile } from './utils/IccProfile/stripIccProfile' + +export { imageLoader } from './utils/image/imageLoader' + +export { replaceJpegChunk } from './utils/image/JPEG/replaceJpegChunk' +export { readJpegChunks } from './utils/image/JPEG/readJpegChunks' + +export { fallback, native } from './utils/render' diff --git a/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts b/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts index 81abc8794..20a99c61f 100644 --- a/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts +++ b/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts @@ -4,7 +4,7 @@ export const MARKER = 0xe2 export const replaceIccProfile = ( blob: Blob | File, iccProfiles: DataView[] -) => { +): Promise => { return replaceJpegChunk( blob, MARKER, diff --git a/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts b/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts index cc2e5d4f5..0e8df3290 100644 --- a/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts +++ b/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts @@ -4,7 +4,8 @@ import { imageLoader } from '../image/imageLoader' export const stripIccProfile = (inputFile: File): Promise => { return new Promise((resolve, reject) => { replaceIccProfile(inputFile, []) - .then((file: File) => { + .then((file: Blob) => { + console.log({ file }, typeof file) imageLoader(URL.createObjectURL(file)) .then((img) => { resolve(img) diff --git a/packages/image-shrink/src/utils/canvas/canvasTest.ts b/packages/image-shrink/src/utils/canvas/canvasTest.ts index ef954d29f..5bd620d0a 100644 --- a/packages/image-shrink/src/utils/canvas/canvasTest.ts +++ b/packages/image-shrink/src/utils/canvas/canvasTest.ts @@ -11,9 +11,11 @@ const TestPixel = { const FILL_STYLE = `rgba(${TestPixel.R}, ${TestPixel.G}, ${TestPixel.B}, ${ TestPixel.A / 255 })` + +type TFillRect = [number, number, number, number] export const canvasTest = (width, height) => { try { - const fill = [width - 1, height - 1, 1, 1] // x, y, width, height + const fill: TFillRect = [width - 1, height - 1, 1, 1] // x, y, width, height const { canvas: cropCvs, ctx: cropCtx } = createCanvas() cropCvs.width = 1 diff --git a/packages/image-shrink/src/utils/canvas/index.ts b/packages/image-shrink/src/utils/canvas/index.ts new file mode 100644 index 000000000..e36fe0dbb --- /dev/null +++ b/packages/image-shrink/src/utils/canvas/index.ts @@ -0,0 +1,6 @@ +export { canvasResize } from './canvasResize' +export { canvasTest } from './canvasTest' +export { canvasToBlob } from './canvasToBlob' +export { createCanvas } from './createCanvas' +export { hasTransparency } from './hasTransparency' +export { testCanvasSize } from './testCanvasSize' diff --git a/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts b/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts index 19d79656b..f4a8c89c1 100644 --- a/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts +++ b/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts @@ -1,7 +1,7 @@ // @ts-nocheck import { readJpegChunks } from './readJpegChunks' -export const replaceJpegChunk = (blob, marker, chunks) => { +export const replaceJpegChunk = (blob, marker, chunks): Promise => { return new Promise(async (resolve, reject) => { const oldChunkPos = [] const oldChunkLength = [] @@ -38,11 +38,11 @@ export const replaceJpegChunk = (blob, marker, chunks) => { newChunks.push(blob.slice(pos, blob.size)) - const newBlob = new Blob(newChunks, { - type: blob.type - }) - - resolve(newBlob) + resolve( + new Blob(newChunks, { + type: blob.type + }) + ) }) .catch(() => reject(blob)) }).catch(() => blob) diff --git a/packages/image-shrink/src/utils/image/imageLoader.ts b/packages/image-shrink/src/utils/image/imageLoader.ts index c57e0464b..896e7a0d8 100644 --- a/packages/image-shrink/src/utils/image/imageLoader.ts +++ b/packages/image-shrink/src/utils/image/imageLoader.ts @@ -1,5 +1,9 @@ // @ts-nocheck -export const processImage = (image: HTMLImageElement, src?: string) => { + +export const processImage = ( + image: HTMLImageElement, + src?: string +): Promise => { return new Promise((resolve, reject) => { if (src) { image.src = src @@ -18,7 +22,9 @@ export const processImage = (image: HTMLImageElement, src?: string) => { }) } -export const imageLoader = (image: unknown) => { +export const imageLoader = ( + image: File | Blob | string +): Promise => { if (image.src) { return processImage(image) } diff --git a/packages/image-shrink/src/utils/render/index.ts b/packages/image-shrink/src/utils/render/index.ts index e69de29bb..eec9f66e9 100644 --- a/packages/image-shrink/src/utils/render/index.ts +++ b/packages/image-shrink/src/utils/render/index.ts @@ -0,0 +1,2 @@ +export { fallback } from './fallback' +export { native } from './native' From 5e9223efbcac376f61445b29561936ff3e7802f4 Mon Sep 17 00:00:00 2001 From: egordidenko Date: Thu, 15 Feb 2024 12:59:05 +0300 Subject: [PATCH 3/8] fix: rename folder constants --- .../{constans => constants}/allowLayers.ts | 0 .../src/{constans => constants}/index.ts | 0 .../src/{constans => constants}/markers.ts | 0 .../src/{constans => constants}/sizes.ts | 0 packages/image-shrink/src/index.ts | 37 ------------------- .../src/utils/canvas/canvasTest.ts | 2 +- .../src/utils/canvas/testCanvasSize.ts | 2 +- .../src/utils/shouldSkipShrink.ts | 2 +- 8 files changed, 3 insertions(+), 40 deletions(-) rename packages/image-shrink/src/{constans => constants}/allowLayers.ts (100%) rename packages/image-shrink/src/{constans => constants}/index.ts (100%) rename packages/image-shrink/src/{constans => constants}/markers.ts (100%) rename packages/image-shrink/src/{constans => constants}/sizes.ts (100%) diff --git a/packages/image-shrink/src/constans/allowLayers.ts b/packages/image-shrink/src/constants/allowLayers.ts similarity index 100% rename from packages/image-shrink/src/constans/allowLayers.ts rename to packages/image-shrink/src/constants/allowLayers.ts diff --git a/packages/image-shrink/src/constans/index.ts b/packages/image-shrink/src/constants/index.ts similarity index 100% rename from packages/image-shrink/src/constans/index.ts rename to packages/image-shrink/src/constants/index.ts diff --git a/packages/image-shrink/src/constans/markers.ts b/packages/image-shrink/src/constants/markers.ts similarity index 100% rename from packages/image-shrink/src/constans/markers.ts rename to packages/image-shrink/src/constants/markers.ts diff --git a/packages/image-shrink/src/constans/sizes.ts b/packages/image-shrink/src/constants/sizes.ts similarity index 100% rename from packages/image-shrink/src/constans/sizes.ts rename to packages/image-shrink/src/constants/sizes.ts diff --git a/packages/image-shrink/src/index.ts b/packages/image-shrink/src/index.ts index 856a0b478..b206b750e 100644 --- a/packages/image-shrink/src/index.ts +++ b/packages/image-shrink/src/index.ts @@ -1,38 +1 @@ -// Path: packages/image-shrink/src/constans -export { allowLayers, markers, sizes } from './constans' - -// Path: packages/image-shrink/src/helper -export { memoize, memoKeySerializer } from './helper/memoize' - export { shrinkFile, type TSetting } from './utils/shrinkFile' - -export { shrinkImage, STEP } from './utils/shrinkImage' - -export { shouldSkipShrink } from './utils/shouldSkipShrink' - -export { - canvasResize, - canvasTest, - testCanvasSize, - createCanvas, - canvasToBlob, - hasTransparency -} from './utils/canvas' - -export { isIOS, isIpadOS } from './utils/devices/mobile' - -export { findExifOrientation } from './utils/exif/findExifOrientation' -export { getExif } from './utils/exif/getExif' -export { isBrowserApplyExif } from './utils/exif/isBrowserApplyExif' -export { replaceExif } from './utils/exif/replaceExif' - -export { getIccProfile } from './utils/IccProfile/getIccProfile' -export { replaceIccProfile } from './utils/IccProfile/replaceIccProfile' -export { stripIccProfile } from './utils/IccProfile/stripIccProfile' - -export { imageLoader } from './utils/image/imageLoader' - -export { replaceJpegChunk } from './utils/image/JPEG/replaceJpegChunk' -export { readJpegChunks } from './utils/image/JPEG/readJpegChunks' - -export { fallback, native } from './utils/render' diff --git a/packages/image-shrink/src/utils/canvas/canvasTest.ts b/packages/image-shrink/src/utils/canvas/canvasTest.ts index 5bd620d0a..970be046a 100644 --- a/packages/image-shrink/src/utils/canvas/canvasTest.ts +++ b/packages/image-shrink/src/utils/canvas/canvasTest.ts @@ -1,6 +1,6 @@ import { createCanvas } from './createCanvas' -// add constans +// add constants const TestPixel = { R: 55, G: 110, diff --git a/packages/image-shrink/src/utils/canvas/testCanvasSize.ts b/packages/image-shrink/src/utils/canvas/testCanvasSize.ts index 0f965eb72..4bd5aed88 100644 --- a/packages/image-shrink/src/utils/canvas/testCanvasSize.ts +++ b/packages/image-shrink/src/utils/canvas/testCanvasSize.ts @@ -1,4 +1,4 @@ -import { sizes } from '../../constans' +import { sizes } from '../../constants' import { memoize, memoKeySerializer } from '../../helper/memoize' import { canvasTest } from './canvasTest' diff --git a/packages/image-shrink/src/utils/shouldSkipShrink.ts b/packages/image-shrink/src/utils/shouldSkipShrink.ts index 8e525d889..99ac17a31 100644 --- a/packages/image-shrink/src/utils/shouldSkipShrink.ts +++ b/packages/image-shrink/src/utils/shouldSkipShrink.ts @@ -1,5 +1,5 @@ import { readJpegChunks } from './image/JPEG/readJpegChunks' -import { allowLayers, markers } from '../constans' +import { allowLayers, markers } from '../constants' export const shouldSkipShrink = async (file: File) => { let skip = false From b38283733c54becff7aa6e8b3752c6196506575e Mon Sep 17 00:00:00 2001 From: egordidenko Date: Thu, 15 Feb 2024 13:02:50 +0300 Subject: [PATCH 4/8] refactor: remove block in imageLoader --- .../src/utils/IccProfile/stripIccProfile.ts | 1 - packages/image-shrink/src/utils/canvas/canvasTest.ts | 2 +- packages/image-shrink/src/utils/image/imageLoader.ts | 10 +--------- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts b/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts index 0e8df3290..aff2f364e 100644 --- a/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts +++ b/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts @@ -5,7 +5,6 @@ export const stripIccProfile = (inputFile: File): Promise => { return new Promise((resolve, reject) => { replaceIccProfile(inputFile, []) .then((file: Blob) => { - console.log({ file }, typeof file) imageLoader(URL.createObjectURL(file)) .then((img) => { resolve(img) diff --git a/packages/image-shrink/src/utils/canvas/canvasTest.ts b/packages/image-shrink/src/utils/canvas/canvasTest.ts index 970be046a..6ccc9b84c 100644 --- a/packages/image-shrink/src/utils/canvas/canvasTest.ts +++ b/packages/image-shrink/src/utils/canvas/canvasTest.ts @@ -53,7 +53,7 @@ export const canvasTest = (width, height) => { return isTestPass } catch (e) { - new Error(`Failed to test for max canvas size of ${width}x${height}.`) + console.error(`Failed to test for max canvas size of ${width}x${height}.`) return false } } diff --git a/packages/image-shrink/src/utils/image/imageLoader.ts b/packages/image-shrink/src/utils/image/imageLoader.ts index 896e7a0d8..96da1f8be 100644 --- a/packages/image-shrink/src/utils/image/imageLoader.ts +++ b/packages/image-shrink/src/utils/image/imageLoader.ts @@ -1,5 +1,3 @@ -// @ts-nocheck - export const processImage = ( image: HTMLImageElement, src?: string @@ -22,12 +20,6 @@ export const processImage = ( }) } -export const imageLoader = ( - image: File | Blob | string -): Promise => { - if (image.src) { - return processImage(image) - } - +export const imageLoader = (image: string): Promise => { return processImage(new Image(), image) } From 9acde5e656cd76b26c63feefdc15fbbef128f791 Mon Sep 17 00:00:00 2001 From: egordidenko Date: Thu, 15 Feb 2024 14:14:26 +0300 Subject: [PATCH 5/8] refactor: improve and clean code --- .../src/utils/IccProfile/stripIccProfile.ts | 33 ++++++++----------- .../src/utils/canvas/canvasTest.ts | 2 +- .../src/utils/canvas/testCanvasSize.ts | 20 ++++++----- .../src/utils/exif/isBrowserApplyExif.ts | 2 +- .../src/utils/image/JPEG/readJpegChunks.ts | 2 +- 5 files changed, 27 insertions(+), 32 deletions(-) diff --git a/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts b/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts index aff2f364e..4dc19cdfa 100644 --- a/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts +++ b/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts @@ -1,24 +1,17 @@ import { replaceIccProfile } from './replaceIccProfile' import { imageLoader } from '../image/imageLoader' -export const stripIccProfile = (inputFile: File): Promise => { - return new Promise((resolve, reject) => { - replaceIccProfile(inputFile, []) - .then((file: Blob) => { - imageLoader(URL.createObjectURL(file)) - .then((img) => { - resolve(img) - return img - }) - .then((img) => { - URL.revokeObjectURL(img.src) - }) - .catch(() => { - reject('Failed to load image') - }) - }) - .catch(() => { - reject('Failed to strip ICC profile and not image') - }) - }) +export const stripIccProfile = async ( + inputFile: File +): Promise => { + try { + const file = await replaceIccProfile(inputFile, []) + const image = await imageLoader(URL.createObjectURL(file)) + + URL.revokeObjectURL(image.src) + + return image + } catch (e) { + throw new Error(`Failed to strip ICC profile and not image ${e}`) + } } diff --git a/packages/image-shrink/src/utils/canvas/canvasTest.ts b/packages/image-shrink/src/utils/canvas/canvasTest.ts index 6ccc9b84c..af845cef8 100644 --- a/packages/image-shrink/src/utils/canvas/canvasTest.ts +++ b/packages/image-shrink/src/utils/canvas/canvasTest.ts @@ -27,7 +27,7 @@ export const canvasTest = (width, height) => { if (testCtx) { testCtx.fillStyle = FILL_STYLE - testCtx.fillRect.apply(testCtx, fill) + testCtx.fillRect(...fill) // Render the test pixel in the bottom-right corner of the // test canvas in the top-left of the 1x1 crop canvas. This diff --git a/packages/image-shrink/src/utils/canvas/testCanvasSize.ts b/packages/image-shrink/src/utils/canvas/testCanvasSize.ts index 4bd5aed88..735a503c4 100644 --- a/packages/image-shrink/src/utils/canvas/testCanvasSize.ts +++ b/packages/image-shrink/src/utils/canvas/testCanvasSize.ts @@ -17,7 +17,7 @@ const squareTest = wrapAsync(memoize(canvasTest, memoKeySerializer)) const dimensionTest = wrapAsync(memoize(canvasTest, memoKeySerializer)) export const testCanvasSize = (w, h) => { - return new Promise(async (resolve, reject) => { + return new Promise((resolve, reject) => { const testSquareSide = sizes.squareSide.find((side) => side * side >= w * h) const testDimension = sizes.dimension.find((side) => side >= w && side >= h) @@ -26,13 +26,15 @@ export const testCanvasSize = (w, h) => { return } - const squareSupported = await squareTest(testSquareSide, testSquareSide) - const dimensionSupported = await dimensionTest(testDimension, 1) - - if (squareSupported && dimensionSupported) { - resolve(true) - } else { - reject() - } + Promise.all([ + squareTest(testSquareSide, testSquareSide), + dimensionTest(testDimension, 1) + ]).then(([squareSupported, dimensionSupported]) => { + if (squareSupported && dimensionSupported) { + resolve(true) + } else { + reject() + } + }) }) } diff --git a/packages/image-shrink/src/utils/exif/isBrowserApplyExif.ts b/packages/image-shrink/src/utils/exif/isBrowserApplyExif.ts index cd2fbc8e2..bb9a1c4cf 100644 --- a/packages/image-shrink/src/utils/exif/isBrowserApplyExif.ts +++ b/packages/image-shrink/src/utils/exif/isBrowserApplyExif.ts @@ -7,7 +7,7 @@ const base64ImageSrc = let isApplied export const isBrowserApplyExif = () => { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { if (isApplied !== undefined) { resolve(isApplied) } else { diff --git a/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts b/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts index 10a775d79..81cc5d4b1 100644 --- a/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts +++ b/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts @@ -6,7 +6,7 @@ type TChunk = { } export const readJpegChunks = () => { - let stack: TChunk[] = [] + const stack: TChunk[] = [] const promiseReadJpegChunks = (file) => new Promise((resolve, reject) => { let pos From 5888ef9ca4c84c91f6377c0e502769a960c66542 Mon Sep 17 00:00:00 2001 From: egordidenko Date: Thu, 15 Feb 2024 14:47:06 +0300 Subject: [PATCH 6/8] perf: replace let to const --- .../src/utils/IccProfile/replaceIccProfile.ts | 2 +- .../image-shrink/src/utils/IccProfile/stripIccProfile.ts | 2 +- .../image-shrink/src/utils/exif/findExifOrientation.ts | 4 ++-- .../image-shrink/src/utils/image/JPEG/readJpegChunks.ts | 8 +++----- .../image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts | 7 +++---- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts b/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts index 20a99c61f..81abc8794 100644 --- a/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts +++ b/packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts @@ -4,7 +4,7 @@ export const MARKER = 0xe2 export const replaceIccProfile = ( blob: Blob | File, iccProfiles: DataView[] -): Promise => { +) => { return replaceJpegChunk( blob, MARKER, diff --git a/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts b/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts index 4dc19cdfa..f8b281637 100644 --- a/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts +++ b/packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts @@ -6,7 +6,7 @@ export const stripIccProfile = async ( ): Promise => { try { const file = await replaceIccProfile(inputFile, []) - const image = await imageLoader(URL.createObjectURL(file)) + const image = await imageLoader(URL.createObjectURL(file as Blob)) URL.revokeObjectURL(image.src) diff --git a/packages/image-shrink/src/utils/exif/findExifOrientation.ts b/packages/image-shrink/src/utils/exif/findExifOrientation.ts index ea2eaedea..b6a19d9a9 100644 --- a/packages/image-shrink/src/utils/exif/findExifOrientation.ts +++ b/packages/image-shrink/src/utils/exif/findExifOrientation.ts @@ -1,5 +1,5 @@ export const findExifOrientation = (exif: DataView, exifCallback) => { - let count, j, little, offset, ref + let j, little, offset, ref if ( !exif || exif.byteLength < 14 || @@ -19,7 +19,7 @@ export const findExifOrientation = (exif: DataView, exifCallback) => { return null } offset = 8 + exif.getUint32(10, little) - count = exif.getUint16(offset - 2, little) + const count = exif.getUint16(offset - 2, little) for (j = 0, ref = count; ref >= 0 ? j < ref : j > ref; ref >= 0 ? ++j : --j) { if (exif.byteLength < offset + 10) { return null diff --git a/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts b/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts index 81cc5d4b1..00e5a94d3 100644 --- a/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts +++ b/packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts @@ -42,22 +42,20 @@ export const readJpegChunks = () => { }) const readNextChunk = () => { - let startPos = pos + const startPos = pos return readToView(file.slice(pos, (pos += 4)), (view) => { - let length, marker - if (view.byteLength !== 4 || view.getUint8(0) !== 0xff) { return reject('Corrupted') } - marker = view?.getUint8(1) + const marker = view?.getUint8(1) if (marker === 0xda) { return resolve(true) } - length = view.getUint16(2) - 2 + const length = view.getUint16(2) - 2 return readToView(file.slice(pos, (pos += length)), (view) => { if (view.byteLength !== length) { return reject('Corrupted') diff --git a/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts b/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts index f4a8c89c1..2781788b0 100644 --- a/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts +++ b/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts @@ -1,10 +1,9 @@ -// @ts-nocheck import { readJpegChunks } from './readJpegChunks' -export const replaceJpegChunk = (blob, marker, chunks): Promise => { +export const replaceJpegChunk = (blob, marker, chunks) => { return new Promise(async (resolve, reject) => { - const oldChunkPos = [] - const oldChunkLength = [] + const oldChunkPos: number[] = [] + const oldChunkLength: number[] = [] const { promiseReadJpegChunks, stack } = readJpegChunks() From a1eef087a40823ef24387abeaf0c5d26534333d8 Mon Sep 17 00:00:00 2001 From: egordidenko Date: Thu, 15 Feb 2024 14:47:53 +0300 Subject: [PATCH 7/8] refactor: Promise executor functions should not be async --- .../image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts b/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts index 2781788b0..b8329bf27 100644 --- a/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts +++ b/packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts @@ -1,13 +1,13 @@ import { readJpegChunks } from './readJpegChunks' export const replaceJpegChunk = (blob, marker, chunks) => { - return new Promise(async (resolve, reject) => { + return new Promise((resolve, reject) => { const oldChunkPos: number[] = [] const oldChunkLength: number[] = [] const { promiseReadJpegChunks, stack } = readJpegChunks() - return await promiseReadJpegChunks(blob) + return promiseReadJpegChunks(blob) .then(() => { stack.forEach((chunk) => { if (chunk.marker === marker) { From de4850d26ccc9793f7e996c824cab7c774774208 Mon Sep 17 00:00:00 2001 From: egordidenko Date: Thu, 15 Feb 2024 15:02:21 +0300 Subject: [PATCH 8/8] refactor: improve and clean code --- .../src/utils/canvas/hasTransparency.ts | 20 +++++++++---------- .../image-shrink/src/utils/render/fallback.ts | 7 +++---- packages/image-shrink/src/utils/shrinkFile.ts | 1 + 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/image-shrink/src/utils/canvas/hasTransparency.ts b/packages/image-shrink/src/utils/canvas/hasTransparency.ts index ad3826b6b..562f935d2 100644 --- a/packages/image-shrink/src/utils/canvas/hasTransparency.ts +++ b/packages/image-shrink/src/utils/canvas/hasTransparency.ts @@ -1,28 +1,28 @@ -import { createCanvas } from "./createCanvas"; +import { createCanvas } from './createCanvas' export const hasTransparency = (img) => { - const canvasSize = 50; + const canvasSize = 50 // Create a canvas element and get 2D rendering context - const { ctx, canvas } = createCanvas(); - canvas.width = canvas.height = canvasSize; + const { ctx, canvas } = createCanvas() + canvas.width = canvas.height = canvasSize // Draw the image onto the canvas - ctx.drawImage(img, 0, 0, canvasSize, canvasSize); + ctx.drawImage(img, 0, 0, canvasSize, canvasSize) // Get the image data - const imageData = ctx.getImageData(0, 0, canvasSize, canvasSize).data; + const imageData = ctx.getImageData(0, 0, canvasSize, canvasSize).data // Reset the canvas dimensions - canvas.width = canvas.height = 1; + canvas.width = canvas.height = 1 // Check for transparency in the alpha channel for (let i = 3; i < imageData.length; i += 4) { if (imageData[i] < 254) { - return true; + return true } } // No transparency found - return false; -}; + return false +} diff --git a/packages/image-shrink/src/utils/render/fallback.ts b/packages/image-shrink/src/utils/render/fallback.ts index 74c172751..ccd05af74 100644 --- a/packages/image-shrink/src/utils/render/fallback.ts +++ b/packages/image-shrink/src/utils/render/fallback.ts @@ -1,11 +1,10 @@ -// @ts-nocheck import { testCanvasSize } from '../canvas/testCanvasSize' import { canvasResize } from '../canvas/canvasResize' const calcShrinkSteps = function (sourceW, targetW, targetH, step) { - const steps = [] - let sW = targetW - let sH = targetH + const steps: Array<[number, number]> = [] + let sW: number = targetW + let sH: number = targetH // result should include at least one target step, // even if abs(source - target) < step * source diff --git a/packages/image-shrink/src/utils/shrinkFile.ts b/packages/image-shrink/src/utils/shrinkFile.ts index 803752e08..a097465e3 100644 --- a/packages/image-shrink/src/utils/shrinkFile.ts +++ b/packages/image-shrink/src/utils/shrinkFile.ts @@ -15,6 +15,7 @@ export type TSetting = { } export const shrinkFile = (file: File, settings: TSetting): Promise => { + /*eslint no-async-promise-executor: "off"*/ return new Promise(async (resolve, reject) => { if (!(URL && DataView && Blob)) { reject('Not support')