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.
The completed project is available on Codesandbox.
You can find the full codebase on my Github
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:
yarn create nuxt-app nuxtjs-image-analysis
# OR
npx create-nuxt-app nuxtjs-image-analysis
# OR
npm init nuxt-app nuxtjs-image-analysis
Code language: PHP (php)
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:
cd nuxtjs-image-analysis
yarn dev
# OR
npm run dev
Code language: PHP (php)
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.
touch .env
Code language: CSS (css)
<!-- .env -->
NUXT_ENV_CLOUDINARY_CLOUD_NAME=<cloud-name>
CLOUDINARY_API_KEY=<api-key>
CLOUDINARY_API_SECRET=<api-secret>
Code language: HTML, XML (xml)
Once this is done, we need to install Cloudinary’s Node.Js library.
npm install cloudinary
# OR
yarn add cloudinary
Code language: PHP (php)
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.
touch server-middleware/api.js
We now need to link this file in the serverMiddleware
section of our nuxt.config.js
file.
// nuxt.config.js
export default {
...
serverMiddleware: [
{ path: "/api", handler: "~/server-middleware/api.js" },
],
}
Code language: JavaScript (javascript)
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.
yarn add express multer
# OR
npm install express multer
Code language: PHP (php)
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.
// server-middleware/api.js
require('dotenv').config()
const app = require('express')()
const cloudinary = require('cloudinary');
const multer = require('multer')
const upload = multer({ dest: 'uploads/' });
cloudinary.config({
cloud_name: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
secure: true
});
app.post('/upload', upload.single('file'), async (req, res) => {
const up_options = {
resource_type: "image",
folder: "nuxtjs-image-upload-analysis",
quality_analysis: true
};
const response = await cloudinary.v2.uploader.upload(
req.file.path,
up_options
);
res.json(response);
});
module.exports = app
Code language: JavaScript (javascript)
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.
To send our file to the upload endpoint, we need to set up a simple HTML to capture the file we want to upload.
<!-- pages/index.vue -->
<template>
...
<form @submit.prevent="upload">
<div>
<label for="file"> Image </label>
<div>
<input
required
@change="changeFile"
name="file"
type="file"
/>
</div>
</div>
<div>
<button
v-if="!uploading"
type="submit"
>
Upload & Analyse
</button>
<p v-else>Uploading...</p>
</div>
</form>
...
</template>
Code language: HTML, XML (xml)
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.
<!-- pages/index.vue -->
<script>
export default {
data(){
return {
image: null,
uploading:false,
cloudinaryImage: null
}
},
methods:{
changeFile(e){
this.image = e.target.files[0]
},
async upload(e){
this.uploading = true;
const url = '/api/upload';
const formData = new FormData();
formData.append('file',this.image);
const config = {
headers: {
'content-type': 'multipart/form-data'
}
}
this.cloudinaryImage = await this.$axios.$post(
url,formData,config
);
this.uploading = false;
}
}
}
</script>
Code language: HTML, XML (xml)
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
Now that we have uploaded our image and received our feedback, let us display the analysis we receive.
<!-- pages/index.vue -->
<template>
...
<div v-if="cloudinaryImage">
<div>
<div>
<h3>Image analysis</h3>
<div>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Format</td>
<td >{{ cloudinaryImage.format }}</td>
</tr>
<tr>
<td>Dimensions</td>
<td >{{ cloudinaryImage.width }} x {{ cloudinaryImage.height }}</td>
</tr>
<tr>
<td>Size</td>
<td >{{ cloudinaryImage.bytes }} Bytes</td>
</tr>
<tr>
<td>Quality score</td>
<td >{{ cloudinaryImage.quality_score }} </td>
</tr>
<tr>
<td>Focus</td>
<td >{{ cloudinaryImage.quality_analysis.focus }} </td>
</tr>
<tr>
<td>Noise</td>
<td >{{ cloudinaryImage.quality_analysis.noise }} </td>
</tr>
<tr>
<td>Contrast</td>
<td >{{ cloudinaryImage.quality_analysis.contrast }}</td>
</tr>
<tr>
<td>Exposure</td>
<td >{{ cloudinaryImage.quality_analysis.exposure }} </td>
</tr>
<tr>
<td>Saturation</td>
<td >{{ cloudinaryImage.quality_analysis.saturation }} </td>
</tr>
<tr>
<td>Lighting</td>
<td >{{ cloudinaryImage.quality_analysis.lighting }} </td>
</tr>
<tr>
<td>Pixel score</td>
<td >{{ cloudinaryImage.quality_analysis.pixel_score }} </td>
</tr>
<tr>
<td>Color score</td>
<td >{{ cloudinaryImage.quality_analysis.color_score }} </td>
</tr>
<tr>
<td>DCT</td>
<td >{{ cloudinaryImage.quality_analysis.dct }} </td>
</tr>
<tr>
<td>Blockiness</td>
<td >{{ cloudinaryImage.quality_analysis.blockiness }} </td>
</tr>
<tr>
<td>Chroma subsampling</td>
<td >{{ cloudinaryImage.quality_analysis.chroma_subsampling }} </td>
</tr>
<tr>
<td>Resolution</td>
<td >{{ cloudinaryImage.quality_analysis.resolution }} </td>
</tr>
</tbody>
</table>
</div>
</div>
<div>
...
<img
width="490"
:src="cloudinaryImage.secure_url"
alt="Image"
/>
</div>
</div>
</div>
...
</template>
Code language: HTML, XML (xml)
With the above, we should now have an analysis similar to this one.
To learn more about how the image quality analysis works feel free to review the documentation here.