這個遊戲的靈感是源自兒子的學校課本裡面,有個叫不插電程式課的書本裡面得到的。書裡面有一類型的題目就是把圖畫裡的數字填上對應的色塊,全部填滿後就能夠得到一張馬賽克風格的圖案。
利用這個概念,再結合之前製作的繪文字刮刮樂的分類資料,使用emoji製作了一個馬賽克像素風的填圖小遊戲。
底下是這個小遊戲的製作技術重點。
把圖片馬賽克化的原理很簡單,簡單的講就是:
對所有馬賽克圖塊作同樣處理,這樣就完成了把一張圖片馬賽克化了,接下來底下說明一些實作細節。
1、繪文字填圖的馬賽克分為大中小三種尺寸,為了在手機上最後呈現的畫面尺寸大小適中,把canvas的大小設定為400x400px。為了方便計算,三種馬賽克方塊的大小分別為:16x16、25x25及40x40,16、25及40正好都可以整除400。在遊戲主選單最下方選擇馬賽克尺寸後,就會以這個大小對400x400尺寸的原圖作馬賽克化的切割。如果選擇最小尺寸的馬賽克,則原圖被切割成25x25個圖塊。如果選擇中等尺寸的馬賽克,則原圖被切割成16x16個圖塊。若選擇最大尺寸的馬賽克,則原圖被切割成10x10個圖塊。
2、計算每個馬賽克圖塊的平均顏色的方法很直接,就是對圖塊中每一個Pixel加總後,再除以Pixel總數得到平均值。需注意的是,加總再求平均的計算是要對顏色的RGB值各別作。最後把得到的平均的RGB值,再組成一個新的顏色作為整個馬賽克圖塊的顏色。
function sumRgb(data, x, y, cw, ch) { var rgb = [0, 0, 0]; for (var i = 0; i < cw; i++) { for (var j = 0; j < ch; j++) { var idx = 4 * ((x + i) + (y + j) * canvas.width); rgb[0] += data[idx]; rgb[1] += data[idx + 1]; rgb[2] += data[idx + 2]; } } for (var i = 0; i < rgb.length; i++) { rgb[i] = clamp(rgb[i] / (cw * ch)); } return rgb; }
data是透過canvas的context2d.getImageData得到的,而cw及ch是馬賽克尺寸,一般cw=ch為正方形。而x及y是馬賽克圖塊在原圖中的左上角座標。注意在sumRgb最後對各別RGB值求平均時還作了一個clamp的動作。這個clamp其實是一個類似四拾五入的動作,目的是為了要減少最後整個原圖馬賽克化的顏色數量,讓調色盤的顏色數不要那麼多。
var CLAMP_EMOJI = 32; var CLAMP_NUM = CLAMP_EMOJI; function clamp(n) { return CLAMP_NUM * Math.round(n / CLAMP_NUM); }
3、整張原圖馬賽克化後,就可以根據所有馬賽克圖塊的顏色建立一個調色盤。再根據這個調色盤再把馬賽克化的原圖,轉換成數字謎題圖。
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); var col = canvas.width / cw; var row = canvas.height / ch; var colors = []; for (var i = 0; i < col; i++) { for (var j = 0; j < row; j++) { var x = cw * i; var y = ch * j; var rgb = sumRgb(imgData.data, x, y, cw, ch); ctx.fillStyle = 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'; var index = colors.indexOf(ctx.fillStyle); if (-1 == index) { colors.push(ctx.fillStyle); } } }