C#更改已上传图像的DPI

7
我有一个函数,用于改变图像的分辨率。我想这样做是为了将上传的图像(例如300dpi)修改为72dpi(用于web)。这个问题与另一个在SO上的问题(链接)相关,我正在处理这个问题。
我正在创建一个扩展方法,以便在我的应用程序中更多地使用此功能,而不仅仅是在上传新文件时使用(请参见上述提到的问题)。
public static byte[] SetDpiTo72(this byte[] imageToFit, string mimeType, Size newSize)
{    
    using (MemoryStream memoryStream = new MemoryStream(), newMemoryStream = new MemoryStream())
    {
        memoryStream.Write(imageToFit, 0, imageToFit.Length);
        var originalImage = new Bitmap(memoryStream);

        using (var canvas = Graphics.FromImage(originalImage))
        {
            canvas.SmoothingMode = SmoothingMode.AntiAlias;
            canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
            canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
            canvas.DrawImage((Image)originalImage,0,0, newSize.Width, newSize.Height);

            newBitmap.SetResolution(72, 72);
            newBitmap.Save(newMemoryStream, ImageFunctions.GetEncoderInfo(mimeType), null);
        }
        return newMemoryStream.ToArray();
    }
}

提到的扩展方法正在类似下面情况的函数中被调用:
if (newSize.Width > originalImage.Width && newSize.Height > originalImage.Height)
{
     newSize.Width = originalImage.Width;
     newSize.Height = originalImage.Height;

     uploadedFileBuffer = uploadedFileBuffer.SetDpiTo72(uploadedFile.ContentType, newSize);

     return CreateFile(newSize, uploadedFile, uploadedFileBuffer);
}

传入的bytearray是文件的字节数组。它已经具有正确的大小,但我想将分辨率更改为72dpi。然而,在执行并保存图像后,分辨率仍然是原始输入的300dpi。我该怎么做?

在几个答案之后更新:

public static byte[] SetDpiTo72(this byte[] imageToFit, string mimeType, Size newSize)
        {
            using (MemoryStream memoryStream = new MemoryStream(), newMemoryStream = new MemoryStream())
            {
                memoryStream.Write(imageToFit, 0, imageToFit.Length);
                var originalImage = new Bitmap(memoryStream);

                using (var canvas = Graphics.FromImage(originalImage))
                {
                    canvas.SmoothingMode = SmoothingMode.AntiAlias;
                    canvas.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    canvas.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    canvas.DrawImage((Image)originalImage,0,0, newSize.Width, newSize.Height);

                    originalImage.SetResolution(72, 72);

                    var epQuality = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 75);
                    var epParameters = new EncoderParameters(1);
                    epParameters.Param[0] = epQuality;

                    Image newimg = Image.FromStream(memoryStream);

                    //Getting an GDI+ exception after the execution of this line.
                    newimg.Save("C:\\test1234.jpg", ImageFunctions.GetEncoderInfo(mimeType), epParameters);

                    originalImage.Save("test.jpg", ImageFormat.Jpeg);

                    //This line give me an Argumentexception - Parameter is not valid.
                    //originalImage.Save(newMemoryStream, ImageFunctions.GetEncoderInfo(mimeType), epParameters);
                    //newMemoryStream.Close();
                }
                return newMemoryStream.ToArray();
            }
        }

异常信息中的堆栈跟踪告诉我以下内容:
   at System.Drawing.Image.Save(String filename, ImageCodecInfo encoder, EncoderParameters encoderParams)
   at Extensions.ByteArrayExtensions.SetDpiTo72(Byte[] imageToFit, String mimeType, Size newSize) in C:\Website\Project\Extensions\ByteArrayExtensions.cs:line 356
   at CMS.Presentation.FileFunctions.CreateFullsizeImage(HttpPostedFileBase uploadedFile, Size newSize, Byte[] uploadedFileBuffer) in C:\Website\Project\CMS.Presentation\FileFunctions.cs:line 197
   at CMS.Presentation.FileFunctions.CreateFile(HttpPostedFileBase uploadedFile, INodeService nodeservice, Guid userId, Node parentNode) in C:\Website\Project\CMS.Presentation\FileFunctions.cs:line 53

