Matviy Kotoniy
Matviy Kotoniy

Reputation: 432

Decrease bitmap size after GreyScaling

I'm taking a screenshot of the screen, serializing the bitmap and sending it over the network. Overall this ends up being ~26KB of data transferred.

I'm trying to make this smaller. One thing I'm trying to do is converting the bitmap to greyscale. This is the function I'm using.

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

This works fine, and i end up getting a greyscale image. Problem is, that the size of the bitmap doesn't change. It's still 26KB, even though it's greyscale. I'm thinking that the new bitmap that's being created is just a regular 32bppargb bitmap with a greyscale image stuck into it.

I tried doing:

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

but i end up getting an "out of memory error".

What am i doing wrong? Also, are there any other ways to minimize the size of my bitmap?

EDIT:

So in an effort to take baby steps to tackle this problem, I'm using this code to convert the 32bpp bitmap to a 16bpp bitmap

        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

I tried doing Format16bbpGreyscale or Format16bppRgb555, but both of those cause "Our of memory errors". The only one that seems to work is the Format16bppRgb256

Regardless, I'm doing my packet sniffing again, and changing the format to 16bppRgb265 INCREASES the size of the image packet from ~26KB to 29KB. So changing to this format seems to increase size. I don't understand ;_;

EDIT2:

I've found multiple ways to convert the image to greyscale now and/or changing the pixelformat of the bitmap to something smaller than 32bpp. Unfortunately none of this seems to decrease the size of the serialized bitmap when it's being sent over the network. Some things seem to even increase the size. Not sure what i can do.

Upvotes: 0

Views: 1100

Answers (3)

Matviy Kotoniy
Matviy Kotoniy

Reputation: 432

So eventually I've figured out the problem.

Essentially i had to binary serialize a bitmap and transmit it over a network stream. And i was trying to decrease the size of the bitmap to make transfer faster.

.NET's "image" class seems to only support bitmaps in certain pixelformats. So no matter what i did to the image (greyscaled, lossy compression, whatever) the size would be the same because i wasn't change the pixelformat of the image, s i was just moving pixels around and changing their colors.

From what i know, there is no native class for JPGs or PNGs that i could serialize, so i was forced to use the image class with it's pixel formats.

One thing i tried was to convert the compressed, greyscaled image into a jpeg, and then convert that into a byte(), and then gzip and serialize that byte(). Problem is that resulted in a 2x increase in network data being transmitted for some reason.

An interesting quick note, is that when you serialize an image object, it is converted to PNG format (compressed), according to the network traffic i sniffed anyway. So there is some optimization that has been done by microsoft to make image serialization efficient.

So i was pretty much forced to use the image class, and just somehow figure out how to convert my image into the 1bpp or 8bpp pixelformats. The 16bpp formats (greyscale for instance) are randomly "unsupported" by microsoft, and just "don't work", and apparently never will. Of course MSDN doesn't mention any of this.

Converting my image to 8bpp or lower was impossible, because i would get "out of memory" errors for unknown reasons, or something about not being allowed to draw on indexed images.

The solution i finally found was the CopyToBpp() function from here: http://www.wischik.com/lu/programmer/1bpp.html

That function, along with the many API's in it, allowed me to quickly convert my 32bpp Image into an 8bpp or even 1bbp image. Which could then be easily and efficiently serialized a binary formatter and sent over my network stream.

So now it works.

Upvotes: 0

Converting to greyscale doesnt do much by itself because all you are doing is changing the RGB values of the pixels. Unfortunately many of the greyscale formats are not fully supported, though there are some opensource image libraries which will do this.

Significant reduction can be gotten using JPG and some quality reduction. 26kb for a full size (?) screenshot doesn't sound all that large (or it is only part of a screen?), and we dont know what your desired target size is. Here is how to reduce quality via 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

Metrics from a screen capture (ACTUAL size depends on screen size and what is on it; the size differences are what is important):

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

The content of the image plays a role in determining the sizes etc. In this case, PNG seems best size/quality balance; you'd have to compress JPG quite a bit to get the same size but with much less quality.

An actual photo type image will result in much larger sizes: 19MB for a 2500x1900 image and almost 13MB for a PNG, so test using actual images.

Upvotes: 1

TaW
TaW

Reputation: 54453

I recommend checking out Aforge's AForge.Imaging.ColorReduction.ColorImageQuantizer .

It reduced a screenshot of a SO homepage from 96kB to 33kB (going to 16 colors) while maintaining readabilty much better that an equally reduced jpg. Reducing to 32 or 64 colors left almost no artifacts, other than color changes while still staying at 48kB.

It does take a few seconds for processing, though..

Here is a piece of code that uses the Aforge libraries.

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);
    }
}

If you're interested I also have a home-grown piece of code, that results in 40% of the original size with perfext text, albeit a little color shift; it is very fast.

Upvotes: 1

Related Questions