C# 从中心裁剪图片

5
我正在使用.NET(4.5) MVC(4.0) C#(5.0)开发应用程序。 我想从已有的图像生成图像缩略图。 现在的需求是,它应该从图像中心生成最大正方形部分的缩略图,而不拉伸整个图像,除非图像是正方形大小。
例如,我的原始图像大小为:578x700 我想为占位符大小生成缩略图:200x150、185x138、140x140、89x66、80x80、45x45、28x28
我已经创建了下面的代码,但没有得到精确的结果。 这是生成缩略图的核心方法。
    public string GenerateThumbnailFromImage(string imageFilePath, int thumbWidth, int thumbHeight)
    {
        try
        {
            //Check if file exist
            if (File.Exists(imageFilePath))
            {
                //bool preserveAspectRatio = true;
                string oldFilePath = imageFilePath;
                string folderPath = Path.GetDirectoryName(imageFilePath);
                string filename = Path.GetFileNameWithoutExtension(imageFilePath);

                //Rename file with thumbnail size
                filename = filename + "_" + thumbWidth.ToString() + Path.GetExtension(imageFilePath);
                imageFilePath = Path.Combine(folderPath, filename);


                using (Image image = Image.FromFile(oldFilePath))
                {
                    decimal originalWidth = image.Width;
                    decimal originalHeight = image.Height;
                    decimal requiredThumbWidth = thumbWidth;
                    decimal requiredThumbHeight = thumbHeight;
                    decimal startXPosition = 0;
                    decimal startYPosition = 0;
                    decimal screenWidth = originalWidth;
                    decimal screenHeight = originalHeight;
                    decimal ar = thumbWidth < thumbHeight 
                                     ? originalWidth / originalHeight
                                     : originalHeight / originalWidth;

                    //Define Starting Position for thumbnail generation
                    if (originalWidth > originalHeight)
                        startXPosition = (originalWidth - originalHeight) / 2;
                    else if (originalHeight > originalWidth)
                        startYPosition = (originalHeight - originalWidth) / 2;

                    if (thumbWidth>thumbHeight)
                    {
                        requiredThumbWidth = thumbWidth;
                        requiredThumbHeight = requiredThumbWidth*ar;
                    }
                    else if (thumbHeight>thumbWidth)
                    {
                        requiredThumbHeight = thumbHeight;
                        requiredThumbWidth = requiredThumbHeight*ar;
                    }
                    else
                    {
                        requiredThumbWidth = thumbWidth;
                        requiredThumbHeight = thumbWidth;
                    }

                    using (var bmp = new Bitmap((int)requiredThumbWidth, (int)requiredThumbHeight))
                    {
                        Graphics gr = Graphics.FromImage(bmp);
                        gr.SmoothingMode = SmoothingMode.HighQuality;
                        gr.CompositingQuality = CompositingQuality.HighQuality;
                        gr.InterpolationMode = InterpolationMode.High;
                        var rectDestination = new Rectangle(0, 0, (int)requiredThumbWidth, (int)requiredThumbHeight);

                        gr.DrawImage(image, rectDestination, (int)startXPosition, (int)startYPosition, (int)screenWidth, (int)screenHeight, GraphicsUnit.Pixel);
                        bmp.Save(imageFilePath);

                        return filename;
                    }
                }
            }
            return null;
        }
        catch (Exception ex)
        {
            GlobalUtil.HandleAndLogException(ex, this);
            throw ex;
        }
        finally
        {

        }
    }

“没有得到精确的结果”并不是非常具体的描述。 - Sayse
可能是重复的问题,参考 这里 - Naren
根据问题的标题,我认为他需要将图像从中心裁剪到所需的大小。 - Naren
如果他确实想要裁剪,那么他的代码离正确还有很大的距离。我只是认为这是糟糕的英语表达。 - Colin Steel
@Nilesh_Moradiya,我提供的链接解决了你的问题吗? - Naren
显示剩余4条评论
7个回答

