Drawing an honeycomb with as3 - actionscript-3

I'm trying to create an honeycomb with as3 but I have some problem on cells positioning.
I've already created the cells (not with code) and for cycled them to a funcion and send to it the parameters which what I thought was need (the honeycomb cell is allready on a sprite container in the center of the stage).
to see the structure of the cycle and which parameters passes, please see the example below, the only thing i calculate in placeCell is the angle which I should obtain directly inside tha called function
alt text http://img293.imageshack.us/img293/1064/honeycomb.png
Note: the angle is reversed but it isn't important, and the color are useful in example only for visually divide cases.
My for cycle calls placeCell and passes cell, current_case, counter (index) and the honeycomb cell_lv (cell level).
I thought it was what i needed but I'm not skilled in geometry and trigonometry, so I don't know how to position cells correctly:
import flash.display.Sprite;
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
function createHoneycomb (cells:int):void {
var honeycomb:Sprite = new Sprite ();
addChild (honeycomb);
var cell_lv:int = 1;
var increment:int = 6;
var tot_cur_cells:int = 1;
var current_case:int = 0;
var case_counter:int = 1;
var case_length:int = 1;
var max_cases:int = 6;
var nucleus:Sprite = new cell (); // hexagon from library
honeycomb.addChild (nucleus);
for (var i:int = 1; i <= cells; i ++) {
if (case_counter < case_length) {
case_counter ++;
} else {
current_case ++;
if (current_case > max_cases) current_case = 1;
case_counter = 1;
}
if (i == tot_cur_cells) { // if i reach the current level
if (i > 1) cell_lv ++;
case_length = cell_lv;
tot_cur_cells += (increment * cell_lv);
}
var current_cell:Sprite = new cell (); // hexagon from library
honeycomb.addChild (current_cell);
placeCell (current_cell, case_counter, current_case, cell_lv);
}
function centerHoneycomb (e:Event):void {
honeycomb.x = stage.stageWidth / 2
honeycomb.y = Math.round (stage.stageHeight / 2);
}
stage.addEventListener (Event.RESIZE, centerHoneycomb)
stage.dispatchEvent (new Event (Event.RESIZE));
}
function placeCell (cell:Sprite, counter:int, current_case:int, cell_lv:int):void {
var margin:int = 2;
// THIS IS MY PROBLEM
var start_degree:Number = (360 / (cell_lv * 6));
var angle:Number = (start_degree * ((current_case - 1) + counter) - start_degree);
var radius:Number = (cell.width + margin) * cell_lv;
cell.x = radius * Math.cos (angle);
cell.y = radius * Math.sin (angle);
// end of the problem
if (angle != 0) trace ("LV " + cell_lv + " current_case " + current_case + " counter " + counter + " angle " + angle + " radius " + radius);
else trace ("LV " + cell_lv + " current_case " + current_case + " counter " + counter + " angle " + angle + " radius " + radius);
}
createHoneycomb (64);
if you copy and paste this code, it works but you need to create an hexagon and call it in the actionscript library as cell
how can I do to solve it?
I've also thought to use a switch with the cases to align it, but i think is a little bit buggy doing this

