How to create and write an image from a 2D array? - c#

I have a Color[,] array (2d array of Drawing.Color) in C#. How can I save that locally as a PNG?
Using only .Net official packages provided in the build, no additional library or Nuget packages.

The simple way
First create a blank Bitmap instance with the desired dimensions:
Bitmap bmp = new Bitmap(100, 100);
Loop through your colors array and plot pixels:
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
bmp.SetPixel(i, j, colors[i,j]);
}
}
Finally, save your bitmap to file:
bmp.Save("myfile.png", ImageFormat.Png);
The faster way
The Bitmap.SetPixel method is slow. A much faster way of accessing a bitmap's pixels is by writing directly to an array of 32-bit values (assuming you're shooting for a 32-bit PNG), and have the Bitmap class use that array as its backing.
One way to do this is by creating said array, and getting a GCHandle on it to prevent it from being garbage-collected. The Bitmap class offers a constructor that allows you to create an instance from an array pointer, a pixel format, and a stride (the number of bytes that make up a single row of the pixel data):
public Bitmap (int width, int height, int stride,
System.Drawing.Imaging.PixelFormat format, IntPtr scan0);
This is how you would create a backing array, a handle, and a Bitmap instance:
Int32[] bits = new Int32[width * height];
GCHandle handle = GCHandle.Alloc(bits, GCHandleType.Pinned);
Bitmap bmp = new Bitmap(width, height, width * 4,
PixelFormat.Format32bppPArgb, handle.AddrOfPinnedObject());
Note that:
The backing array has 32-bit entries, since we are working with a 32-bit pixel format
The Bitmap stride is width*4, which is the number of bytes a single row of pixels takes up (4 bytes per pixel)
With this, you can now write pixel values directly into the backing array, and they'll be reflected in the Bitmap. This is much faster than using Bitmap.SetPixel. Here is a code sample, which assumes that you've wrapped up everything in a class that knows how wide and tall the bitmap is:
public void SetPixelValue(int x, int y, int color)
{
// Out of bounds?
if (x < 0 || x >= Width || y < 0 || y >= Height) return;
int index = x + (y * Width);
Bits[index] = color;
}
Please note that color is an int value, not a Color value. If you have an array of Color values, you'll have to convert each to an int first, e.g.
public void SetPixelColor(int x, int y, Color color)
{
SetPixelValue(x, y, color.ToArgb());
}
This conversion will take time, so it's better to work with int values all the way. You can make this faster still by forgoing the x/y bounds check, if you're sure you're never using out-of-bounds coordinates:
public void SetPixelValueUnchecked(int x, int y, int color)
{
// No out of bounds checking.
int index = x + (y * Width);
Bits[index] = color;
}
A caveat is in order here. If you wrap Bitmap this way, you'll still be able to use Graphics to draw things like lines, rectangles, circles etc. by accessing the Bitmap instance directly, but without the speed gain of going through the pinned array. If you want these primitives to be drawn more quickly as well, you'll have to provide your own line/circle implementations. Note that in my experience, your own Bresenham line routine will hardly outperform GDI's built-in one, so it may not be worth it.
An even faster way
Things could be faster still if you're able to set multiple pixels in one go. This would apply if you have a horizontal sequence of pixels with the same color value. The fastest way I've found of setting sequences in an array is using Buffer.BlockCopy. (See here for a discussion). Here is an implementation:
/// <summary>
/// Set a sequential stretch of integers in the bitmap to a specified value.
/// This is done using a Buffer.BlockCopy that duplicates its size on each
/// pass for speed.
/// </summary>
/// <param name="value">Fill value</param>
/// <param name="startIndex">Fill start index</param>
/// <param name="count">Number of ints to fill</param>
private void FillUsingBlockCopy(Int32 value, int startIndex, int count)
{
int numBytesInItem = 4;
int block = 32, index = startIndex;
int endIndex = startIndex + Math.Min(block, count);
while (index < endIndex) // Fill the initial block
Bits[index++] = value;
endIndex = startIndex + count;
for (; index < endIndex; index += block, block *= 2)
{
int actualBlockSize = Math.Min(block, endIndex - index);
Buffer.BlockCopy(Bits, startIndex * numBytesInItem, Bits, index * numBytesInItem, actualBlockSize * numBytesInItem);
}
}
This would be particularly useful when you need a fast way to clear the bitmap, fill a rectangle or a triangle using horizontal lines (for example after triangle rasterization).

