Tutorials Books Videos Forums

-- online Change the theme! Search!
Rambo ftw!

Customize Theme


Color

Background


Done

Liquify Image Effect

by bAkedSOda@in2mind   | filed under Flash and ActionScript

This is an archived tutorial from the kirupa.com legacy collection. It covers software that may no longer be available, but it is kept online because the ideas still hold up.

An ultra-simple touch of action script can turn an otherwise-still image or even a whole swf into a liquefied, stretchable rubbery treat !... If you're anything like me, you just love imposing contortions onto perfectly innocent loaded images! So if you're ready follow me!

Observe as you move the pointer slowly around the eyes and nose of this guy:

[ move your mouse over the above animation ]

In this tutorial

We'll go over the concept of creating a basic 'lens' effect in flash, and then expand that idea into creating what you see above. We're assuming here that you have at least some idea of what a 'mask' is (other than the kind you wear on your face), but even if you've never masked an image in flash before, we'll go over that in this tutorial too : )

What you need:

  1. Flash MX 2004 (the style of coding we'll use is reflective of the action script 2.0 paradigm of writing code).

  2. Any image that you'd like to apply this effect to. (the one included in the Downloadable Source file ZIP (link at bottom of this page) works just as well as any other.

  3. A desire to get your hands a little dirty in a short, pretty basic slice of action script. Piece of cake!
Note
This effect can be applied to any movie clip or image in your movie. Whether you load it in at runtime using loadMovie() or the MovieClipLoader() class, or if the image lives in the library at author time. For now, we'll build this gummy contraption using an image that's in your movie's library.
 

First let's set up our movie: open up your Flash (as if it's not ALWAYS open 24 hours a day!)

  1. Set up your movie with layers and size as shown above. The image used in the downloadable source file is a .jpeg of about 312 pixels wide and 364 pixels high so I set this movie's Stage size at 330wide and 402 high. (oh yeah: while you're setting the Stage size, make the Frame Rate of this movie 31 fps).
     

Tip
Some movies' animations look 'quirky and not smooth', and some look smoooooth and sweet. why? The Frames per Second setting in the movie.

Sometimes I use 60fps, sometimes 31. I've even seen some people use 90! (but they disappeared and no one can find them now), If you test a movie developed using 90 fps, and your sounds are sync 'd up nicely with your visuals, that movie running in a web browser will play quite differently, and you'll get mad.

A Motion picture made in Hollywood plays at 24 or 31fps...31fps gives a nice smoothness of motion and cpu-friendliness.
 

  1. Create four layers and label them from top to bottom:
     
    • action script
    • mask_mc
    • pic
    • bg
       
  2. On the 'bg' layer, place a background. I used a black rectangle at center Stage with a grey one underneath it at slightly off-center.
     

Practice Observing Subtlety
If something is present in a design, (such as this 'meaningless' black rectangle with a drop shadow) and it's not noticed right away as a 'prominent feature', that's a pretty good sign that it is contributing to the whole in some important way; providing depth or structure. I'm not saying that this silly little black rectangle is a great work of art, but I am saying that something like this does make a difference when it's there, even though it goes unnoticed right away.
  1. Place the image on the 'pic' layer at center Stage, and press <F8>...
    In the 'convert to symbol' pop-up choose 'Movie Clip'.

    And in the tiny registration grid (circled in blue), click on the center square. You can put anything in the 'Name' field. Press 'OK'.

  1. Select the newly created symbol on the Stage and give it an instance name of 'pic_mc' using the Properties toolbar.

  1. Select the 'mask_mc' layer and draw a big oval shape on frame1. It should cover a large portion of the center of pic_mc, which is on the 'pic' layer. Make it a solid color fill with no outline (no Stroke). This will be one of the elements that follows the mouse around and makes the image look rubbery...

  1. Select the big oval you just created (make sure it's the only item that's selected) and press  <F8> to turn it into a Movie Clip, also with a center Registration point. Give it an instance name of 'mask_mc' in the Properties toolbar.

The Stage is set

The movie's setup is complete and we're ready to talk about 'lensing.' Then we'll script this whole thing up.

If you're already well-versed in using masks, dynamically-created masks, and creating a basic 'lens' effect, you could skip the next section and go straight to part 3. Otherwise, the next section and all its goodies is for you!



Making an image look liquidly involves a tricky bit of 'masking'. A basic mask in Flash can be created two ways:

  1. By adding a Layer to your timeline, right-clicking on the layer (CTRL+click mac) and choosing 'Mask Layer" from the drop-down menu.
  2. By using action script's setMask( ) function to make one movieClip mask another movieClip while the movie is playing (as shown in the hi-tech monkey example below).
