Generate UTF-8 Art. Upload your own picture (it will be downsampled to 72 pixels wide).
Try changing the inputs of the scale, since the initial values are Chinese, you’ll need to replace all of them if you are using ASCII for the image to line up.
You can double click on the scale to add a new value. Try dragging the numbers around to see how the image is affected by the scale.
Built with blockbuilder.org
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="scaler.js"></script>
<script src="downscale.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
svg { width: 100%; height: 100%; }
p {
margin: 0;
padding: 0;
display: inline-block;
float:left;
}
#ascii {
margin: 10px;
font-size: 7px;
font-family: 'Courier New', monospace;
letter-spacing: 2px;
line-height: 9px;
font-weight: bold;
}
#file {
margin-left: 30px;
}
</style>
</head>
<body>
<div id="container">
<pre id="ascii">
</pre>
</div>
<input id="file" type="file" name="files[]">
<img id="preview" style="display:none">
<script>
d3.json("nate.json", function(err, nate) {
var targetWidth = 72;
var display = d3.select("body");
var scale = d3.scale.linear()
.domain([0,78, 150, 200, 255])
.range(["一", "二", "三", "四", "无"])
var scaler = d3.scaler()
.scale(scale);
var container = display.append("div")
.attr("id", "scaler")
container.call(scaler);
scaler.on("update", function() {
scale.interpolate(function(a,b) {
return function(t) {
return [a,b][Math.round(t)];
}
})
render();
})
var r, g, b;
function getPixels(data) {
var pixels = [];
for(var i = 0; i < data.length; i++)
{
r = data[i][0];
g = data[i][1];
b = data[i][2];
//converting the pixel into grayscale
gray = r*0.2126 + g*0.7152 + b*0.0722;
pixels.push({r: r, g:g, b:b, gray: gray});
}
return pixels;
}
render(getPixels(nate));
function render(pixels) {
var ps;
if(pixels) {
ps = display.select("#ascii").selectAll("p")
.data(pixels)
ps.enter()
.append("p")
ps.exit().remove();
} else {
ps = display.select("#ascii").selectAll("p");
}
ps.text(function(d) {
//return asciiScale.invert(d.gray);
return scale(d.gray)
})
.style({
"clear": function(d,i) {
if(i !== 0 && i%targetWidth === 0)
return "left";
},
"color": function(d,i) {
return "rgba(" + [d.r, d.g, d.b, 1] + ")";
//return colorScale(i);
}
})
}
d3.select("#file").node()
.addEventListener("change", function(evt) {
d3.select("#downscaled").selectAll("canvas").remove();
var files = evt.target.files;
var file = files[0];
if(file.type.indexOf("image/") === 0) {
var reader = new FileReader();
reader.onload = (function(data) {
var img = document.getElementById('preview')
img.src = data.target.result;
setTimeout(function() {
scaleImage(img);
}, 300);
});
reader.readAsDataURL(file);
}
})
function scaleImage(img) {
var imgCV = document.createElement('canvas');
imgCV.width = img.width;
imgCV.height = img.height;
var imgCtx = imgCV.getContext('2d');
imgCtx.drawImage(img, 0, 0);
var scale = targetWidth / img.width;
console.log("scale", scale)
var downscaled = downScaleCanvas(imgCV, scale);
console.log("scaled", targetWidth, img.height * scale);
render(getPixels(downscaled.jsonArray));
}
})
</script>
</body>
//http://stackoverflow.com/questions/18922880/html5-canvas-resize-downscale-image-high-quality
//http://jsfiddle.net/gamealchemist/r6aVp/
function downScaleCanvas(cv, scale) {
if (!(scale < 1) || !(scale > 0)) throw ('scale must be a positive number <1 ');
var sqScale = scale * scale; // square scale = area of source pixel within target
var sw = cv.width; // source image width
var sh = cv.height; // source image height
var tw = Math.floor(sw * scale); // target image width
var th = Math.floor(sh * scale); // target image height
var sx = 0, sy = 0, sIndex = 0; // source x,y, index within source array
var tx = 0, ty = 0, yIndex = 0, tIndex = 0; // target x,y, x,y index within target array
var tX = 0, tY = 0; // rounded tx, ty
var w = 0, nw = 0, wx = 0, nwx = 0, wy = 0, nwy = 0; // weight / next weight x / y
// weight is weight of current source point within target.
// next weight is weight of current source point within next target's point.
var crossX = false; // does scaled px cross its current px right border ?
var crossY = false; // does scaled px cross its current px bottom border ?
var sBuffer = cv.getContext('2d').
getImageData(0, 0, sw, sh).data; // source buffer 8 bit rgba
var tBuffer = new Float32Array(3 * tw * th); // target buffer Float32 rgb
var sR = 0, sG = 0, sB = 0; // source's current point r,g,b
/* untested !
var sA = 0; //source alpha */
for (sy = 0; sy < sh; sy++) {
ty = sy * scale; // y src position within target
tY = 0 | ty; // rounded : target pixel's y
yIndex = 3 * tY * tw; // line index within target array
crossY = (tY != (0 | ty + scale));
if (crossY) { // if pixel is crossing botton target pixel
wy = (tY + 1 - ty); // weight of point within target pixel
nwy = (ty + scale - tY - 1); // ... within y+1 target pixel
}
for (sx = 0; sx < sw; sx++, sIndex += 4) {
tx = sx * scale; // x src position within target
tX = 0 | tx; // rounded : target pixel's x
tIndex = yIndex + tX * 3; // target pixel index within target array
crossX = (tX != (0 | tx + scale));
if (crossX) { // if pixel is crossing target pixel's right
wx = (tX + 1 - tx); // weight of point within target pixel
nwx = (tx + scale - tX - 1); // ... within x+1 target pixel
}
sR = sBuffer[sIndex ]; // retrieving r,g,b for curr src px.
sG = sBuffer[sIndex + 1];
sB = sBuffer[sIndex + 2];
/* !! untested : handling alpha !!
sA = sBuffer[sIndex + 3];
if (!sA) continue;
if (sA != 0xFF) {
sR = (sR * sA) >> 8; // or use /256 instead ??
sG = (sG * sA) >> 8;
sB = (sB * sA) >> 8;
}
*/
if (!crossX && !crossY) { // pixel does not cross
// just add components weighted by squared scale.
tBuffer[tIndex ] += sR * sqScale;
tBuffer[tIndex + 1] += sG * sqScale;
tBuffer[tIndex + 2] += sB * sqScale;
} else if (crossX && !crossY) { // cross on X only
w = wx * scale;
// add weighted component for current px
tBuffer[tIndex ] += sR * w;
tBuffer[tIndex + 1] += sG * w;
tBuffer[tIndex + 2] += sB * w;
// add weighted component for next (tX+1) px
nw = nwx * scale
tBuffer[tIndex + 3] += sR * nw;
tBuffer[tIndex + 4] += sG * nw;
tBuffer[tIndex + 5] += sB * nw;
} else if (crossY && !crossX) { // cross on Y only
w = wy * scale;
// add weighted component for current px
tBuffer[tIndex ] += sR * w;
tBuffer[tIndex + 1] += sG * w;
tBuffer[tIndex + 2] += sB * w;
// add weighted component for next (tY+1) px
nw = nwy * scale
tBuffer[tIndex + 3 * tw ] += sR * nw;
tBuffer[tIndex + 3 * tw + 1] += sG * nw;
tBuffer[tIndex + 3 * tw + 2] += sB * nw;
} else { // crosses both x and y : four target points involved
// add weighted component for current px
w = wx * wy;
tBuffer[tIndex ] += sR * w;
tBuffer[tIndex + 1] += sG * w;
tBuffer[tIndex + 2] += sB * w;
// for tX + 1; tY px
nw = nwx * wy;
tBuffer[tIndex + 3] += sR * nw;
tBuffer[tIndex + 4] += sG * nw;
tBuffer[tIndex + 5] += sB * nw;
// for tX ; tY + 1 px
nw = wx * nwy;
tBuffer[tIndex + 3 * tw ] += sR * nw;
tBuffer[tIndex + 3 * tw + 1] += sG * nw;
tBuffer[tIndex + 3 * tw + 2] += sB * nw;
// for tX + 1 ; tY +1 px
nw = nwx * nwy;
tBuffer[tIndex + 3 * tw + 3] += sR * nw;
tBuffer[tIndex + 3 * tw + 4] += sG * nw;
tBuffer[tIndex + 3 * tw + 5] += sB * nw;
}
} // end for sx
} // end for sy
// create result canvas
var resCV = document.createElement('canvas');
resCV.width = tw;
resCV.height = th;
var resCtx = resCV.getContext('2d');
var imgRes = resCtx.getImageData(0, 0, tw, th);
var jsonArray = [];
var tByteBuffer = imgRes.data;
// convert float32 array into a UInt8Clamped Array
var pxIndex = 0; //
var r,g,b;
for (sIndex = 0, tIndex = 0; pxIndex < tw * th; sIndex += 3, tIndex += 4, pxIndex++) {
r = tByteBuffer[tIndex] = Math.ceil(tBuffer[sIndex]);
g = tByteBuffer[tIndex + 1] = Math.ceil(tBuffer[sIndex + 1]);
b = tByteBuffer[tIndex + 2] = Math.ceil(tBuffer[sIndex + 2]);
tByteBuffer[tIndex + 3] = 255;
jsonArray.push([r,g,b]);
}
// writing result to canvas.
resCtx.putImageData(imgRes, 0, 0);
return { canvas: resCV, jsonArray: jsonArray };
}
d3.scaler = function() {
var scale;
var height = 255;
var max;
var min;
var selection;
var dispatch = d3.dispatch("update");
var editing;
var drag = d3.behavior.drag()
.on("drag", function(d,i) {
if(editing) return false;
var y = getY(i);
if(!y && y !== 0) return;
var dy = d3.event.dy;
y += dy;
if(y > max) y = max;
if(y < min) y = min;
//move things around
setY(i, y);
//sort & reorder the domain and range
var domain = scale.domain();
var range = scale.range();
var zipped = d3.zip(domain, range);
zipped.sort(function(a,b) {
return a[0] - b[0];
})
domain = zipped.map(function(d) { return d[0] })
range = zipped.map(function(d) { return d[1] })
scaler.update();
})
var scaler = function(g) {
selection = g;
scaler._update();
}
dispatch.on("update.internal", function() {
scaler._update();
});
scaler._update = function() {
selection.each(function() {
var sel = d3.select(this);
var domain = scale.domain();
var range = scale.range();
//background. TODO: use svg?
var bg = sel.selectAll("div.background")
.data([0])
bg.enter().append("div").classed("background",true)
bg.style({
"margin-top": "16px",
width: 10+"px",
height: height+ "px",
"background-color":"grey"
})
bg.on("dblclick", function() {
var domain = scale.domain();
var range = scale.range();
var y = d3.mouse(this)[1];
var i = d3.bisect(domain, y);
domain.splice(i, 0, y);
range.splice(i, 0, "");
scaler.update();
});
//handle things
var handles = sel.selectAll("div.handle")
.data(domain)
var handlesEnter = handles.enter().append("div").classed("handle", true);
handlesEnter.append("div").classed("line", true);
//the input
var inputs = handlesEnter.append("input").classed("ascii", true)
.on("mouseup", function() { this.select(); d3.event.cancelBubble = true; })
.on("keyup", function(d,i) {
setX(i, this.value);
scaler.update();
})
.attr("maxlength", 1);
//the number
var numbers = handlesEnter.append("span.number")
.classed("number", true)
inputs.on("focus", function() {
console.log("focus!")
editing = true;
})
inputs.on("blur", function() {
console.log("blur!")
editing = false;
})
handlesEnter.call(drag)
handles.style("top", function(d,i) {
return getY(i) + "px";
})
handles.select(".number")
.text(function(d,i) {
return getY(i)
})
handles.select(".ascii")
.attr("value", function(d,i) {
return getX(i);
})
})
}
scaler.scale = function(_) {
if(!arguments.length) return scale;
scale = _;
max = d3.max(scale.domain());
min = d3.min(scale.domain());
return scaler;
}
function getY(i) {
var domain = scale.domain();
return domain[i];
}
function setY(i, v) {
var domain = scale.domain();
domain[i] = v;
}
function getX(i) {
var range = scale.range();
return range[i];
}
function setX(i, v) {
var range = scale.range();
range[i] = v;
}
d3.rebind(scaler, dispatch, "on", "update")
return scaler;
}
body {
overflow:scroll;
background-color: #000;
}
canvas {
position:absolute;
top: 100px;
right: 50px;;
}
#scaler {
position:absolute;
top: 100px;
right: 150px;
width: 100px;
}
.background {
border: 1px solid #51A874;
background-color: #000000;
background-image: -webkit-gradient(linear, left top, left bottom, from(rgb(0, 0, 0)), to(rgb(255, 255, 255)));
background-image: -webkit-linear-gradient(top, rgb(0, 0, 0), rgb(255, 255, 255));
background-image: -moz-linear-gradient(top, rgb(0, 0, 0), rgb(255, 255, 255));
background-image: -o-linear-gradient(top, rgb(0, 0, 0), rgb(255, 255, 255));
background-image: -ms-linear-gradient(top, rgb(0, 0, 0), rgb(255, 255, 255));
background-image: linear-gradient(top, rgb(0, 0, 0), rgb(255, 255, 255));
filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0,StartColorStr='#000000', EndColorStr='#ffffff');
}
.handle {
position: absolute;
left: 27px;
}
.handle .line {
width: 15px;
border-top: 1px solid #13D661;
border-bottom: 1px solid #0C7235;
position:absolute;
top: 16px;
left: -15px;
}
input.ascii {
margin-top: 5px;
border: none !important;
font-size: 12px !important;
width: 25px !important;
border-radius: 0 !important;
text-align: center;
height: 20px;
}
.number {
cursor: ns-resize;
margin-left: 5px;
font-family: Helvetica;
font-size: 12px;
color: white;
line-height: 1em;
position:relative;
}