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();
Related
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
This seems like it should be simple, but I can't seem to find any way to do it. I have a custom WinForms control that has an overridden paint method that does some custom drawing.
I have a Bitmap in memory, and all I want to do is paint over the whole thing with a HashBrush, but preserve the alpha channel, so that the transparent parts of the bitmap don't get painted.
The bitmap in memory is not a simple shape, so it will not be feasible to define it as a set of paths or anything.
EDIT: In response to showing the code, there is a lot of code in the paint routine, so I'm only including a relevant snippet, which is the method in question. This method gets called from the main paint override. It accepts a list of images which are black transparency masks and combines them into one, then it uses a ColorMatrix to change the color of the combined image it created, allowing it to be overlayed on top of the background. All I want to accomplish is being able to also paint hashmarks on top of it.
private void PaintSurface(PaintEventArgs e, Image imgParent, List<Image> surfImgs, Rectangle destRect, ToothSurfaceMaterial material)
{
using (Bitmap bmp = new Bitmap(imgParent.Width, imgParent.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb))
{
using (Graphics g = Graphics.FromImage(bmp))
{
foreach (Image img in surfImgs)
{
g.DrawImage(img, System.Drawing.Point.Empty);
}
}
ColorMatrix matrix = new ColorMatrix(
new float[][] {
new float[] { 0, 0, 0, 0, 0},
new float[] { 0, 0, 0, 0, 0},
new float[] { 0, 0, 0, 0, 0},
new float[] { 0, 0, 0, 0.7f, 0},
new float[] { material.R / 255.0f,
material.G / 255.0f,
material.B / 255.0f,
0, 1}
});
ImageAttributes imageAttr = new ImageAttributes();
imageAttr.SetColorMatrix(matrix);
Rectangle r = GetSizedRect(imgParent, destRect);
e.Graphics.DrawImage(bmp,
r,
0,
0,
bmp.Width,
bmp.Height,
GraphicsUnit.Pixel, imageAttr);
}
}
The solution I ended up using was the following method. First I combine the individual masks into one, then create a new Bitmap and paint the whole thing with the HatchBrush, finally iterate through the mask and set the alpha values on the newly generated bitmap based on the mask.
private Bitmap GenerateSurface(Image imgParent, List<Image> surfImgs, ToothSurfaceMaterial material)
{
Bitmap mask = new Bitmap(imgParent.Width, imgParent.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (Graphics g = Graphics.FromImage(mask))
{
foreach (Image img in surfImgs)
{
g.DrawImage(img, System.Drawing.Point.Empty);
}
}
Bitmap output = new Bitmap(mask.Width, mask.Height,
System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (Graphics g = Graphics.FromImage(output))
{
if (material.HatchStyle != null)
{
HatchBrush hb = new HatchBrush((HatchStyle)material.HatchStyle, material.FgColor, material.BgColor);
g.FillRectangle(hb, new Rectangle(0, 0, output.Width, output.Height));
}
else
{
SolidBrush sb = new SolidBrush(material.FgColor);
g.FillRectangle(sb, new Rectangle(0, 0, output.Width, output.Height));
}
}
var rect = new Rectangle(0, 0, output.Width, output.Height);
var bitsMask = mask.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var bitsOutput = output.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
unsafe
{
int offset = 0;
for (int y = 0; y < mask.Height; y++)
{
byte* ptrMask = (byte*)bitsMask.Scan0 + y * bitsMask.Stride;
byte* ptrOutput = (byte*)bitsOutput.Scan0 + y * bitsOutput.Stride;
for (int x = 0; x < mask.Width; x++)
{
offset = 4 * x + 3;
ptrOutput[offset] = (byte)(ptrMask[offset] * 0.7);
}
}
}
mask.UnlockBits(bitsMask);
output.UnlockBits(bitsOutput);
return output;
}
I think you don't need any ColorMatrix which is overkill, you just need a ColorMap, here is the code which may not suit your requirement but should give you the idea. That's because I possibly don't understand your problem well, if you have any problem, just leave some comment and I'll try to improve the answer:
ImageAttributes imgA = new ImageAttributes();
ColorMap cm = new ColorMap();
cm.OldColor = Color.Black
cm.NewColor = Color.FromArgb((byte)(0.7*255), Color.Green);
imgA.SetRemapTable(new ColorMap[] {cm });
GraphicsUnit gu = GraphicsUnit.Pixel;
g.DrawImage(imageToDraw,new Point[]{Point.Empty,
new Point(backImage.Width/2,0),
new Point(0,backImage.Height/2)},
Rectangle.Round(imageToDraw.GetBounds(ref gu)),
GraphicsUnit.Pixel, imgA);
the new Point[] is an array of 3 Points used to locate the destination Rectangle.
The code above is used to Draw the imageToDraw on top of the backImage and convert and color of Black to the color Green with Opacity = 70%. That's what you want to fulfill your code.
UPDATE
This may be what you want, in fact your code doesn't show what you want, it just shows what you have which doesn't implement anything related to your problem now. I deduce this from your very first description in your question. The input is an image with background color (which will be made partially transparent later) being Black. Now the output you want is an image with all the non-Black region being painted with a HatchBrush. This output will then be processed to turn the Black background to a partially transparent background.
public void PaintHatchBrush(Bitmap input, HatchBrush brush){
using(Graphics g = Graphics.FromImage(input)){
g.Clip = GetForegroundRegion(input, Color.Black);
GraphicsUnit gu = GraphicsUnit.Pixel;
g.FillRectangle(brush, input.GetBounds(ref gu));
}
}
//This is implemented using `GetPixel` which is not fast, but it gives you the idea.
public Region GetForegroundRegion(Bitmap input, Color backColor){
GraphicsPath gp = new GraphicsPath();
Rectangle rect = Rectangle.Empty;
bool jumpedIn = false;
for (int i = 0; i < bm.Height; i++) {
for (int j = 0; j < bm.Width; j++) {
Color c = bm.GetPixel(j, i);
if (c != backColor&&!jumpedIn) {
rect = new Rectangle(j, i, 1, 1);
jumpedIn = true;
}
if (jumpedIn && (c == backColor || j == bm.Width - 1)) {
rect.Width = j - rect.Left;
gp.AddRectangle(rect);
jumpedIn = false;
}
}
}
return new Region(gp);
}
//Usage
HatchBrush brush = new HatchBrush(HatchStyle.Percent30, Color.Green, Color.Yellow);
PaintHatchBrush(yourImage, brush);
//then yourImage will have HatchBrush painted on the surface leaving the Black background intact.
//This image will be used in the next process to turn the Black background into 70%
//opacity background as you did using ColorMatrix (or more simply using ColorMap as I posted previously)
I am developing a wpf user control in a wpf user control library it uses Viewport3D.
I see the image in viewport on running application and I can save it to file with RenderTargetBitmap if I call it in a WPF Application.
But if I instantiate user control in the console application and try to export the empty image was saved. The code is
class Program
{
static void Main(string[] args)
{
Thread t = new Thread(DoIt);
t.SetApartmentState(ApartmentState.STA);
t.Start();
Console.WriteLine("press enter");
Console.ReadLine();
}
static void DoIt()
{
var x = new WpfControlLibrary1.Showcase();
x.GenerateViewPort();
x.TakeScreenshot(300, 300);
}
}
public partial class Showcase : UserControl
{
private PerspectiveCamera _PerspectiveCamera = null;
public Showcase()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler((sender, e) => this.GenerateViewPort());
}
public void GenerateViewPort()
{
this._PerspectiveCamera = new PerspectiveCamera(new Point3D(0, 0, 5), new Vector3D(0, 0, -5), new Vector3D(0, 1, 0), 45);
var viewport3D = new Viewport3D() { Camera = this._PerspectiveCamera };
viewport3D.Children.Add(new ModelVisual3D() { Content = GenerateModel() });
viewport3D.Loaded += new RoutedEventHandler(this.viewport3D_Loaded);
this.grdAniContainer.Children.Add(viewport3D);
}
private static Model3DGroup GenerateModel()
{
...
}
void viewport3D_Loaded(object sender, RoutedEventArgs e)
{
...
}
public void TakeScreenshot(int width, int height)
{
takeScreenshot(this.grdAniContainer.Children[0], width, height);
}
private static void takeScreenshot(Visual visual, int width, int height)
{
var v = visual as Viewport3D;
v.Width = width;
v.Height = height;
v.Measure(new Size(width, height));
v.Arrange(new Rect(0, 0, width, height));
var vRect = new Rectangle();
vRect.Width = width;
vRect.Height = height;
vRect.Fill = Brushes.White;
vRect.Arrange(new Rect(0, 0, vRect.Width, vRect.Height));
var bmp = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(vRect);
bmp.Render(v);
var png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(bmp));
var dlg = new SaveFileDialog();
dlg.DefaultExt = ".png";
dlg.Filter = "PNG Images (.png)|*.png";
if (dlg.ShowDialog() ?? false == true)
{
string filepath = dlg.FileName;
using (var stm = File.Create(filepath))
png.Save(stm);
}
}
}
Load event is not fired and viewport3D_Loaded is not handle in console application, so I invoke manually.
Is there a way for loading visuals in memory and render to file programmatically?
I need add this delegate after instantiate "bmp" in "takeScreenshot" method
v.Dispatcher.Invoke(((Action)(() => bmp.Render(v))), DispatcherPriority.Render);
A user selects a portion of an image for a cut and paste operation. I create a new bitmap, paste the selected portion in the new image, wipe the source array and paste it back into the old image. Works but at least half the time it hangs with Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
Any thoughts or help?
public BitmapSource CutToNew(double left, double top, double width, double height, double pageWidth, double pageHeight)
{
var destBmp = new Bitmap((int)pageWidth, (int)pageHeight);
var g = Graphics.FromImage(destBmp);
g.FillRectangle(new SolidBrush(Color.White), 0, 0,
(int)pageHeight, (int)pageWidth);
g.Dispose();
var croppedArea = new Rectangle((int)left, (int)top, (int)width, (int)height);
BitmapData croppedSource = _bitmapImage.LockBits(croppedArea,
ImageLockMode.ReadWrite, BitmapImage.PixelFormat);
var croppedDestArea = new Rectangle((int)left, (int)top, (int)width, (int)height);
BitmapData croppedDest = destBmp.LockBits(croppedDestArea,
ImageLockMode.WriteOnly, BitmapImage.PixelFormat);
// Create data array to hold bmpSource pixel data
int stride = croppedSource.Stride;
int numBytes = stride * (int)height;
var srcData = new byte[numBytes];
var destData = new byte[numBytes];
Marshal.Copy(croppedSource.Scan0, srcData, 0, numBytes);
//Tried creating a separate array in case that helped.
Array.Copy(srcData, destData, srcData.Length);
//Often hangs here with Attempted to read or write protected memory.
Marshal.Copy(destData, 0, croppedDest.Scan0, numBytes);
destBmp.UnlockBits(croppedDest);
var retVal = new DocAppImage {BitmapImage = destBmp};
destBmp.Dispose();
//Blank the source area
for (int y = 0; y < srcData.Length; y++)
srcData[y] = 0xFF;
Marshal.Copy(srcData, 0, croppedSource.Scan0, numBytes);
_bitmapImage.UnlockBits(croppedSource);
return retVal.bmpSource;
}
private Bitmap _bitmapImage;
public Bitmap BitmapImage
{
get
{
if (_bitmapImage != null)
return _bitmapImage;
if (FileImage != null)
{
var stream = new MemoryStream(FileImage); //Fileimage=TIFF read from file.
_bitmapImage = new Bitmap(stream);
return _bitmapImage;
}
return null;
}
set
{
if (value != null)
{
ImageCodecInfo codecInfo = GetImageCodecInfo("TIFF");
... implementation to set the bitmap image.
You may want to try specifying your PixelFormat when you create the new object.
For example:
var destBmp = new Bitmap((int)pageWidth, (int)pageHeight, PixelFormat.Format24bppRgb);
First of all, I'd like to suggest that it is not a duplicate of THIS question. At least that's my opinion :)
What I want to achieve is a series of frames to "fade" animation.
I choose two PNG files (let' say they are the same size), for example:
Picture 1
Picture 2
I want to "simulate" merging them like layers in graphic editor. I put Pic1 on the top with opacity 255, Pic2 below with opacity 0, so at first I see only Pic1. Then I change their opacity, like this:
Pic1-200, Pic2-150
Pic1-150, Pic2-200
Pic1-100, Pic2-230
Is there any simple way for it?
In a winforms app this can be done pretty easily. Create a user control with a few properties:
public Image FromImage { get; set; }
public Image ToImage { get; set; }
private float opacity = 1;
Now override OnPaint
protected override void OnPaint(PaintEventArgs e)
{
if (FromImage != null && ToImage != null)
{
ColorMatrix matrix1 = new ColorMatrix();
matrix1.Matrix33 = opacity;
ImageAttributes attributes1 = new ImageAttributes();
attributes1.SetColorMatrix(matrix1, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
ColorMatrix matrix2 = new ColorMatrix();
matrix2.Matrix33 = 1 - opacity;
ImageAttributes attributes2 = new ImageAttributes();
attributes2.SetColorMatrix(matrix2, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
e.Graphics.DrawImage(FromImage, new Rectangle(0, 0, this.Width, this.Height), 0, 0, this.Width,
this.Height, GraphicsUnit.Pixel, attributes1);
e.Graphics.DrawImage(ToImage, new Rectangle(0, 0, this.Width, this.Height), 0, 0, this.Width,
this.Height, GraphicsUnit.Pixel, attributes2);
}
base.OnPaint(e);
}
Now drop a timer onto the control, set its to enabled with an elapsed time of something like 100ms. Handle the tick event:
private void timer_Tick(object sender, EventArgs e)
{
if(opacity == 0)
{
this.timer.Stop();
return;
}
this.opacity -= 0.01f;
this.Invalidate();
}
et voila. However, there's one thing to be aware of. This makes quite a flickery transition, which can be alieviated somewhat with this line in the control's constructor:
this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint,true);
Update based on Edit: You could turn this into a utility that takes 2 images and, using much the same code, outputs each step to a new image. Somthing like:
public class ImageUtility
{
private Image image1;
private Image image2;
public ImageUtility(Image image1, Image image2)
{
this.image1 = image1;
this.image2 = image2;
}
public void SaveTransitions(int numSteps, string outDir)
{
var opacityChange = 1.0f/(float) numSteps;
for(float opacity = 1,i=0;opacity>0;opacity-=opacityChange,i++)
{
using(var image = new Bitmap(image1.Width,image2.Width))
{
Graphics g = Graphics.FromImage(image);
ColorMatrix matrix1 = new ColorMatrix();
matrix1.Matrix33 = opacity;
ImageAttributes attributes1 = new ImageAttributes();
attributes1.SetColorMatrix(matrix1, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
ColorMatrix matrix2 = new ColorMatrix();
matrix2.Matrix33 = 1 - opacity;
ImageAttributes attributes2 = new ImageAttributes();
attributes2.SetColorMatrix(matrix2, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(image1, new Rectangle(0, 0, image1.Width, image1.Height), 0, 0, image1.Width,
image1.Height, GraphicsUnit.Pixel, attributes1);
g.DrawImage(image2, new Rectangle(0, 0, image2.Width, image2.Height), 0, 0, image2.Width,
image2.Height, GraphicsUnit.Pixel, attributes2);
image.Save(Path.Combine(outDir,"Image" + i + ".png"),ImageFormat.Png);
}
}
}
Usage:
ImageUtility util = new ImageUtility(Image.FromFile(#"C:\path\pic1.png"), Image.FromFile(#"C:\path\pic2.png"));
util.SaveTransitions(100, #"C:\path\output"); // saves 100 images
Using winforms you can use Graphics.DrawImage, using the overload that takes an ImageAttributes parameter. That class can specify manipulation to the colour (and alpha) values.
The example on the ImageAttributes page is nearly what you want. Just draw the original and transformed one in the same place, and change the colour matrix to only change the alpha level.