Search code examples
androidandroid-canvasdrawtextandroid-typefacepixel-perfect

canvas.drawText with pixel perfect text on a canvas


THIS IS NOT ABOUT AntiAlias, Please read the question before answering thank you.

ADDING BOUNTY TO--> android convert text width (in pixels) to percent of screen width/height

Quality info --> https://stackoverflow.com/a/1016941/1815624

I am getting inconsistent results when trying to draw text on a canvas. Please help, thank you.

I want the text to consume the same amount of scaled space on all devices

I do not care about ANTI_ALIAS and have added the paint.setFlags(Paint.ANTI_ALIAS_FLAG); but the problem is the same...

On 1 screen the text consumes half the width, on another only a 1/3 and even one that uses the full width.

I want them all to use equal amounts of screen real estate.

For Example

1600X900 7inch tablet Kitkat physical: inconsistent results

1920.1080 5.2 kitkat physical inconsistent results

1600x900 20in Lollipop emulated inconsistent results

1280x720 4.7inch inconsistent results

These have been created using the guide at http://www.gkproggy.com/2013/01/draw-text-on-canvas-with-font-size.html

where as that tutorial is showing consistent results consistent results

To be thorough here is the source:

public class MainActivity extends Activity
{
    Paint paint;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        paint = new Paint();
        View test = new TestView(this);

        setContentView(test);
    }

    public class TestView extends View
    {

        public TestView(Context context)
        {
            super(context);
            setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        }

        public float pixelsToSp(Context context, float px) {
            float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
            return px/scaledDensity;
        }

        public float spToPixels(float sp) {
            Context context = getContext();
            float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
            return scaledDensity*sp;
        }

        @Override
        protected void onDraw(Canvas canvas)
        {
            int size = getResources().getDimensionPixelSize(R.dimen.sp20);
            int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics());
            Log.v("intSize", Integer.toString(size));
            Log.v("intSize", Integer.toString(px));
            Log.v("intSize", Float.toString(spToPixels(20f)));
            if(true) {
                Typeface myTypeface = Typeface.createFromAsset(getContext().getAssets(), "fonts/Ultra.ttf");
                paint.setTypeface(myTypeface);
            }
            paint.setColor(Color.BLACK);
            paint.setTextSize(size);
            paint.setFlags(Paint.ANTI_ALIAS_FLAG);
            canvas.drawText("HELLOOOOOOOOOOOOOO!", 0, size, paint);
            super.onDraw(canvas);
        }
    }
}

I have tried suggestion from https://stackoverflow.com/a/5369766/1815624

Looking into this

https://stackoverflow.com/a/21895626/1815624

https://stackoverflow.com/a/14753968/1815624

https://stackoverflow.com/a/21895626/1815624 <-- trying now


Solution

  • Resources used:

    https://stackoverflow.com/a/4847027/1815624

    https://stackoverflow.com/a/21895626/1815624

    Got to get screen width, some reference of text size to screen ratio, and calculate. so the results are:

    better results

    and

    better results

    notice the second HELLOOOOOOOOOOOOOOOOO!

    public class MainActivity extends Activity
    {
        Paint paint;
    
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            paint = new Paint();
            View test = new TestView(this);
    
            setContentView(test);
        }
    
        public class TestView extends View
        {
    
            public TestView(Context context)
            {
                super(context);
                setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
            }
    
            public float pixelsToSp(float px) {
                Context context = getContext();
                float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
                return px/scaledDensity;
            }
    
            public float spToPixels(float sp) {
                Context context = getContext();
                float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
                return scaledDensity*sp;
            }
    
            @Override
            protected void onDraw(Canvas canvas)
            {
                int size = getResources().getDimensionPixelSize(R.dimen.sp20);
                int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20, getResources().getDisplayMetrics());
                Log.v("intSize", Integer.toString(size));
                Log.v("intSize", Integer.toString(px));
                Log.v("intSize", Float.toString(spToPixels(20f)));
    
                DisplayMetrics metrics = new DisplayMetrics();
                getWindowManager().getDefaultDisplay().getMetrics(metrics);
    
                Log.v("intSize hm", Integer.toString(metrics.heightPixels));
                Log.v("intSize wm", Integer.toString(metrics.widthPixels));
    
                if(true) {
                    Typeface myTypeface = Typeface.createFromAsset(getContext().getAssets(), "fonts/Ultra.ttf");
                    paint.setTypeface(myTypeface);
                }
                paint.setColor(Color.BLACK);
                paint.setTextSize(size);
                paint.setFlags(Paint.ANTI_ALIAS_FLAG);
                canvas.drawText("HELLOOOOOOOOOOOOOO!", 0, size, paint);
                setTextSizeForWidth(paint, metrics.widthPixels * 0.5f, "HELLOOOOOOOOOOOOOO!");
                canvas.drawText("HELLOOOOOOOOOOOOOO!", 0, size*3, paint);
                super.onDraw(canvas);
            }
            /**
             * Sets the text size for a Paint object so a given string of text will be a
             * given width.
             *
             * @param paint
             *            the Paint to set the text size for
             * @param desiredWidth
             *            the desired width
             * @param text
             *            the text that should be that width
             */
            private void setTextSizeForWidth(Paint paint, float desiredWidth,
                                                    String text) {
    
                // Pick a reasonably large value for the test. Larger values produce
                // more accurate results, but may cause problems with hardware
                // acceleration. But there are workarounds for that, too; refer to
                // https://stackoverflow.com/questions/6253528/font-size-too-large-to-fit-in-cache
                final float testTextSize = 48f;
    
                // Get the bounds of the text, using our testTextSize.
                paint.setTextSize(testTextSize);
                Rect bounds = new Rect();
                paint.getTextBounds(text, 0, text.length(), bounds);
    
                // Calculate the desired size as a proportion of our testTextSize.
                float desiredTextSize = testTextSize * desiredWidth / bounds.width();
    
                // Set the paint for that size.
                paint.setTextSize(desiredTextSize);
            }
    
        }
    }
    

    please note if you not extending Activity use

        DisplayMetrics metrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) context
                .getSystemService(Context.WINDOW_SERVICE);
        windowManager.getDefaultDisplay().getMetrics(metrics);
    

    instead of

        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);