block by enjalot f8456ac8d3c2e8069571

ascii nate: dark

Full Screen

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

index.html

<!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>

downscale.js

//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 };
}

scaler.js

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;
}

style.css

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;
}