block by nitaku ec0171990afec2c38a4e

Distance fields (edit tool - faster)

Full Screen

TBC

The main bottleneck was the color scale invocation.

index.js

(function() {
  // noprotect;
  var BLUR, CX, CY, MAX_D, R, canvas_left, canvas_right, ctx_left, ctx_right, cx_tool, cy_tool, df, dist, dist_tool, edit, height, pixel_x, pixel_y, r_tool, redraw, side, svg, tool, width;

  canvas_left = d3.select('#left');

  canvas_right = d3.select('#right');

  width = canvas_left.node().getBoundingClientRect().width;

  height = canvas_left.node().getBoundingClientRect().height;

  side = Math.min(width, height) - 20;

  ctx_left = canvas_left.node().getContext('2d');

  ctx_right = canvas_right.node().getContext('2d');

  /* define the distance function for the original circle
  */


  CX = 0.5;

  CY = 0.5;

  R = 0.25;

  dist = function(x, y) {
    return R - (Math.pow(x - CX, 2) + Math.pow(y - CY, 2)) / R;
  };

  /* compute the field
  */


  df = (function() {
    var _i, _results;

    _results = [];
    for (pixel_x = _i = 0; 0 <= side ? _i < side : _i > side; pixel_x = 0 <= side ? ++_i : --_i) {
      _results.push((function() {
        var _j, _results1;

        _results1 = [];
        for (pixel_y = _j = 0; 0 <= side ? _j < side : _j > side; pixel_y = 0 <= side ? ++_j : --_j) {
          _results1.push(dist(pixel_x / side, pixel_y / side));
        }
        return _results1;
      })());
    }
    return _results;
  })();

  /* edit tool
  */


  svg = d3.select('svg');

  cx_tool = 0;

  cy_tool = 0;

  r_tool = 0.125;

  dist_tool = function(x, y) {
    return r_tool - (Math.pow(x - cx_tool, 2) + Math.pow(y - cy_tool, 2)) / r_tool;
  };

  tool = svg.append('circle').attr({
    id: 'tool',
    r: r_tool * side
  });

  svg.on('mousemove', function() {
    var x, y, _ref, _ref1;

    _ref = d3.mouse(this), x = _ref[0], y = _ref[1];
    _ref1 = [x / side, y / side], cx_tool = _ref1[0], cy_tool = _ref1[1];
    return tool.attr({
      cx: x + 0.5,
      cy: y + 0.5
    });
  });

  svg.on('click', function() {
    return edit('union');
  });

  svg.on('contextmenu', function() {
    edit('sub');
    return d3.event.preventDefault();
  });

  svg.on('mousewheel', function() {
    if (d3.event.wheelDelta > 0) {
      r_tool *= 1.2;
    } else {
      r_tool /= 1.2;
    }
    return tool.attr({
      r: r_tool * side
    });
  });

  edit = function(op) {
    var _i, _j;

    for (pixel_x = _i = 0; 0 <= side ? _i < side : _i > side; pixel_x = 0 <= side ? ++_i : --_i) {
      for (pixel_y = _j = 0; 0 <= side ? _j < side : _j > side; pixel_y = 0 <= side ? ++_j : --_j) {
        if (op === 'union') {
          df[pixel_x][pixel_y] = Math.max(df[pixel_x][pixel_y], dist_tool(pixel_x / side, pixel_y / side));
        } else if (op === 'intersection') {
          df[pixel_x][pixel_y] = Math.min(df[pixel_x][pixel_y], dist_tool(pixel_x / side, pixel_y / side));
        } else if (op === 'sub') {
          df[pixel_x][pixel_y] = Math.min(df[pixel_x][pixel_y], -dist_tool(pixel_x / side, pixel_y / side));
        }
      }
    }
    return window.requestAnimationFrame(function() {
      return redraw();
    });
  };

  MAX_D = Math.sqrt(2) / 4;

  BLUR = 3;

  redraw = function() {
    /* Draw the distance field...
    */

    var Fxy, a, b, g, image_left, image_right, pixel_i, r, value, _i, _j, _ref;

    image_left = ctx_left.createImageData(side, side);
    /* ...and the reconstructed shape
    */

    image_right = ctx_right.createImageData(side, side);
    for (pixel_x = _i = 0; 0 <= side ? _i < side : _i > side; pixel_x = 0 <= side ? ++_i : --_i) {
      for (pixel_y = _j = 0; 0 <= side ? _j < side : _j > side; pixel_y = 0 <= side ? ++_j : --_j) {
        pixel_i = (pixel_y * side + pixel_x) * 4;
        _ref = [pixel_i + 0, pixel_i + 1, pixel_i + 2, pixel_i + 3], r = _ref[0], g = _ref[1], b = _ref[2], a = _ref[3];
        Fxy = df[pixel_x][pixel_y];
        image_left.data[r] = -Fxy / MAX_D * 255;
        image_left.data[g] = 0;
        image_left.data[b] = Fxy / MAX_D * 255;
        image_left.data[a] = 255;
        value = Math.min(1 + Fxy / (BLUR / side), 1);
        image_right.data[r] = 255;
        image_right.data[g] = 255;
        image_right.data[b] = 255;
        image_right.data[a] = value * 255;
      }
    }
    ctx_left.putImageData(image_left, (width - side) / 2, (height - side) / 2);
    return ctx_right.putImageData(image_right, (width - side) / 2, (height - side) / 2);
  };

  redraw();

}).call(this);

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="description" content="Distance fields (edit tool - faster)" />
    <title>Distance fields (edit tool - faster)</title>
    <link rel="stylesheet" href="index.css">
    <script src="//d3js.org/d3.v3.min.js"></script>
