Welcome to Vintagram
Tags: javascript, filter, gpu, fun
Published: 2021-11-05 11:23:39

Ever wonder if you can build such an offline alternative to the gram from the meta to use the vintage filters you love while avoid all the metal health issues? Wonder no more, 'cause today, in this article, I'll show you how to build an offline one that should be called Vintagram 😀 Have fun using it then 😆

## The Glitch, the Zuck and the Offline
I uploaded this one to glitch, so you can both use it online at [https://vintagram.glitch.me/](https://vintagram.glitch.me/) and offline just by downloading files or copying the source codes I pasted here while avoid the Zuck completely, is it cool? 😀

## Download the files for offline use
In order to use vintagram offline, you need to download the files at

* [https://vintagram.glitch.me/index.html](https://vintagram.glitch.me/index.html)
* [https://vintagram.glitch.me/webgl-image-filter.js](https://vintagram.glitch.me/webgl-image-filter.js) (or from github)
* [https://vintagram.glitch.me/download.js](https://vintagram.glitch.me/download.js) (or from github)
* [https://vintagram.glitch.me/run.js](https://vintagram.glitch.me/run.js)

and run a local webserver to serve them.

## Some little intro
I write code mainly just for fun and I think it's necessary to have fun in life, 'cause it's too short 😓. My main interest now is to find some cool libraries, especially javascript and thinking how to have fun using them 😀. There are actually tons of libraries to have fun with and I hope I have time in the future to do so :D Vintagram is an idea I have when I found many cool effect libraries and there are not many people willing to turn them into something fun to use. So I decide to build one 😊, and I'll show you how to do it too 😀

## Technique details
I list the technique details here so you can easily follow the article

| Technique                             | Solution / Library used                                                           | Note                                                              |
|---------------------------------------|-----------------------------------------------------------------------------------|-------------------------------------------------------------------|
| Building a simple and clean interface | The `pico.css` [library](https://picocss.com/)                                    | `pico.css` provides classless version to build elegant interface  |
| Font face for headers                 | The `Grand Hotel` [font](https://fonts.google.com/specimen/Grand+Hotel)           | Suitable for vintage theme                                        |
| Drag drop files                       | The file drag drop api                                                            | Better UX                                                         |
| Filter processing                     | The `WebGL Image Filter` [library](https://github.com/phoboslab/WebGLImageFilter) | A cool library that provides the core functionality               |
| Download image button                 | The `Download.js` [library](https://github.com/rndme/download)                    | For quick implementation of the download button                   |
| Copy Paste skill                      |                                                                                   | For better coding 😀                                                     |

## Understand how it works
### Interface structure
The interface structure is quite clear. You can view the source code to see the html code and some css then. The `pico.css` and font can be served by the cdn. Just copying the code they give you and that is done.

### App structure and flow
Vintagram is designed that at first, you load a photo to the page via drag drop (desktop / laptop usage) or the upload photo button (smartphones, ...). The photo then get displayed and clicking on the filter image will apply that effect. The download button is for saving image or use can use save image feature from the browser.

### Dragdroping file
This is straightfoward as you'll just have to implement the listeners for the `dragover` and `drop` events. For quick building, I listen on the whole viewport, that is, the `html` element. The `handleDrop` function handles the droping while `handleDragOver` funtion is just for preventing the default action from the browser. Look at the code

```javascript
function handleDrop(ev) {
    ev.preventDefault();

    if (ev.dataTransfer.files && ev.dataTransfer.files.length == 1) {
        let [f] = ev.dataTransfer.files;

        loadFile(f);
    }
}
```
So to extract the dropped file, you query the `ev.dataTransfer.files` property. The `length` is just for ensuring that user only drop 1 image at a time. Also remember to prevent the default action as well. After that the `loadFile` function will handle the file. 

You can infer the code for the upload photo button
```javascript
function handleFileChange(ev) {
    ev.preventDefault();

    if (ev.target.files && ev.target.files.length == 1) {
        let [f] = ev.target.files;

        loadFile(f)
    }
}
```
to handle this situation. This time, to get the file, you query the `ev.target.files` property rather than `ev.dataTransfer.files` above and the event you need to listen to here is  `change` event from the hidden file input of the page. Again, the `loadFile` function is called to do the job.

### Loading the image
In order to load and display the image, I maintain 2 image variable: the `display` variable for displaying result and the `inner` variable for handling loading behind the scene. Also, `inner` is for checking if the given file is a valid image (also for keeping the original version of the image). That's why I listen to the `onload` and `onerror` event from `inner`. There are also two url variable, `current` and `received` to maintain the current url and the one being received.

If a valid image is loaded, discard the `current` url and replace it with the `received` one. If an invalid image is loaded then discard the `received` one. In the case of a valid image, we also display it by passing the url to the `display` image and from the usage of the `webgl-image-filter`, we have to create a new `filter` variable (this requires a canvas with the width and height of the image) and hence the code

```javascript
inner.onload = function () {
    let canvas = document.createElement('canvas');

    canvas.width = inner.naturalWidth;
    canvas.height = inner.naturalHeight;

    filter = new WebGLImageFilter({ canvas });
    display.src = this.src;

    if (current)
        URL.revokeObjectURL(current);

    current = received;
    received = null;
}
```

We are good to go with loading the image then.

### Using `WebGLImageFilter`
The main point of the `webgl-image-filter` library is to create a filter, which we did in the `onload` listener, then call `addFilter` function for each filter you want to add. There is also the `reset` function in case you want to return to the beginning (will clear all the added filters) and finally, using `apply` to apply the filter to the image. The result will be drawn to the canvas given to the filter in the listener above. After that, you can use the data from the canvas. Here is the example of the `filter` variable usage

```javascript
filter.reset()

filter.addFilter('hue', 15);
filter.addFilter('saturation', 0.2);
filter.addFilter('brightness', 0.2);
filter.addFilter('contrast', 0.2);

filter.apply(theImage).toDataURL(); // get the data url from the drawn canvas
```

I find this library kinda cool because the author provided some useful filters that you can use immediately. So rather than knowing how to use hue, saturation, brightness, contrast,... properly, you can use instantly use the polaroid effect with

```javascript
filter.reset()

filter.addFilter('polaroid'); // cool!!!

filter.apply(theImage).toDataURL(); // get the data url from the drawn canvas
```

This article is mean to show you how to build an simple app like vintagram so I won't dig deeper into how to build filters, but rather focus on using the library and the presets. You will have to find more about filters in another articles or try building one.

### Applying the filter
The idea here is simple: using a preset and transfer the data url to the `display` image. I build some filter with associated  name in the `presets` variable so you can use them easily by calling the `applyPreset` function which will find and apply the filter as following code shown

```javascript
function applyPreset(name) {
    if (!filter || !(name in presets))
        return;

    filter.reset();
    presets[name](filter);
    display.src = filter.apply(inner).toDataURL();
}
```

### Download the image
This is quite simple: use the `download` function provided by the `download.js` library through the `downloadImage` wrapper
```javascript
function downloadImage() {
    if (!filter)
        return;

    download(display.src, "vintagram.png");
}
```

Well, and that's vintagram. Hope you enjoy the app, the article and learn something new. Have a nice day then 😊