📜 ⬆️ ⬇️

Pixel effect on Actionscript 3.0

preview

Such is the simple effect. Under a cat source, places comments and explanations.

Thinking about how this or that special effect works, many thoughts come to mind, but it does not always come to realization, in view of the small experience with special effects. “It's time to correct the situation” - I thought. A little thought came up with a simple idea about what would be such a mess. I set myself the task of making the effect of the appearance of the text, as if it were printed by pixel-by-pixel welding, and particles were scattered from the point of welding. And, at the same time, describe it as an article. The writer is really out of me, but I hope that this article will benefit someone.

Algorithm Description

The main effect of this is a piece of Perlin noise ( doc ) cut out according to a given text outline. Above the text is a mask that moves along the X axis to the right, slightly opening the text. In the main loop, we determine where the right edge of the mask is now, and for each Y, along the height of the bitmap with the text, we get an opaque pixel and begin to animate it. Plus, here we show where the point of "welding" is now.

Implementation

The text is cut using BlendMode.ERASE , which erases the pixels of the background object based on the alpha value of the blend object. That is, when alpha is equal to 0xFF, the alpha value of the background channel will be 0x00.
')
noised text
Method of preparing bitmaps with text.
code
private function GetMaskedText(text:String):BitmapData { var tf:TextField = new TextField(); var format:TextFormat = new TextFormat("Arial", 60, 0xFFFFFF, true); tf.defaultTextFormat = format; tf.text = text; tf.width = tf.textWidth + 4.0; tf.height = tf.textHeight + 4.0; tf.filters = [new GlowFilter(0xFFFFFF, 1.0, 2, 2, 4, 3)]; var w:int = tf.width; var h:int = tf.height; var noiseBdata:BitmapData = new BitmapData(w, h, true, 0xFFFFFFFF); //         var channels:int = BitmapDataChannel.GREEN | BitmapDataChannel.RED; //    noiseBdata.perlinNoise( w / 6 , h / 4 , 6 , int(Math.random() * 1000) , false , false , channels , false); //    noiseBdata.colorTransform(noiseBdata.rect, new ColorTransform(1.4, 1.4, 1.4)); var noiseBmp:Bitmap = new Bitmap(noiseBdata); //    var textBdata:BitmapData = new BitmapData(w, h, true, 0x00000000); textBdata.draw(tf); var textBmp:Bitmap = new Bitmap(textBdata); //          var eraseTextBdata:BitmapData = noiseBdata.clone(); eraseTextBdata.draw(noiseBmp); eraseTextBdata.draw(textBmp, null, null, BlendMode.ERASE); var eraseTextBmp:Bitmap = new Bitmap(eraseTextBdata); //  ,      var eraseBackByTextBdata:BitmapData = noiseBdata.clone(); eraseBackByTextBdata.draw(eraseTextBmp, null, null, BlendMode.ERASE); eraseBackByTextBdata.applyFilter( eraseBackByTextBdata , eraseBackByTextBdata.rect , new Point() , new GlowFilter(0xFFFFFF, 1.0, 3, 3) ); noiseBdata.dispose(); textBdata.dispose(); eraseTextBdata.dispose(); head = new BitmapData(6, 6, true, 0xffFFFFFF); return eraseBackByTextBdata; } 



Now you need to add text to the scene, put a mask on top
code
 private var textBdata:BitmapData; private var textBmp:Bitmap; private var txtMask:Shape; private var btnRestart:MiniButton; private function InitalizeLayout():void { screen = new BitmapData(W, H, true, 0x00000000); addChild(new Bitmap(screen)); textBdata = GetMaskedText("..Hello world.."); textBmp = new Bitmap(textBdata); textBmp.x = (W - textBmp.width) * 0.5; textBmp.y = (H - textBmp.height) * 0.5; addChild(textBmp); txtMask = new Shape(); txtMask.graphics.beginFill(0xFFFFFF); txtMask.graphics.drawRect(0, 0, 4, textBmp.height); txtMask.graphics.endFill(); txtMask.x = textBmp.x; txtMask.y = textBmp.y; textBmp.mask = txtMask; addChild(txtMask); btnRestart = new MiniButton("restart"); btnRestart.x = (W - MiniButton.W) * 0.5; btnRestart.y = 10.0; addChild(btnRestart); } 


The screen variable is a large BitmapData in which the particles will be drawn. Immediately a small disclaimer, for the storage of particles, decided to use a single-linked list instead of a Vector , in view of the fact that I often have to remove particles and do vector.splice. And it is easier to remove an element from the list — you just need to exclude the element and change the links from the neighbors.
Particle class
code
 class Particle { private static const GRAVITY:Point = new Point(0, 0.2); private var speed:Point; public var position:Point; public var color:int; public var next:Particle; public function Particle(x:Number, y:Number, color:int) { position= new Point(x, y); //    speed = new Point(); speed.x = Math.random() * 10 - 2; speed.y = Math.random() * 1 - 4; this.color = color; } public function Update():void { speed = speed.add(GRAVITY); pos = pos.add(speed); } } 


To restart the animation you need a restart button. The button has two states - highlighted when pointing and normal.
code
 class MiniButton extends Sprite { private var tf:TextField; public static const W:int = 100; public static const H:int = 24; public function MiniButton(text:String) { tf = new TextField(); var format:TextFormat = new TextFormat( "Arial" , 16 , 0x676767 , true , null, null, null, null , TextFormatAlign.CENTER); tf.defaultTextFormat = format; tf.mouseEnabled = false; tf.text = text; tf.width = W; tf.height = H; Redraw(0xB3F7B6); this.filters = [new GlowFilter(0xFFFFFF, 0.5, 14, 14, 3, 3)]; mouseChildren = false; buttonMode = true; addEventListener(MouseEvent.ROLL_OVER, HandleRollOver); addEventListener(MouseEvent.ROLL_OUT, HandleRollOut); } private function Redraw(color:int):void { graphics.clear(); graphics.beginFill(0xB3F7B6); graphics.drawRoundRect(0, 0, W, H, 16); graphics.endFill(); } private function HandleRollOut(e:MouseEvent):void { Redraw(0xB3F7B6); } private function HandleRollOver(e:MouseEvent):void { Redraw(0x37EC41); } } 


Methods to restart and stop animation.
code
 private function Reset():void { btnRestart.visible = false; txtMask.width = 4; isRunning = true; addEventListener(Event.ENTER_FRAME, HandleEnterFrame); } private function Stop():void { btnRestart.visible = true; isRunning = false; removeEventListener(Event.ENTER_FRAME, HandleEnterFrame); } 


The necessary functionality is ready. In the constructor, call InitializeLayout and start the animation.
code
 public function Main() { InitalizeLayout(); Reset(); btnRestart.addEventListener(MouseEvent.CLICK, HandleResetClick); } 


That's the main cycle, which, however, turned out slightly cumbersome.
code
 private var firstParticle:Particle; private function HandleEnterFrame(e:Event):void { if (!isRunning) return; //    screen.lock(); screen.fillRect(screen.rect, 0x00000000); // X    var currentX:int = (txtMask.x + txtMask.width) - textBmp.x; if (currentX >= 0 && currentX < textBmp.width) { //      var color:int; var alpha:int; while (currentY < txtMask.height) { //    color = textBdata.getPixel32(currentX, currentY); alpha = (color >> 24) & 0xFF; //    if (alpha > 0x7f) { //          alpha /= 1.4; color = alpha << 24 | (color & 0xFFFFFF); for (var i:int = 0; i < 8; ++i) { var pp:Particle = new Particle(txtMask.x + txtMask.width, txtMask.y + currentY + i, color); if (firstParticle == null) { firstParticle = pp; } else { pp.next = firstParticle; firstParticle = pp; } } currentY += 6; screen.copyPixels(head, head.rect, new Point( txtMask.x + txtMask.width , txtMask.y + currentY - head.height / 2 ) ); screen.applyFilter( screen , screen.rect , new Point() , new BlurFilter(2, 2) ); break; } currentY += 2; } } var p:Particle = firstParticle; var prev:Particle; while (p != null) { p.Update(); //         if (p.pos.x < 0 || p.pos.y < 0 || p.pos.x > W || p.pos.y > H) { //     if (prev == null) { p = p.next; firstParticle = p; continue; } else { prev.next = p.next; } } //    var clr:int = pc; screen.setPixel32(p.pos.x, p.pos.y, clr); screen.setPixel32(p.pos.x-1, p.pos.y, clr); screen.setPixel32(p.pos.x+1, p.pos.y, clr); screen.setPixel32(p.pos.x, p.pos.y-1, clr); screen.setPixel32(p.pos.x, p.pos.y + 1, clr); prev = p; p = p.next; } //   ""  screen.applyFilter(screen, screen.rect, new Point(), new GlowFilter(0xFFFF00, 0.8, 10, 10)); screen.applyFilter(screen, screen.rect, new Point(), new BlurFilter(2, 2)); screen.unlock(); if (currentY >= txtMask.height) { currentY = 0; txtMask.width += 2; } if (txtMask.width >= textBmp.width) { if (firstParticle == null) { Stop(); } } } 



Ps. Just in case the full source
code
 package { import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.BitmapDataChannel; import flash.display.BlendMode; import flash.display.Shape; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; import flash.filters.BlurFilter; import flash.filters.GlowFilter; import flash.geom.ColorTransform; import flash.geom.Point; import flash.text.TextField; import flash.text.TextFormat; /** * ... * @author KeeReal */ public class Main extends Sprite { //- PRIVATE & PROTECTED VARIABLES ------------------------------------------------------------------------- private var textBdata:BitmapData; private var textBmp:Bitmap; private var screen:BitmapData; private var head:BitmapData; private var txtMask:Shape; private var firstParticle:Particle; private var btnRestart:MiniButton; private var isRunning:Boolean; private var currentY:int = 0; //- PUBLIC & INTERNAL VARIABLES --------------------------------------------------------------------------- public static const W:int = 460; public static const H:int = 240; //- CONSTRUCTOR ------------------------------------------------------------------------------------------- public function Main() { stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP; InitalizeLayout(); Reset(); btnRestart.addEventListener(MouseEvent.CLICK, HandleResetClick); } //- PRIVATE & PROTECTED METHODS --------------------------------------------------------------------------- private function Reset():void { btnRestart.visible = false; txtMask.width = 4; isRunning = true; addEventListener(Event.ENTER_FRAME, HandleEnterFrame); } private function Stop():void { btnRestart.visible = true; isRunning = false; removeEventListener(Event.ENTER_FRAME, HandleEnterFrame); } private function InitalizeLayout():void { screen = new BitmapData(W, H, true, 0x00000000); addChild(new Bitmap(screen)); textBdata = GetMaskedText("..Hello habr.."); textBmp = new Bitmap(textBdata); textBmp.x = (W - textBmp.width) * 0.5; textBmp.y = (H - textBmp.height) * 0.5; addChild(textBmp); txtMask = new Shape(); txtMask.graphics.beginFill(0xFFFFFF); txtMask.graphics.drawRect(0, 0, 4, textBmp.height); txtMask.graphics.endFill(); txtMask.x = textBmp.x; txtMask.y = textBmp.y; textBmp.mask = txtMask; addChild(txtMask); btnRestart = new MiniButton("restart"); btnRestart.x = (W - MiniButton.W) * 0.5; btnRestart.y = 10.0; addChild(btnRestart); } private function GetMaskedText(text:String):BitmapData { var tf:TextField = new TextField(); var format:TextFormat = new TextFormat("Arial", 60, 0xFFFFFF, true); tf.defaultTextFormat = format; tf.text = text; tf.width = tf.textWidth + 4.0; tf.height = tf.textHeight + 4.0; tf.filters = [new GlowFilter(0xFFFFFF, 1.0, 2, 2, 4, 3)]; var w:int = tf.width; var h:int = tf.height; var noiseBdata:BitmapData = new BitmapData(w, h, true, 0xFFFFFFFF); var channels:int = BitmapDataChannel.GREEN | BitmapDataChannel.RED; noiseBdata.perlinNoise(w / 6, h / 4, 6, int(Math.random() * 1000), false, false, channels, false); noiseBdata.colorTransform(noiseBdata.rect, new ColorTransform(1.4, 1.4, 1.4)); var noiseBmp:Bitmap = new Bitmap(noiseBdata); var textBdata:BitmapData = new BitmapData(w, h, true, 0x00000000); textBdata.draw(tf); var textBmp:Bitmap = new Bitmap(textBdata); var eraseTextBdata:BitmapData = noiseBdata.clone(); eraseTextBdata.draw(noiseBmp); eraseTextBdata.draw(textBmp, null, null, BlendMode.ERASE); var eraseTextBmp:Bitmap = new Bitmap(eraseTextBdata); var eraseBackByTextBdata:BitmapData = noiseBdata.clone(); eraseBackByTextBdata.draw(eraseTextBmp, null, null, BlendMode.ERASE); eraseBackByTextBdata.applyFilter( eraseBackByTextBdata , eraseBackByTextBdata.rect , new Point() , new GlowFilter(0xFFFFFF, 1.0, 3, 3) ); noiseBdata.dispose(); textBdata.dispose(); eraseTextBdata.dispose(); head = new BitmapData(6, 6, true, 0xffFFFFFF); return eraseBackByTextBdata; } //- PUBLIC & INTERNAL METHODS ----------------------------------------------------------------------------- //- EVENT HANDLERS ---------------------------------------------------------------------------------------- private function HandleResetClick(e:MouseEvent):void { Reset(); } private function HandleEnterFrame(e:Event):void { if (!isRunning) return; screen.lock(); screen.fillRect(screen.rect, 0x00000000); var currentX:int = (txtMask.x + txtMask.width) - textBmp.x; if (currentX >= 0 && currentX < textBmp.width) { var color:int; var alpha:int; while (currentY < txtMask.height) { color = textBdata.getPixel32(currentX, currentY); alpha = (color >> 24) & 0xFF; if (alpha > 0x7f) { alpha /= 1.4; color = alpha << 24 | (color & 0xFFFFFF); for (var i:int = 0; i < 8; ++i) { var pp:Particle = new Particle(txtMask.x + txtMask.width, txtMask.y + currentY + i, color); if (firstParticle == null) { firstParticle = pp; } else { pp.next = firstParticle; firstParticle = pp; } } currentY += 6; screen.copyPixels(head, head.rect, new Point( txtMask.x + txtMask.width , txtMask.y + currentY - head.height / 2 ) ); screen.applyFilter( screen , screen.rect , new Point() , new BlurFilter(2, 2) ); break; } currentY += 2; } } var p:Particle = firstParticle; var prev:Particle; while (p != null) { p.Update(); if (p.position.x < 0 || p.position.y < 0 || p.position.x > W || p.position.y > Main.H) { if (prev == null) { p = p.next; firstParticle = p; continue; } else { prev.next = p.next; } } var clr:int = p.color; screen.setPixel32(p.position.x, p.position.y, clr); screen.setPixel32(p.position.x - 1, p.position.y, clr); screen.setPixel32(p.position.x + 1, p.position.y, clr); screen.setPixel32(p.position.x, p.position.y - 1, clr); screen.setPixel32(p.position.x, p.position.y + 1, clr); prev = p; p = p.next; } screen.applyFilter(screen, screen.rect, new Point(), new GlowFilter(0xFFFF00, 0.8, 10, 10)); screen.applyFilter(screen, screen.rect, new Point(), new BlurFilter(2, 2)); screen.unlock(); if (currentY >= txtMask.height) { currentY = 0; txtMask.width += 2; } if (txtMask.width >= textBmp.width) { if (firstParticle == null) { Stop(); } } } //- GETTERS & SETTERS ------------------------------------------------------------------------------------- //- HELPERS ----------------------------------------------------------------------------------------------- } } import flash.display.Sprite; import flash.events.MouseEvent; import flash.filters.GlowFilter; import flash.geom.Point; import flash.text.TextField; import flash.text.TextFormat; import flash.text.TextFormatAlign; class Particle { private static const GRAVITY:Point = new Point(0.0, 0.2); private var speed:Point; public var position:Point; public var color:int; public var next:Particle; public function Particle(x:Number, y:Number, color:int) { position = new Point(x, y); speed = new Point(); speed.x = Math.random() * 10 - 2; speed.y = Math.random() * 1 - 4; this.color = color; } public function Update():void { speed = speed.add(GRAVITY); position = position.add(speed); } } class MiniButton extends Sprite { private var tf:TextField; public static const W:int = 100; public static const H:int = 24; public function MiniButton(text:String) { tf = new TextField(); var format:TextFormat = new TextFormat( "Arial" , 16 , 0x676767 , true , null, null, null, null , TextFormatAlign.CENTER); tf.defaultTextFormat = format; tf.mouseEnabled = false; tf.text = text; tf.width = W; tf.height = H; Redraw(0xB3F7B6); this.filters = [new GlowFilter(0xFFFFFF, 0.5, 14, 14, 3, 3)]; mouseChildren = false; buttonMode = true; addEventListener(MouseEvent.ROLL_OVER, HandleRollOver); addEventListener(MouseEvent.ROLL_OUT, HandleRollOut); } private function Redraw(color:int):void { graphics.clear(); graphics.beginFill(0xB3F7B6); graphics.drawRoundRect(0, 0, W, H, 16); graphics.endFill(); } private function HandleRollOut(e:MouseEvent):void { Redraw(0xB3F7B6); } private function HandleRollOver(e:MouseEvent):void { Redraw(0x37EC41); } } 

Source: https://habr.com/ru/post/186038/


All Articles