Okay, I really loved this question. It was interesting, and challenging, and I got a working result. I didn’t use any of your code as the base though, but started from scratch, so depending on your final use, you might need to change a bit.
I did however created similar variables to those inside of your cells (in the picture). For each cell you have the following properties:
the iterating variable i is equally to your cell number
the radius r equals your level, and expresses the distance from the center (with 0 being the center)
the position p expresses the position in the current radius
the sector s equals your case, but starts with zero
p % r equals your index
I don’t have an angle, simply because I don’t position the individual hexagons using an angle. Instead I base the position of the (fixed) positions of the hexagons at radius 1 and calculate the missing ones in between.
The following code shows my implementation with 61 (60 + the center; but it’s configurable). You can also see the code in action on Wonderfl.
package
{
import flash.display.Sprite;
public class Comb extends Sprite
{
public function Comb ()
{
Hexagon.scale = 0.5;
this.x = stage.stageWidth / 2;
this.y = stage.stageHeight / 2;
// draw honeycomb with 60 cells
drawComb( 60 );
}
private function drawComb ( n:uint ):void
{
var colors:Array = new Array( 0x33CC33, 0x006699, 0xCC3300, 0x663399, 0xFF9900, 0x336666 );
var sectors:Array = new Array(
new Array( 2, 0 ),
new Array( 1, 1 ),
new Array( -1, 1 ),
new Array( -2, 0 ),
new Array( -1, -1 ),
new Array( 1, -1 ) );
var w:Number = 0.50 * Hexagon.hxWidth;
var h:Number = 0.75 * Hexagon.hxHeight;
var r:uint, p:uint, s:uint;
var hx:Hexagon;
for ( var i:uint = 0; i <= n; i++ )
{
r = getRadius( i );
p = getPosition( i, r );
s = getSector( i, r, p );
// create hexagon
if ( r == 0 )
hx = new Hexagon( 0xCCCCCC );
else
hx = new Hexagon( colors[s] );
hx.x = w * ( r * sectors[s][0] - ( p % r ) * ( sectors[s][0] - sectors[ ( s + 1 ) % 6 ][0] ) );
hx.y = h * ( r * sectors[s][1] - ( p % r ) * ( sectors[s][1] - sectors[ ( s + 1 ) % 6 ][1] ) );
addChild( hx );
}
}
private function getRadius ( i:uint ):uint
{
var r:uint = 0;
while ( i > r * 6 )
i -= r++ * 6;
return r;
}
private function getPosition ( i:uint, r:uint ):uint
{
if ( r == 0 )
return i;
while ( r-- > 0 )
i -= r * 6;
return i - 1;
}
private function getSector ( i:uint, r:uint, s:uint ):uint
{
return Math.floor( s / r );
}
}
}
import flash.display.Shape;
class Hexagon extends Shape
{
public static var hxWidth:Number = 90;
public static var hxHeight:Number = 100;
private static var _scale:Number = 1;
public function Hexagon ( color:uint )
{
graphics.beginFill( color );
graphics.lineStyle( 3, 0xFFFFFF );
graphics.moveTo( 0, -50 );
graphics.lineTo( 45, -25 );
graphics.lineTo( 45, 25 );
graphics.lineTo( 0, 50 ),
graphics.lineTo( -45, 25 );
graphics.lineTo( -45, -25 );
graphics.lineTo( 0, -50 );
this.scaleX = this.scaleY = _scale;
}
public static function set scale ( value:Number ):void
{
_scale = value;
hxWidth = value * 90;
hxHeight = value * 100;
}
public static function get scale ():Number
{
return _scale;
}
}

Related

Detect Colour Using Action Script 3

So I have made a game in scratch which uses the following code block:
I am trying to remake this game in Adobe Animate using Action Script 3 (for a class project), is there a similar way to do this in animate?
It is possible. The trick to do it is to create a tiny-teeny BitmapData object and to draw a small portion of stage under the mouse pointer into that object so that you can obtain the pixel color value.
// BitmapData object and some service objects.
var BD:BitmapData = new BitmapData(3, 3, true);
var R:Rectangle = new Rectangle(0, 0, 3, 3);
var M:Matrix = new Matrix;
// Let's create a TextField so we can output the pixel color value.
var T:TextField = new TextField;
var TF:TextFormat = new TextFormat("_typewriter", 12, 0x000000, true, false, false, null, null, TextFormatAlign.CENTER);
T.x = 10;
T.y = 10;
T.width = 100;
T.height = 18;
T.border = true;
T.background = true;
T.selectable = false;
T.mouseEnabled = false;
T.defaultTextFormat = TF;
addChild(T);
// Lets add some semi-transparent color circles
// so we have colored things to point the mouse at.
for (var i:int = 0; i < 10; i++)
{
var aColor:uint = 0;
aColor |= int(128 + 128 * Math.random()) << 16; // RED
aColor |= int(128 + 128 * Math.random()) << 8; // GREEN
aColor |= int(128 + 128 * Math.random()); // BLUE
var anX:int = stage.stageWidth / 8 + Math.random() * stage.stageWidth * 3 / 4;
var anY:int = stage.stageHeight / 8 + Math.random() * stage.stageHeight * 3 / 4;
var aRadius:int = 50 + 100 * Math.random();
var anAlpha:Number = 0.5 + 0.5 * Math.random();
graphics.beginFill(aColor, anAlpha);
graphics.drawCircle(anX, anY, aRadius);
graphics.endFill();
}
// Now let's watch the mouse every frame.
addEventListener(Event.ENTER_FRAME, onFrame);
function onFrame(e:Event):void
{
// Get pixel color as an RRGGBB String and print it.
T.text = "#" + addZeroes(getColorUnderMouse());
}
function getColorUnderMouse():uint
{
// Adjust Matrix so that we draw the correct piece of screen.
M.tx = -root.mouseX + 1;
M.ty = -root.mouseY + 1;
// Clear the BitmapData and capture the 3x3 piece under the mouse pointer.
BD.fillRect(R, 0xFFFFFFFF);
BD.draw(root, M, null, null, R);
// Read the pixel color value at the center of 3x3 and return it.
return BD.getPixel(1, 1);
}
// This function fixes the hexabinary value with leading
// zeroes if the color value is too small (like 0 = black).
function addZeroes(value:uint, count:uint = 6):String
{
var result:String = value.toString(16).toUpperCase();
while (result.length < count)
{
result = "0" + result;
}
return result;
}

