Android Tutorial [B4X] Drawing with BitmapCreator

Discussion in 'Tutorials & Examples' started by Erel, Nov 1, 2018.

  1. Erel

    Erel Administrator Staff Member Licensed User

    BitmapCreator (BC) continues to evolve. Version 4.5 update is a large update with several new drawing features.

    Background

    At its core BC holds an array of bytes that represent an image.
    It can quickly extract the raw data from existing bitmaps and it can quickly generate bitmaps from this data.
    BC is mostly written in B4X and it is cross platform. It depends on XUI library.

    We can directly access the raw data and modify it. As we work with the raw data directly we can do things that are not possible to do with Canvas. You can see all kinds of examples in BitmapCreatorEffects class.

    BC is the graphic engine behind XUI2D game framework. There are all kinds of features in BC that were added specifically for the 2d games use case. They will not be discussed here.
    BitmapCreator can be used in many other cases. It can be used as an alternative to Canvas / B4XCanvas and they can be both used together.

    Simple example

    Code:
    'Depends on XUI / iXUI / jXUI and BitmapCreator / iBitmapCreator / jBitmapCreator libraries
    'Similar code will work in all three platforms.
    Sub Process_Globals
       
    Private xui As XUI
    End Sub

    Sub Activity_Create(FirstTime As Boolean)
       
    Dim bc As BitmapCreator
       bc.Initialize(
    100%x100%y)
       bc.DrawCircle(
    100dip100dip50dip, xui.Color_Red, True10dip)
       bc.DrawLine(
    00, bc.mWidth, bc.mHeight, 0xff0000ff5dip)
       
    Activity.SetBackgroundImage(bc.Bitmap)
       
    'we can continue to use 'bc' 
    End Sub
    New drawing methods

    The following drawing methods were added in v4.5: DrawLine, DrawCircle, DrawRect, DrawRectRounded and DrawPath.
    With the exception of DrawLine, the shapes can be either filled or not filled. All methods include a StrokeWidth parameter that affects the contour width of non-filled drawings.
    All drawings are antialiased. This is very important and was challenging to implement efficiently.
    The drawings are optimized and are quite fast.

    There are three variants for each method:
    Code:
    DrawCircle (X As Float, Y As Float, Radius As Float, Color As Int, Filled As Boolean, StrokeWidth As Int)
    DrawCircle2 (X 
    As Float, Y As Float, Radius As Float, Brush As BCBrush, Filled As Boolean, StrokeWidth As Int)
    AsyncDrawCircle (X 
    As Float, Y As Float, Radius As Float, Brush As BCBrush, Filled As Boolean, StrokeWidth As Int) As DrawTask
    Drawings are done with brushes (BCBrush). There are three ways to create brushes:
    BC.CreateBrushFromColor
    BC.CreateBrushFromBitmap
    BC.CreateBrushFromBitmapCreator

    DrawCircle creates the brush for you and calls DrawCircle2:
    Code:
    Public Sub DrawCircle (X As Float, Y As Float, Radius As Float, Color As Int, Filled As Boolean, StrokeWidth As Int) As BCBrush
       
    Return DrawCircle2(X, Y, Radius, CreateBrushFromColor(Color), Filled, StrokeWidth)       
    End Sub
    Example with one brush created from a bitmap and another one from a BC:
    Code:
    Sub Activity_Create(FirstTime As Boolean)
       
    Dim bc As BitmapCreator
       bc.Initialize(
    100%x100%y)
       
    Dim bmp As B4XBitmap = xui.LoadBitmapResize(File.DirAssets, "logo.png"32dip32dipTrue)
       
    Dim brush As BCBrush = bc.CreateBrushFromBitmap(bmp)
       bc.DrawCircle2(
    200dip200dip150dip, brush, True10dip)
       
       
    Dim bc2 As BitmapCreator
       bc2.Initialize(
    20dip, bc.mHeight)
       bc2.FillGradient(
    Array As Int(xui.Color_Red, xui.Color_Yellow), bc2.TargetRect, "TOP_BOTTOM")
       bc.DrawLine2(
    10dip10dip, bc.mWidth - 10dip, bc.mHeight - 10dip, bc.CreateBrushFromBitmapCreator(bc2), 30dip)
       
    Activity.SetBackgroundImage(bc.Bitmap)
    End Sub
    [​IMG]

    Brushes can and should be reused. If you are making many drawings with the same color then create the brush yourself and use it instead of implicitly creating a new brush each call.

    The third 'async' method is used for making drawings with a background thread. It will be discussed in a different tutorial.

    Brushes

    There are several configurable settings.
    You can change the offset. For example:
    Code:
    Dim Brush2 As BCBrush = bc.CreateBrushFromBitmapCreator(bc2)
    Brush2.SrcOffsetY = bc.TargetRect.CenterY
    bc.DrawLine2(
    10dip10dip, bc.mWidth - 10dip, bc.mHeight - 10dip, Brush2 , 30dip)
    [​IMG]

    Brush.SrcOffsetX / SrcOffsetY define the brush zero position.

    We can easily make nice animations by modifying the offsets (animation looks better than in this gif):
    [​IMG]

    Code:
    Code:
    Sub Process_Globals
       
    Private xui As XUI
    End Sub

    Sub Globals
       
    Private ImageView1 As B4XView 'using ImageView this time to make it cross platform.
    End Sub

    Sub Activity_Create(FirstTime As Boolean)
       
    Activity.LoadLayout("1")
       
    Dim bc As BitmapCreator
       bc.Initialize(ImageView1.Width, ImageView1.Height)
       
    Dim bmp As B4XBitmap = xui.LoadBitmapResize(File.DirAssets, "logo.png"32dip32dipTrue)
       
    Dim brush As BCBrush = bc.CreateBrushFromBitmap(bmp)
       
    Dim bc2 As BitmapCreator
       bc2.Initialize(
    20dip, bc.mHeight)
       bc2.FillGradient(
    Array As Int(xui.Color_Red, xui.Color_Yellow), bc2.TargetRect, "TOP_BOTTOM")
       
    Dim Brush2 As BCBrush = bc.CreateBrushFromBitmapCreator(bc2)
       
    Do While True
           brush.SrcOffsetX = brush.SrcOffsetX + 
    1dip
           brush.SrcOffsetY = brush.SrcOffsetY + 
    1dip
           Brush2.SrcOffsetY = Brush2.SrcOffsetY + 
    1dip
           bc.DrawRect(bc.TargetRect, xui.Color_Transparent, 
    True0)
           bc.DrawLine2(
    10dip10dip, bc.mWidth - 10dip, bc.mHeight - 10dip, Brush2 , 30dip)
           bc.DrawCircle2(
    200dip200dip150dip, brush, True10dip)
           bc.SetBitmapToImageView(bc.Bitmap, ImageView1) 
    '<---- this is the best way to set a BC Bitmap to an ImageView. It sets the Gravity to FILL (unlike B4XView.SetBitmap).
           Sleep(15)
       
    Loop
    End Sub
    There are two additional parameters that we can change:
    BCBrush.BlendBorders (default = true) - Whether the antialiased borders should be blend with the background.
    BCBrush.BlendAll (default = false) - Whether the interior drawings should be blend with the background.
    This is relevant when the brush is not fully opaque. For example the above code with:
    Code:
    brush.BlendAll = True
    [​IMG]

    Two notes:

    - BlendAll = False (the default) is significantly faster.
    - If you want to draw with a semi-transparent color brush then you need to do two things:
    Code:
    BCBrush.IsSolidColor = False 'IsSolidColor is used internally for optimizations.
    BCBrush.BlendAll = Trure
    Paths

    There are three types of paths so it can be confusing: Path (native Canvas), B4XPath (B4XCanvas) and BCPath. As you can guess BitmapCreator works with BCPath.
    A path defines a list of points.
    You can either draw lines between these points or fill the polygon shape.
    The shape is closed automatically if needed.

    Code:
    Dim Rhombus As BCPath
    Rhombus.Initialize(
    0, bc.mHeight / 2).LineTo(bc.mWidth / 20)
    Rhombus.LineTo(bc.mWidth, bc.mHeight / 
    2).LineTo(bc.mWidth / 2, bc.mHeight)
    bc.DrawPath2(Rhombus, brush, 
    True0)
    [​IMG]

    Code:
    bc.DrawPath2(Rhombus, brush, False10dip)
    This time the shape is not closed automatically:

    [​IMG]

    It is not obvious in the above image but by default the connection segments between each two lines are also drawn. It can be disabled with:
    Code:
    Path.DrawConnectionSegments = False
    You can access the points list directly and remove or add points from another path. You should however call Path.Invalidate after those changes.

    To dip or not to dip

    Both options are possible. Both options will work in all three platforms though they only make a difference in B4A.
    In the above examples the BC size is the same as the activity size or the ImageView size. This means that the BC scale is the same as the device scale and we need to use 'dip' units.
    I actually recommend to create normalized BCs as the default and to avoid using 'dip' units. In most cases it will look good enough, the memory footprint will be smaller and performance will be better.

    Two things should be done to work with normalized BCs:
    1.
    Code:
    'divide the width and height with xui.scale:
    bc.Initialize(ImageView1.Width / xui.Scale, ImageView1.Height / xui.Scale)
    2. Don't use 'dip' or %x / %y
    Code:
    Sub Activity_Create(FirstTime As Boolean)
       
    Activity.LoadLayout("1")
       
    Dim bc As BitmapCreator
       bc.Initialize(ImageView1.Width / xui.Scale, ImageView1.Height / xui.Scale)
       
    Dim bmp As B4XBitmap = xui.LoadBitmapResize(File.DirAssets, "logo.png"3232True)
       
    Dim brush As BCBrush = bc.CreateBrushFromBitmap(bmp)
       bc.DrawCircle2(
    200200150, brush, True10)
       
       
    Dim bc2 As BitmapCreator
       bc2.Initialize(
    20, bc.mHeight)
       bc2.FillGradient(
    Array As Int(xui.Color_Red, xui.Color_Yellow), bc2.TargetRect, "TOP_BOTTOM")
       bc.DrawLine2(
    1010, bc.mWidth - 10, bc.mHeight - 10, bc.CreateBrushFromBitmapCreator(bc2), 30)
       bc.SetBitmapToImageView(bc.Bitmap, ImageView1)
    End Sub
    If you get compiler warnings about missing 'dip' units then you can disable them in the current module with:
    Code:
    #IgnoreWarnings: 6
     
    mendiburen, maXim, MarcoRome and 7 others like this.
  2. Erel

    Erel Administrator Staff Member Licensed User

    Last edited: Nov 4, 2018
    asales and Myron like this.
  3. klaus

    klaus Expert Licensed User

    Hi Erel,
    This looks very promising!
    Will BCPath also include methods like moveTo, arcTo etc. ?
     
  4. sorex

    sorex Expert Licensed User

    can we still create that imageview1 by code and size it as width/height 10%x and then use the code you used in your normalize example?

    what's bad about using

    Code:
    bc.Initialize(10%x,10%y)
    would that create a bigger buffer when the scale is not 1 (compared to your width/xui.scale method) ?
     
  5. Erel

    Erel Administrator Staff Member Licensed User

    I plan to add more features. I did make some tests with bezier curves and decided to release this update without them as it is already a large update.

    You can use this code to create curves:
    Code:
    Dim Rhombus As BCPath
    Rhombus.Initialize(
    10, bc.mHeight / 2).LineTo(bc.mWidth / 210)
    CurveTo(Rhombus, bc.mWidth, 
    0, bc.mWidth - 10, bc.mHeight / 2)
    CurveTo(Rhombus, bc.mWidth, bc.mHeight, bc.mWidth / 
    2, bc.mHeight - 10)
    Rhombus.LineTo(
    10, bc.mHeight / 2)
    bc.DrawPath(Rhombus, xui.Color_Red, 
    True10)
    Code:
    Public Sub CurveTo (Path1 As BCPath, ControlPointX As Float, ControlPointY As Float, TargetX As Float, TargetY As Float)
       
    Dim LastPoint As InternalBCPathPointData = Path1.Points.Get(Path1.Points.Size - 1)
       
    Dim CurrentX As Float = LastPoint.X
       
    Dim Currenty As Float = LastPoint.Y
       
    Dim NumberOfSteps As Int = 15 '<--- change as needed
       Dim dt As Float = 1 / NumberOfSteps
       
    Dim t As Float = dt
       
    For i = 1 To NumberOfSteps
           
    Dim tt1 As Float =  (1 - t) * (1 - t)
           
    Dim tt2 As Float = 2 * (1 - t) * t
           
    Dim tt3 As Float = t * t
           
    Dim x As Float = tt1 * CurrentX + tt2 * ControlPointX + tt3 * TargetX
           
    Dim y As Float = tt1 * Currenty + tt2 * ControlPointY + tt3 * TargetY
           Path1.LineTo(x, y)
           t = t + dt
       
    Next
    End Sub
    [​IMG]

    It doesn't matter how you create the views.
    You always have two options:

    1. High scale with dips:
    Code:
    bc.Initialize(ImageView1.Width, ImageView.Height) 'always better to use the view's dimensions.
    2. Normalized scale without dips:
    Code:
    bc.Initialize(ImageView1.Width / xui.Scale, ImageView.Height / xui.Scale)
     
    klaus likes this.
  6. Erel

    Erel Administrator Staff Member Licensed User

  7. Erel

    Erel Administrator Staff Member Licensed User

Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice