Collage 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.
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
Screenshots - In Action!
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 resemblence to 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.
- Alot of options can be configured - not everything, but you can customize it quite abit.
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 (ie. 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 EXE's 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 </stathread()>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 messsy (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 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 DisplayItem's. 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 info 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 IfWhen 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 info.
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.
- 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 PropertyBasically this code will return a random event that happened on the day the code executes.
发表评论
KXNurg Just Browsing While I was browsing yesterday I noticed a great post concerning
Np0R1D Paragraph writing is also a excitement, if you know after that you can write or else it is complicated to write.
u8rpDv Wow, great blog.Really looking forward to read more. Want more.