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-analysis2# OR3npx create-nuxt-app nuxtjs-image-analysis4# OR5npm 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-analysis23yarn dev4# OR5npm 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 cloudinary2# OR3yarn 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.js2export 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 multer2# OR3npm 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.js23require('dotenv').config()45const app = require('express')()67const cloudinary = require('cloudinary');89const multer = require('multer')1011const upload = multer({ dest: 'uploads/' });121314cloudinary.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: true19});2021app.post('/upload', upload.single('file'), async (req, res) => {222324 const up_options = {25 resource_type: "image",26 folder: "nuxtjs-image-upload-analysis",27 quality_analysis: true28 };2930 const response = await cloudinary.v2.uploader.upload(31 req.file.path,32 up_options33 );3435 res.json(response);36});3738module.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 <input9 required10 @change="changeFile"11 name="file"12 type="file"13 />14 </div>15 </div>1617 <div>18 <button19 v-if="!uploading"20 type="submit"21 >22 Upload & Analyse23 </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: null9 }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,config27 );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>8485 </div>8687 <div>88 ...89 <img90 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.