Skip to content

Commit

Permalink
Respect original format by default (#171)
Browse files Browse the repository at this point in the history
* respect original format by default

* fix
  • Loading branch information
chooyan-eng authored Dec 11, 2024
1 parent c824703 commit bbcd1f9
Show file tree
Hide file tree
Showing 10 changed files with 260 additions and 94 deletions.
54 changes: 54 additions & 0 deletions example/lib/my_cropper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'dart:ui';

import 'package:crop_your_image/crop_your_image.dart';
import 'package:image/image.dart' as image hide ImageFormat;

class MyImageCropper extends ImageCropper<image.Image> {
const MyImageCropper();
@override
RectValidator<image.Image> get rectValidator => defaultRectValidator;

@override
RectCropper<image.Image> get rectCropper => _rectCropper;

@override
CircleCropper<image.Image> get circleCropper => _circleCropper;
}

final RectCropper<image.Image> _rectCropper = (
image.Image original, {
required Offset topLeft,
required Size size,
required ImageFormat? outputFormat,
}) {
/// crop image with low quality
return image.encodeJpg(
quality: 10,
image.copyCrop(
original,
x: topLeft.dx.toInt(),
y: topLeft.dy.toInt(),
width: size.width.toInt(),
height: size.height.toInt(),
),
);
};

final CircleCropper<image.Image> _circleCropper = (
image.Image original, {
required Offset center,
required double radius,
required ImageFormat? outputFormat,
}) {
/// crop image with low quality
/// note: jpg can't cropped circle
return image.encodeJpg(
quality: 10,
image.copyCropCircle(
original,
centerX: center.dx.toInt(),
centerY: center.dy.toInt(),
radius: radius.toInt(),
),
);
};
9 changes: 4 additions & 5 deletions lib/crop_your_image.dart
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import 'package:crop_your_image/src/logic/cropper/image_image_cropper.dart';
import 'package:crop_your_image/src/logic/format_detector/format_detector.dart';
import 'package:crop_your_image/src/logic/cropper/legacy_image_image_cropper.dart';
import 'package:crop_your_image/src/logic/format_detector/default_format_detector.dart';
import 'package:crop_your_image/src/logic/parser/image_image_parser.dart';

export 'src/widget/widget.dart';
export 'src/logic/logic.dart';

final defaultImageParser = imageImageParser;

// TODO(chooyan-eng): implement format detector if possible
const FormatDetector? defaultFormatDetector = null;

final defaultFormatDetector = imageFormatDetector;
const defaultImageCropper = ImageImageCropper();
const legacyImageCropper = LegacyImageImageCropper();
25 changes: 25 additions & 0 deletions lib/src/logic/cropper/default_rect_validator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'dart:ui';

import 'package:crop_your_image/crop_your_image.dart';
import 'package:crop_your_image/src/logic/cropper/errors.dart';
import 'package:image/image.dart';

/// default implementation of [RectValidator]
/// this checks if the rect is inside the image, not negative, and not negative size
final RectValidator<Image> defaultRectValidator =
(Image original, Offset topLeft, Offset bottomRight) {
if (topLeft.dx.toInt().isNegative ||
topLeft.dy.toInt().isNegative ||
bottomRight.dx.toInt().isNegative ||
bottomRight.dy.toInt().isNegative ||
topLeft.dx.toInt() > original.width ||
topLeft.dy.toInt() > original.height ||
bottomRight.dx.toInt() > original.width ||
bottomRight.dy.toInt() > original.height) {
return InvalidRectError(topLeft: topLeft, bottomRight: bottomRight);
}
if (topLeft.dx > bottomRight.dx || topLeft.dy > bottomRight.dy) {
return NegativeSizeError(topLeft: topLeft, bottomRight: bottomRight);
}
return null;
};
55 changes: 52 additions & 3 deletions lib/src/logic/cropper/image_cropper.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui';

Expand All @@ -13,7 +14,55 @@ abstract class ImageCropper<T> {
required T original,
required Offset topLeft,
required Offset bottomRight,
ImageFormat outputFormat,
ImageShape shape,
});
ImageFormat? outputFormat,
ImageShape shape = ImageShape.rectangle,
}) async {
final error = rectValidator(original, topLeft, bottomRight);
if (error != null) {
throw error;
}

final size = Size(
bottomRight.dx - topLeft.dx,
bottomRight.dy - topLeft.dy,
);

return switch (shape) {
ImageShape.rectangle => rectCropper(
original,
topLeft: topLeft,
size: size,
outputFormat: outputFormat,
),
ImageShape.circle => circleCropper(
original,
center: Offset(
topLeft.dx + size.width / 2,
topLeft.dy + size.height / 2,
),
radius: min(size.width, size.height) / 2,
outputFormat: outputFormat,
),
};
}

