大家好,
我太坚持为油漆应用创建这种类型的画笔,但没有找到与此相关的任何内容。
我是绘画/画布的新手,所以对于我已完成的基础知识,我对此一无所知,但对于像创建画笔这样的效果,我没有任何关于如何创建/实现它的知识。有人有这方面的例子或代码吗?
我的应用程序需要这种类型的画笔,一个简单的例子需要理解:
谢谢你。
大家好,
我太坚持为油漆应用创建这种类型的画笔,但没有找到与此相关的任何内容。
我是绘画/画布的新手,所以对于我已完成的基础知识,我对此一无所知,但对于像创建画笔这样的效果,我没有任何关于如何创建/实现它的知识。有人有这方面的例子或代码吗?
我的应用程序需要这种类型的画笔,一个简单的例子需要理解:
谢谢你。
我想没有简单的方法。我发现这个讨论,特别是下面的帖子很有趣:
专业的计算机图形学绝非易事。这就是为什么很少有人真正解决它。更糟糕的是,专业技术很少发表。我不知道你想要付出多少努力才能得到它,但我会给你一些启示。因此,如果您愿意,您可以学习、开发并以最佳方式获得它。如果这对你来说太难了,就把它放在这里作为一种好奇心。
现在专业的毛笔制作方法是这样的:
主曲线是平滑的,因为它是基于样条曲线绘制的。要获得更专业的结果,请构建两条样条曲线:一条使用您在样条曲线上获得的点(例如,从鼠标事件中获得),另一条使用样条曲线控制点等点。所以你画的曲线就是这两条样条插值产生的曲线。这样,您就可以绘制“主曲线”。
您还应该有一个必须应用变化的“主厚度”。该厚度变化是根据您想要的结果计算的。更常见的书法毛笔就像您链接的图像中一样:弯曲区域通常比直线区域更薄。这是更常见的类型,因为大多数设计师在使用平板电脑绘图时都会得到这种结果,因此程序会模拟这种行为。尤其是这种效果通常使用基于主样条的二阶导数的函数来计算。厚度变化幅度可以是可配置的值。
细而尖的曲线尖端是在额外的计算中制作的。有时,使用样条曲线或某种“ceil 函数”平滑厚度变化可能是一个好主意。
如果你把一切都做对了,你手中就会有一条厚实的(当然是封闭的)曲线。使用您可以开发的最佳填充算法绘制它。如果可以,请使用抗锯齿。
所有这些技术都可以在用户移动鼠标时实时计算。您获得的分数越多,您进行的计算就越多,但它运作良好,因为您已经进行的大多数计算仍然有效。通常你只需要重建一个小(最后)部分。
最后一个建议:永远不要使用函数回归方法进行 2D 平滑,除非你的点真的代表一个函数(所以你需要尽可能地保持点的“数学意义”)。我无法想象一种更慢的方法来平滑没有特殊语义的点。唯一的例外是当你有非常稀疏的点并且输入顺序无关紧要时,但当有人用画笔绘图时情况并非如此。
您可以通过在画布上绘制位图纹理来实现此效果。我从您共享的图像中裁剪了一些纹理并将其用作画布中的纹理:-
纹理图像:-
这是我的视图类:-
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.view.View;
import com.serveroverload.dali.R;
public class CanvasBrushDrawing extends View {
private Bitmap mBitmapBrush;
private Vector2 mBitmapBrushDimensions;
private List<Vector2> mPositions = new ArrayList<Vector2>(100);
private static final class Vector2 {
public Vector2(float x, float y) {
this.x = x;
this.y = y;
}
public final float x;
public final float y;
}
public CanvasBrushDrawing(Context context) {
super(context);
// load your brush here
mBitmapBrush = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
mBitmapBrushDimensions = new Vector2(mBitmapBrush.getWidth(), mBitmapBrush.getHeight());
setBackgroundColor(0xffffffff);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (Vector2 pos : mPositions) {
canvas.drawBitmap(mBitmapBrush, pos.x, pos.y, null);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_MOVE:
final float posX = event.getX();
final float posY = event.getY();
mPositions.add(new Vector2(posX - mBitmapBrushDimensions.x / 2, posY - mBitmapBrushDimensions.y / 2));
invalidate();
}
return true;
}
}
您可以像这样在您的活动中使用此视图:-
setContentView(new CanvasBrushDrawing(MainActivity.this));
现在你只需要你的设计师提供更好的纹理文件。希望它有所帮助
您可以在 Git repo https://github.com/hiteshsahu/Dali-PaintBox上查看完整的源代码
虽然为时已晚,但我想分享一些东西。这可能会帮助某人。以下链接中讨论了各种画笔技术,其中包含用于 HTML 画布的 JavaScript 代码。您所要做的就是将 JavaScript 代码转换为您期望的代码。将 JavaScript Canvas 代码转换为 Android Canvas 代码非常简单。
我已将“多行”技术转换为 android 的 Java 代码;您可以检查以下android视图代码。
public class MultipleLines extends View {
private Bitmap bitmap;
private Canvas canvas;
private Paint mPaint;
public MultipleLines(Context context) {
super(context);
init();
}
private void init(){
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(0xFFFF0000);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(1);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
touch_start(x, y);
invalidate();
break;
case MotionEvent.ACTION_MOVE:
touch_move(x, y);
invalidate();
break;
case MotionEvent.ACTION_UP:
touch_up();
invalidate();
break;
}
return true;
}
private boolean isDrawing;
private List<PointF> points = new ArrayList<>();
private void touch_start(float touchX, float touchY) {
isDrawing = true;
points.add(new PointF(touchX, touchY));
canvas.save();
}
private void touch_move(float touchX, float touchY) {
if (!isDrawing) return;
canvas.drawColor(Color.TRANSPARENT);
points.add(new PointF(touchX, touchY));
stroke(offsetPoints(-10));
stroke(offsetPoints(-5));
stroke(points);
stroke(offsetPoints(5));
stroke(offsetPoints(10));
}
private void touch_up() {
isDrawing = false;
points.clear();
canvas.restore();
}
private List<PointF> offsetPoints(float val) {
List<PointF> offsetPoints = new ArrayList<>();
for (int i = 0; i < points.size(); i++) {
PointF point = points.get(i);
offsetPoints.add(new PointF(point.x + val, point.y + val));
}
return offsetPoints;
}
private void stroke(List<PointF> points) {
PointF p1 = points.get(0);
PointF p2 = points.get(1);
Path path = new Path();
path.moveTo(p1.x, p1.y);
for (int i = 1; i < points.size(); i++) {
// we pick the point between pi+1 & pi+2 as the
// end point and p1 as our control point
PointF midPoint = midPointBtw(p1, p2);
path.quadTo(p1.x, p1.y, midPoint.x, midPoint.y);
p1 = points.get(i);
if(i+1 < points.size()) p2 = points.get(i+1);
}
// Draw last line as a straight line while
// we wait for the next point to be able to calculate
// the bezier control point
path.lineTo(p1.x, p1.y);
canvas.drawPath(path,mPaint);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bitmap, 0, 0, null);
}
private PointF midPointBtw(PointF p1, PointF p2) {
return new PointF(p1.x + (p2.x - p1.x) / 2.0f, p1.y + (p2.y - p1.y) / 2.0f);
}
}