Skip to content

Instantly share code, notes, and snippets.

@jdelibas
Created June 30, 2016 11:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdelibas/2f8db9116cd0e01b6200c7dbd4dbf21e to your computer and use it in GitHub Desktop.
Save jdelibas/2f8db9116cd0e01b6200c7dbd4dbf21e to your computer and use it in GitHub Desktop.
Monochrome Icon Editor
<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>
(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;
}
}
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.25/vue.min.js"></script>
@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;
}
<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