Pixel Manipulation in CGBitmapContext depend on the view's superview? - c#

I came across something really strange with while manipulating pixels using CGBitmapContext. Basically I'm changing the Alpha values of PNGs. I have an example where I can successfully Zero the Alpha value of a PNGs pixel EXCEPT when my ImageView's superview is an ImageView with an imageā€¦ Why this is? I'm not sure if it's a bug or if something in the Context is set up incorrectly.
Below is an example. It zeros' alpha values successfully, but when you give the superview's UIImageView an Image, it doesn't work right. The pixels that should have an Alpha of zero are slightly visible. But when you remove the image from the superview's UIImageView the Alpha values are zeroed correctly, you see the white background behind it perfectly. I've tried using several different CGBlendModes but that doesn't seem to do it.
Here is a dropbox link to an example project demonstrating this strange event:
https://www.dropbox.com/s/e93hzxl5ru5wnss/TestAlpha.zip
And here is the code copy/pasted:
// Create a Background ImageView
UIImageView Background = new UIImageView(this.View.Bounds);
// Comment out the below line to see the Pixels alpha values change correctly, UnComment the below line to see the pixel's alpha values change incorrectly. Why is this?
// When Commented out, The pixels whose alpha value I set to zero become transparent and I can see the white background through the image.
// When Commented in, I SHOULD see the "GrayPattern.png" Image through the transparent pixels. I can kind of see it, but for some reason I still see the dirt pixels who's alpha is set to zero! Is this a bug? Is the CGBitmapContext set up incorrectly?
Background.Image = UIImage.FromFile("GrayPattern.png");
this.View.AddSubview(Background);
UIImageView MyImageView = new UIImageView(UIScreen.MainScreen.Bounds);
UIImage sourceImage = UIImage.FromFile("Dirt.png");
MyImageView.Image = sourceImage;
Background.AddSubview(MyImageView);
CGImage image = MyImageView.Image.CGImage;
Console.WriteLine(image.AlphaInfo);
int width = image.Width;
int height = image.Height;
CGColorSpace colorSpace = CGColorSpace.CreateDeviceRGB();
int bytesPerRow = image.BytesPerRow;
int bytesPerPixel = bytesPerRow / width;
int bitmapByteCount = bytesPerRow * height;
int bitsPerComponent = image.BitsPerComponent;
CGImageAlphaInfo alphaInfo = CGImageAlphaInfo.PremultipliedLast;
// Allocate memory because the BitmapData is unmanaged
IntPtr BitmapData = Marshal.AllocHGlobal(bitmapByteCount);
CGBitmapContext context = new CGBitmapContext(BitmapData, width, height, bitsPerComponent, bytesPerRow, colorSpace, alphaInfo);
context.SetBlendMode(CGBlendMode.Copy);
context.DrawImage(new RectangleF(0, 0, width, height), image);
UIImageView MyImageView_brush = new UIImageView(new RectangleF(0, 0, 100, 100));
UIImage sourceImage_brush = UIImage.FromFile("BB_Brush_Rect_MoreFeather.png");
MyImageView_brush.Image = sourceImage_brush;
MyImageView_brush.Hidden = true;
Background.AddSubview(MyImageView_brush);
CGImage image_brush = sourceImage_brush.CGImage;
Console.WriteLine(image_brush.AlphaInfo);
int width_brush = image_brush.Width;
int height_brush = image_brush.Height;
CGColorSpace colorSpace_brush = CGColorSpace.CreateDeviceRGB();
int bytesPerRow_brush = image_brush.BytesPerRow;
int bytesPerPixel_brush = bytesPerRow_brush / width_brush;
int bitmapByteCount_brush = bytesPerRow_brush * height_brush;
int bitsPerComponent_brush = image_brush.BitsPerComponent;
CGImageAlphaInfo alphaInfo_brush = CGImageAlphaInfo.PremultipliedLast;
// Allocate memory because the BitmapData is unmanaged
IntPtr BitmapData_brush = Marshal.AllocHGlobal(bitmapByteCount_brush);
CGBitmapContext context_brush = new CGBitmapContext(BitmapData_brush, width_brush, height_brush, bitsPerComponent_brush, bytesPerRow_brush, colorSpace_brush, alphaInfo_brush);
context_brush.SetBlendMode(CGBlendMode.Copy);
context_brush.DrawImage(new RectangleF(0, 0, width_brush, height_brush), image_brush);
for ( int x = 0; x < width_brush; x++ )
{
for ( int y = 0; y < height_brush; y++ )
{
int byteIndex_brush = (bytesPerRow_brush * y) + x * bytesPerPixel_brush;
byte alpha_brush = GetByte(byteIndex_brush+3, BitmapData_brush);
// Console.WriteLine("alpha_brush = " + alpha_brush);
byte setValue = (byte)(255 - alpha_brush);
// Console.WriteLine("setValue = " + setValue);
int byteIndex = (bytesPerRow * y) + x * bytesPerPixel;
SetByte(byteIndex+3, BitmapData, setValue);
}
}
// Set the MyImageView Image equal to the context image, but I still see all top image, I dont' see any bottom image showing through.
MyImageView.Image = UIImage.FromImage (context.ToImage());
context.Dispose();
context_brush.Dispose();
// Free memory used by the BitmapData now that we're finished
Marshal.FreeHGlobal(BitmapData);
Marshal.FreeHGlobal(BitmapData_brush);
public unsafe byte GetByte(int offset, IntPtr buffer)
{
byte* bufferAsBytes = (byte*) buffer;
return bufferAsBytes[offset];
}
public unsafe void SetByte(int offset, IntPtr buffer, byte setValue)
{
byte* bufferAsBytes = (byte*) buffer;
bufferAsBytes[offset] = setValue;
}
Update I've found a workaround (though its performance is pretty unacceptable for what I"m doing) The workaround is simply taking the result image of from my above code, convert it to NSData, then convert the NSData back to a UIIMage... Magically the image looks correct! This workaround; however, is not acceptable for what I'm doing because it literally takes 2 seconds to do this with my Image and that is way too long for my App. A couple hundred ms would be fun, but 2 seconds is way to long for me. If this is a bug hopefully it gets resolved!
Here is the workaround (poor performance) code. Just add this code to the end of the code above (or download my dropbox project and add this code to the end of it and run):
NSData test = new NSData();
test = MyImageView.Image.AsPNG();
UIImage workAroundImage = UIImage.LoadFromData(test);
MyImageView.Image = workAroundImage;
Here are before and after pictures. Before is without the workaround, After includes the workaround.
Notice the Gray doens't show up completely. It's covered in some light brown as if the alpha value isn't full zeroed.
Notice with the workaround you can see the gray perfectly and clearly, no light brown over it at all. The only thing I did was Convert the image to NSData, then back to UIImage again! Very strange...