Note
A mask is a 'window'. You can see only the areas of the object which it is covering. The word 'mask' is tricky because of what we know about 'real-life' masks (put a mask on and it hides you).
 
In flash (and graphic art in general) a mask does the opposite: if you lived inside a flash movie and masked your leg, the only thing a user would be able to see is your leg.

 

 

[using action script to set one movieclip as the mask for another]

Above, you have two normal MovieClip's on the Stage, and the buttons cause one of them to mask the other, using the setMask() function.

What if

instead of empty space behind the masked image, there were another image behind it? That other image behind it would show through. And with some sneaky layering:

 
Now you know

How to create a basic lens effect using some easy layering tricks and how to use setMask().

Note
Instead of using the same image on the middle layer, you can use a completely different one. Click on the moving lens in the movie above to see what that looks like.

We can expand on the 'lens' idea to finish our mission here: a rubbery-looking liquefied image.



The action script used to make this effect is amazingly SHORT (only 26 lines of code). We'll apply the basic lens effect we saw in part 2. Let's do it.

Here's what the movie should look like right now:
 

  1. Pop open your action script window let's add these first lines of code:
//Liquefy an image ////////////
//make an empty movie clip
var theRubberizer:MovieClip = this.createEmptyMovieClip("theScene", this.getNextHighestDepth());
//set up the max num of copies to make (20 is comfy)
var maxImages:Number = 20;

Pretty self- explanatory

We created an empty movie clip called 'theRubberizer', and a Number variable named 'maxImages'.

  1. create a function called 'dupeAndPlace()' with one argument called 'image'
