Search code examples
androidandroid-canvasscale

Drawing text on a scaled Canvas


I'm having a problem drawing text on a canvas that has been scaled. I want to scale the canvas so that drawing dimensions and co-ordinates are always in the range 0.0 to 1.0 i.e. independent of Canvas width and height. (If this is poor practice please feel free to comment giving a reason why this is so.) While I can draw lines and arcs on a scaled canvas correctly, I'm having great difficulty painting text and this problem only seems to occur on Android 4.2

As an example, I have created some code based upon this page here. Not however I have stripped out a lot of the code for clarity.

First, this code works correctly and is as on the above link (albeit stripped down):

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.View;

public class DrawDemo extends Activity {
DemoView demoview;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    demoview = new DemoView(this);
    setContentView(demoview);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

private class DemoView extends View{
    private int width = 0;
    private int height = 0;

    public DemoView(Context context){
        super(context);
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh){
           width = w;
           height = h;
    }
    @Override protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // custom drawing code here
        // remember: y increases from top to bottom
        // x increases from left to right
        int x = 0;
        int y = 0;
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);

        // make the entire canvas white
        paint.setColor(Color.WHITE);
        canvas.drawPaint(paint);
                // draw some text using STROKE style
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(1);
        paint.setColor(Color.MAGENTA);
        paint.setTextSize(30);
        canvas.drawText("Style.STROKE", 75, 75, paint);
    }
  }
}

Next I replaced the absolute pixel sizes with normalised ( 0 to 1.0) values and scaled the canvas:

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.View;

public class DrawDemo extends Activity {
DemoView demoview;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    demoview = new DemoView(this);
    setContentView(demoview);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

private class DemoView extends View{
    private int width = 0;
    private int height = 0;

    public DemoView(Context context){
        super(context);
    }

    @Override
    protected void onSizeChanged (int w, int h, int oldw, int oldh){
           width = w;
           height = h;
    }
    @Override protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // custom drawing code here
        // remember: y increases from top to bottom
        // x increases from left to right
        int x = 0;
        int y = 0;
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);

        // make the entire canvas white
        paint.setColor(Color.WHITE);
        canvas.drawPaint(paint);
                // draw some text using STROKE style
        canvas.scale(width, height);
        paint.setStyle(Paint.Style.STROKE);
        //paint.setStrokeWidth(1);
        paint.setColor(Color.MAGENTA);
        paint.setTextSize(0.2f);
        canvas.drawText("Style.STROKE", 0.5f, 0.5f, paint);
    }
  }
}

The result is nothing appears on the screen. Could someone suggest what I'm doing wrong? Bear in mind that drawing text in the latter has worked for me on Android < 4.2. Apologies for the formatting of the code sample,s I still cannot get to grips with Stackoverflows code formatting.


Solution

  • When hardware acceleration is on, text is rendered into a texture at the font size you specify on the paint. This means that in your case, text is rasterized at 0.2f pixels. That texture is then scaled up at drawing time, which is why you're not seeing anything.

    While this issue has been addressed in a future version of Android, I would strongly recommend you didn't use 0..1 values. You might not only run into precision issues but you will also force the rendering pipelines (software and hardware) to bypass very useful optimizations. For instance, text rendered with a scale transform in software is rendered using paths instead of bitmaps blits.