Related

How to WritePixels (array of ushort[]) to WritableBitmap BGR32

I'm working with WriteableBitmap (PixelFormats.Bgr32).
This is the format I need to save for the rest of the program to work.
Writable = new WriteableBitmap(inImage.XSize, inImage.YSize, dpi, dpi, PixelFormats.Bgr32, null);
The image from the device comes in grayscaled, (ushort[] Gray16).
To use this image in my program I use the following code (inImage - received image, ImageData = ushort[]):
int[] pixels = Array.ConvertAll(inImage.ImageData, val => checked((int)val));
Writable.WritePixels(new Int32Rect(0, 0, width, height), pixels, width * 4, 0);
If I just use
Writable.WritePixels(new Int32Rect(0, 0, width, height), inImage.ImageData, width * 4, 0);
I get a message about insufficient buffer size. (System.ArgumentException: "Buffer size is not sufficient."
)
Code
int[] pixels = Array.ConvertAll(inImage.ImageData, val => checked((int)val))
or
int[] pixels = new int[inImage.XSize * inImage.YSize];
for (int i = 0; i < inImage.ImageData.Length; i++)
{
pixels[i] = inImage.ImageData[i];
}
This seems to me to be a very long and slow and unoptimized process.
Is there any way to optimize the process and immediately write ushort[] array to WriteableBitmap (Bgr32) without converting it to int[] ?
EDIT***
I need this code to be able to work with images with a resolution of 4300x4300 with 45 FPS.
Below is the full code how I load images (this code is called 45 times per second to load a new image)
public class ImageStruct
{
public ushort[] ImageData;
public int XSize;
public int YSize;
public int XDpi;
public int YDpi;
}
WriteableBitmap Writeable;
public void LoadFrame(ImageStruct inImage)
{
var width = inImage.XSize;
var height = inImage.YSize;
var destBpp = PixelFormats.Bgr32.BitsPerPixel / 8;
var uPixels = inImage.ImageData;
fluoroWritable = new WriteableBitmap(inImage.XSize, inImage.YSize, 96, 96, PixelFormats.Bgr32, null);
var iPixels = Array.ConvertAll(uPixels, val => checked((int)val));
fluoroWritable.WritePixels(new Int32Rect(0, 0, width, height), iPixels, width * 4, 0);
}
This code works well when FPS is ~10,
If you increase the FPS, it starts to freeze. Profiling shows that it is the moment when I make from ushort[] => int[] takes the longest time.
I tried to use the code suggested in the answer below:
unsafe public void LoadFrameWithLock(ImageStruct inImage)
{
var width = inImage.XSize;
var height = inImage.YSize;
var destBpp = PixelFormats.Bgr32.BitsPerPixel / 8;
var uPixels = inImage.ImageData;
fluoroWritable = new WriteableBitmap(inImage.XSize, inImage.YSize, 96, 96, PixelFormats.Bgr32, null);
fluoroWritable.Lock();
int* outputIntValues = (int*)fluoroWritable.BackBuffer;
ushort[] inputShortValues = inImage.ImageData;
for (int i = 0; i < inputShortValues.Length; i++)
{
byte as8bpp = (byte)(inputShortValues[i] >> 8);
outputIntValues[i] = /*B*/ as8bpp | /*G*/ (as8bpp << 8) | /*R*/ (as8bpp << 16);
}
fluoroWritable.AddDirtyRect(new Int32Rect(0, 0, fluoroWritable.PixelWidth, fluoroWritable.PixelHeight));
fluoroWritable.Unlock();
}
But the FPS with this code is even lower and in addition the WPF application has a completely hanging interface. and images have no grayscale, but are completely black and white (white or black pixels).
I need the BGR32 format because this WritableBitmap (WPF Image object) is then overlaid with shader effects, and I can also get color images in another place.
Is there any way to optimize the process and immediately write ushort[] array to WriteableBitmap (Bgr32) without converting it to int[] ?
Yes, instead of calling WritePixels use the Lock method so the BackBuffer will be available as a naked pointer (do not forget to call Unlock when you are finished).
Please note though that as your source and target pixel formats are different (16bpp grayscale vs. 32bpp BGRx) simple casting of short values to int will not be correct. All 16 bit grayscale values must be converted to 8 bit RGB values:
// the result as you defined in OP
Writable = new WriteableBitmap(inImage.XSize, inImage.YSize, dpi, dpi, PixelFormats.Bgr32, null);
// you must be in an unsafe scope to use pointers
int* outputIntValues = (int*)Writable.BackBuffer;
short[] inputShortValues = inImage.ImageData;
// converting pixels to BGR32
for (int i = 0; i < inputShortValues.Length; i++)
{
// taking the most significant bits from the 16bpp gray values
byte as8bpp = (byte)(inputShortValues[i] >> 8);
outputIntValues[i] = /*B*/ as8bpp | /*G*/ (as8bpp << 8) | /*R*/ (as8bpp << 16);
}
// notifying the consumers that we edited the raw content
Writable.AddDirtyRect(new Int32Rect(0, 0, Writable.PixelWidth, Writable.PixelHeight));
// releasing the buffer
Writable.Unlock();
The sample above needs to be inside of an unsafe scope because of the pointer and you must enable unsafe blocks for your project in your .csproj file.
To make things simpler you can use my Drawing Libraries, which now has dedicated WPF support so the conversion will just be literally two lines. It does not need unsafe context and is actually faster than the example above because it uses parallel processing:
// Interpret your short[] as a grayscale bitmap
using var bmpGrayscale = BitmapDataFactory.CreateBitmapData(buffer: inImage.ImageData,
size: new Size(inImage.XSize, inImage.XSize),
stride: inImage.XSize * 2, // if input pixels are contiguous
KnownPixelFormat.Format16bppGrayScale));
// Convert it to a BGR32 WriteableBitmap (async overloads are available, too)
WriteableBitmap bmpResult = bmpGrayscale.ToWriteableBitmap(PixelFormats.Bgr32);

