I am new in SharpDX and I want to simulate code to render a 24-bit bitmap image straight from memory and display to PictureBox. *This code is to be use in later project to quickly render images from memory.
I have no issue render using standard DrawImage() method. I opt for SharpDX because DrawImage is too slow.
But when I try render using SharpDX, the image become grey in color and corrupted (see image below)
The image I want to render is in 24-bit RGB bitmap.
Using DrawImage
Using SharpDX
What is wrong with my code?
Below is my code:
using System;
using System.Windows.Forms;
using SharpDX;
using SharpDX.Direct2D1;
using System.Diagnostics;
namespace bitmap_test
{
public partial class Form1 : Form
{
private System.Drawing.Bitmap image1 = null;
private System.Drawing.Imaging.BitmapData bmpdata1 = null;
//target of rendering
WindowRenderTarget target;
//factory for creating 2D elements
SharpDX.Direct2D1.Factory factory = new SharpDX.Direct2D1.Factory();
//this one is for creating DirectWrite Elements
SharpDX.DirectWrite.Factory factoryWrite = new SharpDX.DirectWrite.Factory();
private SharpDX.DXGI.Format bmp_format = SharpDX.DXGI.Format.B8G8R8A8_UNorm;
private AlphaMode bmp_alphamode = AlphaMode.Ignore;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
InitSharpDX();
//load 24-bit depth bitmap
LoadBitmapFromFile(#"D:\lena.bmp"); //https://software.intel.com/sites/default/files/forum/351974/lena.bmp
}
private void InitSharpDX()
{
//Init Direct Draw
//Set Rendering properties
RenderTargetProperties renderProp = new RenderTargetProperties()
{
DpiX = 0,
DpiY = 0,
MinLevel = FeatureLevel.Level_10,
PixelFormat = new PixelFormat(bmp_format, bmp_alphamode),
Type = RenderTargetType.Hardware,
Usage = RenderTargetUsage.None
};
//set hwnd target properties (permit to attach Direct2D to window)
HwndRenderTargetProperties winProp = new HwndRenderTargetProperties()
{
Hwnd = this.pictureBox1.Handle,
PixelSize = new Size2(this.pictureBox1.ClientSize.Width, this.pictureBox1.ClientSize.Height),
PresentOptions = PresentOptions.Immediately
};
//target creation
target = new WindowRenderTarget(factory, renderProp, winProp);
}
//load bmp file into program memory
private void LoadBitmapFromFile(string file)
{
image1 = (System.Drawing.Bitmap)System.Drawing.Image.FromFile(file, true);
var sourceArea = new System.Drawing.Rectangle(0, 0, image1.Width, image1.Height);
bmpdata1 = image1.LockBits(sourceArea, System.Drawing.Imaging.ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
image1.UnlockBits(bmpdata1);
}
private void drawBitmap(IntPtr pBuffer, int len, int width, int height)
{
try
{
var bitmapProperties = new BitmapProperties(new PixelFormat(bmp_format, bmp_alphamode));
var size = new Size2(width, height);
int stride = width * 3; //only want RGB, ignore alpha
var bmp = new SharpDX.Direct2D1.Bitmap(target, size, new DataPointer(pBuffer, len), stride, bitmapProperties);
//draw elements
Draw(ref bmp);
bmp.Dispose();
}
finally
{
}
}
private void Draw(ref SharpDX.Direct2D1.Bitmap bmp)
{
//begin rendering
target.BeginDraw();
//clear target
target.Clear(null);
//draw bitmap
target.DrawBitmap(bmp, 1.0f, BitmapInterpolationMode.Linear);
//end drawing
target.EndDraw();
}
//draw image using SharpDX
private void cmdRender_Click(object sender, EventArgs e)
{
if (bmpdata1 != null)
{
int len = bmpdata1.Width * bmpdata1.Height * 3;
var sw = Stopwatch.StartNew();
drawBitmap(bmpdata1.Scan0, len, bmpdata1.Width, bmpdata1.Height);
sw.Stop();
Console.WriteLine("SharpDX: {0}us", sw.ElapsedTicks / (TimeSpan.TicksPerMillisecond / 1000));
}
}
//draw image using DrawImage()
private void cmdDrawImage_Click(object sender, EventArgs e)
{
if (image1 != null)
{
var g = pictureBox1.CreateGraphics();
var sourceArea = new System.Drawing.Rectangle(0, 0, image1.Width, image1.Height);
var sw = Stopwatch.StartNew();
g.DrawImage(image1, sourceArea); //DrawImage is slow
sw.Stop();
Console.WriteLine("DrawImage: {0}us", sw.ElapsedTicks/(TimeSpan.TicksPerMillisecond / 1000));
}
}
}
}
The bmp_format B8G8R8A8_UNorm doesn't match the System.Drawing lock format Format24bppRgb... also use bmpdata1.Stride instead of calculating a potential invalid stride.
Your stride is usually the width of the bitmap multipled by the byte depth.
so 512 * 4 would be a 512 wide bitmap with a 32 bit palette. Eg RGBA
Related
Right now I'm coding a game and I need to draw text onto a WriteableBitmap; how do I do this without converting from WritableBitmap to Bitmap to WriteableBitmap? I've searched for solutions already, but all of them slow down my game or need something like Silverlight to work.
something like this:
public class ResourceCounter : UIElement
{
public ResourceCounter()
{
RenderBounds = new Bound(new Point(0, 0),
new Point(600, 30));
}
private string goldAmt;
private string woodAmt;
private string stoneAmt;
public override void Tick()
{
goldAmt = GlobalResources.Gold.ToString();
woodAmt = GlobalResources.Wood.ToString();
stoneAmt = GlobalResources.Stone.ToString();
}
public override void Draw(WriteableBitmap bitmap)
{
bitmap.FillRectangle(RenderBounds.A.X,
Renderbounds.A.Y,
Renderbounds.B.X,
Renderbounds.B.Y,
Colors.DarkSlateGray);
bitmap.DrawString("text", other parameters...");
}
}
You can share the Pixel buffer between a Bitmap and a BitmapSource
var writeableBm1 = new WriteableBitmap(200, 100, 96, 96,
System.Windows.Media.PixelFormats.Bgr24, null);
var w = writeableBm1.PixelWidth;
var h = writeableBm1.PixelHeight;
var stride = writeableBm1.BackBufferStride;
var pixelPtr = writeableBm1.BackBuffer;
// this is fast, changes to one object pixels will now be mirrored to the other
var bm2 = new System.Drawing.Bitmap(
w, h, stride, System.Drawing.Imaging.PixelFormat.Format24bppRgb, pixelPtr);
writeableBm1.Lock();
// you might wanna use this in combination with Lock / Unlock, AddDirtyRect, Freeze
// before you write to the shared Ptr
using (var g = System.Drawing.Graphics.FromImage(bm2))
{
g.DrawString("MyText", new Font("Tahoma", 14), System.Drawing.Brushes.White, 0, 0);
}
writeableBm1.AddDirtyRect(new Int32Rect(0, 0, 200, 100));
writeableBm1.Unlock();
Good day
i don't know if my title is correct. sorry for my bad english
How to overlay two picturebox using c# inoder to achieve the image below, and change the opacity of upper picture box on runtime.
what i need to achieve is something like this. i have two images and i need to overlay them
first image:
enter image description here
and i have second image with a text of: Another Text on image.
and the location of the text is lower than the text location of the first image
(i can't upload more than two image because i don't have 10 reputation yet.)
i need to do like on the image below, but using two picturebox and can change the opacity in order for the second picturebox below the first one to be seen
and the output of the two image:
enter image description here
i created the output image using java. i know that i can run the jar file using c#. but the user required to changed the opacity on run time. so how can i do this?
this is the java code i used
BufferedImage biInner = ImageIO.read(inner);
BufferedImage biOutter = ImageIO.read(outter);
System.out.println(biInner);
System.out.println(biOutter);
Graphics2D g = biOutter.createGraphics();
g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));
int x = (biOutter.getWidth() - biInner.getWidth()) / 2;
int y = (biOutter.getHeight() - biInner.getHeight()) / 2;
System.out.println(x + "x" + y);
g.drawImage(biInner, x, y, null);
g.dispose();
ImageIO.write(biOutter, "PNG", new File(output));
i hope my question is understandable. thank you
Here you go, just a sample, but blueBox is transparent (0.5):
public sealed partial class Form1 : Form
{
private readonly Bitmap m_BlueBox;
private readonly Bitmap m_YellowBox;
public Form1()
{
InitializeComponent();
DoubleBuffered = true;
m_YellowBox = CreateBox(Color.Yellow);
m_BlueBox = CreateBox(Color.Blue);
m_BlueBox = ChangeOpacity(m_BlueBox, 0.5f);
}
public static Bitmap ChangeOpacity(Image img, float opacityvalue)
{
var bmp = new Bitmap(img.Width, img.Height);
using (var graphics = Graphics.FromImage(bmp))
{
var colormatrix = new ColorMatrix();
colormatrix.Matrix33 = opacityvalue;
var imgAttribute = new ImageAttributes();
imgAttribute.SetColorMatrix(colormatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
graphics.DrawImage(img, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, img.Width, img.Height,
GraphicsUnit.Pixel, imgAttribute);
}
return bmp;
}
private static Bitmap CreateBox(Color color)
{
var bmp = new Bitmap(200, 200);
for (var x = 0; x < bmp.Width; x++)
{
for (var y = 0; y < bmp.Height; y++)
{
bmp.SetPixel(x, y, color);
}
}
return bmp;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(m_YellowBox, new Point(10, 10));
e.Graphics.DrawImage(m_BlueBox, new Point(70, 70));
}
}
I have written a function which changes the alpha coefficient of an image. I use setpixel and getpixel,which is very slow. I found out that Lockbits method is faster.How can I do it with Lockbits?
Here is my current code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication2
{
public partial class Form1 : Form
{
private static Image Tran(Image s,int alpha)
{
int x = 0, y = 0;
Bitmap tImage = new Bitmap(s);
for (x = 0; x < tImage.Width; x++)
{
for (y = 0; y < tImage.Height; y++)
{
tImage.SetPixel(x, y, Color.FromArgb(alpha, tImage.GetPixel(x, y).R, tImage.GetPixel(x, y).G, tImage.GetPixel(x, y).B));
}
}
return tImage;
}
public Form1()
{
InitializeComponent();
trackBar1.TickStyle = TickStyle.Both;
trackBar1.Orientation = Orientation.Vertical;
trackBar1.Minimum = 0;
trackBar1.Maximum = 255;
trackBar1.Height = 101;
trackBar1.Value = 255;
pictureBox1.Image = Image.FromFile("C:\\Users\\rati\\Desktop\\water.jpg");
pictureBox1.SizeMode = PictureBoxSizeMode.StretchImage;
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
pictureBox1.Image = ChangeAlpha(pictureBox1.Image, trackBar1.Value);
textBox1.Text = trackBar1.Value.ToString();
}
}
}
You can change the opacity of your image by drawing it in a new bitmap using a new ColorMatrix and assigning a float value between 0 and 1 to its Matrix33 as its new alpha value:
public Image ChangeAlpha(Image img, int value)
{
if (value < 0 || value > 255)
throw new Exception("value must be between 0 and 255");
Bitmap bmp = new Bitmap(img.Width, img.Height); // Determining Width and Height of Source Image
Graphics graphics = Graphics.FromImage(bmp);
ColorMatrix colormatrix = new ColorMatrix();
colormatrix.Matrix33 = value / 255f;
ImageAttributes imgAttribute = new ImageAttributes();
imgAttribute.SetColorMatrix(colormatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
graphics.DrawImage(img, new Rectangle(0, 0, bmp.Width, bmp.Height), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imgAttribute);
graphics.Dispose(); // Releasing all resource used by graphics
return bmp;
}
And here is a sample usage:
private void button1_Click(object sender, EventArgs e)
{
int opacityvalue = 127;
var img = ChangeAlpha(Image.FromFile(#"d:\1.png"), opacityvalue);
img.Save(#"d:\2.png");
}
Don't forget to add using System.Drawing; and using System.Drawing.Imaging;.
You can see before and after calling the function with value=127 below:
EDIT
If you want to see the result in a PictureBox you should pay attention to using 2 different picture boxes, one for original image, and one for changed image:
private void trackBar1_Scroll(object sender, EventArgs e)
{
this.pictureBox2.Image = ChangeAlpha(this.pictureBox1.Image, this.trackBar1.Value);
}
As I see in your code when you change the opacity, you set the result as image of your PictureBox1 again and apply opacity again on the result. In other word you apply opacity on an image that you applied opacity to it before over and over again.
public Form1()
{
...
originalImage = new Bitmap("C:\\Users\\rati\\Desktop\\water.jpg");
pictureBox1.Image = originalImage;
...
}
// Add an extra field to the Form class.
Bitmap originalImage;
void trackBar1_Scroll(object sender, EventArgs e)
{
pictureBox1.Image = ChangeAlpha((byte)trackBar1.Value);
textBox1.Text = trackBar1.Value.ToString();
}
Image ChangeAlpha(byte alpha)
{
Bitmap bmp = new Bitmap(originalImage);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, bmp.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];
Marshal.Copy(ptr, rgbValues, 0, bytes);
// Set every fourth value to alpha. A 32bpp bitmap will change transparency.
for (int counter = 3; counter < rgbValues.Length; counter += 4)
rgbValues[counter] = alpha;
// Also you can try parallelize loop.
//int length = rgbValues.Length / 4;
//Parallel.For(3, length, counter => rgbValues[counter * 4 - 1] = alpha);
Marshal.Copy(rgbValues, 0, ptr, bytes);
bmp.UnlockBits(bmpData);
return bmp;
}
I have a problem with a GDI+ metafile. I want to save a metafile by graphic. It works well when the point count is 10000 and the saved metafile can be opened. But when the point count is large (e.g. count = 10000000), the metafile cannot be opened by mspaint.exe.
Is there anything I missed? Is metafile record size limited? By the way, drawrectangles also has this issue.
Here is my code:
private void button1_Click(object sender, EventArgs e)
{
int width = 1489;
int height = 471;
Graphics offScreenBufferGraphics;
Metafile m;
using (MemoryStream stream = new MemoryStream())
{
using (offScreenBufferGraphics = Graphics.FromHwndInternal(IntPtr.Zero))
{
IntPtr deviceContextHandle = offScreenBufferGraphics.GetHdc();
m = new Metafile(
stream,
deviceContextHandle,
new RectangleF(0, 0, width, height),
MetafileFrameUnit.Pixel,
EmfType.EmfPlusOnly);
offScreenBufferGraphics.ReleaseHdc();
}
}
using (Graphics g = Graphics.FromImage(m))
{
// Set everything to high quality
g.SmoothingMode = SmoothingMode.HighQuality;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.HighQuality;
MetafileHeader metafileHeader = m.GetMetafileHeader();
g.ScaleTransform(
metafileHeader.DpiX / g.DpiX,
metafileHeader.DpiY / g.DpiY);
g.PageUnit = GraphicsUnit.Pixel;
g.SetClip(new RectangleF(0, 0, width, height));
// clears the image and colors the entire background
g.Clear(Color.White);
// draw lines
using (Pen pen = new Pen(Color.Black, 1f))
{
Random rnd = new Random(DateTime.Now.Millisecond);
List<PointF> polyPoints = new List<PointF>();
const int count = 10000;
for (int i = 1; i <= 10000000; i++)
{
polyPoints.Add(new PointF(rnd.Next(1000),rnd.Next(1000)));
}
g.DrawLines(pen, polyPoints.ToArray());
// while
} // using
} // using
// Get a handle to the metafile
IntPtr iptrMetafileHandle = m.GetHenhmetafile();
// Export metafile to an image file
CopyEnhMetaFile(iptrMetafileHandle, #"F:\CacheToDisk\test2.emf");
// Delete the metafile from memory
DeleteEnhMetaFile(iptrMetafileHandle);
}
[DllImport("gdi32.dll")]
static extern IntPtr CopyEnhMetaFile( // Copy EMF to file
IntPtr hemfSrc, // Handle to EMF
String lpszFile // File
);
[DllImport("gdi32.dll")]
static extern int DeleteEnhMetaFile( // Delete EMF
IntPtr hemf // Handle to EMF
);
It seems like the limitation. If I use DrawPath instead of DrawLines, it works correctly.
I have two Bitmaps, named largeBmp and smallBmp. I want to draw smallBmp onto largeBmp, then draw the result onto the screen. SmallBmp's white pixels should be transparent. Here is the code I'm using:
public Bitmap Superimpose(Bitmap largeBmp, Bitmap smallBmp) {
Graphics g = Graphics.FromImage(largeBmp);
g.CompositingMode = CompositingMode.SourceCopy;
smallBmp.MakeTransparent();
int margin = 5;
int x = largeBmp.Width - smallBmp.Width - margin;
int y = largeBmp.Height - smallBmp.Height - margin;
g.DrawImage(smallBmp, new Point(x, y));
return largeBmp;
}
The problem is that the result winds up transparent wherever smallBmp was transparent! I just want to see through to largeBmp, not to what's behind it.
CompositingMode.SourceCopy is the problem here. You want CompositingMode.SourceOver to get alpha blending.
Specify the transparency color of your small bitmap. e.g.
Bitmap largeImage = new Bitmap();
Bitmap smallImage = new Bitmap();
--> smallImage.MakeTransparent(Color.White);
Graphics g = Graphics.FromImage(largeImage);
g.DrawImage(smallImage, new Point(10,10);
Winform copy image on top of another
private void timerFFTp_Tick(object sender, EventArgs e)
{
if (drawBitmap)
{
Bitmap bitmap = new Bitmap(_fftControl.Width, _fftControl.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
_fftControl.DrawToBitmap(bitmap, new Rectangle(0, 0, _fftControl.Width, _fftControl.Height));
if (!fDraw)
{
bitmap.MakeTransparent();
Bitmap fftFormBitmap = new Bitmap(_fftForm.BackgroundImage);
Graphics g = Graphics.FromImage(fftFormBitmap);
g.DrawImage(bitmap, 0, 0);
_fftForm.BackgroundImage = fftFormBitmap;
}
else
{
fDraw = false;
_fftForm.Width = bitmap.Width + 16;
_fftForm.Height = bitmap.Height + 48;
_fftForm.BackgroundImage = bitmap;
}
}
}