我需要压缩PNG文件的图像,但不能损失质量。

6
我想压缩一个PNG图片,减小它的大小但保持其质量。我已经尝试过压缩JPEG图像。图像压缩了约90%,质量保持不变,但当我使用它来压缩PNG图像时,没有结果,没有压缩。大小相同。
以下是我的代码。
public const string _StatusLog = "StatusLog.csv";
        static void Main(string[] args)
        {
            Console.WriteLine("                 ###   WELCOME   ###");
            Console.Write("\n\nPlease enter image folder path :");
            string imagePath = Console.ReadLine();
            Program p = new Program();
            p.VaryQualityLevel(imagePath);
            Console.ReadLine();
        }
        private void VaryQualityLevel(string pathOfImage)
        {
            try
            {
                //Console.Write("Target Directory Path :");
                string targetDirectory = pathOfImage;//Console.ReadLine();

                if (targetDirectory != null)
                {
                    string[] allDirectoryInTargetDirectory = Directory.GetDirectories(targetDirectory);
                    //PRODUCT DIRECOTY OPEN
                    Console.Write("Total Folders found = " + allDirectoryInTargetDirectory.Count());
                    Console.Read();
                    if (allDirectoryInTargetDirectory.Any())
                    {
                        foreach (var directory in allDirectoryInTargetDirectory)
                        {
                            string[] subDirectory = Directory.GetDirectories(directory); // ATTRIBUTE DIRECTORY OPEN
                            if (subDirectory.Any())
                            {
                                foreach (var filesInSubDir in subDirectory)
                                {
                                    string[] allFilesInSubDir = Directory.GetFiles(filesInSubDir);
                                    //FILES IN SUB DIR OPEN
                                    if (allFilesInSubDir.Any())
                                    {
                                        foreach (var imageFile in allFilesInSubDir)
                                        {
                                            try
                                            {
                                                Bitmap bmp1 = new Bitmap(imageFile);//pathOfImage);
                                                ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Jpeg);

                                                // Create an Encoder object based on the GUID 
                                                // for the Quality parameter category.
                                                System.Drawing.Imaging.Encoder myEncoder =
                                                    System.Drawing.Imaging.Encoder.Quality;

                                                // Create an EncoderParameters object. 
                                                // An EncoderParameters object has an array of EncoderParameter 
                                                // objects. In this case, there is only one 
                                                // EncoderParameter object in the array.



                                                #region SAVING THE COMPRESS IMAGE FILE
                                                EncoderParameters myEncoderParameters = new EncoderParameters(1);

                                                EncoderParameter myEncoderParameter = new EncoderParameter(myEncoder, 50L);
                                                myEncoderParameters.Param[0] = myEncoderParameter;

                                                bmp1.Save(filesInSubDir + "\\" + "Zip" + GettingImageNameForOptimizedImage(imageFile), jpgEncoder, myEncoderParameters);//pathOfImage
                                                Console.WriteLine(filesInSubDir + GettingImageNameForOptimizedImage(imageFile) + "  CREATED");//pathOfImage 
                                                #endregion

                                                #region DELETING THE ORIGNAL FILE
                                                bmp1.Dispose();
                                                System.IO.File.Delete(filesInSubDir + "\\" + GettingImageNameForOptimizedImage(imageFile));//pathOfImage
                                                Console.WriteLine(imageFile.Replace("jpg", "png") + "  DELETED");//pathOfImage 
                                                #endregion
                                                //myEncoderParameter = new EncoderParameter(myEncoder, 100L);
                                                //myEncoderParameters.Param[0] = myEncoderParameter;
                                                //bmp1.Save("D:\\" + RemovingImageFormat[0] + "100L" + ".jpg", jpgEncoder, myEncoderParameters);

                                                #region BACK RENAMING FILE TO ORIGNAL NAME
                                                System.IO.File.Move(filesInSubDir + "\\" + "Zip" + GettingImageNameForOptimizedImage(imageFile), filesInSubDir + "\\" + GettingImageNameForOptimizedImage(imageFile)); 
                                                #endregion
                                            }
                                            catch (Exception ex)
                                            {
                                                Console.Write("\n" + ex.Message + " Press enter to continue :");
                                                Console.ReadLine();

                                                Console.Write("\nWould you like to retry ? [Y/N] :");
                                                string resp = Console.ReadLine();
                                                if (resp == "Y" || resp == "y")
                                                {
                                                    Console.WriteLine("                 -------------------\n\n");
                                                    Main(null);
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.Write(ex);
                Console.Read();
            }

            Console.Write("Press any key to exit...");
            Console.Read();
            // Get a bitmap. ###################################################################


        }
        private ImageCodecInfo GetEncoder(ImageFormat format)
        {

            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();

            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.FormatID == format.Guid)
                {
                    return codec;
                }
            }
            return null;
        }
        public string GettingImageNameForOptimizedImage(string pathOfImage)
        {
            try
            {
                string[] splitingPathOfImage = pathOfImage.Split('\\');
                string[] RemovingImageFormat = splitingPathOfImage[splitingPathOfImage.Count() - 1].ToString().Split('.');
                return RemovingImageFormat[0] + ".jpg";
            }
            catch (Exception)
            {
                return null;
            }
            return null;
        }
        public static void LoggingOperations(string ImageName, string Status, bool UpdateRequired)
        {
            try
            {
                if (!File.Exists(_StatusLog))
                {
                    using (File.Create(_StatusLog)) { }
                    DirectorySecurity sec = Directory.GetAccessControl(_StatusLog);
                    SecurityIdentifier everyone = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
                    sec.AddAccessRule(new FileSystemAccessRule(everyone, FileSystemRights.Modify | FileSystemRights.Synchronize, InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit, PropagationFlags.None, AccessControlType.Allow));
                    Directory.SetAccessControl(_StatusLog, sec);
                }
                if (UpdateRequired == true)
                {
                    string UpdateStatusText = File.ReadAllText(_StatusLog);
                    UpdateStatusText = UpdateStatusText.Replace(ImageName, ImageName + "," + Status);
                    File.WriteAllText(_StatusLog, UpdateStatusText);
                    UpdateStatusText = "";
                }
                else
                {
                    File.AppendAllText(_StatusLog, Environment.NewLine);
                    File.AppendAllText(_StatusLog, Status);
                }
            }
            catch (Exception)
            {
            }
        }

对于PNG压缩,我更改了以下行。

Bitmap bmp1 = new Bitmap(imageFile);//pathOfImage);
ImageCodecInfo jpgEncoder = GetEncoder(ImageFormat.Png);

有位善良的人可以帮我一下吗?如果有新的方法,我很欢迎。如果这个能够改变,就更好了。


(IT技术相关内容,无需翻译)

3
PNG已经被很好地压缩了,不太可能再被改进。在JPG中进行压缩会导致一些质量损失。 - TaW
1
图片本身并不是PNG或JPEG格式。PNG和JPEG是文件格式,表示存储某些图像的方式。两者都已经压缩过了。JPEG是“有损压缩”的,但它被设计成优先丢失大多数人根本不会注意到的数据,只需查看图像即可。PNG是无损压缩的;这必然防止PNG格式将给定的图像压缩得像JPEG那样多。PNG确实有很多压缩选项,可能其他PNG编码器会比.NET更好地压缩,但您永远不会看到与JPEG相同程度的压缩。 - Peter Duniho
@Peter Duniho JPEG 可以被压缩。显然会有图像质量损失,但在一定程度上是可以接受的。是否有办法将 PNG 压缩到一定程度,同时损失一定程度的质量呢? - Muhammad Bashir
但是,如果您使用PNG进行压缩,就没有像JPEG那样在压缩质量和文件大小之间进行权衡的选项。 - Peter Duniho
关于 Photoshop,你是错误的。无论哪个程序编码 PNG 文件,它都将始终具有与原始图像完全相同的像素数据。在编码过程中唯一的权衡与使用的压缩技术(不同的技术适用于不同类型的数据)和时间有关。使用正确的参数可以导致更优化的压缩,但无论使用什么参数,你始终会得到相同的像素输出,这与损失算法(如 JPEG)完全不同。 - Peter Duniho
显示剩余5条评论
4个回答

13

PNG图像默认为32位。您可以将它们转换为8位:生成的文件大约比原始文件小5倍。对于大多数图像,质量损失几乎不可见。

这就是在线PNG压缩器所做的。

您可以使用nQuant自己执行此操作:http://nquant.codeplex.com/(可在Nuget上获得)。

var quantizer = new WuQuantizer();         
using(var quantized = quantizer.QuantizeImage(bmp1))
{
    quantized.Save(targetPath, ImageFormat.Png);
}

详细的方法说明可在此博客文章中查看http://www.hurryupandwait.io/blog/convert-32-bit-pngs-to-high-quality-8-bit-pngs-with-c


4
谢谢您推荐nQuant。我刚试用了一下。当.NET创建带透明通道的800KB PNG图像时,nQuant仅使用256个颜色就能够创建相同的PNG图像,并且只有240 KB,而且没有明显的质量损失。请注意,它通过减少颜色数量来实现这一点,但它非常聪明地做到了这一点。 - Doug S
1
这个库需要小心使用。对于颜色较多的大型图片,它可能会出现崩溃问题。https://archive.codeplex.com/?p=nquant - Donny V.

4

我建议您也看一下 ImageSharp,它也能与 .NET Core 配合使用。

            using (var image = Image.Load(fileData)) // fileData could be file path or byte array etc.
            {
                var h = image.Size().Height / 2;
                var w = image.Size().Width / 2;
                var options = new ResizeOptions
                {
                    Mode = ResizeMode.Stretch,
                    Size = new Size(w, h)
                };
                image.Mutate(_ => _.Resize(options));
                using (var destStream = new MemoryStream())
                {
                    var encoder = new JpegEncoder();
                    image.Save(destStream, encoder);
                    // Do something with output stream
                }     
            }

1
PNG压缩中唯一的一个变量是在压缩速度和输出大小之间的权衡。PNG压缩可能会非常缓慢,因为它涉及到在数据缓冲区中搜索匹配模式。您可以通过限制编码器搜索缓冲区的数量来加快压缩速度。
您的编码器应该有一个设置,允许您指定它将要搜索的匹配数量。
如果您的输入PNG图像没有使用编码器搜索整个缓冲区进行压缩,则在您的应用程序中搜索整个缓冲区可能会改善压缩效果。但是,您不太可能得到很大的改进。

就像 Photoshop 通过一些质量上的妥协来缩小图像一样,我也想借助 .net 实现这个功能。 - Muhammad Bashir
1
PNG 格式中不存在大小和质量的折衷。PNG 可以在压缩时间和文件大小之间进行权衡。 - user3344003
仅通过对PNG进行索引,您也将看到显着的改进。 - Pierre

0

如果像素大小和颜色深度保持一致,PNG 应该是无损的。如果想要将输出大小与某些基准进行比较,可以考虑使用它。

https://pnggauntlet.com/


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