Type Coercion failed: cannot convert []#13a355b9 to flash.display.DisplayObject

I get this error when running, no compile errors.
package {
import flash.display.*;
import flash.events.*;
import flash.utils.*;
public class mainCode extends MovieClip{
//global variables go here
public var circled:Shape = new Shape();
public var circled2:Shape = new Shape();
public var circled3:Shape = new Shape();
public var angled:int = new int();
public var circlearray1:Array = new Array(4);
public var circlearray2:Array = new Array(4);
public function mainCode(){
makeCircle(circled, 100);
makeCircle(circled2, 50);
makeCircle(circled3, 50);
for(var i:int=0; i<4; i++){circlearray1[i] = new Array(20);}
for(var n:int=0; n<4; n++){circlearray2[n] = new Array(20);}
stage.addEventListener(Event.ENTER_FRAME, mainLoop);
}
//functions go here
private function mainLoop(e:Event){
trace(1);
angled+=1;
angled%=360;
circled.x = stage.stageWidth / 2;
circled.y = stage.stageHeight/ 2;
circled2.x = circled.x + 100*Math.cos(radians(angled));
circled2.y = circled.y + 100*Math.sin(radians(angled));
circled3.x = circled.x + 100*Math.cos(radians((angled + 180) % 360));
circled3.y = circled.y + 100*Math.sin(radians((angled + 180) % 360));
trace(2);
for (var i:int = 0; i < 4; i++)
{
trace(3);
if (circlearray1[i][0] != undefined)
{removeChild(circlearray1[i][0]);}
if (circlearray2[i][0] != undefined)
{removeChild(circlearray2[i][0]);}
trace(4);
for (var m:int = 0; m < 19; m++)
{
circlearray1[i][m] = circlearray1[m+1];
circlearray2[i][m] = circlearray2[m+1];
}
circlearray1[i][19] = new Shape();
circlearray2[i][19] = new Shape();
makeCircle(circlearray1[i][19], 25);
makeCircle(circlearray2[i][19], 25);
circlearray1[i][19].x = circled2.x + 50*Math.cos(radians(((-angled + (i*90)) * 2) % 360));
circlearray1[i][19].y = circled2.y + 50*Math.sin(radians(((-angled + (i*90)) * 2) % 360));
circlearray2[i][19].x = circled3.x + 50*Math.cos(radians(((-angled + (i*90)) * 2) % 360));
circlearray2[i][19].y = circled3.y + 50*Math.sin(radians(((-angled + (i*90)) * 2) % 360));
trace(8);
}
}
private function makeCircle(circles:Shape, radius:int)
{
circles.graphics.clear();
circles.graphics.lineStyle(2,0x000000);
circles.graphics.beginFill(0x990000);
circles.graphics.drawCircle(0,0,radius);
circles.graphics.endFill();
addChild(circles);
}
private function degrees(radians:Number):Number
{
return radians * 180/Math.PI;
}
private function radians(degrees:Number):Number
{
return degrees * Math.PI / 180;
}
private function killCircle(circlei:Shape):void {
removeChild(circlei);
}
}
}
I've traced it down to {removeChild(circlearray1[i][0]);}, which seems to be returning the error. I have no idea why it's doing this, I've tried alternatives such as circlearray1[i][0].parent.removeChild(circlearray1[i][0]); and if (circlearray1[i][0] is Shape) ... , but no dice.
For reference, I'm trying to make some circles circle around other circles, but have the outermost circles leave an "image lag" (or afterimage) while moving. I do this by creating objects and deleting them using for loops and multidimensional arrays, as I dont feel like typing out 50 create object calls manually and edit each one manually.
Probably, this part:
for (var m:int = 0; m < 19; m++)
{
circlearray1[i][m] = circlearray1[m+1];
circlearray2[i][m] = circlearray2[m+1];
}
You assign to endpoint elements, which you initially assume to be Shapes the elements which are Arrays. Then later you go at circlearray1[i][0] assuming it is Shape if it is not empty, but it is already an Array due to the assignment above. It's probably a typo and you meant this:
for (var m:int = 0; m < 19; m++)
{
circlearray1[i][m] = circlearray1[i][m+1];
circlearray2[i][m] = circlearray2[i][m+1];
}
There's actually a more clean way to do that. Array in AS3 is a lot like C# ArrayList, you can insert elements to either end and extract elements from either end without re-indexing elements manually:
trace(3);
// If you are sure the first element is not empty
// you can get its reference removing them from
// the Array at the same time.
removeChild(circlearray1[i].shift());
removeChild(circlearray2[i].shift());
trace(4);
// All Array elements are automatically shifted
// by -1 so you don't need to move each of them manually.
circlearray1[i][19] = new Shape();
circlearray2[i][19] = new Shape();

