clean code , pen effects

This commit is contained in:
Naser Elziadna
2024-11-28 09:50:08 +02:00
parent 4a65dbe540
commit c4aa36bea3
11 changed files with 243 additions and 175 deletions

View File

@@ -10,6 +10,7 @@ enum PenTool {
normalWithShaderPen,
glowPen,
glowWithDotsPen,
sprayPen,
eraserPen,
customPen
}

View File

@@ -0,0 +1,11 @@
import 'dart:ui';
import 'package:doddle/domain/models/effects/pen_effect.dart';
class EraserEffect extends PenEffect {
@override
void paint(Canvas canvas, Path path, Paint paint) {
paint.blendMode = BlendMode.clear;
canvas.drawPath(path, paint);
}
}

View File

@@ -0,0 +1,21 @@
import 'dart:ui';
import 'package:doddle/domain/models/effects/pen_effect.dart';
import 'package:flutter/material.dart';
class GlowEffect extends PenEffect {
@override
void paint(Canvas canvas, Path path, Paint paint) {
canvas.drawPath(
path,
Paint()
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 3.0)
..color = drawController.currentColor
..style = PaintingStyle.stroke
..strokeWidth = drawController.penSize! + 5,
);
canvas.drawPath(path, paint..color = Colors.white);
}
}

View File

@@ -0,0 +1,26 @@
import 'package:doddle/domain/models/effects/pen_effect.dart';
import 'package:flutter/material.dart';
import 'package:path_drawing/path_drawing.dart';
class GlowWithDotsEffect extends PenEffect {
@override
void paint(Canvas canvas, Path path, Paint paint) {
final pathWithDots = dashPath(
path,
dashArray: CircularIntervalList<double>(<double>[5, 10]), // Changed to create 5px dashes with 10px gaps
);
canvas.drawPath(
pathWithDots,
Paint()
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 5.0)
..color = drawController.currentColor
..style = PaintingStyle.stroke
..strokeWidth = 5.0);
canvas.drawPath(
pathWithDots,
paint..strokeWidth = drawController.penSize!);
}
}

View File

@@ -0,0 +1,10 @@
import 'dart:ui';
import 'package:doddle/domain/models/effects/pen_effect.dart';
class NormalEffect extends PenEffect {
@override
void paint(Canvas canvas, Path path, Paint paint) {
canvas.drawPath(path, paint..color = drawController.currentColor);
}
}

View File

@@ -0,0 +1,33 @@
import 'package:doddle/domain/models/effects/pen_effect.dart';
import 'package:flutter/material.dart';
class NormalWithShaderEffect extends PenEffect {
@override
void paint(Canvas canvas, Path path, Paint paint) {
canvas.drawPath(
path,
Paint()
..shader = sweepShader
..strokeJoin = StrokeJoin.round
..style = PaintingStyle.stroke
..strokeWidth = drawController.penSize!,
);
}
static final SweepGradient colorWheelGradient = SweepGradient(
center: Alignment.bottomRight,
colors: [
Color.fromARGB(255, 255, 0, 0),
Color.fromARGB(255, 255, 255, 0),
Color.fromARGB(255, 0, 255, 0),
Color.fromARGB(255, 0, 255, 255),
Color.fromARGB(255, 0, 0, 255),
Color.fromARGB(255, 255, 0, 255),
Color.fromARGB(255, 255, 0, 0),
]);
// If we create a shader from the above SweepGraident, we get
// a crash on web, but only on web.
static final Shader sweepShader =
colorWheelGradient.createShader(const Rect.fromLTWH(0, 0, 100, 10));
}

View File

@@ -0,0 +1,45 @@
import 'dart:ui';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:doddle/domain/models/draw_controller.dart';
import 'package:doddle/application/providers/canvas/canvas_provider.dart';
abstract class PenEffect {
late final DrawController drawController;
void initialize(WidgetRef ref) {
drawController = ref.read(canvasProvider);
}
void paint(Canvas canvas, Path path, Paint paint);
// Utility methods for all effects
List<Offset> getSymmetryPoints(Offset point, Size size) {
final center = Offset(size.width / 2, size.height / 2);
final relativePoint = point - center;
final symmetryLines = drawController.symmetryLines ?? 1;
List<Offset> points = [];
for (var i = 0; i < symmetryLines; i++) {
final angle = 2 * math.pi * i / symmetryLines;
final rotatedPoint = _rotatePoint(relativePoint, angle);
points.add(rotatedPoint + center);
if (drawController.mirrorSymmetry) {
points.add(Offset(-rotatedPoint.dx, rotatedPoint.dy) + center);
}
}
return points;
}
Offset _rotatePoint(Offset point, double angle) {
final cos = math.cos(angle);
final sin = math.sin(angle);
return Offset(
point.dx * cos - point.dy * sin,
point.dx * sin + point.dy * cos,
);
}
}

View File