同时,我还开发了另一个函数(如下所示)来调整位图大小。这似乎能正常工作。然而,我不能在当前的实现中使用此函数,因为它仅返回位图。我是否应该更改所有内容以便与位图一起工作?

private static Bitmap ResizeImage(Image image, int width, int height)
        {
            var frameCount = image.GetFrameCount(new FrameDimension(image.FrameDimensionsList[0]));
            var newDimensions = ImageFunctions.GenerateImageDimensions(image.Width, image.Height, width, height);
            Bitmap resizedImage;

            if (frameCount > 1)
            {
                //we have a animated GIF
                resizedImage = ResizeAnimatedGifImage(image, width, height);
            }
            else
            {
                resizedImage = (Bitmap)image.GetThumbnailImage(newDimensions.Width, newDimensions.Height, null, IntPtr.Zero);
            }

            resizedImage.SetResolution(72,72);

            return resizedImage;
        }

1
你知道你的代码很圆滑吗?newBitmap = originalImage 并不是复制,它只是引用同一张图片。有趣的是它仍然可以工作。 - Euphoric
哈哈,好笑!已经改变了;-) - Rob
等一下,它真的解决了你的问题吗? - Euphoric
没有,它没有。我会更新问题并附上当前的代码。 - Rob
4个回答

3

好的,我只尝试过在硬盘上的文件,但也应该适用于流。

        Bitmap bitmap = new Bitmap(loadFrom);
        Bitmap newBitmap = new Bitmap(bitmap);
        newBitmap.SetResolution(72, 72);
        newBitmap.Save(saveTo);

3

花了一些时间,但我终于找到了问题所在! 问题出在我使用的ResizeImage函数中。具体来说是在“GetThumbnailImage”函数中。我遇到了另一个问题,即图像模糊,这是可以解释的,因为GetThumbnailImage会将创建的缩略图拉伸到所需的大小。而缩略图的分辨率永远不会改变。

private static Bitmap ResizeImage(Image image, int width, int height)
        {
            var frameCount = image.GetFrameCount(new FrameDimension(image.FrameDimensionsList[0]));
            var newDimensions = ImageFunctions.GenerateImageDimensions(image.Width, image.Height, width, height);
            Bitmap resizedImage;

            if (frameCount > 1)
            {
                //we have a animated GIF
                resizedImage = ResizeAnimatedGifImage(image, width, height);
            }
            else
            {
                resizedImage = (Bitmap)image.GetThumbnailImage(newDimensions.Width, newDimensions.Height, null, IntPtr.Zero);
            }

            resizedImage.SetResolution(72,72);

            return resizedImage;
        }

通过将上述函数修改为下面的函数,我能够使用Graphics.DrawImage重新绘制新图像,并在呈现之前解决问题。同时稍微修改了GenerateImageDimensions函数。这些修改共同解决了问题。
private static Bitmap ResizeImage(Image image, int width, int height)
        {
            var frameCount = image.GetFrameCount(new FrameDimension(image.FrameDimensionsList[0]));
            var newDimensions = ImageFunctions.GenerateImageDimensions(image.Width, image.Height, width, height);

            var resizedImage = new Bitmap(newDimensions.Width, newDimensions.Height);
            if (frameCount > 1)
            {
                //we have a animated GIF
                resizedImage = ResizeAnimatedGifImage(image, width, height);
            }
            else
            {

                //we have a normal image
                using (var gfx = Graphics.FromImage(resizedImage))
                {
                    gfx.SmoothingMode = SmoothingMode.HighQuality;
                    gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    gfx.PixelOffsetMode = PixelOffsetMode.HighQuality;

                    var targRectangle = new Rectangle(0, 0, newDimensions.Width, newDimensions.Height);
                    var srcRectangle = new Rectangle(0, 0, image.Width, image.Height);

                    gfx.DrawImage(image, targRectangle, srcRectangle, GraphicsUnit.Pixel);
                }
            }

            return resizedImage;
        }

1

通过“更改分辨率”,您实际上是指要通过72/300减少图像中的像素数量吗?也就是将4000x3000的图像更改为960x720吗?

如果是这样,我看不到您的代码实际上是这样做的。您正在使用的DrawImage()重载执行以下操作:

