I have recently written code to do exactly that, based on the generic colour format converter of the ScummVM project.
The answer is on a different question here on StackOverflow, and although the question isn't really a duplicate, I think, the answer is, so I'll just link to it:
Working with 10 bits depth digital image
Mind you, I'm not sure exactly how the multiplier for each factor works on the 5-6-5 format. Is the 6-bit component simply more accurate? In that case, the automated conversion system will do the job.
Anyway, with the code linked above, this example should suit your needs perfectly. Here it is used to convert a custom 5-5-5-1 RGBA format:
//bytes 84 21 ==> 0x8421 (BE) ==bin==> 1000 0100 0010 0001 ==split==> 10000 10000 10000 1 ==dec==> 16 16 16 1 (RGBA) ==adjust==> 128 128 128 255
// values in constructor are: bytes per pixel, amount of bits and amount to shift for getting R, G, B and A components, and data endianness.
private static PixelFormatter SixteenBppFormatter = new PixelFormatter(2, 5, 11, 5, 6, 5, 1, 1, 0, false);
protected static Byte[] Convert16bTo32b(Byte[] imageData, Int32 startOffset, Int32 width, Int32 height, ref Int32 stride)
{
Int32 newImageStride = width * 4; ;
Byte[] newImageData = new Byte[height * newImageStride];
for (Int32 y = 0; y < height; y++)
{
for (Int32 x = 0; x < width; x++)
{
Int32 sourceOffset = y * stride + x * 2;
Int32 targetOffset = y * newImageStride + x * 4;
Color c = SixteenBppFormatter.GetColor(imageData, startOffset + sourceOffset);
PixelFormatter.Format32BitArgb.WriteColor(newImageData, targetOffset, c);
}
}
stride = newImageStride;
return newImageData;
}
All you need to do is define your own PixelFormatter with the correct bits distribution for the 5-6-5 format.
You will indeed need to look into Bitmap.LockBits()
to get the original 16-bit data out of the image, and to write that data into a new 32-bit ARGB image. My BuildImage
function mentioned in this answer should show how to handle the writing. The read method is actually a lot simpler:
/// <summary>
/// Gets the raw bytes from an image.
/// </summary>
/// <param name="sourceImage">The image to get the bytes from.</param>
/// <param name="stride">Stride of the retrieved image data.</param>
/// <returns>The raw bytes of the image</returns>
public static Byte[] GetImageData(Bitmap sourceImage, out Int32 stride)
{
BitmapData sourceData = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, sourceImage.PixelFormat);
stride = sourceData.Stride;
Byte[] data = new Byte[stride * sourceImage.Height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
sourceImage.UnlockBits(sourceData);
return data;
}
Do note, in all situations where you edit raw image bytes, the difference between "stride" and "width". In a lot of formats, one line of pixels in an image is padded to the next multiple of four bytes, so you can't just read and process it as array assuming it's all image data; those padding bytes will mess that up really quickly. As shown in my example code to convert my 16bpp format to ARGB, you really need to do that line by line, and per line make sure you only use the data that is still within the (width * bytes per pixel) range.
I've noticed that for all functions that may change the stride, it's advised to give it as ref
parameter.