0

I have a function that turns a normal image into an RGB byte array. Then I wrote a function to turn this byte array back into a normal image. Unfortunately the color components are shifted, i.e. {R=0, G=94, B=255} (blue) becomes {R=255, G=94, B=0} (orange), and so on. I have tried several answers, including this one and this one. I even tried swapping the components, but that doesn't work either. What am I doing wrong?


Edit:
I also attempted swapping the color components as a trial, hoping it would rectify the color issue.


These lines of code are a test project. I want to implement the RGB_Byte_Array_To_Image function somewhere else later.

Public Class FormMain
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim filePath As String = "C:\Users\...\Pictures\Testbild mit Gimp erstellen\Test, nicht optimiert, nicht progressiv 2.jpg"
        Dim rgbByteArray As Byte() = RGB_Byte_Array_From_Image(filePath)

        Dim width As Integer
        Dim height As Integer
        Dim pixelformat As Imaging.PixelFormat

        Using image As New Bitmap(filePath)
            width = image.Width
            height = image.Height
            pixelformat = image.PixelFormat
        End Using
        Using normal_Image As System.Drawing.Bitmap = RGB_Byte_Array_To_Image(rgbByteArray, width, height, pixelformat)
            normal_Image.Save("C:\Users\...\Desktop\Test.png", Imaging.ImageFormat.Png)
        End Using
    End Sub

    Private Shared Function RGB_Byte_Array_To_Image(rgbByteArray() As Byte, width As Integer, height As Integer, pixelformat As Imaging.PixelFormat) As Bitmap
        Dim b As New Bitmap(width, height, pixelformat)
        Dim BoundsRect As New Rectangle(0, 0, width, height)
        Dim bmpData As Imaging.BitmapData = b.LockBits(BoundsRect, Imaging.ImageLockMode.WriteOnly, pixelformat)

        Dim bytesPerPixel As Integer = Image.GetPixelFormatSize(pixelformat) \ 8
        Dim stride As Integer = bmpData.Stride

        Dim scan0 As IntPtr = bmpData.Scan0

        For y As Integer = 0 To height - 1
            Dim rowOffset As Integer = y * stride
            Dim sourceOffset As Integer = y * width * bytesPerPixel

            For x As Integer = 0 To width - 1
                Dim pixelOffset As Integer = x * bytesPerPixel

                ' Reverse the order of color components (BGR instead of RGB)
                Dim blue As Byte = rgbByteArray(sourceOffset + pixelOffset)
                Dim green As Byte = rgbByteArray(sourceOffset + pixelOffset + 1)
                Dim red As Byte = rgbByteArray(sourceOffset + pixelOffset + 2)

                Runtime.InteropServices.Marshal.WriteByte(scan0, rowOffset + pixelOffset, blue)
                Runtime.InteropServices.Marshal.WriteByte(scan0, rowOffset + pixelOffset + 1, green)
                Runtime.InteropServices.Marshal.WriteByte(scan0, rowOffset + pixelOffset + 2, red)
            Next
        Next

        b.UnlockBits(bmpData)
        Return b
    End Function


    Private Function RGB_Byte_Array_From_Image(filePath As String) As Byte()
        Dim imageBytes As Byte()

        Using image As New Bitmap(filePath)
            imageBytes = (New Byte(image.Width * image.Height * 3 - 1) {})

            For y As Integer = 0 To image.Height - 1
                For x As Integer = 0 To image.Width - 1
                    Dim pixelColor As Color = image.GetPixel(x, y)
                    Dim pixelIndex As Integer = (y * image.Width + x) * 3

                    imageBytes(pixelIndex) = pixelColor.R
                    imageBytes(pixelIndex + 1) = pixelColor.G
                    imageBytes(pixelIndex + 2) = pixelColor.B
                Next
            Next
        End Using

        Return imageBytes
    End Function
