Skip to content

Commit

Permalink
feat: Add aspect ratio support to the resize directive (#71)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tony Sullivan authored and JonasKruckenberg committed Apr 25, 2021
1 parent c264920 commit 8e905da
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 13 deletions.
23 changes: 23 additions & 0 deletions docs/directives.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- [Quality](#quality)
- [Width](#width)
- [Height](#height)
- [Aspect](#aspect)
- [Rotate](#rotate)
- [Tint](#tint)

Expand Down Expand Up @@ -315,6 +316,28 @@ import Images from 'examepl.jpg?height=200;400;700'

___

### Aspect
**Keyword**: `aspect`<br>
**Type**: _string_<br>


Resizes the image to be the specified aspect ratio.
If height and width are both provided, this will be ignored.
If height is provided, the width will be scaled accordingly.
If width is provided, the width will be scaled accordingly.
If neither height nor width are provided, the image will be cropped to the given aspect ratio.


**Example**:
```js
import Image from 'example.jpg?aspect=16:9'
import Image from 'example.jpg?aspect=16:9&height=200'
import Image from 'example.jpg?aspect=16:9&width=200'
import Images from 'example.jpg?aspect=16:9&h=200;400;700'
```

___

### Rotate
**Keyword**: `rotate`<br>
**Type**: _integer^_<br>
Expand Down
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
102 changes: 102 additions & 0 deletions packages/core/src/transforms/__tests__/resize.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,108 @@ describe('width & height', () => {
//@ts-ignore
const { image, metadata } = await applyTransforms([resize({ w: '300', h: '300', kernel: 'cubic' }, dirCtx)], img)

expect(await image.toBuffer()).toMatchFile()
})
})
})

describe('aspect', () => {
let dirCtx: TransformFactoryContext
beforeAll(() => {
dirCtx = { useParam: jest.fn, warn: jest.fn }
})

test('keyword "aspect"', () => {
const res = resize({ aspect: '16:9' }, dirCtx)

expect(res).toBeInstanceOf(Function)
})

test('missing', () => {
const res = resize({}, dirCtx)

expect(res).toBeUndefined()
})

describe('arguments', () => {
test('invalid', () => {
const res = resize({ aspect: 'invalid' }, dirCtx)

expect(res).toBeUndefined()
})

test('empty', () => {
const res = resize({ aspect: '' }, dirCtx)

expect(res).toBeUndefined()
})

test('colon delimited aspect ratio', () => {
const res = resize({ height: '16:9' }, dirCtx)

expect(res).toBeInstanceOf(Function)
})
})

describe('transform', () => {
let img: Sharp
beforeEach(() => {
img = sharp(join(__dirname, '../../__tests__/__assets__/pexels-allec-gomes-5195763.jpg'))
})

test('basic', async () => {
//@ts-ignore
const { image, metadata } = await applyTransforms([resize({ aspect: '4:3' }, dirCtx)], img)

expect(await image.toBuffer()).toMatchFile()
})

test('w/ fit', async () => {
//@ts-ignore
const { image, metadata } = await applyTransforms([resize({ aspect: '4:3', fit: 'contain' }, dirCtx)], img)

expect(await image.toBuffer()).toMatchFile()
})

test('w/ fit & background', async () => {
//@ts-ignore
const { image, metadata } = await applyTransforms([resize({ aspect: '4:3', fit: 'contain', background: '0f0' }, dirCtx)], img)

expect(await image.toBuffer()).toMatchFile()
})

test('w/ fit and position', async () => {
//@ts-ignore
const { image, metadata } = await applyTransforms([resize({ aspect: '4:3', fit: 'cover', position: 'top' }, dirCtx)], img)

expect(await image.toBuffer()).toMatchFile()
})

test('w/ kernel', async () => {
//@ts-ignore
const { image, metadata } = await applyTransforms([resize({ aspect: '4:3', kernel: 'cubic' }, dirCtx)], img)

expect(await image.toBuffer()).toMatchFile()
})

test('w/ height', async () => {
//@ts-ignore
const { image, metadata } = await applyTransforms([resize({ aspect: '4:3', height: '75' }, dirCtx)], img)

expect(await image.toBuffer()).toMatchFile()
})

test('w/ width', async () => {
//@ts-ignore
const { image, metadata } = await applyTransforms([resize({ aspect: '4:3', width: '300' }, dirCtx)], img)

expect(await image.toBuffer()).toMatchFile()
})

test('w/ width & height', async () => {
//@ts-ignore
const { image, metadata } = await applyTransforms([resize({ aspect: '4:3', height: '300', width: '300' }, dirCtx)], img)

expect(await image.toBuffer()).toMatchFile()
})
})
Expand Down
65 changes: 52 additions & 13 deletions packages/core/src/transforms/resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,26 @@ export interface ResizeOptions {
w: string
height: string
h: string
aspect: string
}

function parseAspect(aspect?: string) {
if (!aspect || !aspect.split) {
return undefined;
}

const [width, height] = aspect.split(':')

if (!width || !height) {
return undefined
}

const widthInt = parseInt(width)
const heightInt = parseInt(height)

return widthInt && heightInt && widthInt > 0 && heightInt > 0
? widthInt / heightInt
: undefined
}

export const resize: TransformFactory<ResizeOptions> = (config, ctx) => {
Expand All @@ -24,28 +44,47 @@ export const resize: TransformFactory<ResizeOptions> = (config, ctx) => {
: config.h
? parseInt(config.h)
: undefined
const aspect = width && height
? width / height
: parseAspect(config.aspect)

if (!width && !height) return
if (!width && !height && !aspect) return

return function resizeTransform(image) {
let finalWidth = width
let finalHeight = height

const w = getMetadata(image, 'width')
const h = getMetadata(image, 'height')

if (!height) {
const w = getMetadata(image, 'width')
const h = getMetadata(image, 'height')
setMetadata(image, 'height', Math.round((width! / w) * h))
setMetadata(image, 'width', width)
const metaAspect = w / h

if (width && height) {
/* both dimensions were provided, aspect is ignored and no calculations are needed */
}
else if (!height && !width) {
/* only aspect was given, need to calculate which dimension to crop */
const useWidth = aspect! > metaAspect

if (!width) {
const w = getMetadata(image, 'width')
const h = getMetadata(image, 'height')
setMetadata(image, 'width', Math.round((height! / h) * w))
setMetadata(image, 'height', height)
finalHeight = useWidth ? Math.round(w / aspect!) : h
finalWidth = useWidth ? w : Math.round(h / aspect!)
} else if (!height) {
/* only width was provided, need to calculate height */
finalHeight = width! / (aspect || metaAspect)
finalWidth = width
} else {
/* only height was provided, need to calculate width */
finalHeight = height
finalWidth = height * (aspect || metaAspect)
}

setMetadata(image, 'height', finalHeight)
setMetadata(image, 'width', finalWidth)
setMetadata(image, 'aspect', aspect || metaAspect)

return image.resize({
width: width,
height: height,
width: finalWidth,
height: finalHeight,
fit: getFit(config, image),
position: getPosition(config, image),
kernel: getKernel(config, image),
Expand Down

0 comments on commit 8e905da

Please sign in to comment.