</head>
<body>
  <canvas id="left" width="480" height="500"></canvas>
  <canvas id="right" width="480" height="500"></canvas>
  <svg width="460" height="460"></svg>
  
  <script src="index.js"></script>
</body>
</html>

index.coffee

`// noprotect`

canvas_left = d3.select('#left')
canvas_right = d3.select('#right')
width = canvas_left.node().getBoundingClientRect().width
height = canvas_left.node().getBoundingClientRect().height
side = Math.min(width, height) - 20

ctx_left = canvas_left.node().getContext('2d')
ctx_right = canvas_right.node().getContext('2d')

### define the distance function for the original circle ###
CX = 0.5
CY = 0.5
R = 0.25
dist = (x, y) -> R - ( Math.pow(x-CX, 2) + Math.pow(y-CY, 2) )/R

### compute the field ###
df = (( dist(pixel_x/side, pixel_y/side) for pixel_y in [0...side]) for pixel_x in [0...side])

### edit tool ###
svg = d3.select('svg')
    
cx_tool = 0
cy_tool = 0
r_tool = 0.125

dist_tool = (x, y) -> r_tool - ( Math.pow(x-cx_tool, 2) + Math.pow(y-cy_tool, 2) )/r_tool

tool = svg.append('circle')
  .attr
    id: 'tool'
    r: r_tool*side

svg.on 'mousemove', () ->
  [x,y] = d3.mouse(this)
  [cx_tool, cy_tool] = [x/side, y/side]
  
  tool
    .attr
      cx: x+0.5
      cy: y+0.5
      
svg.on 'click', () ->
  edit('union')
  
svg.on 'contextmenu', () ->
  edit('sub')
  
  # suppress context menu
  d3.event.preventDefault()
  
# change tool size
svg.on 'mousewheel', () ->
  if d3.event.wheelDelta > 0
    r_tool *= 1.2
  else
    r_tool /= 1.2
    
  tool
    .attr
      r: r_tool*side
  
edit = (op) ->
  for pixel_x in [0...side]
    for pixel_y in [0...side]
      if op is 'union'
        df[pixel_x][pixel_y] = Math.max( df[pixel_x][pixel_y], dist_tool(pixel_x/side, pixel_y/side) )
      else if op is 'intersection'
        df[pixel_x][pixel_y] = Math.min( df[pixel_x][pixel_y], dist_tool(pixel_x/side, pixel_y/side) )
      else if op is 'sub'
        df[pixel_x][pixel_y] = Math.min( df[pixel_x][pixel_y], -dist_tool(pixel_x/side, pixel_y/side) )
      
  window.requestAnimationFrame () ->
    redraw()


MAX_D = Math.sqrt(2)/4  
BLUR = 3

redraw = () ->
  ### Draw the distance field... ###
  image_left = ctx_left.createImageData(side, side)
  ### ...and the reconstructed shape ###
  image_right = ctx_right.createImageData(side, side)
  
  for pixel_x in [0...side]
    for pixel_y in [0...side]
      pixel_i = (pixel_y*side + pixel_x)*4
      [r,g,b,a] = [pixel_i+0, pixel_i+1, pixel_i+2, pixel_i+3]
      
      Fxy = df[pixel_x][pixel_y]
      
      image_left.data[r] = -Fxy/MAX_D*255
      image_left.data[g] = 0
      image_left.data[b] = Fxy/MAX_D*255
      image_left.data[a] = 255
      
      
      value = Math.min(1+Fxy/(BLUR/side), 1)
      
      image_right.data[r] = 255
      image_right.data[g] = 255
      image_right.data[b] = 255
      image_right.data[a] = value*255
    
  ctx_left.putImageData(image_left,(width-side)/2,(height-side)/2)
  ctx_right.putImageData(image_right,(width-side)/2,(height-side)/2)
  
redraw()

index.css

html, body {
  padding: 0;
  margin: 0;
}
body.wait {
  cursor: wait;
}

canvas {
  background: #222;
  position: absolute;
}
#left {
  top: 0;
  left: 0;
}
#right {
  top: 0;
  left: 480px;
}
svg {
  position: absolute;
  top: 20px;
  left: 490px;
}

#tool {
  fill: none;
  stroke: magenta;
  stroke-width: 1.5;
}