paint-brush
Smoothen jagged edges of rotated image viewby@elye.project
1,772 reads
1,772 reads

Smoothen jagged edges of rotated image view

by ElyeSeptember 7th, 2016
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

Have you ever rotate an ImageView before? It’s quite simple to rotate an ImageView, as that could simply be done by even in the layout xml code with the <em>android:rotation </em>attribute.

Company Mentioned

Mention Thumbnail
featured image - Smoothen jagged edges of rotated image view
Elye HackerNoon profile picture

Have you ever rotate an ImageView before? It’s quite simple to rotate an ImageView, as that could simply be done by even in the layout xml code with the android:rotation attribute.

A simple example of code as below

<CustomImageView
    android:id="@+id/image_on"
    android:layout_width="60dp"
    android:layout_height="60dp"
    android:rotation="-6"
    android:scaleType="centerCrop"
    android:src="@drawable/simpleimage"/>

The image generated is rotated to the left as expected. It’s all good until it got inspected by sharp eyes (of a good designer), will bring to light that it is not good, especially on lower resolution devices. (Shhhh … this would make a good “interview test material” for designer :P)

An enlarged image of the rotated ImageView is as below …

Jagged all over the edges

You would notice the rough jagged edges.

Can’t we just apply anti-alias on it?

Applying anti-alias should help the smoother the edges between different color within the image. But unfortunately for the edges of the image will not be anti-alias, as Android doesn’t anti-alias across it’s view.

Trying to be smart, we could add padding, margin, or even use background image to , hopefully Android would be smart to blend them to smooth out the rough jagged edges. Sorry, they don’t help.

There are several stackoverflow that recommend turning off Hardware Acceleration for the particular view, but it doesn’t help in my case as well.

So how did you do it?

What I did was to experiment adding some transparent border on all the sides of my image and save as PNG, then use that image to rotate. Bingo! … the edge got anti-aliased, and as smooth as I like. Check out below, smooth smooth edges!

Jagged cleared in all edges

But wait… we don’t always have the image readily that we could manually change by adding transparent border to it. Even if we do, it’s so much trouble to do so. This is not feasible!

Adding transparent border programmatically

Given that we are sure that transparent border does help, perhaps we could do it programmatically.

So, I try to expand a Canvas by 1 px on all sides but it turn out that this is not as simple as I thought, as stated in this stackoverflow,.

So given that we can’t add 1 px, so now I cheat…. I shrink the image by 1 px for all sides, by painting the bitmap through a shader. That will leave the side of the image as a transparent padding of 1px each.

The code as below (it is added into the void onDraw(Canvas canvas) function of my CustomImageView that inherit from ImageView.

if (getDrawable() instanceof BitmapDrawable) {

    Paint paint = new Paint();
    paint.setAntiAlias(true);
    Bitmap bitmap = ((BitmapDrawable) getDrawable()).getBitmap();
    BitmapShader shader = 
       new BitmapShader(bitmap, 
            Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

    Matrix matrix = new Matrix();
    float scale;

   /* Note: this piece of code handling like Centre-Crop scaling */   
   if (bitmap.getWidth() > bitmap.getHeight()) {
        scale = (float)canvas.getHeight() 
                    / (float) bitmap.getHeight();
        matrix.setScale(scale, scale);
        matrix.postTranslate(
         (canvas.getWidth() - bitmap.getWidth() * scale) * 0.5f, 0);
    
    } else {
        scale = (float)canvas.getWidth() 
                    / (float) bitmap.getWidth();
        matrix.setScale(scale, scale);
        matrix.postTranslate(0, 
         (canvas.getHeight() - bitmap.getHeight() * scale) * 0.5f);
    }

    shader.setLocalMatrix(matrix);
    paint.setShader(shader);

    /* this is where I shrink the image by 1px each side, 
       move it to the center */
    canvas.translate(1, 1);
    canvas.drawRect(
     0.0f, 0.0f, canvas.getWidth()-2, canvas.getHeight()-2, paint);
}

With the above code, you’ll get the exact jagged-clear image that I show above. The actual image although shrink by 1 px on each side, but due to the anti-aliasing effect, it makes it seems expanded 1 px as well, hence canceling the shrink look of 1 px.

Some Caveat on the Code

  • It only handle the Center-Crop scaling.
  • It didn’t take into consideration the padding added to the CustomImageView.
  • It could be optimized by moving some object creation outside the onDraw function. For simplicity, I put them all in a function here.

My hope is Android would automatically by default handle anti-aliasing for all the View’s edges when it is rotated. To me the above is still a ‘hacky’ way of achieving it. If you have a better ‘hack’ to it, do share.

Elye Project (@elye_project) | Twitter