Drawing smooth lines with Flutter - flutter

I am trying to draw lines with flutter using a Custom painter. i.e https://medium.com/flutter-community/drawing-in-flutter-using-custompainter-307a9f1c21f8
How can I make the lines drawling by user smooth?

To draw smooth line path you can use .cubicTo() method of Path. It applies cubic Bezier interpolation. Here is code example of smooth line painter:
class SmoothLinePainter extends CustomPainter {
final Color lineColor;
final List<double> values;
SmoothLinePainter(this.lineColor, this.values);
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = lineColor
..style = PaintingStyle.stroke
..strokeWidth = 1;
final path = Path();
final yMin = values.reduce(min);
final yMax = values.reduce(max);
final yHeight = yMax - yMin;
final xAxisStep = size.width / values.length;
var xValue = 0.0;
for (var i = 0; i < values.length; i++) {
final value = values[i];
final yValue = yHeight == 0
? (0.5 * size.height)
: ((yMax - value) / yHeight) * size.height;
if (xValue == 0) {
path.moveTo(xValue, yValue);
} else {
final previousValue = values[i - 1];
final xPrevious = xValue - xAxisStep;
final yPrevious = yHeight == 0
? (0.5 * size.height)
: ((yMax - previousValue) / yHeight) * size.height;
final controlPointX = xPrevious + (xValue - xPrevious) / 2;
// HERE is the main line of code making your line smooth
path.cubicTo(
controlPointX, yPrevious, controlPointX, yValue, xValue, yValue);
}
xValue += xAxisStep;
}
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(_LineChartPainter old) =>
old.lineColor != lineColor && old.values != values;
}
And here is two images showing difference between using .lineTo() and .cubicTo() methods:
path.lineTo(...)
path.cubicTo(...)

I made a blog on how to make this feature, I hope it helps you.
It describes how to get rid of the weird-looking lines when drawing and also how to implement erase and undo feature as well.
Blog:
https://ivanstajcer.medium.com/flutter-drawing-erasing-and-undo-with-custompainter-6d00fec2bbc2
If you are not interested in all of that I will tell you quick summary of two solutions, one I found online and one I made:
Use the list of offsets and draw a line or a curve between them and also draw a circle on every offset. But this is not performance good and it will give you a bad UX in the long run.
You can read about this in the blog, divide all lines into objects and store a Path for each line, as well as a list of Offsets. Then draw the path by calling canvas.drawPath() (this is better than going through all offsets and drawing in between them) and also draw a circle in all of the offsets.
I hope this solves your problem.

This pub package provides a quick way to draw a smooth graph going through specific points - https://pub.dev/packages/smoothie

When drawing lines using the drawPath method on Canvas, strokeJoin might be another property worth checking out from the Paint class.
Since it defaults to StrokeJoin.miter, changing it StrokeJoin.round did the trick for my use case.
class SmoothPainter extends CustomPainter {
const SmoothPainter(this.path);
final Path path;
#override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..style = PaintingStyle.stroke
..strokeJoin = StrokeJoin.round
..strokeWidth = 25
..color = const Color(0xFFAABBCC);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(SmoothPainter oldDelegate) => path != oldDelegate.path;
}
From docs -
The kind of finish to place on the joins between segments.
This applies to paths drawn when style is set to PaintingStyle.stroke, It does not apply to points drawn as lines with Canvas.drawPoints.
Defaults to StrokeJoin.miter, i.e. sharp corners.
Note: The doc link also contains visuals to each StrokeJoin enum.

Related

Drawing curved corner borders in Flutter with dart:ui Canvas

