Tag Archives: color

Let’s shrink the world: A Tilt Shift Effect

Everybody has seen the cute pictures floating around showing a mini world! Some examples:

But how is it done? And can it be done fast?

Answers: Easy and Yes!

How does it work?
For a digital Tilt-Shift we use some kind of gradient blur. What I mean is a blur that is very light where the focus lens is and gets blurrier the further something is in the picture. Some ‘faster’ systems only use one blur and then put the focused part back into the picture but this creates an ugly edge between the sharp and blurred part of the picture. And we do not want this!

To get his effect we could do several blurs where we enlarge the focus box in the loop so we get some gradience. Eg. If we do it 8 times we get a gradient of 8 layers. Not bad but…we want more!

Doing a blur in languages like Realstudio and Basic4Android (or Java) is not that fast. This is the first issue we’ll have to tackle because we want to be close to doing it real-time.

Second, a lot of focusses can only be a rectangle or a circle. This has a side effect: some objects in the picture will be in focus while we do not want that because it spoils the illusion. So our focus needs to be able to be irregular. In this tutorial the example uses also a rectangle but I’ll show you how to change this to anything you want.

In some cases changing the saturation of the picture can even more give the illusion of a miniature.

So in short we want:
1. few blurs, but still have 255 gradient levels
2. the blur must be fast
3. an irregular focus
4. some kind of saturation
5. did I mention fast?

How will Basic4Android (or java) and Realbasic be able to do this?

Well, we’re gonna cheat! :D

Instead of creating a gradient blur, we are going to write one heavy blur and then resharpen it. In fact you’ll see we can do the 255 levels of sharpness with only two blurs!

In Realbasic we can take advantage of the great mask property in a picture we’ve used a lot in the past. In Basic4Android I’m going to simulate this so the code can be very similar.

For our example let’s say we want to make this:

Let me show you in a schema what the steps are we’re going to use in our Tilt-Shift (click the picture to see full size):

1. our orignal picture. Make sure it is a sharp one
2. a blurred version of (1)
3. our focus area.
4. a blurred version of (3)
5. we use our blurred (4) as a mask on our original (1). As you can see in the schema, (5) fades out to its background (the alpha channel increases), in this case white.
6. so if we draw (5) on our blurred (2) the blurred pixels will re-sharpen! Do you see the magic happening here :-)
7. the saturation is optional but may make the effect more real.

And the clever part is step 3: you can draw any shape you want here! Some examples for Realbasic:

And remember, in Basic4Android (java) it needs to be inverted:

And now for the code.
(For B4A you’ll need my ABExtDrawing library version 1.7 here)

First we’ll need a fast blur. Again we’ll cheat as we’re not doing a real gaussian blur. We are ging to exploit our mask feature again and do a diagonal blur.

We’ll need some help functions:
Resize()

Realbasic:

Function Resize(pic as picture, Percentage as integer) As Picture
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim p as Picture
  dim w,h as Integer
  dim by as Double = Percentage / 100
  
  w = pic.Width * by
  h = pic.Height * by
  
  p = NewPicture(w,h,32)
  p.Graphics.UseOldRenderer=true
  p.Graphics.DrawPicture pic,0,0,w,h, 0,0,pic.Width, pic.Height
  
  Return p
End Function

Java (B4A library):

public Bitmap Resize(Bitmap bmp, float Percentage)
{
	float by = (float) (Percentage/100.0);
	
	int w = (int) (bmp.getWidth()*by);
	int h = (int) (bmp.getHeight()*by);
	
	if (Percentage>100) {
		return Bitmap.createScaledBitmap(bmp, w, h, true);
	} else {
		return Bitmap.createScaledBitmap(bmp, w, h, false);
	}
	
}

Innerblur()
We create a mask on our picture and draw it a number of times on each other, just shifting bits to the left, top, right and bottom.

Realbasic:

Function InnerBlur(Pic as picture, Level as double) As Picture
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim p,t as Picture
  dim L,w,h as Integer
  
  w=pic.Width
  h=pic.Height
  p=NewPicture(w,h,32)
  p.Graphics.UseOldRenderer=true
  p.Graphics.DrawPicture pic,0,0
  
  t=NewPicture(w,h,32)
  t.Graphics.UseOldRenderer=true
  t.Mask.Graphics.UseOldRenderer=true
  t.Mask.Graphics.ForeColor=&c7F7F7F
  t.Mask.Graphics.FillRect 0,0,t.Width,t.Height
  
  for L=abs(Level) DownTo 1
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,-L,-L   'upper left
    
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,-L,L    'lower left
    
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,L,L     'lower right
    
    t.Graphics.DrawPicture p,0,0
    p.Graphics.DrawPicture t,L,-L    'upper right
  next
  
  Return p
End Function

Java (B4A library):

private Bitmap InnerBlur(Bitmap bmp, float Level) {
	int w = bmp.getWidth();
	int h = bmp.getHeight();
	
	Paint paint = new Paint();
	paint.setAlpha(0x7F);
	
	Bitmap t = Bitmap.createBitmap(w, h, Config.ARGB_8888);
	Canvas tc = new Canvas(t);
	
	Bitmap p = Bitmap.createBitmap(w, h, Config.ARGB_8888);
	Canvas pc = new Canvas(p);
	pc.drawBitmap(bmp, 0, 0, null);
	
	int Lev = (int) Level;
	
	for (int L=Lev;L>0;L--) {
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, -L, -L, null);
		
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, -L, L, null);
		
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, L, L, null);
		
		tc.drawBitmap(p, 0, 0, paint);
		pc.drawBitmap(t, L, -L, null);
	}
	
	t.recycle();
	return p;
}

Blur()
The actual blur function is a mix of Resizes and Innerblurs.
1. Resize to smaller
2. Innerblur to level
3. Resize back to original size
4. Innerblur with level 1 to make it smooth

Realbasic:

Function Blur(Pic as picture, Level as double, Speed as Integer) As Picture
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim p as Picture
  dim L,w,h as Integer
  
  w=pic.Width
  h=pic.Height
  p=NewPicture(w,h,32)
  p.Graphics.UseOldRenderer=true
  p.Graphics.DrawPicture pic,0,0
  
  L=Level-round(Level*.4)
  
  select case speed
  case 0
    // shrink the image, blur it
    p=Resize(p,50)
    p=InnerBlur(p, L )
    // back to normal, smooth it
    p=Resize(p, 200)
    p=InnerBlur(p, 1)
  case 1
    // shrink the image, blur it
    p=Resize(p,25)
    p=InnerBlur(p, L )
    // back to normal, smooth it
    p=Resize(p, 400)
    p=InnerBlur(p, 1)
    
  end select
  
  Return p