在指定的坐标对处,使用其原始物理大小绘制指定的图像。

这正是正在发生的事情。

尝试其他重载之一,例如此重载

在指定位置以及指定大小绘制指定的图像。

例如:

// Create image.
Image newImage = Image.FromFile("SampImag.jpg");

// Create coordinates for upper-left corner of image and for size of image.
int x = 0;
int y = 0;
int width = 450;
int height = 150;

// Draw image to screen.
e.Graphics.DrawImage(newImage, x, y, width, height);

编辑:根据评论,我理解OP想要在不减少像素数量的情况下减小文件大小。因此,必须重新压缩文件。

我从这里借用了一些示例代码:

ImageCodecInfo iciJpegCodec = null;

// This will specify the image quality to the encoder. Change the value of 75 from 0 to 100, where 100 is best quality, but highest file size.
EncoderParameter epQuality = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 75);

// Get all image codecs that are available
ImageCodecInfo[] iciCodecs = ImageCodecInfo.GetImageEncoders();

// Store the quality parameter in the list of encoder parameters
EncoderParameters epParameters = new EncoderParameters(1);
epParameters.Param[0] = epQuality;

// Loop through all the image codecs
for (int i = 0; i < iciCodecs.Length; i++)
{
    // Until the one that we are interested in is found, which is image/jpeg
    if (iciCodecs[i].MimeType == "image/jpeg")
    {
        iciJpegCodec = iciCodecs[i];
        break;
    }
}

// Create a new Image object from the current file
Image newImage = Image.FromFile(strFile);

// Get the file information again, this time we want to find out the extension
FileInfo fiPicture = new FileInfo(strFile);

// Save the new file at the selected path with the specified encoder parameters, and reuse the same file name
newImage.Save(outputPath + "\\" + fiPicture.Name, iciJpegCodec, epParameters);

我真正想要的是减少图像每英寸的像素数量。例如,如果我上传一个尺寸为1181 x 1181且分辨率为300dpi的图像,则希望将其保存为原始大小的1181 x 1181,但分辨率改为72dpi而不是原始的300dpi。 - Rob
我刚刚发现在调用drawimage函数时没有提供所需的图像大小。我会尝试进行修改。 - Rob
@tomfanning。是的,我确信这是必要的。我遇到的情况是,我正在开发的Web应用程序包含一个带有图像的库。每种类型的图像都可以上传,并将以最大7500x7500像素的大小保存为blob到数据库中。在包含一个或多个这些图像的页面上,在向用户显示之前,文件会在服务器端呈现。但是,在正确的大小和300dpi作为源的情况下呈现它们会导致我们的服务器出现严重的负载问题。这就是为什么我们想在将其保存为BLOB之前减少文件的DPI的原因。 - Rob
仅仅像这样改变分辨率值并保存文件,对文件大小没有任何影响。调用SetResolution()方法只是在文件内部调整一个数字而已。如果你想要减小文件大小而不降低像素数量,你需要改变文件的压缩质量。这里有一个简单的应用程序:http://www.geekpedia.com/tutorial232_Reducing-JPEG-Picture-Quality-using-Csharp.html - tomfanning
好的,发现了异常。需要将参数值转换为长整型。显然这是GDI+中的一个奇怪问题。在这里阅读更多信息:https://dev59.com/YEbRa4cB1Zd3GeqPww8u - Rob
显示剩余6条评论

0
Rob,我认为你的代码问题在于保存图像 - 实际的数字图像数据将是一定数量的点/像素,即(m x n),在位图上设置分辨率不会/不应该改变点数(因此也不会改变图像的物理字节大小)。分辨率信息将存储在图像头中(供程序在打印/编辑图像时使用) - 如果您将新位图存储到文件而不是内存流中,会发生什么?
newBitmap.Save("c:\test.png", ImageFormat.Png);

从文件 -> 属性 -> 摘要(高级)中检查上述文件的 dpi。它应该是 72 dpi。


将尝试保存到文件系统并查看结果。但这不可能是最终解决方案。最终需要将其保存到数据库中,以便应用程序开发。 - Rob
我已经尝试了您的建议,但是出现了一个GDI+异常。我已经更新了问题。 - Rob

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