How do you set the quality for RenderTarget2D.SaveAsJpeg()? - c#

public void SaveAsJpeg(Stream stream, int width, int height)
RenderTarget2D.SaveAsJpeg() is missing a parameter to set the Jpeg quality and the output has lots of artifacts. (increasing the size of the image does help a bit but it still looks bad)
Is that parameter hidden somewhere in XNA? How can I improve the quality of the jpeg?

There is no quality parameter in the XNA 4.0 API for encoding JPEG images.
As a very round-about way of doing things, you may be able to create a Silverlight WriteableBitmap, copy the data out of your render target and into that, and then use Extensions.SaveJpeg to save it with a custom quality level.
A better alternative might be to simply save a PNG image.

I will agree with Andrew Russel that a much better alternative would be to simply save to PNG, but if you simply HAVE to save to jpg, then there is a much better way than go trhough WriteableBitmap.
What you can do is create an empty Bitmap object and transfer the texture data over so you will do a Texture2D to Bitmap convertion. Simply copy from below:
public static Bitmap ToBitmap(this Microsoft.Xna.Framework.Graphics.Texture2D rd, int Width, int Height)
{
var Bmp = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
byte[] data = ToBytes(rd);
var bmpData = Bmp.LockBits(new Rectangle(0, 0, rd.Width, rd.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
System.Runtime.InteropServices.Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
Bmp.UnlockBits(bmpData);
return Bmp;
}
public static byte[] ToBytes(this Microsoft.Xna.Framework.Graphics.Texture2D rd)
{
byte[] data = new byte[4 * rd.Height * rd.Width];
rd.GetData<byte>(data);
SwapBytes(data);
return data;
}
private static void SwapBytes(byte[] data)
{
System.Threading.Tasks.ParallelOptions po = new System.Threading.Tasks.ParallelOptions();
po.MaxDegreeOfParallelism = -1;
System.Threading.Tasks.Parallel.For(0, data.Length / 4, po, t =>
{
int bi = t * 4;
byte temp = data[bi];
data[bi] = data[bi + 2];
data[bi + 2] = temp;
});
}
Note that the above is pretty efficient, but with certain drawbacks. A similar code to the above can be used to very quickly achieve a Bitmap to Texture2D convertion, but without premultiplying alpha.

Related

Is it possible to achieve the actual binary numbers from a bitmap?

My professor kind of "challenged me" to create an application that draws pixel by pixel an image converted in Bitmap, where it's data is saved in some sort of binary that I can't wrap my head around.
Here's the example given to me:
const byte image[]={
B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,B00000000,
ect ect ect
Now, if the byte data type saves numbers that go from 0 to 255, how is this possible? In the sample code that I was given, there is also the use of "Word" data type but in my IDE it seems like it doesn't exist.
I already wrote the code that converts any image given in input into a bitmap:
FileStream fs = new FileStream(openFileDialog1.FileName, FileMode.Open, FileAccess.Read); //Path is image location
Byte[] bindata = new byte[Convert.ToInt32(fs.Length)];
fs.Read(bindata, 0, Convert.ToInt32(fs.Length));
Bitmap bmp;
using (var ms = new MemoryStream(bindata))
{
bmp = new Bitmap(ms);
}
pictureBox1.Image = bmp; //For now, I just display the converted image on screen
Now I suppose that the next step is to draw the image byte per byte, but I can't get my head around this binary thing and the word data type.. Any kind of help is appreciated :)
if you just want to draw a bitmap pixel at a time, you can do something like this:
Bitmap b = new Bitmap(10, 10);
b.SetPixel(0, 0, Color.Black);
b.SetPixel(1, 3, Color.Red);
pictureBox1.Image = b;
You can just copy your bytes to the Bitmap's memory buffer itself.
BitmapData bufferData = buffer.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
bufferData.SetPixel(x, y, CELL_DEAD);
buffer.UnlockBits(bufferData);
//////////
public static unsafe void SetPixel(BitmapData data, int x, int y, byte pixel)
{
*((byte*)data.Scan0 + y * data.Stride + x) = pixel;
}
I've used it as unsafe but you can play your magic with IntPtr. Of course, you must play your own with width-height synchronization.
UPD: set PixelFormat with care. PixelFormat.Format8bppIndexed is what you need if your colors are in default 256-color palette or you want to define your own palette.

Save as 8bit PNG

I am creating placeholder images in certain sizes that will be used as Data URIs
Bitmap bitmap = new Bitmap(16, 10);
I have done some research, but can't find a good way of saving this bitmap as the smallest possible filesize, which is why I want an 8bit PNG.
My question is: How can I save this bitmap into a file/bytearray/stream as an 8bit PNG? Any good libraries?
You can do this with nQuant (which you can install with nuget, or see references below). The following example converts an image on disk and would be readily adapted to meet your needs.
public static bool TryNQuantify(string inputFilename, string outputFilename)
{
var quantizer = new nQuant.WuQuantizer();
var bitmap = new Bitmap(inputFilename);
if (bitmap.PixelFormat != System.Drawing.Imaging.PixelFormat.Format32bppArgb)
{
ConvertTo32bppAndDisposeOriginal(ref bitmap);
}
try
{
using (var quantized = quantizer.QuantizeImage(bitmap))
{
quantized.Save(outputFilename, System.Drawing.Imaging.ImageFormat.Png);
}
}
catch
{
return false;
}
finally
{
bitmap.Dispose();
}
return true;
}
private static void ConvertTo32bppAndDisposeOriginal(ref Bitmap img)
{
var bmp = new Bitmap(img.Width, img.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (var gr = Graphics.FromImage(bmp))
gr.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height));
img.Dispose();
img = bmp;
}
For more information see:
https://github.com/mcychan/nQuant.cs
https://code.msdn.microsoft.com/windowsdesktop/Convert-32-bit-PNGs-to-81ef8c81
I like the FreeImage project, it is light weight and easy to use. Below is an example of creating a transparent png. You could easily wrap this in a method and set the width and height and transparency value.
//create a new bit map
FIBITMAP dib = new FIBITMAP();
//allocate a 16x10 bitmap with 8bit depth
dib = FreeImage.Allocate(16, 10, 8);
//set the transpareny
byte[] Transparency = new byte[1];
Transparency[0] = 0x00;
FreeImage.SetTransparencyTable(dib, Transparency);
//save the bitmap
FreeImage.Save(FREE_IMAGE_FORMAT.FIF_PNG, dib, "C:\\temp\\tp.png", FREE_IMAGE_SAVE_FLAGS.DEFAULT);
If all you need is a small transparent image, why stop at 8 bit? You can go straight down to 1 bit! You only need one colour anyway, and it'll be even smaller.
In fact, you don't even need to do anything special for that. Since the pixels on a new indexed bitmap will all default to 0, meaning they reference its first palette colour, all you need to do is make a new 1bpp image, and set that first palette colour to transparent:
public static Bitmap MakePlaceholderImage(Int32 width, Int32 height)
{
Bitmap bm = new Bitmap(width, height, PixelFormat.Format1bppIndexed);
// This colour can't be assigned directly since the .Palette getter actually makes a copy of the palette.
ColorPalette pal = bm.Palette;
pal.Entries[0] = Color.Transparent;
bm.Palette = pal;
return bm;
}
I experimented a bit with this, and, saved as png, the end result seemed to consistently be 8 times smaller than the result of the same code executed with 8bpp. For a 5000x5000 image, the file size as png was barely over 3 KiB.
Google search led me to a 6 year old thread and I can't find my solution. It should've been possible 6 years as well, as it uses the System.Drawing Libraray of the .Net Framework.
This example code should help anyone who want to write an 8 bit indexed png.
static class SaveImages
{
public static void SaveGrayImage(Bitmap srcImg, string name)
{
Bitmap grayImg = new Bitmap(srcImg.Width, srcImg.Height, PixelFormat.Format8bppIndexed);
var pal = grayImg.Palette;
foreach (int i in Enumerable.Range(0, 256))
pal.Entries[i] = Color.FromArgb(255, i, i, i);
grayImg.Palette = pal;
var data = grayImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
var bytes = new byte[data.Stride * data.Height];
foreach (int y in Enumerable.Range(0, srcImg.Height))
{
foreach (int x in Enumerable.Range(0, srcImg.Width))
{
var colour = srcImg.GetPixel(x, y);
var c = (int)colour.R + (int)colour.G + (int)colour.B;
c /= 3;
bytes[data.Stride * y + x] = (byte)c;
}
}
Marshal.Copy(bytes, 0, data.Scan0, bytes.Length);
grayImg.Save(name, ImageFormat.Png);
}
}
Edit: To add transparency dabble with the color palette.
1) Create your bitmap with 8 bit per pixel format:
Bitmap bmp = new Bitmap(20, 20, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
See PixelFormat for more formats.
2) Draw to the bitmap ...
3) Then save it as PNG:
bmp.Save(#"c:\temp\xyz.png", System.Drawing.Imaging.ImageFormat.Png);
The created file will have the same pixel format as bmp (8 bit)

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

Saving many screenshots consecutively with Texture2D.SaveAsPng causes out of memory exception

When I make screenshots in my XNA game, each texture.SaveAsPng consumes some memory and it doesn't seem to return back to the game. So eventually I run out of memory. I tried saving the texture data into FileStream and MemoryStream, hoping I could save it from there as Bitmap, but the results are the same. Is there a way to forcefully free this memory or some workaround that will let me get image data and save it some other way without running into out of memory exception?
sw = GraphicsDevice.Viewport.Width;
sh = GraphicsDevice.Viewport.Height;
int[] backBuffer = new int[sw * sh];
GraphicsDevice.GetBackBufferData(backBuffer);
using(Texture2D texture = new Texture2D(GraphicsDevice, sw, sh, false,
GraphicsDevice.PresentationParameters.BackBufferFormat))
{
texture.SetData(backBuffer);
using(var fs = new FileStream("screenshot.png", FileMode.Create))
texture.SaveAsPng(fs, sw, sh); // ← this line causes memory leak
}
You might be able to create the Bitmap from the texture bytes directly and bypass the internal method to check if the SaveAsPng is leaking or its something else.
Try This extension method (unfortunatly I cannot test (no xna at work) but it should work.)
public static class TextureExtensions
{
public static void TextureToPng(this Texture2D texture, int width, int height, ImageFormat imageFormat, string filename)
{
using (Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb))
{
byte blue;
IntPtr safePtr;
BitmapData bitmapData;
Rectangle rect = new Rectangle(0, 0, width, height);
byte[] textureData = new byte[4 * width * height];
texture.GetData<byte>(textureData);
for (int i = 0; i < textureData.Length; i += 4)
{
blue = textureData[i];
textureData[i] = textureData[i + 2];
textureData[i + 2] = blue;
}
bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
safePtr = bitmapData.Scan0;
Marshal.Copy(textureData, 0, safePtr, textureData.Length);
bitmap.UnlockBits(bitmapData);
bitmap.Save(filename, imageFormat);
}
}
}
Its a bit crude but you can clean it up (if it even works).
An last but not least (if all other attempts fail) you could call GarbageCollection yourself but this is NOT recommended as its pretty bad practice.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
The above code should be LAST resort only.
Good luck.

