Image Resize in C# - Algorith to determine resize dimensions (height and width)

后端 未结 5 1140
挽巷
挽巷 2020-12-28 23:32

I need to scale down an image that has a height or width greater than a predefined pixel value.

I wrote some code that takes a look at the original image, checks to

相关标签:
5条回答
  • 2020-12-29 00:13

    Fitting an image to new size requires two operations:

    1. Resize - resize the source image to fit exactly one dimension (width or height - the one with the smaller ratio)

    2. Crop - crop the result of the previous operation to the target dimensions

    Here is a small sample:

        private static Image Resize(Image img, int width, int height)
        {
            Bitmap b = new Bitmap(width, height);
            using (Graphics g = Graphics.FromImage((Image)b))
            {
                g.DrawImage(img, 0, 0, width, height);
            }
    
            return (Image)b;
        }
    
        public static Image Crop(Image image, int width, int height)
        {
            int cropx = image.Width > width ? image.Width / 2 - width / 2 : 0;
            int cropy = image.Height > height ? image.Height / 2 - height / 2 : 0;
            width = image.Width > width ? width : image.Width;
            height = image.Height > height ? height : image.Height;
    
            Rectangle cropRect = new Rectangle(cropx, cropy, width, height);
    
            var target = new Bitmap(cropRect.Width, cropRect.Height);
    
            using (Graphics g = Graphics.FromImage(target))
            {
                g.DrawImage(image, new Rectangle(0, 0, target.Width, target.Height), cropRect, GraphicsUnit.Pixel);
            }
    
            return target;
        }
    
        public static Image FitToSize(Image image, int width, int height)
        {
            var wratio = 1.0 * image.Width / width;
            var hratio = 1.0 * image.Height / height;
    
            int wresize;
            int hresize;
    
            if (wratio >= hratio && wratio > 1)
            {
                wresize = (int)Math.Round((double)image.Width / hratio);
                hresize = height;
    
                image = Resize(image, wresize, hresize);
                image = Crop(image, width, height);  
            }
            else if (hratio >= wratio && hratio > 1)
            {
                hresize = (int)Math.Round((double)image.Height / wratio);
                wresize = width;
    
                image = Resize(image, wresize, hresize);
                image = Crop(image, width, height);
            }
            return image;
    
        }
    
    0 讨论(0)
  • 2020-12-29 00:14

    I did something similar for Bitmaps, but idea is same:

    1. get image height and width
    2. get current screen resolution
    3. calculate aspect ratio (ASR) from image size
    
    Handle following cases:
    
    4. if ASR >=1 and image width > image height
        if image width > screen width {}
            if image height > screen height {}
            else if image width > screen width {}
        else {}
       else
        if image height > screen height {}
        else if image width > screen width {}
        else {}
    

    //SCREEN_SIZE is configurable; Defs.SCREEN_SIZE = 100; // and boolPixelAR is true;

    Try following code:

                // PERCENTAGE OF IMAGE -> TODO: Configurable? IMAZE ZOOM / SCREEN PERCENTAGE
                Double HScale = __bmp.Width;// *Defs.SCREEN_SIZE / 100;
                Double VScale = __bmp.Height;// *Defs.SCREEN_SIZE / 100;
                Double __aspectRatio;
                Double __screenRatio = _currentScreenSize.Width / _currentScreenSize.Height;
    
                // PERCENTAGE OF SCREEN
                if (!_boolPixelAR) {
                    HScale = _currentScreenSize.Width * Defs.SCREEN_SIZE / 100;
                    VScale = _currentScreenSize.Height * Defs.SCREEN_SIZE / 100;
                }
                else {
                    __aspectRatio = HScale / VScale;
                    if( __aspectRatio >= 1)
                        if (HScale >= _currentScreenSize.Width) {  // Long Edge is WIDTH. For 100%, HScale = WIDTH
                            VScale = ((VScale * _currentScreenSize.Width) / HScale) * Defs.SCREEN_SIZE / 100;
                            HScale = _currentScreenSize.Width * Defs.SCREEN_SIZE / 100;
    
                            if (VScale > _currentScreenSize.Height) {                  // Long Edge is HEIGHT. For 100%, VScale = HEIGHT
                                //__aspectRatio = VScale / HScale;
                                HScale = ((HScale * _currentScreenSize.Height) / VScale) * Defs.SCREEN_SIZE / 100;
                                VScale = _currentScreenSize.Height * Defs.SCREEN_SIZE / 100;
                            }
                        }
                        else if (VScale > _currentScreenSize.Height) {                  // Long Edge is HEIGHT. For 100%, VScale = HEIGHT
                            //__aspectRatio = VScale / HScale;
                            HScale = ((HScale * _currentScreenSize.Height) / VScale) * Defs.SCREEN_SIZE / 100;
                            VScale = _currentScreenSize.Height * Defs.SCREEN_SIZE / 100;
                        } 
                        else {
                            //Do nothing... Just set Zoom.
                            HScale = HScale * Defs.SCREEN_SIZE / 100;
                            VScale = VScale * Defs.SCREEN_SIZE / 100;
                        }
                    else 
                        if (VScale > _currentScreenSize.Height) {                  // Long Edge is HEIGHT. For 100%, VScale = HEIGHT
                            //__aspectRatio = VScale / HScale;
                            HScale = ((HScale * _currentScreenSize.Height) / VScale) * Defs.SCREEN_SIZE / 100;
                            VScale = _currentScreenSize.Height * Defs.SCREEN_SIZE / 100;
                        }
                        else if (HScale >= _currentScreenSize.Width) {  // Long Edge is WIDTH. For 100%, HScale = WIDTH
                            VScale = ((VScale * _currentScreenSize.Width) / HScale) * Defs.SCREEN_SIZE / 100;
                            HScale = _currentScreenSize.Width * Defs.SCREEN_SIZE / 100;
                        } 
                        else {
                            //Do nothing... Just set Zoom.
                            HScale = HScale * Defs.SCREEN_SIZE / 100;
                            VScale = VScale * Defs.SCREEN_SIZE / 100;
                        }
    
                    ////__aspectRatio = VScale / HScale;
                    //HScale = ((HScale * _currentScreenSize.Height) / VScale) * Defs.SCREEN_SIZE / 100;
                    //VScale = _currentScreenSize.Height * Defs.SCREEN_SIZE / 100;
                }
    
                Bitmap scaledBmp = GraphicsFactory.ResizeImage(
                                            __bmp,
                                            Convert.ToInt32(HScale),
                                            Convert.ToInt32(VScale));
    
    0 讨论(0)
  • 2020-12-29 00:16

    You can avoid calculating the aspect ratio (and using doubles) using a few integer tricks..

    // You have the new height, you need the new width
    int orgHeight = 1200;
    int orgWidth = 1920;
    
    int newHeight = 400;
    int newWidth = (newHeight * orgWidth) / orgHeight; // 640
    

    or...

    // You have the new width, you need the new height.
    int orgWidth = 1920;
    int orgHeight = 1200;
    
    int newWidth = 800;
    int newHeight = (newWidth * orgHeight) / orgWidth; // 500
    

    The following example will resize an image to any desired rectangle (desWidth and desHeight) and center the image within that rectangle.

    static Image ResizeImage(Image image, int desWidth, int desHeight)
    {
        int x, y, w, h;
    
        if (image.Height > image.Width)
        {
            w = (image.Width * desHeight) / image.Height;
            h = desHeight;
            x = (desWidth - w) / 2;
            y = 0;
        }
        else
        {
            w = desWidth;
            h = (image.Height * desWidth) / image.Width;
            x = 0;
            y = (desHeight - h) / 2;
        }
    
        var bmp = new Bitmap(desWidth, desHeight);
    
        using (Graphics g = Graphics.FromImage(bmp))
        {
            g.CompositingQuality = CompositingQuality.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.DrawImage(image, x, y, w, h);
        }
    
        return bmp;
    }
    
    0 讨论(0)
  • 2020-12-29 00:30

    The solution posted by Nathaniel actually fails if the image height is larger than the image width. The following example yields the correct result :

    private Size ResizeFit(Size originalSize, Size maxSize)
    {
        var widthRatio = (double)maxSize.Width / (double)originalSize.Width;
        var heightRatio = (double) maxSize.Height/(double) originalSize.Height;
        var minAspectRatio = Math.Min(widthRatio, heightRatio);
        if (minAspectRatio > 1)
            return originalSize;
        return new Size((int)(originalSize.Width*minAspectRatio), (int)(originalSize.Height*minAspectRatio));
    }
    
    0 讨论(0)
  • 2020-12-29 00:33

    Here are two ways to make this calculation. Depending upon how you think about the problem, one may seem more intuitive than the other. They are mathematically equivalent to several decimal places.

    Both are safe for Math.Round, but only ConstrainVerbose produces results that are always less than maxWidth/maxHeight.

    SizeF ConstrainConcise(int imageWidth, int imageHeight, int maxWidth, int maxHeight){
        // Downscale by the smallest ratio (never upscale)
        var scale = Math.Min(1, Math.Min(maxWidth / (float)imageWidth, maxHeight / (float) imageHeight));
        return new SizeF(scale * imageWidth, scale * imageHeight);
    }
    
    SizeF ConstrainVerbose(int imageWidth, int imageHeight, int maxWidth, int maxHeight){
        // Coalculate the aspect ratios of the image and bounding box
        var maxAspect = (float) maxWidth / (float) maxHeight;
        var aspect =  (float) imageWidth / (float) imageHeight;
        // Bounding box aspect is narrower
        if (maxAspect <= aspect && imageWidth > maxWidth)
        {
            // Use the width bound and calculate the height
            return new SizeF(maxWidth, Math.Min(maxHeight, maxWidth / aspect));
        }
        else if (maxAspect > aspect && imageHeight > maxHeight)
        {
            // Use the height bound and calculate the width
            return new SizeF(Math.Min(maxWidth, maxHeight * aspect), maxHeight);
        }else{
            return new SizeF(imageWidth, imageHeight);
        }
    }
    

    Brute force unit-test here

    0 讨论(0)
提交回复
热议问题