dupeAndPlace = function(image:MovieClip):Object{
  var arrHolder:Object = new Object();
  arrHolder.pics_arr = new Array(0);
  arrHolder.masks_arr = new Array(0);

The function takes one argument called 'image' and returns an Object. Flash will expect that whatever we pass into this function as 'image' will be a MovieClip.
 
Created a new Object called 'arrHolder', and stuck two arrays onto it. This is the Object that the function will return.

  1. Start filling up the function by adding a for...loop to it:
for(var i=1; i< maxImages; i++){
  var dnm = "image" + i;
  var mnm = "mask" + i;

A simple beginning to a 'for...loop'. Note we didn't close the '{' bracket yet. Every time this loop iterates (repeats), it creates a new variable called 'dnm' and another 'mnm'. And this loop will iterate 20 times, since that's what maxImages is set to.

There's also a reason we started the loop with 'var i=1' instead of the usual 'var i=0'. We'll mention that later.

  1. We need our loop to do some other stuff besides creating those two variables. So let's add more code in there! Here are five more lines, followed by an explanation:
var imgObj = {_x:image._x, _y:image._y, _xscale:100+(i*1.5), _yscale:100+(i*1.5)};
var maskObj = {_xscale:Math.floor(100/i+3), _yscale:Math.floor(100/i+3)};
var theDupedImage = image.duplicateMovieClip(dnm, theRubberizer.getDepth()+i, imgObj);
var theDupedMask = mask_mc.duplicateMovieClip(mnm, theRubberizer.getDepth()+ (i*50), maskObj);
theDupedImage.setMask(theDupedMask);


Explanation
(Keep in mind that every line up there happens 20 times in a row, extremely quickly since it's inside a for..loop):

First we make an 'init object' to stick inside the duplicateMovieClip() call. It tells each duplicate where to be placed:

var imgObj = { _x:image._x, _y:image._y,
_xscale:100+(i*1.5), _yscale:100+(i*1.5) };


(the same _x and _y of the image we'll pass into the function, at _xscale and _yscale of 100% + (whatever i is at *1.5). (slightly larger each time the loop repeats)).

The second init object tells the duplicates of mask_mc what to look like:

var maskObj = {_xscale:Math.floor(100/i+3),
  _yscale:Math.floor(100/i+3), _x:this._xmouse,
_y:this._ymouse};

Same _x and _y as the mouse, and slightly smaller than the original mask_mc with each repeating of the loop.
 

Infinity
When you're using a loop and using the loop's own 'i' to divide, be careful of 'Infinity', it's what you'll get when you divide by 0. But that's not the only reason we're using 'i=1'.

In the next line we duplicate image, giving it an instance name of 'dnm', a depth of 'i' above the Rubberizer's depth, and use the imgObj to place the copies:
 

var theDupedImage = image.duplicateMovieClip(dnm,
theRubberizer.getDepth()+i, imgObj);


We duplicate mask_mc, giving it an instance name of 'mnm', a depth of the Rubberizer's depth + (i*50), and use the 'maskObj' to place the copies:

var theDupedMask = mask_mc.duplicateMovieClip(mnm, i*100, maskObj);

We didn't use the shiny new '_root.getNextHighestDepth()' to place the copied mc's ? If you apply this effect to one image, and then to another, you'd have copies of images and masks created at progressively higher depths every time you applied the effect.

Then: theDupedImage.setMask(theDupedMask);
Which sets each 'theDupedMask' as the mask of each 'theDupedImage'.

Easy Enough
Remember the 'basic lens' effect in part 2? We took an image, put a copy of it on the layer above at a slightly bigger size, and then masked that copy. Well that's exactly what we're doing above, but we're doing it 20 times. Every time our for...loop repeats, it puts a copy of image on top of the original image at a slightly larger size. And gives each copy a mask of a slightly smaller size.
  1. Last but not least, add these lines. We're still in the loop, which is inside the 'dupeAndPlace()' function:
arrHolder.masks_arr.push(theDupedMask);
} //the loop is closed
mask_mc._visible = false;
mask1._visible=false;
image1._visible=false;
//return the object
return arrHolder;
}; //the function is closed

What

In the first line, we push 'theDupedMask' into the arrHolder Object's 'masks_arr' Array. ('push' adds something to the end of an array)
arrHolder.masks_arr.push(theDupedMask);

Then push 'theDupedImage' into the arrHolder Object's 'pics_arr' Array.
arrHolder.pics_arr.push(theDupedImage);
and close the for..loop with a ' } '

Then we made mask_mc, mask1, and image1 invisible. 'mask_mc' is on the Stage, this function did not add it to the array of masks, so we can hide it. Then close the dupeAndPlace function with a ' } '.
And lastly, return the arrHolder Object:
return arrHolder;

At this point

You have 20 duplicated images, masked by 20 duplicated masks. What's left to do? MOTION.

Let's go to the next section.



Applying motion is easy.
The masks and images we duplicated in part 3 are arranged in size like the animation below:
 
 
 
In motion, the masks at the top follow the mouse very closely, while the ones at the bottom follow more slowly.

Let's make a function to do the moving.

A basic scripted motion tween
Here's a cool little tweening function for you. It's pretty well-known, easy-to-use, and we can use it to make our masks move:
 
my_movieClip.onEnterFrame = function(){
   this._x += (destinationX - this._x) / 10;
}

 
'destinationX' is where you want 'my_movieClip' to go, and the number '10 at the end is how fast it'll get there. A higher number creates more eeeeasing...and a slower movement, a low number makes it move fast.
 
  1. Add this to the bottom of the script:
//make waves
makeWaves = function (masks_arr:Array) {
  for (var i = masks_arr.length; i>0; i--) {
  masks_arr[i]._x += (this._xmouse-masks_arr[i]._x)/maxImages*i;
  masks_arr[i]._y += (this._ymouse-masks_arr[i]._y)/maxImages*i;
  }
};

'makeWaves()' loops through every item in the 'masks_arr' array we pass to it and tells each one of them to adjust their _x and _y location according to where the mouse is. It uses the basic scripted tween explained above.
The key to the speed of each mask's movement lies in the number at the end (15*i): a lower number is a faster movement, so the mask will move faster if it's at the beginning of the array (i.e. higher in depth and smaller in size).

 
  1. One more method to add:
///the func to call /////////
liquefyImage = function(theImage:MovieClip){
  var arrHolder:Object = dupeAndPlace(theImage);
  theRubberizer.onEnterFrame = function(){
  makeWaves(arrHolder.masks_arr);
  }
}
//


takes an argument 'theImage', passes it into the dupeAndPlace function, and stores the result in its own 'arrHolder' variable. Then it tells 'theRubberizer' mc we created to call makeWaves ever frame.
 
*Try passing 'arrHolder.pics_arr' when calling makeWaves() here, it'll look a bit odd because of this image's size, but when used in other ways, it can be made to look pretty cool.

You are finished! You can download the source files here:

There's no friendlier or more brilliant group of peeps I've ever met online than the ones in kirupa forums. If you ever have any questions, post them in there.


Cheers!



Just a final word before we wrap up. What you've seen here is freshly baked content without added preservatives, artificial intelligence, ads, and algorithm-driven doodads. A huge thank you to all of you who buy kirupa's books, became a paid subscriber, watch the videos, and/or interact on the forums.

Your support keeps this site going! 😇

The KIRUPA Newsletter

Thought provoking content that lives at the intersection of design 🎨, development 🤖, and business 💰 - delivered weekly to over a bazillion subscribers!

SUBSCRIBE NOW

Creating engaging and entertaining content for designers and developers since 1998.

Follow:

Popular

Loose Ends

:: Copyright KIRUPA 2026 //--