Analyzing images in a NuxtJS app

Eugene Musebe

Objective

In our complex world of constant user-generated content, you may want to analyze images to determine many things from quality to accessibility to semantic data. This is a complex task for a JAMStack website, but nonetheless possible. Let us build a simple Nuxt.Js app to do this.

Codesandbox

The completed project is available on Codesandbox.

You can find the full codebase on my Github

Project creation

For this project, NuxtJS is our framework of choice. It is an intuitive and powerful VueJS framework. To set up our project, you are required to have installed Yarn and NPM v5.2+/v6.1+. We will use the create-nuxt-app utility, open the terminal in your working directory of preference:

1yarn create nuxt-app nuxtjs-image-analysis
2# OR
3npx create-nuxt-app nuxtjs-image-analysis
4# OR
5npm init nuxt-app nuxtjs-image-analysis

This will trigger a series of prompts aimed at customising the project for you. Here are our recommended defaults:

Project name: nuxtjs-image-analysis Programming language: JavaScript Package manager: Yarn UI framework: TailwindCSS Nuxt.js modules: Axios (nuxt/axios) Linting tools: N/A Testing frameworks: None Rendering mode: Universal (SSR / SSG) Deployment target: Server (Node.js hosting) Development tools: N/A What is your Github username: <your-github-username> Version control system: Git

Once the setup is complete, feel free to run it:

1cd nuxtjs-image-analysis
2
3yarn dev
4# OR
5npm run dev

Cloudinary setup

Cloudinary is a media management platform with a comprehensive set of APIs and SDKs. If you do not have an account, you may register here.

We are going to use their image quality analysis feature. Extended image quality analysis is currently in Beta. If you have a paid account you may request for access.

To be able to upload images and get analysis from Cloudinary, we need to be able to specify to our app which accounts to connect to. We will do this by adding our cloud name, api key, and api secret contained on our dashboard to our env secrets file.

1touch .env
1<!-- .env -->
2NUXT_ENV_CLOUDINARY_CLOUD_NAME=<cloud-name>
3CLOUDINARY_API_KEY=<api-key>
4CLOUDINARY_API_SECRET=<api-secret>

Once this is done, we need to install Cloudinary's Node.Js library.

1npm install cloudinary
2# OR
3yarn add cloudinary

Server middleware setup

In order to upload the images to Cloudinary and request access, we are going to utilize Nuxt.Js's server middleware. First, let's create the middleware file and directory.

1touch server-middleware/api.js

We now need to link this file in the serverMiddleware section of our nuxt.config.js file.

1// nuxt.config.js
2export default {
3 ...
4 serverMiddleware: [
5 { path: "/api", handler: "~/server-middleware/api.js" },
6 ],
7}

Uploading our images

