Skip to content

Commit

Permalink
Merge pull request #520 from uploadcare/chore/image-shrink-tests
Browse files Browse the repository at this point in the history
chore: testing setup for image shrink
  • Loading branch information
nd0ut authored Feb 26, 2024
2 parents ac6d684 + e4395ac commit 2015a79
Show file tree
Hide file tree
Showing 24 changed files with 2,935 additions and 11,727 deletions.
14,175 changes: 2,553 additions & 11,622 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions packages/image-shrink/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
LICENSE
src/test/snapshots
coverage
5 changes: 0 additions & 5 deletions packages/image-shrink/jest.config.js

This file was deleted.

15 changes: 11 additions & 4 deletions packages/image-shrink/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
"scripts": {
"prepack": "cp ../../LICENSE ./LICENSE",
"clean": "rimraf dist",
"test": "node --experimental-vm-modules ../../node_modules/jest/bin/jest.js",
"test": "npx playwright install-deps chromium && vitest --run --coverage",
"test:watch": "npx playwright install-deps chromium && vitest",
"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",
Expand All @@ -43,7 +44,13 @@
"signature"
],
"devDependencies": {
"ts-node": "^10.8.1"
},
"dependencies": {}
"@imagemagick/magick-wasm": "^0.0.28",
"@types/content-type": "^1.1.8",
"@vitest/browser": "^1.2.2",
"@vitest/coverage-istanbul": "^1.3.0",
"playwright": "^1.41.2",
"raw-body": "^2.5.2",
"ts-node": "^10.8.1",
"vitest": "^1.2.2"
}
}
12 changes: 12 additions & 0 deletions packages/image-shrink/src/test/helpers/getImageAttributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { readMagickImage } from './readMagickImage'

export const getImageAttributes = async (inputBlob: Blob) => {
return readMagickImage(inputBlob, (image) => {
return image.attributeNames.reduce((acc, name) => {
return {
...acc,
[name]: image.getAttribute(name)
}
}, {})
})
}
10 changes: 10 additions & 0 deletions packages/image-shrink/src/test/helpers/loadImageAsBlob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export const loadImageAsBlob = async (
moduleResolver: () => Promise<{ default: string }>
) => {
const imageUrl = await moduleResolver().then((module) => module.default)
const response = await fetch(imageUrl)
const buffer = await response.arrayBuffer()
return new Blob([buffer], {
type: response.headers.get('content-type') ?? 'application/octet-stream'
})
}
17 changes: 17 additions & 0 deletions packages/image-shrink/src/test/helpers/loadImageMagick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/// <reference types="vite/client" />
import {
ImageMagick,
Magick,
MagickFormat,
Quantum,
initializeImageMagick
} from '@imagemagick/magick-wasm'
// eslint-disable-next-line import/no-unresolved
import wasmUrl from '@imagemagick/magick-wasm/magick.wasm?url'

export const loadImageMagick = async () => {
const wasmBytes = await fetch(wasmUrl).then((res) => res.arrayBuffer())
await initializeImageMagick(wasmBytes)

return { Magick, MagickFormat, Quantum, ImageMagick }
}
15 changes: 15 additions & 0 deletions packages/image-shrink/src/test/helpers/readMagickImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { loadImageMagick } from './loadImageMagick'
import { type IMagickImage } from '@imagemagick/magick-wasm'

export const readMagickImage = async <T>(
inputBlob: Blob,
func: (image: IMagickImage) => T
): Promise<T> => {
const { ImageMagick } = await loadImageMagick()
const blobArray = new Uint8Array(await inputBlob.arrayBuffer())
return new Promise<T>((resolve) => {
ImageMagick.read(blobArray, (image) => {
resolve(func(image))
})
})
}
13 changes: 13 additions & 0 deletions packages/image-shrink/src/test/helpers/uploadImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { TaskContext } from 'vitest'

