3

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 :)

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Jaume
  • 3,672
  • 19
  • 60
  • 119
  • see if you can use a `stream` instead of a file. Hard to be more specific without seeing how you're doing it now. Take each part one step at a time. [this](https://stackoverflow.com/a/8946937/5947043) shows how you can get from a byte array to an Image object. It then saves the image to a file in JPG format, but as you can see from the [Image class documentation](https://learn.microsoft.com/en-us/dotnet/api/system.drawing.image.save?view=netframework-4.8) there are overloads which allow you to save to a stream instead. – ADyson Jan 31 '20 at 12:31
  • ...you can then get that new stream data back into a byte array, and then pass it along to be converted into base64 (using Convert.ToBase64String). In fact, I think the last method, called "CreateBase64Image" in [this answer](https://stackoverflow.com/a/17876001/5947043) pretty much covers everything you would need. I thought you would not be the first person to have such a requirement, and a few minutes of googling confirms it. Did you find much information during your own research? – ADyson Jan 31 '20 at 12:40
  • Yes, I deeply check information first, so I don't find the method to convert from a raw byte array. The post that you mention is useful for an already encoded byte array (jpg, png, bmp...) but not for a raw byte array, that is what I need to do. MemoryStream ms = new MemoryStream(fileBytes) crashes with a raw byte array – Jaume Jan 31 '20 at 13:33
  • crashes how exactly? What error? – ADyson Jan 31 '20 at 13:36
  • 'Parameter is not valid' from System.Drawing.dll. To be more specific, the raw byte array structure for an image of 10x10px RGB (24bpp), has 300 positions (width x height x bands). – Jaume Jan 31 '20 at 14:49
  • Ok. It would really help at this point if you could show the code for how you currently convert the raw array into a JPG file, and then into base64. Then we can understand better the point(s) at which a stream would be helpful. – ADyson Jan 31 '20 at 15:19
  • P.S. if the error is from System.Drawing then it's impossible that the line with new MemoryStream is the one throwing the error (because you haven't reached any code which involves System.Drawing.Image yet. MemoryStream is from System.IO). – ADyson Jan 31 '20 at 15:22
  • Oh sure, my mistake, the line that crashes is Image streamImage = Image.FromStream(ms), for the raw array but works for a formatted one. – Jaume Jan 31 '20 at 21:55
  • About "how you convert the raw array into jpg", it's just what I'm asking. However, I posted the code that I use to convert jpg image to a formatted byte array, and then str base64. – Jaume Jan 31 '20 at 22:26
  • "how you convert the raw array into jpg", it's just what I'm asking"...sorry from the wording of the question I thought you were asking specifically how to convert it **without saving it as a file first**. So do you have a version which can do it but involving saving to a file, or not? It's a bit unclear. P.S. You say you have got code for "converting a raw byte array (RGB or BGR, no matters) into a base64". So perhaps you could show us that, at least? – ADyson Jan 31 '20 at 23:07
  • Yes, I had find some options googling to save a byte array into a jpg file. It involves Image.save("imageName", ImageFormat.Jpeg) or File.WriteAllBytes(). But none of them turns a raw byte array into a byte array with jpeg structure. – Jaume Feb 05 '20 at 16:39
  • It might help if somewhere (e.g. pastebin perhaps) you can upload the "raw" array data so someone can see, understand and then try to work with it. Then people know exactly what kind of data you are referring to. – ADyson Feb 05 '20 at 16:46
  • Do not dispose parameter which is passed to your function. Just dispose the things that you created inside the function. The one who passed an object to your function is responsible to dispose, if they want! you cannot decide in function. – Reza Aghaei Feb 05 '20 at 18:10
  • There are many RGB format standards can u bring an example of your RGB and what's standard it uses? – A Farmanbar Feb 07 '20 at 01:22

1 Answers1

3

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:

  1. 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.
  2. GetImageDataFromImage: Gets image data (width, height, pixel format, pixel data) from an Image
  3. CreateImageFromImageData: Creates an Image from image data
  4. ConvertImageToJpegImage: Converts an Image to Jpeg Image
  5. 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);
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/207400/discussion-on-answer-by-reza-aghaei-convert-a-raw-rgb-byte-array-to-a-jpg-encode). – Bhargav Rao Feb 07 '20 at 06:17
  • I mainly needed the CreateImageFromImageData and all the cycle works perfectly! I am sending now the images with a previous compression 10 times faster. Thank you very much for your time and effort @Reza! – Jaume Feb 07 '20 at 16:18
  • 1
    No problem @Jaume. I hope you get my point in the highlighted part at top. It's important for you to know converting to Jpeg in this way means nothing and has no added value. It doesn't reduce size of array. It just lose a few quality and color! That's why I tried to break the functionality to a few methods. All the methods are really useful individually. – Reza Aghaei Feb 07 '20 at 16:20
  • Finally we could understand each other – Reza Aghaei Feb 07 '20 at 16:24
  • Sure :) I think these methods will help a lot to other developers. Thanks – Jaume Feb 07 '20 at 16:50