MVVM in Photoshop-like application: What object has the responsibility for composing the document

design-patterns

I'm wondering how an application like Adobe Photoshop, could be architectured to conform to the C+M+V+VM design-pattern, specifically how do you handle the canvas?

There would be a Model system which represents the current document, with its layers (each containing raw pixel data, potentially gigabytes worth), effects applied to layers, and other information about the document state.

Then you would have the ViewModel for the Main Window which would have child-ViewModels for each tool-window, as well as a child-ViewModel for the main canvas area. My question relates to how the ViewModel would represent the current canvas data. It is the View's responsibility to draw the pixels of the composited document to the screen (or some virtual output device for tests), but who has responsibility to compose the document? Is it correct to even compose the document at all? – especially if the View is particularly advanced enough to have the capability to do that itself, such as hardware-accelerated Views. What if composition is done at the ViewModel layer on the GPU, the data would have to be passed back into main-memory and then copied again to the GPU for the View – the optimization of keeping the data in GPU memory cannot happen because the View and ViewModel should be ignorant of each other.

Is there a solution to this problem, or would a different design-pattern be appropriate?

Best Answer

There is something I had to learn while designing medical imaging / data visualization software in WPF/MVVM:

If your domain model deals with visualization, then your ViewModel has to contain visual information.

I'll elaborate.

Let's take an example of an accounting system. There you can get every application functionality purely in the model, but it would be weird for an human to deal with all the accounting concepts without visualizing them. Enter the View, purely as an aid (a true Graphical User Interface in a Human-Computer-Interaction sense), so the human operator can see the state of the underlying information, which has not a visual by itself.

On the other hand, let's take your extreme example: an application whose very purpose is to manipulate images. That is, the core Domain Model class is the Composite Image, with its layers, and each one with its pixel effects, and masks, and so on.

One core concept of MVVM, Data Binding totally loses its sense here: how are you suppose to have a property in the ViewModel, which is bound to a property in the View, if the very thing you are representing is already composed of pixels? At least, I suppose, your code-behind would need to be very rich (instead of non-existant in purist by-the-book MVVM).

What I have been doing lately (the current application I am developing has a custom realtime plotter) is more or less like this:

  1. My Model layer is a Domain Model, containing only the conceptual classes related to the broad problem, and the rules by which the objects cooperate with one another. Nothing application-related (that is, which might vary between different applications working on the same domain) lives in this layer, which by the way does not contain WPF infrastructure. Good class candidates would be CompositeCanvas, CanvasLayer, PixelEffect, Mask, Tool, etc.

  2. The ViewModel layer would contain application logic, that is, how the current application chooses to manipulate the domain model objects. Here we start to use WPF related classes. For example, I most probably would represent a CompositeCanvas with a WriteableBitmap, since it can be directly used as the Source property of an Image control via classic Data Binding.

  3. The View layer would be the dumbest as possible. Apart from the skinned input controls (toolbar buttons, custom mouse cursors and manipulator handlers), the actual CompositeImage would be composed of one or more WriteableBitmaps stacked into a Canvas or Grid or ViewBox (or a combination of them).

One important thing to consider is how the layers interact to each other:

  • If you have independent layers which don't interact with one another - that is, an application less sophisticated than Photoshop - then you can composite in the View, stacking multiple Image controls over one another inside a grid.
    • Now if you want to implement blending modes, you might prefer to have a single Image to represent every layer, and apply the composition to its BitmapSource, which might be a DrawingImage with a DrawingGroup containing multiple ImageDrawing, one for each layer. That transfer the compositing to the ViewModel.
    • Now if you want full control, so that you can apply sophisticated image-processing algorithms, represent your image as arrays (of double, that would be a good thing), change color modes, and do other Clever Things, then you could perfectly has your layers represented in the model, and have some operation to, say, transfer that array to a WriteableBitmap in the ViewModel, which then automatically updates the View's Image.

It is interesting to "visualize" (pun intended) how, for example, drawing with a self-smoothing, brushed pencil would work: with the selected tool, you click on the image and drag. You could implement your DataContext (ViewModel) as an Interface, say ITool and make mouse events like MouseMove call methods on the ViewModel. You would then store the drawing-space (not screen-space) coordinates in an array, which could be continuously smoothed by some algorithm defined in the model. Then, you can regenerate your image source (that is, render it in the model), and in the ViewModel you just set its result to the CanvasSource property, and the property changed event would automatically update the View. This is quite different than most mouse-drawing demos found using WPF.

Hope this helps!