// Color 2D Array
var imgColors = new Color[128, 128];
// Get Image Width And Height Form Color Array
int imageH = imgColors.GetLength(0);
int imageW = imgColors.GetLength(1);
// Create Image Instance
Bitmap img = new Bitmap(imageW, imageH);
// Fill Colors on Our Image
for (int x = 0; x < img.Width; ++x)
{
for (int y = 0; y < img.Height; ++y)
{
img.SetPixel(x, y, imgColors[x, y]);
}
}
// Just Save it
img.Save("image.png", ImageFormat.Png);

Related

How convert picture with colored text to picture with white back and black fontcolor

I need to convert pictures with colored text on colored background to image with white background and black font color. Example of original image:
I tried grayscale from Convert an image to grayscale but it's not for me because I need only white back and black font. Result of grayscale method:
When I tried divide original image to Dictionary<pixelColorCode, countInImage>. I think the most popular color is color of background original image, and other colors are for text on image. So I draw new image like original, but with white and black pixels:
for (int i = 0; i < originalImage.Width; i++)
{
for (int j = 0; j < originalImage.Height; j++)
{
if (originalImage.GetPixel(i, j).ToArgb == mostPopularColorOfOriginalImage)
{
newBitmap.SetPixel(i, j, Color.White);
}
else
{
newBitmap.SetPixel(i, j, Color.Black);
}
}
}
It's better, but text in black-and-white image is not good. Result:
The basic method of solving this is the following:
Go over the image, and build up a histogram of all occurring colours. Since colours can be represented as an Int32, You can use a Dictionary<Int32,Int32> for this.
Take the two top colours from this histogram. Designate the most commonly occurring one as the "background colour" and the second as the "content colour".
Take the differences in R, G and B between the background and the content colour, and use those to make a colour palette with a smooth 256-colour fade from one colour to the other.
Go over all pixels of the image, and for each pixel, use the Pythagorean distance in 3D colour space to determine which colour from the generated palette it is the closest to.
On the resulting image, set the colours to a smooth fade from black to white.
Now, GetPixel and SetPixel, when looped over an entire image, are ridiculously slow, because they have to perform a rather heavy LockBits operation on the image for every pixel they handle. So instead, you can perform the LockBits operation once on the entire image, copy the bytes out, do all your operations on the resulting byte array, and then use LockBits a second time to copy the result into a new image.
Since we're dealing with grayscale colours here, it's also probably more efficient to write the end result into a 8-bit image. This also makes it trivially easy to manipulate the colour palette after doing the actual colour matching.
If you instead prefer to match it to exactly two colours, the method is exactly the same, except that instead of generating a colour fade between the two colours, the palette to match to will only contain the two found colours. Since the only possible indices to match on that will be 0 and 1, the final image's palette will likewise just need to have index 0 and index 1 set to black and white, rather than getting a whole grayscale fade.
The resulting method:
/// <summary>
/// Finds the two most prominent colours in an image, and uses them as
/// extremes for matching all pixels on the image to a grayscale palette.
/// </summary>
/// <param name="image">Image to reduce.</param>
/// <param name="bgWhite">True if the background (the most found colour) should become the white colour. If not, it will be the black one.</param>
/// <returns>
/// An 8-bit image with the image content of the input reduced to grayscale,
/// with the found two most found colours as black and white.
/// </returns>
public static Bitmap ReduceToTwoColorFade(Bitmap image, Boolean bgWhite)
{
// Get data out of the image, using LockBits and Marshal.Copy
Int32 width = image.Width;
Int32 height = image.Height;
// LockBits can actually -convert- the image data to the requested colour depth.
// 32 bpp is the easiest to get the colour components out.
BitmapData sourceData = image.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
// Not really needed for 32bpp, but technically the stride does not always match the
// amount of used data on each line, since the stride gets rounded up to blocks of 4.
Int32 stride = sourceData.Stride;
Byte[] imgBytes = new Byte[stride * height];
Marshal.Copy(sourceData.Scan0, imgBytes, 0, imgBytes.Length);
image.UnlockBits(sourceData);
// Make colour population histogram
Int32 lineOffset = 0;
Dictionary<Int32, Int32> histogram = new Dictionary<Int32, Int32>();
for (Int32 y = 0; y < height; y++)
{
Int32 offset = lineOffset;
for (Int32 x = 0; x < width; x++)
{
// Optional check: only handle if not mostly-transparent
if (imgBytes[offset + 3] > 0x7F)
{
// Get colour values from bytes, without alpha.
// Little-endian: UInt32 0xAARRGGBB = Byte[] { BB, GG, RR, AA }
Int32 val = (imgBytes[offset + 2] << 16) | (imgBytes[offset + 1] << 8) | imgBytes[offset + 0];
if (histogram.ContainsKey(val))
histogram[val] = histogram[val] + 1;
else
histogram[val] = 1;
}
offset += 4;
}
lineOffset += stride;
}
// Sort the histogram. This requires System.Linq
KeyValuePair<Int32, Int32>[] histoSorted = histogram.OrderByDescending(c => c.Value).ToArray();
// Technically these colours will be transparent when built like this, since their
// alpha is 0, but we won't use them directly as colours anyway.
// Since we filter on alpha, getting a result is not 100% guaranteed.
Color colBackgr = histoSorted.Length < 1 ? Color.Black : Color.FromArgb(histoSorted[0].Key);
// if less than 2 colors, just default it to the same.
Color colContent = histoSorted.Length < 2 ? colBackgr : Color.FromArgb(histoSorted[1].Key);
// Make a new 256-colour palette, making a fade between these two colours, for feeding into GetClosestPaletteIndexMatch later
Color[] matchPal = new Color[0x100];
Color toBlack = bgWhite ? colContent : colBackgr;
Color toWhite = bgWhite ? colBackgr : colContent;
Int32 rFirst = toBlack.R;
Int32 gFirst = toBlack.G;
Int32 bFirst = toBlack.B;
Double rDif = (toBlack.R - toWhite.R) / 255.0;
Double gDif = (toBlack.G - toWhite.G) / 255.0;
Double bDif = (toBlack.B - toWhite.B) / 255.0;
for (Int32 i = 0; i < 0x100; i++)
matchPal[i] = Color.FromArgb(
Math.Min(0xFF, Math.Max(0, rFirst - (Int32)Math.Round(rDif * i, MidpointRounding.AwayFromZero))),
Math.Min(0xFF, Math.Max(0, gFirst - (Int32)Math.Round(gDif * i, MidpointRounding.AwayFromZero))),
Math.Min(0xFF, Math.Max(0, bFirst - (Int32)Math.Round(bDif * i, MidpointRounding.AwayFromZero))));
// Ensure start and end point are correct, and not mangled by small rounding errors.
matchPal[0x00] = Color.FromArgb(toBlack.R, toBlack.G, toBlack.B);
matchPal[0xFF] = Color.FromArgb(toWhite.R, toWhite.G, toWhite.B);
// The 8-bit stride is simply the width in this case.
Int32 stride8Bit = width;
// Make 8-bit array to store the result
Byte[] imgBytes8Bit = new Byte[stride8Bit * height];
// Reset offset for a new loop through the image data
lineOffset = 0;
// Make new offset var for a loop through the 8-bit image data
Int32 lineOffset8Bit = 0;
for (Int32 y = 0; y < height; y++)
{
Int32 offset = lineOffset;
Int32 offset8Bit = lineOffset8Bit;
for (Int32 x = 0; x < width; x++)
{
Int32 toWrite;
// If transparent, revert to background colour.
if (imgBytes[offset + 3] <= 0x7F)
{
toWrite = bgWhite ? 0xFF : 0x00;
}
else
{
Color col = Color.FromArgb(imgBytes[offset + 2], imgBytes[offset + 1], imgBytes[offset + 0]);
toWrite = GetClosestPaletteIndexMatch(col, matchPal);
}
// Write the found colour index to the 8-bit byte array.
imgBytes8Bit[offset8Bit] = (Byte)toWrite;
offset += 4;
offset8Bit++;
}
lineOffset += stride;
lineOffset8Bit += stride8Bit;
}
// Make new 8-bit image and copy the data into it.
Bitmap newBm = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
BitmapData targetData = newBm.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newBm.PixelFormat);
// get minimum data width for the pixel format.
Int32 newDataWidth = ((Image.GetPixelFormatSize(newBm.PixelFormat) * width) + 7) / 8;
// Note that this Stride will most likely NOT match the image width; it is rounded up to the
// next multiple of 4 bytes. For that reason, we copy the data per line, and not as one block.
Int32 targetStride = targetData.Stride;
Int64 scan0 = targetData.Scan0.ToInt64();
for (Int32 y = 0; y < height; ++y)
Marshal.Copy(imgBytes8Bit, y * stride8Bit, new IntPtr(scan0 + y * targetStride), newDataWidth);
newBm.UnlockBits(targetData);
// Set final image palette to grayscale fade.
// 'Image.Palette' makes a COPY of the palette when accessed.
// So copy it out, modify it, then copy it back in.
ColorPalette pal = newBm.Palette;
for (Int32 i = 0; i < 0x100; i++)
pal.Entries[i] = Color.FromArgb(i, i, i);
newBm.Palette = pal;
return newBm;
}
The used GetClosestPaletteIndexMatch function:
/// <summary>
/// Uses Pythagorean distance in 3D colour space to find the closest match to a given colour on
/// a given colour palette, and returns the index on the palette at which that match was found.
/// </summary>
/// <param name="col">The colour to find the closest match to</param>
/// <param name="colorPalette">The palette of available colours to match</param>
/// <returns>The index on the palette of the colour that is the closest to the given colour.</returns>
public static Int32 GetClosestPaletteIndexMatch(Color col, Color[] colorPalette)
{
Int32 colorMatch = 0;
Int32 leastDistance = Int32.MaxValue;
Int32 red = col.R;
Int32 green = col.G;
Int32 blue = col.B;
for (Int32 i = 0; i < colorPalette.Length; ++i)
{
Color paletteColor = colorPalette[i];
Int32 redDistance = paletteColor.R - red;
Int32 greenDistance = paletteColor.G - green;
Int32 blueDistance = paletteColor.B - blue;
// Technically, Pythagorean distance needs to have a root taken of the result, but this is not needed for just comparing them.
Int32 distance = (redDistance * redDistance) + (greenDistance * greenDistance) + (blueDistance * blueDistance);
if (distance >= leastDistance)
continue;
colorMatch = i;
leastDistance = distance;
if (distance == 0)
return i;
}
return colorMatch;
}
The result:

C# How do I convert my get GetPixel / SetPixel color processing to Lockbits?

EDIT: I deeply appreciate the replies. What I need more than anything here is sample code for what I do with the few lines of code in the nested loop, since that's what works right in GetPixel/SetPixel, but also what I can't get to work right using Lockbits. Thank you
I'm trying to convert my image processing filters from GetPixel / SetPixel to Lockbits, to improve processing time. I have seen Lockbits tutorials here on Stack Overflow, MSDN, and other sites as well, but I'm doing something wrong. I'm starting with an exceedingly simple filter, which simply reduces green to create a red and purple effect. Here's my code:
private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e)
{
// Get bitmap from picturebox
Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone();
// search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0.
for (int y = 0; y < bmpMain.Height; y++)
for (int x = 0; x < bmpMain.Width; x++)
{
bmpMain.GetPixel(x, y);
Color c = bmpMain.GetPixel(x, y);
int myRed = c.R, myGreen = c.G, myBlue = c.B;
myGreen -= 128;
if (myGreen < 0) myGreen = 0;
bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue));
}
// assign the new bitmap to the picturebox
pictureBoxMain.Image = (Bitmap)bmpMain;
// Save a copy to the HD for undo / redo.
string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine);
pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png);
}
So that GetPixel / SetPixel code works fine, but it's slow. So I tried this:
private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e)
{
// Get bitmap from picturebox
Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone();
Rectangle rect = new Rectangle(Point.Empty, bmpMain.Size);
BitmapData bmpData = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat);
// search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0.
for (int y = 0; y < bmpMain.Height; y++)
for (int x = 0; x < bmpMain.Width; x++)
{
bmpMain.GetPixel(x, y);
Color c = new Color();
int myRed = c.R, myGreen = c.G, myBlue = c.B;
myGreen -= 128;
if (myGreen < 0) myGreen = 0;
bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue));
}
bmpMain.UnlockBits(bmpData);
// assign the new bitmap to the picturebox
pictureBoxMain.Image = (Bitmap)bmpMain;
// Save a copy to the HD for undo / redo.
string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine);
pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png);
}
Which throws the error "An unhandled exception of type 'System.InvalidOperationException' occurred in System.Drawing.dll Additional information: Bitmap region is already locked" when it reaches the first line of the nested loop.
I realize this has to be a beginner's error, I'd appreciate if someone could demonstrate the correct way to convert this very simple filter to Lockbits. Thank you very much
The array returned by scan0 is in this format BGRA BGRA BGRA BGRA ... and so on,
where B = Blue, G = Green, R = Red, A = Alpha.
Example of a very small bitmap 4 pixels wide and 3 pixels height.
BGRA BGRA BGRA BGRA
BGRA BGRA BGRA BGRA
BGRA BGRA BGRA BGRA
stride = width*bytesPerPixel = 4*4 = 16 bytes
height = 3
maxLenght = stride*height= 16*3 = 48 bytes
To reach a certain pixel in the image (x, y) use this formula
int certainPixel = bytesPerPixel*x + stride * y;
B = scan0[certainPixel + 0];
G = scan0[certainPixel + 1];
R = scan0[certainPixel + 2];
A = scan0[certainPixel + 3];
public unsafe void Test(Bitmap bmp)
{
int width = bmp.Width;
int height = bmp.Height;
//TODO determine bytes per pixel
int bytesPerPixel = 4; // we assume that image is Format32bppArgb
int maxPointerLenght = width * height * bytesPerPixel;
int stride = width * bytesPerPixel;
byte R, G, B, A;
BitmapData bData = bmp.LockBits(
new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadWrite, bmp.PixelFormat);
byte* scan0 = (byte*)bData.Scan0.ToPointer();
for (int i = 0; i < maxPointerLenght; i += 4)
{
B = scan0[i + 0];
G = scan0[i + 1];
R = scan0[i + 2];
A = scan0[i + 3];
// do anything with the colors
// Set the green component to 0
G = 0;
// do something with red
R = R < 54 ? (byte)(R + 127) : R;
R = R > 255 ? 255 : R;
}
bmp.UnlockBits(bData);
}
You can test is yourself. Create a very small bitmap ( few pixels wide/height) in paint or any other program and put a breakpoint at the begining of the method.
Additional information: Bitmap region is already locked"
You now know why GetPixel() is slow, it also uses Un/LockBits under the hood. But does so for each individual pixel, the overhead steals cpu cycles. A bitmap can be locked only once, that's why you got the exception. Also the basic reason that you can't access a bitmap in multiple threads simultaneously.
The point of LockBits is that you can access the memory occupied by the bitmap pixels directly. The BitmapData.Scan0 member gives you the memory address. Directly addressing the memory is very fast. You'll however have to work with an IntPtr, the type of Scan0, that requires using a pointer or Marshal.Copy(). Using a pointer is the optimal way, there are many existing examples on how to do this, I won't repeat it here.
... = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat);
The last argument you pass is very, very important. It selects the pixel format of the data and that affects the code you write. Using bmpMain.PixelFormat is the fastest way to lock but it is also very inconvenient. Since that now requires you to adapt your code to the specific pixel format. There are many, take a good look at the PixelFormat enum. They differ in the number of bytes taken for each pixel and how the colors are encoded in the bits.
The only convenient pixel format is Format32bppArgb, every pixel takes 4 bytes, the color/alpha is encoded in a single byte and you can very easily and quickly address the pixels with an uint*. You can still deal with Format24bppRgb but you now need a byte*, that's a lot slower. The ones that have a P in the name are pre-multiplied formats, very fast to display but exceedingly awkward to deal with. You may thus be well ahead by taking the perf hit of forcing LockBits() to convert the pixel format. Paying attention to the pixel format up front is important to avoid this kind of lossage.