End Function

Java (B4A library):

/**
 * blur and image. 
 * Speed: 0 = normal, 1 = fast
 */
public Bitmap Blur(Bitmap bmp, float Level, int Speed)
{
	int L = (int) (Level - Math.round(Level*.4));
	Bitmap p = null;	 
	
	switch (Speed) {
	case 0:
		p = Resize(bmp, 50);
		p = InnerBlur(p, L);
		p = Resize(p,200);
		p = InnerBlur(p, 1);
		break;
	case 1:
		p = Resize(bmp, 25);
		p = InnerBlur(p, L);
		p = Resize(p,400);
		p = InnerBlur(p, 1);
		break;		
	}		
	return p;
}

This is, as far as I know, the fastest way to do a blur in Realbasic and Java. If someone knows of a faster one, please contact me, I’ll be very interested. :-)

Before we continue, I’m going to make a function in B4A to merge a Mask layer with a picture so we can do the tilt Shift in a way similar to Realbasic.
As mentioned in a previous article, you have to make sure your images are in a certain format on Android otherwise you can strange colors or resizes.
Here is a function that makes sure the picture is in ARGB_8888 format:

Basic4Android:

' exDraw was dimmed before as Dim ExDraw As ABExtDrawing '(version 1.70 or higher required)
Sub Convert_RGB565_To_ARGB8888(iFolder As String, iFile As String) As Bitmap	
	Dim iBmp As Bitmap
	iBmp.Initialize(iFolder, iFile)

	' only convert it if it is a RGB565
	If ExDraw.GetConfig(iBmp) = ExDraw.RGB_565 Then
		Dim w, h As Int
		
		w = iBmp.Width
		h = iBmp.Height
	
		Dim tmpBmp As Bitmap
		tmpBmp.InitializeMutable(w,h)
		Dim cnvRect As Rect
		cnvRect.Initialize(0,0,w, h)
		Dim cnvCanv As Canvas
		cnvCanv.Initialize2(tmpBmp)
		
		Dim aRect As Rect
		aRect.Initialize(0,0,w,h)
		
		cnvCanv.DrawBitmap(iBmp,Null, aRect)		
		Return tmpBmp
	Else
		Return iBmp
	End If
End Sub

And here is our merge function:

Basic4Android:

Sub MergeWithAlphaLayer(iBmp As Bitmap, iLayerBmp As Bitmap) As Bitmap
	Dim MergedBmp As Bitmap
	Dim MergedBmpCanv As Canvas
	
	Dim w, h As Int
	
	w = iBmp.Width
	h = iBmp.Height
	
	Dim aRect As Rect
	aRect.Initialize(0,0,w,h)
	
	MergedBmp.InitializeMutable(w,h)
	MergedBmpCanv.Initialize2(MergedBmp)
	
	Dim Alpha As Bitmap
	Alpha.InitializeMutable(w,h)
	
	' pic
	MergedBmpCanv.DrawBitmap(iBmp, Null, aRect)
	
	' mask
	Dim Pixels(w*h) As Int
	ExDraw.getPixels(iLayerBmp,Pixels , 0, w, 0, 0, w, h)
	Dim count As Int
	count = (w*h) - 1
	For i = 0 To count
    	    Pixels(i) = Bit.ShiftLeft(Pixels(i),8) 'move the red pixel value to the alpha channel
	Next
	ExDraw.setPixels(Alpha, Pixels, 0, w, 0, 0, w, h)
	
	Dim AlphaP As ABPaint
	AlphaP.Initialize
	AlphaP.SetAntiAlias(True)
	AlphaP.SetPorterDuffXfermode(ExDraw.PorterDuffMode_DST_IN) 'DST_IN only takes over the alpha values
	
	ExDraw.drawBitmap2(MergedBmpCanv, Alpha, 0, 0, AlphaP)
	
	ExDraw.Recycle(Alpha)
	
	Return MergedBmp
End Sub

Ok, back on track! The saturation function. I admit the one in Java has some room for improvement.

Realbasic:

Sub Saturation(Pic as picture, Ammount as integer)
  #pragma BackgroundTasks false
  #pragma BoundsChecking false
  #pragma NilObjectChecking false
  #pragma StackOverflowChecking false
  
  dim c as color
  dim d as Double
  dim a,x,y,w,h,m() as Integer
  dim s as RGBSurface
  dim p as Picture
  dim ir,ig,ib as Integer
  
  s=pic.RGBSurface
  d=Ammount/100
  w=pic.Width-1
  h=pic.Height-1
  
  Redim m(510)
  for x=0 to 510
    m(x)=(x-255)*d
  Next
  
  for y=0 to h
    for x=0 to w
      c=s.Pixel(x,y)
      ir=c.red
      ig=c.green
      ib=c.blue
      a=( ir + ig + ib ) \ 3
      s.Pixel(x,y)=rgb( ir+m(ir-a+255) , ig+m(ig-a+255) , ib+m(ib-a+255) )
    next
  next
End Sub

Java (B4A library):

public Bitmap Saturate(Bitmap bmp,float Ammount) {
	//Initialize the ColorMatrix object  
	ColorMatrix colorMatrix = new ColorMatrix();  
	//Initialize the ColorMatrixColorFilter object  
	ColorMatrixColorFilter cmFilter = new ColorMatrixColorFilter(colorMatrix);  

	//Initialize the cmPaint  
	Paint cmPaint = new Paint();  
	//Set 'cmFilter' as the color filter of this paint  
	cmPaint.setColorFilter(cmFilter);  
	
	colorMatrix.setSaturation(Ammount/(float)100);  
	//Create a new ColorMatrixColorFilter with the recently altered colorMatrix  
	cmFilter = new ColorMatrixColorFilter(colorMatrix);  

	//Assign the ColorMatrix to the paint object again  
	cmPaint.setColorFilter(cmFilter);  

	//Draw the Bitmap into the mutable Bitmap using the canvas. Don't forget to pass the Paint as the last parameter
	Bitmap alteredBitmap = Bitmap.createBitmap(bmp.getWidth(), bmp.getHeight(), bmp.getConfig());
	Canvas canvas = new Canvas(alteredBitmap);
	canvas.drawBitmap(bmp, 0, 0, cmPaint);
	
	return alteredBitmap;
}

The actual Tilt Shift. This is nothing more that going over the steps I’ve shown in the schema.

Realbasic:

