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(gatsby-transformer-sharp): add inside and outside fit options #14852

Merged
merged 27 commits into from
Mar 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d4ff46e
add inside and outside fit options
VGoose Apr 16, 2019
7fc1d8d
add fit and background options to resize and fixed
VGoose Apr 16, 2019
8db98f7
Modifying gatsby-image to use `contain` rather than `cover` for objec…
soundguy66 Jun 17, 2019
d588081
Updating gatsby-plugin-sharp documentation for clarity and to show th…
soundguy66 Jun 17, 2019
63947c3
Merge branch 'master' into topics/sharp-inside-outside - I believe I'…
soundguy66 Jun 18, 2019
bfe4171
Fixing some things that got blown away in the conflict merge.
soundguy66 Jun 18, 2019
c12bca3
Merge branch 'master' into topics/sharp-inside-outside
soundguy66 Jun 18, 2019
384d9e2
Undoing all of the merge errors from the last 2 merges
soundguy66 Jun 18, 2019
84efaba
Updating e2e test to match cover->contain change for gatsby-image.
soundguy66 Jun 18, 2019
2cd7ef3
Updating packages/gasby-image readme to reflect PR change. Updating d…
soundguy66 Jun 18, 2019
b0cbfb1
Merge branch 'master' into topics/sharp-inside-outside
soundguy66 Jun 21, 2019
25cec48
Fixing the snapshots for gatsby-image.
soundguy66 Jun 21, 2019
ab4b32e
Merge branch 'master' into topics/sharp-inside-outside to resolve con…
soundguy66 Jun 21, 2019
de9cea9
Merge remote-tracking branch 'upstream/master' into topics/sharp-insi…
Aug 14, 2019
2f587c9
Merge branch 'master' into topics/sharp-inside-outside
chris-hammond Aug 23, 2019
2f484f8
WIP
soundguy66 Oct 30, 2019
d1e3dfb
WIP
soundguy66 Oct 30, 2019
9137696
Cleanup.
soundguy66 Oct 30, 2019
4901fd1
More cleanup.
soundguy66 Oct 30, 2019
31f69c4
Merge branch 'master' into wip-inside-outside
soundguy66 Oct 30, 2019
7bebda8
Removing changes to gatsby-image.
soundguy66 Oct 30, 2019
db8d1ad
Cleaning up some weird auto-formatting.
soundguy66 Oct 30, 2019
75260f6
More missed merges and auto-formatted fixes.
soundguy66 Oct 30, 2019
c6ee761
Hopefully the last commit!
soundguy66 Oct 30, 2019
f3696d9
Merge branch 'master' into topics/sharp-inside-outside
soundguy66 Jan 27, 2020
479c416
cleanup calculations
wardpeet Mar 9, 2020
c8bede8
Merge branch 'master' into topics/sharp-inside-outside
wardpeet Mar 10, 2020
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
31 changes: 17 additions & 14 deletions docs/docs/gatsby-image.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ file(relativePath: { eq: "images/default.jpg" }) {
}
```

Read more in the [gatsby-plugin-sharp](/packages/gatsby-plugin-sharp/?=#fixed) README.
Read more about fixed image queries in the [gatsby-plugin-sharp](/packages/gatsby-plugin-sharp/#fixed) README.

### Images that stretch across a _fluid_ container

Expand Down Expand Up @@ -177,18 +177,17 @@ In a query, you can specify options for fluid images.
- `maxHeight`(int)
- `quality` (int, default: 50)
- `srcSetBreakpoints` (array of int, default: [])
- `fit` (string, default: `[sharp.fit.cover][6]`)
- `background` (string, default: `rgba(0,0,0,1)`)

#### Returns

- `base64` (string)
- `src` (string)
- `width` (int)
- `height` (int)
- `aspectRatio` (float)
- `src` (string)
- `srcSet` (string)
- `srcSetType` (string)
- `sizes` (string)
- `originalImg` (string)

This is where fragments like `GatsbyImageSharpFluid` come in handy, as they'll return all the above items in one line without having to type them all out:

Expand All @@ -204,7 +203,7 @@ file(relativePath: { eq: "images/default.jpg" }) {
}
```

