views:

262

answers:

3

I have recently begin creating an image editing tool which will cater to a very specific need. This is as much for the people who are going to use it as it is for my own entertainment. however, I have hit a bit of an architectural snag early on.

Like any image editor, the user will use 'tools' to draw on and manipulate an image. My first attempt at this consisted of a simple interface:

public interface IDrawingTool
{
    void DrawEffect( Graphics g );
    // other stuff
}

This (I thought) would be nice and clean and would allow for easy maintenance and extension. Just add the interface objects in and call the DrawEffect method of the selected one at runtime.

The problem with this approach is that different drawing tools do not cleanly adhere to a single interface. For example, a pen tool need only know the point to draw at in order to work. The rectangle however needs the first point clicked, as well as the current position. The polygon tool needs to keep track of multiple mouse clicks.

I am having trouble thinking of a nice way to implement this. The best method that I can think of now would involve a switch statement and a case for each tool, which would mean that the drawing logic would be in the Canvas class, not encapsulated by Tool type objects. because this is practice, I would like to do this the right way. Thanks for any help in advance.

A: 

Okay, rule of thumb: if you see a switch statement in your code sketch, it's a sign you need to use polymorphism instead. So, in this case, you want to be able to have various operations, and you're finding yourself wanting a switch, so you should think "how can I make this something using polymorphism?"

Now,have a look at the Command pattern, where your objects are verbs instead of nouns. Each Command implements a doThis() method; when you construct the object, you establish what the command wil do.

public interface Command {
   public void doThis(Graphics g);  // I don't promise returning 
                                    // void is the best choice
   // Would it be better to return a Graphics object?
}

public class DrawRectangle implements Command {
   public DrawRectagle( Point topLeft, Point btmRight) { // ...
   }
   public void doThis(Graphics g){ // ...
   }
}

Now, consider what you would do if you wanted to implement undo?

Update

Okay, let's extend this a bit more. The point of using this pattern is to make sure the client doesn't need to know all that much, except when you're doing the original construction. So for this example, let's think about drawing a rectangle. When you picka Rectangle tool, you're going to have some code on the button-click event handler (this is all pseudocode btw)

 cmdlist = [] // empty list
 bool firstClick = true
 Point tl = br = new Point(0,0)
 onClick:
   if firstClick:
     get mouse position into tl
     firstClick = false
   else:
     get mouse position into br
     cmdlist.append(new DrawRectangle(tl, br))
     firstClick = true

So now when you've picked out the rectangle, you add a DrawRectangle object to the command list structure. Sometime later, you run through the list

for cmd in cmdlist:
   cmd.doThis(Graphics g)

and these things get done. It should be obvious now that you would implement undo by adding an "undoThis" method to Command. When you create a command, you have to buuild code so that the object will know how to undo itself. Then undo means just taking the last Command object off the list and doing its undoThis method.

Charlie Martin
Yes, I stopped when I saw the switch statement writing itself :). however, I don't think that solves my problem. I would like to use the tools through the same interface, but how to get them the (disparate) information they need to do their job?
Ed Swangren
...For example, the DrawRectnagle method is not exposed by the Command interface, so the client would still need to know that they are dealing with a DrawRectangle object and not just a Command object.
Ed Swangren
Why? As long as the object implementing Command knows what to do, why does the client care? I'll add more on this, it won't fit in a comment.
Charlie Martin
A: 

I faced a similar problem when trying to redesign my mapping SW to support both GDI+ and Cairo graphics libraries. I solved it by reducing the drawing interface to some common operations/primitives, see the code below.

After this, the "effects" you want to draw are Commands (like Charlie says). They use the IPainter interface to draw. The nice thing about this approach is that the effects are completely decoupled from a concrete drawing engine like GDI+. This comes handy to me, since I can then get my drawing exported to SVG by switching to Cairo engine.

Of course, if you need some additional graphics operations, you would have to extend the IPainter interface with it, but the basic philosophy stays the same. See more about this here: http://igorbrejc.net/development/c/welcome-to-cairo

public interface IPainter : IDisposable
{
    void BeginPainting ();
    void Clear ();
    void DrawLines (int[] coords);
    void DrawPoint (int x, int y);
    void EndPainting ();
    void PaintCurve (PaintOperation operation, int[] coords);
    void PaintPolygon (PaintOperation operation, int[] coords);
    void PaintRectangle (PaintOperation operation, int x, int y, int width, int height);
    void SetHighQualityLevel (bool highQuality);
    void SetStyle (PaintingStyle style);
}

public class PaintingStyle
{
    public PaintingStyle()
    {
    }

    public PaintingStyle(int penColor)
    {
        this.penColor = penColor;
    }

    public int PenColor
    {
        get { return penColor; }
        set { penColor = value; }
    }

    public float PenWidth
    {
        get { return penWidth; }
        set { penWidth = value; }
    }

    private int penColor;
    private float penWidth;
}

public enum PaintOperation
{
    Outline,
    Fill,
    FillAndOutline,
}
Igor Brejc
A: 

How about you design your interface a bit complexer? Lets start off with some code and afterwards I'll explain how it's supposed to work.

public class AbstractDrawingTool {

    private Graphics g;

    void AbstractDrawingTool( Graphics g ) {
        this.g = g;
    }

    void keyDown(KeyEvent e);
    void keyUp(KeyEvent e);
    void mouseMove(MouseEvent e);
    void mouseClick(MouseEvent e);
    void drop();
    // other stuff
}

The idea is to hand down the user input to the tool, once the user starts working with a specific implementation. This way, you could create lots of different drawing tools all using the same interface. A simple PointDrawingTool for example would only implement the mouseClick event to place a point on the canvas. A PolygonDrawingTool would also implement the keyUp event so that it could stop the drawing the lines when a specific key (i.e. the escape key) was pressed.

A special case is the drop method. It would be called, to "drop" the currently selected tool. This would happen, if another implementation was selected from a toolbar or similar.

You could also combine this definition with the command pattern. In this case, an implementation of the AbstractDrawingTool would be responsible for creating instances of the Command interface and perhaps place them on the stack once an operation is finished (i.e. placing a point on the canvas).

Stefan Schmidt