Sub DoTiltShiftRect(bmp as Picture, BlurRadius as integer, focusX as integer, focusY as integer, focusW as integer, focusH as integer, DoSaturtion as boolean)
  mBuffer = new Picture(640,480, 32)
  tmpPic = NewPicture(640,480, 32)
  tmpMask = NewPicture(640,480, 32)
  
  mBuffer = Blur(bmp,blurRadius,0)
  
  tmpPic.Graphics.DrawPicture bmp,0,0
  
  tmpMask.Graphics.FillRect(focusX,focusY - focusH/2, focusW,focusH)
  tmpMask = Blur(tmpMask, 7,1)
  
  tmpPic.Mask.Graphics.DrawPicture tmpMask,0,0
  
  mBuffer.Graphics.DrawPicture tmpPic,0,0
  
  if DoSaturtion then
    Saturation mBuffer,80
  end if
  
  Canvas1.Refresh(false)
End Sub

Basic4Android:

Sub DoTiltShiftRect(iBmp As Bitmap, BlurRadius As Int, focusR As Rect, DoSaturation As Boolean) As Bitmap
	Dim tgtCanv As Canvas
	
	Dim w, h As Int
		
	w = iBmp.Width
	h = iBmp.Height

	Dim tgtBmp As Bitmap
	tgtBmp.InitializeMutable(w, h)
	tgtCanv.Initialize2(tgtBmp)
	
	' blur the picture
	Dim srcBlurred As Bitmap
	srcBlurred = ExDraw.Blur(iBmp, BlurRadius, 0)	
	
	' create the sharp box
	Dim Alpha As Bitmap
	Alpha.InitializeMutable(w, h)
	Dim AlphaCanv As Canvas
	AlphaCanv.Initialize2(Alpha)
	' draw a full white rect on a black background
	Dim tmpR As Rect
	tmpR.Initialize(0,0,w, h)
	AlphaCanv.DrawRect(tmpR, Colors.black, True, 0dip)
	AlphaCanv.DrawRect(focusR, Colors.white, True, 0dip)
	' blur the white box to emulate a gradient blur, may be fast and ugly
	Dim AlphaBlurred As Bitmap
	AlphaBlurred = ExDraw.Blur(Alpha, 7, 1)
	
	' merge the blurred alpha layer with the bitmap into the overlay
	Dim Overlay As Bitmap
	Overlay = MergeWithAlphaLayer(iBmp, AlphaBlurred)		
	
	' saturate the picture to make it more life like
	Overlay = ExDraw.Saturate(Overlay, 80)
		
	Dim aRect As Rect
	aRect.Initialize(0,0,w,h)
	
	' draw the blurred image
	tgtCanv.DrawBitmap(srcBlurred, aRect, aRect)
	' draw the overlay
	tgtCanv.DrawBitmap(Overlay, aRect, aRect)	
	
	' saturate
	If DoSaturation Then
		tgtBmp = ExDraw.Saturate(tgtBmp, 90)
	End If
	
	ExDraw.Recycle(srcBlurred)
	ExDraw.Recycle(Alpha)
	ExDraw.Recycle(AlphaBlurred)
	ExDraw.Recycle(Overlay)
	
	Return tgtBmp
End Sub

And that’s it! Remember I’ll only did it for a rectangle here in the tutorial, but you can use anything.

Notes about the programs:

1. you can point anywhere on the y-axe in the picture to re-position the focusbox.
2. in the Realbasic version you can change the picture in the main.open() event: CurrentPicture = beach2
3. for the compiled RB version you can use the command line:

	Syntax: TiltShift.exe /PIC=picturename.jpg;focusleft;focustop;focuswidth;focusheight;dosaturation

		focus variables = the focus box
		DoSaturation: 0 = false, 1 = true

		Example:

		TiltShift.exe /PIC=beach2.jpg;30;300;580;240;1

4. in the Basic4Android version you can change the picture in the Activity_Create() sub: srcBmp = ABBitmapTools.Convert_RGB565_To_ARGB8888(File.DirAssets, "beach2.jpg")
5. the TiltShift.apk can be found in the B4A version in the folder \Objects\.

It would be nice if you give me credit if you use this code in your own program. ;-)

The full source code can be downloaded from:
Realbasic: download here
Basic4Android: download here

And some more eye candy!

Click here to Donation if you like my work


RealBasic: Computer Vision -> FAST Corner Detection


As promised, here is the FAST (Features from Accelerated Segment Test) implementation in RealBasic! And as you’ll notice, this is extremely fast!

Wiki:

AST is an acronym standing for Accelerated Segment Test. This test is a relaxed version of the SUSAN corner criterion. Instead of evaluating the circular disc only the pixels in a Bresenham circle of radius r around the candidate point are considered. If n contiguous pixels are all brighter than the nucleus by at least t or all darker than the nucleus by t, then the pixel under the nucleus is considered to be a feature. This test is reported to produce very stable features.[17] The choice of the order in which the pixels are tested is a so called Twenty Questions problem. Building short decision trees for this problem results in the most computationally efficient feature detectors available.

The first corner detection algorithm based on the AST is FAST (Features from Accelerated Segment Test). Although r can in principle take any value, FAST uses only a value of 3 (corresponding to a circle of 16 pixels circumference), and tests show that the best results are achieved with n being 9. This value of n is the lowest one at which edges are not detected. The order in which pixels are tested is determined by the ID3 algorithm from a training set of images. Confusingly, the name of the detector is somewhat similar to the name of the paper describing Trajkovic and Hedley’s detector.

It is the RealBasic translation of the nonmaximal FAST-9 written by Edward Rosten http://www.edwardrosten.com/work/fast.html
With this library you can find Corners very fast within a picture.

and it can do this under e.g. different light intensities:

Again, the RealBasic engine has a simple interface so you can focus on the fun stuff!

    dim FCD as new ABFastCornerDetector
  
  dim tmpXYTraining(-1) as ABXY
  dim i as integer
  dim j as integer
  
  dim pic as Picture
  dim tmpPic as Picture
  
  dim DownSample as integer
  dim build2Dim as Boolean = true
  
  Dim f as FolderItem
  
  dim ft1 as new filetype
  ft1.name = "picture"
  ft1.Extensions = ".png;.bmp;.jpg;.jpeg"
  
  dim dlg as OpenDialog
  dlg = new OpenDialog
  
  dlg.Title = "Pick picture"
  dlg.PromptText = "Pick a picture to compare"
  dlg.Filter=ft1
  if dlg.InitialDirectory = nil then
    dlg.InitialDirectory = GetFolderItem("")
  end if
  
  f = dlg.ShowModal
  if f <> nil then
    if f.Exists then
      pic = f.OpenAsPicture
    else
      Return
    end if
  else
    Return
  end if
  
  ' these are then numbers you can play with
  FCD.threshold_detection = 20
  FCD.threshold_scoring = 0
  DownSample = 2
  ' do the FAST detection
  tmpXYTraining = FCD.detectFASTCornerFeatures(pic,DownSample, build2Dim)
  
  tmpPic = NewPicture(pic.Width, pic.Height, 32)
  tmpPic.Graphics.DrawPicture pic,0,0
  
  tmpPic.Graphics.ForeColor = &cFF0000
  
  ' and draw them
  for i = 0 to UBound(tmpXYTraining)
    tmpPic.Graphics.DrawLine tmpXYTraining(i).x*DownSample - 3, tmpXYTraining(i).y*DownSample, tmpXYTraining(i).x*DownSample + 3, tmpXYTraining(i).y*DownSample
    tmpPic.Graphics.DrawLine tmpXYTraining(i).x*DownSample, tmpXYTraining(i).y*DownSample-3, tmpXYTraining(i).x*DownSample, tmpXYTraining(i).y*DownSample+3
  next
  
  Canvas1.Graphics.FillRect 0,0, 640,480
  Canvas1.Graphics.DrawPicture tmpPic,0,0

You can download the FAST engine from: http://www.gorgeousapps.com/ABFAST.zip

Until the next post!

Click here to Donation if you like my work


RealBasic: isolated project 4 -> A Sepia filter


Like the good old fashion style? As the name suggested, this filter will apply sepia tone (effect) to your photos, and it’s very easy to use.

Converting a photo to sepia is a relatively easy effect.

For each pixel in the image, we first convert the pixel to greyscale. We then add a value to the green component, and double that value to the red component to give the sepia effect.

The depth of the sepia effect is determined by this value, which we pass here as the depth parameter. A value of 0 will give a standard greyscale image.
In the above image a depth value of 20 was used.

Here is the function to do this:

Sub ApplySepia(Byref pic as picture, depth as Integer)
  Dim R As Integer
  Dim G As Integer
  Dim B As Integer
  Dim pixelColor As Color
  Dim picRGB as RGBSurface
  
  picRGB = pic.RGBSurface
  
  For y As Integer = 0 To pic.Height - 1
    For x As Integer = 0 To pic.Width - 1
      pixelColor = picRGB.Pixel(x, y)
      R = (0.299 * pixelColor.Red) + (0.587 * pixelColor.Green) + (0.114 * pixelColor.Blue)
      B = R
      G = B
      
      R = R + (depth * 2)
      If R > 255 Then
        R = 255
      End If
      G = G + depth
      If G > 255 Then
        G = 255
      End If
      
      picRGB.Pixel(x, y) = RGB(R,G,B)
    Next
  Next
End Sub

RealBasic: isolated project 3 -> Liquid physics


This is a very rudimentary physics simulation of liquid water. It cannot show al lot of particles in plain RealBasic. I suspect if you rewrite it for GDI+ or even OpenGL you could use thousands of particles.

It was based on the work of Grant Kot (who made several demos for HTML5) and jgittins/quinbd’s version for Android.

It’s Grant Kots implementation of the Material Point Method (MPM). Wiki: http://en.wikipedia.org/wiki/Material_Point_Method

Grant Kot:

For interpolation, I use the quadratic B-spline presented here: Analysis and Reduction of Quadrature Errors in the Material Point Method. Please note that there is an error in equation 17, for the cubic B-spline. The middle two equations should end with 2/3. I don’t use the cubic spline though, because I’m interested in real-time simulation and I feel that the quadratic spline is the best balance of speed and quality.

Instead of integrating the density over time (which is what most of the MPM papers do), I do a density summation every frame. Because this is not dependent on previously calculated values of density, there is no accumulated error. To minimize grid artifacts, I use the cubic interpolation method presented here: A Semi-Lagrangian CIP Fluid Solver without Dimensional Splitting.

I had to read it also several times before it made any sense. :-)

If you want to get deeper into the mechanism read:
Analysis and Reduction of Quadrature Errors in the Material Point Method: http://www.gorgeousapps.com/steffen_08_IJNME_mpm_preprint.pdf

and
A Semi-Lagrangian CIP Fluid Solver without Dimensional Splitting: http://www.gorgeousapps.com/PA-08-09-19.pdf

