gatsby: BUG: MozJPEG with PNG and `toFormat: JPG` outputs PNG with jpg extension

Description

When enabling mozjpeg in my gatsby-config.js, jpg images do see better compression, but any png source images if using toFormat: JPG in their graphql query, will not actually format the image into jpeg, but instead output a png image data with a jpg file extension.

Steps to reproduce

Enable mozjpeg in gatsby-config.js for gatsby-plugin-sharp:

//   plugins: [
    {
      resolve: `gatsby-plugin-sharp`,
      options: {
        useMozJpeg: true,
      },
    },

Use the default starter project with gatsby new and edit the image.js component to include toFormat: JPG:

const Image = () => (
  <StaticQuery
    query={graphql`
      query {
        placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) {
          childImageSharp {
            fluid(maxWidth: 300 toFormat: JPG) {
              ...GatsbyImageSharpFluid
            }
          }
        }
      }
    `}
    render={data => <Img fluid={data.placeholderImage.childImageSharp.fluid} />}
  />
)

Build the site, notice no error, check the image assets, eg the same dimension will be output 800x800(despite the maxWidth: 300?) it is roughly 150KiB, vs about 34KiB when mozjpeg is not enabled. For the default displayed image of 300x300, it’s 9KiB vs 40KiB(mozjpeg). It displays fine in the browser so can go unnoticed, if you use any image tools or for some file browsers(Dolphin for me) the image preview may fail to generate because it’s not actually a jpeg image when inspected.

If unable to reproduce, this could be due to the build of mozjpeg installed to your system/OS. Some mozjpeg will support PNG input if they’ve been built with that support, mine does not. I can provide a reproducible project with an Alpine docker container if interested.

Expected result

Either actually convert into a JPG image format, or don’t use jpg extension(and probably throw an error/warning instead of failing silently).

Actual result

PNG image format but jpg file extension. PNG did have slightly reduced filesize for the same dimensions but was still PNG data.

Environment

Gatsby v2.4.8, Custom Alpine Linux 3.8 Docker container, NodeJS v10, custom built mozjpeg(v3.4.0 via CMake not autotools, functions correctly for JPG, only static and win32 optionally support PNG via CMake builds). Project is the default starter from gatsby new.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 20 (14 by maintainers)

Most upvoted comments

we could convert it to a jpg buffer before passing it to mozjpg if it’s not a jpg

@polarathene Thanks for your extended answer! I don’t mind having larger images, but are more concerned about build times. I’ll see what I can come up with. Thanks again!

@jeliasson Then your issue is unrelated to this bug, MozJPEG is only for JPEG outputs, there are versions that can take PNG images as inputs(but it’s unclear that will be supported in newer versions built with CMake).

I haven’t touched my Docker alpine gatsby image for a few months now, it wasn’t very reliable(npm packages installed binaries for png/webp and maybe jpg that were not compatible for alpine, sharp needed libvips if it’s binary wasn’t compatible at the time, when pre-built binaries did work, it wasn’t updated to the latest sharp like other binaries even though a specific bug only affecting alpine was patched).

I have a multi-stage docker image that builds sharp binary for alpine, mozjpeg also required being built. for the imagemin packages that bring in cwebp(webp support) and pngquant(png support), I used alpine local packages and copied over the binaries(their packages don’t support using local versions). It still had some issues though, so I never got around to publishing the images.

I’d advise using a different base image and accepting the extra weight, you can give alpine build dependencies but that will add 200MB roughly to size.

Another bit of advice if you’re not aware, you can’t run npm/yarn locally for packages and have the binaries work in your alpine image, they’re not likely compatible, so you must install node packages inside the container(which may lose cache whenever you kill the container).

Thanks for the feedback, @polarathene!

You are absolutely right, after a quick look I am also sure this is due to the toFormat here: https://github.com/gatsbyjs/gatsby/blob/master/packages/gatsby-plugin-sharp/src/index.js#L264

Should an image format be converted to regular JPG at 100% lossless quality first before being run through mozjpeg? Or should mozjpeg support only allow JPG input?

Converting non-jpgs first to jpg when mozjpeg is enabled should cover all cases in my opinion, although I’m not sure how the image results of conversion + mozjpeg will look like.

I won’t be able to have a deeper look into and fix this before the end of next week. If you would like to submit a PR by yourself before then - feel free 😃