RectValidator<T> get rectValidator;
RectCropper<T> get rectCropper;
CircleCropper<T> get circleCropper;
}

typedef RectValidator<T> = Error? Function(
T original, Offset topLeft, Offset bottomRight);
typedef RectCropper<T> = Uint8List Function(
T original, {
required Offset topLeft,
required Size size,
required ImageFormat? outputFormat,
});

typedef CircleCropper<T> = Uint8List Function(
T original, {
required Offset center,
required double radius,
required ImageFormat? outputFormat,
});
101 changes: 36 additions & 65 deletions lib/src/logic/cropper/image_image_cropper.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
import 'dart:async';
import 'dart:math';
import 'dart:typed_data';
import 'dart:ui';

import 'package:crop_your_image/src/logic/cropper/errors.dart';
import 'package:crop_your_image/src/logic/cropper/image_cropper.dart';
import 'package:crop_your_image/src/logic/format_detector/format.dart';
import 'package:crop_your_image/src/logic/shape.dart';
import 'package:crop_your_image/crop_your_image.dart';

import 'package:image/image.dart' hide ImageFormat;

Expand All @@ -15,82 +10,58 @@ class ImageImageCropper extends ImageCropper<Image> {
const ImageImageCropper();

@override
FutureOr<Uint8List> call({
required Image original,
required Offset topLeft,
required Offset bottomRight,
ImageFormat outputFormat = ImageFormat.jpeg,
ImageShape shape = ImageShape.rectangle,
}) {
if (topLeft.dx.toInt().isNegative ||
topLeft.dy.toInt().isNegative ||
bottomRight.dx.toInt().isNegative ||
bottomRight.dy.toInt().isNegative ||
topLeft.dx.toInt() > original.width ||
topLeft.dy.toInt() > original.height ||
bottomRight.dx.toInt() > original.width ||
bottomRight.dy.toInt() > original.height) {
throw InvalidRectError(topLeft: topLeft, bottomRight: bottomRight);
}
if (topLeft.dx > bottomRight.dx || topLeft.dy > bottomRight.dy) {
throw NegativeSizeError(topLeft: topLeft, bottomRight: bottomRight);
}
RectCropper<Image> get rectCropper => defaultRectCropper;

final function = switch (shape) {
ImageShape.rectangle => _doCrop,
ImageShape.circle => _doCropCircle,
};
@override
CircleCropper<Image> get circleCropper => defaultCircleCropper;

return function(
original,
topLeft: topLeft,
size: Size(
bottomRight.dx - topLeft.dx,
bottomRight.dy - topLeft.dy,
),
);
}
@override
RectValidator<Image> get rectValidator => defaultRectValidator;
}