Rendering bitmap with XNA slow when grabbing image from webcam, why?

I've been working for a few weeks on grabbing a webcam image and rendering it on a windows form, but have been struggeling with speed issues the entire time. I need at least a frame rate of 10 Hz to be able to update my background process.
I started of using a pictureBox but the solution I've ended up with is to create a XNA-panel inside my Form, and then rendering the image as a background-sprite by converting the bitmap into a Texture2D using a script I found here.
The problem I've encountered now and have not been able to solve is; when I load a Bitmap in the code by calling the bitmap constructor like below, everything runs smoothly and I can get high fps. This is what I did during testing and was very happy with the results.
Bitmap image = new Bitmap(320, 240);
But as soon as I send the bitmap that I grab from the webcam it takes a lot longer to render for some reason I can not fathom. To my knowledge of bitmaps the images are the same format, it's just the colour of the pixels that are different. What I checked for format is size (320*240), resolution (96) and pixel format (Format32bppArgb). Am I missing anything?
This is how I grab the image from the webcam:
VideoCaptureDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
FinalVideoSource = new VideoCaptureDevice(VideoCaptureDevices[0].MonikerString);
FinalVideoSource.DesiredFrameSize = new Size(320, 240);
FinalVideoSource.DesiredFrameRate = fps;
FinalVideoSource.NewFrame += new NewFrameEventHandler(FinalVideoSource_NewFrame);
void FinalVideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
{
// create bitmap from frame
image = eventArgs.Frame.Clone(new Rectangle(0, 0, 320, 240), PixelFormat.Format32bppArgb);
...
This is my draw function in XNA:
protected override void Draw()
{
backTexture = GetTexture(GraphicsDevice, image);
GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.CornflowerBlue);
// TODO: Add your drawing code here
sprites.Begin();
Vector2 pos = new Vector2(0, 0);
sprites.Draw(backTexture, pos, Microsoft.Xna.Framework.Color.White);
sprites.End();
}
private Texture2D GetTexture(GraphicsDevice dev, System.Drawing.Bitmap bmp)
{
int[] imgData = new int[bmp.Width * bmp.Height];
Texture2D texture = new Texture2D(dev, bmp.Width, bmp.Height);
unsafe
{
// lock bitmap
System.Drawing.Imaging.BitmapData origdata =
bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
uint* byteData = (uint*)origdata.Scan0;
// Switch bgra -> rgba
for (int i = 0; i < imgData.Length; i++)
{
byteData[i] = (byteData[i] & 0x000000ff) << 16 | (byteData[i] & 0x0000FF00) | (byteData[i] & 0x00FF0000) >> 16 | (byteData[i] & 0xFF000000);
}
// copy data
System.Runtime.InteropServices.Marshal.Copy(origdata.Scan0, imgData, 0, bmp.Width * bmp.Height);
byteData = null;
// unlock bitmap
bmp.UnlockBits(origdata);
}
texture.SetData(imgData);
return texture;
}
I would be very grateful if someone could help me with this since I'm stuck now. The community here has been great and I managed to get this far without asking before, which is amazing since I have no prior experience with C# or XNA. With that in mind I realize I might be missing something simple or just approaching this the wrong way.
I've narrowed it down to the bitmap image loading. The only thing I change when using the newly constructed bitmap is to simply overwrite the one from the webcam before processing it in XNA.
My question right now is really, am I missing something with how the bitmaps are constructed which can explain the big difference in rendering speed? Is the conversion to Texture2D the problem here? But I don't see how a different image could affect the speed of that conversion.
Ok. I don't know what is the problem exactly. But I can give you some comments.
-First, set the frame rate of the XNA game equal or less that your webcam fps.
By default XNA run at 60fps, so you are calling the GetTexture() method twice for each frame of your webcam if you are using 30fps.
In the Initialize code:
TargetElapsedTime = TimeSpan.FromSeconds(1f/webcam fps)
If that don't work...
you can try this code to convert from bitmap to texture.
protected override void Draw()
{
//Unset the texture from the GraphicsDevice
for (int i = 0; i < 16; i++)
{
if (Game.GraphicsDevice.Textures[i] == backTexture)
{
Game.GraphicsDevice.Textures[i] = null;
break;
}
}
backTexture.SetData<byte>(image.GetBytes());
GraphicsDevice.Clear(Microsoft.Xna.Framework.Color.CornflowerBlue);
// TODO: Add your drawing code here
sprites.Begin();
Vector2 pos = new Vector2(0, 0);
sprites.Draw(backTexture, pos, Microsoft.Xna.Framework.Color.White);
sprites.End();
}
public static byte[] GetBytes(this Bitmap bitmap)
{
var data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height),
System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
// calculate the byte size: for PixelFormat.Format32bppArgb (standard for GDI bitmaps) it's the hight * stride
int bufferSize = data.Height * data.Stride; // stride already incorporates 4 bytes per pixel
// create buffer
byte[] bytes = new byte[bufferSize];
// copy bitmap data into buffer
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
// unlock the bitmap data
bitmap.UnlockBits(data);
return bytes;
}
I was testing this code and works fine. Hope this help.

Categories