Unsafe C# bitmap floodfill

**
How to make 'GetPixel2' work for finding the color at a point
**
So I have a bitmap with lots of single colored shapes.
I have a list of x,y points for those shapes. Then, a second list
with the expected color at those points.
Finally have an algorithm using bitmap.Getpixel and SetPixel working.
Which was definitely slow.
http://csharpexamples.com/fast-image-processing-c/
Suggests using direct memory access to solve this. I'd like to use their sample without looping through the entire image, and hit a single x,y point.
Bitmap bmp2 = (Bitmap)Bitmap.FromFile(Environment.CurrentDirectory + #"\Content\map\provinces.bmp");
BitmapData bitmapData = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, bmp2.Width, bmp2.Height), ImageLockMode.ReadWrite, bmp2.PixelFormat);
int bytesPerPixel = System.Drawing.Bitmap.GetPixelFormatSize(bmp2.PixelFormat) / 8;
int heightInPixels = bitmapData.Height;
int widthInBytes = bitmapData.Width * bytesPerPixel;
System.Drawing.Point pt = new System.Drawing.Point((int)provpos2[0].X, (int)provpos2[0].Y);
System.Drawing.Color targetColor = System.Drawing.Color.FromArgb(255, provcolors[0].R, provcolors[0].G, provcolors[0].B);
if (!ColorMatch(GetPixel2(pt.X, pt.Y, bytesPerPixel, bitmapData), targetColor)){
// This hits the completely wrong area.
}
public System.Drawing.Color GetPixel2(int x, int y, int bytesPerPixel, BitmapData bitmapData)
{
unsafe
{
byte* ptrFirstPixel = (byte*)bitmapData.Scan0;
byte* currentLine = ptrFirstPixel + (y * bitmapData.Stride);
x = x + bytesPerPixel;
System.Drawing.Color a = System.Drawing.Color.FromArgb(255, currentLine[x + 2], currentLine[x + 1], currentLine[x]);
return a;
}
}
public static bool ColorMatch(System.Drawing.Color a,System.Drawing.Color b)
{
return (a.ToArgb() & 0xffffff) == (b.ToArgb() & 0xffffff);
}
bytesPerPixel comes out at 3. Tried changing it to 4 just hits another undesired location on the bitmap.
It seems to hit around 1023x,351y instead of the desired 3084x,319y on a 5632x2048 bitmap.
Not entirely sure why it doesnt workout fo you, but keep in mind this:
Bits per pixel comes from colour format used there are a few formats some are handier then others, and sometimes you need to convert them to a strict RGB format. ea 8 bits per colour channel, there also exists RGBA, and there is RGB in bitwise 565 notation as used in some camera's, and there is 24bits per colour. Some formats are not supported in winforms, but are supported in wpf based applications, like 16bit gray formats. (since wpf is more new age like design friendly)
maybe try this it works great for me:
http://www.codeproject.com/Tips/240428/Work-with-bitmap-faster-with-Csharp?msg=5136670
if its 565 maybe do something like
private Bitmap Convert565bppTo24bpp(Bitmap ConvertMe)
{
Bitmap clone = new Bitmap(ConvertMe.Width, ConvertMe.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);//.Format32bppPArgb);
using (Graphics gr = Graphics.FromImage(clone))
{ gr.DrawImage(ConvertMe, new Rectangle(0, 0, clone.Width, clone.Height)); }
return clone;
}

