1 var jaws = (function(jaws) {
  2 /**
  3 * @class jaws.PixelMap
  4 * @constructor
  5 *
  6 * Worms-style terrain collision detection. Created from a normal image. 
  7 * Read out specific pixels. Modify as you would do with a canvas.
  8 *  
  9 * @property {string} image        the image of the terrain
 10 * @property {int} scale_image     Scale the image by this factor
 11 *
 12 * @example
 13 * tile_map = new jaws.Parallax({image: "map.png", scale_image: 4})  // scale_image: 4 for retro blocky feeling!
 14 * tile_map.draw()                                     // draw on canvas
 15 * tile_map.nameColor([0,0,0,255], "ground")           // give the color black the name "ground"
 16 * tile_map.namedColorAtRect("ground", player.rect())  // True if players boundingbox is touching any black pixels on tile_map 
 17 *
 18 */
 19 jaws.PixelMap = function PixelMap(options) {
 20   if( !(this instanceof arguments.callee) ) return new arguments.callee( options );
 21 
 22   this.options = options
 23   this.scale = options.scale || 1
 24   this.x = options.x || 0
 25   this.y = options.y || 0
 26 
 27   if(options.image) {
 28     this.setContext(options.image);
 29 
 30     if(options.scale_image) {
 31       this.setContext(  jaws.retroScaleImage(this.context.canvas, options.scale_image) )
 32     }
 33 
 34     this.width = this.context.canvas.width * this.scale;
 35     this.height = this.context.canvas.height * this.scale;
 36   }
 37   else { jaws.log.warn("PixelMap needs an image to work with") }
 38   
 39   this.named_colors = [];
 40   this.update();
 41 }
 42 
 43 /*
 44 * Initiates a drawable context from given image.
 45 * @private
 46 */
 47 jaws.PixelMap.prototype.setContext = function(image) {
 48   var image = jaws.isDrawable(image) ? image : jaws.assets.get(image)
 49   this.context = jaws.imageToCanvasContext(image)
 50 } 
 51 
 52 /**
 53 * Updates internal pixel-array from the canvas. If we modify the 'terrain' (paint on pixel_map.context) we'll need to call this method.
 54 */
 55 jaws.PixelMap.prototype.update = function(x, y, width, height) {
 56   if(x === undefined || x < 0) x = 0;
 57   if(y === undefined || y < 0) y = 0;
 58   if(width === undefined || width > this.width)     width = this.width;
 59   if(height === undefined || height > this.height)  height = this.height;
 60  
 61   // No arguments? Read whole canvas, replace this.data
 62   if(arguments.length == 0) {
 63     this.data = this.context.getImageData(x, y, width, height).data
 64   }
 65   // Read a rectangle from the canvas, replacing relevant pixels in this.data
 66   else {
 67     var tmp = this.context.getImageData(x, y, width, height).data
 68     var tmp_count = 0;
 69 
 70     // Some precalculation-optimizations
 71     var one_line_down = this.width * 4;
 72     var offset = (y * this.width * 4)  + (x*4);
 73     var horizontal_line = width*4;
 74 
 75     for(var y2 = 0; y2 < height; y2++) {
 76       for(var x2 = 0; x2 < horizontal_line; x2++) {
 77         this.data[offset + x2] = tmp[tmp_count++];
 78       }  
 79       offset += one_line_down;
 80     }
 81   }
 82 }
 83 
 84 /**
 85 * Draws the pixel map on the maincanvas
 86 */ 
 87 jaws.PixelMap.prototype.draw = function() {
 88   jaws.context.drawImage(this.context.canvas, this.x, this.y, this.width, this.height)
 89 }
 90 
 91 /**
 92 * Trace the outline of a Rect until a named color found.
 93 *
 94 * @param {object} Rect          Instance of jaws.Rect()
 95 * @param {string} Color_Filter  Only look for this named color
 96 *
 97 * @return {string}  name of found color
 98 */
 99 jaws.PixelMap.prototype.namedColorAtRect = function(rect, color) {
100   var x = rect.x
101   var y = rect.y
102 
103   for(; x < rect.right-1; x++)  if(this.namedColorAt(x, y) == color || color===undefined) return this.namedColorAt(x,y);
104   for(; y < rect.bottom-1; y++) if(this.namedColorAt(x, y) == color || color===undefined) return this.namedColorAt(x,y);
105   for(; x > rect.x; x--)      if(this.namedColorAt(x, y) == color || color===undefined) return this.namedColorAt(x,y);
106   for(; y > rect.y; y--)      if(this.namedColorAt(x, y) == color || color===undefined) return this.namedColorAt(x,y);
107 
108   return false;
109 }
110 
111 /**
112 * Read current color at given coordinates X/Y 
113 *
114 * @return {array}   4 integers [R, G, B, A] representing the pixel at x/y
115 */
116 jaws.PixelMap.prototype.at = function(x, y) {
117   x = parseInt(x)
118   y = parseInt(y)
119   if(y < 0) y = 0;
120 
121   var start = (y * this.width * 4) + (x*4);
122   var R = this.data[start];
123   var G = this.data[start + 1];
124   var B = this.data[start + 2];
125   var A = this.data[start + 3];
126   return [R, G, B, A];
127 }
128 
129 /**
130 * Get previously named color if it exists at given x/y-coordinates.
131 *
132 * @return {string} name or color
133 */
134 jaws.PixelMap.prototype.namedColorAt = function(x, y) {
135   var a = this.at(x, y);
136   for(var i=0; i < this.named_colors.length; i++) {
137     var name = this.named_colors[i].name;
138     var c = this.named_colors[i].color;
139     if(c[0] == a[0] && c[1] == a[1] && c[2] == a[2] && c[3] == a[3]) return name;
140   }
141 }
142 
143 /**
144 * Give a RGBA-array a name. Later on we can work with names instead of raw colorvalues.
145 *
146 * @example
147 * pixel_map.nameColor([0,0,0,255], "ground")    // Give the color black (with no transparency) the name "ground"
148 */
149 jaws.PixelMap.prototype.nameColor = function(color, name) {
150   this.named_colors.push({name: name, color: color});
151 }
152 
153 return jaws;
154 })(jaws || {});
155