/// process cropping image.
/// this method is supposed to be called only via compute()
Uint8List _doCrop(
final RectCropper<Image> defaultRectCropper = (
Image original, {
required Offset topLeft,
required Size size,
required ImageFormat? outputFormat,
}) {
return Uint8List.fromList(
encodePng(
copyCrop(
original,
x: topLeft.dx.toInt(),
y: topLeft.dy.toInt(),
width: size.width.toInt(),
height: size.height.toInt(),
),
return _findCropFunc(outputFormat)(
copyCrop(
original,
x: topLeft.dx.toInt(),
y: topLeft.dy.toInt(),
width: size.width.toInt(),
height: size.height.toInt(),
),
);
}
};

/// process cropping image with circle shape.
/// this method is supposed to be called only via compute()
Uint8List _doCropCircle(
final CircleCropper<Image> defaultCircleCropper = (
Image original, {
required Offset topLeft,
required Size size,
required Offset center,
required double radius,
required ImageFormat? outputFormat,
}) {
final center = Point(
topLeft.dx + size.width / 2,
topLeft.dy + size.height / 2,
);
return Uint8List.fromList(
encodePng(
copyCropCircle(
original,
centerX: center.xi,
centerY: center.yi,
radius: min(size.width, size.height) ~/ 2,
),
return _findCropFunc(outputFormat)(
copyCropCircle(
original,
centerX: center.dx.toInt(),
centerY: center.dy.toInt(),
radius: radius.toInt(),
),
);
};

Uint8List Function(Image) _findCropFunc(ImageFormat? outputFormat) {
return switch (outputFormat) {
ImageFormat.bmp => encodeBmp,
ImageFormat.ico => encodeIco,
ImageFormat.jpeg => encodeJpg,
ImageFormat.png => encodePng,
_ => encodePng,
};
}
58 changes: 58 additions & 0 deletions lib/src/logic/cropper/legacy_image_image_cropper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'dart:ui';

import 'package:crop_your_image/crop_your_image.dart';

import 'package:image/image.dart' hide ImageFormat;

/// an implementation of [ImageCropper] using image package
/// this implementation is legacy that behaves the same as the version 1.1.0 or earlier
/// meaning that it doesn't respect the outputFormat and always encode result as png
class LegacyImageImageCropper extends ImageCropper<Image> {
const LegacyImageImageCropper();

@override
RectCropper<Image> get rectCropper => legacyRectCropper;

@override
CircleCropper<Image> get circleCropper => legacyCircleCropper;

@override
RectValidator<Image> get rectValidator => defaultRectValidator;
}

/// process cropping image.
/// this method is supposed to be called only via compute()
final RectCropper<Image> legacyRectCropper = (
Image original, {
required Offset topLeft,
required Size size,
required ImageFormat? outputFormat,
}) {
return encodePng(
copyCrop(
original,
x: topLeft.dx.toInt(),
y: topLeft.dy.toInt(),
width: size.width.toInt(),
height: size.height.toInt(),
),
);
};

/// process cropping image with circle shape.
/// this method is supposed to be called only via compute()
final CircleCropper<Image> legacyCircleCropper = (
Image original, {
required Offset center,
required double radius,
required ImageFormat? outputFormat,
}) {
return encodePng(
copyCropCircle(
original,
centerX: center.dx.toInt(),
centerY: center.dy.toInt(),
radius: radius.toInt(),
),
);
};
17 changes: 17 additions & 0 deletions lib/src/logic/format_detector/default_format_detector.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'dart:typed_data';

import 'package:crop_your_image/crop_your_image.dart';
import 'package:image/image.dart' as img;

final FormatDetector imageFormatDetector = (Uint8List data) {
final format = img.findFormatForData(data);

return switch (format) {
img.ImageFormat.png => ImageFormat.png,
img.ImageFormat.jpg => ImageFormat.jpeg,
img.ImageFormat.webp => ImageFormat.webp,
img.ImageFormat.bmp => ImageFormat.bmp,
img.ImageFormat.ico => ImageFormat.ico,
_ => ImageFormat.png,
};
};
1 change: 1 addition & 0 deletions lib/src/logic/logic.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export 'cropper/image_cropper.dart';
export 'cropper/default_rect_validator.dart';
export 'format_detector/format_detector.dart';
export 'format_detector/format.dart';
export 'parser/image_parser.dart';
Expand Down
Loading

0 comments on commit bbcd1f9

Please sign in to comment.