[mob][photos] When cropping a face from an image, make the image a square and add some buffer around it

This commit is contained in:
ashilkn 2024-04-27 09:59:16 +05:30
parent caa72ba830
commit 8b236cde09
2 changed files with 87 additions and 4 deletions

View file

@ -41,3 +41,23 @@ class FaceBox {
'height': height,
};
}
/// Bounding box of a face.
///
/// [xMin] and [yMin] are the coordinates of the top left corner of the box, and
/// [width] and [height] are the width and height of the box.
///
/// One unit is equal to one pixel in the original image.
class FaceBoxImage {
final int xMin;
final int yMin;
final int width;
final int height;
FaceBoxImage({
required this.xMin,
required this.yMin,
required this.width,
required this.height,
});
}

View file

@ -1,3 +1,4 @@
import "dart:math";
import "dart:typed_data";
import "package:computer/computer.dart";
@ -8,6 +9,7 @@ import "package:photos/face/model/box.dart";
final _logger = Logger("FaceUtil");
final _computer = Computer.shared();
const _faceImageBufferFactor = 0.2;
///Convert img.Image to ui.Image and use RawImage to display.
Future<List<img.Image>> generateImgFaceThumbnails(
@ -74,16 +76,77 @@ Future<img.Image> decodeToImgImage(String imagePath) async {
/// Returns an Image from 'package:image/image.dart'
img.Image cropFaceBoxFromImage(img.Image image, FaceBox faceBox) {
final squareFaceBox = _getSquareFaceBoxImage(image, faceBox);
final squareFaceBoxWithBuffer =
_addBufferAroundFaceBox(squareFaceBox, _faceImageBufferFactor);
return img.copyCrop(
image,
x: (image.width * faceBox.xMin).round(),
y: (image.height * faceBox.yMin).round(),
width: (image.width * faceBox.width).round(),
height: (image.height * faceBox.height).round(),
x: squareFaceBoxWithBuffer.xMin,
y: squareFaceBoxWithBuffer.yMin,
width: squareFaceBoxWithBuffer.width,
height: squareFaceBoxWithBuffer.height,
antialias: false,
);
}
/// Returns a square face box image from the original image with
/// side length equal to the maximum of the width and height of the face box in
/// the OG image.
FaceBoxImage _getSquareFaceBoxImage(img.Image image, FaceBox faceBox) {
final width = (image.width * faceBox.width).round();
final height = (image.height * faceBox.height).round();
final side = max(width, height);
final xImage = (image.width * faceBox.xMin).round();
final yImage = (image.height * faceBox.yMin).round();
if (height >= width) {
final xImageAdj = (xImage - (height - width) / 2).round();
return FaceBoxImage(
xMin: xImageAdj,
yMin: yImage,
width: side,
height: side,
);
} else {
final yImageAdj = (yImage - (width - height) / 2).round();
return FaceBoxImage(
xMin: xImage,
yMin: yImageAdj,
width: side,
height: side,
);
}
}
///To add some buffer around the face box so that the face isn't cropped
///too close to the face.
FaceBoxImage _addBufferAroundFaceBox(
FaceBoxImage faceBoxImage,
double bufferFactor,
) {
final heightBuffer = faceBoxImage.height * bufferFactor;
final widthBuffer = faceBoxImage.width * bufferFactor;
final xMinWithBuffer = faceBoxImage.xMin - widthBuffer;
final yMinWithBuffer = faceBoxImage.yMin - heightBuffer;
final widthWithBuffer = faceBoxImage.width + 2 * widthBuffer;
final heightWithBuffer = faceBoxImage.height + 2 * heightBuffer;
//Do not add buffer if the top left edge of the image is out of bounds
//after adding the buffer.
if (xMinWithBuffer < 0 || yMinWithBuffer < 0) {
return faceBoxImage;
}
//Another similar case that can be handled is when the bottom right edge
//of the image is out of bounds after adding the buffer. But the
//the visual difference is not as significant as when the top left edge
//is out of bounds, so we are not handling that case.
return FaceBoxImage(
xMin: xMinWithBuffer.round(),
yMin: yMinWithBuffer.round(),
width: widthWithBuffer.round(),
height: heightWithBuffer.round(),
);
}
List<Uint8List> _encodeImagesToJpg(Map args) {
final images = args["images"] as List<img.Image>;
return images.map((img.Image image) => img.encodeJpg(image)).toList();