export const uploadImage = (blob: Blob, variant: string, ctx?: TaskContext) => {
const ext = blob.type.replace('image/', '')
let filename = `${variant}.${ext}`
if (ctx) {
filename = `${ctx.task.suite.name}__${ctx.task.name}__${variant}.${ext}`
}
return fetch(`/upload-image?filename=${filename}`, {
method: 'POST',
body: blob
})
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/image-shrink/src/test/samples/line.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 16 additions & 17 deletions packages/image-shrink/src/utils/IccProfile/getIccProfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@ export const getIccProfile = async (blob: Blob) => {
const iccProfile: DataView[] = []
const { promiseReadJpegChunks, stack } = readJpegChunks()

return await promiseReadJpegChunks(blob)
.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)
await promiseReadJpegChunks(blob)

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
}
36 changes: 18 additions & 18 deletions packages/image-shrink/src/utils/exif/getExif.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,23 @@ export const getExif = async (blob: Blob) => {
let exif: DataView | null = null

const { promiseReadJpegChunks, stack } = readJpegChunks()
return promiseReadJpegChunks(blob)
.then(() => {
stack.forEach(({ marker, view }) => {
if (!exif && marker === 0xe1) {
if (view.byteLength >= 14) {
if (
// check for "Exif\0"
view.getUint32(0) === 0x45786966 &&
view.getUint16(4) === 0
) {
exif = view
return
}
}

await promiseReadJpegChunks(blob)

stack.forEach(({ marker, view }) => {
if (!exif && marker === 0xe1) {
if (view.byteLength >= 14) {
if (
// check for "Exif\0"
view.getUint32(0) === 0x45786966 &&
view.getUint16(4) === 0
) {
exif = view
return
}
})
return exif
})
.catch(() => exif)
}
}
})

return exif
}
4 changes: 2 additions & 2 deletions packages/image-shrink/src/utils/image/JPEG/readJpegChunks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ type TChunk = {
view: DataView
}

// TODO: unwrap promises
export const readJpegChunks = () => {
const stack: TChunk[] = []
const promiseReadJpegChunks = (blob: Blob) =>
Expand Down Expand Up @@ -37,7 +38,6 @@ export const readJpegChunks = () => {
break
}
}

readNextChunk()
})

Expand All @@ -50,7 +50,7 @@ export const readJpegChunks = () => {
return
}

const marker = view?.getUint8(1)
const marker = view.getUint8(1)

if (marker === 0xda) {
resolve(true)
Expand Down
75 changes: 35 additions & 40 deletions packages/image-shrink/src/utils/image/JPEG/replaceJpegChunk.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,47 @@
import { readJpegChunks } from './readJpegChunks'

export const replaceJpegChunk = (
export const replaceJpegChunk = async (
blob: Blob,
marker: number,
chunks: ArrayBuffer[]
) => {
return new Promise<Blob>((resolve, reject) => {
{
const oldChunkPos: number[] = []
const oldChunkLength: number[] = []

const { promiseReadJpegChunks, stack } = readJpegChunks()

return promiseReadJpegChunks(blob)
.then(() => {
stack.forEach((chunk) => {
if (chunk.marker === marker) {
oldChunkPos.push(chunk.startPos)
return oldChunkLength.push(chunk.length)
}
})
})
.then(() => {
const newChunks: (ArrayBuffer | Blob)[] = [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))

resolve(
new Blob(newChunks, {
type: blob.type
})
)
})
.catch(() => reject(blob))
}).catch(() => blob)
await promiseReadJpegChunks(blob)

stack.forEach((chunk) => {
if (chunk.marker === marker) {
oldChunkPos.push(chunk.startPos)
return oldChunkLength.push(chunk.length)
}
})

const newChunks: (ArrayBuffer | Blob)[] = [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))

return new Blob(newChunks, {
type: blob.type
})
}
}
2 changes: 1 addition & 1 deletion packages/image-shrink/src/utils/image/imageLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const processImage = (
resolve(image)
})
image.addEventListener('error', () => {
reject(image)
reject(new Error('Failed to load image. Probably not an image.'))
})
}
})
Expand Down
Loading

0 comments on commit 2015a79

Please sign in to comment.