@@ -0,0 +1,52 @@
import 'dart:ui';
import 'package:doddle/domain/models/effects/pen_effect.dart';
import 'dart:math' as math;
class SprayEffect extends PenEffect {
@override
void paint(Canvas canvas, Path path, Paint paint) {
final random = math.Random();
for (var j = 0; j < drawController.points!.length - 1; j++) {
if (drawController.points![j + 1] == null) {
j++;
continue;
}
final currentPoint = drawController.points![j]?.offset;
final nextPoint = drawController.points![j + 1]?.offset;
if (currentPoint != null && nextPoint != null) {
// If points are the same (holding still), create multiple spray iterations
final isHolding = currentPoint == nextPoint;
final iterations =
isHolding ? 3 : 1; // Increase spray density when holding still
for (var iter = 0; iter < iterations; iter++) {
// Create random spray points around the current position
final sprayDensity = (drawController.penSize! * 2).round();
for (var i = 0; i < sprayDensity; i++) {
final sprayRadius = drawController.penSize! * 2;
final randomRadius = random.nextDouble() * sprayRadius;
final randomAngle = random.nextDouble() * 2 * math.pi;
final sprayX =
currentPoint.dx + randomRadius * math.cos(randomAngle);
final sprayY =
currentPoint.dy + randomRadius * math.sin(randomAngle);
canvas.drawCircle(
Offset(sprayX, sprayY),
0.5,
Paint()
..color = drawController.currentColor.withOpacity(0.3)
..style = PaintingStyle.fill,
);
}
}
}
}
}
}

View File

@@ -15,6 +15,7 @@ class BrushToolGrid extends ConsumerWidget {
BrushTool(penTool: PenTool.normalPen, picture: Assets.svg.pen2Preview.svg()),
BrushTool(penTool: PenTool.glowWithDotsPen, picture: Assets.svg.pen5Preview.svg()),
BrushTool(penTool: PenTool.normalWithShaderPen, picture: Assets.svg.pen6Preview.svg()),
BrushTool(penTool: PenTool.sprayPen, picture: Assets.svg.pen3Preview.svg()),
];
final selectedPenTool = ref.watch(canvasProvider).penTool;

View File