Properly hovering over isometric tile sprite

I have four classes: Room, TileGrid, HoverTile, and Tile.
Room is composed of walls and a TileGrid. TileGrid is made out of Tile. Currently, I use this code to generate a TileGrid out of Tiles:
this.mapArray = [[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1, 1, 1, 1, 1, 1, 1]];
this._mapHeight = this.mapArray.length;
this._mapWidth = this.mapArray[0].length;
this._tileHeight = 23;
this._tileWidth = 46;
var initialX:Number = 260;
var initialY:Number = 150;
for (var isoY:int = 0; isoY < mapArray.length; isoY++)
{
for (var isoX:int = 0; isoX < mapArray[isoY].length; isoX++)
{
if (isoX == 0 && isoY == 0)
{
var _tile:Tile = new Tile();
_tile.x = initialX;
_tile.y = initialY;
this.addChild(_tile);
}
if (this.mapArray[isoY][isoX] == 1)
{
var _tile:Tile = new Tile();
_tile.x = initialX - (isoX * 20) - (isoY * 20);
_tile.y = initialY - (isoX * 10) + (isoY * 10);
addChild(_tile);
_tile.addEventListener(MouseEvent.MOUSE_OVER, updateHover);
}
}
}
My current issue is that I want to add a white square around the tile that a mouse is hovering over. The code I used to use wasn't sufficient, because transparent parts of the Tile sprite are still counted as part of it. So even if I'm pointing at another Tile2 (which is next to Tile1), for example, if I'm not far enough onto Tile2, it'll highlight Tile1.
So, here's the current code I'm using:
public function updateHover(e:MouseEvent):void
{
var mX:int = e.stageX - (_tileWidth / 2);
var tPoint:Point = pointToXY(mX, e.stageY);
var isoX = tPoint.x;
var isoY = tPoint.y;
if (isoX >= 0 && isoY >= 0)
{
if (isoY < mapArray.length)
{
if (isoX < mapArray[0].length)
{
tPoint = xyToPoint(isoX, isoY);
_tileHover.x = tPoint.x;
_tileHover.y = tPoint.y;
_tileHover.visible = true;
return;
}
}
}
_tileHover.visible = false;
}
public function pointToXY(x:int, y:int):Point
{
x -= 260;
y -= 150;
var pRatio:int = (_tileWidth / 2) / (_tileHeight / 2);
var tX:int = (y + x / pRatio) * (pRatio / 2) / (_tileWidth / 2);
var tY:int = (y - x / pRatio) * (pRatio / 2) / (_tileWidth / 2);
return new Point(tX, tY);
}
public function xyToPoint(x:int, y:int):Point
{
x -= 1;
var worldPoint:Point = new Point(0, 0);
worldPoint.x = (x * (_tileWidth / 2)) - (y * (_tileWidth / 2));
worldPoint.y = (x * (_tileHeight / 2)) + (y * (_tileHeight / 2));
worldPoint.x = worldPoint.x + (_tileWidth / 2);
worldPoint.y = worldPoint.y + (_tileHeight / 2);
worldPoint.x += 260;
worldPoint.y += 150;
return worldPoint;
}
Sorry I have to post so many code blocks. Now, 260 and 150 are the default starting point for the entire room. That said, I'm really confused on how to get the last two functions in particular to work so that they'll give me the correct answer. This is what I expected from using this code:
That would be perfect. But, again, I don't know why the code isn't working. The sizes are all correct and I believe the offset is, too. So, I'm
First, you should add the listener to this, not to _tile, because then you are locked to stage coordinates to determine the tile that's selected, which is not good. Second, your listener should be against MouseEvent.MOUSE_MOVE event, not over, this way you'll constantly get updated mouse coords to properly move your rectangle over tiles. And you have a minor error out there, you have a (0,0) tile created two times, one being inactive.
for (var isoY:int = 0; isoY < mapArray.length; isoY++)
{
for (var isoX:int = 0; isoX < mapArray[isoY].length; isoX++)
{
if (this.mapArray[isoY][isoX] == 1)
{
var _tile:Tile = new Tile();
_tile.x = initialX - (isoX * 20) - (isoY * 20);
_tile.y = initialY - (isoX * 10) + (isoY * 10);
addChild(_tile);
}
}
}
this.addEventListener(MouseEvent.MOUSE_MOVE, updateHover);
Also, it'll be better that you'd store (x,y) pairs on the array (as tiles, most likely), so that your initial array of zeroes and ones would transform into an array of Tile objects. To do that, you first do this:
this.tileArray=[];
for (var i:int=0;i<this.mapArray.length;i++)
this.tileArray.push(new Array(this.mapArray[i].length));
This will create an array of nulls that matches your mapArray by dimensions, that will serve as placeholder for created Tile objects. After you do this, you call this.tileArray[isoY][isoX]=_tile; to place the newly created tile to its place. After that, you can rewrite your listener to this:
public function updateHover(e:MouseEvent):void
{
var p:Point=pointToXY(e.localX,e.localY);
_tileHover.visible = false; // hide hover for now
if ((p.y<0) || (p.y>=tileArray.length)) return; // range error on Y
if ((p.x<0)||(p.x>=tileArray[p.y].length)) return; // range error on X
if (!tileArray[p.y][p.x]) return; // no tile
var _tile:Tile=tileArray[p.y][p.x];
_tileHover.x=_tile.x;
_tileHover.y=_tile.y; // no need to convert xyToPoint() we have coords stored in tile
_tileHover.visible=true;
}

