I have a bitmap that I am performing a colorizing transformation on. I have the new array of pixels but I'm not sure how to then save them back to disk as an image
public static void TestProcessBitmap(string inputFile, string outputFile)
{
Bitmap bitmap = new Bitmap(inputFile);
Bitmap formatted = bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
byte[] pixels = BitmapToPixelArray(formatted);
pixels = Process8Bits(pixels, System.Windows.Media.Colors.Red);
Bitmap output = new Bitmap(pixels); //something like this
}
How can I then save the new pixels as a bitmap on disk?
I do believe you can use the Bitmap.Save() method after you have loaded the bytes back into a Bitmap object. This post may give you some insight on how to do it.
According to this MSDN document, if you only specify a path while using Bitmap.Save(),
If no encoder exists for the file format of the image, the Portable
Network Graphics (PNG) encoder is used.
You can convert a byte array to a bitmap using a MemoryStream, and then feeding it into the Image.FromStream method. Your example with this in place would be..
public static void TestProcessBitmap(string inputFile, string outputFile)
{
Bitmap bitmap = new Bitmap(inputFile);
Bitmap formatted = bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
byte[] pixels = BitmapToPixelArray(formatted);
pixels = Process8Bits(pixels, System.Windows.Media.Colors.Red);
using (MemoryStream ms = new MemoryStream(pixels))
{
Bitmap output = (Bitmap)Image.FromStream(ms);
}
}
Related
I am successfully converting a JPG image file into a base64 string, and then that string into a JPG image again.
string str64 = ImageToStrBase64(My.Resources.ImageJpg);
PictureBox1.Image = Base64StrToImage(str64);
ImageToStrBase64() and Base64StrToImage() are custom functions just to explain the idea, but I can place the code if necessary.
I am also converting a raw byte array (RGB or BGR, no matters) into a base64.
However, I do now need to convert a raw byte array into a JPG encoded base64 string. I am finding solutions that involves only an image file saving, but file management it's high time consuming. How to encode then a BGR byte array into a JPG byte array using, for example a memory stream?
Function use to convert jpg into a formatted byte array:
public static string ImagetoStrBase64(System.Drawing.Image imageToEncode)
{
string base64String = "";
using (System.Drawing.Image image = imageToEncode)
{
using (MemoryStream m = new MemoryStream())
{
image.Save(m, image.RawFormat);
byte[] imageBytes = m.ToArray();
base64String = Convert.ToBase64String(imageBytes);
}
}
return base64String;
}
Update (regarding to chat)
Let's simplify it... We have a byte[] with single green pixel:
byte[] rawPixelArray = new byte[] {0, 255, 0};
Then, GetJpgBytes(rawPixelArray) must return an encoded byte[] for a Jpeg image of 1x1 pixel. This is more clear I guess :)
Before you read the answer, read the bullet points:
A real useful conversion, should return JPEG encoded image which is equivalent to content of a Jpeg file, which is not pixel data.
When you get an array of pixel data of a Jpeg image, you will have non of benefits of a Jpeg image. It's no more Jpeg encoded image, it's an array of pixel data which can be used to create a BitMap.
To better understand the point, for example for a 10K JPEG white image, the pixel data will be 2MB!
I've shared a few methods in this answer which are really useful standalone, and I've showed how you can combine them to get what you want. (While I cannot see any benefit in the conversion which you are trying to do.)
These are very useful pieces which you can put together to solve the puzzle:
ImageData: In addition to a byte[] of image pixels, you need to know about width, height and pixel format of the image. Then you can describe the image and create an Image from that data. Without having all these information a byte[] is meaningless on its own.
GetImageDataFromImage: Gets image data (width, height, pixel format, pixel data) from an Image
CreateImageFromImageData: Creates an Image from image data
ConvertImageToJpegImage: Converts an Image to Jpeg Image
ByteArrayToBase64: Converts a byte[] to Base64 string.
Then after having these pieces, you can achieve what you want.
Assuming you have a byte[] of pixel data, width, height and pixel format of your data, this is what you need:
// A 2x2 image having 24 bit RGB pixel format, all pixels are #FF0000 (Red)
var imageData = new ImageData()
{
Width = 2,
Height = 2,
PixelFormat = PixelFormat.Format24bppRgb,
PixelData = new byte[] {
0x0, 0x0, 0xFF, 0x0, 0x0, 0xFF,
0xF, 0x0, 0xFF, 0x0, 0x0, 0xFF
}
};
var image = CreateImageFromImageData(imageData);
var jpeg = ConvertImageToJpegImage(image);
var jpegImageData = GetImageDataFromImage(jpeg);
var jpegPixelsBase64 = ByteArrayToBase64(jpegImageData.PixelData);
Which result in AAD+AAD+AAD+AAD+ which is in fact an image having #FE0000 color!
Note: I didn't dispose images to keep it clear, in action, you need to dispose image and jpeg.
ImageData
Required information about an image, including width, height, pixel format, and pixel data array:
public class ImageData
{
public int Width { get; set; }
public int Height { get; set; }
public PixelFormat PixelFormat { get; set; }
public byte[] PixelData { get; set; }
}
GetImageDataFromImage
Gets image data from an image.
ImageData GetImageDataFromImage(Image image)
{
using (var bitmap = new Bitmap(image.Width, image.Height, image.PixelFormat))
{
using (var g = Graphics.FromImage(bitmap))
g.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height));
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly, bitmap.PixelFormat);
var rowLength = image.Width * Image.GetPixelFormatSize(image.PixelFormat) / 8;
var bytes = new byte[image.Height * rowLength];
var ptr = data.Scan0;
for (var i = 0; i < image.Height; i++)
{
Marshal.Copy(ptr, bytes, i * rowLength, rowLength);
ptr += data.Stride;
}
bitmap.UnlockBits(data);
return new ImageData
{
Width = bitmap.Width,
Height = bitmap.Height,
PixelFormat = bitmap.PixelFormat,
PixelData = bytes
};
}
}
We rely on LockBits to get or set image byte array.
CreateImageFromImageData
Creates image from image data:
Image CreateImageFromImageData(ImageData imageData)
{
var bitmap = new Bitmap(imageData.Width, imageData.Height, imageData.PixelFormat);
var data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadWrite, bitmap.PixelFormat);
var rowLength = imageData.Width * Image.GetPixelFormatSize(imageData.PixelFormat) / 8;
var bytes = new byte[imageData.Height * rowLength];
var ptr = data.Scan0;
for (var i = 0; i < imageData.Height; i++)
{
Marshal.Copy(imageData.PixelData, i * rowLength, ptr, rowLength);
ptr += data.Stride;
}
bitmap.UnlockBits(data);
return bitmap;
}
We rely on LockBits to get or set image byte array.
ConvertImageToJpegImage
Converts an image to Jpeg Image:
public Image ConvertImageToJpegImage(Image img)
{
using (var stream = new MemoryStream())
{
img.Save(stream, ImageFormat.Jpeg);
var bytes = stream.ToArray();
return (Image)new ImageConverter().ConvertFrom(bytes);
}
}
If you care about compression level use jpeg encoder.
ByteArrayToBase64
Conversion from byte[] to Base64String and is straightforward, but to have better readability of the answer and the code:
public string ByteArrayToBase64(byte[] bytes)
{
return Convert.ToBase64String(bytes);
}
I'm having some trouble converting an image to a video using the SharpAVI.dll.
I have managed to produce a video file using a randomly generated byte array by using the documentation on SharpAVI's website:
Getting Started with SharpAVI
So the next step I thought I would take was to take an Image, create a Bitmap image, convert the bitmap to a byte array and then simply save the byte array to each frame of the video file. When I run the program, I get no errors or anything and a video file of an appropriate file size is produced however the video file is unreadable and will not open. I'm really struggling to see why this won't work. Any help would be greatly appreciated!
My Code:
private void GenerateSingleImageVideo()
{
string imagePath = textBoxImagePath.Text;
Bitmap thisBitmap;
//generate bitmap from image file
using (Stream BitmapStream = System.IO.File.Open(imagePath, FileMode.Open))
{
Image img = Image.FromStream(BitmapStream);
thisBitmap = new Bitmap(img);
}
//convert the bitmap to a byte array
byte[] byteArray = BitmapToByteArray(thisBitmap);
//creates the writer of the file (to save the video)
var writer = new AviWriter(textBoxFileName.Text + ".avi")
{
FramesPerSecond = int.Parse(textBoxFrameRate.Text),
EmitIndex1 = true
};
var stream = writer.AddVideoStream();
stream.Width = thisBitmap.Width;
stream.Height = thisBitmap.Height;
stream.Codec = KnownFourCCs.Codecs.Uncompressed;
stream.BitsPerPixel = BitsPerPixel.Bpp32;
int numberOfFrames = ((int.Parse(textBoxFrameRate.Text)) * (int.Parse(textBoxVideoLength.Text)));
int count = 0;
while (count <= numberOfFrames)
{
stream.WriteFrame(true, byteArray, 0, byteArray.Length);
count++;
}
writer.Close();
MessageBox.Show("Done");
}
private byte[] BitmapToByteArray(Bitmap img)
{
ImageConverter converter = new ImageConverter();
return (byte[])converter.ConvertTo(img, typeof(byte[]));
}
You're wrong in assuming that you should pass a Bitmap object to WriteFrame method. It expects pixel data in bottom to top 32bpp format. See example in
// Buffer for pixel data
var buffer = new byte[width * height * 4];
...
// Copy pixels from Bitmap assuming it has expected 32bpp pixel format
var bits = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
Marshal.Copy(bits.Scan0, buffer, 0, buffer.Length);
bitmap.UnlockBits(bits);
You can see code of a sample app as a reference
https://github.com/baSSiLL/SharpAvi/blob/master/Sample/Recorder.cs
I have an API that takes the base64string of an image of size 80x20(either JPG, PNG or GIF) and stores in database. In order to test this API I have to generate a random base64string which can be converted into a real image when decoded.
I have find the example here which seems to work with WPF application. How to use the same for a console application?
A variety of methods should work. How about the following:
public static string GenerateBase64ImageString()
{
// 1. Create a bitmap
using (Bitmap bitmap = new Bitmap(80, 20, PixelFormat.Format24bppRgb))
{
// 2. Get access to the raw bitmap data
BitmapData data = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, bitmap.PixelFormat);
// 3. Generate RGB noise and write it to the bitmap's buffer.
// Note that we are assuming that data.Stride == 3 * data.Width for simplicity/brevity here.
byte[] noise = new byte[data.Width * data.Height * 3];
new Random().NextBytes(noise);
Marshal.Copy(noise, 0, data.Scan0, noise.Length);
bitmap.UnlockBits(data);
// 4. Save as JPEG and convert to Base64
using (MemoryStream jpegStream = new MemoryStream())
{
bitmap.Save(jpegStream, ImageFormat.Jpeg);
return Convert.ToBase64String(jpegStream.ToArray());
}
}
}
Just be sure to add a reference to System.Drawing.
I have a byte array that contains a jpeg image. I was just wondering if it is possible to reduce its size?
Edit: Ok. I acknowledged my mistake. Then my question is how can I reduce the quality of the image comes from a byte array.
Please understand that there is no free lunch. Decreasing the size of a JPEG image by increasing the compression will also decrease the quality of the image. However, that said, you can reduce the size of a JPEG image using the Image class. This code assumes that inputBytes contains the original image.
var jpegQuality = 50;
Image image;
using (var inputStream = new MemoryStream(inputBytes)) {
image = Image.FromStream(inputStream);
var jpegEncoder = ImageCodecInfo.GetImageDecoders()
.First(c => c.FormatID == ImageFormat.Jpeg.Guid);
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, jpegQuality);
Byte[] outputBytes;
using (var outputStream = new MemoryStream()) {
image.Save(outputStream, jpegEncoder, encoderParameters);
outputBytes = outputStream.ToArray();
}
}
Now outputBytes contains a recompressed version of the image using a different JPEG quality.
By decreasing the jpegQuality (should be in the range 0-100) you can increase the compression at the cost of lower image quality. See the Encoder.Quality field for more information.
Here is an example where you can see how jpegQuality affects the image quality. It is the same photo compressed using 20, 50 and 80 as the value of jpegQuality. Sizes are 4.99, 8.28 and 12.9 KB.
Notice how the text becomes "smudged" even when the quality is high. This is why you should avoid using JPEG for images with uniformly colored areas (images/diagrams/charts created on a computer). Use PNG instead. For photos JPEG is very suitable if you do not lower the quality too much.
Please try:
// Create a thumbnail in byte array format from the image encoded in the passed byte array.
// (RESIZE an image in a byte[] variable.)
public static byte[] CreateThumbnail(byte[] PassedImage, int LargestSide)
{
byte[] ReturnedThumbnail;
using (MemoryStream StartMemoryStream = new MemoryStream(),
NewMemoryStream = new MemoryStream())
{
// write the string to the stream
StartMemoryStream.Write(PassedImage, 0, PassedImage.Length);
// create the start Bitmap from the MemoryStream that contains the image
Bitmap startBitmap = new Bitmap(StartMemoryStream);
// set thumbnail height and width proportional to the original image.
int newHeight;
int newWidth;
double HW_ratio;
if (startBitmap.Height > startBitmap.Width)
{
newHeight = LargestSide;
HW_ratio = (double)((double)LargestSide / (double)startBitmap.Height);
newWidth = (int)(HW_ratio * (double)startBitmap.Width);
}
else
{
newWidth = LargestSide;
HW_ratio = (double)((double)LargestSide / (double)startBitmap.Width);
newHeight = (int)(HW_ratio * (double)startBitmap.Height);
}
// create a new Bitmap with dimensions for the thumbnail.
Bitmap newBitmap = new Bitmap(newWidth, newHeight);
// Copy the image from the START Bitmap into the NEW Bitmap.
// This will create a thumnail size of the same image.
newBitmap = ResizeImage(startBitmap, newWidth, newHeight);
// Save this image to the specified stream in the specified format.
newBitmap.Save(NewMemoryStream, System.Drawing.Imaging.ImageFormat.Jpeg);
// Fill the byte[] for the thumbnail from the new MemoryStream.
ReturnedThumbnail = NewMemoryStream.ToArray();
}
// return the resized image as a string of bytes.
return ReturnedThumbnail;
}
// Resize a Bitmap
private static Bitmap ResizeImage(Bitmap image, int width, int height)
{
Bitmap resizedImage = new Bitmap(width, height);
using (Graphics gfx = Graphics.FromImage(resizedImage))
{
gfx.DrawImage(image, new Rectangle(0, 0, width, height),
new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);
}
return resizedImage;
}
The byte array size can always be reduced but you will lose data about your image. You could reduce the quality of the jpeg then it would take up less data as a byte array.
Think of the JPEG bytes as being a very compressed representation of a much larger number of bytes.
So if you try to apply some function to reduce the number of bytes it would be like trying to compress an already compressed thing.
I have 3D WPF visual that I want to pass into an Excel cell (via clipboard buffer).
With "normal" BMP images it works but I do not know how to convert a RenderTargetBitmap.
My code looks like this:
System.Windows.Media.Imaging.RenderTargetBitmap renderTarget = myParent.GetViewPortAsImage(DiagramSizeX, DiagramSizeY);
System.Windows.Controls.Image myImage = new System.Windows.Controls.Image();
myImage.Source = renderTarget;
System.Drawing.Bitmap pg = new System.Drawing.Bitmap(DiagramSizeX, DiagramSizeY);
System.Drawing.Graphics gr = System.Drawing.Graphics.FromImage(pg);
gr.DrawImage(myImage, 0, 0);
System.Windows.Forms.Clipboard.SetDataObject(pg, true);
sheet.Paste(range);
My problem is that gr.DrawImage does not accept a System.Windows.Controls.Image or a System.Windows.Media.Imaging.RenderTargetBitmap; only a System.Drawing.Image.
How do I convert the Controls.Image.Imaging.RenderTargetBitmap into an Image, or are there any easier ways?
You can copy the pixels from the RenderTargetBitmap directly into the pixel buffer of a new Bitmap. Note that I've assumed that your RenderTargetBitmap uses PixelFormats.Pbrga32, as use of any other pixel format will throw an exception from the constructor of RenderTargetBitmap.
var bitmap = new Bitmap(renderTarget.PixelWidth, renderTarget.PixelHeight,
PixelFormat.Format32bppPArgb);
var bitmapData = bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size),
ImageLockMode.WriteOnly, bitmap.PixelFormat);
renderTarget.CopyPixels(Int32Rect.Empty, bitmapData.Scan0,
bitmapData.Stride*bitmapData.Height, bitmapData.Stride);
bitmap.UnlockBits(bitmapData);
This was the solution I came up with
System.Windows.Media.Imaging.RenderTargetBitmap renderTarget = myParent.GetViewPortAsImage(DiagramSizeX, DiagramSizeY);
System.Windows.Media.Imaging.BitmapEncoder encoder = new System.Windows.Media.Imaging.PngBitmapEncoder();
MemoryStream myStream = new MemoryStream();
encoder.Frames.Add(System.Windows.Media.Imaging.BitmapFrame.Create(renderTarget));
encoder.Save(myStream);
//
System.Drawing.Bitmap pg = new System.Drawing.Bitmap(DiagramSizeX, DiagramSizeY);
System.Drawing.Graphics gr = System.Drawing.Graphics.FromImage(pg);
//
// Background
//
gr.FillRectangle(new System.Drawing.SolidBrush(BKGC), 0, 0, DiagramSizeX, DiagramSizeY);
//
gr.DrawImage(System.Drawing.Bitmap.FromStream(myStream), 0, 0);
System.Windows.Forms.Clipboard.SetDataObject(pg, true);
sheet.Paste(range);
Maybe I don't understand the question right, but you want to copy a RenderTargetBitmap to the clipboard, couldn't you just call SetImage ?:
Dim iRT As RenderTargetBitmap = makeImage() //this is what you do to get the rendertargetbitmap
If iRT Is Nothing Then Exit Sub
Clipboard.SetImage(iRT)