I am using the https://pub.dev/packages/ncnn_yolox_flutter package for object detection in my Flutter app. I want the border outline of detections to look similar to the image below; curved corners only. (Different opacity not necessary, although if anyone knows how to draw that dynamically I would be interested).
What I'm looking for:
I currently have two options working.
Curved corners, but drawing the entire rectangle... Code and picture below (e references my detection results and drawRectPaint is my decoration):
final rect = ui.Rect.fromLTWH(
e.x,
e.y,
e.width,
e.height,
);
canvas.drawRRect(
RRect.fromRectAndRadius(rect, Radius.circular(10.0)),
drawRectPaint,
);
Corners only but without the curve. (Code for just the top left corner below):
var topLeftPath = Path();
topLeftPath.addPolygon([
Offset((e.x), (e.y + 40)),
Offset((e.x), (e.y)),
Offset((e.x + 40), (e.y)),
], false);
canvas.drawPath(
topLeftPath,
drawRectPaint,
);
You have to use moveTo, lineTo and arcTo to draw border outlines with rounded corners. You need to adjust the values to your needs.
Try this:
topLeftPath.moveTo(e.x, 40);
topLeftPath.relativeLineTo(e.x, -20);
topLeftPath.arcTo(
Rect.fromCenter(
center: Offset(20,20),
width: 40,
height: 40
), math.pi, math.pi/2, false);
topLeftPath.relativeLineTo(20, e.y);
canvas.drawPath(topLeftPath,drawRectPaint);
Clever use of PathFillType.evenOdd to darken the outside, and arcTo for the outer lines:
https://dartpad.dartlang.org/?id=20afacc59439722a28c2f7ccea4782bf
class ViewfinderPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
final canvasRect = Offset.zero & size;
const rectWidth = 300.0;
final rect = Rect.fromCircle(
center: canvasRect.center,
radius: rectWidth / 2,
);
const radius = 16.0;
const strokeWidth = 6.0;
const extend = radius + 24.0;
const arcSize = Size.square(radius * 2);
canvas.drawPath(
Path()
..fillType = PathFillType.evenOdd
..addRRect(
RRect.fromRectAndRadius(
rect,
const Radius.circular(radius),
).deflate(strokeWidth / 2),
)
..addRect(canvasRect),
Paint()..color = Colors.black26,
);
canvas.save();
canvas.translate(rect.left, rect.top);
final path = Path();
for (var i = 0; i < 4; i++) {
final l = i & 1 == 0;
final t = i & 2 == 0;
path
..moveTo(l ? 0 : rectWidth, t ? extend : rectWidth - extend)
..arcTo(
Offset(l ? 0 : rectWidth - arcSize.width,
t ? 0 : rectWidth - arcSize.width) &
arcSize,
l ? pi : pi * 2,
l == t ? pi / 2 : -pi / 2,
false)
..lineTo(l ? extend : rectWidth - extend, t ? 0 : rectWidth);
}
canvas.drawPath(
path,
Paint()
..color = Colors.deepOrange
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke,
);
canvas.restore();
}
#override
bool shouldRepaint(ViewfinderPainter oldDelegate) => false;
}

How can you know at what time an intersection of two paths takes place with Canvas?

I am trying to get a boolean every time a path intersects, with the code :
final intersection = Path.combine(
pathOperation.intersect,
path,
path2,
);
final pathsAreIntersecting = intersection.getBounds().isEmpty;
It works very well, however the problem comes when it depends on the strokeWidth of Paint, since it is too thick, I do not know when it really intersects, I give an example of a square and an eraser.
// * ERASE PAINT
final erasePaint = Paint()
..blendMode = BlendMode.clear
..color = Colors.transparent
..strokeCap = StrokeCap.round
..strokeWidth = 5.0
..style = PaintingStyle.stroke;
// * PENCIL PAINT
final pencilPaint = Paint()
..strokeCap = StrokeCap.round
..isAntiAlias = true
..color = state.currentColor
..strokeWidth = 25.0
..style = PaintingStyle.stroke;
I only want to know at what point is it taken into account the intersect operation.
I don't know if it's because of the strokeWidth, or the length, the only thing I want is that if you touch it with the other paint at the slightest edge, it becomes [false]
CODE :
final last = _drawsList.last;
if (last.type == ActionType.erase) {
final Path path = Path();
// * ONLY THIS FOR THE ERASE
path.moveTo(
last.pointList[0].dx,
last.pointList[0].dy,
);
for (int f = 1; f < last.pointList.length; f++) {
path.lineTo(last.pointList[f].dx, last.pointList[f].dy);
}
if (_drawsList.isNotEmpty) {
for (var currentShape in _drawsList.sublist(0, _drawsList.length - 1)) { /// I do not take into account the last drawing
final Path path2 = Path();
switch (currentShape.type) {
case ActionType.rectangle:
path2.addRect(Rect.fromPoints(
currentShape.pointList[0],
currentShape.pointList[1],
));
break;
default:
break;
}
final intersection = Path.combine(
PathOperation.intersect,
path,
path2,
);
final pathsAreIntersecting = intersection.getBounds().isEmpty;
print(pathsAreIntersecting); // [true] : not intersecting, [false] : intersecting
print("====");
}
}
}

Converting matrix of colors to image in flutter