My dynamically created movie clips are not going away

I am working on a breakout type game with dynamically-created squares. When a brick gets hit, I fire off a removeMC function that is supposed to turn it white and make it shrink down in size. However, sometimes the squares just turn white instead of tweening down. I have included the script for the removeMV function as well as the function that creates the squares. Arg1 is the square to be removed. I apologize for the bad naming conventions, but the fire got corrupted and had to be recovered with a decompiler.
This is the removeMC function
public function removeMC(arg1:flash.display.Sprite):*
{
this.score --;
this.uiBar.txtScore.text=this.score;
var loc1:*=new flash.geom.ColorTransform();
loc1.color = 0xffffFF;
arg1.transform.colorTransform = loc1;
TweenMax.to(arg1, 0.4, {colorTransform:{tint:0x0000ff, tintAmount:1}});
var loc2:*=this.ballMC.x - this.ballMC.x % 30;
var loc3:*=this.ballMC.y - this.ballMC.y % 30;
arg1.scaleY = arg1.scaleY * -1;
// trace("Ball x:" + this.ballMC.x + " ballY:" + this.ballMC.y + " block x:" + loc2 + " block y:" + loc3);
var loc4:*=new fl.transitions.Tween(arg1, "width", null, 30, 0, 0.5, true);
var loc5:*=new fl.transitions.Tween(arg1, "height", null, 30, 0, 0.5, true);
var loc6:*=new fl.transitions.Tween(arg1, "x", null, 0, loc2, 0.5, true);
var loc7:*=new fl.transitions.Tween(arg1, "y", null, 0, loc3, 0.5, true);
this.brickArray.splice(this.indexSearch(this.brickArray, arg1), 1);
arg1.x+=3000;//failsafe to remove the squares. doesn't work
arg1.y+=3000;
return;
}
This is the function that creates the squares. Arg 1 and arg2 are the width and height in squares
public function createImgNodeGrid(arg1:int=1, arg2:int=1, arg3:Number=0):void
{
var loc6:*=0;
var loc7:*=null;
var loc8:*=null;
var loc9:*=null;
var loc10:*=null;
var loc1:*=this._img.width / arg1;
var loc2:*=this._img.height / arg2;
var loc3:*=arg1 * arg2;
this._imgNodes = [];
var loc4:*=0;
var loc5:*=0;
while (loc5 < arg1)
{
loc6 = 0;
while (loc6 < arg2)
{
loc7 = new flash.geom.Rectangle(loc5 * loc1, loc6 * loc2, loc1, loc2);
loc8 = new flash.display.BitmapData(loc1, loc2, true);
loc8.copyPixels(this._img.bitmapData, loc7, this.zero);
loc9 = new flash.display.Bitmap(loc8);
loc9.x = loc5 * (loc1 + arg3);
loc9.y = loc6 * (loc2 + arg3);
var loc11:*;
this._imgNodes[loc11 = loc4++] = loc9;
loc10 = new flash.display.Sprite();
loc10.mouseChildren = false;
this.brickArray.push(loc10);
loc10.addChild(loc9);
this._tiledImg.addChild(loc10);
++loc6;
}
++loc5;
}
return;
}
I may miss the mark here as its quite hard to read that lot, however, this is my suggestion for your removeMc function. I noticed you have TweenMax, so I would utilise that as opposed to the flash tweener class.
public function removeMC(arg1:flash.display.Sprite):*
{
this.score --;
this.uiBar.txtScore.text=this.score;
var loc1:*=new flash.geom.ColorTransform();
loc1.color = 0xffffFF;
arg1.transform.colorTransform = loc1;
var loc2:*=this.ballMC.x - this.ballMC.x % 30;
var loc3:*=this.ballMC.y - this.ballMC.y % 30;
TweenMax.to(arg1, 0.4, {scaleY:0, scaleX:0, x:loc2, y:loc3, colorTransform:{tint:0x0000ff, tintAmount:1}, onComplete:tweenComplete(arg1)});
// trace("Ball x:" + this.ballMC.x + " ballY:" + this.ballMC.y + " block x:" + loc2 + " block y:" + loc3);
this.brickArray.splice(this.indexSearch(this.brickArray, arg1), 1);
return;
}
private function tweenComplete(square:Sprite):void
{
this.removeChild(square);
}
That should result in the square shrinking to 0 size and becoming white at the same time. Then once it completes it will remove the child completely.

