Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions lib/core/commands/command_factory/command_factory.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import 'dart:typed_data';
import 'dart:ui' as ui;

import 'package:flutter/material.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/delete_region_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/text_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart';
Expand Down Expand Up @@ -55,6 +60,21 @@ class CommandFactory {
angle,
);

ClipboardCommand createClipboardCommand(
Paint paint,
Uint8List imageData,
ui.Offset offset,
double scale,
double rotation,
) =>
ClipboardCommand(
paint,
imageData,
offset,
scale,
rotation,
);

TextCommand createTextCommand(
Offset point,
String text,
Expand Down Expand Up @@ -108,4 +128,12 @@ class CommandFactory {
SprayCommand createSprayCommand(List<Offset> points, Paint paint) {
return SprayCommand(points, paint);
}

DeleteRegionCommand createDeleteRegionCommand(
ui.Rect region,
) =>
DeleteRegionCommand(
Paint(),
region,
);
}
8 changes: 8 additions & 0 deletions lib/core/commands/command_implementation/command.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'package:equatable/equatable.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/delete_region_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/path_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/shape/ellipse_shape_command.dart';
Expand All @@ -13,6 +15,8 @@ abstract class Command with EquatableMixin {

Map<String, dynamic> toJson();

Future<void> prepareForRuntime() async {}

factory Command.fromJson(Map<String, dynamic> json) {
String type = json['type'] as String;
switch (type) {
Expand All @@ -24,6 +28,10 @@ abstract class Command with EquatableMixin {
return SquareShapeCommand.fromJson(json);
case SerializerType.ELLIPSE_SHAPE_COMMAND:
return EllipseShapeCommand.fromJson(json);
case SerializerType.CLIPBOARD_COMMAND:
return ClipboardCommand.fromJson(json);
case SerializerType.DELETE_REGION_COMMAND:
return DeleteRegionCommand.fromJson(json);
case SerializerType.TEXT_COMMAND:
return TextCommand.fromJson(json);
case SerializerType.HEART_SHAPE_COMMAND:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// ignore_for_file: must_be_immutable

import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart';
import 'package:paintroid/core/json_serialization/converter/offset_converter.dart';
import 'package:paintroid/core/json_serialization/converter/paint_converter.dart';
import 'package:paintroid/core/json_serialization/converter/uint8list_base64_converter.dart';
import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart';
import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart';
import 'package:paintroid/core/models/loggable_mixin.dart';
import 'package:toast/toast.dart';

part 'clipboard_command.g.dart';

@JsonSerializable()
class ClipboardCommand extends GraphicCommand with LoggableMixin {
@Uint8ListBase64Converter()
final Uint8List imageData;
@OffsetConverter()
final ui.Offset offset;
final double scale;
final double rotation;

final int version;
final String type;

@JsonKey(includeFromJson: false, includeToJson: false)
ui.Image? _runtimeImage;

ClipboardCommand(
super.paint,
this.imageData,
this.offset,
this.scale,
this.rotation, {
int? version,
this.type = SerializerType.CLIPBOARD_COMMAND,
}) : version = version ??
VersionStrategyManager.strategy.getClipboardCommandVersion();

@override
Future<void> prepareForRuntime() async {
if (_runtimeImage == null && imageData.isNotEmpty) {
try {
final buffer = await ui.ImmutableBuffer.fromUint8List(imageData);
final descriptor = await ui.ImageDescriptor.encoded(buffer);
final codec = await descriptor.instantiateCodec();
final frameInfo = await codec.getNextFrame();
_runtimeImage = frameInfo.image;
} catch (e, _) {
Toast.show(
'Error: $e',
duration: Toast.lengthShort,
gravity: Toast.bottom,
);
_runtimeImage = null;
}
}
}

@override
void call(ui.Canvas canvas) {
if (_runtimeImage == null) {
Toast.show(
'ClipboardCommand.call: _runtimeImage is null. Cannot draw.',
duration: Toast.lengthShort,
gravity: Toast.bottom,
);

return;
}

canvas.save();
canvas.translate(offset.dx, offset.dy);
canvas.rotate(rotation);
canvas.scale(scale);

final imageWidth = _runtimeImage!.width.toDouble();
final imageHeight = _runtimeImage!.height.toDouble();

final src = ui.Rect.fromLTWH(0, 0, imageWidth, imageHeight);
final dst = ui.Rect.fromLTWH(
-imageWidth / 2,
-imageHeight / 2,
imageWidth,
imageHeight,
);
final imagePaint = ui.Paint()..filterQuality = ui.FilterQuality.high;
canvas.drawImageRect(_runtimeImage!, src, dst, imagePaint);
canvas.restore();
}

@override
List<Object?> get props => [
paint,
imageData,
offset,
scale,
rotation,
version,
type,
];

@override
Map<String, dynamic> toJson() => _$ClipboardCommandToJson(this);

factory ClipboardCommand.fromJson(Map<String, dynamic> json) {
final int version = json['version'] as int? ?? Version.v1;
switch (version) {
case Version.v1:
return _$ClipboardCommandFromJson(json);
case Version.v2:
// For different versions of ClipboardCommand the deserialization
// has to be implemented manually.
// Autogenerated code can only be used for one version
default:
return _$ClipboardCommandFromJson(json);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// ignore_for_file: must_be_immutable

import 'dart:ui' as ui;

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart';
import 'package:paintroid/core/json_serialization/converter/paint_converter.dart';
import 'package:paintroid/core/json_serialization/converter/rect_converter.dart';
import 'package:paintroid/core/json_serialization/versioning/serializer_version.dart';
import 'package:paintroid/core/json_serialization/versioning/version_strategy.dart';

part 'delete_region_command.g.dart';

@JsonSerializable()
class DeleteRegionCommand extends GraphicCommand {
@RectConverter()
final ui.Rect region;

final int version;
final String type;

DeleteRegionCommand(
super.paint,
this.region, {
int? version,
this.type = SerializerType.DELETE_REGION_COMMAND,
}) : version = version ??
VersionStrategyManager.strategy.getDeleteRegionCommandVersion();

@override
void call(ui.Canvas canvas) {
final clearPaint = ui.Paint()
..blendMode = ui.BlendMode.clear
..style = ui.PaintingStyle.fill;
canvas.drawRect(region, clearPaint);
}

@override
List<Object?> get props => [
paint,
region,
version,
type,
];

@override
Map<String, dynamic> toJson() => _$DeleteRegionCommandToJson(this);

factory DeleteRegionCommand.fromJson(Map<String, dynamic> json) {
final int version = json['version'] as int? ?? Version.v1;
switch (version) {
case Version.v1:
return _$DeleteRegionCommandFromJson(json);
case Version.v2:
// For different versions of DeleteRegionCommand the deserialization
// has to be implemented manually.
// Autogenerated code can only be used for one version
default:
return _$DeleteRegionCommandFromJson(json);
}

}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions lib/core/commands/command_manager/command_manager.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:ui';

import 'package:paintroid/core/commands/command_implementation/command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/clipboard_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/text_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/graphic_command.dart';
import 'package:paintroid/core/commands/command_implementation/graphic/line_command.dart';
Expand Down Expand Up @@ -116,6 +117,8 @@ class CommandManager {
return ToolData.SHAPES;
} else if (command.runtimeType == EllipseShapeCommand) {
return ToolData.SHAPES;
} else if (command.runtimeType == ClipboardCommand) {
return ToolData.CLIPBOARD;
} else if (command.runtimeType == TextCommand) {
return ToolData.TEXT;
} else if (command.runtimeType == SprayCommand) {
Expand Down
6 changes: 5 additions & 1 deletion lib/core/commands/command_painter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:paintroid/core/enums/tool_types.dart';
import 'package:paintroid/core/providers/state/canvas_state_provider.dart';
import 'package:paintroid/core/providers/state/paint_provider.dart';
import 'package:paintroid/core/providers/state/toolbox_state_provider.dart';
import 'package:paintroid/core/tools/implementation/clipboard_tool.dart';
import 'package:paintroid/core/tools/implementation/shapes_tool.dart';
import 'package:paintroid/core/tools/implementation/text_tool.dart';
import 'package:paintroid/core/tools/implementation/brush_tool.dart';
Expand All @@ -28,7 +29,8 @@ class CommandPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
if (currentTool.type != ToolType.SHAPES &&
currentTool.type != ToolType.TEXT) {
currentTool.type != ToolType.TEXT &&
currentTool.type != ToolType.CLIPBOARD) {
canvas.clipRect(Rect.fromLTWH(0, 0, size.width, size.height));
}
switch (currentTool.type) {
Expand All @@ -40,6 +42,8 @@ class CommandPainter extends CustomPainter {
..drawShape(canvas, ref.read(paintProvider))
..drawGuides(canvas);
break;
case ToolType.CLIPBOARD:
(currentTool as ClipboardTool).paint(canvas, size);
case ToolType.TEXT:
(currentTool as TextTool).drawGuides(canvas, ref.read(paintProvider));
break;
Expand Down
27 changes: 27 additions & 0 deletions lib/core/json_serialization/converter/rect_converter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import 'dart:ui';

import 'package:json_annotation/json_annotation.dart';

class RectConverter implements JsonConverter<Rect, Map<String, dynamic>> {
const RectConverter();

@override
Rect fromJson(Map<String, dynamic> json) {
return Rect.fromLTRB(
(json['left'] as num).toDouble(),
(json['top'] as num).toDouble(),
(json['right'] as num).toDouble(),
(json['bottom'] as num).toDouble(),
);
}

@override
Map<String, dynamic> toJson(Rect rect) {
return <String, dynamic>{
'left': rect.left,
'top': rect.top,
'right': rect.right,
'bottom': rect.bottom,
};
}
}
Loading