A Pen by James Delibas on CodePen.
Created
June 30, 2016 11:11
-
-
Save jdelibas/2f8db9116cd0e01b6200c7dbd4dbf21e to your computer and use it in GitHub Desktop.
Monochrome Icon Editor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="row center-xs"> | |
<div class="col-xs-6"> | |
<h3> Monochrome Icon Editor </h3> | |
</div> | |
</div> | |
<div class="row center-xs" id="demo"> | |
<div class="col-xs-12 col-md-3"> | |
<form v-on:submit.prevent> | |
<fieldset> | |
<legend>Filters</legend> | |
<p v-show="!filters"> | |
Load an image to apply filters too | |
</p> | |
<p v-show="filters"> | |
<select v-model="settings.selected" v-on:change="resetChanges()"> | |
<option v-for="option in settings.options" v-bind:value="option.value"> | |
{{ option.text }} | |
</option> | |
</select> | |
</p> | |
<div v-show="settings.selected === 'grayscale'"> | |
<p> | |
<button class="button--sm" v-on:click="applyGrayscale">Grayscale</button> | |
</p> | |
<p> | |
<button class="button--sm" v-on:click="resetChanges()">Reset</button> | |
<button class="button--sm" v-on:click="commitFilter('grayscale')">Apply</button> | |
</p> | |
</div> | |
<div v-show="settings.selected === 'bow'"> | |
<p class="form--inline"> | |
<label for="threshold" class="tooltip--left" data-hint="Range">Threshold:</label> | |
<input type="range" min="0" max="100" v-model="filtering.bow.threshold" v-on:change="applyBow(filtering.bow.threshold)"/> | |
<label> {{ filtering.bow.threshold }} </label> | |
</p> | |
<p> | |
<button class="button--sm" v-on:click="resetChanges()">Reset</button> | |
<button class="button--sm" v-on:click="commitFilter('b/w')">Apply</button> | |
</p> | |
</div> | |
<div v-show="settings.selected === 'invert'"> | |
<p> | |
<button class="button--sm" v-on:click="applyInvert">Invert</button> | |
</p> | |
<p> | |
<button class="button--sm" v-on:click="resetChanges()">Reset</button> | |
<button class="button--sm" v-on:click="commitFilter('invert')">Apply</button> | |
</p> | |
</div> | |
<div v-show="settings.selected === 'dither'"> | |
<p class="form--inline"> | |
<label for="threshold" class="tooltip--left"> Type:</label> | |
</p> | |
<p> | |
<input type="radio" v-on:change="applyDither()" v-model="filtering.dither.type" value="none"> <label for="none">none</label> | |
<input type="radio" v-on:change="applyDither()" v-model="filtering.dither.type" value="bayer"> <label for="bayer">bayer</label> | |
</p> | |
<p> | |
<input type="radio" v-on:change="applyDither()" v-model="filtering.dither.type" value="floydsteinberg"> <label for="floydsteinberg">floydsteinberg</label> | |
<input type="radio" v-on:change="applyDither()" v-model="filtering.dither.type" value="batkinson"> <label for="batkinson">batkinson</label> | |
</p> | |
<p class="form--inline"> | |
<label for="threshold" class="tooltip--left" data-hint="Range">Threshold:</label> | |
<input type="range" min="0" max="255" v-model="filtering.dither.threshold" v-on:change="applyDither()"/> | |
<label> {{ filtering.dither.threshold }} </label> | |
</p> | |
<p> | |
<button class="button--sm" v-on:click="resetChanges()">Reset</button> | |
<button class="button--sm" v-on:click="commitFilter('dither')">Apply</button> | |
</p> | |
</div> | |
<div v-show="settings.selected === 'canny'"> | |
<p> | |
<button class="button--sm" v-on:click="applyCanny">Canny</button> | |
</p> | |
<p> | |
<button class="button--sm" v-on:click="resetChanges()">Reset</button> | |
<button class="button--sm" v-on:click="commitFilter('canny')">Apply</button> | |
</p> | |
</div> | |
</fieldset> | |
</form> | |
</div> | |
<div class="col-xs-12 col-md-4"> | |
<form v-on:submit.prevent> | |
<fieldset> | |
<legend>Preview</legend> | |
<input type="file" v-model="file" accept="image/*" v-on:change="onFile"> | |
<canvas v-el:canvas width="92" height="92"></canvas> | |
</fieldset> | |
</form> | |
</div> | |
<div class="col-xs-12 col-md-3"> | |
<form v-on:submit.prevent> | |
<fieldset> | |
<legend>History</legend> | |
<div class="history"> | |
<p v-show="!filters || !filters.history.length"> No history to show </p> | |
<p v-bind:class="{ 'active': filters.historyCurrent === $index}" v-for="history in filters.history" track-by="$index" v-on:click="setHistory($index)"> | |
{{ $index + 1 }}: {{ history.title }} | |
</p> | |
</div> | |
</fieldset> | |
</form> | |
</div> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function() { | |
var demo = new Vue({ | |
el: '#demo', | |
data: { | |
settings: { | |
selected: '', | |
options: [{ | |
text: 'none', | |
value: '' | |
}, { | |
text: 'Grayscale', | |
value: 'grayscale' | |
}, { | |
text: 'B/W', | |
value: 'bow' | |
}, { | |
text: 'Invert', | |
value: 'invert' | |
}, { | |
text: 'Dither', | |
value: 'dither' | |
}, { | |
text: 'Canny', | |
value: 'canny' | |
}] | |
}, | |
filtering: { | |
bow: { | |
threshold: 0 | |
}, | |
dither: { | |
threshold: 125, | |
type: 'none' | |
} | |
}, | |
file: null, | |
imageURL: null, | |
ctx: null, | |
filters: null | |
}, | |
methods: { | |
onFile: function(e) { | |
e.preventDefault(); | |
this.ctx = this.$els.canvas.getContext('2d'); | |
this.imageURL = createURL(e.target.files[0]); | |
loadAndDrawImage(this.ctx, this.imageURL, function() { | |
this.filters = new Filters(this.$els.canvas); | |
}.bind(this)); | |
}, | |
setHistory: function(index) { | |
this.filters.setHistory(index); | |
}, | |
resetChanges: function() { | |
this.filtering.bow.threshold = 0; | |
this.filters.resetContext(); | |
}, | |
applyGrayscale: function(value) { | |
this.filters.grayscale(value / 100); | |
}, | |
applyBow: function(value) { | |
this.filters.bow(value / 100); | |
}, | |
applyInvert: function() { | |
this.filters.invert(); | |
}, | |
applyDither: function() { | |
this.filters.dither( | |
this.filtering.dither.threshold, | |
this.filtering.dither.type | |
); | |
}, | |
applyCanny: function() { | |
this.filters.canny(); | |
}, | |
commitFilter: function(name) { | |
this.filters.save('Filters - ' + name); | |
this.settings.selected = ''; | |
} | |
} | |
}); | |
function loadAndDrawImage(ctx, url, done) { | |
var image = new Image(); | |
image.onload = function() { | |
ctx.clearRect(0, 0, 92, 92); | |
ctx.drawImage(image, 0, 0, 92, 92); | |
done(); | |
} | |
image.src = url; | |
} | |
function createURL(file) { | |
window.URL = window.URL || window.webkitURL; | |
return window.URL.createObjectURL(new Blob([file])); | |
} | |
/* | |
Filters class | |
*/ | |
function Filters(canvas) { | |
this.canvas = canvas; | |
this.ctx = canvas.getContext('2d'); | |
this.pixels = this.ctx.getImageData(0, 0, canvas.width, canvas.height); | |
this.history = [{ | |
title: 'Load image', | |
data: this.cloneImageData(this.pixels) | |
}]; | |
this.historyCurrent = 0; | |
}; | |
Filters.prototype.setHistory = function(index) { | |
this.historyCurrent = index; | |
this.ctx.putImageData(this.history[index].data, 0, 0); | |
}; | |
Filters.prototype.cloneImageData = function(src) { | |
var clone = this.ctx.createImageData(src.width, src.height); | |
clone.data.set(src.data); | |
return clone; | |
}; | |
Filters.prototype.apply = function(data) { | |
this.ctx.putImageData(data, 0, 0); | |
this.resetData(); | |
}; | |
Filters.prototype.save = function(name) { | |
this.pixels = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); | |
this.history.push({ | |
title: name, | |
data: this.cloneImageData(this.pixels) | |
}); | |
this.historyCurrent = this.history.length - 1; | |
} | |
Filters.prototype.resetData = function() { | |
this.pixels = this.cloneImageData(this.history[this.history.length - 1].data); | |
} | |
Filters.prototype.resetContext = function() { | |
this.resetData(); | |
this.apply(this.pixels); | |
} | |
Filters.prototype.grayscale = function(threshold) { | |
//http://www.html5rocks.com/en/tutorials/canvas/imagefilters/ | |
var imgData = this.cloneImageData(this.pixels); | |
var d = imgData.data; | |
for (var i = 0; i < d.length; i += 4) { | |
var r = d[i]; | |
var g = d[i + 1]; | |
var b = d[i + 2]; | |
// CIE luminance for the RGB | |
// The human eye is bad at seeing red | |
// and blue, so we de-emphasize them. | |
//(ptr[0]*53 + ptr[1]*184 + ptr[2]*18) / (255) | |
var v = 0.2126 * r + 0.7152 * g + 0.0722 * b; | |
d[i] = d[i + 1] = d[i + 2] = v | |
} | |
this.apply(imgData); | |
}; | |
Filters.prototype.bow = function(threshold) { | |
var imgData = this.cloneImageData(this.pixels); | |
var d = imgData.data; | |
for (var i = 0; i < d.length; i += 4) { | |
var darkness = 1 - (0.299 * d[i] + | |
0.587 * d[i + 1] + | |
0.114 * d[i + 2]) / 255; | |
d[4] = 255; | |
if (darkness < threshold) { | |
d[i] = d[i + 1] = d[i + 2] = 255; | |
} else { | |
d[i] = d[i + 1] = d[i + 2] = 0; | |
} | |
} | |
this.apply(imgData); | |
}; | |
Filters.prototype.invert = function() { | |
var imgData = this.cloneImageData(this.pixels); | |
var d = imgData.data; | |
for (var i = 0; i < d.length; i += 4) { | |
d[i] = 255 - d[i]; | |
d[i + 1] = 255 - d[i + 1]; | |
d[i + 2] = 255 - d[i + 2]; | |
} | |
this.apply(imgData); | |
}; | |
Filters.prototype.dither = function(threshold, type) { | |
//https://github.com/meemoo/iframework/blob/gh-pages/src/nodes/image-monochrome-worker.js | |
var bayerThresholdMap = [ | |
[15, 135, 45, 165], | |
[195, 75, 225, 105], | |
[60, 180, 30, 150], | |
[240, 120, 210, 90] | |
]; | |
var lumR = []; | |
var lumG = []; | |
var lumB = []; | |
for (var i = 0; i < 256; i++) { | |
lumR[i] = i * 0.299; | |
lumG[i] = i * 0.587; | |
lumB[i] = i * 0.114; | |
} | |
var imageData = this.cloneImageData(this.pixels); | |
var imageDataLength = imageData.data.length; | |
// Greyscale luminance (sets r pixels to luminance of rgb) | |
for (var i = 0; i <= imageDataLength; i += 4) { | |
imageData.data[i] = Math.floor(lumR[imageData.data[i]] + lumG[imageData.data[i + 1]] + lumB[imageData.data[i + 2]]); | |
} | |
var w = imageData.width; | |
var newPixel, err; | |
for (var currentPixel = 0; currentPixel <= imageDataLength; currentPixel += 4) { | |
if (type === "none") { | |
// No dithering | |
imageData.data[currentPixel] = imageData.data[currentPixel] < threshold ? 0 : 255; | |
} else if (type === "bayer") { | |
// 4x4 Bayer ordered dithering algorithm | |
var x = currentPixel / 4 % w; | |
var y = Math.floor(currentPixel / 4 / w); | |
var map = Math.floor((imageData.data[currentPixel] + bayerThresholdMap[x % 4][y % 4]) / 2); | |
imageData.data[currentPixel] = (map < threshold) ? 0 : 255; | |
} else if (type === "floydsteinberg") { | |
// Floyd–Steinberg dithering algorithm | |
newPixel = imageData.data[currentPixel] < 129 ? 0 : 255; | |
err = Math.floor((imageData.data[currentPixel] - newPixel) / 16); | |
imageData.data[currentPixel] = newPixel; | |
imageData.data[currentPixel + 4] += err * 7; | |
imageData.data[currentPixel + 4 * w - 4] += err * 3; | |
imageData.data[currentPixel + 4 * w] += err * 5; | |
imageData.data[currentPixel + 4 * w + 4] += err * 1; | |
} else { | |
// Bill Atkinson's dithering algorithm | |
newPixel = imageData.data[currentPixel] < 129 ? 0 : 255; | |
err = Math.floor((imageData.data[currentPixel] - newPixel) / 8); | |
imageData.data[currentPixel] = newPixel; | |
imageData.data[currentPixel + 4] += err; | |
imageData.data[currentPixel + 8] += err; | |
imageData.data[currentPixel + 4 * w - 4] += err; | |
imageData.data[currentPixel + 4 * w] += err; | |
imageData.data[currentPixel + 4 * w + 4] += err; | |
imageData.data[currentPixel + 8 * w] += err; | |
} | |
// Set g and b pixels equal to r | |
imageData.data[currentPixel + 1] = imageData.data[currentPixel + 2] = imageData.data[currentPixel]; | |
} | |
this.apply(imageData); | |
}; | |
Filters.prototype.canny = function(options) { | |
var imageData = new CannyEdgeDetecotor({ | |
imageData: this.cloneImageData(this.pixels), | |
GaussianSiguma: 4, | |
GaussianSize: 5, | |
hysteresisHeigh: 100, | |
hysteresisLow: 30, | |
isConvertGrayScale: true, | |
isApplyGaussianFilter: true, | |
isApplySobelFilter: true, | |
isHysteresisThreshold: true | |
}); | |
this.apply(imageData); | |
} | |
//http://jsdo.it/Hiroto.Takahashi/mvmP | |
/** | |
* CannyEdgeDetector Class | |
*/ | |
var CannyEdgeDetecotor = function(args) { | |
this.imageData = args.imageData; | |
//params | |
this.GaussianSiguma = args.GaussianSiguma || 4; | |
this.GaussianSize = args.GaussianSize || 5; | |
this.hysteresisHeigh = args.hysteresisHeigh || 100; | |
this.hysteresisLow = args.hysteresisLow || 30; | |
//実行制御 | |
this.isConvertGrayScale = args.isConvertGrayScale != null ? args.isConvertGrayScale : true; | |
this.isApplyGaussianFilter = args.isApplyGaussianFilter != null ? args.isApplyGaussianFilter : true; | |
this.isApplySobelFilter = args.isApplySobelFilter != null ? args.isApplySobelFilter : true; | |
if (this.isApplySobelFilter == true) { | |
this.isHysteresisThreshold = args.isHysteresisThreshold != null ? args.isHysteresisThreshold : true; | |
} else { | |
this.isHysteresisThreshold = false; | |
} | |
this.color; //color matrix | |
/** | |
* this.convertGrayScaleでtrueに変更。 | |
* this.colorMatrixToImageData および this.imageDataTocolorMatrix は、この値により処理を分岐。 | |
* trueの場合: this.color = [], falseの場合: this.color={r:[], g:[], b[]} として扱う。 | |
* グレースケールおよびカラー、両方とも可能なfilterの場合は処理を分岐させる。 | |
*/ | |
this.isGrayScale = false; | |
//for sobel filter | |
this.edge; //edge matrix | |
this.edgeDir; //edgeDir matrix | |
this.edgeHeighVal = 255; | |
this.edgeLowVal = 0; | |
return this.init(); | |
} | |
CannyEdgeDetecotor.prototype = { | |
init: function() { | |
if (this.isConvertGrayScale) this.convertGrayScale(this.imageData); | |
if (this.isApplyGaussianFilter) this.applyGaussianFilter(this.imageData, this.GaussianSiguma, this.GaussianSize); | |
if (this.isApplySobelFilter) this.applySobelFilter(this.imageData); | |
if (this.isHysteresisThreshold) this.hysteresisThreshold(this.imageData, this.hysteresisHeigh, this.hysteresisLow); | |
return this.imageData; | |
}, | |
convertGrayScale: function(imageData) { | |
this.isGrayScale = true; | |
var data = imageData.data, | |
pixels = data.length / 4; | |
for (var i = 0, l = pixels; i < l; i++) { | |
var r = data[i * 4], | |
g = data[i * 4 + 1], | |
b = data[i * 4 + 2]; | |
var g = parseInt((11 * r + 16 * g + 5 * b) / 32); | |
data[i * 4] = g; | |
data[i * 4 + 1] = g; | |
data[i * 4 + 2] = g; | |
} | |
}, | |
applyGaussianFilter: function(imageData, siguma, filterW) { | |
//make filter | |
var filter = []; | |
var k = 1 / Math.sqrt(2 * Math.PI) / siguma; | |
for (var x = 0; x < filterW; x++) { | |
filter[x] = []; | |
for (var y = 0; y < filterW; y++) { | |
var _x = x - parseInt(filterW / 2 - 0.5), //中心座標を0に | |
_y = y - parseInt(filterW / 2 - 0.5); | |
filter[x][y] = k * Math.exp(-(_x * _x + _y * _y) / (2 * siguma * siguma)); | |
} | |
} | |
var color = this.imageDataToColorMatrix(imageData); | |
var w = imageData.width, | |
h = imageData.height; | |
//filter | |
for (var x = 0; x < w; x++) { | |
for (var y = 0; y < h; y++) { | |
for (var xf = 0; xf < filterW; xf++) { | |
for (var yf = 0; yf < filterW; yf++) { | |
var _xf = xf - parseInt(filterW / 2 - 0.5), | |
_yf = yf - parseInt(filterW / 2 - 0.5); | |
if ((x + _xf) > -1 && (y + _yf) > -1 && (x + _xf) < w && (y + _yf) < h) { | |
if (this.isGrayScale) { | |
color.origin[x + _xf][y + _yf] += color.copy[x][y] * filter[xf][yf]; | |
} else { | |
color.origin.r[x + _xf][y + _yf] += color.copy.r[x][y] * filter[xf][yf]; | |
color.origin.g[x + _xf][y + _yf] += color.copy.g[x][y] * filter[xf][yf]; | |
color.origin.b[x + _xf][y + _yf] += color.copy.b[x][y] * filter[xf][yf]; | |
} | |
} | |
} | |
} | |
} | |
} | |
this.colorMatrixToImageData(color.origin, imageData, filterW); | |
//override color matrix | |
this.color = color.origin; | |
}, | |
applySobelFilter: function(imageData) { | |
if (!this.isGrayScale) { | |
this.convertGrayScale(imageData); | |
} | |
//filter | |
var filterHx = [ | |
[-1, 0, 1], | |
[-2, 0, 2], | |
[-1, 0, 1] | |
], | |
filterHy = [ | |
[-1, -2, -1], | |
[0, 0, 0], | |
[1, 2, 1] | |
]; | |
var filterHxW = filterHx.length, | |
filterHyW = filterHy.length; | |
var colorHx = this.imageDataToColorMatrix(imageData), | |
colorHy = this.imageDataToColorMatrix(imageData); | |
var color = []; | |
var edge = [], | |
edgeDir = [], | |
rad = []; | |
var w = imageData.width, | |
h = imageData.height; | |
//filter | |
for (var x = 0; x < w; x++) { | |
edge[x] = []; | |
edgeDir[x] = []; | |
rad[x] = []; | |
for (var y = 0; y < h; y++) { | |
//sobel filter | |
for (var xf = 0; xf < filterHxW; xf++) { | |
for (var yf = 0; yf < filterHxW; yf++) { | |
var _xf = xf - parseInt(filterHxW / 2 - 0.5), | |
_yf = yf - parseInt(filterHxW / 2 - 0.5); | |
if ((x + _xf) > -1 && (y + _yf) > -1 && (x + _xf) < w && (y + _yf) < h) { | |
//colorHx | |
colorHx.origin[x][y] += colorHx.copy[x + _xf][y + _yf] * filterHx[xf][yf]; | |
//colorHy | |
colorHy.origin[x][y] += colorHy.copy[x + _xf][y + _yf] * filterHy[xf][yf]; | |
} | |
} | |
} | |
//edge | |
edge[x][y] = Math.sqrt(Math.abs(colorHx.origin[x][y] * colorHx.origin[x][y] - colorHy.origin[x][y] * colorHy.origin[x][y])); | |
//edge dir | |
rad[x][y] = Math.abs(Math.atan2(colorHy.origin[x][y], colorHx.origin[x][y])) * 180 / Math.PI; | |
if (0 < rad[x][y] && rad[x][y] <= 22.5) { | |
// 0 deg | |
edgeDir[x][y] = 0; | |
} else if (22.5 < rad[x][y] && rad[x][y] <= 67.5) { | |
// 45 deg | |
edgeDir[x][y] = 45; | |
} else if (67.5 < rad[x][y] && rad[x][y] <= 112.5) { | |
// 90 deg | |
edgeDir[x][y] = 90; | |
} else if (112.5 < rad[x][y] && rad[x][y] <= 157.5) { | |
// 135 deg | |
edgeDir[x][y] = 135; | |
} else { | |
// 0 deg | |
edgeDir[x][y] = 0; | |
} | |
} | |
} | |
//detect edge | |
for (var x = 0; x < w; x++) { | |
color[x] = []; | |
for (var y = 0; y < h; y++) { | |
if ((x - 1) > -1 && (y - 1) > -1 && (x + 1) < w && (y + 1) < h) { | |
// 0 deg | |
if (edgeDir[x][y] == 0) { | |
if (edge[x][y] > edge[x][y - 1] && edge[x][y] > edge[x][y + 1]) { | |
color[x][y] = edge[x][y]; | |
} else { | |
color[x][y] = 0; | |
} | |
// 45 deg | |
} else if (edgeDir[x][y] == 45) { | |
if (edge[x][y] > edge[x - 1][y - 1] && edge[x][y] > edge[x + 1][y + 1]) { | |
color[x][y] = edge[x][y]; | |
} else { | |
color[x][y] = 0; | |
} | |
// 90 deg | |
} else if (edgeDir[x][y] == 90) { | |
if (edge[x][y] > edge[x - 1][y] && edge[x][y] > edge[x + 1][y]) { | |
color[x][y] = edge[x][y]; | |
} else { | |
color[x][y] = 0; | |
} | |
// 135 deg | |
} else if (edgeDir[x][y] == 135) { | |
if (edge[x][y] > edge[x - 1][y + 1] && edge[x][y] > edge[x + 1][y - 1]) { | |
color[x][y] = edge[x][y]; | |
} else { | |
color[x][y] = 0; | |
} | |
} | |
} else { | |
color[x][y] = 0; | |
} | |
} | |
} | |
//this.colorMatrixToImageData(colorHx.origin, imageData, filterHxW); | |
//this.colorMatrixToImageData(colorHy.origin, imageData, filterHxW); | |
//this.colorMatrixToImageData(edge, imageData, filterHxW); | |
this.colorMatrixToImageData(color, imageData, 1); | |
//override color matrix | |
this.color = color; | |
this.edge = edge; | |
this.edgeDir = edgeDir; | |
}, | |
hysteresisThreshold: function(imageData, heigh, low) { | |
var color = this.color, | |
edgeDir = this.edgeDir; | |
var w = imageData.width, | |
h = imageData.height; | |
var isEdge = []; // edge flag matrix 1:edge, 0:noEdge, 2: middle | |
//height, low, mid に分解 | |
for (var x = 0; x < w; x++) { | |
isEdge[x] = []; | |
for (var y = 0; y < h; y++) { | |
if (color[x][y] > heigh) { | |
isEdge[x][y] = 1; | |
} else if (color[x][y] < low) { | |
isEdge[x][y] = 0; | |
} else { | |
isEdge[x][y] = 2; | |
} | |
} | |
} | |
var isEdgeCopy = this.copyArray2d(isEdge); | |
//this.trackMiddle用に一旦解放 | |
this.isEdge = isEdge; | |
this.isEdgeCopy = isEdgeCopy; | |
this.w = w; | |
this.h = h; | |
this.edgeDir = edgeDir; | |
//midを処理 | |
for (var x = 0; x < w; x++) { | |
for (var y = 0; y < h; y++) { | |
if (isEdgeCopy[x][y] == 1) { | |
if ((x - 1) > -1 && (y - 1) > -1 && (x + 1) < w && (y + 1) < h) { | |
// 0 deg | |
if (edgeDir[x][y] == 0) { | |
if (isEdgeCopy[x + 1][y] == 2) { | |
//追跡開始 | |
var storeTrackDataA = []; //edgeの追跡データを保存 | |
this.trackMiddle(x, y, x + 1, y, storeTrackDataA); | |
} | |
if (isEdgeCopy[x - 1][y] == 2) { | |
//追跡開始 | |
var storeTrackDataB = []; //edgeの追跡データを保存 | |
this.trackMiddle(x, y, x - 1, y, storeTrackDataB); | |
} | |
// 45 deg | |
} else if (edgeDir[x][y] == 45) { | |
if (isEdgeCopy[x + 1][y - 1] == 2) { | |
var storeTrackDataA = []; | |
this.trackMiddle(x, y, x + 1, y - 1, storeTrackDataA); | |
} | |
if (isEdgeCopy[x - 1][y + 1] == 2) { | |
var storeTrackDataB = []; | |
this.trackMiddle(x, y, x - 1, y + 1, storeTrackDataB); | |
} | |
// 90 deg | |
} else if (edgeDir[x][y] == 90) { | |
if (isEdgeCopy[x][y - 1] == 2) { | |
var storeTrackDataA = []; | |
this.trackMiddle(x, y, x, y - 1, storeTrackDataA); | |
} | |
if (isEdgeCopy[x][y + 1] == 2) { | |
var storeTrackDataB = []; | |
this.trackMiddle(x, y, x, y + 1, storeTrackDataB); | |
} | |
// 135 deg | |
} else if (edgeDir[x][y] == 135) { | |
if (isEdgeCopy[x - 1][y - 1] == 2) { | |
var storeTrackDataA = []; | |
this.trackMiddle(x, y, x - 1, y - 1, storeTrackDataA); | |
} | |
if (isEdgeCopy[x + 1][y + 1] == 2) { | |
var storeTrackDataB = []; | |
this.trackMiddle(x, y, x + 1, y + 1, storeTrackDataB); | |
} | |
} | |
} | |
} | |
} | |
} //for | |
//isEdge→color | |
for (var x = 0; x < w; x++) { | |
for (var y = 0; y < h; y++) { | |
if (isEdge[x][y] == 1) { | |
color[x][y] = this.edgeHeighVal; | |
} else { | |
color[x][y] = this.edgeLowVal; | |
} | |
} | |
} | |
this.colorMatrixToImageData(color, imageData); | |
//override color matrix | |
this.color = color; | |
}, | |
//only use hysteresisThreshold | |
trackMiddle: function(beforeX, beforeY, afterX, afterY, storeTrackData) { | |
var x = afterX, | |
y = afterY, | |
_x = beforeX, | |
_y = beforeY; | |
if ((x - 1) > -1 && (y - 1) > -1 && (x + 1) < this.w && (y + 1) < this.h) { | |
// 0 deg | |
if (this.edgeDir[x][y] == 0) { | |
if (_x != x + 1 && _y != y) { | |
if (this.isEdgeCopy[x + 1][y] == 2) { | |
storeTrackData.push({ | |
x: x, | |
y: y | |
}); | |
this.trackMiddle(x, y, x + 1, y, storeTrackData); | |
} else if (this.isEdgeCopy[x + 1][y] == 1) { | |
this.drawLine(storeTrackData, 1); | |
} else { | |
this.drawLine(storeTrackData, 0); | |
} | |
} | |
if (_x != x - 1 && _y != y) { | |
if (this.isEdgeCopy[x - 1][y] == 2) { | |
storeTrackData.push({ | |
x: x, | |
y: y | |
}); | |
this.trackMiddle(x, y, x - 1, y, storeTrackData); | |
} else if (this.isEdgeCopy[x - 1][y] == 1) { | |
this.drawLine(storeTrackData, 1); | |
} else { | |
this.drawLine(storeTrackData, 0); | |
} | |
} | |
// 45 deg | |
} else if (this.edgeDir[x][y] == 45) { | |
if (_x != x + 1 && _y != y - 1) { | |
if (this.isEdgeCopy[x + 1][y - 1] == 2) { | |
storeTrackData.push({ | |
x: x, | |
y: y | |
}); | |
this.trackMiddle(x, y, x + 1, y - 1, storeTrackData); | |
} else if (this.isEdgeCopy[x + 1][y - 1] == 1) { | |
this.drawLine(storeTrackData, 1); | |
} else { | |
this.drawLine(storeTrackData, 0); | |
} | |
} | |
if (_x != x - 1 && _y != y + 1) { | |
if (this.isEdgeCopy[x - 1][y + 1] == 2) { | |
storeTrackData.push({ | |
x: x, | |
y: y | |
}); | |
this.trackMiddle(x, y, x - 1, y + 1, storeTrackData); | |
} else if (this.isEdgeCopy[x - 1][y + 1] == 1) { | |
this.drawLine(storeTrackData, 1); | |
} else { | |
this.drawLine(storeTrackData, 0); | |
} | |
} | |
// 90 deg | |
} else if (this.edgeDir[x][y] == 90) { | |
if (_x != x && _y != y - 1) { | |
if (this.isEdgeCopy[x][y - 1] == 2) { | |
storeTrackData.push({ | |
x: x, | |
y: y | |
}); | |
this.trackMiddle(x, y, x, y - 1, storeTrackData); | |
} else if (this.isEdgeCopy[x][y - 1] == 1) { | |
this.drawLine(storeTrackData, 1); | |
} else { | |
this.drawLine(storeTrackData, 0); | |
} | |
} | |
if (_x != x && _y != y + 1) { | |
if (this.isEdgeCopy[x][y + 1] == 2) { | |
storeTrackData.push({ | |
x: x, | |
y: y | |
}); | |
this.trackMiddle(x, y, x, y + 1, storeTrackData); | |
} else if (this.isEdgeCopy[x][y + 1] == 1) { | |
this.drawLine(storeTrackData, 1); | |
} else { | |
this.drawLine(storeTrackData, 0); | |
} | |
} | |
// 135 deg | |
} else if (this.edgeDir[x][y] == 135) { | |
if (_x != x - 1 && _y != y - 1) { | |
if (this.isEdgeCopy[x - 1][y - 1] == 2) { | |
storeTrackData.push({ | |
x: x, | |
y: y | |
}); | |
this.trackMiddle(x, y, x - 1, y - 1, storeTrackData); | |
} else if (this.isEdgeCopy[x - 1][y - 1] == 1) { | |
this.drawLine(storeTrackData, 1); | |
} else { | |
this.drawLine(storeTrackData, 0); | |
} | |
} | |
if (_x != x + 1 && _y != y + 1) { | |
if (this.isEdgeCopy[x + 1][y + 1] == 2) { | |
storeTrackData.push({ | |
x: x, | |
y: y | |
}); | |
this.trackMiddle(x, y, x + 1, y + 1, storeTrackData); | |
} else if (this.isEdgeCopy[x + 1][y + 1] == 1) { | |
this.drawLine(storeTrackData, 1); | |
} else { | |
this.drawLine(storeTrackData, 0); | |
} | |
} | |
} | |
} else { | |
this.drawLine(storeTrackData, 1); | |
} | |
}, | |
//only use hysteresisThreshold | |
drawLine: function(storeTrackData, edgeVal) { | |
for (var i = 0, l = storeTrackData.length; i < l; i++) { | |
if (edgeVal == 1) { | |
this.edge[storeTrackData[i].x, storeTrackData[i].y] = 1; | |
} else { | |
this.edge[storeTrackData[i].x, storeTrackData[i].y] = 0; | |
} | |
} | |
}, | |
imageDataToColorMatrix: function(imageData, isGrayScale) { | |
var data = imageData.data, | |
pixels = data.length / 4, | |
w = imageData.width, | |
h = imageData.height; | |
isGrayScale = isGrayScale || this.isGrayScale; | |
if (isGrayScale) { | |
//color matrix | |
var color = [], | |
colorCopy = []; | |
//imageData→color matrix | |
for (var x = 0; x < w; x++) { | |
color[x] = []; | |
colorCopy[x] = []; | |
for (var y = 0; y < h; y++) { | |
color[x][y] = data[(x + (y * w)) * 4]; | |
colorCopy[x][y] = data[(x + (y * w)) * 4]; | |
} | |
} | |
} else { | |
//color matrix | |
var color = { | |
r: [], | |
g: [], | |
b: [] | |
}, | |
colorCopy = { | |
r: [], | |
g: [], | |
b: [] | |
}; | |
//imageData→color matrix | |
for (var x = 0; x < w; x++) { | |
color.r[x] = []; | |
color.g[x] = []; | |
color.b[x] = []; | |
colorCopy.r[x] = []; | |
colorCopy.g[x] = []; | |
colorCopy.b[x] = []; | |
for (var y = 0; y < h; y++) { | |
color.r[x][y] = data[(x + (y * w)) * 4]; | |
color.g[x][y] = data[(x + (y * w)) * 4 + 1]; | |
color.b[x][y] = data[(x + (y * w)) * 4 + 2]; | |
colorCopy.r[x][y] = data[(x + (y * w)) * 4]; | |
colorCopy.g[x][y] = data[(x + (y * w)) * 4 + 1]; | |
colorCopy.b[x][y] = data[(x + (y * w)) * 4 + 2]; | |
} | |
} | |
} | |
return { | |
origin: color, | |
copy: colorCopy | |
}; | |
}, | |
colorMatrixToImageData: function(color, imageData, filterW, isGrayScale) { | |
var data = imageData.data, | |
pixels = data.length / 4, | |
w = imageData.width, | |
h = imageData.height; | |
filterW = filterW || 1; | |
isGrayScale = isGrayScale || this.isGrayScale; | |
if (this.isGrayScale) { | |
//color matrix→imageData | |
for (var i = 0, l = pixels; i < l; i++) { | |
var x = i % w, | |
y = parseInt(i / w); | |
data[i * 4] = color[x][y] / filterW; | |
data[i * 4 + 1] = color[x][y] / filterW; | |
data[i * 4 + 2] = color[x][y] / filterW; | |
} | |
} else { | |
//color matrix→imageData | |
for (var i = 0, l = pixels; i < l; i++) { | |
var x = i % w, | |
y = parseInt(i / w); | |
data[i * 4] = color.r[x][y] / filterW; | |
data[i * 4 + 1] = color.g[x][y] / filterW; | |
data[i * 4 + 2] = color.b[x][y] / filterW; | |
} | |
} | |
}, | |
copyArray2d: function(arr) { | |
var rArr = []; | |
var w = arr.length, | |
h = arr[0].length; | |
for (var x = 0; x < w; x++) { | |
rArr[x] = []; | |
for (var y = 0; y < h; y++) { | |
rArr[x][y] = arr[x][y]; | |
} | |
} | |
return rArr; | |
} | |
} | |
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.25/vue.min.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import 'https://fonts.googleapis.com/css?family=Lato'; | |
html { | |
background: #fff; | |
height:100%; | |
padding: 0.3em; | |
} | |
body { | |
background: #f0f0f0; | |
height:100%; | |
overflow: auto; | |
} | |
body * { | |
font-family: 'Lato', sans-serif !important; | |
} | |
input[type="range"] { | |
background: transparent; | |
} | |
canvas { | |
width: 276px; | |
height: 276px; | |
background: white; | |
margin: 0 auto; | |
display: block; | |
} | |
.history { | |
text-align: center; | |
} | |
.history p{ | |
background: #dcdcdc; | |
margin: .2em auto; | |
text-decoration: none; | |
color: #444; | |
cursor: pointer; | |
text-align: left; | |
max-width: 200px; | |
padding-left: 1em; | |
} | |
.history .active{ | |
color: #fff; | |
background: #4591aa; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<link href="https://cdnjs.cloudflare.com/ajax/libs/flexboxgrid/6.3.0/flexboxgrid.min.css" rel="stylesheet" /> | |
<link href="http://cdn.concisecss.com/v3.4.0/concise.min.css" rel="stylesheet" /> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment