Repository: gatsbyjs/gatsby
File: packages/gatsby-plugin-sharp/src/index.js (JavaScript)

Code before:
  filteredSizes = _.sortBy(filteredSizes)

  // Queue sizes for processing.
  const dimensionAttr = fixedDimension === `maxWidth` ? `width` : `height`
  const otherDimensionAttr = fixedDimension === `maxWidth` ? `height` : `width`

  const imageWithDensityOneIndex = filteredSizes.findIndex(
    size => size === (fixedDimension === `maxWidth` ? maxWidth : maxHeight)
  )

  // Sort sizes for prettiness.
  const transforms = filteredSizes.map(size => {
    const arrrgs = createTransformObject(options)
    if (arrrgs[otherDimensionAttr]) {
      arrrgs[otherDimensionAttr] = undefined
    }
    arrrgs[dimensionAttr] = Math.round(size)

    // we need pathPrefix to calculate the correct outputPath
    if (options.pathPrefix) {
      arrrgs.pathPrefix = options.pathPrefix
    }

    if (options.maxWidth !== undefined && options.maxHeight !== undefined) {
      arrrgs.height = Math.round(size * (maxHeight / maxWidth))
    }

    return arrrgs
  })

  const images = batchQueueImageResizing({
    file,
    transforms,
    reporter,
  })

  let base64Image
  if (options.base64) {
    const base64Width = options.base64Width || defaultBase64Width()
    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,

Review comment (bug):
Issue description wasn't clear for me. `options` props are replaced by the earlier defined `maxHeight` and `maxWidth` variables that take a `Math.min()` into account between the image width/height and the graphql maxHeight/minHeight parameters:

```js
  const maxWidth = options.maxWidth
    ? Math.min(options.maxWidth, width)
    : undefined
  const maxHeight = options.maxHeight
    ? Math.min(options.maxHeight, height)
    : undefined
```

---

## Problem

Input image of 720w x 1080h(portrait) is processed to a 270w x 405h image despite not exceeding either of the queries max width/height values. When both max limits are provided, they're used to define the output image height which is incorrect and leads to the 270x405 image output, instead of 720x1080.

## Comparison of change

Fluid graphql query params" `fluid(maxWidth:3840, maxHeight:2160, fit:INSIDE)`. Resulting in the following values:

```js
arrrgs.height = Math.round(size * (options.maxHeight / options.maxWidth))
```

- Good(PR): `maxWidth`: 720, `maxHeight`: 1080, h/w == `1.5`, `720 * 1.5` == 1080
- Bad: `options.maxWidth`: 3840, `options.maxHeight`: 2160, h/w == `0.5625`, `720 * 0.5625` = 405

Next method in the flow is `calculateImageDimensionsAndAspectRatio()`, with the query param `fit:INSIDE`:

```js
const dimensions = getImageSize(file)
const imageAspectRatio = dimensions.width / dimensions.height

// .. case for `fit:INSIDE`
width = Math.min(widthOption, Math.round(heightOption * imageAspectRatio))
height = Math.min(
  heightOption,
  Math.round(widthOption / imageAspectRatio)
)
// .. end of method
return {
  width,
  height,
  aspectRatio: width / height,
}
```

- `imageAspectRatio`: `720 / 1080` == 0.6666666666666667 (AR)

Before PR (Bad):

- `width`: min of 720 vs 270(405 * AR) == 270
- `height` min of 405 vs 1080(720 / AR) == 405
- `aspectRatio` == 0.6666666666666667

With PR (Good):

- `width`: min of 720 vs 720(1080 * AR) == 720
- `height` min of 1080 vs 1080(720 / AR) == 1080
- `aspectRatio` == 0.6666666666666667


## Requests

~~This change is only affecting `fluid`, there is a corresponding `fixed` line that has the same logic. You should mirror this change there too.~~ **EDIT:** Nevermind, I'm an idiot :sweat_smile: 

https://github.com/gatsbyjs/gatsby/blob/17a39881d31d2314f5c7f844b9e9e8e98f5e7394/packages/gatsby-plugin-sharp/src/index.js#L677



Code after:
  filteredSizes.push(fixedDimension === `maxWidth` ? width : height)
  filteredSizes = _.sortBy(filteredSizes)

  // Queue sizes for processing.
  const dimensionAttr = fixedDimension === `maxWidth` ? `width` : `height`
  const otherDimensionAttr = fixedDimension === `maxWidth` ? `height` : `width`

  const imageWithDensityOneIndex = filteredSizes.findIndex(
    size => size === (fixedDimension === `maxWidth` ? maxWidth : maxHeight)
  )

  // Sort sizes for prettiness.
  const transforms = filteredSizes.map(size => {
    const arrrgs = createTransformObject(options)
    if (arrrgs[otherDimensionAttr]) {
      arrrgs[otherDimensionAttr] = undefined
    }
    arrrgs[dimensionAttr] = Math.round(size)

    // we need pathPrefix to calculate the correct outputPath
    if (options.pathPrefix) {
      arrrgs.pathPrefix = options.pathPrefix
    }

    if (options.maxWidth !== undefined && options.maxHeight !== undefined) {
      if (options.fit === sharp.fit.inside) {
        arrrgs.height = Math.round(size * (maxHeight / maxWidth))
      } else {
        arrrgs.height = Math.round(
          size * (options.maxHeight / options.maxWidth)
        )
      }
    }

    return arrrgs
  })

  const images = batchQueueImageResizing({
    file,
    transforms,
    reporter,
  })

  let base64Image
  if (options.base64) {
    const base64Width = options.base64Width || defaultBase64Width()
    const base64Height = Math.max(
      1,
      Math.round(base64Width / images[0].aspectRatio)
    )
    const base64Args = {