开发者

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

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 see if either the width, the height, or the height and width are greater than the Max Width/Max Height settings.

I now need to figure out what dimensions to resize to based on the max of the latter value.

For example: If the image is 900h x 300w and the MAX Height is 700h I will need to resize the height to 700 and the width to ???? <-- this is what I need to calculate..

Creating and saving the image file is simple, and outside the scope of this post:

// First I get the max height and width allowed:

int resizeMaxHeight =  int.Parse(Utility.GetConfigValue("ResizeMaxHeight")); // in config: 700px
int resizeMaxWidth =  int.Parse(Utility.GetConfigValue("ResizeMaxWidth"));  //  in config: 500px

// Save original: 
try
{
    filebase.SaveAs(savedFileName);
}
catch (System.IO.DirectoryNotFoundException ex)
{
    Logger.Instance.LogException(ex, 0, "FileTransfer");
}

// Determin original dimensions:
Image image = System.Drawing.Image.FromFile(Server.MapPath(savedFileName));

int resizeHeight, resizeWidth;
bool doResize = true;

// both height and width are greater than the allowed height and width:
if (image.Width > resizeMaxWidth && image.Height > resizeMaxHeight)
{
    if (image.Height > image.Width) 
        resizeHeight = resizeMaxHeight;
    else
        resizeWidth = resizeMaxWidth;
}
else if (image.Width > resizeMaxWidth)
{
    // width is too great, but height is ok
    resizeWidth = resizeMaxWidth;
}
else if (image.Height > resizeMaxHeight)
{
    // height is too great, but width is o开发者_C百科k
    resizeHeight = resizeMaxHeight;
}
else
{
    // image is ok size, don't resize:
    doResize = false;
}

Create thumbnail: This is what I'm working now... not complete:

if (doResize)
{
    ImageUtilities.ResizeImage(image, resizeWidth, resizeHeight);
}


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));
}


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


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;
}


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));


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