I need to convert List<List<int>>> to an dart:ui.Image. I have an algorithm to convert an integer to a color. I tried to do this by drawing one-pixel rectangles on the dart:ui.Canvas, but it is about 100 times slower than I expected! _getPixelColor is the method by which I convert int to Color. Here is my code:
Future<void> matrixToImage(FField sourceMatrix) async {
PictureRecorder p = PictureRecorder();
Canvas c = Canvas(
p,
Rect.fromLTWH(0, 0, sourceMatrix.length.toDouble(),
sourceMatrix[0].length.toDouble()));
Paint paint = Paint();
for (int x = 0; x < sourceMatrix.length; x++) {
for (int y = 0; y < sourceMatrix[0].length; y++) {
int pixelValue = sourceMatrix[x][y];
paint.color = _getPixelColor(pixelValue / 40 / paletteLength + paletteOffset);
c.drawRect(Rect.fromLTWH(x.toDouble(), y.toDouble(), 1, 1), paint);
}
}
Picture picture = p.endRecording();
Image result = await picture.toImage(sourceMatrix.length, sourceMatrix[0].length);
}
If you can compute your int value to an int value that the Image class understands, you don't need the detour through canvas drawing.
Just use Image.fromBytes with the .value of the Color as the list of ints.
That depends on how the image data is stored in the list of INTs.
Generally, I like this package to manipulate images and display them:
https://pub.dev/packages/image
import 'package:image/image.dart' as im;
im.Image? img = decodeImage(bytes); // different-IMAGE class !
Uint8List uint8list = getEncodedJpg(img!,quality: 85) as Uint8List;
// ...
build(BuildContext context) {
return Container(child:
Image.memory(uint8list) // material-IMAGE class !
);
}

revoluteJointDef.localAnchorA y-axes looks wrong

From this, I would expect the following RevoluteJoint bodyB, to be higher up than the blue box on the y-axe - setting
revoluteJointDef.localAnchorA?
flame_forge2d: ^0.11.0
class Box extends BaseBox { // just a BodyComponent for same shape
BaseBox? boxB;
Box(position, paint, [this.boxB]) : super(position, paint);
#override
Body createBody() {
super.createBody();
if (this.boxB != null) {
paint.color = Colors.blue;
final revoluteJointDef = RevoluteJointDef();
revoluteJointDef.bodyA = body;
revoluteJointDef.bodyB = this.boxB!.body;
revoluteJointDef.localAnchorA.setFrom(Vector2(1, 1));
RevoluteJoint joint = RevoluteJoint(revoluteJointDef);
world.createJoint(joint);
}
return body;
}
}
I would expect it to be like this:
In flame_forge2d 0.11.0 the coordinate system was flipped on the y-axis to be the same coordinate system as Flame, so larger y-values will result in further down, instead of further up like it is in normal forge2d.
Just negate the y-value of the anchor and you should get your intended result:
revoluteJointDef.localAnchorA.setFrom(Vector2(1, -1));

Flutter - How to create a custom indicator for the TabBar that move when I switch tabs - I have a DartPad example

I custom indicator for my TabBar that looks like this.
The problem is when I move to another Tab the indicator stays at the same index.
How can I make the bottom indicator move when I select another Tab.
This is my DartPad with the current problem.
DartPad working example
You're almost there! You just need to add the offset to the x coordinate within your painter.
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration cfg) {
Path _trianglePath = Path();
_trianglePath.moveTo(offset.dx + cfg.size!.width / 2 - 10, cfg.size!.height);
_trianglePath.lineTo(offset.dx + cfg.size!.width / 2 + 10, cfg.size!.height);
_trianglePath.lineTo(offset.dx + cfg.size!.width / 2, cfg.size!.height - 10);
_trianglePath.lineTo(offset.dx + cfg.size!.width / 2 - 10, cfg.size!.height);
_trianglePath.close();
canvas.drawPath(_trianglePath, _paint);
}
Just move your offset from drawpath
class _CirclePainter extends BoxPainter {
final Paint _paint;
_CirclePainter(Color color)
: _paint = Paint()
..color = color
..isAntiAlias = true;
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration cfg) {
Path _trianglePath = Path();
_trianglePath.moveTo(cfg.size.width / 2 - 10, cfg.size.height);
_trianglePath.lineTo(cfg.size.width / 2 + 10, cfg.size.height);
_trianglePath.lineTo(cfg.size.width / 2, cfg.size.height - 10);
_trianglePath.lineTo(cfg.size.width / 2 - 10, cfg.size.height);
_trianglePath.close();
canvas.drawPath(_trianglePath.shift(offset), _paint);// here need to pass offset
}
}