End Class
Daniel
  • 374
  • 1
  • 12
  • 2
    Read the notes here: [Analyze colors of an Image](https://stackoverflow.com/a/59102380/7444103) (if you can stomach it) and check the corresponding `Image.LockBits()` method -- In your code, one method stores colors in one order and the other methods reads them in the reverse order -- What's the point of all this? Just to change the image format? If that's the case, you don't need any of this at all – Jimi Jun 30 '23 at 17:19
  • In your question, you write *Unfortunately the color components are shifted* but in your code you have a comment indicating this is intentional: *`' Reverse the order of color components (BGR instead of RGB)`*. Why are you doing that? Do you want to swap the R and B values, or not? – dbc Jun 30 '23 at 17:49
  • @Jimi, I'm going to read through that. `What's the point of all this?` In another project, I get an RGB byte array from a function. I want to convert this array to a normal image. The code above is just a test project for you and me to develop. So I had to make a normal image to a RGB byte array first. So no, this is not for image format conversion. – Daniel Jun 30 '23 at 17:54
  • @dbc yes I have tried around a lot. I do **not** want to swap colors. I want the color to stay the same. I thought I could make a difference by swapping. – Daniel Jun 30 '23 at 17:56
  • OK, then what is the value of `pixelformat` for the case that does not work? – dbc Jun 30 '23 at 18:16
  • @dbc it's PixelFormat.Format24bppRgb – Daniel Jun 30 '23 at 18:17
  • The sourceOffset variable has the wrong value. As long as it came from an image file, .jpg here, you must still use stride. Whether you need it for the real usage of this code is not obvious. – Hans Passant Jun 30 '23 at 18:34
  • Focus first on the ***Important notes about the Stride*** in that Q&A -- If you *get an RGB byte array*, then specify what is the origin of this buffer, what it contains and in what sequence. Of course, you cannot use that method as it is, since you won't know the width and height of the Bitmap just from the buffer (which is a linear sequence of bytes), unless you also receive this information. These details are the *sensible* part of the question and cannot be omitted – Jimi Jun 30 '23 at 19:11
  • Could it be an endianness issue? – SSS Jul 01 '23 at 00:16

2 Answers2

0

If you are trying to save images as byte arrays...

    'image to bytes
    Dim img As Image = Image.FromFile(pathOfImg)
    Dim ms As New IO.MemoryStream
    img.Save(ms, Drawing.Imaging.ImageFormat.Jpeg)
    Dim imageData() As Byte = ms.GetBuffer()



    'image from bytes
    Using RDms As New IO.MemoryStream(imageData, 0, imageData.Length)
        RDms.Write(imageData, 0, imageData.Length)
        PictureBox1.Image = Image.FromStream(ms, True)
    End Using
dbasnett
  • 11,334
  • 2
  • 25
  • 33
0

In my solution, I now have a byte array that represents color components in the BGR (Blue-Green-Red) order. This is important because the LockBits method expects the pixel data to be arranged in BGR order when using certain pixel formats.

Private Shared Function BGR_Byte_Array_To_Image(rgbByteArray As Byte(),
                                                width As Integer,
                                                height As Integer,
                                                pixelformat As Imaging.PixelFormat) As Bitmap
    ' ‘LockBits’ expects the color components to be in the order BGR
    Dim b As New Bitmap(width, height, pixelformat)
    Dim bitmapData As Imaging.BitmapData = b.LockBits(New Rectangle(0, 0, width, height),
                                                      Imaging.ImageLockMode.ReadOnly,
                                                      pixelformat)
    Dim bytes_per_Pixel As Integer = Image.GetPixelFormatSize(b.PixelFormat) \ 8
    Dim stride As Integer = CInt(Math.Floor(
                                            (bytes_per_Pixel * 8.0 * width + 31.0) / 32.0
                                            ) * 4.0
                                 )

    Runtime.InteropServices.Marshal.Copy(rgbByteArray, 0, bitmapData.Scan0, stride * height)

    b.UnlockBits(bitmapData)
    Return b
End Function

Call it like this

Using normal_Image As System.Drawing.Bitmap = BGR_Byte_Array_To_Image(rgbByteArray, width, height, pixelformat)
    normal_Image.Save("C:\Users\...\Desktop\Test.png", Imaging.ImageFormat.Png)
End Using
Daniel
  • 374
  • 1
  • 12