更新:本文的内容只是一部分,这段时间添加了橡皮擦这个新功能,于是问题接二连三的来,比如说:如果用本文的内容去做橡皮擦的话,难!(至少我没解决,不是没背景图,就是有背景图但是更新要在下一下刷橡皮擦的时候才能更新效果),然后有个setbackgroundresource的函数,这个函数就可以了,但是问题又来了,比如说保存,清屏,但是我都解决了(清屏的话就是重新构造一个图,当clear的时候就把这张图赋值给以前的图片。保存的话我就是把绘下个图放到一张有背景的canvas上面,至是分辨率的问题自己去解决就行了,保证存下来的跟你用setbackgoundresource绘图看到的效果一致,需要源码的请联系我)
本人也是在网上查了很多文章后才做出来的,感觉网上的一些涂鸦功能不是很完善,在此就稍微完善了一下。先看下效果图吧(想做成全屏的话需要弄一张跟你屏幕一样大小的背景图,找不到也没关系,我有改尺寸的代码,一并献上)。代码下载请到 其实涂鸦的难点就是如何能在canvas上进行清屏又能保存,至少目前我碰到的情况是这样,这就需要对canvas与bitmap的较为深入的理解了,这个你多写这方面的代码就行了,网上有许多涂鸦的作品,看看源代码。
涂鸦中关于canvas的学习需要掌握三点吧:1:view 2:onDraw函数 3:onTouchEvent
public class HandWrite extends View
{
Paint paint = null;
private Bitmap originalBitmap = null;
Bitmap new1Bitmap = null;
private Bitmap new2Bitmap = null;
private float clickX = 0,clickY = 0;
private float startX = 0,startY = 0;
private boolean isMove = true;
private boolean isClear = false;
int color = Color.WHITE;
float strokeWidth = 3.0f;
public HandWrite(Context context, AttributeSet attrs)
{
super(context, attrs);
// originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.t);
originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a).copy(Bitmap.Config.ARGB_8888, true);
new1Bitmap = Bitmap.createBitmap(originalBitmap);
}
public void clear(){
isClear = true;
new2Bitmap = Bitmap.createBitmap(originalBitmap);
invalidate();
}
public void setstyle(float strokeWidth){
this.strokeWidth = strokeWidth;
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawBitmap(HandWriting(new1Bitmap), 0, 0,null);
}
public Bitmap HandWriting(Bitmap originalBitmap)
{
Canvas canvas = null;
if(isClear){
canvas = new Canvas(new2Bitmap);
}
else{
canvas = new Canvas(originalBitmap);
}
paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setAntiAlias(true);
paint.setColor(color);
paint.setStrokeWidth(strokeWidth);
if(isMove){
canvas.drawLine(startX, startY, clickX, clickY, paint);
}
startX = clickX;
startY = clickY;
if(isClear){
return new2Bitmap;
}
return originalBitmap;
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
clickX = event.getX();
clickY = event.getY();
if(event.getAction() == MotionEvent.ACTION_DOWN){
isMove = false;
invalidate();
return true;
}
else if(event.getAction() == MotionEvent.ACTION_MOVE){
isMove = true;
invalidate();
return true;
}
return super.onTouchEvent(event);
}
public class HandWrite extends View
{
Paint paint = null;
private Bitmap originalBitmap = null;
Bitmap new1Bitmap = null;
private Bitmap new2Bitmap = null;
private float clickX = 0,clickY = 0;
private float startX = 0,startY = 0;
private boolean isMove = true;
private boolean isClear = false;
int color = Color.WHITE;
float strokeWidth = 3.0f;
public HandWrite(Context context, AttributeSet attrs)
{
super(context, attrs);
// originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.t);
originalBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.a).copy(Bitmap.Config.ARGB_8888, true);
new1Bitmap = Bitmap.createBitmap(originalBitmap);
}
public void clear(){
isClear = true;
new2Bitmap = Bitmap.createBitmap(originalBitmap);
invalidate();
}
public void setstyle(float strokeWidth){
this.strokeWidth = strokeWidth;
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.drawBitmap(HandWriting(new1Bitmap), 0, 0,null);
}
public Bitmap HandWriting(Bitmap originalBitmap)
{
Canvas canvas = null;
if(isClear){
canvas = new Canvas(new2Bitmap);
}
else{
canvas = new Canvas(originalBitmap);
}
paint = new Paint();
paint.setStyle(Style.STROKE);
paint.setAntiAlias(true);
paint.setColor(color);
paint.setStrokeWidth(strokeWidth);
if(isMove){
canvas.drawLine(startX, startY, clickX, clickY, paint);
}
startX = clickX;
startY = clickY;
if(isClear){
return new2Bitmap;
}
return originalBitmap;
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
clickX = event.getX();
clickY = event.getY();
if(event.getAction() == MotionEvent.ACTION_DOWN){
isMove = false;
invalidate();
return true;
}
else if(event.getAction() == MotionEvent.ACTION_MOVE){
isMove = true;
invalidate();
return true;
}
return super.onTouchEvent(event);
}
这个view的代码网上有,这里面必须得实现两个重要的方法,一个是onDraw一个是onTouchEvent,onDraw是在你每次触碰屏幕的时候都会触发,包括你初始化的时候。onTouchEvent就是在你触碰屏幕后采取的相应操作。其实涂鸦的关键就是通过drawLine将瞬间变化的两点连起来画成直线,然后画在canvas上的bitmap上。
在mainActivity中我实现了一个菜单按钮
public class CanvasDrawActivity extends Activity
{
private static final String TAG = "CanvasDrawActivity";
/** Called when the activity is first created. */
private int width;
private int height;
private HandWrite handWrite = null;
private Button clear = null;
private int whichColor = 0;
private int whichStrokeWidth = 0;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
handWrite = (HandWrite)findViewById(R.id.handwriteview);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
menu.add(0, 1, 1, "清屏");
menu.add(0, 2, 2, "颜色");
menu.add(0, 3, 3, "画笔");
menu.add(0, 4, 4, "保存");
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
if(item.getItemId() == 4){
File f = new File(Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/aaa.jpg");
try {
saveMyBitmap(f, handWrite.new1Bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}else if(item.getItemId() == 1){
handWrite.clear();
}else if(item.getItemId() == 2){
Dialog mDialog = new AlertDialog.Builder(CanvasDrawActivity.this)
.setTitle("颜色设置")
.setSingleChoiceItems(new String[]{"白色","绿色","红色"}, whichColor, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
// TODO Auto-generated method stub
switch(which)
{
case 0:
{
handWrite.color = Color.WHITE;
whichColor = 0;
break;
}
case 1:
{
handWrite.color = Color.GREEN;
whichColor = 1;
break;
}
case 2:
{
handWrite.color = Color.RED;
whichColor = 2;
break;
}
}
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
// TODO Auto-generated method stub
dialog.dismiss();
}
})
.create();
mDialog.show();
}else if(item.getItemId() == 3){
Dialog mDialog = new AlertDialog.Builder(CanvasDrawActivity.this)
.setTitle("画笔设置")
.setSingleChoiceItems(new String[]{"细","中","粗"}, whichStrokeWidth, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
// TODO Auto-generated method stub
switch(which)
{
case 0:
{
handWrite.strokeWidth = 3.0f;
whichStrokeWidth = 0;
break;
}
case 1:
{
handWrite.strokeWidth = 6.0f;
whichStrokeWidth = 1;
break;
}
case 2:
{
handWrite.strokeWidth = 9.0f;
whichStrokeWidth = 2;
break;
}
}
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
// TODO Auto-generated method stub
dialog.dismiss();
}
})
.create();
mDialog.show();
}
return super.onOptionsItemSelected(item);
}
public void saveMyBitmap(File f, Bitmap mBitmap) throws IOException {
try {
f.createNewFile();
FileOutputStream fOut = null;
fOut = new FileOutputStream(f);
mBitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
fOut.flush();
fOut.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class CanvasDrawActivity extends Activity
{
private static final String TAG = "CanvasDrawActivity";
/** Called when the activity is first created. */
private int width;
private int height;
private HandWrite handWrite = null;
private Button clear = null;
private int whichColor = 0;
private int whichStrokeWidth = 0;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.main);
handWrite = (HandWrite)findViewById(R.id.handwriteview);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
menu.add(0, 1, 1, "清屏");
menu.add(0, 2, 2, "颜色");
menu.add(0, 3, 3, "画笔");
menu.add(0, 4, 4, "保存");
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
if(item.getItemId() == 4){
File f = new File(Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/aaa.jpg");
try {
saveMyBitmap(f, handWrite.new1Bitmap);
} catch (IOException e) {
e.printStackTrace();
}
}else if(item.getItemId() == 1){
handWrite.clear();
}else if(item.getItemId() == 2){
Dialog mDialog = new AlertDialog.Builder(CanvasDrawActivity.this)
.setTitle("颜色设置")
.setSingleChoiceItems(new String[]{"白色","绿色","红色"}, whichColor, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
// TODO Auto-generated method stub
switch(which)
{
case 0:
{
handWrite.color = Color.WHITE;
whichColor = 0;
break;
}
case 1:
{
handWrite.color = Color.GREEN;
whichColor = 1;
break;
}
case 2:
{
handWrite.color = Color.RED;
whichColor = 2;
break;
}
}
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
// TODO Auto-generated method stub
dialog.dismiss();
}
})
.create();
mDialog.show();
}else if(item.getItemId() == 3){
Dialog mDialog = new AlertDialog.Builder(CanvasDrawActivity.this)
.setTitle("画笔设置")
.setSingleChoiceItems(new String[]{"细","中","粗"}, whichStrokeWidth, new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
// TODO Auto-generated method stub
switch(which)
{
case 0:
{
handWrite.strokeWidth = 3.0f;
whichStrokeWidth = 0;
break;
}
case 1:
{
handWrite.strokeWidth = 6.0f;
whichStrokeWidth = 1;
break;
}
case 2:
{
handWrite.strokeWidth = 9.0f;
whichStrokeWidth = 2;
break;
}
}
}
})
.setPositiveButton("确定", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
// TODO Auto-generated method stub
dialog.dismiss();
}
})
.create();
mDialog.show();
}
return super.onOptionsItemSelected(item);
}
public void saveMyBitmap(File f, Bitmap mBitmap) throws IOException {
try {
f.createNewFile();
FileOutputStream fOut = null;
fOut = new FileOutputStream(f);
mBitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut);
fOut.flush();
fOut.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这些就是通过菜单选项对相应的canvas上面的paint进行设置即可达到效果。
那么现在就看看涂鸦这个程序上与canvas相关的几个函数的源码吧!(初始函数,drawLine,drawBitmap这3个)canvas.java文件位于android2.3.3/frameworks/base/graphics/java/android/graphics
canvas(Bitmap bitmap)
// assigned in constructors, freed in finalizer
final int mNativeCanvas;
private Bitmap mBitmap; // if not null, mGL must be null
// Package-scoped for quick access.
int mDensity = Bitmap.DENSITY_NONE;
public Canvas(Bitmap bitmap) {
if (!bitmap.isMutable()) {
throw new IllegalStateException(
"Immutable bitmap passed to Canvas constructor");
}
throwIfRecycled(bitmap);
mNativeCanvas = initRaster(bitmap.ni());
mBitmap = bitmap;
mDensity = bitmap.mDensity;
}
// assigned in constructors, freed in finalizer
final int mNativeCanvas;
private Bitmap mBitmap; // if not null, mGL must be null
// Package-scoped for quick access.
int mDensity = Bitmap.DENSITY_NONE;
public Canvas(Bitmap bitmap) {
if (!bitmap.isMutable()) {
throw new IllegalStateException(
"Immutable bitmap passed to Canvas constructor");
}
throwIfRecycled(bitmap);
mNativeCanvas = initRaster(bitmap.ni());
mBitmap = bitmap;
mDensity = bitmap.mDensity;
}
这个初始化比较简单,就主要是一个叫做initRaster(bitmap.ni())这个函数
private static native int initRaster(int nativeBitmapOrZero);
private static native int initRaster(int nativeBitmapOrZero);
这个需要涉及到向底层本地函数传递一个int参数,那么这个bitmap.ni()是什么呢?查看bitmap.java同样位于android2.3.3/frameworks/base/graphics/java/android/graphics
/* package */ final int ni() {
return mNativeBitmap;
}
// Note: mNativeBitmap is used by FaceDetector_jni.cpp
// Don't change/rename without updating FaceDetector_jni.cpp
private final int mNativeBitmap;
/* package */ final int ni() {
return mNativeBitmap;
}
// Note: mNativeBitmap is used by FaceDetector_jni.cpp
// Don't change/rename without updating FaceDetector_jni.cpp
private final int mNativeBitmap;
也就是想下面传递的是mNativeBitmap这个值,而这个值会在FaceDetector_jni.cpp中使用。
那么initRaster怎么实现的呢,看Canvas.cpp位于android/frameworks/base/core/jni/android/graphics,注册函数有
{"initRaster","(I)I", (void*) SkCanvasGlue::initRaster},
{"native_drawBitmap","(IIFFIIII)V",
(void*) SkCanvasGlue::drawBitmap__BitmapFFPaint},
{"native_drawLine","(IFFFFI)V", (void*) SkCanvasGlue::drawLine__FFFFPaint},
{"native_drawColor","(III)V", (void*) SkCanvasGlue::drawColor__II},
{"native_drawPaint","(II)V", (void*) SkCanvasGlue::drawPaint},
{"drawPoint", "(FFLandroid/graphics/Paint;)V",
(void*) SkCanvasGlue::drawPoint},
{"drawPoints", "([FIILandroid/graphics/Paint;)V",
(void*) SkCanvasGlue::drawPoints},
{"drawLines", "([FIILandroid/graphics/Paint;)V",
(void*) SkCanvasGlue::drawLines},
{"native_drawLine","(IFFFFI)V", (void*) SkCanvasGlue::drawLine__FFFFPaint},
{"native_drawRect","(ILandroid/graphics/RectF;I)V",
(void*) SkCanvasGlue::drawRect__RectFPaint},
{"native_drawRect","(IFFFFI)V", (void*) SkCanvasGlue::drawRect__FFFFPaint},
{"native_drawOval","(ILandroid/graphics/RectF;I)V",
(void*) SkCanvasGlue::drawOval},
{"native_drawCircle","(IFFFI)V", (void*) SkCanvasGlue::drawCircle},
{"native_drawArc","(ILandroid/graphics/RectF;FFZI)V",
{"initRaster","(I)I", (void*) SkCanvasGlue::initRaster},
{"native_drawBitmap","(IIFFIIII)V",
(void*) SkCanvasGlue::drawBitmap__BitmapFFPaint},
{"native_drawLine","(IFFFFI)V", (void*) SkCanvasGlue::drawLine__FFFFPaint},
{"native_drawColor","(III)V", (void*) SkCanvasGlue::drawColor__II},
{"native_drawPaint","(II)V", (void*) SkCanvasGlue::drawPaint},
{"drawPoint", "(FFLandroid/graphics/Paint;)V",
(void*) SkCanvasGlue::drawPoint},
{"drawPoints", "([FIILandroid/graphics/Paint;)V",
(void*) SkCanvasGlue::drawPoints},
{"drawLines", "([FIILandroid/graphics/Paint;)V",
(void*) SkCanvasGlue::drawLines},
{"native_drawLine","(IFFFFI)V", (void*) SkCanvasGlue::drawLine__FFFFPaint},
{"native_drawRect","(ILandroid/graphics/RectF;I)V",
(void*) SkCanvasGlue::drawRect__RectFPaint},
{"native_drawRect","(IFFFFI)V", (void*) SkCanvasGlue::drawRect__FFFFPaint},
{"native_drawOval","(ILandroid/graphics/RectF;I)V",
(void*) SkCanvasGlue::drawOval},
{"native_drawCircle","(IFFFI)V", (void*) SkCanvasGlue::drawCircle},
{"native_drawArc","(ILandroid/graphics/RectF;FFZI)V",
其实这些就是对canva的常见操作,包括下面遇到的drawline,drawBitmap,请看: drawBitmap(Bitmap bitmap,float, left,float top, Paint ,paint)
public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
throwIfRecycled(bitmap);
native_drawBitmap(mNativeCanvas, bitmap.ni(), left, top,
paint != null ? paint.mNativePaint : 0, mDensity, mScreenDensity,
bitmap.mDensity);
}
public void drawBitmap(Bitmap bitmap, float left, float top, Paint paint) {
throwIfRecycled(bitmap);
native_drawBitmap(mNativeCanvas, bitmap.ni(), left, top,
paint != null ? paint.mNativePaint : 0, mDensity, mScreenDensity,
bitmap.mDensity);
}
drawLine(float startX, float startY, float stopX, float stopY, Paint paint)
public void drawLine(float startX, float startY, float stopX, float stopY,
Paint paint) {
native_drawLine(mNativeCanvas, startX, startY, stopX, stopY,
paint.mNativePaint);
}
public void drawLine(float startX, float startY, float stopX, float stopY,
Paint paint) {
native_drawLine(mNativeCanvas, startX, startY, stopX, stopY,
paint.mNativePaint);
}
他们实际上都是使用的SkCanvasGlue中的对应函数,而这个时候,canvas已经不再撒过去那个canvas了,它换成了SKCanvas。
比如说drawBitmap,这个是在注册函数中表示的对应的函数
static void drawBitmap__BitmapFFPaint(JNIEnv* env, jobject jcanvas,
SkCanvas* canvas, SkBitmap* bitmap,
jfloat left, jfloat top,
SkPaint* paint, jint canvasDensity,
jint screenDensity, jint bitmapDensity) {
SkScalar left_ = SkFloatToScalar(left);
SkScalar top_ = SkFloatToScalar(top);
if (canvasDensity == bitmapDensity || canvasDensity == 0
|| bitmapDensity == 0) {
if (screenDensity != 0 && screenDensity != bitmapDensity) {
SkPaint filteredPaint;
if (paint) {
filteredPaint = *paint;
}
filteredPaint.setFilterBitmap(true);
canvas->drawBitmap(*bitmap, left_, top_, &filteredPaint);
} else {
canvas->drawBitmap(*bitmap, left_, top_, paint);
}
} else {
canvas->save();
SkScalar scale = SkFloatToScalar(canvasDensity / (float)bitmapDensity);
canvas->translate(left_, top_);
canvas->scale(scale, scale);
SkPaint filteredPaint;
if (paint) {
filteredPaint = *paint;
}
filteredPaint.setFilterBitmap(true);
canvas->drawBitmap(*bitmap, 0, 0, &filteredPaint);
canvas->restore();
}
}
static void drawBitmap__BitmapFFPaint(JNIEnv* env, jobject jcanvas,
SkCanvas* canvas, SkBitmap* bitmap,
jfloat left, jfloat top,
SkPaint* paint, jint canvasDensity,
jint screenDensity, jint bitmapDensity) {
SkScalar left_ = SkFloatToScalar(left);
SkScalar top_ = SkFloatToScalar(top);
if (canvasDensity == bitmapDensity || canvasDensity == 0
|| bitmapDensity == 0) {
if (screenDensity != 0 && screenDensity != bitmapDensity) {
SkPaint filteredPaint;
if (paint) {
filteredPaint = *paint;
}
filteredPaint.setFilterBitmap(true);
canvas->drawBitmap(*bitmap, left_, top_, &filteredPaint);
} else {
canvas->drawBitmap(*bitmap, left_, top_, paint);
}
} else {
canvas->save();
SkScalar scale = SkFloatToScalar(canvasDensity / (float)bitmapDensity);
canvas->translate(left_, top_);
canvas->scale(scale, scale);
SkPaint filteredPaint;
if (paint) {
filteredPaint = *paint;
}
filteredPaint.setFilterBitmap(true);
canvas->drawBitmap(*bitmap, 0, 0, &filteredPaint);
canvas->restore();
}
}
它最终调用的就是SKCanvas中的
canvas->drawBitmap(*bitmap, left_, top_, &filteredPaint);
canvas->drawBitmap(*bitmap, left_, top_, &filteredPaint);
类似的,其他的一些canvas的操作都是调用的SKCanvas的对应的方法。(skcanvas与skia的关系请大家网上查询)
那么SKCnvas的源码究竟存放在哪里呢?android/external/skia/src/core,因为所有的方法的实现都是类似的,这里我就单独选择一个简单的drawLine吧
void SkCanvas::drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1,
const SkPaint& paint) {
SkPoint pts[2];
pts[0].set(x0, y0);
pts[1].set(x1, y1);
this->drawPoints(kLines_PointMode, 2, pts, paint);
}
void SkCanvas::drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1,
const SkPaint& paint) {
SkPoint pts[2];
pts[0].set(x0, y0);
pts[1].set(x1, y1);
this->drawPoints(kLines_PointMode, 2, pts, paint);
}
就是将亮点的坐标值存放在一个SKPoint数组中然后作为参数传递给drawPoints函数,继续找drawPoints
void SkCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[],
const SkPaint& paint) {
if ((long)count <= 0) {
return;
}
SkASSERT(pts != NULL);
ITER_BEGIN(paint, SkDrawFilter::kPoint_Type)
while (iter.next()) {
iter.fDevice->drawPoints(iter, mode, count, pts, paint);
}
ITER_END
}
void SkCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[],
const SkPaint& paint) {
if ((long)count <= 0) {
return;
}
SkASSERT(pts != NULL);
ITER_BEGIN(paint, SkDrawFilter::kPoint_Type)
while (iter.next()) {
iter.fDevice->drawPoints(iter, mode, count, pts, paint);
}
ITER_END
}
首先确保这个数组的非空的存在性,然后用一个迭代器去不断的drawPoint,因为fDevice十一个SKDevice*类型。然后查看DKDevice.cpp文件,同样位于android/external/skia/src/core
void SkDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count,
const SkPoint pts[], const SkPaint& paint) {
draw.drawPoints(mode, count, pts, paint);
}
void SkDevice::drawPoints(const SkDraw& draw, SkCanvas::PointMode mode, size_t count,
const SkPoint pts[], const SkPaint& paint) {
draw.drawPoints(mode, count, pts, paint);
}
调用的是SkDraw中的drawPoints方法
void SkDraw::drawPoints(SkCanvas::PointMode mode, size_t count,
const SkPoint pts[], const SkPaint& paint) const {
// if we're in lines mode, force count to be even
if (SkCanvas::kLines_PointMode == mode) {
count &= ~(size_t)1;
}
if ((long)count <= 0) {
return;
}
SkAutoRestoreBounder arb;
if (fBounder) {
if (!bounder_points(fBounder, mode, count, pts, paint, *fMatrix)) {
return;
}
// clear the bounder for the rest of this function, so we don't call it
// again later if we happen to call ourselves for drawRect, drawPath,
// etc.
arb.clearBounder(this);
}
SkASSERT(pts != NULL);
SkDEBUGCODE(this->validate();)
// nothing to draw
if (fClip->isEmpty() ||
(paint.getAlpha() == 0 && paint.getXfermode() == NULL)) {
return;
}
PtProcRec rec;
if (rec.init(mode, paint, fMatrix, fClip)) {
SkAutoBlitterChoose blitter(*fBitmap, *fMatrix, paint);
SkPoint devPts[MAX_DEV_PTS];
const SkMatrix* matrix = fMatrix;
SkBlitter* bltr = blitter.get();
PtProcRec::Proc proc = rec.chooseProc(bltr);
// we have to back up subsequent passes if we're in polygon mode
const size_t backup = (SkCanvas::kPolygon_PointMode == mode);
do {
size_t n = count;
if (n > MAX_DEV_PTS) {
n = MAX_DEV_PTS;
}
matrix->mapPoints(devPts, pts, n);
proc(rec, devPts, n, bltr);
pts += n - backup;
SkASSERT(count >= n);
count -= n;
if (count > 0) {
count += backup;
}
} while (count != 0);
} else {
switch (mode) {
case SkCanvas::kPoints_PointMode: {
// temporarily mark the paint as filling.
SkAutoPaintStyleRestore restore(paint, SkPaint::kFill_Style);
SkScalar width = paint.getStrokeWidth();
SkScalar radius = SkScalarHalf(width);
if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
SkPath path;
SkMatrix preMatrix;
path.addCircle(0, 0, radius);
for (size_t i = 0; i < count; i++) {
preMatrix.setTranslate(pts[i].fX, pts[i].fY);
// pass true for the last point, since we can modify
// then path then
this->drawPath(path, paint, &preMatrix, (count-1) == i);
}
} else {
SkRect r;
for (size_t i = 0; i < count; i++) {
r.fLeft = pts[i].fX - radius;
r.fTop = pts[i].fY - radius;
r.fRight = r.fLeft + width;
r.fBottom = r.fTop + width;
this->drawRect(r, paint);
}
}
break;
}
case SkCanvas::kLines_PointMode:
case SkCanvas::kPolygon_PointMode: {
count -= 1;
SkPath path;
SkPaint p(paint);
p.setStyle(SkPaint::kStroke_Style);
size_t inc = (SkCanvas::kLines_PointMode == mode) ? 2 : 1;
for (size_t i = 0; i < count; i += inc) {
path.moveTo(pts[i]);
path.lineTo(pts[i+1]);
this->drawPath(path, p, NULL, true);
path.rewind();
}
break;
}
}
}
}
void SkDraw::drawPoints(SkCanvas::PointMode mode, size_t count,
const SkPoint pts[], const SkPaint& paint) const {
// if we're in lines mode, force count to be even
if (SkCanvas::kLines_PointMode == mode) {
count &= ~(size_t)1;
}
if ((long)count <= 0) {
return;
}
SkAutoRestoreBounder arb;
if (fBounder) {
if (!bounder_points(fBounder, mode, count, pts, paint, *fMatrix)) {
return;
}
// clear the bounder for the rest of this function, so we don't call it
// again later if we happen to call ourselves for drawRect, drawPath,
// etc.
arb.clearBounder(this);
}
SkASSERT(pts != NULL);
SkDEBUGCODE(this->validate();)
// nothing to draw
if (fClip->isEmpty() ||
(paint.getAlpha() == 0 && paint.getXfermode() == NULL)) {
return;
}
PtProcRec rec;
if (rec.init(mode, paint, fMatrix, fClip)) {
SkAutoBlitterChoose blitter(*fBitmap, *fMatrix, paint);
SkPoint devPts[MAX_DEV_PTS];
const SkMatrix* matrix = fMatrix;
SkBlitter* bltr = blitter.get();
PtProcRec::Proc proc = rec.chooseProc(bltr);
// we have to back up subsequent passes if we're in polygon mode
const size_t backup = (SkCanvas::kPolygon_PointMode == mode);
do {
size_t n = count;
if (n > MAX_DEV_PTS) {
n = MAX_DEV_PTS;
}
matrix->mapPoints(devPts, pts, n);
proc(rec, devPts, n, bltr);
pts += n - backup;
SkASSERT(count >= n);
count -= n;
if (count > 0) {
count += backup;
}
} while (count != 0);
} else {
switch (mode) {
case SkCanvas::kPoints_PointMode: {
// temporarily mark the paint as filling.
SkAutoPaintStyleRestore restore(paint, SkPaint::kFill_Style);
SkScalar width = paint.getStrokeWidth();
SkScalar radius = SkScalarHalf(width);
if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
SkPath path;
SkMatrix preMatrix;
path.addCircle(0, 0, radius);
for (size_t i = 0; i < count; i++) {
preMatrix.setTranslate(pts[i].fX, pts[i].fY);
// pass true for the last point, since we can modify
// then path then
this->drawPath(path, paint, &preMatrix, (count-1) == i);
}
} else {
SkRect r;
for (size_t i = 0; i < count; i++) {
r.fLeft = pts[i].fX - radius;
r.fTop = pts[i].fY - radius;
r.fRight = r.fLeft + width;
r.fBottom = r.fTop + width;
this->drawRect(r, paint);
}
}
break;
}
case SkCanvas::kLines_PointMode:
case SkCanvas::kPolygon_PointMode: {
count -= 1;
SkPath path;
SkPaint p(paint);
p.setStyle(SkPaint::kStroke_Style);
size_t inc = (SkCanvas::kLines_PointMode == mode) ? 2 : 1;
for (size_t i = 0; i < count; i += inc) {
path.moveTo(pts[i]);
path.lineTo(pts[i+1]);
this->drawPath(path, p, NULL, true);
path.rewind();
}
break;
}
}
}
}
我起初看的时候瞬间想砸电脑,这得耽误我晚上的dota时间啊,但是细看,你会注意到那个case语句,因为我们分析的是drawLine函数,而drawLine函数传入的是kLines_PointMode,那么我们就分析这个语句,其实就这一个for循环
for (size_t i = 0; i < count; i += inc) {
path.moveTo(pts[i]);
path.lineTo(pts[i+1]);
this->drawPath(path, p, NULL, true);
path.rewind();
}
for (size_t i = 0; i < count; i += inc) {
path.moveTo(pts[i]);
path.lineTo(pts[i+1]);
this->drawPath(path, p, NULL, true);
path.rewind();
}
它再一次不甘寂寞地调用了SKpath的两个函数以及自身的drawPath函数
void SkPath::moveTo(SkScalar x, SkScalar y) {
SkDEBUGCODE(this->validate();)
int vc = fVerbs.count();
SkPoint* pt;
if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
pt = &fPts[fPts.count() - 1];
} else {
pt = fPts.append();
*fVerbs.append() = kMove_Verb;
}
pt->set(x, y);
fBoundsIsDirty = true;
}
void SkPath::lineTo(SkScalar x, SkScalar y) {
SkDEBUGCODE(this->validate();)
if (fVerbs.count() == 0) {
fPts.append()->set(0, 0);
*fVerbs.append() = kMove_Verb;
}
fPts.append()->set(x, y);
*fVerbs.append() = kLine_Verb;
fBoundsIsDirty = true;
}
void SkDraw::drawPath(const SkPath& origSrcPath, const SkPaint& paint,
const SkMatrix* prePathMatrix, bool pathIsMutable) const {
SkDEBUGCODE(this->validate();)
// nothing to draw
if (fClip->isEmpty() ||
(paint.getAlpha() == 0 && paint.getXfermode() == NULL)) {
return;
}
SkPath* pathPtr = (SkPath*)&origSrcPath;
bool doFill = true;
SkPath tmpPath;
SkMatrix tmpMatrix;
const SkMatrix* matrix = fMatrix;
if (prePathMatrix) {
if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style ||
paint.getRasterizer()) {
SkPath* result = pathPtr;
if (!pathIsMutable) {
result = &tmpPath;
pathIsMutable = true;
}
pathPtr->transform(*prePathMatrix, result);
pathPtr = result;
} else {
if (!tmpMatrix.setConcat(*matrix, *prePathMatrix)) {
// overflow
return;
}
matrix = &tmpMatrix;
}
}
// at this point we're done with prePathMatrix
SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;)
/*
If the device thickness < 1.0, then make it a hairline, and
modulate alpha if the thickness is even smaller (e.g. thickness == 0.5
should modulate the alpha by 1/2)
*/
SkAutoPaintRestoreColorStrokeWidth aprc(paint);
// can we approximate a thin (but not hairline) stroke with an alpha-modulated
// hairline? Only if the matrix scales evenly in X and Y, and the device-width is
// less than a pixel
if (paint.getStyle() == SkPaint::kStroke_Style && paint.getXfermode() == NULL) {
SkScalar width = paint.getStrokeWidth();
if (width > 0 && map_radius(*matrix, &width)) {
int scale = (int)SkScalarMul(width, 256);
int alpha = paint.getAlpha() * scale >> 8;
// pretend to be a hairline, with a modulated alpha
((SkPaint*)&paint)->setAlpha(alpha);
((SkPaint*)&paint)->setStrokeWidth(0);
}
}
if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
doFill = paint.getFillPath(*pathPtr, &tmpPath);
pathPtr = &tmpPath;
}
if (paint.getRasterizer()) {
SkMask mask;
if (paint.getRasterizer()->rasterize(*pathPtr, *matrix,
&fClip->getBounds(), paint.getMaskFilter(), &mask,
SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
this->drawDevMask(mask, paint);
SkMask::FreeImage(mask.fImage);
}
return;
}
// avoid possibly allocating a new path in transform if we can
SkPath* devPathPtr = pathIsMutable ? pathPtr : &tmpPath;
// transform the path into device space
pathPtr->transform(*matrix, devPathPtr);
SkAutoBlitterChoose blitter(*fBitmap, *fMatrix, paint);
// how does filterPath() know to fill or hairline the path??? <mrr>
if (paint.getMaskFilter() &&
paint.getMaskFilter()->filterPath(*devPathPtr, *fMatrix, *fClip,
fBounder, blitter.get())) {
return; // filterPath() called the blitter, so we're done
}
if (fBounder && !fBounder->doPath(*devPathPtr, paint, doFill)) {
return;
}
if (doFill) {
if (paint.isAntiAlias()) {
SkScan::AntiFillPath(*devPathPtr, *fClip, blitter.get());
} else {
SkScan::FillPath(*devPathPtr, *fClip, blitter.get());
}
} else { // hairline
if (paint.isAntiAlias()) {
SkScan::AntiHairPath(*devPathPtr, fClip, blitter.get());
} else {
SkScan::HairPath(*devPathPtr, fClip, blitter.get());
}
}
}
void SkPath::moveTo(SkScalar x, SkScalar y) {
SkDEBUGCODE(this->validate();)
int vc = fVerbs.count();
SkPoint* pt;
if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
pt = &fPts[fPts.count() - 1];
} else {
pt = fPts.append();
*fVerbs.append() = kMove_Verb;
}
pt->set(x, y);
fBoundsIsDirty = true;
}
void SkPath::lineTo(SkScalar x, SkScalar y) {
SkDEBUGCODE(this->validate();)
if (fVerbs.count() == 0) {
fPts.append()->set(0, 0);
*fVerbs.append() = kMove_Verb;
}
fPts.append()->set(x, y);
*fVerbs.append() = kLine_Verb;
fBoundsIsDirty = true;
}
void SkDraw::drawPath(const SkPath& origSrcPath, const SkPaint& paint,
const SkMatrix* prePathMatrix, bool pathIsMutable) const {
SkDEBUGCODE(this->validate();)
// nothing to draw
if (fClip->isEmpty() ||
(paint.getAlpha() == 0 && paint.getXfermode() == NULL)) {
return;
}
SkPath* pathPtr = (SkPath*)&origSrcPath;
bool doFill = true;
SkPath tmpPath;
SkMatrix tmpMatrix;
const SkMatrix* matrix = fMatrix;
if (prePathMatrix) {
if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style ||
paint.getRasterizer()) {
SkPath* result = pathPtr;
if (!pathIsMutable) {
result = &tmpPath;
pathIsMutable = true;
}
pathPtr->transform(*prePathMatrix, result);
pathPtr = result;
} else {
if (!tmpMatrix.setConcat(*matrix, *prePathMatrix)) {
// overflow
return;
}
matrix = &tmpMatrix;
}
}
// at this point we're done with prePathMatrix
SkDEBUGCODE(prePathMatrix = (const SkMatrix*)0x50FF8001;)
/*
If the device thickness < 1.0, then make it a hairline, and
modulate alpha if the thickness is even smaller (e.g. thickness == 0.5
should modulate the alpha by 1/2)
*/
SkAutoPaintRestoreColorStrokeWidth aprc(paint);
// can we approximate a thin (but not hairline) stroke with an alpha-modulated
// hairline? Only if the matrix scales evenly in X and Y, and the device-width is
// less than a pixel
if (paint.getStyle() == SkPaint::kStroke_Style && paint.getXfermode() == NULL) {
SkScalar width = paint.getStrokeWidth();
if (width > 0 && map_radius(*matrix, &width)) {
int scale = (int)SkScalarMul(width, 256);
int alpha = paint.getAlpha() * scale >> 8;
// pretend to be a hairline, with a modulated alpha
((SkPaint*)&paint)->setAlpha(alpha);
((SkPaint*)&paint)->setStrokeWidth(0);
}
}
if (paint.getPathEffect() || paint.getStyle() != SkPaint::kFill_Style) {
doFill = paint.getFillPath(*pathPtr, &tmpPath);
pathPtr = &tmpPath;
}
if (paint.getRasterizer()) {
SkMask mask;
if (paint.getRasterizer()->rasterize(*pathPtr, *matrix,
&fClip->getBounds(), paint.getMaskFilter(), &mask,
SkMask::kComputeBoundsAndRenderImage_CreateMode)) {
this->drawDevMask(mask, paint);
SkMask::FreeImage(mask.fImage);
}
return;
}
// avoid possibly allocating a new path in transform if we can
SkPath* devPathPtr = pathIsMutable ? pathPtr : &tmpPath;
// transform the path into device space
pathPtr->transform(*matrix, devPathPtr);
SkAutoBlitterChoose blitter(*fBitmap, *fMatrix, paint);
// how does filterPath() know to fill or hairline the path??? <mrr>
if (paint.getMaskFilter() &&
paint.getMaskFilter()->filterPath(*devPathPtr, *fMatrix, *fClip,
fBounder, blitter.get())) {
return; // filterPath() called the blitter, so we're done
}
if (fBounder && !fBounder->doPath(*devPathPtr, paint, doFill)) {
return;
}
if (doFill) {
if (paint.isAntiAlias()) {
SkScan::AntiFillPath(*devPathPtr, *fClip, blitter.get());
} else {
SkScan::FillPath(*devPathPtr, *fClip, blitter.get());
}
} else { // hairline
if (paint.isAntiAlias()) {
SkScan::AntiHairPath(*devPathPtr, fClip, blitter.get());
} else {
SkScan::HairPath(*devPathPtr, fClip, blitter.get());
}
}
}
再调用skmatrix,skmask,skscan。。。。图形化的东西了解太少了,鄙人就做抛砖引玉的作用吧,分析到此结束,希望大侠补充了。
最后,在背景图上需要改大小的,这里有代码
public class test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
BufferedImage image;
try {
image = ImageIO.read(new File("D:\\t.JPG"));
resize(image, 300, 300);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void resize(BufferedImage source, int targetW, int targetH) throws IOException {
// TODO Auto-generated method stub
int type = source.getType();
BufferedImage target = null;
double sx = (double) targetW / source.getWidth();
double sy = (double) targetH / source.getHeight();
// 这里想实现在targetW,targetH范围内实现等比缩放。如果不需要等比缩放
// 则将下面的if else语句注释即可
// if (sx > sy)
// {
// sx = sy;
// targetW = (int) (sx * source.getWidth());
// }
// else
// {
// sy = sx;
// targetH = (int) (sy * source.getHeight());
// }
// if (type == BufferedImage.TYPE_CUSTOM)
// { // handmade
ColorModel cm = source.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(targetW,
targetH);
boolean alphaPremultiplied = cm.isAlphaPremultiplied();
target = new BufferedImage(cm, raster, alphaPremultiplied, null);
// }
// else
// {
// //固定宽高,宽高一定要比原图片大
// //target = new BufferedImage(targetW, targetH, type);
// target = new BufferedImage(800, 600, type);
// }
Graphics2D g = target.createGraphics();
//写入背景
g.drawImage(ImageIO.read(new File("D:\\t.jpg")), 0, 0, null);
// smoother than exlax:
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));
g.dispose();
ImageIO.write(target, "png", new FileOutputStream("D:\\a.JPG"));
}
public class test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
BufferedImage image;
try {
image = ImageIO.read(new File("D:\\t.JPG"));
resize(image, 300, 300);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static void resize(BufferedImage source, int targetW, int targetH) throws IOException {
// TODO Auto-generated method stub
int type = source.getType();
BufferedImage target = null;
double sx = (double) targetW / source.getWidth();
double sy = (double) targetH / source.getHeight();
// 这里想实现在targetW,targetH范围内实现等比缩放。如果不需要等比缩放
// 则将下面的if else语句注释即可
// if (sx > sy)
// {
// sx = sy;
// targetW = (int) (sx * source.getWidth());
// }
// else
// {
// sy = sx;
// targetH = (int) (sy * source.getHeight());
// }
// if (type == BufferedImage.TYPE_CUSTOM)
// { // handmade
ColorModel cm = source.getColorModel();
WritableRaster raster = cm.createCompatibleWritableRaster(targetW,
targetH);
boolean alphaPremultiplied = cm.isAlphaPremultiplied();
target = new BufferedImage(cm, raster, alphaPremultiplied, null);
// }
// else
// {
// //固定宽高,宽高一定要比原图片大
// //target = new BufferedImage(targetW, targetH, type);
// target = new BufferedImage(800, 600, type);
// }
Graphics2D g = target.createGraphics();
//写入背景
g.drawImage(ImageIO.read(new File("D:\\t.jpg")), 0, 0, null);
// smoother than exlax:
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.drawRenderedImage(source, AffineTransform.getScaleInstance(sx, sy));
g.dispose();
ImageIO.write(target, "png", new FileOutputStream("D:\\a.JPG"));
}
这个java工程就是把D盘的t.jpg图片改成300*300的a.jpg图片,这个就得根据你屏幕的大小了。