C# - Converting Bitmap to byte[] using Marshal.Copy not working consistently?

I have been trying to implement the image comparing algorithm seen here: http://www.dotnetexamples.com/2012/07/fast-bitmap-comparison-c.html
The problem I have been having is that when I try to compare a large amount of images one after another using the method pasted below (a slightly modified version from the link above), my results seem to be inaccurate. In particular, if I try to compare too many different images, even the ones that are the same will occasionally be detected as different. The problem seems to be that certain bytes in the array are different, as you can see in the screenshot I have included of two of the same images being compared (this occurs when I repeatedly compare images from an array of about 100 images - but there are actually only 3 unique images in the array):
private bool byteCompare(Bitmap image1, Bitmap image2) {
if (object.Equals(image1, image2))
return true;
if (image1 == null || image2 == null)
return false;
if (!image1.Size.Equals(image2.Size) || !image1.PixelFormat.Equals(image2.PixelFormat))
return false;
#region Optimized code for performance
int bytes = image1.Width * image1.Height * (Image.GetPixelFormatSize(image1.PixelFormat) / 8);
byte[] b1bytes = new byte[bytes];
byte[] b2bytes = new byte[bytes];
Rectangle rect = new Rectangle(0, 0, image1.Width - 1, image1.Height - 1);
BitmapData bmd1 = image1.LockBits(rect, ImageLockMode.ReadOnly, image1.PixelFormat);
BitmapData bmd2 = image2.LockBits(rect, ImageLockMode.ReadOnly, image2.PixelFormat);
try
{
Marshal.Copy(bmd1.Scan0, b1bytes, 0, bytes);
Marshal.Copy(bmd2.Scan0, b2bytes, 0, bytes);
for (int n = 0; n < bytes; n++)
{
if (b1bytes[n] != b2bytes[n]) //This line is where error occurs
return false;
}
}
finally
{
image1.UnlockBits(bmd1);
image2.UnlockBits(bmd2);
}
#endregion
return true;
}
I've added a comment to show where in the method this error is occurring. I assume it has something to do with the memory not being allocated properly, but I haven't been able to figure out what the source of the error is.
I should probably also mention that I don't get any issues when I convert the image to a byte array like so:
ImageConverter converter = new ImageConverter();
byte[] b1bytes = (byte[])converter.ConvertTo(image1, typeof(byte[]));
However, this approach is far slower.
If (Width * bytesperpixel) != Stride, then there will be unused bytes at the end of each line that are not guaranteed to have any particular value and in practice can be filled with random garbage.
You need to iterate line by line, increment by Stride each time, and only checking the bytes that actually correspond to pixels on each line.
Once you got the BitmapData object, the Stride can be found in that BitmapData object's Stride property. Make sure to extract that for both images.
Then, you have to loop over all pixels in the data so you can accurately determine where the image width for each line ends and the leftover data of the stride begins.
Also note this only works for high-colour images. Comparing 8-bit images is still possible (though you need to compare their palettes as well), but for lower than 8 you need to go bit-shifting to get the actual palette offset out of the image.
A simple workaround for that is to just paint your image on a new 32bpp image, effectively converting it to high colour.
public static Boolean CompareHiColorImages(Byte[] imageData1, Int32 stride1, Byte[] imageData2, Int32 stride2, Int32 width, Int32 height, PixelFormat pf)
{
Int32 byteSize = Image.GetPixelFormatSize(pf) / 8;
for (Int32 y = 0; y < height; y++)
{
for (Int32 x = 0; x < width; x++)
{
Int32 offset1 = y * stride1 + x * byteSize;
Int32 offset2 = y * stride2 + x * byteSize;
for (Int32 n = 0; n > byteSize; n++)
if (imageData1[offset1 + n] != imageData2[offset2 + n])
return false;
}
}
return true;
}

