There's a Hole in my Bitmap

The JSL below creates two bitmaps: a rainbow-colored background and a Mandelbrot shaped mask.  The two bitmaps are added to a graph and the rainbow is moved around under the mask.  It's very fast because the bitmaps are only created once and the OS provides support for the mask's transparent hole.

The transparent hole in the mask is created with the alpha layer, which is similar to the red, green, and blue layers.  Alpha is a number between zero and one that determine how transparent/opaque a pixel is when rendered on top of a previously drawn pixel.  In this example, alpha is zero (transparent) or one (opaque).  You can blend bitmaps with intermediate values.

This inline version of the JSL was tweaked for making the bitmaps for the video.  The attached file might look a little better as it runs because it uses the timer to determine what frame to show next.

Craige Hales - YouTube

// this example demonstrates one way to make an image with a hole.

// Most images just use Red, Green, and Blue (like the background rainbow

// image below).  Some images add Alpha as a fourth channel describing

// opaqueness or transparency.  JMP uses 1 for opaque.

//

scale = 35; // overall size

nr = 20 * scale; // aspect ratio

nc = 25 * scale; // aspect ratio

// pixels are initially built in a matrix, allocate with the J function

transp = J( nr, nc, 0 );

// populate the pixels with the mandelbrot function.  Use parallel assign

// rather than nested loops, for speed.

Parallel Assign(

{nr = N Rows( transp ), nr2 = N Rows( transp ) / 2,

nc = N Cols( transp ), nc3 = N Cols( transp ) / 1.3333, limit = 1000},

transp[r, c] = Mandelbrot( limit, 4, // returns integer answers from 1..limit

2 * (c - nc3) / Sqrt( nr * nc ), // scale matrix rows,cols to interesting space

2 * (r - nr2) / Sqrt( nr * nc ) ) / limit // divide by limit scales 0..1

);

// the next line is the important one.  this image is created with 4 channels of data,

// Red, Green, Blue, and Alpha.  Because the same "transp" value is used for r,g,b the

// image is gray scale, but because of the data from Mandelbrot() you won't see much

// gray in the mask...only black.  change the 2nd transp to 1-transp and you'll see

// a green mask.  But the important parameter is transp<.5 which creates the alpha

// channel.  transp is a rectangular matrix of values between 0 and 1; high values

// are the flat area inside the mandelbrot set, low values are outside.  transp<.5

// is true, or 1, outside and false, or 0, inside.  this makes a rectangular array of

// alphas that are 0 or 1: 1 is the mask and 0 is transparent.  (alpha can be fractional

// and that will blend the bitmaps.  Not using that here.)

transpImage = New Image( "rgba", {transp, transp, transp, transp < .5} );

// this example doesn't use the saved image, but you can, like this, and the png

// that is save has transparency if you open it with Gimp, for example.

// test:    transpimage << saveimage( "\\VBOXSVR\windowsShare\transparent\test.png", "png" );

// make a window to hold the animation and a graphbox to hold the bitmaps.

// your best bet for making transparent bitmaps show off in JMP is in a graph

// because other displayboxes with bitmaps don't really want to overlap each other.

New Window( "transparency",

gb = Graph Box( framesize( nc + 20, nr + 20 ), // 10 pixel rainbow border all around

<<backgroundcolor( "red" ) ) ); // you might see red just before the 2nd image is added

// insert the first image into the frame and get a handle to the image segment

gb[framebox( 1 )] << addimage( image( transpimage ) );

transpSeg = gb[framebox( 1 )] << childseg;

// position it; this "mask" is only positioned once, right here

transpSeg << move( 50, 50 );

//

//

// make a second reainbow image without any transparency

background = J( nr * 2, nc * 2, 0 ); // extra for scrolling.  make some rainbow pixels...

Parallel Assign(

{nr = N Rows( background ), nc = N Cols( background )},

background[r, c] = RGB Color( (Sin( r / 70 ) + 1) / 2, (Sin( c / 50 ) + 1) / 2, Sin( (r + c) / 60 ) )

);

// turn the pixels into a bitmap image, add to frame, get a handle

backgroundImage = New Image( background );

gb[framebox( 1 )] << addimage( image( backgroundImage ) );

backgroundSeg = gb[framebox( 1 )] << childseg;

//

wait(.1);// make sure the window gets to open before the loop begins

//

start = 0;  // use time to create a smoother animation

frame=0;

while( start < 30, // run for 30 seconds

position = start; // used as radian arg to sin and cos...

start = start + 1/30;

x = 50 + 50 * Sin( position * .39 ); // arbitrary scaling using

y = 50 + 50 * Cos( position * .27 ); // artistic license

// OK, this is clumsy; it appears the move method needs help evaluating...

// EvalExpr looks at its argument and searches for Expr to evaluate.

// EvalExpr returns an expression that still needs to be evaluated

Eval( Eval Expr( backgroundseg << move( Expr( x ), Expr( y ) ) ) );

// gb<<inval; wait(0); animation is ragged with wait (that allows interaction)

gb << updatewindow; // this is very smooth animation, but not interactive

//make picture:   gb << savepicture("\\VBOXSVR\windowsShare\transparent\v\v"||right(char(frame++,9),9,"0")||".png", "png");

);

//

// more ideas (not tested, let us know what happens):

// set the graphbox xscale and yscale to match the frame size

// if you want to work in precise pixels.

// experiment with alpha values around .5 to blend images.

// arrange other segments in the graph to be drawn before the mask segment;

// a marker seg can appear through the hole in the mask.  Movie binoculars.

// move the mask instead of the data.

// multiple layers with transparency.  Side scrollers using parallax idea.

// use MouseTrap to drag a mask.

// try rotating a mask, maybe also with the MouseTrap, depending how far the click

// is from the center of the mask.