Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: implemented stable image shrink #513

Merged
merged 8 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 7 additions & 46 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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",
Expand Down
1 change: 1 addition & 0 deletions packages/image-shrink/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
LICENSE
Empty file added packages/image-shrink/README.md
Empty file.
5 changes: 5 additions & 0 deletions packages/image-shrink/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import baseConfig from '../../jest.config.js'

export default {
...baseConfig
}
49 changes: 49 additions & 0 deletions packages/image-shrink/package.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
12 changes: 12 additions & 0 deletions packages/image-shrink/rollup.config.js
Original file line number Diff line number Diff line change
@@ -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 })]
3 changes: 3 additions & 0 deletions packages/image-shrink/src/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"env": {"es6": true, "node": true, "browser": true}
}
4 changes: 4 additions & 0 deletions packages/image-shrink/src/constants/allowLayers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const allowLayers = [
1, // L (black-white)
3 // RGB
]
3 changes: 3 additions & 0 deletions packages/image-shrink/src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { allowLayers } from './allowLayers'
export { markers } from './markers'
export { sizes } from './sizes'
15 changes: 15 additions & 0 deletions packages/image-shrink/src/constants/markers.ts
Original file line number Diff line number Diff line change
@@ -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)
]
45 changes: 45 additions & 0 deletions packages/image-shrink/src/constants/sizes.ts
Original file line number Diff line number Diff line change
@@ -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
]
}
38 changes: 38 additions & 0 deletions packages/image-shrink/src/helper/memoize.ts
Original file line number Diff line number Diff line change
@@ -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
}
1 change: 1 addition & 0 deletions packages/image-shrink/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { shrinkFile, type TSetting } from './utils/shrinkFile'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only thing we should export is this one.

24 changes: 24 additions & 0 deletions packages/image-shrink/src/utils/IccProfile/getIccProfile.ts
Original file line number Diff line number Diff line change
@@ -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)
}
13 changes: 13 additions & 0 deletions packages/image-shrink/src/utils/IccProfile/replaceIccProfile.ts
Original file line number Diff line number Diff line change
@@ -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)
)
}
17 changes: 17 additions & 0 deletions packages/image-shrink/src/utils/IccProfile/stripIccProfile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { replaceIccProfile } from './replaceIccProfile'
import { imageLoader } from '../image/imageLoader'

export const stripIccProfile = async (
inputFile: File
): Promise<HTMLImageElement> => {
try {
const file = await replaceIccProfile(inputFile, [])
const image = await imageLoader(URL.createObjectURL(file as Blob))

URL.revokeObjectURL(image.src)

return image
} catch (e) {
throw new Error(`Failed to strip ICC profile and not image ${e}`)
}
}
Loading
Loading