How do I set a color other than black to image created with makeTransparent()

I am using the MakeTransparent() function call on the c# Bitmap object to convert images to transparent images. When this method is called, it will convert a background color to be transparent by setting the alpha channel, but it then converts the background color to black.
I need to find a fast way of converting this background color back to white or whatever the original color was, as occasionally I need to flatten the image to a non-alpha channel enabled format.
Make transparent doesn't seem to have any flags or overloads that allows you to tell it to leave the background color alone, and altering the image pixel by pixel is way to inefficient. Anyone have any suggestions or GDI tricks to solve this problem?
There doesn't seem to be a fast way to do this using the managed code interface. Using individual pixel manipulation, or using unmanaged code to update the pixels seem to be the only real options.
This is actually possible in managed code, by using Marshal.Copy to copy the backing byte array out of a bitmap object, then editing it, and then copying it back.
So basically, with that general method in mind, you just go over the pixels, line by line, detect which pixels have the colour you want replaced, and set their alpha byte to 0.
Note that "ARGB" refers to the order of the components inside the Int32 value of one read pixel. Since this value is little-endian, the actual order of the bytes at a given offset is the reverse; B = offset + 0, G = offset + 1, R = offset + 2, A = offset + 3.
/// <summary>
/// Clears the alpha value of all pixels matching the given colour.
/// </summary>
public static Bitmap MakeTransparentKeepColour(Bitmap image, Color clearColour)
{
Int32 width = image.Width;
Int32 height = image.Height;
// Paint on 32bppargb, so we're sure of the byte data format
Bitmap bm32 = new Bitmap(width, height, PixelFormat.Format32bppArgb);
using (Graphics gr = Graphics.FromImage(bm32))
gr.DrawImage(image, new Rectangle(0, 0, width, height));
BitmapData sourceData = bm32.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, bm32.PixelFormat);
Int32 stride = sourceData.Stride;
// Copy the image data into a local array so we can use managed functions to manipulate it.
Byte[] data = new Byte[stride * height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
Byte colR = clearColour.R;
Byte colG = clearColour.G;
Byte colB = clearColour.B;
for (Int32 y = 0; y < height; y++)
{
Int32 inputOffs = y * stride;
for (Int32 x = 0; x < width; x++)
{
if (data[inputOffs + 2] == colR && data[inputOffs + 1] == colG && data[inputOffs] == colB)
data[inputOffs + 3] = 0;
inputOffs += 4;
}
}
// Copy the edited image data back.
Marshal.Copy(data, 0, sourceData.Scan0, data.Length);
bm32.UnlockBits(sourceData);
return bm32;
}
This can easily be enhanced with a tolerance level instead of an exact match, with something like Math.Abs(data[inputOffs + 2] - colR) < tolerance, or by actually converting the bytes to a colour object and doing some other kind of approximation (like hue/saturation/brightness).