How can I implement Lanczos resampling after every canvas transform without having to make a new canvas?

UPDATE: Once I got this demo working... holy smokes, it's SLOW, like 12-16 seconds for only a level 2 render (when image is around 1000x2000 pixels). This is not even worth bothering with.
I found this really awesome and hopeful looking code in the top answer here: Resizing an image in an HTML5 canvas
//returns a function that calculates lanczos weight
function lanczosCreate(lobes){
return function(x){
if (x > lobes)
return 0;
x *= Math.PI;
if (Math.abs(x) < 1e-16)
return 1
var xx = x / lobes;
return Math.sin(x) * Math.sin(xx) / x / xx;
}
}
//elem: canvas element, img: image element, sx: scaled width, lobes: kernel radius
function thumbnailer(elem, img, sx, lobes){
this.canvas = elem;
elem.width = img.width;
elem.height = img.height;
elem.style.display = "none";
this.ctx = elem.getContext("2d");
this.ctx.drawImage(img, 0, 0);
this.img = img;
this.src = this.ctx.getImageData(0, 0, img.width, img.height);
this.dest = {
width: sx,
height: Math.round(img.height * sx / img.width),
};
this.dest.data = new Array(this.dest.width * this.dest.height * 3);
this.lanczos = lanczosCreate(lobes);
this.ratio = img.width / sx;
this.rcp_ratio = 2 / this.ratio;
this.range2 = Math.ceil(this.ratio * lobes / 2);
this.cacheLanc = {};
this.center = {};
this.icenter = {};
setTimeout(this.process1, 0, this, 0);
}
thumbnailer.prototype.process1 = function(self, u){
self.center.x = (u + 0.5) * self.ratio;
self.icenter.x = Math.floor(self.center.x);
for (var v = 0; v < self.dest.height; v++) {
self.center.y = (v + 0.5) * self.ratio;
self.icenter.y = Math.floor(self.center.y);
var a, r, g, b;
a = r = g = b = 0;
for (var i = self.icenter.x - self.range2; i <= self.icenter.x + self.range2; i++) {
if (i < 0 || i >= self.src.width)
continue;
var f_x = Math.floor(1000 * Math.abs(i - self.center.x));
if (!self.cacheLanc[f_x])
self.cacheLanc[f_x] = {};
for (var j = self.icenter.y - self.range2; j <= self.icenter.y + self.range2; j++) {
if (j < 0 || j >= self.src.height)
continue;
var f_y = Math.floor(1000 * Math.abs(j - self.center.y));
if (self.cacheLanc[f_x][f_y] == undefined)
self.cacheLanc[f_x][f_y] = self.lanczos(Math.sqrt(Math.pow(f_x * self.rcp_ratio, 2) + Math.pow(f_y * self.rcp_ratio, 2)) / 1000);
weight = self.cacheLanc[f_x][f_y];
if (weight > 0) {
var idx = (j * self.src.width + i) * 4;
a += weight;
r += weight * self.src.data[idx];
g += weight * self.src.data[idx + 1];
b += weight * self.src.data[idx + 2];
}
}
}
var idx = (v * self.dest.width + u) * 3;
self.dest.data[idx] = r / a;
self.dest.data[idx + 1] = g / a;
self.dest.data[idx + 2] = b / a;
}
if (++u < self.dest.width)
setTimeout(self.process1, 0, self, u);
else
setTimeout(self.process2, 0, self);
};
thumbnailer.prototype.process2 = function(self){
self.canvas.width = self.dest.width;
self.canvas.height = self.dest.height;
self.ctx.drawImage(self.img, 0, 0);
self.src = self.ctx.getImageData(0, 0, self.dest.width, self.dest.height);
var idx, idx2;
for (var i = 0; i < self.dest.width; i++) {
for (var j = 0; j < self.dest.height; j++) {
idx = (j * self.dest.width + i) * 3;
idx2 = (j * self.dest.width + i) * 4;
self.src.data[idx2] = self.dest.data[idx];
self.src.data[idx2 + 1] = self.dest.data[idx + 1];
self.src.data[idx2 + 2] = self.dest.data[idx + 2];
}
}
self.ctx.putImageData(self.src, 0, 0);
self.canvas.style.display = "block";
}
...
img.onload = function() {
var canvas = document.createElement("canvas");
new thumbnailer(canvas, img, 188, 3); //this produces lanczos3
//but feel free to raise it up to 8. Your client will appreciate
//that the program makes full use of his machine.
document.body.appendChild(canvas);
}
However, this implementation loads an image and renders it, end of story.
I have been trying to re-implement this code so that it does the filtering every time an existing canvas is scaled (think, zooming in and out of an image or document) without having to load a new image or create a new canvas.
How can I adapt it to work this way? Or is that even possible?
What you want to do is something like a singleton to reuse your canvas object. This will let you save the cost of create a new canvas object each time and you will reuse the same object
function getCanvas(){
var canvas;
if (typeof canvas === "undefined"){ canvas = document.createElement("canvas");}
return canvas;
}
img.onload = function() {
var canvas = getCanvas("canvas");
.... THE REST OF YOUR CODE .......
}
.
However this is not what slows your code, image scaling Algorithms are really heavy algorithms with intensive cpu use "usually make use of gpu acceleration at a really low level", and use advanced techniques like multiple bufferr and so others. here is a interesting tutorial in java.net on how image scaling works, it is in java but you can interpolate to any language.
Javascript is not ready for this techniques, so I recommend you to use the transformations available in the canvas api, as in the tutorial you read the efficient way is using the canvas2Dcontext.
var ctx = canvas.getContext("2d");
ctx.scale(2,2);