Skip to content

Commit

Permalink
feat(gatsby-transformer-sharp): add inside and outside fit opt… (#14852)
Browse files Browse the repository at this point in the history
Added `INSIDE` and `OUTSIDE` options for for gatsby-transformer-sharp. Here's a use case for inside [#13078 (comment)](#13078 (comment)).
This includes changing gatsby-image to use `contain` instead of `cover` for object-fit, which does not affect the existing fit options.
Updated documentation for gatsby-transformer-sharp to clarify the fit options and show that they are available for all three functions.

NOTE: This is an extension of PR #13393 by @VGoose , which was closed because he didn't have time to complete the work.


Co-authored-by: AnhVoMBP15 <[email protected]>
Co-authored-by: soundguy66 <[email protected]>
Co-authored-by: gatsbybot <[email protected]>
Co-authored-by: Ward Peeters <[email protected]>
  • Loading branch information
5 people authored Mar 10, 2020
1 parent 97fa23e commit 1aa2974
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 51 deletions.
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`,
`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

0 comments on commit 1aa2974

Please sign in to comment.