15
你需要获取目标尺寸和实际尺寸之间的比率。将较短的一侧进行缩放,直到它与实际图像大小相接触。从中心开始裁剪并按所需大小进行缩放。
以下是代码:


 public static Image ResizeImage(Image imgToResize, Size destinationSize)
        {
            //获取原始图像的宽度和高度
            var originalWidth = imgToResize.Width;
            var originalHeight = imgToResize.Height;
//计算原始长度有多少个单位 var hRatio = (float)originalHeight/destinationSize.Height; var wRatio = (float)originalWidth/destinationSize.Width;
//获取较短的一侧 var ratio = Math.Min(hRatio, wRatio);
//计算新的宽度和高度 var hScale = Convert.ToInt32(destinationSize.Height * ratio); var wScale = Convert.ToInt32(destinationSize.Width * ratio);
//从中心开始裁剪 var startX = (originalWidth - wScale)/2; var startY = (originalHeight - hScale)/2;
//裁剪指定位置和大小的图像 var sourceRectangle = new Rectangle(startX, startY, wScale, hScale);
//新图像的大小 var bitmap = new Bitmap(destinationSize.Width, destinationSize.Height);
//填充整个位图 var destinationRectangle = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
//生成新图像 using (var g = Graphics.FromImage(bitmap)) { g.InterpolationMode = InterpolationMode.HighQualityBicubic;//设置插值模式为高质量双三次插值 g.DrawImage(imgToResize, destinationRectangle, sourceRectangle, GraphicsUnit.Pixel);//绘制图像 }
return bitmap;
}

这样来调用:


var thumbImage = ImageHelper.ResizeImage(image, new Size(45, 45));
thumbImage.Save(thumbFullPath);

这段代码调用了一个名为"ImageHelper"的图像操作类,将指定的图片调整到尺寸为45x45像素,并保存为缩略图。

似乎你只是在调整图像大小。我尝试了你的函数,它产生了与我的函数相同的结果,即只是调整了图像大小。它不应该裁剪吗? - Teilmann
不,只需尝试使用更大的图像,它会进行裁剪和缩放,以保持比例。它的效果非常好。 - Roel van Roozendaal
它运行得非常好,而且解释得很清楚。我希望我能点赞两次。 - T-moty
运行良好。感谢这个有用的解决方案。祝你工作顺利。:* - Sedat Kumcu

8

试试这个:

bool SaveCroppedImage(Image image, int targetWidth, int targetHeight, string filePath)
{
    ImageCodecInfo jpgInfo = ImageCodecInfo.GetImageEncoders().Where(codecInfo => codecInfo.MimeType == "image/jpeg").First();
    Image finalImage = image;
    System.Drawing.Bitmap bitmap = null;
    try
    {
        int left = 0;
        int top = 0;
        int srcWidth = targetWidth;
        int srcHeight = targetHeight;
        bitmap = new System.Drawing.Bitmap(targetWidth, targetHeight);
        double croppedHeightToWidth = (double)targetHeight / targetWidth;
        double croppedWidthToHeight = (double)targetWidth / targetHeight;

        if (image.Width > image.Height)
        {
            srcWidth = (int)(Math.Round(image.Height * croppedWidthToHeight));
            if (srcWidth < image.Width)
            {
                srcHeight = image.Height;
                left = (image.Width - srcWidth) / 2;
            }
            else
            {
                srcHeight = (int)Math.Round(image.Height * ((double)image.Width / srcWidth));
                srcWidth = image.Width;
                top = (image.Height - srcHeight) / 2;
            }
        }
        else
        {
            srcHeight = (int)(Math.Round(image.Width * croppedHeightToWidth));
            if (srcHeight < image.Height)
            {
                srcWidth = image.Width;
                top = (image.Height - srcHeight) / 2;
            }
            else
            {
                srcWidth = (int)Math.Round(image.Width * ((double)image.Height / srcHeight));
                srcHeight = image.Height;
                left = (image.Width - srcWidth) / 2;
            }
        }
        using (Graphics g = Graphics.FromImage(bitmap))
        {
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.PixelOffsetMode = PixelOffsetMode.HighQuality;
            g.CompositingQuality = CompositingQuality.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.DrawImage(image, new Rectangle(0, 0, bitmap.Width, bitmap.Height), new Rectangle(left, top, srcWidth, srcHeight), GraphicsUnit.Pixel);
        }
        finalImage = bitmap;
    }
    catch { }
    try
    {
        using (EncoderParameters encParams = new EncoderParameters(1))
        {
            encParams.Param[0] = new EncoderParameter(Encoder.Quality, (long)100);
            //quality should be in the range [0..100] .. 100 for max, 0 for min (0 best compression)
            finalImage.Save(filePath, jpgInfo, encParams);
            return true;
        }
    }
    catch { }
    if (bitmap != null)
    {
        bitmap.Dispose();
    }
    return false;
}