WritableBitmap.WritePixels showing in wrong location

Here is a code snippet where I attempt to put a blue dot icon (Bgr32; 169x169) on a graphic (Bgra32 3985x3443) in a WPF Image control.
string s = new FileInfo(System.Reflection.Assembly.GetExecutingAssembly().Location).DirectoryName + "\\lg_dot.jpg";
BitmapImage icon = new BitmapImage(new Uri(s));
var baseLayer = img_BackgroundLayer.Source as BitmapImage;
WriteableBitmap composite = new WriteableBitmap(baseLayer);
int bytesPerPixel = (int)(icon.Format.BitsPerPixel + 7) / 8;
int stride = (int)(bytesPerPixel * icon.Width);
int size = stride * (int)icon.Height;
byte[] pixels = new byte[size];
icon.CopyPixels(pixels, stride, 0);
composite.WritePixels(new Int32Rect(0, 0, (int)icon.Width, (int)icon.Height), pixels, stride, 1000, 1000);
Notice that I am placing the dot (icon) at (1000, 1000) on the image. When viewed however, the upper left of the dot is not at (1000, 1000) it is at about (760,760). If I try to place the icon at (2000,2000) it appears at about (1520,1520). The place the icon appears is always approx (0.76 * X, 0.76 * Y) where X and Y are the target coordinates.
I think I know what is happening here... something to do with putting a Bgr32 image onto a Bgra32 background... but I am not clever enough to see how to solve it.
Bytes per pixel formula :
(bits per pixel + 7) / 8
Stride formula :
bytes per pixel * image width
Notice bytes and bits and use integers for calculation !
So for a 1024 pixels wide image :
Bgr32
bytes per pixel : (24 + 7) / 8 = 3
stride : 3 * 1024 = 3072
Bgra32
bytes per pixel : (32 + 7) / 8 = 4
stride : 4 * 1024 = 4096
By looking at your code :
the stride should be 3, not 4 :D
you are needlessly using stride for an int[], remember sizeof(int) == 4
byte offset of the pixel in your image array of byte[] : stride * y + x * bytes per pixel
int offset of the pixel in your image array of int[] : y * width + x
By the way, did you check that your Image.Stretch is set to None ?
Finally, beware of the components order :
BGRA is represented as ARGB (you might see nothing if A is 0 and you think you've set B)
Last tip : https://writeablebitmapex.codeplex.com/ might be easier for you in the end.
EDIT
This is really bad, in my case it is null as BitmapFrameDecode cannot be casted to BitmapImage, it's better to keep a reference to the source instead :
var baseLayer = Image1.Source as BitmapImage;
The formulas I gave you are correct, I only had to change destX and destY as my background was not as large as yours.
var icon = new BitmapImage(new Uri("..\\..\\avatar92.jpg", UriKind.Relative));
var background = new BitmapImage(new Uri("..\\..\\canary_1024_600_a_0.jpg", UriKind.Relative));
var writeableBitmap = new WriteableBitmap(background);
int bytesPerPixel = (icon.Format.BitsPerPixel + 7) / 8;
int stride = bytesPerPixel * icon.PixelWidth;
int size = stride * icon.PixelHeight;
var pixels = new byte[size];
icon.CopyPixels(pixels, stride, 0);
writeableBitmap.WritePixels(new Int32Rect(0, 0, icon.PixelWidth, icon.PixelHeight), pixels, stride, 500, 500);
Image1.Source = writeableBitmap;
Again,
Is Image.Stretch == None ?
Are the image dimensions correct ? they should be I guess :D
Do bitmaps have the same format ? Not that it won't fail if they are different but you'll get all sort of funny surprises, try using a .GIF and a .JPEG file for instance.
I really suggest you to use WriteableBitmapEx instead, you can draw pixels, lines, rectangles, shapes etc ...
Here's the same result with WriteableBitmapEx without having to deal with all the details such as stride, array, bpp, etc ...
var icon = new BitmapImage(new Uri("..\\..\\avatar92.jpg", UriKind.Relative));
var background = new BitmapImage(new Uri("..\\..\\canary_1024_600_a_0.jpg", UriKind.Relative));
var img1 = BitmapFactory.ConvertToPbgra32Format(icon);
var img2 = BitmapFactory.ConvertToPbgra32Format(background);
var img1Size = new Size(img1.PixelWidth, img1.PixelHeight);
img2.Blit(new Rect(new Point(500, 500), img1Size), img1, new Rect(img1Size));
Image1.Source = img2;
If you prefer the hard way then make sure your bitmaps have the same format : FormatConvertedBitmap
If you still cannot get it working, post a Short, Self Contained, Correct (Compilable), Example and links to the two images so people can really help you. I found nothing on your code so I guess there's something not right but as you haven't pasted all the code we can't really tell.

Image Manipulation c#

I have the following code which takes an array of bytes which i generated and writes them out to this bitmap. If i set the pixel format to Format4bppIndexed, then i get a readable image repeating width wise 4 times, if i set it to Format1bppIndexed(which is the correct setting) then i get one big unreadable image.
The image was a decoded Jbig2 image , i know the bytes are correct i can't seem to figure out how to get it into a 1bpp readable format.
Does anyone have any advice on that matter
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format1bppIndexed);
//Create a BitmapData and Lock all pixels to be written
BitmapData bmpData = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.WriteOnly, bitmap.PixelFormat);
//Copy the data from the byte array into BitmapData.Scan0
Marshal.Copy(newarray, 0, bmpData.Scan0, newarray.Length);
//Unlock the pixels
bitmap.UnlockBits(bmpData);
The following may work although, if I remember correctly, Stride sometimes has an effect and a simple block-copy won't suffice (line by line must be used instead).
Bitmap bitmap = new Bitmap(
width,
height,
System.Drawing.PixelFormat.Format16bppGrayScale
);
To handle the Stride you'd want:
BitmapData^ data = bitmap->LockBits(oSize,
ImageLockMode::ReadOnly, bitmap->PixelFormat);
try {
unsigned char *pData = (unsigned char *)data->Scan0.ToPointer();
for( int x = 0; x < bmpImage->Width; ++x )
{
for( int y = 0; y < bmpImage->Height; ++y )
{
// Note: Stride is data width of scan line rounded up
// to 4 byte boundary.
// Requires use of Stride, not (width * pixelWidth)
int ps = y*bmpImage->Width*(nBitsPerPixel / 8)
+ x * (nBitsPerPixel / 8);
int p = y * data->Stride + x * (nBitsPerPixel / 8);
Byte lo = newarray[ps + 1];
Byte hi = newarray[ps + 0];
pData[p + 1] = lo;
pData[p + 0] = hi;
}
}
} finally {
bmpImage->UnlockBits(data);
}
Note: This was written in C++/CLI. Let me know if you need C# equivalents for any of the operations here. (Also, I pulled it from a read from bitmap rather than a write to bitmap so it may yet be a bit rough, but should hopefully give you the idea...)
I figured this out Although i'm still not sure why it should matter.
Based on this stackoverflow posting How can I load the raw data of a 48bpp image into a Bitmap?
I used the WPF classes instead of the GDI and wrote the code like this
var bitmap = new WriteableBitmap(width, height, 96, 96, System.Windows.Media.PixelFormats.BlackWhite, null);
bitmap.WritePixels(new System.Windows.Int32Rect(0, 0, width, height), newarray, stride, 0);
MemoryStream stream3 = new MemoryStream();
var encoder = new TiffBitmapEncoder ();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
encoder.Save(stream3);
This correctly creates the image.
If anyone has any insight into why this might be the case please comment below
The port which now mostly works(lots of cleanup code) was based on a java implementation of JPedal Big2 Decoder to .NET. If anyone knows anyone interested send them here
https://github.com/devteamexpress/JBig2Decoder.NET

Categories