Anyhow, the result of it was this piece of code I translated to RealBasic:

  Dim drag As Boolean = False
  Dim mdx As Single
  Dim mdy As Single
  
  Dim i, j as Integer
  Dim cxi As Integer
  Dim cyj As Integer
  Dim cyi as Integer
  Dim n As ABNode
  
  Dim phi As Single
  Dim dx, dy As Single
  Dim x, y As Single
  
  Dim cx As Integer
  Dim cy As Integer
  
  Dim p00 As Single
  Dim x00 As Single
  Dim y00 As Single
  Dim p01 As Single
  Dim x01 As Single 
  Dim y01 As Single 
  Dim p10 As Single 
  Dim x10 As Single 
  Dim y10 As Single 
  Dim p11 As Single 
  Dim x11 As Single 
  Dim y11 As Single 
  
  Dim pdx As Single 
  Dim pdy As Single 
  Dim C20 As Single 
  Dim C02 As Single 
  Dim C30 As Single 
  Dim C03 As Single 
  Dim csum1 As Single 
  Dim csum2 As Single 
  Dim C21 As Single 
  Dim C31 As Single 
  Dim C12 As Single 
  Dim C13 As Single 
  Dim C11 As Single 
  
  Dim u As Single 
  Dim u2 As Single 
  Dim u3 As Single 
  Dim v As Single 
  Dim v2 As Single 
  Dim v3 As Single 
  Dim density As Single 
  
  Dim gx,gy as Single
  Dim mu, mv As Single 
  Dim gu, gv As Single 
  
  Dim pressure As Single 
  
  Dim fx As Single
  Dim fy As Single
  
  Dim p as ABParticle
  
  Dim vx As Single
  Dim vy As Single
  Dim weight As Single
  
  If (pressed) And (pressedprev) Then
    drag = True
    mdx = 0.25 * (mx - mxprev)
    mdy = 0.25 * (my - myprev)
  End If
  
  pressedprev = pressed
  mxprev = mx
  myprev = my
  
  For Each n  In active
    n.m = 0
    n.d = 0
    n.gx = 0
    n.gy = 0
    n.u = 0
    n.v = 0
    n.ax = 0
    n.ay = 0
    n.active = False
  Next
  Redim active(-1)
  
  For Each p  In Particles
    p.cx = p.x - 0.5
    p.cy = p.y - 0.5
    
    x = p.cx - p.x
    p.px(0) = (0.5 * x * x + 1.5 * x + 1.125)
    p.gx(0) = (x + 1.5)
    x = x + 1.0
    p.px(1) = (-x * x + 0.75)
    p.gx(1) = (-2.0 * x)
    x = x + 1.0
    p.px(2) = (0.5 * x * x - 1.5 * x + 1.125)
    p.gx(2) = (x - 1.5)
    
    y = p.cy - p.y
    p.py(0) = (0.5 * y * y + 1.5 * y + 1.125)
    p.gy(0) = (y + 1.5)
    y = y + 1.0
    p.py(1) = (-y * y + 0.75)
    p.gy(1) = (-2.0 * y)
    y = y + 1.0
    p.py(2) = (0.5 * y * y - 1.5 * y + 1.125)
    p.gy(2) = (y - 1.5)
    
    For i  = 0 To 2
      For j  = 0 To 2
        cxi = p.cx + i
        cyj = p.cy + j
        n = grid(cxi,cyj)
        If Not n.active Then
          n.active = True
          active.Append n
        End If
        phi = p.px(i) * p.py(j)
        n.m = n.m + phi * p.mat.m
        n.d = n.d + phi
        dx = p.gx(i) * p.py(j)
        dy = p.px(i) * p.gy(j)
        n.gx = n.gx + dx
        n.gy = n.gy + dy
      Next
    Next
  Next
  
  For Each p In Particles
    cx = p.x
    cy = p.y
    cxi  = cx + 1
    cyi  = cy + 1
    
    p00 =grid(cx,cy).d
    x00 =grid(cx,cy).gx
    y00 =grid(cx,cy).gy
    p01 =grid(cx,cyi).d
    x01 =grid(cx,cyi).gx
    y01 =grid(cx,cyi).gy
    p10 =grid(cxi,cy).d
    x10 =grid(cxi,cy).gx
    y10 =grid(cxi,cy).gy
    p11 =grid(cxi,cyi).d
    x11 =grid(cxi,cyi).gx
    y11 =grid(cxi,cyi).gy
    
    pdx = p10 - p00
    pdy = p01 - p00
    C20 = 3.0 * pdx - x10 - 2.0 * x00
    C02 = 3.0 * pdy - y01 - 2.0 * y00
    C30 = -2.0 * pdx + x10 + x00
    C03 = -2.0 * pdy + y01 + y00
    csum1 = p00 + y00 + C02 + C03
    csum2 = p00 + x00 + C20 + C30
    C21 = 3.0 * p11 - 2.0 * x01 - x11 - 3.0 * csum1 - C20
    C31 = -2.0 * p11 + x01 + x11 + 2.0 * csum1 - C30
    C12 = 3.0 * p11 - 2.0 * y10 - y11 - 3.0 * csum2 - C02
    C13 = -2.0 * p11 + y10 + y11 + 2.0 * csum2 - C03
    C11 = x01 - C13 - C12 - x00
    
    u = p.x - cx
    u2 = u * u
    u3 = u * u2
    v = p.y - cy
    v2 = v * v
    v3 = v * v2
    density = p00 + x00 * u + y00 * v + C20 * u2 + C02 * v2 + C30 * u3 + C03 * v3 + C21 * u2 * v + C31 * u3 * v + C12 * u * v2 + C13 * u * v3 + C11 * u * v
    
    pressure  = density - 1.0
    If pressure > 2.0 Then
      pressure = 2.0
    End If
    
    fx = 0.0
    fy = 0.0
    
    If p.x < 4.0 Then
      fx = fx + p.mat.m * (4.0 - p.x)
    ElseIf p.x > gsizeX - 5 Then
      fx = fx + p.mat.m * (gsizeX - 5 - p.x)
    End If
    If p.y < 4.0 Then
      fy = fy + p.mat.m * (4.0 - p.y)
    ElseIf p.y > gsizeY - 5 Then
      fy = fy + p.mat.m * (gsizeY - 5 - p.y)
    End If
    
    If drag Then
      vx = Abs(p.x - 0.25 * mx)
      vy = Abs(p.y - 0.25 * my)
      If (vx < 10.0) And (vy < 10.0) Then
        weight = p.mat.m * (1.0 - vx / 10.0) * (1.0 - vy / 10.0)
        fx = fx + weight * (mdx - p.u)
        fy = fy + weight * (mdy - p.v)
      End If
    End If
    
    For i  = 0 To 2
      For j  = 0 To 2
        n = grid((p.cx + i),(p.cy + j))
        phi  = p.px(i) * p.py(j)
        gx  = p.gx(i) * p.py(j)
        gy  = p.px(i) * p.gy(j)
        
        n.ax = n.ax + -(gx * pressure) + fx * phi
        n.ay = n.ay + -(gy * pressure) + fy * phi
      Next
    Next
  Next
  
  For Each n  In Active
    If n.m > 0.0 Then
      n.ax = n.ax / n.m
      n.ay = n.ay / n.m
      n.ay = n.ay + 0.03
    End If
  Next
  For Each p in Particles
    For i  = 0 To 2
      For j  = 0 To 2
        n = grid((p.cx + i),(p.cy + j))
        phi = p.px(i) * p.py(j)
        p.u = p.u + phi * n.ax
        p.v = p.v + phi * n.ay
      Next
    Next
    mu = p.mat.m * p.u
    mv = p.mat.m * p.v
    For i  = 0 To 2
      For j  = 0 To 2
        n = grid((p.cx + i),(p.cy + j))
        phi  = p.px(i) * p.py(j)
        n.u = n.u + phi * mu
        n.v = n.v + phi * mv
      Next
    Next
  Next
  For Each n  In Active
    If n.m > 0.0 Then
      n.u = n.u / n.m
      n.v = n.v / n.m
    End If
  Next
  For Each p  In Particles
    gu = 0.0
    gv = 0.0
    For i  = 0 To 2
      For j  = 0 To 2
        n = grid((p.cx + i),(p.cy + j))
        phi = p.px(i) * p.py(j)
        gu = gu + phi * n.u
        gv = gv + phi * n.v
      Next
    Next
    p.x = p.x + gu
    p.y = p.y + gv
    p.u = p.u + 1.0 * (gu - p.u)
    p.v = p.v + 1.0 * (gv - p.v)
    If p.x < 1.0 Then
      p.x = (1.0 + rnd() * 0.01)
      p.u = 0.0
    ElseIf p.x > gsizeX - 2 Then
      p.x = (gsizeX - 2 - rnd() * 0.01)
      p.u = 0.0
    End If
    If p.y < 1.0 Then
      p.y = (1.0 + rnd() * 0.01)
      p.v = 0.0
    ElseIf p.y > gsizeY - 2 Then
      p.y = (gsizeY - 2 - rnd() * 0.01)
      p.v = 0.0
    End If
  Next 

