将图像裁剪成 "perfect" 正方形,就像本机照片应用一样

Cropping an image to a "perfect" square like native Photos app

在开发用于查看从我们的一台服务器获取的嵌入式图像的功能时,其中一个要求是制作一个显示所有图像的概览页面,类似于 iOS本机照片应用程序(方形图块,以 4/5 行排列,点击时会在全屏显示图像。

这已经成功实现,除了图像被变形(图像方面从未被考虑在内)。因此,他们让我设计一种裁剪图像的方法,使原始方面保持不变。

为了做到这一点,我想先调整图像的大小,直到宽度达到我想要的最大宽度 ((screenwidth - 10) / 4 - 5)。

这是我目前使用的方法:

//Images is an array/List with base64 strings
foreach (var image in Images)
{
    UIImage imageToAdd = UIImage.LoadFromData(NSData.FromArray(Convert.FromBase64String(image.Content)));

    var width = imageToAdd.Size.Width;
    var height = imageToAdd.Size.Height;

    //GlobalSupport.ScreenWidth is a static variable that contains the actual screensize
    var newWidth = (GlobalSupport.ScreenWidth - 10) / 4 - 5;

    var newHeight = height * newWidth / width;

    var widthToCrop = 0.0f;
    var heightToCrop = 0.0f;

    //If the new height is smaller than the new width, make the new height equal to the new width, and modify the new width accordingly.
    if (newHeight < newWidth)
    {
        newHeight = newWidth;
        newWidth = width * newHeight / height;

        widthToCrop = newWidth - newHeight;
    }
    //Or, if the new height is greater than the new width, just crop the height.
    else if (newHeight > newWidth)
    {
        heightToCrop = newHeight - newWidth;
    }

    UIGraphics.BeginImageContext(new SizeF(newWidth, newHeight));
    var cropRectangle = new RectangleF(-widthToCrop, -heightToCrop, newWidth, newHeight);
    imageToAdd.Draw(cropRectangle);
    imageToAdd = UIGraphics.GetImageFromCurrentImageContext();
    UIGraphics.EndImageContext();

    UIButton btnToAdd = new UIButton(new RectangleF(-widthToCrop, -heightToCrop, newWidth, newHeight));

    btnToAdd.SetBackgroundImage(imageToAdd, UIControlState.Normal);

    btnToAdd.TouchUpInside += (sender, e) =>
    {
        Task.Factory.StartNew(() =>
            {
                //Displays a loading spinner
                GlobalSupport.EnableLoadingOverlay(true, NSBundle.MainBundle.LocalizedString("txtOneMoment", "", ""));
            }).ContinueWith(task => InvokeOnMainThread(() =>
                {
                    //Navigate to the full-screen image viewer
                    this.NavigationController.PushViewController(
                        new VCDCMPhotoDetails(
                            UIImage.LoadFromData(
                                NSData.FromArray(
                                    Convert.FromBase64String(image.Content)))), true);

                    GlobalSupport.EnableLoadingOverlay(false, "");
                }));
    };

    //Add btn to the scrollable view's children
    scrollView.AddSubview(btnToAdd);
}

这个方法很好用,除了一件事:如果 new height 小于 new widthnew heightnew width 成功修改,但是之后ImageToAdd.Draw(RectangleF) ImageToAddSize 仍然具有 new width 值,而不是裁剪后的宽度(也就是说,如果我可以假设,如果我用-20 作为它的 x 值,宽度也被修改为 -20。

我不知道这样做是否正确。如果不是,那么欢迎所有帮助!如果这是正确的方法,但我遗漏了什么,请告诉我!提前致谢!

裁剪图片时,x坐标等于图片的宽度减去缩略图的最大宽度:http://i.stack.imgur.com/uNm7k.png

裁剪后,图像仍然具有原始的新宽度,而不是裁剪后的宽度:http://i.stack.imgur.com/rabeY.png

生成的图像网格:http://i.stack.imgur.com/opo2f.jpg

通过创建具有目标图像大小的图像视图,最容易实现您正在做的事情。然后将您的图像设置为它,并将内容模式设置为缩放纵横比填充并简单地创建视图的屏幕截图(您可以在网络上找到非常简单的解决方案)。

从你的问题中我看不出你是否有问题或者它是什么。但是有一件事困扰着我。行

RectangleF(-widthToCrop, -heightToCrop, newWidth, newHeight)

将按照您始终获得图像右下部分的方式裁剪它。您很可能应该将其居中:

RectangleF(-widthToCrop*0.5, -heightToCrop*0.5, newWidth, newHeight)

好的,我想我发现了你的问题。您计算的框架是您应该在其上绘制图像以将其放在正方形上的框架。但是当你开始图像上下文时,你应该将大小设置为正方形。因此,如果宽度较大,则尺寸应为 (newHeight, newHeight),否则为 (newWidth, newWidth)。按钮框架对我来说也毫无意义。为什么要使用本应仅用于重绘图像的矩形?

让我向您展示如何使用 FILL 操作裁剪图像的正确步骤。代码在 Swift 中,但移植它应该没有问题:

    public static func resampleImageToSize(image: UIImage!, size: CGSize) -> UIImage {
        let originalWidth = image.size.width
        let originalHeight = image.size.height
        let originalRatio = originalWidth/originalHeight

        let targetRatio = size.width/size.height

        var targetFrame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height)

        if originalRatio>targetRatio {
            // crop width
            let targetHeight = size.height
            let targetWidth = targetHeight * originalRatio
            targetFrame = CGRect(x: (size.width-targetWidth)*0.5, y: (size.height-targetHeight)*0.5, width: targetWidth, height: targetHeight)

        } else if originalRatio<targetRatio {
            // crop height
            let targetWidth = size.width
            let targetHeight = targetWidth / originalRatio
            targetFrame = CGRect(x: (size.width-targetWidth)*0.5, y: (size.height-targetHeight)*0.5, width: targetWidth, height: targetHeight)
        }

        UIGraphicsBeginImageContext(size)
        image.drawInRect(targetFrame)
        let outputImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return outputImage
    }

所以这是一种通用方法,它接受原始图像和 returns 具有您选择的大小的图像。在您的情况下,这是您要显示的图标的大小。这里的操作是fill,如果你需要fit图像你只需要交换<> 在两个 if 语句中。