1 Mar 2014

Mapping Images on Spherical Surfaces Using Javascript and HTML5 Canvas

Earth

andalmeida posted on codeproject.com showing how to map images onto spherical surfaces using C#. I have taken his code and converted it to work with javascript and an HTML5 Canvas.

Original article: http://www.codeproject.com/Articles/19712/Mapping-Images-on-Spherical-Surfaces-Using-C

Demo:


Source Code

Download source code   Note: You must run it in a web project to avoid cross domain issues which will prevent the reading of image data.

Html:

Contains a canvas 'myCanvas' which will be drawn to.
<img src="images/worldmap.gif" id="myImage" />
<canvas id="myCanvas" width="150" height="150" style="background-color: #000000"></canvas>
<br />
<input type="range" id="rangeInput" min="-31" max="31">& Drag to Rotate
<script src="./js/WrapSphereDemo.js"></script>

Javascript:

Loads the image to wrap, then calls a library to wrap it around a sphere, then writes the result onto the canvas.
var WrapSphereDemo;
(function (WrapSphereDemo) {
    var image;
    var context;
    var myImg = new Image();
    window.onload = function () {
        // hook up range change and image onload to render
        document.getElementById("rangeInput").onchange = Render;
        myImg.onload = Render;
		
	// load image
        myImg.src = "./images/worldmap.gif";
    };
    function Render() {
        GenerateImage(myImg);
    }
    function GenerateImage(myImg) {
        var canvasToDrawOn = document.getElementById("myCanvas");
        var canvasToDrawOnContext = canvasToDrawOn.getContext("2d");
        // Copy the image into a canvas
        var offscreenCanvas = document.createElement("canvas");
        offscreenCanvas.width = myImg.width;
        offscreenCanvas.height = myImg.height;
        var offscreenContext = offscreenCanvas.getContext("2d");
        offscreenContext.drawImage(myImg, 0, 0);
		
	// get the image data from the hidden canvas
        var fromImage = offscreenContext.getImageData(0, 0, myImg.width, myImg.height);
        // Create a new image with the source image wrapped around a sphere
        var yRotate = (parseInt(document.getElementById("rangeInput").value) - 31) / 10;
        var xRotate = Math.PI / 2;
        var toImage = canvasToDrawOnContext.getImageData(0, 0, 50 * 2, 50 * 2);
        var image = SphereImageLib.WrapSphere(fromImage, toImage, xRotate, yRotate, 50);
		
	// copy the image to the canvas
        canvasToDrawOnContext.putImageData(image, 25, 25);
    }
})(WrapSphereDemo || (WrapSphereDemo = {}));

Javascript Lib:

Javascript which maps each pixel in the image onto a sphere, then copies the pixel onto the destination image. Refactored from: http://www.codeproject.com/Articles/19712/Mapping-Images-on-Spherical-Surfaces-Using-C
var SphereImageLib;
(function (SphereImageLib) {
    function WrapSphere(fromImage, toImage, xRotate, yRotate, radius) {
        var coordinates = new SphereMapper(fromImage.width, fromImage.height, xRotate, yRotate, radius);
        for (var i = 0; i < toImage.data.length; i++) {
            toImage.data[i] = 0;
        }
        for (var i = 0; i < fromImage.width; i++) {
            for (var j = 0; j < fromImage.height; j++) {
                coordinates.Map(i, j);
                if (coordinates.z > 0) {
                    var fromPixel = new Pixel(fromImage, i, j);
                    var toPixel = new Pixel(toImage, coordinates.x + coordinates.radius, coordinates.y + coordinates.radius);
                    toPixel.Copy(fromPixel);
                }
            }
        }
        return toImage;
    }
    SphereImageLib.WrapSphere = WrapSphere;
    var RotateResult = (function () {
        function RotateResult() {
        }
        return RotateResult;
    })();
    SphereImageLib.RotateResult = RotateResult;
    var SphereMapper = (function () {
        function SphereMapper(width, height, xRotate, yRotate, radius) {
            this.theta0 = 0.0;
            this.theta1 = 2.0 * Math.PI;
            this.phi0 = 0.0;
            this.phi1 = Math.PI;
            this.width = width;
            this.height = height;
            this.xRotate = xRotate;
            this.yRotate = yRotate;
            this.radius = radius;
        }
        //// map a pixel to a position on the sphere
        SphereMapper.prototype.Map = function (i, j) {
            var theta = this.MapCoordinate(0.0, this.width - 1, this.theta1, this.theta0, i);
            var phi = this.MapCoordinate(0.0, this.height - 1, this.phi0, this.phi1, j);
            this.x = this.radius * Math.sin(phi) * Math.cos(theta);
            this.y = this.radius * Math.sin(phi) * Math.sin(theta);
            this.z = this.radius * Math.cos(phi);
            var result;
            result = this.Rotate(this.xRotate, this.y, this.z);
            this.y = result.r1;
            this.z = result.r2;
            result = this.Rotate(this.yRotate, this.x, this.z);
            this.x = result.r1;
            this.z = result.r2;
        };
        SphereMapper.prototype.MapCoordinate = function (i1, i2, w1, w2, p) {
            return ((p - i1) / (i2 - i1)) * (w2 - w1) + w1;
        };
        SphereMapper.prototype.Rotate = function (angle, axisA, axisB) {
            return {
                r1: axisA * Math.cos(angle) - axisB * Math.sin(angle),
                r2: axisA * Math.sin(angle) + axisB * Math.cos(angle)
            };
        };
        return SphereMapper;
    })();
    SphereImageLib.SphereMapper = SphereMapper;
    var Pixel = (function () {
        function Pixel(image, x, y) {
            this.image = image;
            this.index = (Math.floor(x) + Math.floor(y) * this.image.width) * 4;
        }
        Pixel.prototype.Copy = function (from) {
            for (var i = 0; i < 3; i++) {
                this.image.data[this.index + i] = from.image.data[from.index + i];
            }
            this.image.data[this.index + 3] = 255; // Alpha not opaque
        };
        return Pixel;
    })();
    SphereImageLib.Pixel = Pixel;
})(SphereImageLib || (SphereImageLib = {}));
blog comments powered by Disqus

Image attribution: "tools" by Gayle Brindley is licensed under CC BY 2.0

About Me


My first computer was a Commodore VIC-20, I had great fun trying to code text adventures and side scrolling shoot ‘em ups in BASIC. This helped me lead the way as the first in my school to pass a computer exam.

Currently I work as a Senior Software Engineer in Bedford for a FTSE 100 Company. Coding daily in C#, JavaScript and SQL. Outside of work I work on whatever is interesting me at that time.