@@ -1,56 +1,57 @@
import 'package:doddle/domain/models/draw_controller.dart';
import 'package:doddle/domain/models/effects/eraser_effect.dart';
import 'package:doddle/domain/models/effects/glow_effect.dart';
import 'package:doddle/domain/models/effects/glow_with_dots_effect.dart';
import 'package:doddle/domain/models/effects/normal_effect.dart';
import 'package:doddle/domain/models/effects/normal_with_shader_effect.dart';
import 'package:doddle/domain/models/effects/pen_effect.dart';
import 'package:doddle/domain/models/effects/spray_effect.dart';
import 'package:doddle/domain/models/point.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'package:flutter_riverpod/flutter_riverpod.dart';
const pi = math.pi;
class Sketcher extends CustomPainter {
final List<Point?> points;
final Size screenSize;
final double symmetryLines;
final Color color;
final PenTool penTool;
final double penSize;
final bool mirrorSymmetry;
final bool showGuidelines;
final DrawController controller;
final WidgetRef ref;
final Map<PenTool, PenEffect> _effects;
Sketcher(
this.points,
this.screenSize,
this.symmetryLines,
this.color,
this.penTool,
this.penSize, {
this.mirrorSymmetry = false,
this.showGuidelines = true,
});
Sketcher(this.controller, this.ref) : _effects = {
PenTool.eraserPen: EraserEffect()..initialize(ref),
PenTool.sprayPen: SprayEffect()..initialize(ref),
PenTool.glowPen: GlowEffect()..initialize(ref),
PenTool.normalPen: NormalEffect()..initialize(ref),
PenTool.normalWithShaderPen: NormalWithShaderEffect()..initialize(ref),
PenTool.glowWithDotsPen: GlowWithDotsEffect()..initialize(ref),
// Initialize other effects
};
@override
bool shouldRepaint(Sketcher oldDelegate) {
return true;
}
@override
@override
void paint(Canvas canvas, Size size) {
canvas.save();
final center = Offset(size.width / 2, size.height / 2);
canvas.translate(center.dx, center.dy);
Paint paint = Paint()
..color = color
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke
..strokeWidth = penSize;
Path path = Path();
_drawPoints(canvas, path, paint);
for (var i = 0; i < symmetryLines; i++) {
canvas.save();
_applyPenEffects(canvas, path, paint);
canvas.restore();
canvas.rotate(2 * pi / symmetryLines);
final effect = _effects[controller.penTool];
if (effect != null) {
Path path = Path();
Paint paint = Paint()
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke
..strokeWidth = controller.penSize ?? 2.0;
_drawPoints(canvas, path, paint);
effect.paint(canvas, path, paint);
}
canvas.restore();
@@ -63,13 +64,13 @@ class Sketcher extends CustomPainter {
final angle = math.atan2(relX, relY);
List<Offset> result = [];
for (int i = 0; i < symmetryLines; i++) {
final theta = angle + 2 * pi * i / symmetryLines;
for (int i = 0; i < controller.symmetryLines!; i++) {
final theta = angle + 2 * pi * i / controller.symmetryLines!;
final x = math.sin(theta) * dist;
final y = math.cos(theta) * dist;
result.add(Offset(x, y));
if (mirrorSymmetry) {
if (controller.mirrorSymmetry) {
result.add(Offset(-x, y));
}
}
@@ -77,14 +78,14 @@ class Sketcher extends CustomPainter {
}
void _drawPoints(Canvas canvas, Path path, Paint paint) {
for (var j = 0; j < points.length - 1; j++) {
if (points[j + 1] == null) {
for (var j = 0; j < controller.points!.length - 1; j++) {
if (controller.points![j + 1] == null) {
j++;
continue;
}
final currentPoint = points[j]?.offset;
final nextPoint = points[j + 1]?.offset;
final currentPoint = controller.points![j]?.offset;
final nextPoint = controller.points![j + 1]?.offset;
if (currentPoint != null && nextPoint != null) {
// Get all symmetry points for both current and next points
@@ -99,131 +100,4 @@ class Sketcher extends CustomPainter {
}
}
}
void _applyPenEffects(Canvas canvas, Path path, Paint paint) {
if (penTool == PenTool.eraserPen) {
canvas.drawPath(
path,
Paint()
..color = Colors.black
..strokeJoin = StrokeJoin.round
..style = PaintingStyle.stroke
..strokeWidth = penSize,
);
return;
}
_drawPathWithEffect(canvas, path, paint);
}
void _drawPathWithEffect(Canvas canvas, Path path, Paint paint) {
switch (penTool) {
case PenTool.glowPen:
_drawGlowPath(canvas, path, paint);
break;
case PenTool.normalPen:
_drawNormalPath(canvas, path);
break;
case PenTool.normalWithShaderPen:
_drawShaderPath(canvas, path);
break;
case PenTool.glowWithDotsPen:
_drawGlowDotsPath(canvas, path, paint);
break;
default:
break;
}
}
void _drawGlowPath(Canvas canvas, Path path, Paint paint) {
canvas.drawPath(
path,
Paint()
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 5.0)
..color = color
..style = PaintingStyle.stroke
..strokeWidth = 10.0);
canvas.drawPath(path, paint..color = Colors.white);
// canvas.drawPath(
// path,
// paint
// ..color = color.withOpacity(0.2)
// ..strokeWidth = penSize * 3
// ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 10),
// );
// canvas.drawPath(
// path,
// paint
// ..color = color.withOpacity(0.4)
// ..strokeWidth = penSize * 2
// ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 5),
// );
// canvas.drawPath(
// path,
// paint
// ..color = color
// ..strokeWidth = penSize
// ..maskFilter = null,
// );
}
void _drawNormalPath(Canvas canvas, Path path) {
canvas.drawPath(
path,
Paint()
..color = color
..strokeJoin = StrokeJoin.round
..style = PaintingStyle.stroke
..strokeWidth = penSize,
);
}
void _drawShaderPath(Canvas canvas, Path path) {
canvas.drawPath(
path,
Paint()
..shader = sweepShader
..strokeJoin = StrokeJoin.round
..style = PaintingStyle.stroke
..strokeWidth = penSize,
);
}
void _drawGlowDotsPath(Canvas canvas, Path path, Paint paint) {
_drawGlowPath(canvas, path, paint);
for (var i = 0.0; i <= 1.0; i += 0.1) {
final metric = path.computeMetrics().first;
final tangent = metric.getTangentForOffset(metric.length * i);
if (tangent != null) {
final position = tangent.position;
canvas.drawCircle(
position,
penSize / 4,
Paint()
..color = color
..style = PaintingStyle.fill,
);
}
}
}
}
const SweepGradient colorWheelGradient =
SweepGradient(center: Alignment.bottomRight, colors: [
Color.fromARGB(255, 255, 0, 0),
Color.fromARGB(255, 255, 255, 0),
Color.fromARGB(255, 0, 255, 0),
Color.fromARGB(255, 0, 255, 255),
Color.fromARGB(255, 0, 0, 255),
Color.fromARGB(255, 255, 0, 255),
Color.fromARGB(255, 255, 0, 0),
]);
// If we create a shader from the above SweepGraident, we get
// a crash on web, but only on web.
final Shader sweepShader =
colorWheelGradient.createShader(const Rect.fromLTWH(0, 0, 100, 10));

View File

@@ -275,14 +275,8 @@ class _CanvasScreenState extends ConsumerState<CanvasScreen> {
controller: ref.read(recorderControllerProvider),
child: CustomPaint(
foregroundPainter: Sketcher(
drawController.points ?? [],
kCanvasSize,
drawController.symmetryLines ?? 5,
drawController.currentColor ?? Colors.red,
drawController.penTool ?? PenTool.glowPen,
drawController.penSize ?? 1,
mirrorSymmetry: drawController.mirrorSymmetry ?? false,
showGuidelines: drawController.showGuidelines ?? true,
drawController,
ref,
),
painter: LastImageAsBackground(
image: drawController.stamp?.isEmpty ?? true