DEV Community

Cover image for Building a Custom Image Editor with Xata
Moronfolu Olufunke
Moronfolu Olufunke

Posted on

Building a Custom Image Editor with Xata

Imagine having the ability to build our very own image editor, one that is not just functional but also oozes with style and user-friendliness. In this article, we will unveil the secret recipe for creating a custom image editor that is as delightful to use as it is to build.

"Building an image editor? Isn't that too complex?" Well, fret not! We are about to prove that with a pinch of Vue.js, a dash of Xata, and a sprinkle of imagination, we can create an image editor that will leave users clicking and smiling.

This article will discuss building an image editing tool that allows users to upload, transform, and download images effortlessly. We will walk through the process of building an image editor step by step using Vue.js and Xata.

Xata is a Serverless Data platform designed to simplify how developers work with data. It combines the power of a traditional database with the ease of use found in SaaS (Software as a Service) spreadsheet applications.

Xata recently introduced a new feature - File Attachments. This feature lets developers integrate files, images, and documents directly into their database records. It eliminates the need for separate storage services and streamlines the process of managing structured data and binary objects like images, videos, and documents.

In essence, Xata's File Attachments revolutionize data app development by simplifying the architecture and reducing the number of services developers need to manage while enhancing the user experience and versatility of applications.

GitHub

Check out the complete source code here.

Netlify

We can access the live demo here.

Prerequisite

Understanding this article requires the following:

  • Installation of Node.js
  • Basic knowledge of JavaScript
  • Creating a free account with Xata

Creating a Vue app

We can create a new Vue app using the npm create vue@latest command.
Scaffolding the project would provide a list of options from which we can select the best fit for the project.

Styling

The CSS framework we will use in this project is Tachyons CSS, which we will install by running the command below in the terminal.

npm i tachyons
Enter fullscreen mode Exit fullscreen mode

Afterward, we will make it globally available for usage in the project by adding the line below in our src/main.js:

import 'tachyons/css/tachyons.css';
Enter fullscreen mode Exit fullscreen mode

Creating a Xata Database

To set up a database in Xata, we will start by logging into our existing account or creating a new one. After accessing our account, we will navigate to the database section in the user dashboard. Here, we can create a new database. We will name this database image-transformation for our specific use case since it aligns with the task at hand.

Once we create this database, the next step is to establish a table within that database and define the necessary records or fields we want the table to contain. In our project, we will name the table images. Within the images table, we will create a record called image with a type of file. This image record will be the focal point for all our manipulations.

The accompanying media below provides a visual guide to creating a record.

Creating a record

Setting up Xata in our Vue application

To incorporate Xata into our project, we will follow these installation steps:

Install Xata SDK
We will run the following command in the command line interface:

npx xata
Enter fullscreen mode Exit fullscreen mode

Initialize Xata
Now, we can initialize Xata for use within the application with this command:

xata init
Enter fullscreen mode Exit fullscreen mode

Configuration Options
Xata will present us with various configuration options to choose from. After making our selections, Xata will generate essential files, including .xatrc and .env.

Edit API Key
To ensure our Vue application can access the Xata environment, we will need to modify the Xata API key within the Vue app like so:

VITE_XATA_API_KEY="add your api key"
Enter fullscreen mode Exit fullscreen mode

We can now modify the xata.js file to use this intialised VITE_XATA_API_KEY like so:

const defaultOptions = {
  enableBrowser: true,
  apiKey: import.meta.env.VITE_XATA_API_KEY,
  databaseURL:
    "https://...",
};
Enter fullscreen mode Exit fullscreen mode

We would have seamlessly integrated Xata into our project by following the steps above.

Creating the landing page

In an ideal scenario, our solution should include a welcoming landing page that provides users with essential information about the product. For our project, we will design a simple landing page that offers users a quick overview of the product's purpose. To achieve this, we will create a views/HomeView.vue file and insert the following code:

