DEV Community

Muhammad Ilyasa Fadhlih
Muhammad Ilyasa Fadhlih

Posted on

Converting HEIC image extension in Node.js with the sharp library

I want to share my experience using the Sharp library from the Node Package Manager (NPM) to dealing with images with heic extension. First, let's talk about the problems.

Background

There are many problems. First is why? Why did I have to mind the .heic extension?

That problem comes from iPhone users, they produce this .heic extension as the output of the image when they use their camera. This behaviour is different from an Android phone, which usually outputs .jpeg or .jpg.

This really makes things complicated on the backend side because browsers don't usually support displaying .heic extension out-of-the-box. As a backend engineer, I'm the one who takes responsibility for converting the image that isn't supported by the browser to one that is widely compatible, in addition to that I have to resize the image so it loads faster when displayed on the browser. Also, most of my customers are using iPhones, which is really have to consider be an issue.

So, I had to either convert it to .jpeg or .webp. That time, I had an issue with the .webp result, for example, the image is being rotated by 90 degrees for some reason, which I think could be easily solved by calling the rotate() method on the Sharp library. At the end, I decided to stick with .jpeg while keeping the EXIF metadata to keep the rotation and other metadata.

The other problem is Sharp library doesn't come out-of-the-box with support for converting the .heic extension. So, what should I do in this scenario?

Solution

After a quick reading of the sharp's documentation, I decided to stick with the sharp library, I was excited to find out that it may have the ability to process files with the heic extension. It was possible because Sharp actually uses a library called libvips as the underlying backend for processing the image.

Libvips has heic support, but why doesn't my Sharp library support it?

We already know that sharp is using libvips. When we install the sharp library with npm install sharp, it'll also download the libvips binary inside the node_modules directory and sharp will use that binary, but it has limited support for image extensions. What we can do to make it wider support is to do a custom, manual installation of libvips, I'm talking about building libvips from source.

Make it work!

It takes a lot of steps to get there. Here is it:

  1. Install build tools.
  2. Install ninja & meson (build from source)
  3. Install libvips and it's dependencies
  4. Test libvips installation
  5. Install sharp and its dependencies

Let's get started (I assume you use the Debian Linux distribution).

I highly recommend you use a Docker container with a Debian image; I will provide a full Dockerfile below.

1. Install build tools

Here's where we're going to install git, Python, and other essential build tools.

# Install dependencies
sudo apt-get update && \
    sudo apt-get install -y curl python3 git build-essential pkg-config
Enter fullscreen mode Exit fullscreen mode

2. Install ninja & meson (build from source)

Let's install ninja first

# Install ninja
git clone --branch=v1.12.1 --depth=1 https://github.com/ninja-build/ninja.git && \
    cd ninja && \
    ./configure.py --bootstrap && \
    chmod +x ninja && \
    sudo mv ninja /usr/local/bin/ninja
Enter fullscreen mode Exit fullscreen mode

Install meson

# Install meson
git clone --branch=1.6.1 --depth=1 https://github.com/mesonbuild/meson.git && \
    cd meson && \
    ./packaging/create_zipapp.py --outfile meson.pyz --interpreter '/usr/bin/env python3' && \
    chmod +x meson.pyz && \
    sudo mv meson.pyz /usr/local/bin/meson
Enter fullscreen mode Exit fullscreen mode

Done, on to step 3

3. Install libvips and its dependencies

# install runtime dependencies
sudo apt-get update && \
    sudo apt-get install -y \
    curl python3 build-essential git pkg-config libglib2.0-dev libexpat1-dev libheif-dev\
    liblcms2-dev libjpeg-dev libpng-dev libwebp-dev libexif-dev
Enter fullscreen mode Exit fullscreen mode
# Build libvips
git clone --branch=v8.16.0 --depth=1 https://github.com/libvips/libvips.git && \
    cd libvips && \
    meson setup build --prefix /usr/local && \
    cd build && \
    meson compile && \
    meson test && \
    meson install && \
    sudo ldconfig && \
    cd .. && \
    rm -rf libvips
Enter fullscreen mode Exit fullscreen mode

3. Test libvips installation

At this stage, you have libvips installed on your machine, you can test if it's properly installed.

vips --version
Enter fullscreen mode Exit fullscreen mode

4. Install sharp and its dependencies

Install node-addon-api

npm install node-addon-api@^8.1.0
Enter fullscreen mode Exit fullscreen mode

Install node-gyp

npm install node-gyp@^11.0.0
Enter fullscreen mode Exit fullscreen mode

Finally, install sharp

npm install sharp@^0.33.5 --build-from-source
Enter fullscreen mode Exit fullscreen mode

Dockerfile

I assume that you already includes node-gyp, node-addon-api, and sharp on your package.json file.

{
    "dependencies": {
        "node-addon-api": "^8.1.0",
        "node-gyp": "^11.0.0",
        "sharp": "^0.33.5"
    }
}
Enter fullscreen mode Exit fullscreen mode

Here's the Dockerfile looks like:

# Stage 1
FROM debian:bookworm-slim AS builder

WORKDIR /build

# Install dependencies
RUN apt-get update && \
    apt-get install -y curl python3 git build-essential pkg-config && \
    rm -rf /var/lib/apt/lists/*

# Install ninja
RUN git clone --branch=v1.12.1 --depth=1 https://github.com/ninja-build/ninja.git && \
    cd ninja && \
    ./configure.py --bootstrap && \
    chmod +x ninja && \
    mv ninja /usr/local/bin/ninja

# Install meson
RUN git clone --branch=1.6.1 --depth=1 https://github.com/mesonbuild/meson.git && \
    cd meson && \
    ./packaging/create_zipapp.py --outfile meson.pyz --interpreter '/usr/bin/env python3' && \
    chmod +x meson.pyz && \
    mv meson.pyz /usr/local/bin/meson

# Stage 2
FROM debian:bookworm-slim 

# Install runtime dependencies
RUN apt-get update && \
    apt-get install -y \
    curl python3 build-essential git pkg-config libglib2.0-dev libexpat1-dev libheif-dev\
    liblcms2-dev libjpeg-dev libpng-dev libwebp-dev libexif-dev && \
    curl -fsSL https://deb.nodesource.com/setup_23.x | bash - && \
    apt install -y nodejs

# Copy build tools from builder stage
COPY --from=builder /usr/local/bin/ninja /usr/local/bin/ninja
COPY --from=builder /usr/local/bin/meson /usr/local/bin/meson

# Build libvips
RUN git clone --branch=v8.16.0 --depth=1 https://github.com/libvips/libvips.git && \
    cd libvips && \
    meson setup build --prefix /usr/local && \
    cd build && \
    meson compile && \
    meson test && \
    meson install && \
    ldconfig && \
    cd .. && \
    rm -rf libvips

# Remove unused build tools
RUN apt-get purge -y git && apt-get autoremove -y && \
    rm -rf /var/lib/apt/lists/* /usr/local/bin/ninja /usr/local/bin/meson

WORKDIR /app

RUN --mount=type=bind,src=package.json,target=package.json \
    --mount=type=bind,src=package-lock.json,target=package-lock.json \
    npm ci --build-from-source

COPY . .

# Build the application
RUN npm run build || true

# Remove source code
RUN rm -rf ./src

EXPOSE 3000

ENTRYPOINT [ "npm", "start" ]
Enter fullscreen mode Exit fullscreen mode

Conclusion

Th default sharpjs package doesn't include vips with heic support, it's up to the developer to build vips from source with wider support

Top comments (0)