Reducing color depth in an image is not reducin the file size?

I use this code to reduce the depth of an image:
public void ApplyDecreaseColourDepth(int offset)
{
int A, R, G, B;
Color pixelColor;
for (int y = 0; y < bitmapImage.Height; y++)
{
for (int x = 0; x < bitmapImage.Width; x++)
{
pixelColor = bitmapImage.GetPixel(x, y);
A = pixelColor.A;
R = ((pixelColor.R + (offset / 2)) - ((pixelColor.R + (offset / 2)) % offset) - 1);
if (R < 0)
{
R = 0;
}
G = ((pixelColor.G + (offset / 2)) - ((pixelColor.G + (offset / 2)) % offset) - 1);
if (G < 0)
{
G = 0;
}
B = ((pixelColor.B + (offset / 2)) - ((pixelColor.B + (offset / 2)) % offset) - 1);
if (B < 0)
{
B = 0;
}
bitmapImage.SetPixel(x, y, Color.FromArgb(A, R, G, B));
}
}
}
first question is: the offset that I give the function is not the depth, is that right?
the second is that when I try to save the image after I reduce the depth of its colors, I get the same size of the original Image. Isn't it logical that I should get a file with a less size, or I am wrong.
This is the code that I use to save the modified image:
private Bitmap bitmapImage;
public void SaveImage(string path)
{
bitmapImage.Save(path);
}
You are just setting the pixel values to a lower level.
For example, is if a pixel is represented by 3 channels with 16 bits per channel, you are reducing each pixel colour value to 8-bits per channel. This will never reduce the image size as the pixels allocated have already a fixed depth of 16 bits.
Try saving the new values to a new image with maximum of 8-bit depth.
Surely you will have a reduced image in bytes but not the overall size that is, X,Y dimensions of the image will remain intact. What you are doing will reduce image quality.
Let's start by cleaning up the code a bit. The following pattern:
R = ((pixelColor.R + (offset / 2)) - ((pixelColor.R + (offset / 2)) % offset) - 1);
if (R < 0)
{
R = 0;
}
Is equivalent to this:
R = Math.Max(0, (pixelColor.R + offset / 2) / offset * offset - 1);
You can thus simplify your function to this:
public void ApplyDecreaseColourDepth(int offset)
{
for (int y = 0; y < bitmapImage.Height; y++)
{
for (int x = 0; x < bitmapImage.Width; x++)
{
int pixelColor = bitmapImage.GetPixel(x, y);
int A = pixel.A;
int R = Math.Max(0, (pixelColor.R + offset / 2) / offset * offset - 1);
int G = Math.Max(0, (pixelColor.G + offset / 2) / offset * offset - 1);
int B = Math.Max(0, (pixelColor.B + offset / 2) / offset * offset - 1);
bitmapImage.SetPixel(x, y, Color.FromArgb(A, R, G, B));
}
}
}
To answer your questions:
Correct; the offset is the size of the steps in the step function. The depth per color component is the original depth minus log2(offset). For example, if the original image has a depth of eight bits per component (bpc) and the offset is 16, then the depth of each component is 8 - log2(16) = 8 - 4 = 4 bpc. Note, however, that this only indicates how much entropy each output component can hold, not how many bits per component will actually be used to store the result.
The size of the output file depends on the stored color depth and the compression used. Simply reducing the number of distinct values each component can have won't automatically result in fewer bits being used per component, so an uncompressed image won't shrink unless you explicitly choose an encoding that uses fewer bits per component. If you are saving a compressed format such as PNG, you might see an improvement with the transformed image, or you might not; it depends on the content of the image. Images with a lot of flat untextured areas, such as line art drawings, will see negligible improvement, whereas photos will probably benefit noticeably from the transform (albeit at the expense of perceptual quality).
First i would like to ask you one simple question :)
int i = 10;
and now i = i--;
douse it effect on size of i ?
ans is No.
you are doing the same thing
Index imaged are represent in two matrix
1 for color mapping and
2 fro image mapping
you just change the value of element not deleting it
so it will not effect on size of image
You can't decrease color depth with Get/SetPixel. Those methods only change the color.
It seems you can't easily save an image to a certain pixel format, but I did find some code to change the pixel format in memory. You can try saving it, and it might work, depending what format you save to.
From this question: https://stackoverflow.com/a/2379838/785745
He gives this code to change color depth:
public static Bitmap ConvertTo16bpp(Image img) {
var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format16bppRgb555);
using (var gr = Graphics.FromImage(bmp))
{
gr.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));
}
return bmp;
}
You can change the PixelFormat in the code to whatever you need.
A Bitmap image of a certain pixel count is always the same size, because the bitmap format does not apply compression.
If you compress the image with an algorithm (e.g. JPEG) then the 'reduced' image should be smaller.
R = ((pixelColor.R + (offset / 2)) - ((pixelColor.R + (offset / 2))
Doesn't this always return 0?
If you want to reduce the size of your image, you can specify a different compression format when calling Image.Save().
GIF file format is probably a good candidate, since it works best with contiguous pixels of identical color (which happens more often when your color depth is low).
JPEG works great with photos, but you won't see significant results if you convert a 24-bit image into a 16-bit one and then compresses it using JPEG, because of the way the algorithm works (you're better off saving the 24-bit pictures as JPEG directly).
And as others have explained, your code won't reduce the size used by the Image object unless you actually copy the resulting data into another Bitmap object with a different PixelFormat such as Format16bppRgb555.

How to convert a byte array of 19200 bytes in size where each byte represents 4 pixels (2 bits per pixel) to a bitmap arranged as 320x240 characters

I am communicating with an instrument (remote controlling it) and
one of the things I need to do is to draw the instrument screen.
In order to get the screen I issue a command and the instrument
replies with an array of bytes that represents the screen.
Below is what the instrument manual has to say about converting the response to the actual screen:
The command retrieves the framebuffer data used for the display.
It is 19200 bytes in size, 2-bits per pixel, 4 pixels per byte arranged as
320x240 characteres.
The data is sent in RLE encoded form.
To convert this data into a BMP for use in Windows, it needs to be
turned into a 4BPP. Also note that BMP files are upside down relative
to this data, i.e. the top display line is the last line in the BMP.
I managed to unpack the data, but now I am stuck on how to actually
go from the unpacked byte array to a bitmap.
My background on this is pretty close to zero and my searches
have not revealed much either.
I am looking for directions and/or articles I could use to help me
undestand how to get this done.
Any code or even pseudo code would also help. :-)
So, just to summarize it all:
How to convert a byte array of 19200 bytes in size, where
each byte represents 4 pixels (2 bits per pixel),
to a bitmap arranged as 320x240 characters.
Thanks in advance.
To do something like this, you'll want a routine like this:
Bitmap ConvertToBitmap(byte[] data, int width, int height)
{
Bitmap bm = new Bitmap(width, height, PixelFormat.Format24bppRgb);
for (int y=0; y < height; y++) {
for (int x=0; x < width; x++) {
int value = ReadPixelValue(data, x, y, width);
Color c = ConvertValToColor(value);
bm.SetPixel(x, y, c);
}
}
return bm;
}
from here, you need ReadPixelValue and ConvertValToColor.
static int ReadPixelValue(byte[] data, int x, int y, width)
{
int pixelsPerByte = 4;
// added the % pixelsPerByte to deal with width not being a multiple of pixelsPerByte,
// which won't happen in your case, but will in the general case
int bytesPerLine = width / pixelsPerByte + (width % pixelsPerByte != 0 ? 1 : 0);
int index = y * bytesPerLine + (x / pixelsPerByte);
byte b = data[index];
int pixelIndex = (x % pixelsPerByte) * 2;
// if every 4 pixels are reversed, try this:
// int pixelIndex = 8 - (x % pixelsPerByte) * 2;
return ((int b) >> pixelIndex) & 0x3;
}
Basically, I pull each set of two bits out of each byte and return it as an int.
As for converting to color that's up to you how to make heads or tail of the 4 values that come back.
Most likely you can do something like this:
static Color[] _colors = new Color[] { Color.Black, Color.Red, Color.Blue, Color.White };
static Color ConvertValToColor(int val)
{
if (val < 0 || val > _colors.Length)
throw new ArgumentOutOfRangeException("val");
return _colors[val];
}
If you have two bits per pixel, for each pixel you have 4 different possible colors. Probably the colors are indexed or just hardcoded (i.e. 0 means black, 1 white, etc).
Don't know if this is of much help ( I don't know what bitmap object you are using, but perhaps it has a regular RGB or ARGB scheme with 1 byte per channel), but in pseudo-actionscript, I think you should do something like this.
// 80 -> 320 / 4
for(var x:int = 0; x < 80; x++) {
for(var y:int = 0; y < 240; y++) {
var byteVal:int = readByte();
var px_1:int = (byteVal >> 6) & 0x03;
var px_2:int = (byteVal >> 4) & 0x03;
var px_3:int = (byteVal >> 2) & 0x03;
var px_4:int = (byteVal) & 0x03;
// map your pixel value to ARGB
px_1 = getPixelValue(px_1);
px_2 = getPixelValue(px_2);
px_3 = getPixelValue(px_3);
px_4 = getPixelValue(px_4);
// assuming setPixel(x,y,pixelValue)
setPixel((x * 4), y, px_1);
setPixel((x * 4) + 1, y, px_2);
setPixel((x * 4) + 2, y, px_3);
setPixel((x * 4) + 3, y, px_4);
}
}
function getPixelValue(idx:int):uint {
// just an example...
switch(idx) {
case 0: return 0xff000000; // black
case 1: return 0xffffffff; // white
case 2: return 0xffff0000; // red
case 3: return 0xff0000ff; // blue
}
}
The above code, suffice it to say, is just to give you an idea (hopefully!) and is based on some assumptions like how these four pixels are stored in a byte.
Hope it makes sense.
I dont know if this helps, I use this for data I got from a rare old hardware:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.IO;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
byte[] imageBytes = new byte[19201];
//(Fill it with the data from the unit before doing the rest).
Bitmap bmp_workarea = new Bitmap(320, 240, System.Drawing.Imaging.PixelFormat.Format4bppIndexed);
Image newImage = Image.FromStream(new MemoryStream(imageBytes));
using (Graphics gr = Graphics.FromImage(bmp_workarea))
{
gr.DrawImage(newImage, new Rectangle(0, 0, bmp_workarea.Width, bmp_workarea.Height));
}
//now you can use newImage, for example picturebox1.image=newimage
}
}
}

Categories

Resources