Skip to content

Commit 1aa2974

Browse files
chris-hammondVGoosesoundguy66gatsbybotwardpeet
authored
feat(gatsby-transformer-sharp): add inside and outside fit opt… (#14852)
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]>
1 parent 97fa23e commit 1aa2974

File tree

5 files changed

+140
-51
lines changed

5 files changed

+140
-51
lines changed

docs/docs/gatsby-image.md

+17-14
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ file(relativePath: { eq: "images/default.jpg" }) {
129129
}
130130
```
131131

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

134134
### Images that stretch across a _fluid_ container
135135

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

183182
#### Returns
184183

185184
- `base64` (string)
186-
- `src` (string)
187-
- `width` (int)
188-
- `height` (int)
189185
- `aspectRatio` (float)
190186
- `src` (string)
191187
- `srcSet` (string)
188+
- `srcSetType` (string)
189+
- `sizes` (string)
190+
- `originalImg` (string)
192191

193192
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:
194193

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

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

209208
### Resized images
210209

@@ -240,15 +239,19 @@ allImageSharp {
240239
}
241240
```
242241

242+
Read more about resized image queries in the [gatsby-plugin-sharp](/packages/gatsby-plugin-sharp/#resize) README.
243+
243244
### Shared query parameters
244245

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

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

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

@@ -286,13 +289,13 @@ fixed(
286289
<figcaption>Grayscale | Before - After</figcaption>
287290
</figure>
288291

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

291294
## Image query fragments
292295

293296
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.
294297

295-
> 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.
298+
> 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.
296299
297300
### Common fragments with `gatsby-transformer-sharp`
298301

packages/gatsby-plugin-sharp/README.md

+27-8
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,10 @@ a base64 image to use as a placeholder) you need to implement the "blur up"
101101
technique popularized by Medium and Facebook (and also available as a Gatsby
102102
plugin for Markdown content as gatsby-remark-images).
103103

104-
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.
105-
106-
#### Note
107-
108-
fit strategies `CONTAIN` and `FILL` will not work when `cropFocus` is assigned to [sharp.strategy][6]. The `cropFocus` option cannot be `ENTROPY` or `ATTENTION`
104+
When both a `maxWidth` and `maxHeight` are provided, sharp will [resize the image][6] using
105+
`COVER` as a fit strategy by default. You can choose between `COVER`, `CONTAIN`, `FILL`,
106+
`INSIDE`, and `OUTSIDE` as a fit strategy. See the [fit parameter below](#fit)
107+
for more details.
109108

110109
#### Parameters
111110

@@ -116,7 +115,6 @@ fit strategies `CONTAIN` and `FILL` will not work when `cropFocus` is assigned t
116115
- `pngQuality` (int)
117116
- `webpQuality` (int)
118117
- `srcSetBreakpoints` (array of int, default: [])
119-
- `fit` (string, default: '[sharp.fit.cover][6]')
120118
- `background` (string, default: 'rgba(0,0,0,1)')
121119
- [deprecated] `sizeByPixelDensity` (bool, default: false)
122120
- 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.
@@ -139,8 +137,10 @@ following:
139137
- `grayscale` (bool, default: false)
140138
- `duotone` (bool|obj, default: false)
141139
- `toFormat` (string, default: '')
142-
- `cropFocus` (string, default: '[sharp.strategy.attention][6]')
140+
- `cropFocus` (string, default: 'ATTENTION')
141+
- `fit` (string, default: 'COVER')
143142
- `pngCompressionSpeed` (int, default: 4)
143+
- `rotate` (int, default: 0)
144144

145145
#### toFormat
146146

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

152152
Change the cropping focus. Available options: `CENTER`, `NORTH`, `NORTHEAST`,
153153
`EAST`, `SOUTHEAST`, `SOUTH`, `SOUTHWEST`, `WEST`, `NORTHWEST`, `ENTROPY`,
154-
`ATTENTION`. See Sharp's [crop][6].
154+
`ATTENTION`. See Sharp's [resize][6].
155+
156+
#### fit
157+
158+
Select the fit strategy for sharp to use when resizing images. Available options
159+
are: `COVER`, `CONTAIN`, `FILL`, `INSIDE`, `OUTSIDE`. See Sharp's [resize][6].
160+
161+
**Note:** The fit strategies `CONTAIN` and `FILL` will not work when `cropFocus` is
162+
set to `ENTROPY` or `ATTENTION`.
163+
164+
The following image shows the effects of each fit option. You can see that the
165+
`INSIDE` option results in one dimension being smaller than requested, while
166+
the `OUTSIDE` option results in one dimension being larger than requested.
167+
![Sharp transform fit options](./sharp-transform-fit-options.png)
168+
169+
#### pngCompressionSpeed
170+
171+
Change the speed/quality tradeoff for PNG compression from 1 (brute-force) to
172+
10 (fastest). See pngquant's [options][19].
155173

156174
#### rotate
157175

@@ -357,3 +375,4 @@ If updating these doesn't fix the issue, your project probably uses other plugin
357375
[16]: https://github.com/mozilla/mozjpeg
358376
[17]: https://www.sno.phy.queensu.ca/~phil/exiftool/
359377
[18]: https://www.npmjs.com/package/color
378+
[19]: https://pngquant.org/#options
Loading

packages/gatsby-plugin-sharp/src/index.js

+94-29
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,67 @@ exports.setBoundActionCreators = actions => {
4545
boundActionCreators = actions
4646
}
4747

48+
function calculateImageDimensionsAndAspectRatio(file, options) {
49+
// Calculate the eventual width/height of the image.
50+
const dimensions = getImageSize(file)
51+
const imageAspectRatio = dimensions.width / dimensions.height
52+
53+
let width = options.width
54+
let height = options.height
55+
56+
switch (options.fit) {
57+
case sharp.fit.fill: {
58+
width = options.width ? options.width : dimensions.width
59+
height = options.height ? options.height : dimensions.height
60+
break
61+
}
62+
case sharp.fit.inside: {
63+
const widthOption = options.width
64+
? options.width
65+
: Number.MAX_SAFE_INTEGER
66+
const heightOption = options.height
67+
? options.height
68+
: Number.MAX_SAFE_INTEGER
69+
70+
width = Math.min(widthOption, Math.round(heightOption * imageAspectRatio))
71+
height = Math.min(
72+
heightOption,
73+
Math.round(widthOption / imageAspectRatio)
74+
)
75+
break
76+
}
77+
case sharp.fit.outside: {
78+
const widthOption = options.width ? options.width : 0
79+
const heightOption = options.height ? options.height : 0
80+
81+
width = Math.max(widthOption, Math.round(heightOption * imageAspectRatio))
82+
height = Math.max(
83+
heightOption,
84+
Math.round(widthOption / imageAspectRatio)
85+
)
86+
break
87+
}
88+
89+
default: {
90+
if (options.width && !options.height) {
91+
width = options.width
92+
height = Math.round(options.width / imageAspectRatio)
93+
}
94+
95+
if (options.height && !options.width) {
96+
width = Math.round(options.height * imageAspectRatio)
97+
height = options.height
98+
}
99+
}
100+
}
101+
102+
return {
103+
width,
104+
height,
105+
aspectRatio: width / height,
106+
}
107+
}
108+
48109
function prepareQueue({ file, args }) {
49110
const { pathPrefix, ...options } = args
50111
const argsDigestShort = createArgsDigest(options)
@@ -60,30 +121,10 @@ function prepareQueue({ file, args }) {
60121
// make sure outputDir is created
61122
fs.ensureDirSync(outputDir)
62123

63-
let width
64-
let height
65-
// Calculate the eventual width/height of the image.
66-
const dimensions = getImageSize(file)
67-
let aspectRatio = dimensions.width / dimensions.height
68-
69-
// If the width/height are both set, we're cropping so just return
70-
// that.
71-
if (options.width && options.height) {
72-
width = options.width
73-
height = options.height
74-
// Recalculate the aspectRatio for the cropped photo
75-
aspectRatio = width / height
76-
} else if (options.width) {
77-
// Use the aspect ratio of the image to calculate what will be the resulting
78-
// height.
79-
width = options.width
80-
height = Math.round(options.width / aspectRatio)
81-
} else {
82-
// Use the aspect ratio of the image to calculate what will be the resulting
83-
// width.
84-
height = options.height
85-
width = Math.round(options.height * aspectRatio)
86-
}
124+
const { width, height, aspectRatio } = calculateImageDimensionsAndAspectRatio(
125+
file,
126+
options
127+
)
87128

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

307+
console.log({
308+
src: file.absolutePath,
309+
width: options.width,
310+
height: options.height,
311+
position: options.cropFocus,
312+
fit: options.fit,
313+
background: options.background,
314+
})
266315
pipeline
267-
.resize(options.width, options.height, {
316+
.resize({
317+
width: options.width,
318+
height: options.height,
268319
position: options.cropFocus,
320+
fit: options.fit,
321+
background: options.background,
269322
})
270323
.png({
271324
compressionLevel: options.pngCompressionLevel,
@@ -508,14 +561,19 @@ async function fluid({ file, args = {}, reporter, cache }) {
508561
let base64Image
509562
if (options.base64) {
510563
const base64Width = options.base64Width || defaultBase64Width()
511-
const base64Height = Math.max(1, Math.round((base64Width * height) / width))
564+
const base64Height = Math.max(
565+
1,
566+
Math.round(base64Width / images[0].aspectRatio)
567+
)
512568
const base64Args = {
513569
duotone: options.duotone,
514570
grayscale: options.grayscale,
515571
rotate: options.rotate,
516572
trim: options.trim,
517573
toFormat: options.toFormat,
518574
toFormatBase64: options.toFormatBase64,
575+
cropFocus: options.cropFocus,
576+
fit: options.fit,
519577
width: base64Width,
520578
height: base64Height,
521579
}
@@ -626,16 +684,23 @@ async function fixed({ file, args = {}, reporter, cache }) {
626684

627685
let base64Image
628686
if (options.base64) {
687+
const base64Width = options.base64Width || defaultBase64Width()
688+
const base64Height = Math.max(
689+
1,
690+
Math.round(base64Width / images[0].aspectRatio)
691+
)
629692
const base64Args = {
630-
// height is adjusted accordingly with respect to the aspect ratio
631-
width: options.base64Width,
632693
duotone: options.duotone,
633694
grayscale: options.grayscale,
634695
rotate: options.rotate,
696+
trim: options.trim,
635697
toFormat: options.toFormat,
636698
toFormatBase64: options.toFormatBase64,
699+
cropFocus: options.cropFocus,
700+
fit: options.fit,
701+
width: base64Width,
702+
height: base64Height,
637703
}
638-
639704
// Get base64 version
640705
base64Image = await base64({
641706
file,

packages/gatsby-transformer-sharp/src/types.js

+2
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ const ImageFitType = new GraphQLEnumType({
2626
COVER: { value: sharp.fit.cover },
2727
CONTAIN: { value: sharp.fit.contain },
2828
FILL: { value: sharp.fit.fill },
29+
INSIDE: { value: sharp.fit.inside },
30+
OUTSIDE: { value: sharp.fit.outside },
2931
},
3032
})
3133

0 commit comments

Comments
 (0)