├── .gitignore ├── README.md ├── demo ├── .actionScriptProperties ├── .project ├── .settings │ └── org.eclipse.core.resources.prefs ├── assets │ ├── bg.png │ ├── particle.pex │ ├── particle.png │ ├── pixelmask.fla │ ├── pixelmask.png │ ├── pixelmask.swf │ ├── pixelmask.xml │ ├── shimmer.png │ ├── shimmer.psd │ ├── shimmer_button.png │ └── shimmer_button.psd ├── bin-debug │ ├── PixelMask_Shimmer.swf │ └── org │ │ └── axgl │ │ └── resource │ │ ├── button.png │ │ ├── font.png │ │ └── icon.png └── src │ ├── MaskSprite.as │ ├── PixelMask.as │ ├── PixelMaskScene.as │ └── ShimmerButtonScene.as └── src └── starling └── extensions └── pixelmask └── PixelMaskDisplayObject.as /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pixelmask 2 | ========= 3 | 4 | A Starling Extension to provide pixel based masking for Starling display objects 5 | 6 | For documentation and usage, please see the wiki: 7 | 8 | http://wiki.starling-framework.org/extensions/pixelmask 9 | 10 | /demo/ Notes: 11 | 12 | To run the demo, the following are required as linked source paths: 13 | 14 | - Pixelmask (found in the /src directory of this repository) 15 | - Starling (found here: https://github.com/PrimaryFeather/Starling-Framework) 16 | - Starling Particles Extension (found here: https://github.com/PrimaryFeather/Starling-Extension-Particle-System) 17 | 18 | IMPORTANT NOTE: 19 | Please use the latest version of Starling. There was a crucial performance improvement post 1.4's release that has a significantly positive impact on users of Pixelmask! 20 | 21 | Author: 22 | Jonathan Hart 23 | jonathan.hart@gmail.com 24 | -------------------------------------------------------------------------------- /demo/.actionScriptProperties: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /demo/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | PixelMaskProject 4 | 5 | 6 | 7 | 8 | 9 | com.adobe.flexbuilder.project.flexbuilder 10 | 11 | 12 | 13 | 14 | 15 | com.adobe.flexbuilder.project.actionscriptnature 16 | 17 | 18 | 19 | [source path] Axel-src 20 | 2 21 | /Users/jonathan/Documents/git/Axel/src 22 | 23 | 24 | [source path] Starling-Extension-Particle-System-src 25 | 2 26 | /Users/jonathan/Documents/git/Starling-Extension-Particle-System/src 27 | 28 | 29 | [source path] pixelmask-src 30 | 2 31 | /Users/jonathan/Documents/git/pixelmask/src 32 | 33 | 34 | [source path] starling-src 35 | 2 36 | /Users/jonathan/Documents/git/Starling-Framework/starling/src 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /demo/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | #Tue Nov 20 00:28:26 PST 2012 2 | eclipse.preferences.version=1 3 | encoding/=utf-8 4 | -------------------------------------------------------------------------------- /demo/assets/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/assets/bg.png -------------------------------------------------------------------------------- /demo/assets/particle.pex: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /demo/assets/particle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/assets/particle.png -------------------------------------------------------------------------------- /demo/assets/pixelmask.fla: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/assets/pixelmask.fla -------------------------------------------------------------------------------- /demo/assets/pixelmask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/assets/pixelmask.png -------------------------------------------------------------------------------- /demo/assets/pixelmask.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/assets/pixelmask.swf -------------------------------------------------------------------------------- /demo/assets/pixelmask.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/assets/pixelmask.xml -------------------------------------------------------------------------------- /demo/assets/shimmer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/assets/shimmer.png -------------------------------------------------------------------------------- /demo/assets/shimmer.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/assets/shimmer.psd -------------------------------------------------------------------------------- /demo/assets/shimmer_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/assets/shimmer_button.png -------------------------------------------------------------------------------- /demo/assets/shimmer_button.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/assets/shimmer_button.psd -------------------------------------------------------------------------------- /demo/bin-debug/PixelMask_Shimmer.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/bin-debug/PixelMask_Shimmer.swf -------------------------------------------------------------------------------- /demo/bin-debug/org/axgl/resource/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/bin-debug/org/axgl/resource/button.png -------------------------------------------------------------------------------- /demo/bin-debug/org/axgl/resource/font.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/bin-debug/org/axgl/resource/font.png -------------------------------------------------------------------------------- /demo/bin-debug/org/axgl/resource/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonathanhart/pixelmask/9d0fdd570056108d22c69885bb007105c165119a/demo/bin-debug/org/axgl/resource/icon.png -------------------------------------------------------------------------------- /demo/src/MaskSprite.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import starling.core.Starling; 4 | import starling.display.MovieClip; 5 | import starling.display.Sprite; 6 | import starling.textures.Texture; 7 | import starling.textures.TextureAtlas; 8 | 9 | public class MaskSprite extends Sprite 10 | { 11 | [Embed(source="../assets/pixelmask.xml",mimeType="application/octet-stream")] 12 | private var AnimData:Class; 13 | [Embed(source="../assets/pixelmask.png")] 14 | private var AnimTexture:Class; 15 | 16 | public function MaskSprite() 17 | { 18 | super(); 19 | 20 | var heroTexture:Texture = Texture.fromEmbeddedAsset(AnimTexture); 21 | var heroXmlData:XML = XML(new AnimData()); 22 | var heroTextureAtlas:TextureAtlas = 23 | new TextureAtlas(heroTexture, heroXmlData); 24 | //Fetch the sprite sequence form the texture using their name 25 | var _mc:MovieClip = new MovieClip(heroTextureAtlas.getTextures("mask_text"), 18); 26 | addChild(_mc); 27 | Starling.juggler.add(_mc); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /demo/src/PixelMask.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.display.Sprite; 4 | import flash.display.StageAlign; 5 | import flash.display.StageScaleMode; 6 | 7 | import starling.core.Starling; 8 | 9 | [SWF(frameRate=60,width=800,height=300)] 10 | public class PixelMask extends Sprite 11 | { 12 | private var _starling:Starling; 13 | 14 | public function PixelMask() 15 | { 16 | stage.align = StageAlign.TOP_LEFT; 17 | stage.scaleMode = StageScaleMode.NO_SCALE; 18 | 19 | _starling = new Starling(PixelMaskScene, stage); 20 | _starling.start(); 21 | //_starling.showStats = true; 22 | _starling.stage.color = 0xff222222; 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /demo/src/PixelMaskScene.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import starling.core.Starling; 4 | import starling.display.DisplayObjectContainer; 5 | import starling.display.Image; 6 | import starling.events.Event; 7 | import starling.events.Touch; 8 | import starling.events.TouchEvent; 9 | import starling.events.TouchPhase; 10 | import starling.extensions.PDParticleSystem; 11 | import starling.extensions.pixelmask.PixelMaskDisplayObject; 12 | import starling.textures.Texture; 13 | 14 | public class PixelMaskScene extends DisplayObjectContainer 15 | { 16 | // embed configuration XML 17 | [Embed(source="../assets/particle.pex", mimeType="application/octet-stream")] 18 | private static const ParticleConfig:Class; 19 | 20 | // embed particle texture 21 | [Embed(source = "../assets/particle.png")] 22 | private static const ParticleClass:Class; 23 | 24 | [Embed(source="../assets/bg.png")] 25 | private var BgTexture:Class; 26 | 27 | private var _particleContainer:PixelMaskDisplayObject; 28 | 29 | public function PixelMaskScene() 30 | { 31 | super(); 32 | addEventListener(Event.ADDED_TO_STAGE, handleAddedToStage); 33 | } 34 | 35 | private function handleAddedToStage(event:Event) : void 36 | { 37 | // background image 38 | var background:Image = new Image(Texture.fromEmbeddedAsset(BgTexture)); 39 | addChild(background); 40 | 41 | // instantiate embedded objects 42 | var psConfig:XML = XML(new ParticleConfig()); 43 | var psTexture:Texture = Texture.fromBitmap(new ParticleClass()); 44 | 45 | // create particle system 46 | var ps:PDParticleSystem = new PDParticleSystem(psConfig, psTexture); 47 | ps.x = stage.stageWidth/2; 48 | ps.y = stage.stageHeight; 49 | ps.scaleY = -1; 50 | Starling.juggler.add(ps); 51 | ps.start(); 52 | 53 | // create mask sprite 54 | var mask:MaskSprite = new MaskSprite(); 55 | mask.x = (stage.stageWidth-mask.width)/2; 56 | mask.y = (stage.stageHeight-mask.height)/2; 57 | addEventListener(TouchEvent.TOUCH, handleClick); 58 | 59 | // apply the masking here: 60 | _particleContainer = new PixelMaskDisplayObject(); 61 | 62 | // NOTE: to test non-animated mode, assign "false" to the second constructor parameter: 63 | //_particleContainer = new PixelMaskDisplayObject(-1, false); 64 | 65 | // uncomment this to see inverted mode: 66 | //_particleContainer.inverted = true; 67 | 68 | addChild(_particleContainer); 69 | _particleContainer.pixelMask = mask; 70 | _particleContainer.addChild(ps); 71 | //_particleContainer.scaleX = _particleContainer.scaleY = .5; 72 | //_particleContainer.x = _particleContainer.y = 100; 73 | } 74 | 75 | private function handleClick (e:TouchEvent) : void 76 | { 77 | var touch:Touch = e.getTouch(this, TouchPhase.ENDED); 78 | if (touch) _particleContainer.inverted = !_particleContainer.inverted; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /demo/src/ShimmerButtonScene.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.utils.setInterval; 4 | import flash.utils.setTimeout; 5 | 6 | import starling.animation.Tween; 7 | import starling.core.Starling; 8 | import starling.display.DisplayObjectContainer; 9 | import starling.display.Image; 10 | import starling.display.Sprite; 11 | import starling.events.Event; 12 | import starling.extensions.pixelmask.PixelMaskDisplayObject; 13 | import starling.text.TextField; 14 | import starling.textures.Texture; 15 | 16 | public class ShimmerButtonScene extends DisplayObjectContainer 17 | { 18 | 19 | // NOTE ON THIS DEMO: 20 | // This is a button shimmer demo that illustrates putting a sheen on any button and animating it. 21 | // For extra credit, it also has a shimmer on dynamic text that sits on top of the button 22 | // One shimmer is masked to the button, the other is masked to the text. 23 | 24 | // embed shimmer texture 25 | [Embed(source="../assets/shimmer.png")] 26 | private var ShimmerClass:Class; 27 | 28 | [Embed(source="../assets/bg.png")] 29 | private var BgTexture:Class; 30 | 31 | [Embed(source="../assets/shimmer_button.png")] 32 | private var ButtonTexture:Class; 33 | 34 | private var _shimmer:Image; 35 | private var _buttonShimmer:Image; 36 | private var _text:TextField; 37 | private var _buttonShimmerContainer:PixelMaskDisplayObject; 38 | 39 | private var _txt:String = "PIXELMASK"; 40 | public function ShimmerButtonScene() 41 | { 42 | super(); 43 | addEventListener(Event.ADDED_TO_STAGE, handleAddedToStage); 44 | } 45 | 46 | private function handleAddedToStage(event:Event) : void 47 | { 48 | // background image 49 | var bgTexture:Texture = Texture.fromEmbeddedAsset(BgTexture); 50 | addChild(new Image(bgTexture)); 51 | 52 | // container 53 | var container:Sprite = new Sprite(); 54 | 55 | var buttonTexture:Texture = Texture.fromEmbeddedAsset(ButtonTexture); 56 | var button:Image = new Image(buttonTexture); 57 | container.addChild(button); 58 | 59 | // textfield 60 | _text = new TextField(button.width, button.height, "P"); 61 | _text.format.setTo("Helvetica Bold", 64, 0x222222); 62 | 63 | // a little nudge to center the text vertically 64 | _text.y += 5; 65 | 66 | _buttonShimmerContainer = new PixelMaskDisplayObject(); 67 | _buttonShimmerContainer.pixelMask = button; 68 | 69 | var shimmerTexture:Texture = Texture.fromEmbeddedAsset(ShimmerClass); 70 | _buttonShimmer = new Image(shimmerTexture); 71 | 72 | _buttonShimmer.alpha = 0.4; 73 | _buttonShimmerContainer.addChild(_buttonShimmer); 74 | container.addChild(_text); 75 | container.addChild(_buttonShimmerContainer); 76 | // shimmer 77 | _shimmer = new Image(shimmerTexture); 78 | 79 | // apply the masking here: 80 | var textMaskContainer:PixelMaskDisplayObject = new PixelMaskDisplayObject(); 81 | textMaskContainer.pixelMask = _text; 82 | textMaskContainer.addChild(_shimmer); 83 | 84 | container.addChild(textMaskContainer); 85 | container.x = (stage.stageWidth - button.width)/2; 86 | container.y = (stage.stageHeight - button.height)/2; 87 | addChild(container); 88 | 89 | setInterval(updateText, 500); 90 | fireShimmer(); 91 | } 92 | 93 | private function updateText () : void 94 | { 95 | // this is just to illustrate that the text is dynamic 96 | _txt = _txt.substr(1) + _txt.substr(0,1); 97 | _text.text = _txt.substr(0,1); 98 | } 99 | 100 | private function fireShimmer() : void 101 | { 102 | _shimmer.visible = false; 103 | _buttonShimmerContainer.visible = false; 104 | setTimeout(performShimmer, 1000); 105 | 106 | function performShimmer() : void 107 | { 108 | _shimmer.visible = true; 109 | _buttonShimmerContainer.visible = true; 110 | 111 | _shimmer.x = _buttonShimmer.x = -200; 112 | var tween:Tween = new Tween(_shimmer, 2.5); 113 | tween.animate("x", _text.textBounds.width+200); 114 | tween.onComplete = fireShimmer; 115 | Starling.juggler.add(tween); 116 | 117 | var tweenButton:Tween = new Tween(_buttonShimmer, tween.totalTime); 118 | tweenButton.animate("x", _text.textBounds.width+200); 119 | Starling.juggler.add(tweenButton); 120 | } 121 | } 122 | } 123 | } -------------------------------------------------------------------------------- /src/starling/extensions/pixelmask/PixelMaskDisplayObject.as: -------------------------------------------------------------------------------- 1 | package starling.extensions.pixelmask 2 | { 3 | import flash.display3D.Context3DBlendFactor; 4 | import flash.geom.Matrix; 5 | import flash.geom.Rectangle; 6 | 7 | import starling.core.Starling; 8 | import starling.display.BlendMode; 9 | import starling.display.DisplayObject; 10 | import starling.display.DisplayObjectContainer; 11 | import starling.display.Quad; 12 | import starling.events.Event; 13 | import starling.rendering.Painter; 14 | import starling.textures.RenderTexture; 15 | import starling.utils.Pool; 16 | 17 | public class PixelMaskDisplayObject extends DisplayObjectContainer 18 | { 19 | private static const MASK_MODE_NORMAL:String = "mask"; 20 | private static const MASK_MODE_INVERTED:String = "maskInverted"; 21 | 22 | private var _mask:DisplayObject; 23 | private var _renderTexture:RenderTexture; 24 | private var _maskRenderTexture:RenderTexture; 25 | 26 | private var _quad:Quad; 27 | private var _maskQuad:Quad; 28 | 29 | private var _superRenderFlag:Boolean = false; 30 | private var _scaleFactor:Number; 31 | private var _isAnimated:Boolean = true; 32 | private var _maskRendered:Boolean = false; 33 | 34 | private static var sIdentity:Matrix = new Matrix(); 35 | 36 | public function PixelMaskDisplayObject(scaleFactor:Number=-1, isAnimated:Boolean=true) 37 | { 38 | super(); 39 | 40 | BlendMode.register(MASK_MODE_NORMAL, Context3DBlendFactor.ZERO, Context3DBlendFactor.SOURCE_ALPHA); 41 | BlendMode.register(MASK_MODE_INVERTED, Context3DBlendFactor.ZERO, Context3DBlendFactor.ONE_MINUS_SOURCE_ALPHA); 42 | 43 | _isAnimated = isAnimated; 44 | _scaleFactor = scaleFactor; 45 | 46 | _quad = new Quad(100, 100); 47 | _maskQuad = new Quad(100, 100); 48 | _maskQuad.blendMode = MASK_MODE_NORMAL; 49 | 50 | // Handle lost context. By using the conventional event, we can make a weak listener. 51 | // This avoids memory leaks when people forget to call "dispose" on the object. 52 | Starling.current.stage3D.addEventListener(Event.CONTEXT3D_CREATE, 53 | onContextCreated, false, 0, true); 54 | } 55 | 56 | override public function dispose():void 57 | { 58 | clearRenderTextures(); 59 | 60 | _quad.dispose(); 61 | _maskQuad.dispose(); 62 | 63 | Starling.current.stage3D.removeEventListener(Event.CONTEXT3D_CREATE, onContextCreated); 64 | super.dispose(); 65 | } 66 | 67 | private function onContextCreated(event:Object):void 68 | { 69 | refreshRenderTextures(); 70 | } 71 | 72 | public function get isAnimated():Boolean { return _isAnimated; } 73 | public function set isAnimated(value:Boolean):void { _isAnimated = value; } 74 | 75 | public function get inverted():Boolean { return _maskQuad.blendMode == MASK_MODE_INVERTED; } 76 | public function set inverted(value:Boolean):void 77 | { 78 | _maskQuad.blendMode = value ? MASK_MODE_INVERTED : MASK_MODE_NORMAL; 79 | } 80 | 81 | public function get pixelMask():DisplayObject { return _mask; } 82 | public function set pixelMask(value:DisplayObject):void 83 | { 84 | _mask = value; 85 | 86 | if (value) 87 | { 88 | if (_mask.width == 0 || _mask.height == 0) 89 | throw new Error ("Mask must have dimensions. Current dimensions are " + 90 | _mask.width + "x" + _mask.height + "."); 91 | 92 | refreshRenderTextures(); 93 | } 94 | else 95 | { 96 | clearRenderTextures(); 97 | } 98 | } 99 | 100 | private function clearRenderTextures():void 101 | { 102 | if (_maskRenderTexture) _maskRenderTexture.dispose(); 103 | if (_renderTexture) _renderTexture.dispose(); 104 | } 105 | 106 | private function refreshRenderTextures():void 107 | { 108 | if (_mask) 109 | { 110 | clearRenderTextures(); 111 | 112 | var maskBounds:Rectangle = _mask.getBounds(_mask, Pool.getRectangle()); 113 | var maskWidth:Number = maskBounds.width; 114 | var maskHeight:Number = maskBounds.height; 115 | Pool.putRectangle(maskBounds); 116 | 117 | _renderTexture = new RenderTexture(maskWidth, maskHeight, false, _scaleFactor); 118 | _maskRenderTexture = new RenderTexture(maskWidth, maskHeight, false, _scaleFactor); 119 | 120 | // quad using the new render texture 121 | _quad.texture = _renderTexture; 122 | _quad.readjustSize(); 123 | 124 | // quad to blit the mask onto 125 | _maskQuad.texture = _maskRenderTexture; 126 | _maskQuad.readjustSize(); 127 | } 128 | 129 | _maskRendered = false; 130 | } 131 | 132 | public override function render(painter:Painter):void 133 | { 134 | if (_isAnimated || (!_isAnimated && !_maskRendered)) 135 | { 136 | painter.finishMeshBatch(); 137 | painter.excludeFromCache(this); 138 | 139 | if (_superRenderFlag || !_mask) 140 | { 141 | super.render(painter); 142 | } 143 | else 144 | { 145 | if (_mask) 146 | { 147 | _maskRenderTexture.draw(_mask, sIdentity); 148 | _renderTexture.drawBundled(drawRenderTextures); 149 | 150 | painter.pushState(); 151 | painter.state.transformModelviewMatrix(_mask.transformationMatrix); 152 | 153 | _quad.render(painter); 154 | _maskRendered = true; 155 | 156 | painter.popState(); 157 | } 158 | } 159 | } 160 | else 161 | { 162 | _quad.render(painter); 163 | } 164 | } 165 | 166 | private function drawRenderTextures():void 167 | { 168 | var matrix:Matrix = Pool.getMatrix(); 169 | matrix.copyFrom(_mask.transformationMatrix); 170 | matrix.invert(); 171 | 172 | _superRenderFlag = true; 173 | _renderTexture.draw(this, matrix); 174 | _superRenderFlag = false; 175 | _renderTexture.draw(_maskQuad, sIdentity); 176 | 177 | Pool.putMatrix(matrix); 178 | } 179 | } 180 | } --------------------------------------------------------------------------------