-1

Is it possible to create another Graphics form an existing Graphics with a different coordinate system? For example, let's say that the black rectangle is the ClipRectangle of the Graphics object I got in OnPaint(). If I only want to draw inside the blue rectangle, I would have to constantly calculate the offset. That is, to draw at the logical point (0,0) in the blue rectangle, I would have to add the x and y offsets. That makes drawing code complicated.

Is there a way to create some sort of virtual Graphics using the Graphics above but with a different coordinate system? Something like below

//not actual code
var virtualG = Graphics.FromExistingGraphics(e.Graphics, 200/*x1*/, 200/*y1*/, 1000/*x2*/, 600/*y2*/);

//Doing the same thing as e.Graphics.DrawLine(pen, 200, 200, 400, 400) 
virtualG.DrawLine(pen, 0, 0, 200, 200); 

//Draws nothing, NOT the same as e.Graphics.DrawLine(pen, 1100, 200, 1200, 400)
//, because its outside of the virtualG's bounds.
virtualG.DrawLine(pen, 900, 0, 1000, 200); 

I thought about creating a new Graphics using bitmap, draw there and then copy the bitmap to the specified location of the Graphics from OnPaint, but that could be slow.

enter image description here

Damn Vegetables
  • 11,484
  • 13
  • 80
  • 135
  • 2
    [Graphics.ScaleTransform](https://learn.microsoft.com/en-us/dotnet/api/system.drawing.graphics.scaletransform), [Matrix.Scale](https://learn.microsoft.com/en-us/dotnet/api/system.drawing.drawing2d.matrix.scale) (and all related transformations), [Rectangle.Inflate](https://learn.microsoft.com/en-us/dotnet/api/system.drawing.rectangle.inflate) (more...) – Jimi Jul 16 '20 at 11:17
  • The output to `Graphics` is the end of output. But you can add more layers before that, where you operate in different units, add camera logic (also useful in 2d), transformations of coordinates, needed abstractions. So you can have 2 planes with different coordinate systems, which are somehow positioned in your 2d/3d world and are shown on screen through camera. – Sinatr Jul 16 '20 at 11:30
  • You could also check the [Graphics.TransformPoints](https://docs.microsoft.com/en-us/dotnet/api/system.drawing.graphics.transformpoints) method (works, well, pretty fast). All transformation (using a Matrix or the provided helper methods) can be applied to both the Graphics object and the [GraphicsPath](https://docs.microsoft.com/en-us/dotnet/api/system.drawing.drawing2d.graphicspath.transform) object. When combined, these allow to build a lot of different tool. Not mentoning the transformations that can be applied to Pens and Brushes (see the GraphicsPath about this, to transform using Pens) – Jimi Jul 16 '20 at 11:30
  • 1
    Please note: The `Graphics` object does not __contain__ any graphics; it is a **tool** that lets you draw onto a related bitmap, including a control's surface. The system needs to draw all the controls' surfaces at times you can't control; therefore all you want to add to those surfaces must be created from the one event that the system will call, which is the `Paint` event. Only __non-persistent__ graphics operation like displaying a dynamic rubber-band rectangle are ok with a `Graphics` object you get from `control.CreateGraphics()`. And measurements without drawing... – TaW Jul 16 '20 at 11:58
  • Also: You can't cache a Graphics object but you can store and restore the current settings including scale and translations.. – TaW Jul 16 '20 at 11:58
  • @Jimi Scaling is not what I wanted. `TranslateTransform` and `ResetTransform` seem to be close to what I wanted to do. By writing some sort of a simple utility method that uses `TranslateTransform` and `SetClip`, I think I can achieve what I wanted. Thanks. – Damn Vegetables Jul 16 '20 at 13:18
  • Sure, you can use different tools to perform specific transformations. I'd avoid `SetClip()` when possible, since it's difficult to deal with clipping regions when the shapes you're drawing are not exactly rectangular (anti-aliasing stuff, you'll see). See [here](https://stackoverflow.com/a/52921415/7444103), for example, about `TranslateTransform` and `RotateTransform`. Don't underestimate the Matrix class, it leaves you a lot of freedom when applying different transformations. Simple example + some instructions [here](https://stackoverflow.com/a/53182901/7444103). – Jimi Jul 16 '20 at 13:57
  • The transformation you're showing here is a ScaleTransform operation (if it includes world transformations and not just resampling a graphic object or scaling a container/shape - see also the [GraphicsContainer](https://learn.microsoft.com/en-us/dotnet/api/system.drawing.drawing2d.graphicscontainer) Class). – Jimi Jul 16 '20 at 14:01
  • @Jimi Not underestimating the Matrix class, but I cannot understand how the Matrix class can be used to clip the graphics not to use SetClip(). Also, the GraphicsContainer's name sounds as if that is what I wanted, but the documentation page lacks a good explanation about what it is for. Searching Google for it just gave me [this answer](https://stackoverflow.com/questions/29170453/how-do-graphic-containers-work) which basically says that it is poorly documented and few people know how to use it. – Damn Vegetables Jul 19 '20 at 13:29
  • It's pretty simple, actually. A GraphicsContainer is used to apply transformations to an existing Graphics state (note that GraphicsContainer can be nested): the two Rectangles arguments define two sections of two Matrices, the first Rectangle is the Identity Matrix, the second represents the Transformations applied to the Identity. The first two values define a Translation, the other two define a Scale (it's actually the same thing as using `TranslateTransform` + `ScaleTransform` or a single Matrix object - a description [here](https://stackoverflow.com/a/53182901/7444103))... – Jimi Jul 19 '20 at 20:57
  • Note that the Scale is applied to ALL measures, including the initial Location and, e.g., the Pen used for drawing What is the difference: using a clipping region, you define an *unbreakable* constraint (i.e., the Clipping Region cannot *bleed*: what you draw outside of it's bounds doesn't appear in the source DC), while using a GraphicsContainer OR a GraphicsPath (these are quite similar), you can draw transformed graphics elements **inside** the DC AND transformed graphic parts can *bleed* into the DC. – Jimi Jul 19 '20 at 20:57
  • Another big difference is that Regions don't support anti-aliasing, so if you clip non-rectangular regions, no anti-aliasing can be applied to the borders, which will be **quite visibly** pixellated. So, if you need to just define a rectangular Region and clip all the graphics objects inside it, by all means, use `SetClip()`. If you need non-rectangular shapes, you have to choose between a simple method or slightly more complex methods that can preserve the quality of the drawing. In the end, you choose the tools that best fit your requirements in a specific context (well, what's new). – Jimi Jul 19 '20 at 21:02

1 Answers1

0

If you don't want any scaling, and simply want to move the origin to (200, 200), then use TranslateTransform as suggested previously. If you want to clip any drawings outside that rectangle, then use SetClip.

Here's an example, showing the same set of lines drawn before and after translating and clipping. The black lines are before, while the blue line is after (the second blue line is clipped and therefore not visible). The red rectangle shows the clipping region:

enter image description here

Code:

public partial class Form1 : Form
{

    public Form1()
    {
        InitializeComponent();
    }

    public class Line
    {
        public Point ptA;
        public Point ptB;
        public void Draw(Graphics G, Color color)
        {
            using (Pen P = new Pen(color))
            {
                G.DrawLine(P, ptA, ptB);
            }
        }
    }

    private List<Line> Lines = new List<Line>();

    private void Form1_Load(object sender, EventArgs e)
    {
        Lines.Add(new Line() { ptA = new Point(0,0), ptB = new Point(200, 200)});
        Lines.Add(new Line() { ptA = new Point(900, 0), ptB = new Point(1000, 200) });
    }        

    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
        Graphics G = e.Graphics;
        G.Clear(Color.DarkGray);

        foreach(Line line in Lines)
        {
            line.Draw(G, Color.Black);
        }

        Rectangle rc = new Rectangle(new Point(200, 200), new Size(800, 400));
        G.DrawRectangle(Pens.Red, rc);
        G.SetClip(rc);

        G.TranslateTransform(rc.Left, rc.Top); // origin is now at (200, 200)

        foreach (Line line in Lines)
        {
            line.Draw(G, Color.Blue);
        }
    }

}
Idle_Mind
  • 38,363
  • 3
  • 29
  • 40