更新:本文的内容只是一部分,这段时间添加了橡皮擦这个新功能,于是问题接二连三的来,比如说:如果用本文的内容去做橡皮擦的话,难!(至少我没解决,不是没背景图,就是有背景图但是更新要在下一下刷橡皮擦的时候才能更新效果),然后有个setbackgroundresource的函数,这个函数就可以了,但是问题又来了,比如说保存,清屏,但是我都解决了(清屏的话就是重新构造一个图,当clear的时候就把这张图赋值给以前的图片。保存的话我就是把绘下个图放到一张有背景的canvas上面,至是分辨率的问题自己去解决就行了,保证存下来的跟你用setbackgoundresource绘图看到的效果一致,需要源码的请联系我)
本人也是在网上查了很多文章后才做出来的,感觉网上的一些涂鸦功能不是很完善,在此就稍微完善了一下。先看下效果图吧(想做成全屏的话需要弄一张跟你屏幕一样大小的背景图,找不到也没关系,我有改尺寸的代码,一并献上)。代码下载请到http://www.oschina.net/code/snippet_729469_20445 其实涂鸦的难点就是如何能在canvas上进行清屏又能保存,至少目前我碰到的情况是这样,这就需要对canvas与bitmap的较为深入的理解了,这个你多写这方面的代码就行了,网上有许多涂鸦的作品,看看源代码。
涂鸦中关于canvas的学习需要掌握三点吧:1:view 2:onDraw函数 3:onTouchEvent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
|
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中我实现了一个菜单按钮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 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())这个函数
1
|
private
static
native
int
initRaster(
int
nativeBitmapOrZero);
|
这个需要涉及到向底层本地函数传递一个int参数,那么这个bitmap.ni()是什么呢?查看bitmap.java同样位于android2.3.3/frameworks/base/graphics/java/android/graphics
1
2
3
4
5
6
7
|
/* 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,注册函数有
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
{
"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)
1
2
3
4
5
6
|
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)
1
2
3
4
5
|
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,这个是在注册函数中表示的对应的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
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中的
1
|
canvas->drawBitmap(*bitmap, left_, top_, &filteredPaint);
|
类似的,其他的一些canvas的操作都是调用的SKCanvas的对应的方法。(skcanvas与skia的关系请大家网上查询)
那么SKCnvas的源码究竟存放在哪里呢?android/external/skia/src/core,因为所有的方法的实现都是类似的,这里我就单独选择一个简单的drawLine吧
1
2
3
4
5
6
7
8
|
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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
1
2
3
4
|
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方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
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循环
1
2
3
4
5
6
|
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函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
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。。。。图形化的东西了解太少了,鄙人就做抛砖引玉的作用吧,分析到此结束,希望大侠补充了。
最后,在背景图上需要改大小的,这里有代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
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图片,这个就得根据你屏幕的大小了。