Skip to content

Instantly share code, notes, and snippets.

@LokieVikky
Last active May 23, 2024 12:34
Show Gist options
  • Save LokieVikky/a9a2a10b705e189ed0ec4fac42905220 to your computer and use it in GitHub Desktop.
Save LokieVikky/a9a2a10b705e189ed0ec4fac42905220 to your computer and use it in GitHub Desktop.
Image Editor API
import 'dart:io';
import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:image_editor/image_editor_v2.dart';
class ImageEditor {
double imageHeight = 0.0;
double imageWidth = 0.0;
Rect? areaToCrop;
Orientation currentOrientation = Orientation.vertical;
late ui.PictureRecorder pr;
late ui.Image image;
ColorFilter? colorFilter;
late Canvas canvas;
List<double> getDynamicColorFilter(num brightness, num contrast, num midtone) {
final double b = brightness / 100.0;
final double c = contrast / 100.0 + 1.0;
final double m = midtone / 100.0;
return [
c,
0,
0,
0,
b - (c * 0.5) + m,
0,
c,
0,
0,
b - (c * 0.5) + m,
0,
0,
c,
0,
b - (c * 0.5) + m,
0,
0,
0,
1,
0,
];
}
Future<ui.Image> editFromJson(Map<String, dynamic> editParams) async {
try {
String? filePath = editParams['fileName'];
if (filePath == null) {
throw Exception('"fileName" not found');
}
File file = File(filePath);
ui.Codec codec = await ui.instantiateImageCodec(await file.readAsBytes());
ui.FrameInfo frame = await codec.getNextFrame();
image = frame.image;
imageHeight = image.height.toDouble();
imageWidth = image.width.toDouble();
pr = ui.PictureRecorder();
canvas = Canvas(pr);
ImageFrame? imageFrame;
if (editParams.containsKey('exposure')) {
Map<String, dynamic> exposure = editParams['exposure'];
bool auto = exposure['auto'] ?? false;
double brightness = (exposure['brightness'] ?? 0).toDouble();
double contrast = (exposure['contrast'] ?? 0).toDouble();
double midTone = (exposure['midtone'] ?? 0).toDouble();
colorFilter = _getColorFilter(brightness: brightness, contrast: contrast, midTone: midTone);
// colorFilter = _calculateColorFilter(brightness: 0,contrast: 0,midTone: 10);
drawImage(image, colorFilter);
await updateImage("filter");
}
if (editParams.containsKey('eraseEdges')) {
Map<String, dynamic> frame = editParams['eraseEdges'];
String? colorCode = frame['fillColor'];
Color color = getColorFromString(colorCode);
if (frame['makeAllEdgesSame'] ?? false) {
imageFrame = ImageFrame(
Paint()..color = color,
FrameInsets.all((frame['topEdge'] ?? 0).toDouble()),
getUnitsFromString(frame['units']));
} else {
imageFrame = ImageFrame(
Paint()..color = color,
FrameInsets.only(
left: (frame['leftEdge'] ?? 0).toDouble(),
bottom: (frame['bottomEdge'] ?? 0).toDouble(),
right: (frame['rightEdge'] ?? 0).toDouble(),
top: (frame['topEdge'] ?? 0).toDouble(),
),
getUnitsFromString(frame['units']),
);
}
}
if (editParams.containsKey('deleteSeletion')) {
List actions = editParams['deleteSeletion'] ?? [];
for (int i = 0; i < actions.length; i++) {
Map<String, dynamic> todo = actions[i];
String? action = todo["option"];
if (action == null) continue;
switch (action) {
case "crop":
Rect? areaToCrop = Rect.fromLTWH(
todo['cropImageX'].toDouble(),
todo['cropImageY'].toDouble(),
todo['cropImageWidth'].toDouble(),
todo['cropImageHeight'].toDouble());
await crop(areaToCrop);
break;
case "delete":
Rect? areasToDelete = Rect.fromLTWH(todo['deleteImageX'], todo['deleteImageY'],
todo['deleteImageWidth'], todo['deleteImageHeight']);
DeleteArea deleteArea = DeleteArea(
areasToDelete, Paint()..color = getColorFromString(todo['deleteFillColor']));
await deleteSelection([deleteArea]);
break;
case "rotate":
int? angle = todo['deleteRotationAngel'];
if (angle == null) continue;
if (angle < 90 || angle > 270) continue;
await rotate(getImageRotationFromAngle(angle));
break;
case "straighten":
int? angle = todo['deleteRotationAngel'];
if (angle == null) continue;
await straighten(angle.toDouble());
break;
}
}
await drawFrame(imageFrame);
}
return pr.endRecording().toImageSync(imageWidth.toInt(), imageHeight.toInt());
// switch (currentOrientation) {
// case Orientation.vertical:
// return pr.endRecording().toImageSync(imageWidth.toInt(), imageHeight.toInt());
// case Orientation.horizontal:
// return pr.endRecording().toImageSync(imageHeight.toInt(), imageWidth.toInt());
// }
} catch (e) {
rethrow;
}
}
// Helper functions
Units getUnitsFromString(String? unit) {
if (unit == null) return Units.cm;
try {
if (unit == 'Cm') {
return Units.cm;
}
return Units.inch;
} catch (e) {
debugPrint("Error occurred while converting Frame Units, so setting to Cm");
return Units.cm;
}
}
ImageRotation getImageRotationFromAngle(int angle) {
if (angle >= 90 && angle < 180) {
return ImageRotation.rotate90;
} else if (angle >= 180 && angle < 270) {
return ImageRotation.rotate180;
} else {
return ImageRotation.rotate270;
}
}
Color getColorFromString(String? color) {
try {
if (color == null) return Colors.white;
List<String> rgba = color.split(',');
if (rgba.length != 4) return Colors.white;
return Color.fromRGBO(
int.parse(rgba[0]), int.parse(rgba[1]), int.parse(rgba[2]), double.parse(rgba[3]));
} catch (e) {
debugPrint("Error occurred while converting color code, so setting to White");
return Colors.white;
}
}
void swapOrientation(Orientation orientation) {
if (currentOrientation == orientation) {
return;
}
currentOrientation = orientation;
double temp = imageWidth;
imageWidth = imageHeight;
imageHeight = temp;
}
double _unitToPixel(Units unit, double value) {
switch (unit) {
case Units.cm:
return cmToPixel(cm: value);
case Units.inch:
return inchToPixel(inch: value);
}
}
double cmToPixel({required double cm, int dpi = 300}) => (dpi / 2.54) * cm;
double inchToPixel({required double inch, int dpi = 300}) => inch * dpi;
Offset findActualOffset(Offset offset, Size widgetSize, Size imageSize) {
return Offset(((offset.dx / widgetSize.width) * 100) * imageSize.width,
((offset.dy / widgetSize.height) * 100) * imageSize.height);
}
/// Canvas core functions
void drawImage(ui.Image image, ColorFilter? colorFilter) {
canvas.drawImage(image, Offset.zero, Paint()..colorFilter = colorFilter);
}
Future<void> deleteSelection(List<DeleteArea> areasToDelete) async {
for (DeleteArea element in areasToDelete) {
canvas.drawRect(
element.rect,
element.paint,
);
}
await updateImage("delete");
}
Future<void> drawFrame(ImageFrame? imageFrame) async {
await saveAndClearCanvas();
canvas.drawColor(Colors.white, BlendMode.color);
drawImage(image, null);
await updateImage("frame");
if (imageFrame != null) {
double leftPixels = _unitToPixel(imageFrame.unit, imageFrame.frame.left);
double topPixels = _unitToPixel(imageFrame.unit, imageFrame.frame.top);
double rightPixels = _unitToPixel(imageFrame.unit, imageFrame.frame.right);
double bottomPixels = _unitToPixel(imageFrame.unit, imageFrame.frame.bottom);
Rect leftRect = Rect.fromLTWH(0.0, 0.0, leftPixels, imageHeight);
Rect topRect = Rect.fromLTWH(0.0, 0.0, imageWidth, topPixels);
Rect rightRect = Rect.fromLTWH(imageWidth - rightPixels, 0.0, rightPixels, imageHeight);
Rect bottomRect = Rect.fromLTWH(0.0, imageHeight - bottomPixels, imageWidth, bottomPixels);
canvas.drawRect(leftRect, imageFrame.paint);
canvas.drawRect(topRect, imageFrame.paint);
canvas.drawRect(rightRect, imageFrame.paint);
canvas.drawRect(bottomRect, imageFrame.paint);
}
}
Future<void> crop(Rect? areaToCrop) async {
if (areaToCrop != null) {
// await saveAndClearCanvas();
imageHeight = areaToCrop.height;
imageWidth = areaToCrop.width;
canvas.drawImageRect(
image, areaToCrop, Rect.fromLTWH(0, 0, imageWidth, imageHeight), Paint());
// await updateImage("crop");
}
}
Future<void> rotate(ImageRotation? imageRotation) async {
if (imageRotation != null) {
int angle;
switch (imageRotation) {
case ImageRotation.rotate90:
angle = 90;
break;
case ImageRotation.rotate180:
angle = 180;
break;
case ImageRotation.rotate270:
angle = 270;
break;
}
var radians = angle * pi / 180;
final double r = sqrt(imageWidth * imageWidth + imageHeight * imageHeight) / 2;
double alpha;
double beta;
double shiftY;
double shiftX;
double translateX;
double translateY;
alpha = atan(imageHeight / imageWidth);
beta = alpha + radians;
shiftY = r * sin(beta);
shiftX = r * cos(beta);
if (angle == 90 || angle == 270) {
translateX = imageHeight / 2 - shiftX;
translateY = imageWidth / 2 - shiftY;
swapOrientation(Orientation.horizontal);
} else {
translateX = imageWidth / 2 - shiftX;
translateY = imageHeight / 2 - shiftY;
swapOrientation(Orientation.vertical);
}
// await saveAndClearCanvas();
canvas.translate(translateX, translateY);
canvas.rotate(radians);
drawImage(image, null);
await updateImage("rotate");
}
}
Future<void> straighten(double? angle) async {
if (angle == null) {
return;
}
if (angle < -45 || angle > 45) {
throw Exception("Straighten angle can only range from -45 to +45");
}
var radians = angle * pi / 180;
final double r = sqrt(imageWidth * imageWidth + imageHeight * imageHeight) / 2;
double alpha;
double beta;
double shiftY;
double shiftX;
double translateX;
double translateY;
alpha = atan(imageHeight / imageWidth);
beta = alpha + radians;
shiftY = r * sin(beta);
shiftX = r * cos(beta);
translateX = imageWidth / 2 - shiftX;
translateY = imageHeight / 2 - shiftY;
// await saveAndClearCanvas();
canvas.translate(translateX, translateY);
canvas.rotate(radians);
drawImage(image, null);
await updateImage("straighten");
}
Future<void> saveAndClearCanvas() async {
image = pr.endRecording().toImageSync(imageWidth.toInt(), imageHeight.toInt());
pr = ui.PictureRecorder();
canvas = Canvas(pr);
}
Future<void> updateImage(String action) async {
image = pr.endRecording().toImageSync(imageWidth.toInt(), imageHeight.toInt());
pr = ui.PictureRecorder();
canvas = Canvas(pr);
drawImage(image, null);
}
ColorFilter _getColorFilter({double brightness = 0, double contrast = 0, double midTone = 0}) {
return ColorFilter.matrix(_multiplyMatrices([
// _getBrightnessFilter(brightness),
// _getContrastFilter(contrast),
// _getMidToneFilter(midTone),
]));
}
List<double> _getBrightnessFilter(num value) {
double brightnessScale = value.abs() / 100.0;
brightnessScale = value >= 0 ? brightnessScale + 1.0 : 1.0 - brightnessScale;
List<double> colorMatrix = <double>[
brightnessScale,
0,
0,
0,
0,
0,
brightnessScale,
0,
0,
0,
0,
0,
brightnessScale,
0,
0,
0,
0,
0,
1,
0,
];
return colorMatrix;
}
List<double> _getContrastFilter(num value) {
double contrast = (value + 100) / 100.0;
double factor = contrast * contrast;
double offset = 0.5 * (1 - factor);
return [
factor,
0,
0,
0,
offset,
0,
factor,
0,
0,
offset,
0,
0,
factor,
0,
offset,
0,
0,
0,
1,
0,
];
}
/* /// Brightness adjustment
List<double> _getBrightnessFilter(double value) {
if (value <= 0) {
value = value * 255;
} else {
value = value * 100;
}
if (value == 0) {
return [
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
0,
1,
0,
];
}
return List<double>.from(
<double>[1, 0, 0, 0, value, 0, 1, 0, 0, value, 0, 0, 1, 0, value, 0, 0, 0, 1, 0])
.map((i) => i.toDouble())
.toList();
}
/// Contrast adjustment
List<double> _getContrastFilter(double value) {
value = value/100;
// RGBA contrast(RGBA color, num adj) {
// adj *= 255;
// double factor = (259 * (adj + 255)) / (255 * (259 - adj));
// return new RGBA(
// red: (factor * (color.red - 128) + 128),
// green: (factor * (color.green - 128) + 128),
// blue: (factor * (color.blue - 128) + 128),
// alpha: color.alpha,
// );
// }
double adj = value * 255;
double factor = (259 * (adj + 255)) / (255 * (259 - adj));
return [
factor,
0,
0,
0,
128 * (1 - factor),
0,
factor,
0,
0,
128 * (1 - factor),
0,
0,
factor,
0,
128 * (1 - factor),
0,
0,
0,
1,
0,
];
}*/
List<double> _getMidToneFilter(num midTone) {
double value = 0;
if (midTone >= 0 && midTone <= 99) {
value = value - (20 - (1.9 * midTone));
value = value < 1 ? 1 : value;
} else if (midTone >= 100 && midTone <= 200) {
value = value - (20 - (1.9 * midTone));
value = value > 60 ? 60 : value;
}
return _getBrightnessFilter(value);
}
List<double> _multiplyMatrices(List<List<double>> matrices) {
List<List<double>> result = List.generate(4, (_) => List.filled(5, 0.0));
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 5; col++) {
double sum = 0.0;
for (int k = 0; k < matrices.length; k++) {
sum += matrices[k][row * 5 + col];
}
result[row][col] = sum;
}
}
return result.expand((row) => row).toList();
}
}
// Helper Classes and enums
enum Sequence { rotate, crop, delete, straighten, drawImage, drawFrame }
enum Units { cm, inch }
enum ImageRotation {
rotate90,
rotate180,
rotate270,
}
enum Orientation { vertical, horizontal }
class DeleteArea {
Rect rect;
Paint paint;
DeleteArea(this.rect, this.paint);
}
class FrameInsets {
double left = 0.0;
double top = 0.0;
double right = 0.0;
double bottom = 0.0;
FrameInsets.all(double value)
: left = value,
top = value,
right = value,
bottom = value;
FrameInsets.only({
this.left = 0.0,
this.top = 0.0,
this.right = 0.0,
this.bottom = 0.0,
});
}
class ImageFrame {
Paint paint;
FrameInsets frame;
Units unit;
ImageFrame(this.paint, this.frame, this.unit);
}
// Usage
// Json Param Format
/*
Map<String, dynamic> params = {
"fileName":"C:\\Users\\Lokesh\\Downloads\\1678442336877edit.jpeg",
"exposure": {"auto": false, "brightness": 0, "contrast": 42, "midtone": 0},
"eraseEdges": {
"erase": false,
"makeAllEdgesSame": true,
"topEdge": 1,
"bottomEdge": 0,
"leftEdge": 0,
"rightEdge": 0,
"fillColor": "255, 99, 71, 0.5",
"units": "Inch"
},
"deleteSeletion": [
{
"cropSelectionEnabled": true,
"cropFillColor": 1,
"cropImageX": 1.6948497479951952e-13,
"cropImageY": 443.00000000000017,
"cropImageWidth": 2518.999999999999,
"cropImageHeight": 2528,
"cropRotationAngel": 0,
"option": "crop"
},
{
"deleteSelectionEnabled": true,
"deleteFillColor": "255,255,255,1",
"deleteImageX": 1014.6584938704029,
"deleteImageY": 1431.1891418563926,
"deleteImageWidth": 524.9754816112085,
"deleteImageHeight": 511.74080560420316,
"deleteRotationAngel": 0,
"option": "delete"
},
{
"deleteSelectionEnabled": true,
"deleteFillColor": "255,255,255,1",
"deleteImageX": 0,
"deleteImageY": 0,
"deleteImageWidth": 0,
"deleteImageHeight": 0,
"deleteRotationAngel": 90,
"option": "rotate"
},
{
"deleteSelectionEnabled": true,
"deleteFillColor": "255,255,255,1",
"deleteImageX": 569.091068301226,
"deleteImageY": 1619.0420315236427,
"deleteImageWidth": 202.9316987740806,
"deleteImageHeight": 710.2609457092819,
"deleteRotationAngel": 0,
"option": "delete"
},
{
"deleteSelectionEnabled": true,
"deleteFillColor": "255,255,255,1",
"deleteImageX": 0,
"deleteImageY": 0,
"deleteImageWidth": 0,
"deleteImageHeight": 0,
"deleteRotationAngel": -45,
"option": "straighten"
},
]
};
*/
/*
ImageEditor ie = ImageEditor();
ui.Image editedImage = await ie.editFromJson(params);
ByteData? byteData = await editedImage.toByteData(format: ui.ImageByteFormat.png);
writeToFile(byteData, 'C:\\Users\\Lokesh\\Documents\\edited.jpeg');
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment