Click here to Skip to main content
Click here to Skip to main content

Collage Screensaver

, 4 Nov 2011 Ms-PL
Rate this:
Please Sign up or sign in to vote.
Displays photos in a collage format as screensaver

Introduction

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
http://channel9.msdn.com/coding4fun/articles/Photo-Screensaver.

Sorry about sticking the source and binaries on CodePlex - the downloads are reasonably large.

Screenshots - Configuration

screen1.jpg

screen2.jpg

screen3.jpg

screen4.jpg

Screenshots - In Action!

Multi_Small.jpg

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.

Directories

  • 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.

Sidebar

  • 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.

Custom Quotes

  • 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.

Advanced

  • 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:

<stathread()> _
Public Shared Sub Main(ByVal args() As String)
    ' no args, if the exe is a SCR, run a screensaver.
    ' otherwise start as app.
    If args.Length <= 0 Then
        _RunningAsScreensaver = Application.ExecutablePath.ToUpper().EndsWith(".SCR")
        If RunningAsScreensaver Then
            ShowScreenSaver()
        Else
            ShowDesktopApp()
        End If
    Else
        ' we have args, determine what todo.
        Dim str As String = args(0).ToLower().Trim().Substring(0, 2)
        Select Case str
            Case "/c"
                ' start in config mode.
                ' read in full quote database.
                ReadAllData(True)
                Dim form As New ConfigForm()
                form.ShowDialog()
                Return
                
            Case "/p"
                Return
                
            Case "/d"
                ' start as desktop app.
                ShowDesktopApp()
                Return
                
            Case "/s"
                ' start as screensaver app.
                ShowScreenSaver()
                Return
        End Select
        MessageBox.Show("Invalid command line argument :" & str, _
        "Invalid Command Line Argument", MessageBoxButtons.OK, MessageBoxIcon.Hand)
    End If
End Sub

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))
    SyncLock MyArray
        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)
            Loop
            Dim info As DisplayItem = MyArray(i)
            MyArray(i) = MyArray(num)
            MyArray(num) = info
            num = i + 1
        Next i
    End SyncLock
End Sub

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))
    Else
        _QuoteCeiling = Program.Quotes.Count
    End If
    '
    If My.Settings.PercentWords <> 100 Then
        Dim percent As Double = My.Settings.PercentWords * 0.01
        _WordsCeiling = CInt(Fix(Program.Words.Count * percent))
    Else
        _WordsCeiling = Program.Words.Count
    End If
End Sub

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
End If

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)
    Using graphics
        graphics.CopyFromScreen_
        (bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy)
    End Using
    Return image
End Function

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)
    '
    Try
        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
            Else
                x = 50
            End If
            y = CInt((Me._HomeScreen.Bounds.Height - My.Resources.AllBlacks.Height) / 2)
            '
            e.Graphics.FillRectangle(Me._DrawBrushSidebar, 0, 0, _
            Me._HomeScreen.Bounds.Width, Me._HomeScreen.Bounds.Height)
            e.Graphics.DrawImage(My.Resources.AllBlacks, x, y, _
            My.Resources.AllBlacks.Width, My.Resources.AllBlacks.Height)
        Else
            ' if we have no photos at all, draw a background.
            If (Me._PhotoSource.PhotoCount = 0) Then
                e.Graphics.FillRectangle(Me._DrawBrushSidebar, 0, 0, _
                Me._HomeScreen.Bounds.Width, Me._HomeScreen.Bounds.Height)
            End If
            
            ' dim the background if user wants.
            ' don't do it twice though (if we have no photos).
            If (My.Settings.DimScreen AndAlso Me._PhotoSource.PhotoCount > 0) Then
                e.Graphics.FillRectangle(Me._DrawBrushSidebar, 0, 0, _
                Me._HomeScreen.Bounds.Width, Me._HomeScreen.Bounds.Height)
            End If
        End If
        '
        Select Case My.Settings.BarDrawFormat.ToLower()
            Case "none"
                ' don't do anything
                Exit Select
            Case "all"
                ' draw on all monitors.
                Me.DrawSidebar(e)
                Exit Select
            Case "nonprimary (s)"
                If Me._HomeScreen.Primary = False Then
                    Me.DrawSidebar(e)
                End If
                Exit Select
            Case "primary"
                If Me._HomeScreen.Primary Then
                    Me.DrawSidebar(e)
                End If
                Exit Select
            Case Else
                Me.DrawSidebar(e)
                Exit Select
        End Select
        
    Catch ex As Exception
    End Try
End Sub

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.

The 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
    Get
        If Events.Count <= 0 Then
            Return Nothing
        Else
            ' get a filtered list of events that happened on this day and month.
            Dim FilteredEvents As List(Of DisplayItem)
            FilteredEvents = Events.FindAll(
                Function(dItem As DisplayItem) dItem.Day = _
		DateTime.Now.Day AndAlso dItem.Month = DateTime.Now.Month)
                
            ' we have our list, get a random one in that list.
            Dim pos As Integer = _MyRandom.Next(0, FilteredEvents.Count)
            Return FilteredEvents(pos)
        End If
    End Get
End Property

Basically, this code will return a random event that happened on the day the code executes.

Thanks

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!

History

  • Version 8.0 - Initial release

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Tyron Harford
Software Developer
New Zealand New Zealand
No Biography provided

Comments and Discussions

 
QuestionVery very nice PinmvpSacha Barber23-Aug-13 5:17 
AnswerRe: Very very nice PinmemberTyron Harford26-Aug-13 19:49 
GeneralMy vote of 5 PinmvpMd. Marufuzzaman18-Dec-11 6:45 
Questionvery cool PinmemberCIDev14-Dec-11 7:08 
AnswerRe: very cool PinmemberTyron Harford14-Dec-11 8:57 
GeneralMy vote of 5 PinadminChris Maunder17-Nov-11 0:28 
GeneralRe: My vote of 5 PinmemberTyron Harford22-Nov-11 9:37 
QuestionMy +5 Pinmemberrushijoshi11-Nov-11 7:14 
AnswerRe: My +5 PinmemberTyron Harford13-Nov-11 15:59 
GeneralMy vote of 5 Pinmemberrushijoshi11-Nov-11 7:13 
GeneralMy vote of 5 PinmvpDalek Dave6-Nov-11 14:04 
GeneralRe: My vote of 5 PinmemberTyron Harford7-Nov-11 12:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 4 Nov 2011
Article Copyright 2011 by Tyron Harford
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid