在GreyScaling之后减小位图大小

时间:2014-07-20 10:47:00

标签: c# vb.net

我正在截取屏幕截图,序列化位图并通过网络发送。总的来说,最终传输的数据大约为26KB。

我试图让它变小。我尝试做的一件事是将位图转换为灰度。这是我正在使用的功能。

Public Function ConvertGreyscale(original As Bitmap) As Bitmap

    Dim NewBitmap As New Bitmap(original.Width, original.Height)
    Dim g As Graphics = Graphics.FromImage(NewBitmap)

    Dim attributes As New ImageAttributes

    attributes.SetColorMatrix(New ColorMatrix(New Single()() {New Single() {0.3F, 0.3F, 0.3F, 0, 0}, New Single() {0.59F, 0.59F, 0.59F, 0, 0}, New Single() {0.11F, 0.11F, 0.11F, 0, 0}, New Single() {0, 0, 0, 1, 0}, New Single() {0, 0, 0, 0, 1}}))

    g.DrawImage(original, New Rectangle(0, 0, original.Width, original.Height), 0, 0, original.Width, original.Height, GraphicsUnit.Pixel, attributes)
    g.Dispose()
    Return NewBitmap

End Function

这很好用,我最终得到了灰度图像。问题是,位图的大小没有变化。它仍然是26KB,即使它是灰度级的。我认为正在创建的新位图只是一个常规的32bppargb位图,其中插入了灰度图像。

我尝试过:

Dim NewBitmap As New Bitmap(original.Width, original.Height, PixelFormat.Format16bppgreyscale)

但我最终得到了#34;内存错误"。

我做错了什么?另外,还有其他方法可以最小化我的位图大小吗?

编辑:

因此,为了努力解决这个问题,我使用此代码将32bpp位图转换为16bpp位图

        Dim clone = New Bitmap(tmpImg.Width, tmpImg.Height, Imaging.PixelFormat.Format16bppRgb565)
        Using gr = Graphics.FromImage(clone)
            gr.DrawImage(tmpImg, New Rectangle(0, 0, clone.Width, clone.Height))
        End Using

我尝试过做Format16bbpGreyscale或Format16bppRgb555,但这两个原因导致"我们的内存错误"。似乎唯一有效的是Format16bppRgb256

无论如何,我再次进行数据包嗅探,并将格式更改为16bppRgb265 增加图像数据包的大小从~26KB到29KB。因此,更改为此格式似乎会增加大小。我不明白; _;

EDIT2:

我现在找到了多种方法将图像转换为灰度和/或将位图的像素格式更改为小于32bpp的值。不幸的是,当它通过网络发送时,这似乎都不会减小序列化位图的大小。有些事情似乎甚至会增加规模。不知道我能做什么。

3 个答案:

答案 0 :(得分:1)

转换为灰度并不会自行完成,因为您所做的只是更改像素的RGB值。不幸的是,许多灰度格式并不完全受支持,尽管有一些开源图像库可以做到这一点。

使用JPG和一些质量降低可以获得显着的降低。 26kb的全尺寸(?)屏幕截图听起来不大(或者它只是屏幕的一部分?),我们不知道你想要的目标尺寸是多少。以下是如何通过JPG降低质量。

Dim jpgEncoder As ImageCodecInfo = GetJPGEncoder()
Dim myEncoder As System.Drawing.Imaging.Encoder =
          System.Drawing.Imaging.Encoder.Quality

Dim jEncoderParams As New EncoderParameters(1)
' set the quality (100& here)
jEncoderParams.Param(0) = New EncoderParameter(myEncoder, 100&)

' dont do this...creates a false baseline for size tests
'Dim bmp As Bitmap = My.Resources.testimage

Using ms As New System.IO.MemoryStream(), 
        fs As New FileStream("C:\Temp\zser.bin", FileMode.Create),
        bmp As New Bitmap(My.Computer.Screen.WorkingArea.Width, 
             My.Computer.Screen.WorkingArea.Height),
        g As Graphics = Graphics.FromImage(bmp)

    ' get screen in (BMP format)
    g.CopyFromScreen(0, 0, 0, 0, My.Computer.Screen.WorkingArea.Size)

    ' save image to memstream in desired format
    bmp.Save(ms, Imaging.ImageFormat.Png)

    ' use jpgEncoder to control JPG quality/compression
    'bmp.Save(ms, jpgEncoder , jEncoderParams)

    ms.Position = 0
    Dim bf As New BinaryFormatter
    bf.Serialize(fs, ms)           ' serialize memstr to file str

    jEncoderParams.Dispose()

End Using

屏幕截图中的指标(实际尺寸取决于屏幕尺寸及其上的内容;尺寸差异非常重要):

Method      memstr size       file size after BF
  BMP         5,568,054        5438 (same) 
  PNG           266,624        261k
  JPG 100       634,861       1025
  JPG 90        277,575        513

图像的内容在确定尺寸等方面起作用。在这种情况下,PNG似乎是最佳的尺寸/质量平衡;你需要压缩JPG以获得相同的尺寸,但质量要低得多。

实际的照片类型图像会产生更大的尺寸:2500x1900图像为19MB,PNG为13MB,因此请使用实际图像进行测试。

答案 1 :(得分:1)

我建议查看Aforge的AForge.Imaging.ColorReduction.ColorImageQuantizer。

它将SO主页的屏幕截图从96kB缩小到33kB(达到16种颜色),同时保持可读性远远高于同样降低的jpg。减少到32或64种颜色几乎没有任何伪影,除了颜色变化,同时仍然保持在48kB。

虽然处理过程确实需要几秒钟。

这是一段使用Aforge库的代码。

using AForge.Imaging.ColorReduction;

void reduceColors(string inFile, string outFile, int numColors)
{
    using (Bitmap image = new Bitmap(inFile) )
    {
        ColorImageQuantizer ciq =    new ColorImageQuantizer(new MedianCutQuantizer());
        Color[] colorTable = ciq.CalculatePalette(image, numColors);
        using (Bitmap newImage = ciq.ReduceColors(image, numColors))
            newImage.Save(outFile);
    }
}

如果你感兴趣我还有一个自制的代码片段,它会产生40%的原始大小和perfext文本,尽管有一点颜色变化;它非常快。

答案 2 :(得分:0)

所以最终我发现了问题。

基本上我必须二进制序列化位图并通过网络流传输它。我试图减小位图的大小,使传输速度更快。

.NET的“image”类似乎只支持某些像素格式的位图。所以无论我对图像做了什么(灰度,有损压缩,无论如何),大小都是一样的,因为我没有改变图像的像素格式,我只是移动像素并改变它们的颜色。

据我所知,我没有可以序列化的JPG或PNG本地类,所以我不得不使用带有像素格式的图像类。

我试过的一件事是将压缩的,灰度图像转换为jpeg,然后将其转换为byte(),然后gzip并序列化该byte()。问题是由于某种原因导致网络数据传输量增加了2倍。

一个有趣的快速说明是,当您序列化图像对象时,它会根据我嗅到的网络流量转换为PNG格式(压缩)。因此,微软已经做了一些优化,以使图像序列化有效。

所以我非常被迫使用图像类,并且只是想办法如何将我的图像转换为1bpp或8bpp像素格式。 16bpp格式(例如灰度)被微软随机“不支持”,只是“不工作”,显然永远不会。当然MSDN没有提到这一点。

将我的图像转换为8bpp或更低是不可能的,因为我会因为未知原因而出现“内存不足”错误,或者不允许在索引图像上绘制内容。

我终于找到的解决方案是这里的CopyToBpp()函数: http://www.wischik.com/lu/programmer/1bpp.html

该功能连同其中的许多API,使我能够快速将32bpp图像转换为8bpp甚至1bbp图像。然后可以轻松有效地序列化二进制格式化程序并通过我的网络流发送。

所以现在它有效。