I have a fine, working system in C# that draws with Cairo commands in a render method. However, sometimes I would like to draw into a pixmap, rather than dynamically when the screen needs to be updated. For example, currently I have:
public override void render(Cairo.Context g) {
g.Save();
g.Translate(x, y);
g.Rotate(_rotation);
g.Scale(_scaleFactor, _scaleFactor);
g.Scale(1.0, ((double)_yRadius)/((double)_xRadius));
g.LineWidth = border;
g.Arc(x1, y2, _xRadius, 0.0, 2.0 * Math.PI);
g.ClosePath();
}
But I would like, if I choose, to render the Cairo commands to a Gtk.Pixbuf. Something like:
g = GetContextFromPixbuf(pixbuf);
render(g);
Is that possible? It would be great if I didn't have to turn the context back into a pixbuf, but that the cairo drawing would go directly to the pixbuf. Any hints on this would be appreciated!
The answer is actually quite easy: when you render the objects, render them to a context created from a saved surface. Then when you render the window, insert a context based on the same saved surface.
Create a surface:
surface = new Cairo.ImageSurface(Cairo.Format.Argb32, width, height);
Render a shape to the surface:
using (Cairo.Context g = new Cairo.Context(surface)) {
shape.render(g); // Cairo drawing commands
}
Render the window:
g.Save();
g.SetSourceSurface(surface, 0, 0);
g.Paint();
g.Restore();
... // other Cairo drawing commands
That's it!
Related
I am looking to get the same effect as the GraphicsPath from Winforms which allows to keep some particular areas of myForm unrefreshed. f.i.:
myForm.Invalidate(new Region(graphicsPath));
My final goal is to draw things at the unrefreshed location, using a HDC (Device context handle) that I will provide to an external application. (This currently works using winforms).
I am using SFML.Net 2.4 and I create my window this way:
SFML.Graphics.RenderWindow mySfmlWindow = new RenderWindow(myForm.Handle, settings);
I can still create the HDC on myForm, however, even without calling :
mySfmlWindow.Clear(color);
, the things drawn by the external application are still instantly cleared.
Manual approach
You can draw your desired background yourself. I've got an example, where I draw a half of the window background manually, while the other half is not cleared.
The left half is "cleared" in gray just to show the point.
In the code, I use a sf::RectangleShape to clear the window, but you can use a sf::VertexArray if your shape is more complex.
Full Code
int main(){
sf::RenderWindow win(sf::VideoMode(640, 480), "SFML Test");
sf::RectangleShape r1;
r1.setOrigin(sf::Vector2f(25, 25));
r1.setPosition(50, 50);
r1.setSize(sf::Vector2f(50, 50));
r1.setFillColor(sf::Color::Red);
sf::RectangleShape r2;
r2.setOrigin(sf::Vector2f(25, 25));
r2.setPosition(400, 50);
r2.setSize(sf::Vector2f(50, 50));
r2.setFillColor(sf::Color::Blue);
sf::RectangleShape updatedRegion;
updatedRegion.setPosition(0, 0);
updatedRegion.setSize(sf::Vector2f(320, 480)); // Half window
updatedRegion.setFillColor(sf::Color(30,30,30)); // Dark gray just for the sake of the example
while (win.isOpen())
{
sf::Event event;
while (win.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
win.close();
}
}
//win.clear(); // We skip the clearing process
win.draw(updatedRegion); // And do our own "clear"
win.draw(r1);
win.draw(r2);
win.display();
// Just some movement to test the concept
r1.rotate(0.01);
r2.rotate(0.01);
}
return 0;
}
I am trying to make a retro game engine in C#. I want to use a resolution of 320x200, but the screen does not natively support that, so I'm trying to decide what is the most efficient method of emulating that. Do I create a Bitmap object and then use SetPixel and create methods for drawing basic shapes? Then scale the image to the size of the screen. Should I draw little Rectangle objects instead to mimic the pixels? What do you think would be the most efficient? Also any other ideas?
You could just use the DrawImage of the Graphics object to paint your 320x200 bitmap on a rectangle of any size.
By setting the interpolation mode on the graphics object first, you can control the way the image is painted when resized. Different interpolation modes should give different visual results and chances are you will be satisfied with one of built-in modes so that you don't have to provide any custom implementation of the streching algorithm.
On the other hand, have you considered OpenGL/DirectX rather than GDI+?
If you must use GDI+ then let it handle the scaling using this method can leverage hardware acceleration if available as opposed to drawing into bitmaps. But agreed with other poster there are better frameworks for this have a look at XNA.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var gameTick = new Timer {Interval = 10};
gameTick.Tick += (s, e) => BeginInvoke((Action)(Invalidate));
gameTick.Start();
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
//calculate the scale ratio to fit a 320x200 box in the form
var width = g.VisibleClipBounds.Width;
var height = g.VisibleClipBounds.Height;
var widthRatio = width / 320f;
var heightRatio = height / 200f;
var scaleRatio = Math.Min(widthRatio, heightRatio);
g.ScaleTransform(scaleRatio, scaleRatio);
//draw a 320x200 rectangle (because of scale transform this always fills form)
g.FillRectangle(Brushes.Gray, 0, 0, 320, 200);
}
}
I'm creating an XNA game that creates random islands from multiple sprites. It creates them in a separate thread, then compiles them to a single texture using RenderTarget2D.
To create my RenderTarget2D I need a graphics device. If I use the automatically created graphics device, things work okay for the most part, except that draw calls in the main game thread conflict with it. Using lock() on the graphics device causes flickering, and even then the texture is sometimes not created properly.
If I create my own Graphics device, there are no conflicts but the islands never render correctly, instead coming out pure black and white. I have no idea why this happens. Basically I need a way to create a second graphics device that lets me get the same results, instead of the black / white. Anyone got any ideas?
Here's the code I'm using to try and create my second graphics device for exclusive use by the TextureBuilder:
var presParams = game.GraphicsDevice.PresentationParameters.Clone();
// Configure parameters for secondary graphics device
GraphicsDevice2 = new GraphicsDevice(game.GraphicsDevice.Adapter, GraphicsProfile.HiDef, presParams);
Here's the code I'm using to render my islands to a single texture:
public IslandTextureBuilder(List<obj_Island> islands, List<obj_IslandDecor> decorations, SeaGame game, Vector2 TL, Vector2 BR, int width, int height)
{
gDevice = game.Game.GraphicsDevice; //default graphics
//gDevice = game.GraphicsDevice2 //created graphics
render = new RenderTarget2D(gDevice, width, height, false, SurfaceFormat.Color, DepthFormat.None);
this.islands = islands;
this.decorations = decorations;
this.game = game;
this.width = width;
this.height = height;
this.TL = TL; //top left coordinate
this.BR = BR; //bottom right coordinate
}
public Texture2D getTexture()
{
lock (gDevice)
{
//Set render target. Clear the screen.
gDevice.SetRenderTarget(render);
gDevice.Clear(Color.Transparent);
//Point camera at the island
Camera cam = new Camera(gDevice.Viewport);
cam.Position = TL;
cam.Update();
//Draw all of the textures to render
SpriteBatch batch = new SpriteBatch(gDevice);
batch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, null, null, null, null, cam.Transform);
{
foreach (obj_Island island in islands)
{
island.Draw(batch);
}
foreach (obj_IslandDecor decor in decorations)
{
decor.Draw(batch);
}
}
batch.End();
//Clear render target
gDevice.SetRenderTarget(null);
//Copy to texture2D for permanant storage
Texture2D texture = new Texture2D(gDevice, render.Width, render.Height);
Color[] color = new Color[render.Width * render.Height];
render.GetData<Color>(color);
texture.SetData<Color>(color);
Console.WriteLine("done");
return texture;
}
Here's what should happen, with a transparent background (and usually does if I use the default device)
http://i110.photobucket.com/albums/n81/taumonkey/GoodIsland.png
Here's happens when the default device conflicts and the main thread manages to call Clear() (even though it's locked too)
NotSoGoodIsland.png (need 10 reputation....)
Here's what happens when I use my own Graphics device
http://i110.photobucket.com/albums/n81/taumonkey/BadIsland.png
Thanks in advance for any help provided!
I may have solved this by moving the RenderToTarget code into the Draw() method and calling it from within the main thread the first time Draw() is called.
I'm attempting to use a custom built .PNG file of letters from the alphabet to give my game some nice looking textual graphics. However, since the core functionality of my WinForms game works using minimal graphics (I have a timer set to fire a little animation routine that fades out the word "Guessed" when a user enters a word that they've already tried - this is done using DrawString()). Right now I'm trying to get DrawImage(Image, dstRect, srcRect, Unit) to work, and I have it set to run on form load.
private void DrawLetters()
{
// Create image.
Graphics letters = this.CreateGraphics();
Image newImage = Image.FromFile(strPath + "Letter_Map.png");
// Create rectangle for displaying image.
Rectangle destRect = new Rectangle(25, 25, 80, 80);
// Create rectangle for source image.
Rectangle srcRect = new Rectangle(0, 0, 833, 833);
GraphicsUnit units = GraphicsUnit.Pixel;
// Draw image to screen.
letters.DrawImage(newImage, destRect, srcRect, units);
}
This is practically verbatim from the MSDN site. My form populates with an array of Labels right now to show the user the grid of letters needed for the game. Is it them being drawn that overwrites the custom rectangle? I want to replace them all with images eventually. I understand srcRect to be (x, y, width, height) so I gave it the full size of the 'spritesheet'. For dstRect I only want it to place a block 80x80px on the form starting around 25x, 25y.
For the heck of it I created a blank form and called my DrawLetters() function during that form load event (I copied the function into that Form's code). I saw nothing, though, so I am starting to get a bit confused. I may need some education about just HOW the drawing works in conjunction with forms and controls being drawn on screen.
EDIT This https://stackoverflow.com/questions/837423/render-a-section-of-an-image-to-a-bitmap-c-sharp-winforms was what initially got me going, but this user has a working knowledge of XNA and seems to be trying to combine that with WinForms. XNA would be overkill, I believe, for the simple text game I am trying to 'pretty up'.
You need to override the forms OnPaintMethod to get access to the forms Graphics object which you can then use to display the image on the form.
If you want to display a portion of the image, you will need to use a different overload of DrawImage, as follows:
public partial class DrawImageDemo : Form
{
public DrawImageDemo()
{
InitializeComponent();
}
private Image _sprites;
public Image Sprites
{
get
{
if (_sprites == null)
{
_sprites = Image.FromFile("test.jpg");
}
return _sprites;
}
}
protected override void OnPaint(PaintEventArgs e)
{
// The forms graphics object
Graphics g = e.Graphics;
// Portion of original image to display
Rectangle region = new Rectangle(0, 0, 80, 80);
g.DrawImage(Sprites, 25, 25, region, GraphicsUnit.Pixel);
base.OnPaint(e);
}
}
Im drawing a linear gradient manually by drawing lines with changing colors. However, this is very slow, and i seems to update, when i resize the window. How do i make it faster? The color scale is linear in this example, but later i wan't to make non-linear gradients.
protected override void OnPaintBackground(PaintEventArgs paintEvnt)
{
SuspendLayout();
// Get the graphics object
Graphics gfx = paintEvnt.Graphics;
// Create a new pen that we shall use for drawing the line
// Loop and create a horizontal line 10 pixels below the last one
for (int i = 0; i <= 500; i++)
{
Pen myPen = new Pen(Color.FromArgb(i/2,0,0));
gfx.DrawLine(myPen, 0, i, 132, i);
}
ResumeLayout();
}
The problem is that GDI+ is incredibly slow.
You should use high level constructs with GDI+ which are relatively fast (relative to drawing lines like you do now). See http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.lineargradientbrush.aspx for more information about e.g. the LinearGradientBrush. There are much more of these brushes and pens which should help you increase your performance.
One more thing: the Suspend/ResumeLayout doesn't do anything in your example. These methods only apply when you are doing layout by e.g. adding Controls to the current form or changing properties on existing Controls like the Dock property or the Height and Width.
If you want to paint it once and only once, without resizing, I suggest you write this to a Bitmap object once, and then draw this bitmap to the background. Also, you can enable double buffering on the form. this should be a property called DoubleBuffering, or something similar. This should reduce the flashing you get when redrawing your form.
You could pre-compute the color values so you won't have to do it on every redraw. Other than that, there's not much more you can do without resorting to more lowlevel APIs, like XNA.
Update: it is perfectly feasible to host XNA within WinForms controls. There's some nice links forward in this question.
Perhaps specifying a ColorBlend to use with the LinearGradientBrush suggested by Pieter will address your concerns about being able to paint non-linear gradients in the future?
You can create a ColorBlend object that specifies the colors of your choice and an arbitrary position for each. By setting the InterpolationColors property of the LinearGradientBrush to your ColorBlend object, you should be able to get any effect that you want.
MSDN gives the following sample:
protected override void OnPaint(PaintEventArgs e)
{
//Draw ellipse using ColorBlend.
Point startPoint2 = new Point(20, 110);
Point endPoint2 = new Point(140, 110);
Color[] myColors = {Color.Green, Color.Yellow, Color.Yellow, Color.Blue, Color.Red, Color.Red};
float[] myPositions = {0.0f,.20f,.40f,.60f,.80f,1.0f};
ColorBlend myBlend = new ColorBlend();
myBlend.Colors = myColors;
myBlend.Positions = myPositions;
LinearGradientBrush lgBrush2 = new LinearGradientBrush(startPoint2, endPoint2, Color.Green, Color.Red);
lgBrush2.InterpolationColors = myBlend;
Rectangle ellipseRect2 = new Rectangle(20, 110, 120, 80);
e.Graphics.FillEllipse(lgBrush2, ellipseRect2);
}