Create a coloring book in canvas: Developed an app that you can create and enjoy your own original coloring from an image.
Tags: canvas, webdev, nuxt
Published: 2021-08-23 18:26:30

Hey guys,

I developed an app that you can create and enjoy your own original coloring book from an image.

Look this!

![Angry](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uwlhx47dxrwv4gi8xanm.jpg)

You can also play coloring in this app.

[![Image from Gyazo](https://i.gyazo.com/604295b7558d5d63d1031e95465cb29c.gif)](https://gyazo.com/604295b7558d5d63d1031e95465cb29c) 

The part about turning the image into a coloring book is almost the same as [this article](https://dev.to/yuikoito/using-opencv-developed-a-web-app-to-convert-images-to-manga-style-23p0) I wrote before.

So, in this article, I will explain how to develop this coloring functions using canvas.

DEMO→https://nurie-maker.com/
github→https://github.com/yuikoito/nurie-creator

※I'm using Nuxt.js here. Please replace it as necessary.

## Draw a background image on the canvas

First, we need to prepare a canvas element and draw a background image on the canvas.

It takes a little time to draw the background image on the canvas, and I want to make sure to emit loading during that time.
Therefore, I wait here for the image to be fully loaded in Promise.

```javascript
<script>
export default {
  async asyncData({ params }) {
    return {
      url: `${process.env.BASE_URL}/nurie/${params.id}`,
      image: `${process.env.AWS_IMAGE_URL}/nurie/${params.id}.jpg`,
      twitterImage: `${process.env.AWS_IMAGE_URL}/nurie/${params.id}.jpg`,
    }
  },
  data() {
    return {
      canvas: null,
      ctx: null,
      noPicture: require('@/assets/img/noPic.png'),
      overlay: true,
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    async init() {
      this.canvas = this.$refs.canvas
      this.ctx = this.canvas.getContext('2d')
      const wrapper = this.$refs.wrapper
      await this.loadImage(this.image).then((res) => {
        const scale = wrapper.clientWidth / res.naturalWidth
        this.canvas.width = res.naturalWidth * scale
        this.canvas.height = res.naturalHeight * scale
        this.ctx.drawImage(res, 0, 0, this.canvas.width, this.canvas.height)
      })
      this.overlay = false
    },
    loadImage(src) {
      return new Promise((resolve) => {
        const img = new Image()
        img.src = src
        img.onload = () => resolve(img)
        img.onerror = () => {
          img.src = this.noPicture
        }
      })
    },
  }
}
</script>
```

Now we can draw an image on canvas.

## Use Retina Canvas Support

If you look at the image drawn above, you will see that the resolution is a little rough, so we will fix that part.

Specifically, when loading an image in canvas, load it at twice the size and set the canvas style to 1/2. The actual canvas drawing size will be the same as above, but the resolution will be finer and the image will be drawn beautifully.

```javascript
      await this.loadImage(this.image).then((res) => {
        this.ctx.scale(2, 2)
        const scale = wrapper.clientWidth / res.naturalWidth
        this.canvas.width = res.naturalWidth * scale * 2
        this.canvas.height = res.naturalHeight * scale * 2
        this.canvas.style.width = this.canvas.width / 2 + 'px'
        this.canvas.style.height = this.canvas.height / 2 + 'px'
        this.ctx.drawImage(res, 0, 0, this.canvas.width, this.canvas.height)
      })
```

## Implement a drawing function.

When a user clicks on the screen, the event can be detected by ``mousedown`` on a PC, ``mousemove`` for dragging, and ``mouseup`` (or ``mouseout``) for the timing when the dragging is finished.

```javascript
<canvas
  ref="canvas"
  @mousedown.prevent="dragStart"
  @mouseup.prevent="dragEnd"
  @mouseout.prevent="dragEnd"
  @mousemove.prevent="draw"
>
</canvas>
```

※The ``lineWidth`` and ``currentColor`` can be changed by the user.

```javascript
    dragStart() {
      this.ctx.beginPath()
      this.isDrag = true
    },
    draw(e) {
      const x = (e.clientX - this.canvas.getBoundingClientRect().left) * 2
      const y = (e.clientY - this.canvas.getBoundingClientRect().top) * 2
      if (!this.isDrag) {
        return
      }
      this.ctx.lineCap = 'round'
      this.ctx.lineJoin = 'round'
      this.ctx.lineWidth = this.lineWidth
      this.ctx.strokeStyle = this.currentColor
      if (this.lastPosition.x === null || this.lastPosition.y === null) {
        this.ctx.moveTo(x, y)
      } else {
        this.ctx.moveTo(this.lastPosition.x, this.lastPosition.y)
      }
      this.ctx.lineTo(x, y)
      this.ctx.stroke()
      this.lastPosition.x = x
      this.lastPosition.y = y
    },
    dragEnd() {
      this.ctx.closePath()
      this.isDrag = false
      this.lastPosition.x = null
      this.lastPosition.y = null
      this.isErase = false
    },
```

## Change color of the line

I used [vue-color](https://www.npmjs.com/package/vue-color) to change color.

```
yarn add vue-color
```

Create ``vueColor.js`` under plugins.
In this case, we are using a design called ``Sketch``, so we are only loading Sketch.

```javascript
import Vue from 'vue'
import { Sketch } from 'vue-color'

Vue.component('Sketch', Sketch)
```

Add in nuxt.config.js.

```javascript
  plugins: [{ src: '@/plugins/vueColor.js', mode: 'client' }],
```

Then draw this component in the page.
The drawing is fine as it is, but you will get a warning, so move it inside ``<client-only>``.

```javascript
  <client-only>
    <Sketch :value="colors" @input="updateValue"></Sketch>
  </client-only>
```

Set a default value for colors.
When a user change color, you will get the color by the following function. 

```javascript
    updateValue(e) {
      this.currentColor = e.hex
    },
```

This library is easy and useful!

## Download the canvas

Then, we will implement a function to download the canvas when the button is clicked.

```javascript
    download() {
      let link = document.createElement('a')
      link.href = this.canvas.toDataURL('image/jpeg')
      link.download = 'nurie-' + new Date().getTime() + '.jpg'
      link.click()
    },
```

... However, we can't download canvas at this point.

## Execution of toDataURL fails due to CORS.

In fact, this time, for the background image of canvas, I was referring to the URL stored in s3 from uuid, so the error occurred due to CORS in that part.

```javascript
image: `${process.env.AWS_IMAGE_URL}/nurie/${params.id}.jpg`
```

Since the image is loaded as shown above, the domain of the current URL and the domain of the image URL are different, causing the following error.

```
tainted canvases may not be exported
```

Then I used [@nuxtjs/proxy](https://github.com/nuxt-community/proxy-module) to fix this problem.

```
yarn add @nuxtjs/proxy
```

Add ``nuxt.config.js``.

```javascript
  modules: ['@nuxtjs/dotenv', '@nuxtjs/proxy'],
  proxy: {
    '/nurie/': {
      target: AWS_IMAGE_URL,
      changeOrigin: true,
      secure: false,
    },
  },
```

Now, you can replace ``${process.env.AWS_IMAGE_URL}/nurie/${params.id}.jpg`` with the URL of your site.
Change the image part to the following.

```javascript
image: `${process.env.BASE_URL}/nurie/${params.id}.jpg`,
```

## Enable to play coloring on the phone

As it is, we can't do coloring on the phone, so we will also add events for the phone.
On the phone, you can detect events with ``touchstart``, ``touchmove``, and ``touchend``.

```javascript
<canvas
  ref="canvas"
  @mousedown.prevent="dragStart"
  @touchstart.prevent="dragStart"
  @touchend.prevent="dragEnd"
  @mouseup.prevent="dragEnd"
  @mouseout.prevent="dragEnd"
  @mousemove.prevent="draw"
  @touchmove.prevent="spDraw"
>
</canvas>
```

The events that can be detected on a smartphone differ from those on a PC only for the part where the line is drawn, so adjust the following.
The point is that on a PC you can get the location of the touch by ``e.clientX``, etc., but on a smartphone it is ``e.changedTouches[0].clientX``. (In the case of a single finger)

```javascript
    spDraw(e) {
      if (e.changedTouches.length == 1) {
        this.draw(e.changedTouches[0])
      }
    },
```

Finish!

## That's it!
Thanks for reading.
I am very happy if you enjoy it!

🍎🍎🍎🍎🍎🍎

Please send me a message if you need.

yuiko.dev@gmail.com
https://twitter.com/yui_active

🍎🍎🍎🍎🍎🍎