Transformation an introduction
Android Graphics
Prerequisite: Read Andriod Graphics creating the first program
My other article describes how to write a basic android graphics program. The four points in the final result were corners of a square. As you might notice the X value increases as we go down direction. And Y value increases as we move in the right direction.
We are all taught in high school that X increases Upward and Y increases toward the right in a cartesian coordinate system. If we want to follow that convention we need to do some transformation of these coordinates from the Android Coordinate system to the Cartesian coordinate system.
These kinds of coordinate transformations are important when we translate real-world images or data to the Android canvas. In this section, we will discuss how to achieve it.
How will we do it?
Android Matrix is the answer. This library implements the Affine transformation. We will learn about Affine Transformation late. To start with we will start using the affine transformation using matrix.
Create a class named AffineTransformer
In the android studio, create a class named AffineTransformer as the code below
package com.example.transformation
class AffineTransformer {
}
Since all transformation we are doing here depends on the size of the Activity screen let us add a default constructor that takes the basic width and height of the drawing canvas.
package com.example.transformation
class AffineTransformer constructor(width: Int, height: Int) {
}
Add a init body for the class
package com.example.transformation
class AffineTransformer constructor(width: Int, height: Int) {
init {
}
}
Now we need to do some transformation to map the cartesian coordinate to screen/Canvas coordinates. The canvas has a height and width as shown in the image below. The direction of the y axis is inverted.
You can see the cartesian (1,1) is mapped to canvas (1, 15)
&
Cartesian (2,15) is mapped to canvas (2,1)
Without further explanation, let me introduce a translation for (x, y) value of screen coordinate in terms of the cartesian coordinates (primed)
These two equations can be written in a matrix form as
Since this equation is in the Matrix form of X′ =AX+B so, it is an affine transformation.
The affine transformation can also be written, ignoring the last
value, as.
This is equivalent to X′ =M.X where M is the android matrix
This is how Android Matrix works. In fact, we can work with this matrix with all the transformations.
In the future chapters, we will discuss in detail how these transformations are working and how the order of the transformation makes a difference.
Using this transformation in Android Graphics
Now we will see how to build this transformation in our AffineTransformer class. We need to look at some key points in our earlier equation. The x value doesn’t change.
And the Y value has two changes. One is the sign (direction) is changed. This can be considered as scaling in the negative direction. A shift in the positive direction of the screen happens. This shift is a translation of a point in the positive direction of the screen.
So the transformation is scaling and translation
To start this let's define a Matrix
Let us define a Matrix instance in the AffineTransformer class as below.
package com.example.transformation
import android.graphics.Matrix
class AffineTransformer constructor(width: Int, height: Int) {
private var cartesianToAndroid : Matrix = Matrix()
init {
}
}
At this point, we will set up the matrix for the cartesian to Android transformation.
package com.example.transformation
import android.graphics.Matrix
class AffineTransformer constructor(width: Int, height: Int) {
private var cartesianToAndroid : Matrix = Matrix()
init {
//Tranformation for changing the orientation
cartesianToAndroid.setScale( 1f, -1f);
//Trnasformation for translation
cartesianToAndroid.postTranslate(0f, height.toFloat())
}
}
Add a function to transform the input x and y value to the transformed point.
package com.example.transformation
import android.graphics.Matrix
import android.graphics.PointF
class AffineTransformer constructor(width: Int, height: Int) {
private var cartesianToAndroid : Matrix = Matrix()
init {
//Tranformation for changing the orientation
cartesianToAndroid.setScale( 1f, -1f);
//Trnasformation for translation
cartesianToAndroid.postTranslate(0f, height.toFloat())
}
fun tranFormPointToCartesian(x: Float, y: Float) : PointF {
var dst = floatArrayOf(x, y)
cartesianToAndroid.mapPoints(dst)
return PointF(dst[0], dst[1])
}
}
In your TransformationView class add the code to initialize the AffineTransformer with width and height.
package com.example.transformation
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
class TransformationView
@JvmOverloads
constructor(context: Context, attr: AttributeSet? = null, defStyleAttr: Int = 0): View(context, attr, defStyleAttr) {
private var drawPaint: Paint = Paint()
private lateinit var transFormer: AffineTransformer
init {
drawPaint.textSize = 25f
}
@Override
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int)
{
super.onSizeChanged(w, h, oldw, oldh)
transFormer = AffineTransformer(w, h)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawText("@(200, 200)", 200f, 200f, drawPaint)
canvas?.drawText("@(400, 200)", 400f, 200f, drawPaint)
canvas?.drawText("@(200, 400)", 200f, 400f, drawPaint)
canvas?.drawText("@(400, 400)", 400f, 400f, drawPaint)
}
}
Introduce a new function drawText with will use the point transformation before drawing.
package com.example.transformation
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PointF
import android.util.AttributeSet
import android.view.View
class TransformationView
@JvmOverloads
constructor(context: Context, attr: AttributeSet? = null, defStyleAttr: Int = 0): View(context, attr, defStyleAttr) {
private var drawPaint: Paint = Paint()
private lateinit var transFormer: AffineTransformer
init {
drawPaint.textSize = 25f
}
@Override
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
transFormer = AffineTransformer(w, h)
}
fun drawText(canvas: Canvas?, point: PointF, text: String) {
var transFormedpoint = transFormer
.tranFormPointToCartesian(point.x, point.y)
canvas?.drawText(text, transFormedpoint.x,
transFormedpoint.y, drawPaint)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawText("@(200, 200)", 200f, 200f, drawPaint)
canvas?.drawText("@(400, 200)", 400f, 200f, drawPaint)
canvas?.drawText("@(200, 400)", 200f, 400f, drawPaint)
canvas?.drawText("@(400, 400)", 400f, 400f, drawPaint)
}
}
Replace the old canvas.drawText function with this new function as below
package com.example.transformation
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PointF
import android.util.AttributeSet
import android.view.View
class TransformationView
@JvmOverloads
constructor(context: Context, attr: AttributeSet? = null, defStyleAttr: Int = 0): View(context, attr, defStyleAttr) {
private var drawPaint: Paint = Paint()
private lateinit var transFormer: AffineTransformer
init {
drawPaint.textSize = 25f
}
@Override
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
transFormer = AffineTransformer(w, h)
}
fun drawText(canvas: Canvas?, point: PointF, text: String) {
var transFormedpoint = transFormer.tranFormPointToCartesian(point.x, point.y)
canvas?.drawText(text, transFormedpoint.x, transFormedpoint.y, drawPaint)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// canvas?.drawText("(200, 200)", 200f, 200f, drawPaint)
drawText(canvas, PointF(200f, 200f), "(200, 200)")
//canvas?.drawText("(400, 200)", 400f, 200f, drawPaint)
drawText(canvas, PointF(400f, 200f), "(400, 200)")
//canvas?.drawText("(200, 400)", 200f, 400f, drawPaint)
drawText(canvas, PointF(200f, 400f), "(200, 400)")
//canvas?.drawText("(400, 400)", 400f, 400f, drawPaint)
drawText(canvas, PointF(400f, 400f), "(400, 400)")
}
}
Run the code. And you will see that now the direction of the Y-axis is as in a cartesian coordinate system.
Later we will discuss in more detail the Android Transformations