This application is designed to surf through the photos you specify on your computer and present them with a collage/Polaroid type view. It's mostly finished. More or less. I didn't create the code that does the really cool Polaroid effect, that came from the below link. What I did do as add all the other stuff around it.
The original project came from
Sorry about sticking the source and binaries on CodePlex - the downloads are reasonably large.
Screenshots - Configuration
Screenshots - In Action!
Click here for big version
Couple Things First
- There may be profanity or bad, bad, bad words in the quotes and words.
- In fact, there is. I haven't taken them out.
- Some events or text may be too long and it gets cut off.
- So keep your custom quotes of wisdom to smallish text.
- This project came from an open source project which provided my starting point.
- But was a pretty cool app to get me started on this.
- I've rewritten the bulk of it and now it's VB.NET.
- Has little resemblance to the original project.
- It displays quotes, events and word definitions. That's why it's just about 14MB.
- It was originally written with most values hard coded.
- I've put in the ability to change a lot of what you can do.
- Some font combos won't work, really dependant on font you choose.
- If you're scared, just go with the defaults.
- They've been tried and tested.
- If you stuff things up, hit reset.
- There is a single thread for painting. For each form.
- Most collections and thread-safe and sync locks are used.
- .NET 4.0 is required. I'll be surprised if you're reading this in the app without it.
- Give us a shot if you want something done to it.
- Source code is available to those who want it.
- Data is Unicode. So go crazy with special characters.
- A lot of options can be configured - not everything, but you can customize it quite a bit.
A Note on Photo Dates and Captions
- The date comes from the Date Taken of a photo.
- This isn't available on all photos, in case of null, nothing is displayed.
- The caption comes from the photo title (again, metadata).
- If that's not available, the filename is used.
- If odd characters are present, the caption is not used.
- Enter directories to search here.
- If you include a directory with child directories, they are included automatically.
- If you enter a child directory when you have entered the parent, that's handled.
- The sidebar displays quotes, events, your quotes, and word definitions.
- You can turn these on or off. Depending on your fancy.
- Play around with the values to see what happens.
- Use fonts wisely. Big fonts will probably screw things up.
- Enter your own quotes in here, something like: Quote text|Quote Author Quote text>Another line|Quote Author
- Use > character for new line.
- If you actually want the > character, use another one. Like › or something else.
- When the text on the right is drawn, it piggy backs on the event for when a photo is drawn.
- There is a reason why the times are linked (i.e., read above point).
- Opacity is well... opacity.
- Caption date format is handy to change.
- Enclose characters in double quotes when you want to use them.
- Like "date:" (the d won't be considered the day then).
- Fonts can be changed, but change them wisely.
- Bold fonts on the photos only suit certain fonts.
- Forcing the text to lowercase wasn't done on the captions.
- That's because casing is sometimes wanted for captions.
- Like OMG. Or I feel SUPER DUPER Captain AWESOME!!!!
- Note the use of capitals.
- Layout can't be changed.
- Percentages were put in so not to use all of the data.
- Percentages indicate a ceiling is used on data, but the arrays are shuffled.
- That means the same data is not used all the time.
How This Works
OK, now we've gotten some of the basic stuff out of the way, how does this work? If you didn't know, screensavers tend to just be EXEs renamed as SCR files. Stick it in a directory like C:\Windows, and it just runs. You do have to do some extra coding, but for the most part apps run fine. Basically when the app starts up, this is done:
Public Shared Sub Main(ByVal args() As String)
If args.Length <= 0 Then
_RunningAsScreensaver = Application.ExecutablePath.ToUpper().EndsWith(".SCR")
If RunningAsScreensaver Then
Dim str As String = args(0).ToLower().Trim().Substring(0, 2)
Select Case str
Dim form As New ConfigForm()
MessageBox.Show("Invalid command line argument :" & str, _
"Invalid Command Line Argument", MessageBoxButtons.OK, MessageBoxIcon.Hand)
So we first check to see if we have arguments, if we don't check the extension. If we've got an SCR, run as a screensaver. If we don't, run the app as a desktop application. If we run as a screensaver, check to see if
/c was passed in. If it has, we start in config mode (Windows will pass in a
/c for the settings button when selecting a screensaver).
If running as a desktop app, we just show the screensaver with the exception you need to press Escape to exit. We also only show the app on one monitor. There is a form which loads which asks the user which screen to use (this code is from the original article).
If running as a screensaver, we enumerate through the screens and we create a screensaver form for each screen on the system (so, multi-monitor friendly).
There is not a whole lot more done on app start up, except for reading in data. In addition to displaying photos, you can display quotes, your own quotes, world event data (from Wikipedia, collected around 2009ish) and word definitions. Be warned there are younger audience unfriendly words in there. Most of the code is straight forward, some of the code is pretty messy (this was an app just for me when I made it, decided not to be selfish!). Some of the more cooler aspects include array shuffling and using a ceiling on the amount of quotes and words to use. I put this in case you have your own quotes so that the chances of getting your quotes used rather than system quotes would be greater.
Private Shared Sub ShuffleArray(ByVal MyArray As List(Of DisplayItem))
Dim num As Integer = 0
For i As Integer = 0 To (MyArray.Count - 1) - 1
Do While num = i
num = _MyRandom.Next(0, MyArray.Count)
Dim info As DisplayItem = MyArray(i)
MyArray(i) = MyArray(num)
MyArray(num) = info
num = i + 1
The code above shuffles the array of
DisplayItems. Pretty nifty stuff as it further randomizes what data is picked.
Public Shared Sub ConfigureCeilings()
If My.Settings.PercentQuotes <> 100 Then
Dim percent As Double = My.Settings.PercentQuotes * 0.01
_QuoteCeiling = CInt(Fix(Program.Quotes.Count * percent))
_QuoteCeiling = Program.Quotes.Count
If My.Settings.PercentWords <> 100 Then
Dim percent As Double = My.Settings.PercentWords * 0.01
_WordsCeiling = CInt(Fix(Program.Words.Count * percent))
_WordsCeiling = Program.Words.Count
This code is what configures the ceilings for how much data is used. At all times, all the data is read in, we just put a limit on it.
Once we've read in all the data, we then create a photo queue. The job of this is to read in all the images in the directories specified. This is the biggest change from the original project. I wanted to manually specify what pictures to show rather than have it come from somewhere else. Supported images are bmp, gif, jpg/jpeg.
A photo queue consists of a list of
PhotoInfo classes. The
PhotoInfo classes are important because they represent a photo on the collage. This class basically does the image reading, image resizing and also extracts metadata from the image. We basically try to get the date taken and title of the photo from metadata. Some photos don't have that information though and in that case we default to the file name.
The Screensaver Form
The bulk of the work is done in the screensaver form. This is just a normal Form that looks out for mouse movement and keystrokes to close the screensaver. The constructor is a little different and when you create an instance of a form, it needs to know what screen to run on and the source of photos. For multimonitor setups, multiple forms are created and displayed on each screen.
Initialization is done on this form and if figures out the delay interval for each new photo and also the reset interval which will clear the desktop back to it's original state (that state being the snapshot taken when the screensaver was first started. It also starts the background thread which manages the calling of methods to put new photos on.
If we in debugging mode, we set the opacity to make life easier while debugging:
If Debugger.IsAttached Then
MyBase.Opacity = 0.8
MyBase.TopMost = False
When we get the background image, we can do this via:
Public Function GetBackgroundImage() As Bitmap
Dim image As Bitmap = Nothing
Dim graphics As Graphics
Dim bounds As Rectangle = Me._HomeScreen.Bounds
image = New Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb)
graphics = graphics.FromImage(image)
(bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy)
When the background worker thinks an image needs to be drawn onto the collage, it creates a snapshot of the image (
CreateSnapshotImage), draws the image rotated (
DrawImageRotated), and then calls the refresh handler (
_RefreshHandler). So at this point, we have a new image that needs to be drawn, but we need to actually now draw that image onto the main canvas (the form). To trigger that, all we do is call Refresh on the form and this code will kick in:
Protected Overrides Sub OnPaintBackground(ByVal e As PaintEventArgs)
Dim currentImage As Bitmap = Me.GetCurrentImage()
e.Graphics.DrawImage(currentImage, 0, 0, currentImage.Width, currentImage.Height)
If (My.Settings.AllBlacksMode) Then
Dim y As Integer
Dim x As Integer
If (My.Settings.SidebarPosition.ToLower() = "left") Then
x = (Me._HomeScreen.Bounds.Width - My.Resources.AllBlacks.Width) - 50
x = 50
y = CInt((Me._HomeScreen.Bounds.Height - My.Resources.AllBlacks.Height) / 2)
e.Graphics.FillRectangle(Me._DrawBrushSidebar, 0, 0, _
e.Graphics.DrawImage(My.Resources.AllBlacks, x, y, _
If (Me._PhotoSource.PhotoCount = 0) Then
e.Graphics.FillRectangle(Me._DrawBrushSidebar, 0, 0, _
If (My.Settings.DimScreen AndAlso Me._PhotoSource.PhotoCount > 0) Then
e.Graphics.FillRectangle(Me._DrawBrushSidebar, 0, 0, _
Select Case My.Settings.BarDrawFormat.ToLower()
Case "nonprimary (s)"
If Me._HomeScreen.Primary = False Then
If Me._HomeScreen.Primary Then
Catch ex As Exception
The job of this code is to get the currently drawn image which is just the collage of photos, no overlay and draw that onto the canvas. Depending on the options the user has enabled, we draw the awesome All Blacks mode, dim the screen and draw the sidebar.
DrawSidebar will then draw the date and time and also the quote/event/word text.
That's about it really! Most of the collage code itself comes from the link at the top, I didn't create that code and I've probably butchered a lot of that code, but I just wanted to take that project and show photos from directories I specified and display some cool information.
Points of Interest
- Don't stick periods/full stops in the file name apart from extension.
- Periods tend to confuse Windows as to what the screensaver name is.
- When creating .NET screensaver apps, I found it easier to not use the Application Framework because I wanted access to Main and not actually have a main form.
One actual problem that took me a bit to figure out was how to retrieve the events that occurred today. What I ended up doing for that was:
Public Shared ReadOnly Property RandomEvent() As DisplayItem
If Events.Count <= 0 Then
Dim FilteredEvents As List(Of DisplayItem)
FilteredEvents = Events.FindAll(
Function(dItem As DisplayItem) dItem.Day = _
DateTime.Now.Day AndAlso dItem.Month = DateTime.Now.Month)
Dim pos As Integer = _MyRandom.Next(0, FilteredEvents.Count)
Basically, this code will return a random event that happened on the day the code executes.
Thanks to Gary Noble for helping out with the screensaver preview! His modification isn't on CodePlex yet, but it lets you view a preview in Windows, cool stuff!
- Version 8.0 - Initial release