0

Given an jpg image slightly larger than 19" x 23" I need to crop it to exactly 19" x 23" and preserve the original quality using VB.NET.

I can do this in MS paint, If I open a 2851 x 4651 200 DPI jpg and use the Image Properties dialog I can change the width and Height to 3800 x 4600 (exactly 19" x 23" @ 200 DPI).

The resultant image is identical to the original in quality and compression but is cropped on the right and bottom by the 51 pixels. The file size is slightly smaller as expected.

When I use the many techniques I have found on SO to crop/resize an image when I save the image it always saves as 96 DPI. I can adjust the width and height to accommodate the 96 DPI so the end result is exactly 19" x 23", however the resulting pixilation is higher than the original, and the files size is considerably smaller, so obvious quality loss.

What I want is to do is (a simple?) crop like MS paint does. Just take a little off the side and bottom, but I cannot seem to save an image with anything other than 96 DPI.

If I can figure out how to the save the cropped file at 200 DPI (or whatever the original image was) I think what I have will work fine.

I am willing to use an external library if that is what it takes.

Here is one example that works in the sense that the resulting image is 19" x 23" and the image is actually scaled preserving the aspect ratio, however the quality is less than the original.

This code is from another SO answer with some minor modifications.

Public Shared Function ResizeImage(SourceImage As Drawing.Image, TargetWidthIn As Decimal, TargetHeightIn As Decimal) As Drawing.Bitmap

    'Dim TargetWidth As Integer = TargetWidthIn * SourceImage.HorizontalResolution
    'Dim TargetHeight As Integer = TargetHeightIn * SourceImage.VerticalResolution
    Dim TargetWidth As Integer = TargetWidthIn * 96
    Dim TargetHeight As Integer = TargetHeightIn * 96

    Dim bmSource = New Drawing.Bitmap(SourceImage)

    Dim bmDest As New Drawing.Bitmap(TargetWidth, TargetHeight, Drawing.Imaging.PixelFormat.Format32bppArgb)

    Dim nSourceAspectRatio = bmSource.Width / bmSource.Height
    Dim nDestAspectRatio = bmDest.Width / bmDest.Height

    Dim NewX = 0
    Dim NewY = 0
    Dim NewWidth = bmDest.Width
    Dim NewHeight = bmDest.Height

    If nDestAspectRatio = nSourceAspectRatio Then
        'same ratio
    ElseIf nDestAspectRatio > nSourceAspectRatio Then
        'Source is taller
        NewWidth = Convert.ToInt32(Math.Floor(nSourceAspectRatio * NewHeight))
        NewX = Convert.ToInt32(Math.Floor((bmDest.Width - NewWidth) / 2))
    Else
        'Source is wider
        NewHeight = Convert.ToInt32(Math.Floor((1 / nSourceAspectRatio) * NewWidth))
        NewY = Convert.ToInt32(Math.Floor((bmDest.Height - NewHeight) / 2))
    End If

    Using grDest = Drawing.Graphics.FromImage(bmDest)
        With grDest
            .CompositingQuality = Drawing.Drawing2D.CompositingQuality.HighQuality
            '.InterpolationMode = Drawing.Drawing2D.InterpolationMode.HighQualityBicubic
            .InterpolationMode = Drawing.Drawing2D.InterpolationMode.NearestNeighbor
            .PixelOffsetMode = Drawing.Drawing2D.PixelOffsetMode.HighQuality
            .CompositingMode = Drawing.Drawing2D.CompositingMode.SourceCopy
            '.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias
            '.CompositingMode = Drawing.Drawing2D.CompositingMode.SourceOver
            .DrawImage(bmSource, NewX, NewY, NewWidth, NewHeight)
        End With
    End Using

    Return bmDest
End Function
kpg
  • 589
  • 6
  • 28
  • When you build the new Bitmap, you can use the `SetResolution()` method (see the different methods [here](https://stackoverflow.com/a/59102380/7444103), for example). Then you just need to specify the source Rectangle. The destination rectangle size is the same as the destination bitmap's size -- If you want to control the compression, you need to use the JPEG encoder, instead of accepting the default (lossy) settings. In case the size of the final image doesn't count much, save to PNG or TIFF – Jimi Feb 10 '23 at 18:21
  • You may also find [How to: Set JPEG Compression Level](https://learn.microsoft.com/en-us/dotnet/desktop/winforms/advanced/how-to-set-jpeg-compression-level?view=netframeworkdesktop-4.8) to be of use. – Andrew Morton Feb 10 '23 at 18:22
  • I tried the `SetResolution` but the saved file was still 96 DPI. Then I found if I use the jpeg encoder to save the file it does honor the `SetResolution`. bm.Save(sTgt, jpgEncoder, myEncoderParams) – kpg Feb 10 '23 at 19:55

1 Answers1

0

I found a solution on SO here

I modifyed my original code above to keep the DPI of the original image:

    Dim TargetWidth As Integer = TargetWidthIn * SourceImage.HorizontalResolution
    Dim TargetHeight As Integer = TargetHeightIn * SourceImage.VerticalResolution

Then after the call to ResizeImage:

                        Select Case imageType.ToLower
                            Case "jpg"
                                Dim jpgEncoder As ImageCodecInfo = GetEncoder(ImageFormat.Jpeg)
                                Dim myEncoder As System.Drawing.Imaging.Encoder = System.Drawing.Imaging.Encoder.Quality
                                Dim myEncoderParams As New EncoderParameters(1)
                                Dim myEncoderQuality As New EncoderParameter(myEncoder, CType(98L, Int32)) '98%
                                myEncoderParams.Param(0) = myEncoderQuality
                                bm.SetResolution(img.HorizontalResolution, img.VerticalResolution)
                                bm.Save(tempfile, jpgEncoder, myEncoderParams)
                            Case "png", "gif"
                                bm.Save(tempfile, System.Drawing.Imaging.ImageFormat.Png)
                            Case "tiff", "tif"
                                bm.Save(tempfile, System.Drawing.Imaging.ImageFormat.Tiff)
                            Case Else
                                bm.Save(tempfile, System.Drawing.Imaging.ImageFormat.Png)
                        End Select

                        bm.Dispose()

I only use jpg now so I don't know if the tiff and png parts work, but it seems using the jpeg encoder allowed me to save the file with 200 DPI and maintain the original quality.

Here is the GetEncoder part that is missing from the other post:

Private Shared Function GetEncoder(f As Drawing.Imaging.ImageFormat) As ImageCodecInfo

    Dim myEncoders() As ImageCodecInfo
    myEncoders = ImageCodecInfo.GetImageEncoders()
    Dim numEncoders As Integer = myEncoders.GetLength(0)
    Dim strNumEncoders As String = numEncoders.ToString()

    ' Get the info. for all encoders in the array.
    If numEncoders > 0 Then
        Dim myEncoderInfo(numEncoders * 10) As String
        For i As Integer = 0 To numEncoders - 1
            If myEncoders(i).FilenameExtension.Contains(f.ToString.ToUpper) Then
                Return myEncoders(i)
            End If
        Next
    End If

    Return Nothing
kpg
  • 589
  • 6
  • 28