VB.NET实现无画质损失的JPG图片裁剪(仿MS Paint功能)
精准裁剪JPG至指定尺寸并保留原始DPI与画质(VB.NET实现)
现有一张略大于19"×23"的JPG图片,需用VB.NET实现精准裁剪至19"×23"并完全保留原始画质。
在MS Paint中可轻松完成该操作:打开一张2851×4651像素、200 DPI的JPG,通过「图像属性」将宽高修改为3800×4600像素(对应200 DPI下的19"×23"),最终仅裁剪右侧和底部各51像素,画质、压缩率与原图完全一致,仅文件大小略有减小。
但使用常规裁剪/缩放方法时,保存的图片始终默认96 DPI;即便调整像素尺寸使最终物理尺寸为19"×23",也会出现明显像素化,文件大小大幅降低,存在严重画质损失。
核心需求是实现类似MS Paint的无损裁剪——仅裁去侧边和底部的少量区域,同时必须保留原始DPI(如200 DPI),允许使用外部库。
存在画质损失的示例代码
以下代码虽能生成19"×23"的图片,但因硬编码96 DPI且涉及缩放,存在画质损失:
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
无损裁剪解决方案
方案1:原生GDI+实现
核心思路:基于原始图片DPI计算目标像素尺寸,直接裁剪像素区域,保留原始DPI与压缩参数。
Imports System.Drawing Imports System.Drawing.Imaging Imports System.Linq Public Shared Function CropImageToExactSize(sourceImage As Image, targetWidthInches As Decimal, targetHeightInches As Decimal) As Bitmap ' 基于原始DPI计算目标像素尺寸 Dim targetWidth As Integer = CInt(Math.Round(targetWidthInches * sourceImage.HorizontalResolution)) Dim targetHeight As Integer = CInt(Math.Round(targetHeightInches * sourceImage.VerticalResolution)) ' 校验原始图片尺寸是否足够 If sourceImage.Width < targetWidth OrElse sourceImage.Height < targetHeight Then Throw New ArgumentException("原始图片尺寸小于目标裁剪尺寸") End If ' 定义裁剪区域:此处裁剪右侧和底部,起始点为(0,0) ' 若需居中裁剪,可调整起始坐标为(sourceImage.Width - targetWidth)/2、(sourceImage.Height - targetHeight)/2 Dim cropRect As New Rectangle(0, 0, targetWidth, targetHeight) ' 创建裁剪后的Bitmap并设置原始DPI Dim croppedBitmap As New Bitmap(targetWidth, targetHeight) croppedBitmap.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution) ' 绘制裁剪区域到新Bitmap Using g As Graphics = Graphics.FromImage(croppedBitmap) g.CompositingMode = Drawing2D.CompositingMode.SourceCopy g.DrawImage(sourceImage, New Rectangle(0, 0, targetWidth, targetHeight), cropRect, GraphicsUnit.Pixel) End Using Return croppedBitmap End Function ' 保存裁剪后的图片,保留原始JPG压缩参数 Public Shared Sub SaveCroppedImage(croppedBitmap As Bitmap, originalImagePath As String, outputPath As String) ' 获取JPG编码器 Dim jpgCodec As ImageCodecInfo = ImageCodecInfo.GetImageEncoders().FirstOrDefault(ici => ici.FormatID = ImageFormat.Jpeg.Guid) If jpgCodec Is Nothing Then Throw New NotSupportedException("不支持JPG格式") End If ' 读取原始图片的压缩质量参数 Dim encoderParams As New EncoderParameters(1) Dim originalQuality As Long = 100 ' 默认最高质量 Using originalImage As Image = Image.FromFile(originalImagePath) Dim qualityParam = originalImage.PropertyItems.FirstOrDefault(pi => pi.Id = &H1D) If qualityParam IsNot Nothing Then originalQuality = BitConverter.ToInt32(qualityParam.Value, 0) End If End Using encoderParams.Param(0) = New EncoderParameter(Encoder.Quality, originalQuality) ' 保存图片,保留DPI与压缩质量 croppedBitmap.Save(outputPath, jpgCodec, encoderParams) End Sub ' 使用示例 'Using sourceImg As Image = Image.FromFile("原始图片路径.jpg") ' Using croppedBmp As Bitmap = CropImageToExactSize(sourceImg, 19, 23) ' SaveCroppedImage(croppedBmp, "原始图片路径.jpg", "输出图片路径.jpg") ' End Using 'End Using
方案2:使用ImageSharp库(推荐)
ImageSharp是跨平台的.NET图像处理库,无需依赖GDI+,处理DPI与压缩参数更简洁。
首先安装NuGet包:SixLabors.ImageSharp 和 SixLabors.ImageSharp.Formats.Jpeg
Imports SixLabors.ImageSharp Imports SixLabors.ImageSharp.Formats.Jpeg Imports SixLabors.ImageSharp.Processing Public Shared Sub CropImageWithImageSharp(sourcePath As String, outputPath As String, targetWidthInches As Decimal, targetHeightInches As Decimal) Using image As Image = Image.Load(sourcePath) ' 获取原始DPI Dim dpiX As Double = image.Metadata.HorizontalResolution Dim dpiY As Double = image.Metadata.VerticalResolution ' 计算目标像素尺寸 Dim targetWidth As Integer = CInt(Math.Round(targetWidthInches * dpiX)) Dim targetHeight As Integer = CInt(Math.Round(targetHeightInches * dpiY)) ' 裁剪右侧和底部区域 image.Mutate(x => x.Crop(targetWidth, targetHeight)) ' 保留原始DPI设置 image.Metadata.HorizontalResolution = dpiX image.Metadata.VerticalResolution = dpiY ' 使用原始压缩质量保存(可读取原始参数,此处默认最高质量) Dim jpegEncoder = New JpegEncoder() jpegEncoder.Quality = 100 image.Save(outputPath, jpegEncoder) End Using End Sub
关键注意点
- 必须基于原始图片的DPI计算目标像素尺寸,而非硬编码96 DPI
- 裁剪操作直接截取像素区域,避免缩放,杜绝画质损失
- 保存时务必保留原始图片的DPI设置与JPG压缩参数(质量、子采样等)
内容的提问来源于stack exchange,提问作者kpg
相关产品推荐
相关产品推荐

