最終效果大家可以試一下:https://areocrystal.github.io/drawbydice/點擊打開鏈接,上傳圖片後記得要點一下那個start(那個滑動條最好不要拉到最左邊去了,雖然渲染出的最接近原圖,但cpu不好的同學瀏覽器會卡死)。
其實這個個人小玩意早在幾個月前就做好了,主要就是用大小不一的點(或其他)來重新繪製圖片。
首先在一個閉包中進行初始化,並進行原生代碼的封裝
(function(doc){
var concatImg = {
init(){
var getId = doc.getElementById.bind(doc)
this.file = getId('uoploadImg')
this.cas = getId('canvas')
this.canvas = doc.createElement('canvas')
this.ctx = this.cas.getContext('2d')
this.ctx2 = this.canvas.getContext('2d')
this.img = new Image()
this.fr = new FileReader()
this.range = getId('range')
this.autoCalc = getId('autocalc')
this.trigger = getId('start')
this.colour = getId('colour')
this.path = getId('pathinfo')
this.selection = getId('selection')
this.maxUnitPixel = +this.range.value
this.unitPixelColor = this.colour.value
this.isAutoCalc = this.autoCalc.checked
this.selectionValue = this.selection.value
this.pixelCollection =
[...'1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+{}|:"<>?-=[]\\;\',./`~']
this.pixelCollectionLen = this.pixelCollection.length
this.drawDetail = this.primitiveDrawDetail
this.bindEvent.call(this)
},
}
}(document)
這裏是表單控制
<div class="row"> <div class="col-lg-4"> <div class="input-group"> <section class="input-group-btn"> <button class="btn btn-success" type="button" id="start">Start</button> </section> <input type="text" class="form-control" placeholder="name of the file:" id="pathinfo"> <input type="file" id="uoploadImg" class="form-control" accept="image/*"> <span class="input-group-addon">auto calculate: <input type="checkbox" aria-label="..." id="autocalc"> </span> </div> <select id="selection" class="form-control"> <option value="1">dot</option> <option value="2">character</option> <option value="3">brick</option> </select> </div> <div class="col-lg-2"> long of side setting: <input type="range" max=100 min=1 class="form-control" id="range"> </div> <div class="col-lg-2"> color setting: <input type="color" class="form-control" id="colour" value="#ffffff"> </div> </div> <canvas id="canvas"></canvas>
由於此處有多個input輸入框來控制,一個個綁定“change”事件實在過於繁瑣。這裏可以利用事件委託給document綁定“onchange”事件,這樣事件就能依次傳遞到以下各個input之中,並且涉及文件上傳就要使用fileReader對象,當中有文件讀取與圖片上傳的兩個異步操作,可以使用Promise來進行初步的封裝,整體代碼如下:
bindEvent(){ doc.addEventListener('change', function (e) { switch (e.target) { case this.range: this.maxUnitPixel = +e.target.value break case this.colour: this.unitPixelColor = e.target.value break case this.selection: this.selectionValue = e.target.value this.drawDetail = this.primitiveDrawDetail break case this.autoCalc: this.isAutoCalc = e.target.checked break case this.file: let fe = e.target.files, primitivePath = e.target.value this.path.value = primitivePath.substr(primitivePath.search(/[\\\/][^\\\/]+$/) + 1) if (fe.length > 0 && /image\/\w{3,4}/.test(fe[0].type)) { this.loadFileReader(fe[0]).then(progressEvent => { return this.loadImage(progressEvent) }).then(() => { this.cw = this.cas.width = this.canvas.width = this.img.width this.ch = this.cas.height = this.canvas.height = this.img.height this.trigger.onclick = () => this.getImgSegments() }) } break } }.bind(this), false) }
loadFileReader(f){ return new Promise(resolve => { this.fr.readAsDataURL(f) this.fr.onload = (progressEvent) => resolve(progressEvent) }) }, loadImage(progressEvent){ return new Promise(resolve => { this.img.src = progressEvent.target.result this.img.onload = () => resolve() }) },
接下來可以把圖片分割而成的每個最小單元都當作一個對象:
class imgElements { constructor(x, y) { this._x = x this._y = y this.edge = concatImg.unitImgEdge this.r = this.edge / 6 this.drawElement() } drawElement() { concatImg.ctx2.drawImage( concatImg.img, this._x, this._y, this.edge, this.edge, this._x, this._y, this.edge, this.edge) this.acquireGrayscale() } acquireGrayscale() { var imgData = concatImg.ctx2.getImageData( this._x, this._y, this.edge, this.edge).data, average = 0, len = imgData.length, count = 0 for (let i = 0; i < len; i += 4) { average += (imgData[i] + imgData[i + 1] + imgData[i + 2]) / 3 count++ } this.grayscale = average / count concatImg.drawUnit(this) } }
值得一提的是上面的acquireGrayscale方法來獲取圖片的灰度值,當中使用了canvas的getImageData的方法,所得到的imgData是一個二進制數組(ArrayBuffer),這個數組就是所得圖像數據非常龐大,當中成員每四個爲一組,分別表示圖像的rgba值,由於a(alpha)是沒有用的,所以再接下來的for循壞中要+4,來把rgb三個值進行混合來取平均數。接着進入封裝在concatImg.drawUnit來畫出每一個單元:
drawUnit(_self){ var step = _self.grayscale / 25.6 | 0 this.ctx2.fillStyle = '#000' this.ctx2.fillRect(_self._x, _self._y, _self.edge, _self.edge) for (let i = dice[step].length - 1; i > -1 ; i--) { for (let j = dice[step][i].length - 1; j > -1 ; j--) { dice[step][i][j] === 1 && this.drawDetail(_self._x, _self._y, _self.r, i, j) } } },
很容易知道所得的灰度平均值肯定不會超過0xff,也就是256,所以以25.6來劃分成十份來取整,來根據這個dice這個三維數組來進行繪製。
最後的效果圖就是用canvas來繪製canvas,寫的比較倉促,可能一些小的細節沒有到位。