I'm hoping writing this out may help with my issue. I have images that contain faces, and I want to recrop these images, respecting aspect ratio, while maintaining as much of the face as possible.
I want to crop the image to an arbitrary rectangle, which could be tall, short, square, or anything up to 4:1 aspect. As much as possible, I want to avoid cropping out a face or feature that I've specified (as an interior rectangle to the image's rect). E.G. I have an image rectangle of (0,0,500, 300) and a feature rectangle of (20, 40, 75, 75), and I want to crop it (respecting its aspect ratio) to a rectangle of (0,0, 320,200), all while minimally cropping the face rectangle.
I have a working implementation for center cropping, where I determine the larger side of the image and scale the other side based on the same ratio, setting x and y to the center of scaled w/h - target w/h.
For clarity, the signature would look something like this (in Obj-C):
(CGRect)crop:(CGSize)sourceSize toFitSize:(CGSize)fitSize withoutCroppingRect:(CGRect)featuresRect
Where sourceSize
is the original image's size, fitSize
is the shape I want to crop the image into, and featuresRect
is a rectangle, within the bounds of (0,0,sourceSize.width, sourceSize,height)
.
Perhaps this should be done as a "center crop", centered on the feature rectangle? Trying to wrap my head around what this would even mean.
Have a go with this, I think it does what you want, or at least it nudges you in the right direction. The output rect keeps the same aspect ratio as the source image rect.
Example usage:
CGRect sizedRect = [self cropSize:self.inputView.image.size
toFitSize:self.outputView.bounds.size
withoutCroppingRect:self.regionOfInterest.frame];
CGImageRef cgImage = self.inputView.image.CGImage;
CGImageRef cgCroppedImage = CGImageCreateWithImageInRect (
cgImage,
sizedRect
);
self.outputView.image = [UIImage imageWithCGImage:cgCroppedImage];
There are a number of issues that you are going to have to consider, in particular:
- Your returned rect may be larger than the fitSize, as it will not crop the featuresRect, so you may have to check for this and scale the image after applying the crop (or set your output imageView's scaleToFit appropriately).
- where are you getting your ROI frame data from? Is it relative to the original source image, or to the imageView containing the source image. If it is the latter you will need to ensure your source image is sized 1:1 with it's imageView, as the crop rect is applied to the image, not it's imageView.
- the output imageView is smaller than the input imageView. I am not sure what this does if it is larger, but it may introduce similar scaling issues.
- you will need to add divide-by-zero error checking.
- (CGRect)cropSize:(CGSize)sourceSize
toFitSize:(CGSize)fitSize
withoutCroppingRect:(CGRect)featuresRect
{
CGRect result = CGRectZero;
BOOL fitSizeIsTaller;
CGFloat sourceRatio = sourceSize.width / sourceSize.height;
CGFloat fitRatio = fitSize.width / fitSize.height;
if (sourceRatio > fitRatio)
fitSizeIsTaller = YES;
else fitSizeIsTaller = NO;
//size sourceRect to fitSize
if (fitSizeIsTaller){
result.size.width = fitSize.width;
result.size.height = result.size.width / sourceRatio;
} else {
result.size.height = fitSize.height;
result.size.width = result.size.height * sourceRatio;
}
//make sure it is at least as large as fitSize
if (result.size.height < featuresRect.size.height) {
result.size.height = featuresRect.size.height;
result.size.width = result.size.height * sourceRatio;
}
if (result.size.width < featuresRect.size.width) {
result.size.width = featuresRect.size.width;
result.size.height = result.size.width / sourceRatio;
}
//locate resultRect in center
result.origin.x = (sourceSize.width - result.size.width )/2;
result.origin.y = (sourceSize.height - result.size.height)/2;
//shift origin of result to make sure it includes ROI
if (featuresRect.origin.x < result.origin.x ) //shift right?
result.origin.x = featuresRect.origin.x;
else
if ((featuresRect.origin.x + featuresRect.size.width)
> (result.origin.x + result.size.width)) //shift left?
result.origin.x = (featuresRect.origin.x + featuresRect.size.width)
- result.size.width;
if (featuresRect.origin.y < result.origin.y ) //shift up?
result.origin.y = featuresRect.origin.y;
else
if ((featuresRect.origin.y + featuresRect.size.height)
> (result.origin.y + result.size.height)) //shift down?
result.origin.y = (featuresRect.origin.y+featuresRect.size.height)
- result.size.height;
return result;
}