4
在你的NuGet包中,添加ImageFactory解决方案,作者是James South。里面包含所需的一切。我希望我能给James买更多的啤酒。
使用示例:
using (ImageFactory imgf = new ImageFactory(preserveExifData: true)) {
    imgf
        .Load(img)
        .Crop(rect)
        .Format(new JpegFormat { Quality = 100 })
        .Save(destination)
}

// given that :
// 'img' is your Image object, could be an Image.FromFile() object or the likes
// 'rect' is the size of your crop and that you have already did the math
// new JpegFormat { Quality = 70 } is part of the package
// and 'destination' is the destination path of your new image in your disk

1
这只回答了问题的一半。OP要求在图像中心进行裁剪。您需要先找到图像中心(坐标),然后再执行您在此处所做的操作。 - Alternatex

3

同时使用ImageFactory NuGet包时,我建议使用Resize Crop功能。

在这里可以看到此功能的示例

using ImageProcessor;
using ImageProcessor.Imaging;
using System.Drawing;
using System.IO;

public static byte[] ResizeAndCrop(byte[] image, int width, int height)
    {
        using (var ms = new MemoryStream())
        {
            using (var imgf = new ImageFactory(true))
                imgf
                    .Load(image)
                    .Resize(new ResizeLayer(new Size(width, height), ResizeMode.Crop))
                    .Save(ms);
            return ms.ToArray();
        }
    }

这将获取任何以byte[]格式表示的图像并从中心进行裁剪。

这是由ResizeLayer中的anchorPosition参数设置的,其默认值为AnchorPosition.Center。

new ResizeLayer(new Size(width, height), ResizeMode.Crop/*, AnchorPosition.Center*/)

3
public Image ScaleImage(Image image, int maxWidth, int maxHeight)
{
    var ratioX = (double)maxWidth / image.Width;
    var ratioY = (double)maxHeight / image.Height;
    var ratio = Math.Min(ratioX, ratioY);

    var newWidth = (int)(image.Width * ratio);
    var newHeight = (int)(image.Height * ratio);

    var newImage = new Bitmap(maxWidth, maxWidth);
    using (var graphics = Graphics.FromImage(newImage))
    {
        // Calculate x and y which center the image
        int y = (maxHeight/2) - newHeight / 2;
        int x = (maxWidth / 2) - newWidth / 2;

        // Draw image on x and y with newWidth and newHeight
        graphics.DrawImage(image, x, y, newWidth, newHeight);
    }

    return newImage;
}

1
做过几次,诀窍在于先适应图像的高度,然后将宽度重新缩放到你必须减少高度的比例,如果宽度仍不适合,则从宽度重复此步骤,并通过该额外比例减少新缩放的高度。这样,您就有一个始终适合的缩略图,可能在X或Y上有一些空白,但图像仍具有相同的比例,而不是被拉伸。
int originalHeight;
int originalWidth;
int imageHeight;
int imageWidth;
int requiredHeight;
int requiredWidth;
double scale;

if(originalHeight > requiredHeight)
{
    scale = requiredHeight / originalHeight;
    imageHeight = requiredHeight;
    imageWidth = originalHeight * scale;
}

if(imageWidth > requiredWidth)
{
    scale = requiredWidth / imageWidth;
    imageWidth = requiredWidth;
    imageHeight = imageHeight * scale;
}

然后使用 Graphics 对象将该 Image 绘制到具有此大小的新 Bitmap


0
我正在使用SkiaSharp。但如果你使用其他系统,也没有太大的区别。
输入widthIn和heightIn作为您所需缩略图的边长。
public async Task<string?> CropImageAndSave(string filePath, int widthIn, int heightIn)
{
    // get the image
    using SKImage sKImage = SKImage.FromEncodedData(filePath);

    // check if the given sides are not larger than the image size
    if (widthIn < sKImage.Width && heightIn < sKImage.Height)
    {
        // find the center
        int centerX = sKImage.Width / 2;
        int centerY = sKImage.Height / 2;

        // find the start points
        int startX = centerX - widthIn / 2;
        int startY = centerY - heightIn / 2;

        // crop the image
        SKImage croppedImage = sKImage.Subset(SKRectI.Create(startX, startY, widthIn, heightIn));
        using SKData sKData = croppedImage.Encode(SKEncodedImageFormat.Jpeg, 100);

        // (await) SAVE the image in your desired location

        // then
        return $"Your image is cropped; Width: {widthIn}, Height: {heightIn}";
    }
    else return null;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接