The full source code and demo can be downloaded from: http://www.gorgeousapps.com/ABLiquid.zip

Until next time!


RealBasic: isolated project 2 -> Fire!

The second one is a little ‘old school’ demo effect of moving fire. I know it’s not the most convincing version out there but it is a quick and dirty one. Maybe if you play around with the params you’ll get it better. I’m not sure who made the original code. It may even be a mix of several projects.

First we need to setup the fire color palette. There are some formulas out there to generate the colors. A typical one is this (pseudo):

HSLtoRGB is used to generate colors:
Hue goes from 0 to 85: red to yellow
Saturation is always the maximum: 255
Lightness is 0..255 for x=0..128, and 255 for x=128..255

But I was lazy and just filled a table of 256 items with the colors I would need ;-)

The next part is the main loop that animates the fire.

  'start the loop (one frame per loop)
  While Not StopFire
    'make the background black
    gBuffer.ForeColor = &c000000
    gBuffer.FillRect(0,0,w,h)
    
    'randomize the bottom row of the fire buffer
    For x = 0 To w - 1
      fire(x, h - 1) = Abs(32768 + rnd()*256) Mod 256 + 128
      if fire(x, h - 1) > 255 then
        fire(x, h - 1) = 255
      end if
    Next
    'do the fire calculations for every pixel, from top to bottom
    'I remember there are other formulas out there that have different effects. Just Google it.
    For y  = 0 To h - 2
      For x  = 0 To w - 1
        fire(x, y) = ((fire((x - 1 + w) Mod w, (y + 1) Mod h) + fire((x) Mod w, (y + 1) Mod h) + fire((x + 1) Mod w, (y + 1) Mod h) + fire((x) Mod w, (y + 2) Mod h)) * 32) / 130 + rnd()
      Next
    Next
    
    'set the drawing buffer to the fire buffer, using the palette colors
    For x  = 0 To w - 1
      For y  = 0 To h - 1
        rgbBuffer.pixel(x, y) = palette(fire(x, y))
      Next
    Next
    
    'redraw the screen
    me.Refresh(false)
    app.DoEvents
   wend

That’s it! Or as Tom Hanks would have said in Cast Away: “I have made fire!”

The code and demo can be downloaded from: http://www.gorgeousapps.com/ABFire.zip

Happy coding!


RealBasic: isolated project 1 -> Plasma generator

I was doing some cleanup of my computer the other day and I stumbled on some nice projects I did over the years. Some of it may be interesting to someone so I decided to post them on the blog. These projects are AS IS, meaning they were made a long time ago and were mostly conversions from other programming languages to RealBasic without going deep into the math myself. If you want to know more on how it works, I suggest you dive into some articles on the InterWeb.

I’ll try to give credit to the original coder if i can find it back. If you are the coder, please mail me and I’ll add it to the article.
From some of the projects I even saved the original article, but unfortunately not the website were I found it.

The first one I want to share is a plasma generator. It was a conversion from a JavaScript written by Justin Seyster.

The plasma generator uses a recursive algorithm known as random midpoint displacement. The algorithm begins with a rectangular grid that has four points, one at each corner. These corners are each randomly assigned a color value.


Start with a rectangular grid that has four points, each with a color value.

Now, five new points are added to the grid. One point is added to each edge that has a color value in-between the color value of the two corners connected to the edge (i.e., it is the average value of those two corners). The fifth point is placed in the center, or midpoint, of the grid. It is the average of the original four corners.


Create a new point at each of the edges with its color value averaged from the two adjacent corners. Create a midpoint with its color value averaged from all four corners and randomly “displaced.”

This is where the algorithm gets its name. The midpoint is randomly displaced. That is to say, some random value is chosen, and the color value is shifted by the value. The range of possible random numbers is made to be proportional to the size of the square. A large square may have its midpoint displaced a great deal, and a small square can not have its midpoint displaced by more than a little bit.


Repeat

Adding the five extra points divided the original rectangle into four similar rectangles of one fourth the area. The algorithm is repeated for each of these new squares, yielding a total of sixteen squares. The process can be repeated recursively as necessary. In this case, it is repeated until each rectangle is less than the size of a single pixel. At that point, the recursion ends, and the pixel is drawn.

A great deal of the magic has to do with the selection of colors. Every color value is a number from zero to one. What the algorithm does is create a field of smoothly changing color values. A function converts these color values to colors so that the color values from zero to one flow smoothly between colors, creating the plasma effect.


This plasma fractal has been colored to show you what color value each pixel was assigned. All black means a color value of 0.0 and all white is a color value of 1.0. The chart on the right illustrates how each color value is converted to a color. The chart to the right shows all the colors from the first chart broken into their red, green and blue components.

Most of all this is done by the recursive function DivideGrid:

Private Sub DivideGrid(ByVal x As Single, ByVal y As Single, ByVal width As Single, ByVal height As Single, ByVal c1 As Single, ByVal c2 As Single, ByVal c3 As Single, ByVal c4 As Single)
  Dim Edge1, Edge2, Edge3, Edge4, Middle As Single
  Dim newWidth As Single = width / 2
  Dim newHeight As Single = height / 2
  
  If width &gt; 1 Or height &gt; 1 Then
    Middle = (c1 + c2 + c3 + c4) / 4 + Displace(newWidth + newHeight) 'Randomly displace the midpoint!
    Edge1 = (c1 + c2) / 2 'Calculate the edges by averaging the two corners of each edge.
    Edge2 = (c2 + c3) / 2
    Edge3 = (c3 + c4) / 2
    Edge4 = (c4 + c1) / 2
    
    'Make sure that the midpoint doesn't accidentally "randomly displaced" past the boundaries!
    If Middle  1.0 Then
      Middle = 1.0
    End If
    
    'Do the operation over again for each of the four new grids.
    DivideGrid(x, y, newWidth, newHeight, c1, Edge1, Middle, Edge4)
    DivideGrid(x + newWidth, y, newWidth, newHeight, Edge1, c2, Edge2, Middle)
    DivideGrid(x + newWidth, y + newHeight, newWidth, newHeight, Middle, Edge2, c3, Edge3)
    DivideGrid(x, y + newHeight, newWidth, newHeight, Edge4, Middle, Edge3, c4)
  Else 'This is the "base case," where each grid piece is less than the size of a pixel.
    'The four corners of the grid piece will be averaged and drawn as a single pixel.
    Dim c As Single = (c1 + c2 + c3 + c4) / 4
    
    rgbBuffer.pixel(x,y) = ComputeColor(c)
  End If
