Skip to content

Commit 2a13bfa

Browse files
Amit-Matthbakicelebijuliajulie95
authored
PAINTROID-795 add clipboard tool (#128)
* PAINTROID-795 add clipboard tool * PAINTROID-795 add tests and fix bugs * PAINTROID-795 remove prepare() * PAINTROID-795: remove debug log show toast instead * PAINTROID-795 Fix test --------- Co-authored-by: Baki Celebi <clozopin@gmail.com> Co-authored-by: Julia Herold <37836518+juliajulie95@users.noreply.github.com>
1 parent ad1a169 commit 2a13bfa

35 files changed

+3086
-603
lines changed

lib/core/commands/command_factory/command_factory.dart

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
import 'dart:typed_data';
2+
import 'dart:ui' as ui;
3+
14
import 'package:flutter/material.dart';
5+
import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart';
6+
import 'package:paintroid/core/commands/command_implementation/graphic/delete_region_command.dart';
27
import 'package:paintroid/core/commands/command_implementation/graphic/text_command.dart';
38
import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart';
49
import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart';
@@ -55,6 +60,21 @@ class CommandFactory {
5560
angle,
5661
);
5762

63+
ClipboardCommand createClipboardCommand(
64+
Paint paint,
65+
Uint8List imageData,
66+
ui.Offset offset,
67+
double scale,
68+
double rotation,
69+
) =>
70+
ClipboardCommand(
71+
paint,
72+
imageData,
73+
offset,
74+
scale,
75+
rotation,
76+
);
77+
5878
TextCommand createTextCommand(
5979
Offset point,
6080
String text,
@@ -108,4 +128,12 @@ class CommandFactory {
108128
SprayCommand createSprayCommand(List<Offset> points, Paint paint) {
109129
return SprayCommand(points, paint);
110130
}
131+
132+
DeleteRegionCommand createDeleteRegionCommand(
133+
ui.Rect region,
134+
) =>
135+
DeleteRegionCommand(
136+
Paint(),
137+
region,
138+
);
111139
}

lib/core/commands/command_implementation/command.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import 'package:equatable/equatable.dart';
2+
import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart';
3+
import 'package:paintroid/core/commands/command_implementation/graphic/delete_region_command.dart';
24
import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart';
35
import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart';
46
import 'package:paintroid/core/commands/command_implementation/graphic/shape/ellipse_shape_command.dart';
@@ -13,6 +15,8 @@ abstract class Command with EquatableMixin {
1315

1416
Map<String, dynamic> toJson();
1517

18+
Future<void> prepareForRuntime() async {}
19+
1620
factory Command.fromJson(Map<String, dynamic> json) {
1721
String type = json['type'] as String;
1822
switch (type) {
@@ -24,6 +28,10 @@ abstract class Command with EquatableMixin {
2428
return SquareShapeCommand.fromJson(json);
2529
case SerializerType.ELLIPSE_SHAPE_COMMAND:
2630
return EllipseShapeCommand.fromJson(json);
31+
case SerializerType.CLIPBOARD_COMMAND:
32+
return ClipboardCommand.fromJson(json);
33+
case SerializerType.DELETE_REGION_COMMAND:
34+
return DeleteRegionCommand.fromJson(json);
2735
case SerializerType.TEXT_COMMAND:
2836
return TextCommand.fromJson(json);
2937
case SerializerType.HEART_SHAPE_COMMAND:
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// ignore_for_file: must_be_immutable
2+
3+
import 'dart:ui' as ui;
4+
import 'package:flutter/foundation.dart';
5+
import 'package:freezed_annotation/freezed_annotation.dart';
6+
import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart';
7+
import 'package:paintroid/core/json_serialization/converter/offset_converter.dart';
8+
import 'package:paintroid/core/json_serialization/converter/paint_converter.dart';
9+
import 'package:paintroid/core/json_serialization/converter/uint8list_base64_converter.dart';
10+
import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart';
11+
import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart';
12+
import 'package:paintroid/core/models/loggable_mixin.dart';
13+
import 'package:toast/toast.dart';
14+
15+
part 'clipboard_command.g.dart';
16+
17+
@JsonSerializable()
18+
class ClipboardCommand extends GraphicCommand with LoggableMixin {
19+
@Uint8ListBase64Converter()
20+
final Uint8List imageData;
21+
@OffsetConverter()
22+
final ui.Offset offset;
23+
final double scale;
24+
final double rotation;
25+
26+
final int version;
27+
final String type;
28+
29+
@JsonKey(includeFromJson: false, includeToJson: false)
30+
ui.Image? _runtimeImage;
31+
32+
ClipboardCommand(
33+
super.paint,
34+
this.imageData,
35+
this.offset,
36+
this.scale,
37+
this.rotation, {
38+
int? version,
39+
this.type = SerializerType.CLIPBOARD_COMMAND,
40+
}) : version = version ??
41+
VersionStrategyManager.strategy.getClipboardCommandVersion();
42+
43+
@override
44+
Future<void> prepareForRuntime() async {
45+
if (_runtimeImage == null && imageData.isNotEmpty) {
46+
try {
47+
final buffer = await ui.ImmutableBuffer.fromUint8List(imageData);
48+
final descriptor = await ui.ImageDescriptor.encoded(buffer);
49+
final codec = await descriptor.instantiateCodec();
50+
final frameInfo = await codec.getNextFrame();
51+
_runtimeImage = frameInfo.image;
52+
} catch (e, _) {
53+
Toast.show(
54+
'Error: $e',
55+
duration: Toast.lengthShort,
56+
gravity: Toast.bottom,
57+
);
58+
_runtimeImage = null;
59+
}
60+
}
61+
}
62+
63+
@override
64+
void call(ui.Canvas canvas) {
65+
if (_runtimeImage == null) {
66+
Toast.show(
67+
'ClipboardCommand.call: _runtimeImage is null. Cannot draw.',
68+
duration: Toast.lengthShort,
69+
gravity: Toast.bottom,
70+
);
71+
72+
return;
73+
}
74+
75+
canvas.save();
76+
canvas.translate(offset.dx, offset.dy);
77+
canvas.rotate(rotation);
78+
canvas.scale(scale);
79+
80+
final imageWidth = _runtimeImage!.width.toDouble();
81+
final imageHeight = _runtimeImage!.height.toDouble();
82+
83+
final src = ui.Rect.fromLTWH(0, 0, imageWidth, imageHeight);
84+
final dst = ui.Rect.fromLTWH(
85+
-imageWidth / 2,
86+
-imageHeight / 2,
87+
imageWidth,
88+
imageHeight,
89+
);
90+
final imagePaint = ui.Paint()..filterQuality = ui.FilterQuality.high;
91+
canvas.drawImageRect(_runtimeImage!, src, dst, imagePaint);
92+
canvas.restore();
93+
}
94+
95+
@override
96+
List<Object?> get props => [
97+
paint,
98+
imageData,
99+
offset,
100+
scale,
101+
rotation,
102+
version,
103+
type,
104+
];
105+
106+
@override
107+
Map<String, dynamic> toJson() => _$ClipboardCommandToJson(this);
108+
109+
factory ClipboardCommand.fromJson(Map<String, dynamic> json) {
110+
final int version = json['version'] as int? ?? Version.v1;
111+
switch (version) {
112+
case Version.v1:
113+
return _$ClipboardCommandFromJson(json);
114+
case Version.v2:
115+
// For different versions of ClipboardCommand the deserialization
116+
// has to be implemented manually.
117+
// Autogenerated code can only be used for one version
118+
default:
119+
return _$ClipboardCommandFromJson(json);
120+
}
121+
}
122+
}

lib/core/commands/command_implementation/graphic/clipboard_command.g.dart

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// ignore_for_file: must_be_immutable
2+
3+
import 'dart:ui' as ui;
4+
5+
import 'package:freezed_annotation/freezed_annotation.dart';
6+
import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart';
7+
import 'package:paintroid/core/json_serialization/converter/paint_converter.dart';
8+
import 'package:paintroid/core/json_serialization/converter/rect_converter.dart';
9+
import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart';
10+
import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart';
11+
12+
part 'delete_region_command.g.dart';
13+
14+
@JsonSerializable()
15+
class DeleteRegionCommand extends GraphicCommand {
16+
@RectConverter()
17+
final ui.Rect region;
18+
19+
final int version;
20+
final String type;
21+
22+
DeleteRegionCommand(
23+
super.paint,
24+
this.region, {
25+
int? version,
26+
this.type = SerializerType.DELETE_REGION_COMMAND,
27+
}) : version = version ??
28+
VersionStrategyManager.strategy.getDeleteRegionCommandVersion();
29+
30+
@override
31+
void call(ui.Canvas canvas) {
32+
final clearPaint = ui.Paint()
33+
..blendMode = ui.BlendMode.clear
34+
..style = ui.PaintingStyle.fill;
35+
canvas.drawRect(region, clearPaint);
36+
}
37+
38+
@override
39+
List<Object?> get props => [
40+
paint,
41+
region,
42+
version,
43+
type,
44+
];
45+
46+
@override
47+
Map<String, dynamic> toJson() => _$DeleteRegionCommandToJson(this);
48+
49+
factory DeleteRegionCommand.fromJson(Map<String, dynamic> json) {
50+
final int version = json['version'] as int? ?? Version.v1;
51+
switch (version) {
52+
case Version.v1:
53+
return _$DeleteRegionCommandFromJson(json);
54+
case Version.v2:
55+
// For different versions of DeleteRegionCommand the deserialization
56+
// has to be implemented manually.
57+
// Autogenerated code can only be used for one version
58+
default:
59+
return _$DeleteRegionCommandFromJson(json);
60+
}
61+
62+
}
63+
}

lib/core/commands/command_implementation/graphic/delete_region_command.g.dart

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/core/commands/command_manager/command_manager.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:ui';
22

33
import 'package:paintroid/core/commands/command_implementation/command.dart';
4+
import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart';
45
import 'package:paintroid/core/commands/command_implementation/graphic/text_command.dart';
56
import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart';
67
import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart';
@@ -116,6 +117,8 @@ class CommandManager {
116117
return ToolData.SHAPES;
117118
} else if (command.runtimeType == EllipseShapeCommand) {
118119
return ToolData.SHAPES;
120+
} else if (command.runtimeType == ClipboardCommand) {
121+
return ToolData.CLIPBOARD;
119122
} else if (command.runtimeType == TextCommand) {
120123
return ToolData.TEXT;
121124
} else if (command.runtimeType == SprayCommand) {

lib/core/commands/command_painter.dart

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:paintroid/core/enums/tool_types.dart';
66
import 'package:paintroid/core/providers/state/canvas_state_provider.dart';
77
import 'package:paintroid/core/providers/state/paint_provider.dart';
88
import 'package:paintroid/core/providers/state/toolbox_state_provider.dart';
9+
import 'package:paintroid/core/tools/implementation/clipboard_tool.dart';
910
import 'package:paintroid/core/tools/implementation/shapes_tool.dart';
1011
import 'package:paintroid/core/tools/implementation/text_tool.dart';
1112
import 'package:paintroid/core/tools/implementation/brush_tool.dart';
@@ -28,7 +29,8 @@ class CommandPainter extends CustomPainter {
2829
@override
2930
void paint(Canvas canvas, Size size) {
3031
if (currentTool.type != ToolType.SHAPES &&
31-
currentTool.type != ToolType.TEXT) {
32+
currentTool.type != ToolType.TEXT &&
33+
currentTool.type != ToolType.CLIPBOARD) {
3234
canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height));
3335
}
3436
switch (currentTool.type) {
@@ -40,6 +42,8 @@ class CommandPainter extends CustomPainter {
4042
..drawShape(canvas, ref.read(paintProvider))
4143
..drawGuides(canvas);
4244
break;
45+
case ToolType.CLIPBOARD:
46+
(currentTool as ClipboardTool).paint(canvas, size);
4347
case ToolType.TEXT:
4448
(currentTool as TextTool).drawGuides(canvas, ref.read(paintProvider));
4549
break;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import 'dart:ui';
2+
3+
import 'package:json_annotation/json_annotation.dart';
4+
5+
class RectConverter implements JsonConverter<Rect, Map<String, dynamic>> {
6+
const RectConverter();
7+
8+
@override
9+
Rect fromJson(Map<String, dynamic> json) {
10+
return Rect.fromLTRB(
11+
(json['left'] as num).toDouble(),
12+
(json['top'] as num).toDouble(),
13+
(json['right'] as num).toDouble(),
14+
(json['bottom'] as num).toDouble(),
15+
);
16+
}
17+
18+
@override
19+
Map<String, dynamic> toJson(Rect rect) {
20+
return <String, dynamic>{
21+
'left': rect.left,
22+
'top': rect.top,
23+
'right': rect.right,
24+
'bottom': rect.bottom,
25+
};
26+
}
27+
}

0 commit comments

Comments
 (0)