Android – Canvas’ drawLine and drawRect not including end position

androidandroid-canvasdrawingpaint

To my surprise I've just discovered that drawLine and drawRect don't include the ending position, i.e.:

canvas.drawLine(100, 100, 100, 100, paint);

or

RectF rect = new RectF(100, 100, 100, 100);
canvas.drawRect(rect, paint);

won't draw anything.

My paint is defined as follows:

Paint paint = new Paint();
paint.setAntiAlias(false);
paint.setStyle(Paint.Style.FILL);
return paint;

I've tried defining my paint as FILL_AND_STROKE, but it wouldn't help.

Android's drawPaint() javadoc don't even list the stopX and stopY params!

So, if I want to draw a vertical line that goes exactly from beginY to endY (inclusive), I have to do the following:

canvas.drawLine(constX, beginY, constX, endY + 1)

Notice that I didn't add 1 to the ending X position, only to ending Y (xstays the same as I want a vertical line).

My device is HTC SENSE.

Edit: Simon, you're right, rather than asking a question I've just tried to share my feeling of surprise that Android does not do what its docs say in such a fundamental case as basic drawing, and make sure that I didn't do any stupid mistake on my way.

To make myself clearer: drawRect's javadoc says:

public void drawRect (float left, float top, float right, float bottom, Paint paint)

Draw the specified Rect using the specified paint. The rectangle will be filled or framed based on the Style in the paint.

left – The left side of the rectangle to be drawn

top – The top side of the rectangle to be drawn

right – The right side of the rectangle to be drawn

bottom – The bottom side of the rectangle to be drawn

paint – The paint used to draw the rect

So, when writing

canvas.drawRect(x1, y1, x2, y2)

You expect a rectangle whose corners are at (x1, y1); (x1, y2); (x2, y1) and (x2, y2).

Android says: wrong! They will be at (x1, y1); (x1, y2-1); (x2-1, y1) and (x2-1, y2-1).

For the curious ones: set the canvas clipping:

canvas.clipRect(x1, y1, x2, y2)

Then try to draw a point:

canvas.drawPoint(x1, y1, paint);

and you get a point on the screen.

Then try in the opposite corner:

canvas.drawPoint(x2, y2, paint);

nothing appears. nothing will appear in the remaining 2 corners as well:

canvas.drawPoint(x1, y2, paint);


canvas.drawPoint(x2, y2, paint);

Still not surprising to you folks?

So the conclusion is that Android treats right and bottom coordinates as exclusive, meaning that e.g. when writing:

canvas.clipRect(x1, y1, x2, y2)

You will get the clipping bounds of (x1, y1, x2 – 1, y2 – 1). The same goes with every method that takes right and bottom coordinates or Rect/RectF objects.

Best Answer

Your question reveals an inconsistency in the drawing API of Android. You say

So, if I want to draw a vertical line that goes exactly from beginY to endY (inclusive), I have to do the following:

canvas.drawLine(constX, beginY, constX, endY + 1)

Notice that I didn't add 1 to the ending X position, only to ending Y (xstays the same as I want a vertical line).

The sentence in brackets is in my opinion the key to understand the nature of the inconsistency:

You also could add 1 to the ending X position (or even to the beginning X position!), and you would get exactly the pixel-wise identical line. Why is that? Because the underlying algorithm to transform from Android's "left pixel in/right pixel out"-concept to the "start and end pixel in"-concept is as follows (shown only for x, for y it's the same):

int left, top, right, bottom; // left/top pixel inclusive, right/bottom pixel exclusive
int x1, y1, x2, y2;           // x1/y1 and x2/y2 pixels inclusive

if ( left == right ) {
    x1 = x2 = left;
} else if ( left < right ) {
    x1 = left;
    x2 = right - 1;
} else {
    x1 = right;
    x2 = left - 1;
}

The result of this (in my opinion sub-optimal) conversion is that the line

canvas.drawLine(150, 150, 149, 160, paint);

is exactly parallel to

canvas.drawLine(150, 150, 151, 160, paint);

I think everybody would expect kind of inverse V, since the end points are separated by at least 1 pixel (their distance being two pixels) and the starting points are identical.

But tested on various devices and Android versions, the 1st perfectly vertical line is in pixel column 149 and the 2nd in column 150.

BTW: The correct transformation to use the "start and end pixel in"-concept is:

int x1, y1, x2, y2;           // x1/y1 and x2/y2 pixels inclusive

if ( x1 <= x2 )
    ++x2;
else
    ++x1;
if ( y1 <= y2 )
    ++y2;
else
    ++y1;
canvas.drawLine(x1, y1, x2, y2, paint);
Related Topic