so currently i\'m trying to crop and resize a picture to fit it into a specific size without losing the ratio.
a small image to show what i mean:
Had the same task for preview image in a gallery. For fixed Crop-Area (Image for SwiftUI and Canvas Rect for Kotiln) I want to crop central content of image - by maximum of on of the image's side. (see explanation below)
Here solutions for
Swift
func getImageCropped(srcImage : UIImage, sizeToCrop : CGSize) -> UIImage{
let ratioImage = Double(srcImage.cgImage!.width ) / Double(srcImage.cgImage!.height )
let ratioCrop = Double(sizeToCrop.width) / Double(sizeToCrop.height)
let cropRect: CGRect = {
if(ratioCrop > 1.0){
// crop LAND -> fit image HORIZONTALLY
let widthRatio = CGFloat(srcImage.cgImage!.width) / CGFloat(sizeToCrop.width)
var cropWidth = Int(sizeToCrop.width * widthRatio)
var cropHeight = Int(sizeToCrop.height * widthRatio)
var cropX = 0
var cropY = srcImage.cgImage!.height / 2 - cropHeight / 2
// [L1] [L2] : OK
if(ratioImage > 1.0) {
// image LAND
// [L3] : OK
if(cropHeight > srcImage.cgImage!.height) {
// [L4] : Crop-Area exceeds Image-Area > change crop orientation to PORTrait
let heightRatio = CGFloat(srcImage.cgImage!.height) / CGFloat(sizeToCrop.height)
cropWidth = Int(sizeToCrop.width * heightRatio)
cropHeight = Int(sizeToCrop.height * heightRatio)
cropX = srcImage.cgImage!.width / 2 - cropWidth / 2
cropY = 0
}
}
return CGRect(x: cropX, y: cropY, width: cropWidth, height: cropHeight)
}
else if(ratioCrop < 1.0){
// crop PORT -> fit image VERTICALLY
let heightRatio = CGFloat(srcImage.cgImage!.height) / CGFloat(sizeToCrop.height)
var cropWidth = Int(sizeToCrop.width * heightRatio)
var cropHeight = Int(sizeToCrop.height * heightRatio)
var cropX = srcImage.cgImage!.width / 2 - cropWidth / 2
var cropY = 0
// [P1] [P2] : OK
if(ratioImage < 1.0) {
// // image PORT
// [P3] : OK
if(cropWidth > srcImage.cgImage!.width) {
// [L4] : Crop-Area exceeds Image-Area > change crop orientation to LANDscape
let widthRatio = CGFloat(srcImage.cgImage!.width) / CGFloat(sizeToCrop.width)
cropWidth = Int(sizeToCrop.width * widthRatio)
cropHeight = Int(sizeToCrop.height * widthRatio)
cropX = 0
cropY = srcImage.cgImage!.height / 2 - cropHeight / 2
}
}
return CGRect(x: cropX, y: cropY, width: cropWidth, height: cropHeight)
}
else {
// CROP CENTER SQUARE
var fitSide = 0
var cropX = 0
var cropY = 0
if (ratioImage > 1.0){
// crop LAND -> fit image HORIZONTALLY !!!!!!
fitSide = srcImage.cgImage!.height
cropX = srcImage.cgImage!.width / 2 - fitSide / 2
}
else if (ratioImage < 1.0){
// crop PORT -> fit image VERTICALLY
fitSide = srcImage.cgImage!.width
cropY = srcImage.cgImage!.height / 2 - fitSide / 2
}
else{
// ImageAre and GropArea are square both
fitSide = srcImage.cgImage!.width
}
return CGRect(x: cropX, y: cropY, width: fitSide, height: fitSide)
}
}()
let imageRef = srcImage.cgImage!.cropping(to: cropRect)
let cropped : UIImage = UIImage(cgImage: imageRef!, scale: 0, orientation: srcImage.imageOrientation)
return cropped
}
and
Kotlin
data class RectCrop(val x: Int, val y: Int, val width: Int, val height: Int)
fun getImageCroppedShort(srcBitmap: Bitmap, sizeToCrop: Size):Bitmap {
val ratioImage = srcBitmap.width.toFloat() / srcBitmap.height.toFloat()
val ratioCrop = sizeToCrop.width.toFloat() / sizeToCrop.height.toFloat()
// 1. choose fit size
val cropRect: RectCrop =
if(ratioCrop > 1.0){
// crop LAND
val widthRatio = srcBitmap.width.toFloat() / sizeToCrop.width.toFloat()
var cropWidth = (sizeToCrop.width * widthRatio).toInt()
var cropHeight= (sizeToCrop.height * widthRatio).toInt()
var cropX = 0
var cropY = srcBitmap.height / 2 - cropHeight / 2
if(ratioImage > 1.0) {
// image LAND
if(cropHeight > srcBitmap.height) {
val heightRatio = srcBitmap.height.toFloat() / sizeToCrop.height.toFloat()
cropWidth = (sizeToCrop.width * heightRatio).toInt()
cropHeight = (sizeToCrop.height * heightRatio).toInt()
cropX = srcBitmap.width / 2 - cropWidth / 2
cropY = 0
}
}
RectCrop(cropX, cropY, cropWidth, cropHeight)
}
else if(ratioCrop < 1.0){
// crop PORT
val heightRatio = srcBitmap.height.toFloat() / sizeToCrop.height.toFloat()
var cropWidth = (sizeToCrop.width * heightRatio).toInt()
var cropHeight= (sizeToCrop.height * heightRatio).toInt()
var cropX = srcBitmap.width / 2 - cropWidth / 2
var cropY = 0
if(ratioImage < 1.0) {
// image PORT
if(cropWidth > srcBitmap.width) {
val widthRatio = srcBitmap.width.toFloat() / sizeToCrop.width.toFloat()
cropWidth = (sizeToCrop.width * widthRatio).toInt()
cropHeight = (sizeToCrop.height * widthRatio).toInt()
cropX = 0
cropY = srcBitmap.height / 2 - cropHeight / 2
}
}
RectCrop(cropX, cropY, cropWidth, cropHeight)
}
else {
// CROP CENTER SQUARE
var fitSide = 0
var cropX = 0
var cropY = 0
if (ratioImage > 1.0){
fitSide = srcBitmap.height
cropX = srcBitmap.width/ 2 - fitSide / 2
}
else if (ratioImage < 1.0){
fitSide = srcBitmap.width
cropY = srcBitmap.height / 2 - fitSide / 2
}
else{
fitSide = srcBitmap.width
}
RectCrop(cropX, cropY, fitSide, fitSide)
}
return Bitmap.createBitmap(
srcBitmap,
cropRect.x,
cropRect.y,
cropRect.width,
cropRect.height)
}
An explanation for those who want to understand algorithm. The main idea - we should stretch a Crop-Area proportionally(!) until the biggest side of it fits image. But there is one unacceptable case (L4 and P4) when Crop-Area exceeds Image-Area. So here we have only one way - change fit direction and stretch Crop-Area to the other side
On Scheme I didn't centering of crop (for better understanding idea), but both of this solutions do this. Here result of getImageCropped:
This SwiftUI code provides images above to test:
var body: some View {
// IMAGE LAND
let ORIG_NAME = "image_land.jpg"
let ORIG_W = 400.0
let ORIG_H = 265.0
// > crop Land
let cropW = 400.0
let cropH = 200.0
// > crop Port
// let cropW = 50.0
// let cropH = 265.0
// > crop Center Square
// let cropW = 265.0
// let cropH = 265.0
// IMAGE PORT
// let ORIG_NAME = "image_port.jpg"
// let ORIG_W = 350.0
// let ORIG_H = 500.0
// > crop Land
// let cropW = 350.0
// let cropH = 410.0
// > crop Port
// let cropW = 190.0
// let cropH = 500.0
// > crop Center Square
// let cropW = 350.0
// let cropH = 350.0
let imageOriginal = UIImage(named: ORIG_NAME)!
let imageCropped = self.getImageCroppedShort(srcImage: imageOriginal, sizeToCrop: CGSize(width: cropW, height: cropH))
return VStack{
HStack{
Text("ImageArea \nW:\(Int(ORIG_W)) \nH:\(Int(ORIG_H))").font(.body)
Text("CropArea \nW:\(Int(cropW)) \nH:\(Int(cropH))").font(.body)
}
ZStack{
Image(uiImage: imageOriginal)
.resizable()
.opacity(0.4)
Image(uiImage: imageCropped)
.resizable()
.frame(width: CGFloat(cropW), height: CGFloat(cropH))
}
.frame(width: CGFloat(ORIG_W), height: CGFloat(ORIG_H))
.background(Color.black)
}
}
Kotlin solution works identically. Trust me)