Transformation an introduction

Banchanattu
6 min readSep 10, 2021

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

--

--