GUI Designers need colors — lot of colors! Sometimes you may find nice set of colors in magazines, pictures or simply in nature.
So: take a picture, scan the picture and pick the colors which you like. The problem is that the quality of the picture may not be so good: any pixel from the roster may not be as good as the general perception of the color from a wide surface. To obtain even better results, artists know that is advisable to combine adjacent colors (kill contrast). Averaging the entire picture may result in a color suitable for the background (or may not...).
The program that I'm presenting here is able to do exactly that: separate a rectangular area from an image and average its color. Using nice pictures, you may produce interesting set of colors and use them in your graphical interfaces.
This program was produced with Visual Studio 2008 Beta 2. It exhibits features as XAML GUI design: designing menus and context menus, working with image, canvas, scroll-view, grid and List Box WPF controls, and manipulating shapes and image content with the old GDI+. The result may be extracted to the Clipboard.
I expect the interface to have a big area for displaying a loaded image, a menu, eventually a status bar and a list of selected colors. The program will be able to manipulate the image through a scroll-view and by changing the zoom factor. The available functions will allow loading the image from a file, cropping the part of interest from the image (and eventually saving it back on the disk,) and averaging the color of a selected rectangle. The extracted colors would go in a list box, and would be available as hex strings to be added in other code files or documents. The main functions (load, save, zoom, crop and pick colors) will be available in the main menu grouped in categories: "
View" and "
Edit", while extracting functions will come as a popup menu on each color item in the list of selected colors. Finally, the last selection will be available in the status bar.
To start, open VS2008 and create a new "
WPF Project". The project contains already a main window which it presents in a designer. In the XAML part of the designer we may change the title of the window (it is possible to change the name of the window too, but since I don't expect to create more windows, it will stay as it is: "
Window1" as a class in the "
To accommodate various controls for the UI, WPF provided a very versatile control: the Grid. Add a grid to the main form and split the main area in three rows: the menu row, the content row and the status-bar row. Note that some dimensions are post fixed with a "
*" – those will be variable accommodating more or less content as the grid resizes with the form. I have added also two columns, mainly for the image and the selected-color list.
Title="Pick Fashion" Height="481" Width="377">
<Grid Name="grid1" >
<ColumnDefinition Width="315*" Name="Col0"/>
<ColumnDefinition Width="40" Name="Col1"/>
<RowDefinition Height="22" />
<RowDefinition Height="395*" Name="Row1"/>
<RowDefinition Height="28" />
For working with the image, we anticipate the image to be bigger than the provided real estate space in the form. For this reason in
Col0 we add a
ScrollView control; then in the scroll view — a
Canvas which will hold the image and a shape — the selecting rectangle. This last will be added by code.
<ScrollViewer Grid.Row="1" Grid.Column="0" Name="scrollViewer1"
<Canvas Name="canvas1" Height="393" Width="315">
<Image Name="image1" Cursor="Cross" ForceCursor="True"
MouseUp="image1_MouseUp" Canvas.Top="0" Canvas.Left="0"
SizeChanged="image1_SizeChanged" Height="18" Width="66" />
To populate the image, as well as for the other functions, we need a MainMenu: In the Edit, View and Zoom menus observe the SubmenuOpened event handlers: These events are activated before the submenu opens and gives a chance to the code for cheching or enabling other menu items:
- the edit menu enables the
Crop menu items only when the private variable
FileName is not null and the
selectedRectangle shape was initialized;
View menus just check corresponding menu items before those are activated.
To show, or hide the Color List Box, the
Colors_Click handler just changes the
Col1.Width = new GridLength((Colors.IsChecked) ? 0.0 : 40.0);
events; they are responsible for drawing the rectangle which delimits the area to be cropped or averaged:
By default the image will fit inside the Canvas and will resize with the window without changing the aspect ratio. However, some may find working at this size difficult, unless maximizing the window to the entire screen. To make this issue easier, I've introduced the concept of the zoom factor: there is no
200%... fit to page zoom; instead, the image will fit completely at zoom factor 1.0, just half — either the height or the width, whichever is appropriate, and so on. Zoom factor less than 1 does not make sense. For 1.0 — the scroll view does not display any scroll bars, while for bigger factors, the control displays whatever scroll bar is appropriate (at least one,) so the user may go to whatever section s/he wants.
Because the selection rectangle shape is inside the same Canvas with the image, and the canvas is inside the
ScrollView control, the rectangle moves around with the image, however resizing the image with a zoom factor needs adjustments. This goes like this:
double zoomFactor = 2.0;
When the image size should change as a result of window resizing or a
ZoomX menu item click, the image size is calculated as a multiplication of the
zoomFactor with the corresponding grid cell (
Row1). As a result of changing the size of the image, the canvas is readjusted to match the Actual Size of the image, and the
selectedRectangle (if any) is rescaled.
Ah – Crop! ...GDI
As an alternative to using the Zoom Factor, the user may choose to crop the selected rectangle and continue working on a smaller image.
Unfortunately, I did not find any better way to work my way to cropping and averaging the rectangle's pixels without the good, old GDI library. Two routines from the internet proved themselves very useful for this process:
With these, the crop goes as follows:
Note that we could have read the
System.Drawing.Bitmap from its file; however that method would have been inconvenient in the case of successive crops — unless we care to save and rename the new images. In the end, I cared to clear the
selectedRectangle, as per the new image it does not make any sense, and to reset the
FileName variable, so the
Save menu item would know to default to
The same goes averaging the colors in the selected rectangle:
After getting the source as a
System.Drawing.Bitmap the code just iterates on rows and columns of pixels in the selected rectangle area and averages each component. In the end, it produces a
System.Windows.Media.Color from these components and color and label the status bar. With the same color it creates a new Shape object and adds it in the
ColorListBox on the right hand, near the image. The new list element has a tool tip which shows its color hex-value, and a context menu which allows extracting the color value(s) in the Clipboard, and deleting the unwanted elements. The
ContextMenu is one for all elements and has been created in the constructor of the window:
Col1.Width = new GridLength(0.0);
MenuItem miCopy = new MenuItem();
miCopy.Header = "Copy to Clipboard";
miCopy.Click += new RoutedEventHandler(miCopy_Click);
MenuItem miCopyAll = new MenuItem();
miCopyAll.Header = "Copy All to Clipboard";
miCopyAll.Click += new RoutedEventHandler(miCopyAll_Click);
MenuItem miDelete = new MenuItem();
miDelete.Header = "Delete";
miDelete.Click += new RoutedEventHandler(miDelete_Click);
I have hidden the
ColorListBox in code, because is more convenient to have it open during the design time.
Right-clicking any list item and selecting "Copy All to Clipboard", the clipboard will be filled with the following text:
Which was the targeted goal of this project!
Points of Interest
So much for the WPF and GDI in this project! I wish I could remove the GDI round-trips from this program — maybe someone will provide us with a better way, but until then, I hope these methods will come in handy for lot of us.
Note that the
SaveAs functions are not yet implemented (who cares?!)
Also I wish to implement a command pattern for cropping/outcropping, selecting/unselecting etc.
Stay tuned — I might come back with some improvements, among them — form persistence with XML.