End Sub

The RealBasic source code and a compiled example can be downloaded from: http://www.gorgeousapps.com/ABPlasma.zip


Basic4Android: Using the new 3D Camera in ABExtDrawing 1.1

I’ve added the Camera object to the ABExtDrawing library for Basic4Android. This object must not be confused with the hardware camera. There are other libraries available for that. The new version 1.1 of the library can be downloaded from http://www.basic4ppc.com.

The camera I’m talking about is a nice feature you can use to do 3D effects on the canvas without using OpenGL. The picture above is just a noral B4A canvas where we did some rotations and translations on. It simulates a scrolling list where the listitems rotate around their axe.

Additional, I shows other features of the ABExtDrawing library to do some lighting effects.

Let me show you how this is done in B4A.

We create a new type item3D. This will hold one item in the list.

Sub Process_Globals
	Type item3D (bmp As Bitmap, Top As Int, Left As Int, Width As Int, Height As Int)
End Sub

We also have to declare some variables. Note our mCamera variable which will do the 3D conversions and some constants for our lighting effects. On a Paint object we can set color filters which will affect the color values of what we draw with that Paint object. SetLightingColorFilter takes care of that. A LightingColorFilter takes two colors that are used to modify the colors that we are drawing. The first color will be multiplied with the colors we draw, while the second one will be added to the colors we draw. The multiplication will darken the color and adding will make it brighter so we can use this class to model both shadows and highlights. It would have been even better if instead of adding it would have implemented the screen blend mode, but add works OK.

To actually calculate the light we’ll use a simplified version of Phong lighting.

Sub Globals
	'These global variables will be redeclared each time the activity is created.
	'These variables can only be accessed from this module.
	Dim items As List
	Dim HalfHeight As Float
	
	Dim SCALE_DOWN_FACTOR As Float: SCALE_DOWN_FACTOR = 0.15
	Dim DEGREES_PER_SCREEN As Int: DEGREES_PER_SCREEN = 270
	
	' Ambient light intensity
    Dim AMBIENT_LIGHT As Int: AMBIENT_LIGHT = 55
    ' Diffuse light intensity
    Dim DIFFUSE_LIGHT As Int: DIFFUSE_LIGHT = 200
    ' Specular light intensity
    Dim SPECULAR_LIGHT As Float: SPECULAR_LIGHT = 70
    ' Shininess constant
    Dim SHININESS As Float: SHININESS = 200
    ' The Max intensity of the light
    Dim MAX_INTENSITY As Int: MAX_INTENSITY = 0xFF

	Dim CurrentRotation As Int
	Dim CurrentTop As Int 
	Dim MyCanvas As Canvas
	
	Dim ScreenTop As Int: ScreenTop = 1
	Dim Panel1 As Panel
	
	Dim ExDraw As ABExtDrawing
	Dim mCamera As ABCamera
	Dim mMatrix As ABMatrix
	Dim mPaint As ABPaint
	
	Dim PI As Double: PI= 3.141592653589793238462643383279502884197
	
	Dim ClearRect As Rect
	
	Dim CurrY As Int	
End Sub

In the Activity_Create sub we initialize mCamera and the other variables like mPaint which we will also need for our lighting. Also our pictures are preloaded.

Sub Activity_Create(FirstTime As Boolean)
	Activity.LoadLayout("1")
	
	MyCanvas.Initialize(Panel1)

	HalfHeight = Activity.Height / 2
	
	Dim backbmp As Bitmap
	backbmp.Initialize(File.DirAssets, "background.png")
	Dim backbmp2 As Bitmap
	backbmp2.Initialize(File.DirAssets, "background2.png")
	Dim backbmp3 As Bitmap
	backbmp3.Initialize(File.DirAssets, "background3.png")
	
	Dim conbmp As Bitmap
	conbmp.Initialize(File.DirAssets, "contact_image.png")
	Dim conbmp2 As Bitmap
	conbmp2.Initialize(File.DirAssets, "contact_image2.png")
	Dim conbmp3 As Bitmap
	conbmp3.Initialize(File.DirAssets, "contact_image3.png")

	items.Initialize
	Dim i As Int
	Dim random As Int
	For i = 0 To 19
		random = Rnd(0,3)
		If random = 0 Then
			items.Add(CreateNewItem(backbmp, conbmp, "Colleague " & i, "Name of colleague " & i, 25, i*175, Activity.Width - 50, 120))
		Else
			If random = 1 Then
				items.Add(CreateNewItem(backbmp2, conbmp2, "Friend " & i, "Name of friend " & i, 25, i*175, Activity.Width - 50, 120))
			Else
				items.Add(CreateNewItem(backbmp3, conbmp3, "Client " & i, "Name of the client " & i, 25, i*175, Activity.Width - 50, 120))
			End If
		End If
	Next
	
	CurrentRotation = -(DEGREES_PER_SCREEN * ScreenTop) / Activity.Height
    
	ClearRect.Initialize(0,0,Activity.Width, Activity.Height)
	
	mCamera.Initialize
	mMatrix.Initialize
	mPaint.Initialize
	mPaint.SetAntiAlias(True)
    mPaint.SetFilterBitmap(True)
	
	DrawMe
End Sub

The sub CreateNewItem() is used to make one list item. Default B4A canvas drawing functions are used to show how well they work together with the ABExtDrawing functions.

Sub CreateNewItem(Background As Bitmap, icon As Bitmap, Subj As String, desc As String, Left As Int, Top As Int, Width As Int, Height As Int) As item3D
	Dim item As item3D
	Dim c As Canvas
	item.Initialize
	item.bmp.InitializeMutable(Width, Height)
	
	c.Initialize2(item.bmp)
	
	' background
	Dim dstR As Rect	
	dstR.Initialize(0,0,Width,Height)
	c.DrawBitmap(Background,Null, dstR)
		
	' draw Icon
	dstR.Initialize(15,15, icon.Width, icon.Height)
	c.DrawBitmap(icon, Null, dstR)
	
	c.DrawText(Subj, 100, 30,Typeface.DEFAULT_BOLD,16, Colors.White,"LEFT") 
	c.DrawText(desc, 100, 60, Typeface.DEFAULT,16, Colors.White,"LEFT")
	
	item.Top = Top
	item.Left = Left
	item.Width = Width
	item.Height = Height
	Return item
End Sub

In the drawItem() sub all calculations are done for one item in the list. Each item will be a block that will rotate around its X-axis and look like it is rolling on the ground when the list stars to scroll. Each block will be as wide as the item normally is and the depth will be the same as the height. We’ll use the same bitmap for all the sides.

