block by fil c6efde7d8271a8728cd23c0d268ea787

Raster Reprojection of panorama images (360°)

Full Screen

Use Jason Davies’ d3.geo.zoom to reproject 360° panorama images.

Forked from mbostock‘s block: Raster Reprojection.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>
    body {
        background: #222;
    }
    
    canvas {
        cursor:move; cursor:-webkit-grab; cursor:-moz-grab;
    }
</style>

<body>
    <script src="//d3js.org/d3.v3.min.js"></script>
    <script src="//d3js.org/d3.geo.projection.v0.min.js"></script>
    <script src="d3.geo.zoom.js"></script>
    <script>
        var width = 1024,
            height = 512;

        var canvas = d3.select("body").append("canvas")
            .attr("width", width)
            .attr("height", height);

        var context = canvas.node().getContext("2d");

        var image = new Image;
        image.onload = onload;
        image.src = "image.jpg";

        function onload() {
            var dx = image.width,
                dy = image.height;

            var canvas2 = d3.select("body").append("canvas")
                .attr("width", dx)
                .attr("height", dy),
                context2 = canvas2.node().getContext("2d");;

            context2.drawImage(image, 0, 0, dx, dy);

            var sourceData = context2.getImageData(0, 0, dx, dy).data,
                target = context.createImageData(width, height),
                targetData = target.data;

            canvas2.remove();

            var projection = d3.geo.equirectangular().scale(500);

            draw = function () {

                for (var y = 0, i = -1; y < height; ++y) {
                    for (var x = 0; x < width; ++x) {
                        var p = projection.invert([x, y]),
                            λ = p[0],
                            φ = p[1];
                        if (λ > 180 || λ < -180 || φ > 90 || φ < -90) {
                            i += 4;
                            continue;
                        }
                        var q = ((90 - φ) / 180 * dy | 0) * dx + ((180 + λ) / 360 * dx | 0) << 2;
                        targetData[++i] = sourceData[q];
                        targetData[++i] = sourceData[++q];
                        targetData[++i] = sourceData[++q];
                        targetData[++i] = 255;
                    }
                }

                //context.clearRect(0, 0, width, height);
                context.putImageData(target, 0, 0);
            }


            draw();

            canvas
                .call(d3.geo.zoom().projection(projection)
                    .scaleExtent([projection.scale() * .4, projection.scale() * 2])
                    .on("zoom.redraw", function () {
                        d3.event.sourceEvent.preventDefault();
                        draw();
                    }));

        }
    </script>

d3.geo.zoom.js

// Copyright (c) 2013, Jason Davies, http://www.jasondavies.com
// See LICENSE.txt for details.
(function() {

var radians = Math.PI / 180,
    degrees = 180 / Math.PI;

// TODO make incremental rotate optional

d3.geo.zoom = function() {
  var projection,
      zoomPoint,
      event = d3.dispatch("zoomstart", "zoom", "zoomend"),
      zoom = d3.behavior.zoom()
        .on("zoomstart", function() {
          var mouse0 = d3.mouse(this),
              rotate = quaternionFromEuler(projection.rotate()),
              point = position(projection, mouse0);
          if (point) zoomPoint = point;

          zoomOn.call(zoom, "zoom", function() {
                projection.scale(d3.event.scale);
                var mouse1 = d3.mouse(this),
                    between = rotateBetween(zoomPoint, position(projection, mouse1));
                projection.rotate(eulerFromQuaternion(rotate = between
                    ? multiply(rotate, between)
                    : multiply(bank(projection, mouse0, mouse1), rotate)));
                mouse0 = mouse1;
                event.zoom.apply(this, arguments);
              });
          event.zoomstart.apply(this, arguments);
        })
        .on("zoomend", function() {
          zoomOn.call(zoom, "zoom", null);
          event.zoomend.apply(this, arguments);
        }),
      zoomOn = zoom.on;

  zoom.projection = function(_) {
    return arguments.length ? zoom.scale((projection = _).scale()) : projection;
  };

  return d3.rebind(zoom, event, "on");
};

function bank(projection, p0, p1) {
  var t = projection.translate(),
      angle = Math.atan2(p0[1] - t[1], p0[0] - t[0]) - Math.atan2(p1[1] - t[1], p1[0] - t[0]);
  return [Math.cos(angle / 2), 0, 0, Math.sin(angle / 2)];
}

function position(projection, point) {
  var t = projection.translate(),
      spherical = projection.invert(point);
  return spherical && isFinite(spherical[0]) && isFinite(spherical[1]) && cartesian(spherical);
}

function quaternionFromEuler(euler) {
  var λ = .5 * euler[0] * radians,
      φ = .5 * euler[1] * radians,
      γ = .5 * euler[2] * radians,
      sinλ = Math.sin(λ), cosλ = Math.cos(λ),
      sinφ = Math.sin(φ), cosφ = Math.cos(φ),
      sinγ = Math.sin(γ), cosγ = Math.cos(γ);
  return [
    cosλ * cosφ * cosγ + sinλ * sinφ * sinγ,
    sinλ * cosφ * cosγ - cosλ * sinφ * sinγ,
    cosλ * sinφ * cosγ + sinλ * cosφ * sinγ,
    cosλ * cosφ * sinγ - sinλ * sinφ * cosγ
  ];
}

function multiply(a, b) {
  var a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3],
      b0 = b[0], b1 = b[1], b2 = b[2], b3 = b[3];
  return [
    a0 * b0 - a1 * b1 - a2 * b2 - a3 * b3,
    a0 * b1 + a1 * b0 + a2 * b3 - a3 * b2,
    a0 * b2 - a1 * b3 + a2 * b0 + a3 * b1,
    a0 * b3 + a1 * b2 - a2 * b1 + a3 * b0
  ];
}

function rotateBetween(a, b) {
  if (!a || !b) return;
  var axis = cross(a, b),
      norm = Math.sqrt(dot(axis, axis)),
      halfγ = .5 * Math.acos(Math.max(-1, Math.min(1, dot(a, b)))),
      k = Math.sin(halfγ) / norm;
  return norm && [Math.cos(halfγ), axis[2] * k, -axis[1] * k, axis[0] * k];
}

function eulerFromQuaternion(q) {
  return [
    Math.atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees,
    Math.asin(Math.max(-1, Math.min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees,
    Math.atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees
  ];
}

function cartesian(spherical) {
  var λ = spherical[0] * radians,
      φ = spherical[1] * radians,
      cosφ = Math.cos(φ);
  return [
    cosφ * Math.cos(λ),
    cosφ * Math.sin(λ),
    Math.sin(φ)
  ];
}

function dot(a, b) {
  for (var i = 0, n = a.length, s = 0; i < n; ++i) s += a[i] * b[i];
  return s;
}

function cross(a, b) {
  return [
    a[1] * b[2] - a[2] * b[1],
    a[2] * b[0] - a[0] * b[2],
    a[0] * b[1] - a[1] * b[0]
  ];
}

})();