背景
在Canvas上绘制一个点,比如(1.0f,1.0f),原本以为绘制的范围应该是[1.0f,1.0f]-[2.0f,2.0f]这么一个矩形区域,但是实际结果却是[0.5f,0.5f]-[1.5f,1.5f]这么一个矩形区域。于是联想到如果通过绘制点的方式来绘制一张图片会是什么效果呢?
绘制一个点
下面代码是绘制一个点的简单例子:
/**
* 绘制一个点
*/
public class PointView extends View {
//View的宽度
private int mWidth;
//View的高度
private int mHeight;
//画线的画笔
private Paint mLinePaint = new Paint();
//画点的画笔
private Paint mPointPaint = new Paint();
//Canvas缩放的比例
private float mScale = 100.0f;
public PointView(Context context) {
this(context, null);
}
public PointView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PointView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLinePaint.setColor(Color.BLUE);
mLinePaint.setStyle(Paint.Style.STROKE);
mPointPaint.setColor(Color.RED);
mPointPaint.setStyle(Paint.Style.FILL);
//设置画笔大小
mPointPaint.setStrokeWidth(1.0f);
mPointPaint.setAntiAlias(true);
mPointPaint.setDither(true);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mWidth = right - left;
mHeight = bottom - top;
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.save();
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale, 0.0f, 0.0f);
//让画布扩大倍
canvas.setMatrix(matrix);
//绘制网格
for (int i = 0; i <= mWidth; i++) {
float[] points = new float[]{i, 0, i, mHeight};
canvas.drawLines(points, mLinePaint);
}
for (int i = 0; i <= mHeight; i++) {
float[] points = new float[]{0, i, mWidth, i};
canvas.drawLines(points, mLinePaint);
}
//绘制点
canvas.drawPoint(1.0f, 1.0f, mPointPaint);
canvas.restore();
}
}
下面图片中红色的方块区域是绘制点(1.0f,1.0f)的结果,把画布放大了一定倍数,便于观察绘。
为什么绘制的区域是[0.5f,0.5f]-[1.5f,1.5f]这个矩形区域,而不是主观意识中[1.0f,1.0f]-[2.0f,2.0f]。Android关于绘制点的API其中一个有下面介绍:
/**
* Draw a series of points. Each point is centered at the coordinate specified by pts[], and its
* diameter is specified by the paint's stroke width (as transformed by the canvas' CTM), with
* special treatment for a stroke width of 0, which always draws exactly 1 pixel (or at most 4
* if antialiasing is enabled). The shape of the point is controlled by the paint's Cap type.
* The shape is a square, unless the cap type is Round, in which case the shape is a circle.
*
* @param pts Array of points to draw [x0 y0 x1 y1 x2 y2 ...]
* @param offset Number of values to skip before starting to draw.
* @param count The number of values to process, after skipping offset of them. Since one point
* uses two values, the number of "points" that are drawn is really (count >> 1).
* @param paint The paint used to draw the points
*/
public void drawPoints(@Size(multiple = 2) float[] pts, int offset, int count,
@NonNull Paint paint) {
super.drawPoints(pts, offset, count, paint);
}
上面这段注释说明绘制一个点是以这个点为中心,画笔的大小为直径绘制的一个方形。所以最后在画布上显示的矩形区域就是[0.5f,0.5f]-[1.5f,1.5f]
绘制一张图片
如果通过很多个点来绘制一张图片,会是什么效果呢?具体代码如下:
/**
* 利用像素绘制一个点,绘制出整张图片
*/
public class PixelsView extends View {
//View的宽度
private int mWidth;
//View的高度
private int mHeight;
//画线的画笔
private Paint mLinePaint = new Paint();
//画点的画笔
private Paint mPointPaint = new Paint();
//存储图片的像素
private int[] mPixels;
//图片对象
private Bitmap mBitmap;
//图片宽度
private int mBmpWidth;
//图片高度
private int mBmpHeight;
//Canvas缩放的比例
private float mScale = 15.0f;
public PixelsView(Context context) {
this(context, null);
}
public PixelsView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PixelsView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mLinePaint.setColor(Color.BLUE);
mLinePaint.setStyle(Paint.Style.STROKE);
mPointPaint.setColor(Color.BLACK);
mPointPaint.setStyle(Paint.Style.FILL);
mPointPaint.setStrokeWidth(1.0f);
mPointPaint.setAntiAlias(true);
mPointPaint.setDither(true);
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test);
int bmpWidth = mBmpWidth = bitmap.getWidth();
int bmpHeight = mBmpHeight = bitmap.getHeight();
int[] pixels = new int[bmpWidth * bmpHeight];
bitmap.getPixels(pixels, 0, bmpWidth, 0, 0, bmpWidth, bmpHeight);
mPixels = pixels;
mBitmap = bitmap;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
mWidth = right - left;
mHeight = bottom - top;
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.save();
Matrix matrix = new Matrix();
matrix.setScale(mScale, mScale, 0.0f, 0.0f);
canvas.setMatrix(matrix);
for (int i = 0; i <= mWidth; i++) {
float[] points = new float[]{i, 0, i, mHeight};
canvas.drawLines(points, mLinePaint);
}
for (int i = 0; i <= mHeight; i++) {
float[] points = new float[]{0, i, mWidth, i};
canvas.drawLines(points, mLinePaint);
}
for (int i = 0; i < mBmpHeight; i++) {
for (int j = 0; j < mBmpWidth; j++) {
int argbs = mPixels[i * mBmpWidth + j];
mPointPaint.setColor(argbs);
canvas.drawPoint(j, i, mPointPaint);
//点的位置移动0.5单位个大小
//canvas.drawPoint(((float)j+0.5f),(float)i+0.5f,mPointPaint);
}
}
canvas.drawBitmap(mBitmap, 0f, mBmpHeight + 1, null);
canvas.restore();
}
}
这段代码通过把图片的像素绘制到对应的点上来绘制图片对象,同时还绘制了一个Bitmap对象来作为对比。程序运行结果如下:
(通过点绘制像素形成的图片由于把画布放大了就变得模糊了)对比通过点绘制的图像,以及直接通过Bitmap绘制图像的方式,发现通过点绘制的图像在宽与高出都少了半个格子(0.5个单位)。
于是把通过点绘制图像的方式中所有的点都加上0.5个单位,这样两种绘制的方式就是一样的了。程序运行结果如下:
疑问
直接通过Bitmap对象来绘制一张图片时,传入的顶点坐标并没有偏移0.5个单位而绘制出了正确位置的图像,比如前面程序中的(0,mBmpHeight + 1)。难道是系统内部实现的时候自动调整了0.5个单位吗?
希望有了解过的人告诉下,或者是自己一开始就理解错了。