Adding Blurhash to image uploads

I’m trying to add Blurhash (https://blurha.sh) to a project and figure the hash should be stored along with the image.

Can someone point me in the right direction? I think I saw something about extending the plugin but I can’t find any other docs for it.

System Information
  • Strapi Version: 3.4.1
  • Operating System: FROM strapi/base:12
  • Database: mongo
  • Node Version: v14.5.0
  • NPM Version: 6.14.5
  • Yarn Version: 1.22.5

I know this is a bit late. I found a solution and wrote about it here Oliver Butler

This will let you add a blurHash attribute to all uploads on Strapi :slight_smile:

1 Like

@oliverbutler, thanks for the solution. It worked for me. That said, you should really include all packages installed and all files touched to implement your solution. As is, the tutorial’s incomplete.

1 Like

@oliverbutler thanks for the solution.

My blog is actually built using Strapi & Next.js. I have two options:

1: I can store blurHash value on the Strapi as explained in your tutorial.

2: I can use plaiceholder library to generate the blurhash on the frontend.

Can you please guide me on which approach is better?

Thanks

If you don’t mind, can you please guide a littler further on what packages & files needed beside the tutorial? I am also trying to implement it in my project.

hey @oliverbutler - your link is dead now, but I just came across this thread. We are using next/image and having a blurhash coming directly from strapi would be amazing!

new link is Oliver Butler

1 Like

Do you have any ideas how to connect it for v4?

1 Like

Hello everyone.

To configure blur hash for v4 you can do as follows:

  1. By the using the register function in ./src/index.js do an extension the content type and redefine the default function ‘uploadFileAndPersist’ with blur hash logic.
    Add the following:
register({ strapi }) {

    strapi.contentType('plugin::upload.file').attributes.blurHash = {
      type: 'string',
    }

    strapi.plugin('upload').services.upload.uploadFileAndPersist = async function (fileData, { user } = {}) {

      const config = strapi.config.get('plugin.upload');

      const { getDimensions, generateThumbnail, generateResponsiveFormats } = strapi.plugin('upload').service('image-manipulation')

      await strapi.plugin('upload').provider.upload(fileData);

      const thumbnailFile = await generateThumbnail(fileData);

      if (thumbnailFile) {
        await strapi.plugin('upload').provider.upload(thumbnailFile);
        // Begin Override

        const encodeImageToBlurhash = (imageBuffer) =>
          new Promise((resolve, reject) => {
            sharp(imageBuffer)
              .raw()
              .ensureAlpha()
              .toBuffer((err, buffer, { width, height }) => {
                if (err) return reject(err);
                resolve(
                  encode(new Uint8ClampedArray(buffer), width, height, 4, 4)
                )
              });
          });

        const blurHash = await encodeImageToBlurhash(thumbnailFile.buffer);

        // Add a custom field to add a blurHash

        fileData.blurHash = blurHash;

        // End Override
        delete thumbnailFile.buffer;
        _.set(fileData, 'formats.thumbnail', thumbnailFile);
      }

      const formats = await generateResponsiveFormats(fileData);

      if (Array.isArray(formats) && formats.length > 0) {

        for (const format of formats) {

          if (!format) continue;

          const { key, file } = format;

          await strapi.plugin('upload').provider.upload(file);

          delete file.buffer;

          _.set(fileData, ['formats', key], file);

        }
      }
      const { width, height } = await getDimensions(fileData.buffer);

      delete fileData.buffer;

      _.assign(fileData, {
        provider: config.provider,
        width,
        height,
      });

      return this.add(fileData, { user });
    }
  },
  1. Additionally, you will need to define the functions used:
  const _ = require('lodash');
  const sharp = require('sharp')
  const { encode } = require('blurhash')

It is should work.

Hi everyone, based on Nikita_Ostryakov answer there’s a slightly easier way:

  1. We are leveraging https://plaiceholder.co to make our base64 (or any other type of placeholder type). To install this run npm install plaiceholder or yarn add plaiceholder

  2. Make a file inside the ./src/extensions folder named extendFileUpload.js. Put the following code inside the file (I chose to have a separate file for the extension to keep things clean):

// ./src/extensions/extendFileUpload.js
const plaiceholder = require('plaiceholder');
const _ = require('lodash');

module.exports = {
    generatePlaceholder(strapi) {
       
        // Here we are adding the attribute 'placeholder' to the file types
        strapi.contentType('plugin::upload.file').attributes.placeholder = {
            type: 'string',
        };

        strapi.plugin('upload').services.upload.uploadFileAndPersist =
            async function (fileData, { user } = {}) {
                const config = strapi.config.get('plugin.upload');

                const {
                    getDimensions,
                    generateThumbnail,
                    generateResponsiveFormats,
                } = strapi.plugin('upload').service('image-manipulation');

                await strapi.plugin('upload').provider.upload(fileData);

                const thumbnailFile = await generateThumbnail(fileData);

                if (thumbnailFile) {
                    await strapi
                        .plugin('upload')
                        .provider.upload(thumbnailFile);

                    // Here we are generating the placeholder based on the thumbnail buffer
                    try {
                        await plaiceholder
                            .getPlaiceholder(thumbnailFile.buffer)
                            .then(({ base64 }) => {

                                // We'll set the generated base64 string to your placeholder attribute
                                fileData.placeholder = base64;
                            });
                    } catch (error) {
                        console.log(error);
                        error;
                    }

                    delete thumbnailFile.buffer;
                    _.set(fileData, 'formats.thumbnail', thumbnailFile);
                }

                const formats = await generateResponsiveFormats(fileData);

                if (Array.isArray(formats) && formats.length > 0) {
                    for (const format of formats) {
                        if (!format) continue;

                        const { key, file } = format;

                        await strapi.plugin('upload').provider.upload(file);

                        delete file.buffer;

                        _.set(fileData, ['formats', key], file);
                    }
                }
                const { width, height } = await getDimensions(fileData.buffer);

                delete fileData.buffer;

                _.assign(fileData, {
                    provider: config.provider,
                    width,
                    height,
                });

                return this.add(fileData, { user });
            };
    },
};

  1. Open the ./src/index.js where you add the following:
// ./src/index.js
const extendFileUpload = require('./extensions/extendFileUpload');

register({ strapi }) {
    extendFileUpload.generatePlaceholder(strapi);
},
  1. Restart your strapi server.

  2. When you upload a new image, the placeholder will be generated as well. When you fetch your images a new attribute will be populated, namely the placeholder attribute.

  3. (bonus) If you are using Next.js, you can use the placeholder="blur" and blurDataURL={placeholder} attributes to make your image utilise the placeholder we just created.

Cheers!

1 Like

The code from @slashinteractive didn’t work for me in Strapi v4.1.1. However, I was able to make it work with some changes:

const plaiceholder = require('plaiceholder')
const _ = require('lodash')

module.exports = {
  generatePlaceholder (strapi) {
    strapi.contentType('plugin::upload.file').attributes.placeholder = {
      type: 'text'
    }

    strapi.plugin('upload').services.upload.uploadFileAndPersist = async function (fileData, { user } = {}) {
      const config = strapi.config.get('plugin.upload')

      const {
        getDimensions,
        generateThumbnail,
        generateResponsiveFormats,
        isSupportedImage
      } = strapi.plugin('upload').service('image-manipulation')

      await strapi.plugin('upload').service('provider').upload(fileData)

      if (await isSupportedImage(fileData)) {
        const thumbnailFile = await generateThumbnail(fileData)

        if (thumbnailFile) {
          await strapi.plugin('upload').service('provider').upload(thumbnailFile)

          try {
            await plaiceholder
              .getPlaiceholder(thumbnailFile.url)
              .then(({ base64 }) => { fileData.placeholder = base64 })
          } catch (e) {
            fileData.placeholder = ''
          }

          delete thumbnailFile.buffer
          _.set(fileData, 'formats.thumbnail', thumbnailFile)
        }

        const formats = await generateResponsiveFormats(fileData)

        if (Array.isArray(formats) && formats.length > 0) {
          for (const format of formats) {
            if (!format) { continue }

            const { key, file } = format

            strapi.plugin('upload').service('provider').upload(file)

            _.set(fileData, ['formats', key], file)
          }
        }

        const { width, height } = await getDimensions(fileData)

        _.assign(fileData, {
          provider: config.provider,
          width,
          height
        })
      }

      return this.add(fileData, { user })
    }
  }
}
1 Like

For v4 just use plugin strapi-plugin-placeholder

I’m on Strapi v4.3.4 and the solution @SoftCreatR gave breaks image formats (no longer has a url) for me.

The strapi-plugin-placeholder plugin is working great.