<template>
  <header class="vh-100 bg-light-pink dt w-100 helvetica">
    <div
      style="
        background: url(http://mrmrs.github.io/photos/display.jpg) no-repeat
          center right;
        background-size: cover;
      "
      class="dtc cover ph3 ph4-m ph5-l tc">
      <div class="tc mt4 mb5">
        <h1 class="f2 f-subheadline-l measure lh-title nb1 fw9">X-Transformr</h1>
        <h2 class="f6 fw6 black">
          Store, transform and update images using Xata (File Attachments)
        </h2>
      </div>
    </div>
  </header>
</template>
Enter fullscreen mode Exit fullscreen mode

At the addition of the code above, our view should appear like the below:

landing page

Adding the uploading mechanism

Creating the upload UI
We will include a user-friendly input interface that enables users to upload images from which we can extract text. We will integrate the following code block into the components/Uploadcontainer.vue file to achieve this.

<template>
  <div class="flex flex-column flex-row-ns pa3 calisto bg-black-05">
    <div class="w-50-ns w-100 ph3">
      <form enctype="multipart/form-data">
        <h2>Add Image</h2>
        <div
          class="b--dashed bw1 b--light-purple pa3 hover-bg-black-10 bg-animate pointer relative h4">
          <input
            type="file"
            id="fileInput"
            accept="image/*"
            class="input-file absolute w-100 h4 pointer o-0" />
          <p class="tc f4">
            Drag your image here to begin<br />
            or click to browse
          </p>
        </div>
      </form>
    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

We achieved the following from the above:

  • Implemented a <form> element with the enctype="multipart/form-data" attribute, facilitating file uploads.
  • Incorporated an input field with the file type attribute, allowing file uploads and restricting the input to accept only image files using the **accept="image/*" attribute."

Creating the transformation UI
Xata has a number of image transformation options that we can apply to images to transform them to our desired taste in real-time. In our case, we will focus on four transformation options, which include the height, width blur, and sharpness of the image. To achieve this, we want to create a mechanism that allows users to add the transformation option values. For this, in the components/Uploadcontainer.vue we will create a <form></form> and add input methods that can be fed to Xata.

In the code above, we achieved the following:

  • Created a form with input fields that allow the user to adjust the parameters of interest
  • In the data property, we initialized the input fields and set default values for each parameter.
  • Added a download button to allow users to get the transformed images

In the views/HomeView.vue, we imported the component like so:

<template>
  <header class="vh-100 bg-light-pink dt w-100 helvetica">
    <div
      style="
        background: url(http://mrmrs.github.io/photos/display.jpg) no-repeat
          center right;
        background-size: cover;
      "
      class="dtc cover ph3 ph4-m ph5-l tc">
      <div class="tc mt4 mb5">
        ...
      </div>
      <Uploadcontainer />
    </div>
  </header>
</template>
<script>
  import Uploadcontainer from "@/components/Uploadcontainer.vue";
  export default {
    components: {
      Uploadcontainer,
    },
  };
</script>
Enter fullscreen mode Exit fullscreen mode

We did the above to facilitate the component to show up on the landing page. At this point, our landing page should look like the below:

transformation ui

Adding an upload service

To handle the file upload to be displayed in the UI, we will create a utils/file-upload.service.js and add the code block below:

function upload(formData) {
  const photos = formData.getAll('photos');
  const promises = photos.map((x) => getImage(x)
  .then(img => ({
    id: img,
    originalName: x.name,
    fileName: x.name,
    url: img
  })));
  return Promise.all(promises);
}
function getImage(file) {
  return new Promise((resolve, reject) => {
    const fReader = new FileReader();
    const img = document.createElement('img');
    fReader.onload = () => {
      img.src = fReader.result;
      resolve(getBase64Image(img));
    }
    fReader.readAsDataURL(file);
  })
}
function getBase64Image(img) {
  const canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);
  const dataURL = img.src;
  return dataURL;
}
export { upload }
Enter fullscreen mode Exit fullscreen mode

The code above reads the image we will upload, draws it into a canvas, and then converts it to a Base64 string.

Uploading the image to Xata

Before we can transform the image, we will have to first upload the image to Xata. To do this, we will edit the components/Uploadcontainer.vue to the below:

The code block above does the following:

  • Represented various component states during image processing using STATUS_INITIAL, STATUS_SAVING, STATUS_SUCCESS, and STATUS_FAILED definitions, which we initialised in the corresponding computed properties - isInitial, isSaving, isSuccess, and isFailed.
  • Image Upload Section:
    • This section contains a form (<form>) with enctype="multipart/form-data" for file upload.
    • It displays an "Add Image" heading and a drag-and-drop or file input field that allows users to select image files.
    • Depending on the component's state, it shows messages like "Drag your image here to begin" or "Adding image..." to provide user feedback.
    • When an image is successfully added, it displays a success message along with an option to upload another image and previews the uploaded image.
  • Component Data:
    • The component manages various data properties, including uploadedImages, uploadError, currentStatus, uploadFieldName, imageDataArray, recordId, imageURL, and imageName, to track the state and information related to image uploads and processing.
  • Methods:
    • reset(): Resets the form and component's state to its initial state - STATUS_INITIAL
    • save(formData): Initiate the image uploadImageToXata() to foster uploading to Xata by using the upload service
    • filesChange(fieldName, fileList): Handles changes in the selected image files and prepares a formData object for upload.
    • prepareFormData(): Processes uploaded image data to create an object with metadata, base64 content, name, and URL for the uploaded image.
    • uploadImageToXata(): Uploads the processed image data to a database using the Xata client.

At this point, if you check your Xata dashboard, you should see an image uploaded like the below:

Xata dashboard showing uploaded file

In this image, we can see the images table with the image record. This image record currently has the uploaded image with the id (record_id) which we will be using later in our project.

When we upload an image, we should see our UI look like this:

UI after uploading

Transforming images

Now that we have successfully uploaded our image, it is now time to apply transformations. To do this, we will edit the components/Uploadcontainer.vue file and add the following:

In this code block, we achieved the following:

  • Added a conditional render (v-if="imageURL") based on the existence of an imageURL property. If the image has been uploaded successfully to Xata and we have a URL, the form component for customising the transformation values will become visible to the user
  • debounceTransform: This function is a debounced version of the editImage method, which means it waits for a specified delay (2000 milliseconds or 2 seconds) after the user stops typing before executing editImage.
  • editImage: This asynchronous function performs image editing and transformation using data from the Xata API. It calculates a new image URL based on the user's height, width, sharpening, and blur input and updates the component's properties accordingly.
  • Installed lodash and used the debounce() function to control the rate at which the debounceTransform method is executed.

Download image

After editing the pictures, we made available functionality for the user to download the edited image. We will edit the components/Uploadcontainer.vue to reflect this change.

<template>
  <div class="flex flex-column flex-row-ns pa3 calisto bg-black-05">
    <div class="w-50-ns w-100 ph3">
      <!-- upload image -->
    </div>
    <section class="w-40-l w-50-m w-100 ph3 tl" v-if="imageURL">
      <h2>Transform Image</h2>
      <form class="ba b--black bw2 bg-white br2 mw6 center pv2 ph4 shadow-5 f6">
        <!-- input fields -->
        <button
          @click="downloadimage"
          class="center db pv2 ph3 mb3 tracked bg-black ba br3 white hover-black hover-bg-black-30 bg-animate pointer f7">
          Download
        </button>
      </form>
    </section>
  </div>
</template>
<script>
  import { transformImage } from "@xata.io/client";
  export default {
    data() {
      return {
        imageURL: "",
        imageName: "",
        transformedImageUrl: "",
      };
    },
    computed: {...},
    methods: {
    downloadimage() {
        this.transformedImageUrl = transformImage(this.imageURL, {
          download: this.imageName,
          format: "jpeg",
        });
        window.open(this.transformedImageUrl, "_blank");
      },
    },
    mounted() {...},
  };
</script>
Enter fullscreen mode Exit fullscreen mode

At this point, our whole application should look like the below:

Final application

Conclusion

In this article, we talked about building an image editor using Xata. Xata acts as our database and the image transformer using the options made available by the Xata service.

Here are some additional resources that will be helpful when working with Xata:

Top comments (0)