So what do we need to do to achieve this effect? In order to draw the blocks we need to draw the bitmap two times (since we will almost always see two sides of the block). We also need to have some kind of rotation variable to keep track of the main rotation. Since the blocks should rotate when the user scrolls the list and the blocks should have the same rotation (so that they all face up at the same time, see further).

Sub DrawItem(item As item3D)
	Dim CenterX As Float
	Dim CenterY As Float

	' get centerX AND centerY
	CenterX = item.Width / 2
	CenterY = item.Height / 2

    ' get scale
    Dim distFromCenter As Float 
	distFromCenter = (item.Top + CenterY - HalfHeight) / HalfHeight
	Dim scale As Float
	scale = (1 - SCALE_DOWN_FACTOR * (1 - Cos(distFromCenter)))

    ' get rotation
    Dim RotationX As Float
	RotationX = CurrentRotation - 20 * distFromCenter
    RotationX = RotationX Mod 90
    If (RotationX < 0) Then
        RotationX = RotationX + 90
    End If	

    ' draw it
    If (RotationX < 45) Then
		drawFace(item, CenterX, CenterY, scale, RotationX - 90)
        drawFace(item, CenterX, CenterY, scale, RotationX)
		       
    Else
        drawFace(item, CenterX, CenterY, scale, RotationX)
        drawFace(item, CenterX, CenterY, scale, RotationX - 90)
    End If
End Sub

Finally, DrawFace is called and this is where the magic happens. Worth noting is that the code that will draw one face of the block is the same, it just depends on the rotation, so it’s extracted to a method. To draw a complete block we then simply draw two faces 90 degrees apart at the same place.

To draw a face we first translate the camera so that the face will be drawn closer to us. Then we rotate it and after that we translate it back so we don’t scale it. Keep in mind that the calls to the camera, just like the rotate, translate and scale methods on Canvas, needs to be written in reversed order, so to speak. In the code below, it is the last line that translates the face towards us, then we rotate it, and finally, with the first line, we translate it back.

mCamera.translate(0, 0, centerY);
mCamera.rotateX(rotation);
mCamera.translate(0, 0, -centerY);

The rest of drawFace is not that hard. It gets the matrix from the camera, pre and post translates the matrix and then draws the bitmap with the matrix.

This code will draw each item as if placed in the origin in 3D space and then we move the items to the correct place on the screen using pre and post translate on the matrix. This moves what we draw in 2D space without changing the perspective. We could apply the translation in X and Y on the camera instead, then the translation would be in 3D space and it would affect the perspective. We’re not doing that here because I want the appearance of a larger field of view than the fixed field of view of the camera. Instead, we fake it by slightly rotating and scaling the items depending on the distance from center of the screen.

We calculate the light and create a LightingColorFilter that we can set to our Paint object.

Sub drawFace(item As item3D, CenterX As Float, CenterY As Float, scale As Float, RotationX As Float) 
	' save the camera state
    mCamera.save

    ' translate AND Then rotate the camera
    mCamera.translate(0, 0, CenterY)
    mCamera.rotateX(RotationX)
	mCamera.translate(0, 0, -CenterY)

    
    ' get the matrix from the camera AND Then restore the camera
    mCamera.getMatrix(mMatrix)
    mCamera.restore()

    ' translate AND scale the matrix
    mMatrix.preTranslate(-CenterX, -CenterY)
    mMatrix.postScale(scale, scale)
    mMatrix.postTranslate(item.left + CenterX, item.top + CenterY)

    ' set the light
	Dim cosRotation As Double
	cosRotation = Cos(PI * RotationX / 180)
    Dim intensity As Int
	intensity = AMBIENT_LIGHT + (DIFFUSE_LIGHT * cosRotation)
    Dim highlightIntensity As Int
	highlightIntensity = (SPECULAR_LIGHT * Power(cosRotation,SHININESS))	
    If (intensity > MAX_INTENSITY) Then
        intensity = MAX_INTENSITY
    End If
    If (highlightIntensity > MAX_INTENSITY) Then
        highlightIntensity = MAX_INTENSITY
    End If
    Dim light As Int
	light = Colors.rgb(intensity, intensity, intensity)
    Dim highlight As Int
	highlight = Colors.rgb(highlightIntensity, highlightIntensity, highlightIntensity)
    mPaint.SetLightingColorFilter(light, highlight)   
    
    ' draw the Bitmap
    ExDraw.drawBitmap4(MyCanvas, item.bmp,  mMatrix, mPaint)
End Sub

The DrawMe() sub is the overall function to draw all the items.

Sub DrawMe()
	Dim i As Int
	ExDraw.save2(MyCanvas, ExDraw.MATRIX_SAVE_FLAG)
	MyCanvas.DrawRect(ClearRect, Colors.Black, True, 1dip)
	'MyCanvas.DrawBitmap(FormBack, Null, formR)
	For i = 0 To items.Size - 1
		DrawItem(items.Get(i))
	Next
	ExDraw.restore(MyCanvas)
	
	Panel1.Invalidate
End Sub

And in the Panel1_Touch sub we’ll animate our list. Note that it is here that we make sure all boxes face the same. This is done by the lines:

ScreenTop = ScreenTop + DeltaY
CurrentRotation = -(DEGREES_PER_SCREEN * ScreenTop) / Activity.Height

Doing like this will make the blocks rotate DEGREES_PER_SCREEN degrees when the user scrolls the list an entire screen no matter the pixel-height of the screen.

Here is the full sub:

Sub Panel1_Touch (Action As Int, X As Float, Y As Float) As Boolean 'Return True to consume the event
	Dim DeltaY As Int
	Select Action
        Case Activity.ACTION_DOWN
			CurrY = Y
            'Log("down")
        Case Activity.ACTION_MOVE
			DeltaY = Y - CurrY
			CurrY = Y
			Dim i As Int
			Dim it As item3D
			For i = 0 To items.Size - 1
				it = items.Get(i)
				it.Top = it.Top + DeltaY
				items.Set(i, it)
			Next
			
			ScreenTop = ScreenTop + DeltaY
			CurrentRotation = -(DEGREES_PER_SCREEN * ScreenTop) / Activity.Height
						
			DrawMe
            'Log("move")
        Case Activity.ACTION_UP
            'Log("up")
    End Select
    Return True
End Sub

So this concludes this tutorial. You can download the project from http://www.gorgeousapps.com/AB3DCamera.zip

Note that at the first run, it may not work very smooth on some devices. But after some time it goes very well.

Have fun programming and until next time!

Click here to Donation if you like my work


Follow

Get every new post delivered to your Inbox.

Join 79 other followers