Reputation: 432
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
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
Reputation: 38905
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
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