Read more in the [gatsby-plugin-sharp](/packages/gatsby-plugin-sharp/?=#fluid) README.
Read more about fluid image queries in the [gatsby-plugin-sharp](/packages/gatsby-plugin-sharp/#fluid) README.

### Resized images

Expand Down Expand Up @@ -240,15 +239,19 @@ allImageSharp {
}
```

Read more about resized image queries in the [gatsby-plugin-sharp](/packages/gatsby-plugin-sharp/#resize) README.

### Shared query parameters

In addition to `gatsby-plugin-sharp` settings in `gatsby-config.js`, there are additional query options that apply to both _fluid_ and _fixed_ images:
In addition to `gatsby-plugin-sharp` settings in `gatsby-config.js`, there are additional query options that apply to _fluid_, _fixed_, and _resized_ images:

- `grayscale` (bool, default: false)
- `duotone` (bool|obj, default: false)
- `toFormat` (string, default: \`\`)
- `cropFocus` (string, default: `[sharp.strategy.attention][6]`)
- `pngCompressionSpeed` (int, default: 4)
- [`grayscale`](/packages/gatsby-plugin-sharp/#grayscale) (bool, default: false)
- [`duotone`](/packages/gatsby-plugin-sharp/#duotone) (bool|obj, default: false)
- [`toFormat`](/packages/gatsby-plugin-sharp/#toformat) (string, default: \`\`)
- [`cropFocus`](/packages/gatsby-plugin-sharp/#cropfocus) (string, default: `ATTENTION`)
- [`fit`](/packages/gatsby-plugin-sharp/#fit) (string, default: `COVER`)
- [`pngCompressionSpeed`](/packages/gatsby-plugin-sharp/#pngcompressionspeed) (int, default: 4)
- [`rotate`](/packages/gatsby-plugin-sharp/#rotate) (int, default: 0)

Here's an example of using the `duotone` option with a fixed image:

Expand Down Expand Up @@ -286,13 +289,13 @@ fixed(
<figcaption>Grayscale | Before - After</figcaption>
</figure>

Read more in the [`gatsby-plugin-sharp`](/packages/gatsby-plugin-sharp) README.
Read more about shared image query parameters in the [`gatsby-plugin-sharp`](/packages/gatsby-plugin-sharp/#shared-options) README.

## Image query fragments

GraphQL includes a concept called "query fragments", which are a part of a query that can be reused. To ease building with `gatsby-image`, Gatsby image processing plugins which support `gatsby-image` ship with fragments which you can easily include in your queries.

> Note: using fragments in your queries depends on which data source(s) you have configured. Read more in the [gatsby-image](/packages/gatsby-image#fragments) README.
> Note: using fragments in your queries depends on which data source(s) you have configured. Read more about image query fragments in the [gatsby-image](/packages/gatsby-image/#fragments) README.

### Common fragments with `gatsby-transformer-sharp`

Expand Down
35 changes: 27 additions & 8 deletions packages/gatsby-plugin-sharp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,10 @@ a base64 image to use as a placeholder) you need to implement the "blur up"
technique popularized by Medium and Facebook (and also available as a Gatsby
plugin for Markdown content as gatsby-remark-images).

When both a `maxWidth` and `maxHeight` are provided, sharp will use `COVER` as a fit strategy by default. This might not be ideal so you can now choose between `COVER`, `CONTAIN` and `FILL` as a fit strategy. To see them in action the [CSS property object-fit](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) comes close to its implementation.

#### Note

fit strategies `CONTAIN` and `FILL` will not work when `cropFocus` is assigned to [sharp.strategy][6]. The `cropFocus` option cannot be `ENTROPY` or `ATTENTION`
When both a `maxWidth` and `maxHeight` are provided, sharp will [resize the image][6] using
`COVER` as a fit strategy by default. You can choose between `COVER`, `CONTAIN`, `FILL`,
chris-hammond marked this conversation as resolved.
Show resolved Hide resolved
`INSIDE`, and `OUTSIDE` as a fit strategy. See the [fit parameter below](#fit)
for more details.

#### Parameters

Expand All @@ -116,7 +115,6 @@ fit strategies `CONTAIN` and `FILL` will not work when `cropFocus` is assigned t
- `pngQuality` (int)
- `webpQuality` (int)
- `srcSetBreakpoints` (array of int, default: [])
- `fit` (string, default: '[sharp.fit.cover][6]')
- `background` (string, default: 'rgba(0,0,0,1)')
- [deprecated] `sizeByPixelDensity` (bool, default: false)
- Pixel density is only used in vector images, which Gatsby’s implementation of Sharp doesn’t support. This option is currently a no-op and will be removed in the next major version of Gatsby.
Expand All @@ -139,8 +137,10 @@ following:
- `grayscale` (bool, default: false)
- `duotone` (bool|obj, default: false)
- `toFormat` (string, default: '')
- `cropFocus` (string, default: '[sharp.strategy.attention][6]')
- `cropFocus` (string, default: 'ATTENTION')
- `fit` (string, default: 'COVER')
- `pngCompressionSpeed` (int, default: 4)
- `rotate` (int, default: 0)

#### toFormat

Expand All @@ -151,7 +151,25 @@ Convert the source image to one of the following available options: `NO_CHANGE`,

Change the cropping focus. Available options: `CENTER`, `NORTH`, `NORTHEAST`,
`EAST`, `SOUTHEAST`, `SOUTH`, `SOUTHWEST`, `WEST`, `NORTHWEST`, `ENTROPY`,
`ATTENTION`. See Sharp's [crop][6].
`ATTENTION`. See Sharp's [resize][6].

#### fit

Select the fit strategy for sharp to use when resizing images. Available options
are: `COVER`, `CONTAIN`, `FILL`, `INSIDE`, `OUTSIDE`. See Sharp's [resize][6].

**Note:** The fit strategies `CONTAIN` and `FILL` will not work when `cropFocus` is
set to `ENTROPY` or `ATTENTION`.

The following image shows the effects of each fit option. You can see that the
`INSIDE` option results in one dimension being smaller than requested, while
the `OUTSIDE` option results in one dimension being larger than requested.
![Sharp transform fit options](./sharp-transform-fit-options.png)

#### pngCompressionSpeed

Change the speed/quality tradeoff for PNG compression from 1 (brute-force) to
10 (fastest). See pngquant's [options][19].

#### rotate

Expand Down Expand Up @@ -357,3 +375,4 @@ If updating these doesn't fix the issue, your project probably uses other plugin
[16]: https://github.com/mozilla/mozjpeg
[17]: https://www.sno.phy.queensu.ca/~phil/exiftool/
[18]: https://www.npmjs.com/package/color
[19]: https://pngquant.org/#options
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
123 changes: 94 additions & 29 deletions packages/gatsby-plugin-sharp/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,67 @@ exports.setBoundActionCreators = actions => {
boundActionCreators = actions
}

function calculateImageDimensionsAndAspectRatio(file, options) {
// Calculate the eventual width/height of the image.
const dimensions = getImageSize(file)
const imageAspectRatio = dimensions.width / dimensions.height

let width = options.width
let height = options.height

switch (options.fit) {
case sharp.fit.fill: {
width = options.width ? options.width : dimensions.width
height = options.height ? options.height : dimensions.height
break
}
case sharp.fit.inside: {
const widthOption = options.width
? options.width
: Number.MAX_SAFE_INTEGER
const heightOption = options.height
? options.height
: Number.MAX_SAFE_INTEGER

width = Math.min(widthOption, Math.round(heightOption * imageAspectRatio))
height = Math.min(
heightOption,
Math.round(widthOption / imageAspectRatio)
)
break
}
case sharp.fit.outside: {
const widthOption = options.width ? options.width : 0
const heightOption = options.height ? options.height : 0

width = Math.max(widthOption, Math.round(heightOption * imageAspectRatio))
height = Math.max(
heightOption,
Math.round(widthOption / imageAspectRatio)
)
break
}

default: {
if (options.width && !options.height) {
width = options.width
height = Math.round(options.width / imageAspectRatio)
}

if (options.height && !options.width) {
width = Math.round(options.height * imageAspectRatio)
height = options.height
}
}
}

return {
width,
height,
aspectRatio: width / height,
}
}

function prepareQueue({ file, args }) {
const { pathPrefix, ...options } = args
const argsDigestShort = createArgsDigest(options)
Expand All @@ -60,30 +121,10 @@ function prepareQueue({ file, args }) {
// make sure outputDir is created
fs.ensureDirSync(outputDir)

let width
let height
// Calculate the eventual width/height of the image.
const dimensions = getImageSize(file)
let aspectRatio = dimensions.width / dimensions.height

// If the width/height are both set, we're cropping so just return
// that.
if (options.width && options.height) {
width = options.width
height = options.height
// Recalculate the aspectRatio for the cropped photo
aspectRatio = width / height
} else if (options.width) {
// Use the aspect ratio of the image to calculate what will be the resulting
// height.
width = options.width
height = Math.round(options.width / aspectRatio)
} else {
// Use the aspect ratio of the image to calculate what will be the resulting
// width.
height = options.height
width = Math.round(options.height * aspectRatio)
}
const { width, height, aspectRatio } = calculateImageDimensionsAndAspectRatio(
file,
options
)

// encode the file name for URL
const encodedImgSrc = `/${encodeURIComponent(file.name)}.${options.toFormat}`
Expand Down Expand Up @@ -263,9 +304,21 @@ async function generateBase64({ file, args = {}, reporter }) {
args.toFormat = forceBase64Format
}

console.log({
src: file.absolutePath,
width: options.width,
height: options.height,
position: options.cropFocus,
fit: options.fit,
background: options.background,
})
pipeline
.resize(options.width, options.height, {
.resize({
width: options.width,
height: options.height,
position: options.cropFocus,
fit: options.fit,
background: options.background,
})
.png({
compressionLevel: options.pngCompressionLevel,
Expand Down Expand Up @@ -508,14 +561,19 @@ async function fluid({ file, args = {}, reporter, cache }) {
let base64Image
if (options.base64) {
const base64Width = options.base64Width || defaultBase64Width()
const base64Height = Math.max(1, Math.round((base64Width * height) / width))
const base64Height = Math.max(
1,
Math.round(base64Width / images[0].aspectRatio)
)
const base64Args = {
duotone: options.duotone,
grayscale: options.grayscale,
rotate: options.rotate,
trim: options.trim,
toFormat: options.toFormat,
toFormatBase64: options.toFormatBase64,
cropFocus: options.cropFocus,
fit: options.fit,
width: base64Width,
height: base64Height,
}
Expand Down Expand Up @@ -626,16 +684,23 @@ async function fixed({ file, args = {}, reporter, cache }) {

let base64Image
if (options.base64) {
const base64Width = options.base64Width || defaultBase64Width()
const base64Height = Math.max(
1,
Math.round(base64Width / images[0].aspectRatio)
)
const base64Args = {
// height is adjusted accordingly with respect to the aspect ratio
width: options.base64Width,
duotone: options.duotone,
grayscale: options.grayscale,
rotate: options.rotate,
trim: options.trim,
toFormat: options.toFormat,
toFormatBase64: options.toFormatBase64,
cropFocus: options.cropFocus,
fit: options.fit,
width: base64Width,
height: base64Height,
}

// Get base64 version
base64Image = await base64({
file,
Expand Down
2 changes: 2 additions & 0 deletions packages/gatsby-transformer-sharp/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const ImageFitType = new GraphQLEnumType({
COVER: { value: sharp.fit.cover },
CONTAIN: { value: sharp.fit.contain },
FILL: { value: sharp.fit.fill },
INSIDE: { value: sharp.fit.inside },
OUTSIDE: { value: sharp.fit.outside },
},
})

Expand Down