To upload our images our server middleware needs to be able to receive and process the requests. To enable this, we will use Express.Js, a fast unopinionated, minimalist web framework for node. We will also install multer to help us handle `multipart/form-data data.

1yarn add express multer
2# OR
3npm install express multer

Now let us enable our api to receive files in the file property of the form data and upload it to Cloudinary for analysis. To enable analysis, we need to set quality_analysis to true in the upload options.

1// server-middleware/api.js
2
3require('dotenv').config()
4
5const app = require('express')()
6
7const cloudinary = require('cloudinary');
8
9const multer = require('multer')
10
11const upload = multer({ dest: 'uploads/' });
12
13
14cloudinary.config({
15 cloud_name: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,
16 api_key: process.env.CLOUDINARY_API_KEY,
17 api_secret: process.env.CLOUDINARY_API_SECRET,
18 secure: true
19});
20
21app.post('/upload', upload.single('file'), async (req, res) => {
22
23
24 const up_options = {
25 resource_type: "image",
26 folder: "nuxtjs-image-upload-analysis",
27 quality_analysis: true
28 };
29
30 const response = await cloudinary.v2.uploader.upload(
31 req.file.path,
32 up_options
33 );
34
35 res.json(response);
36});
37
38module.exports = app

With the above code, our file will be received and uploaded to Cloudinary and the response with the analysis returned back to the caller. The above endpoint will receive requests on the api/upload endpoint.

Sending file for upload

To send our file to the upload endpoint, we need to set up a simple HTML to capture the file we want to upload.

1<!-- pages/index.vue -->
2<template>
3 ...
4 <form @submit.prevent="upload">
5 <div>
6 <label for="file"> Image </label>
7 <div>
8 <input
9 required
10 @change="changeFile"
11 name="file"
12 type="file"
13 />
14 </div>
15 </div>
16
17 <div>
18 <button
19 v-if="!uploading"
20 type="submit"
21 >
22 Upload & Analyse
23 </button>
24 <p v-else>Uploading...</p>
25 </div>
26 </form>
27 ...
28</template>

The above form will trigger the changeFile method on the file selection and upload on form submission. Let's add these methods to our page.

1<!-- pages/index.vue -->
2<script>
3export default {
4 data(){
5 return {
6 image: null,
7 uploading:false,
8 cloudinaryImage: null
9 }
10 },
11 methods:{
12 changeFile(e){
13 this.image = e.target.files[0]
14 },
15 async upload(e){
16 this.uploading = true;
17 const url = '/api/upload';
18 const formData = new FormData();
19 formData.append('file',this.image);
20 const config = {
21 headers: {
22 'content-type': 'multipart/form-data'
23 }
24 }
25 this.cloudinaryImage = await this.$axios.$post(
26 url,formData,config
27 );
28 this.uploading = false;
29 }
30 }
31}
32</script>

When the file changes, we store it in our app state. Thus when our form is submitted, we send the file for upload to the api/upload endpoint. Once uploaded, we receive the response and store it in cloudinaryImage

Displaying the analysis

Now that we have uploaded our image and received our feedback, let us display the analysis we receive.

1<!-- pages/index.vue -->
2<template>
3 ...
4 <div v-if="cloudinaryImage">
5 <div>
6 <div>
7 <h3>Image analysis</h3>
8 <div>
9 <table>
10 <thead>
11 <tr>
12 <th>Metric</th>
13 <th>Value</th>
14 </tr>
15 </thead>
16 <tbody>
17 <tr>
18 <td>Format</td>
19 <td >{{ cloudinaryImage.format }}</td>
20 </tr>
21 <tr>
22 <td>Dimensions</td>
23 <td >{{ cloudinaryImage.width }} x {{ cloudinaryImage.height }}</td>
24 </tr>
25 <tr>
26 <td>Size</td>
27 <td >{{ cloudinaryImage.bytes }} Bytes</td>
28 </tr>
29 <tr>
30 <td>Quality score</td>
31 <td >{{ cloudinaryImage.quality_score }} </td>
32 </tr>
33 <tr>
34 <td>Focus</td>
35 <td >{{ cloudinaryImage.quality_analysis.focus }} </td>
36 </tr>
37 <tr>
38 <td>Noise</td>
39 <td >{{ cloudinaryImage.quality_analysis.noise }} </td>
40 </tr>
41 <tr>
42 <td>Contrast</td>
43 <td >{{ cloudinaryImage.quality_analysis.contrast }}</td>
44 </tr>
45 <tr>
46 <td>Exposure</td>
47 <td >{{ cloudinaryImage.quality_analysis.exposure }} </td>
48 </tr>
49 <tr>
50 <td>Saturation</td>
51 <td >{{ cloudinaryImage.quality_analysis.saturation }} </td>
52 </tr>
53 <tr>
54 <td>Lighting</td>
55 <td >{{ cloudinaryImage.quality_analysis.lighting }} </td>
56 </tr>
57 <tr>
58 <td>Pixel score</td>
59 <td >{{ cloudinaryImage.quality_analysis.pixel_score }} </td>
60 </tr>
61 <tr>
62 <td>Color score</td>
63 <td >{{ cloudinaryImage.quality_analysis.color_score }} </td>
64 </tr>
65 <tr>
66 <td>DCT</td>
67 <td >{{ cloudinaryImage.quality_analysis.dct }} </td>
68 </tr>
69 <tr>
70 <td>Blockiness</td>
71 <td >{{ cloudinaryImage.quality_analysis.blockiness }} </td>
72 </tr>
73 <tr>
74 <td>Chroma subsampling</td>
75 <td >{{ cloudinaryImage.quality_analysis.chroma_subsampling }} </td>
76 </tr>
77 <tr>
78 <td>Resolution</td>
79 <td >{{ cloudinaryImage.quality_analysis.resolution }} </td>
80 </tr>
81 </tbody>
82 </table>
83 </div>
84
85 </div>
86
87 <div>
88 ...
89 <img
90 width="490"
91 :src="cloudinaryImage.secure_url"
92 alt="Image"
93 />
94 </div>
95 </div>
96 </div>
97 ...
98</template>

With the above, we should now have an analysis similar to this one.

References

To learn more about how the image quality analysis works feel free to review the documentation here.

Eugene Musebe

Software Developer

I’m a full-stack software developer, content creator, and tech community builder based in Nairobi, Kenya. I am addicted to learning new technologies and loves working with like-minded people.