Monday, April 20, 2009

WPF Cover Flow Tutorial : Part 7 (source)

Before disclosing sources of Part 7, here a few notes.

I provide a sample ThumbnailManager, working with an IsolatedStorageFile.
public class ThumbnailManager : IThumbnailManager
{
#region Fields
private readonly IsolatedStorageFile store;
#endregion
public ThumbnailManager()
{
store = IsolatedStorageFile.GetUserStoreForAssembly();
}
public ImageSource GetThumbnail(string host, string path)
{
string thumbName = Path.GetFileName(path);
if (store.GetFileNames(thumbName).Length == 0)
{
using (var stream = new IsolatedStorageFileStream(thumbName, FileMode.CreateNew, store))
{
byte[] data = GetThumbnail(path);
stream.Write(data, 0, data.Length);
}
}
using (var stream = new IsolatedStorageFileStream(thumbName, FileMode.Open, store))
{
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
image.Freeze();
return image;
}
}
}
I deal with host names because I am also working on an implementation dealing with many shares on the network. The GetThumbnail method just resizes pictures.
    private byte[] GetThumbnail(string path)
{
Image source = Image.FromFile(path);
source = AmazonCut(source);
int height = source.Height;
int width = source.Width;
int factor = (height - 1) / 250 + 1;
int smallHeight = height / factor;
int smallWidth = width / factor;
Image thumb = source.GetThumbnailImage(smallWidth, smallHeight, null, IntPtr.Zero);
using (var ms = new MemoryStream())
{
thumb.Save(ms, ImageFormat.Png);
ms.Flush();
ms.Seek(0, SeekOrigin.Begin);
var result = new byte[ms.Length];
ms.Read(result, 0, (int)ms.Length);
return result;
}
}
For the sample videos on youtube, I downloaded many covers from amazon. That's why I need the helper function that removes the blank frame around the picture:
    private static Image AmazonCut(Image image)
{
if (image.Width != image.Height)
return image;
var bmp = new Bitmap(image);
int size = image.Height;
int white = System.Drawing.Color.FromKnownColor(KnownColor.White).ToArgb();
int i = 0;
while (i < size / 2)
{
if (bmp.GetPixel(i, i).ToArgb() != white)
break;
if (bmp.GetPixel(i, size - 1 - i).ToArgb() != white)
break;
if (bmp.GetPixel(size - 1 - i, i).ToArgb() != white)
break;
if (bmp.GetPixel(size - 1 - i, size - 1 - i).ToArgb() != white)
break;
i++;
}
if (i > 0)
{
i += 8;
var zone = new Rectangle(i, i, size - 2 * i, size - 2 * i);
return bmp.Clone(zone, System.Drawing.Imaging.PixelFormat.DontCare);
}
return bmp;
}
Well this whole class is not perfect code, but it is sufficient for a demo.

If you do not know innerings of IsolatedStorage, you will find the thumbnails in a folder like C:\Documents and Settings\ded\Local Settings\Application Data\IsolatedStorage\0ypqvhod.rll\j4pydd3v.g4v\StrongName.alr3sbzvfezz2fk22sd5g0b5dxbwzr0b\AssemFiles.

Edit 2014-02-23 : Code has moved to github.

50 comments:

Unknown said...

Bonjour, ton travail sur coverflow m'intéresse beaucoup mais le lien pour obtenir la source (http://vianney.philippe.googlepages.com/WPFCoverFlowTutorialPart7.zip) ne marche plus.

Est-ce que tu peux m'aider ?

Merci en avance

Sven

Anonymous said...

The source links are dead. Can you send me new source links at shawn [at) shawnbass *dot* com

Thanks.

Shawn

ded' said...

Links fixed.

@ruantec said...

hi ded!

first of all congratulations on your awesome tutorial and code.. sadly i have to report a memory leak that is hidden somewhere in the code... i´ve checked the code and i think its hidden in the thumbnail class were you read the file stream.. somehow i was unable to find the problem and all my tries to fix the problem failed.

to reproduce the problem just load several pics and scroll through them forward and backward.. you will notice the memory usage keeps increasing.

Ricardo said...
This comment has been removed by the author.
一个人的角落 said...

When you click on the second, or next image, I'd like the current image to move to become the last image in the line on the right.
can gv me any suggestion of how to do this?

ded' said...

private double RotationAngle(int index)
{
return pos == index ? 0 : -90;
}
private double TranslationX(int index)
{
if (pos >= index)
return pos * CoverStep + Math.Sign(pos - index) * 1.5;
return (count + pos) * CoverStep + 1.5;
}

john said...

Hi ded! any idea regarding the memory leak posted previously???? all you have to do is load some images and scroll through them then you will notice how the memory usage keep increasing.

am i doing something wrong??? or is there a better option?

ded' said...

Well it seems that my tutorial does not have any memory leak.

john said...
This comment has been removed by the author.
john said...

Hi ded!

Thank you for your answer but the memory leak its present under Vista/Win7. if you load more than 20 images and scroll through them the memory usage will keep increasing over and over until it reach up to 1GB while under XP seems to work fine.

I did not modified your code or anything but just changed the path and loaded 30 PNG files instead.

Hope it helps :)

ded' said...

I use Windows 7 Pro. I download the sources again from the link in my post and convert the sources to VS 2010 (Express). You would need to add a reference to System.Xaml in FlowComponent assembly. I update TestWindow ctor to load a folder with 300 photos (1.1 GB). Then I launch the Part7 application with no debugger attached (Ctrl-F5). I configure Windows performance monitoring tools to watch my application private bytes and total bytes in all heaps. The first counter stay close to 170 MB while generating all thumbnails and stay below when it's done. The second stay close to 3 MB. Where do you see a memory leak ?

john said...

Hi ded!

Thank you once again for your answer as you are the only one that could help me to fix the issue am having. a while back i checked your code using a tool(i can't remember the name right now) were i saw that the memory increasement is created everytime you erase/load a cover while scrolling through the covers.

Just a suggestion:
Wouldn't be better to load lets say 20 covers and create some sort of roulett where the last pic becomes the first one and in that way you keep just scrolling through the same covers but with different contents?

anyways i made 3 screenshots to show you what i mean. i know the taskmanter isn't the best option to test such things but it clearly shows how much the memory usage increase which could lead to a crash at the end.

http://img155.imageshack.us/img155/8325/pic3ns.jpg
http://img687.imageshack.us/img687/7113/pic2eev.jpg
http://img231.imageshack.us/img231/4326/picft.jpg

Hope it helps!

Greetings
John

john said...
This comment has been removed by the author.
john said...

I used CTRL+F5 as you suggested too but apart from working faster(which is obvious) it didn't change anything and the memory still increase as usual.

Hope you can find the issue and help me out :)

Apart from the problem am having i have to admit you've done an amazing job.

ded' said...

Hello john. Please tell where I can download your code to analyse these memory leaks.

john said...

I downloaded the code you posted here before i uploaded the screenshots and just modified the path and the format to accept .png instead of .jpg.

The memory increase only when scrolling through covers and not when loading the first time.

anyways if you need my modified code(only path and format) just tell me and i will upload the file somewhere.

ded' said...

Looking closer at your screenshots :
1) Whole RAM usage of your full machine has nothing to do with a single application !
2) vshosts process includes VS 2010 stuff. You must use Ctrl-F5 or launch the exe from bin folder.
4) Does it crashes eventually ? Did you try to load hundreds of pictures ?
5) Please DO read the article linked in my first comment where I say that my app is not leaking memory. Monitor private bytes of the tutorial when running _independently_ from VS and check that this is not growing forever.

john said...

Thank you once again for your time ded!

I will re-read the link in your previous post and see if i can find a fix for it.

anyways i tried what you suggested(CTRL+F5) but just as i said in my previous post it makes no difference as the memory keep increasing and as you will see in the pic am even able to reach 2,6GB or ram.

Here is the pic:
http://img687.imageshack.us/img687/7484/picswq.jpg

Here are the pics i use:
http://www.mediafire.com/?qz1zglmy0yk

Hope it help you to figure what could be :)

john said...

On a second thought i think that in order to fix the problem you should make it IDisposable. i haven't worked with it myself that much but i will try my best to see if i can modify the code to make it IDisposable.

Maybe am wrong but i think it may fix the problem after all.

Anonymous said...

Hey,
first of all: great work! Thanks!

I tried with about 500 images (but only very small ones) and it was deadslow.
To fix I just created an own Thumbnailmanager which loads the images on creation in a dictionary. So it was nice fast again ;)
Just as a hint.

ded' said...

This is just a tutorial. Initial thumbnail generation in isolated storage can take some time but once all thumbnails are cached, performance is back. But thanks for the hint anyway.

SIDDHAROOD said...

I downloaded and run your Code but its showing XamlParseException. Let me if i should add any references.

ded' said...

You need to set a valid path with pictures in TestWindow ctor.

SIDDHAROOD said...

Ya i got it...Thanks for reply...And I have one more problem is there any way i can get selected Item of the list and is Binding Possible

ded' said...

To get selected item, just add the corresponding property. ContentTemplates are beyond scope of this tutorial, sorry.

Chris said...
This comment has been removed by the author.
Chris said...

one qestion... how can i do to remove a cover from visualmodel?

i changed the code to bind it to a database with some pictures and transform this to a cover, but, when i needed to remove one from this visualmode, there's a blank space between the covers, something like "oh one cover is missing here, theres just space"...

i tried with many ways , but still cant get it ... any suggestion?

thanks a lot

ded' said...

@Chris in my tutorial, covers are removed from visual model with the ICover.Destroy method.

Chris said...
This comment has been removed by the author.
Chris said...
This comment has been removed by the author.
Chris said...
This comment has been removed by the author.
Chris said...

Im sorry =D, now its ok , thanks a lot =D

really thanks

mytestblog said...

hello ded,
the tutorial is working great. The Sliding is fast. thanks. but the pictures quality changes with poor quality. i have 70 pictures with high resolution, and i'm running the project in maximized_mode. why does change quality of pictures?
thanks

ded' said...

@mytestblog performance ! this is a tutorial, to run fast in maximized mode, other optimizations are required...

mytestblog said...

thanks for your fast answer. could you please show me, how we can get a selectedItem? I know, this is beyond your tutorial. I want to get it for manipulation(Zoom/rotate). i dont have enough experience to find it out. that's very helpful, if give me clue.
thanks ded

ded' said...

index in FlowControl gives current item (cover) position. coverlist dictionary maps indexes to covers (ICover).

mytestblog said...
This comment has been removed by the author.
mytestblog said...

hi ded,
i think i wrote my question so complicated. in one sentence:
- how can i get a list of Pictures in ?
// put image 01.jpg in _im1
Image _im1= ListOfPicture[0]
// put image 02.jpg in im2
Image _im2= ListOfPicture[1]
thats all.
thanks

ded' said...

Pictures are created in Cover class (LoadMaterial method). Sorry, I cannot help you to refactor my tutorial for your needs.

Anonymous said...

Hi , first of all great article and very well written code. I could make almost perfect sense of everything.
One question I had was if I change the angle to a negative value ( that is make the images on the sides face outwards than inwards), why does the effect break. I played around with different variables like CoverStep and angle etc.but could not make it behave like the original . Any suggestions on what should I be looking at if I want the images to be facing outwards then inwards.

ded' said...

@Max If you change the angles (adding 180 or Pi), images will not be visible anymore. E.g. if you rotate a square, you need two pictures, one on each side. I think you'd better apply some 'mirror' transformation on the pictures.

nullPointer said...

Hi ded

First, thanks for this excellent series of tutorials! Not only is this the only tutorial of its kind, it’s also probably one of the best WPF tutorials available on the web. Thanks for all of that. This was the tutorial that finally helped me to start making some sense of WPF. Having said that, I still struggle a bit with the 3D transformations, and how to optimize them for performance. In windowed mode my code performs beautifully, but in a maximized window my performance bottoms out. I’ve messed around with some of the information presented here, but still can’t make the maximized window operate with the any where near the same efficiency as in windowed mode.

You mentioned earlier in the comments that further optimizations would be required to run in a maximized window. Obviously I’m not asking for a code sample, but would you mind elaborating a bit as to how one might optimize the code in this fashion?

ded' said...

@nullPointer thanks for your feedback :-) I had the same performance loss in maximized mode. But unfortunately, I haven't spend enough time to find a satisfying solution. A few ideas anyway : picture size matters of course, a better cache implementation (loading more pictures in parallel with threads maybe)... Msdn has a few articles about WPF performance improvements but the one I think about targets use case with millions of triangles. HTH. Regards.

ded' said...

@nullPointer disabling animations (updating translation and rotation values) might help in some cases.

nullPointer said...

Hi ded

Thanks for your rapid response time, and your helpful tips. I was able to optimize fullscreen performance in my project to a level that I’m happy with. There was a bit of a tradeoff though. In loadImageMirror(ImageSource), I’m using an ImageBrush rather than a VisualBrush. This isn’t as cool as your solution, and I definitely lose some visual bling (I’m stuck with a single opacity value rather than the neat LinearGradient Opacity), but for my particular use case the tradeoff is acceptable (to me). Thanks again for this excellent educational resource!

Unknown said...

Thanks Ded's blog for your great tutorial.

@nullpointer: using an ImageBrush rather than a VisualBrush in LoadImageMirror(ImageSource imSrc)
give me a reasonable performance gain. Thank you ;)

For people who have MEMORY LEAKS problems, create a .NET 4.0 project with this wpf_coverflow_component.
Memory leaks disappears ;)

Thanks again Ded for your great tutorial

Unknown said...

Hi, I was wondering if I can draw geometry object on the image and load it in the flow control?

Unknown said...

Salut ded,

merci pour ton super tutoriel.
je m'en sers depuis plusieurs années sur un projet personnel.

tout fonctionne pour le mieux, c'est du bon boulot.

J'ai toutefois des difficultés pour gagner en netteté au niveau des images.

J'ai définis une taille fixe pour chaque images, mais je n'arrive pas à désactiver l'étirement de l'image (comme on le ferait sur un contrôle Image avec la propriété "Stretch" ou "RenderOptions.BitmapScalingMode")

Je ne vois pas où je peux modifier ça (j'ai essayé désespérément quelques tentatives, mais toutes sans succès)

Un petit indice me ferait grand bien.

Merci en tout cas pour ce superbe tutoriel.

ded' said...

Hello Inconnu,
Je ne suis pas sûr de bien comprendre le figeage de la taille des images car pour moi ce sont des textures plaquées sur des objets 3D, donc je dirais que l'on ne peut pas maîtriser la taille de l'image une fois projetée sur l'objet 3D puis avec le rendu 2D.
Pour améliorer la netteté, je pense qu'il faut utiliser de plus grandes images (avec une plus grande résolution).
Mais mon modeste tutoriel doit être largement amélioré pour avoir les performances qui vont avec.
Cordialement,
ded', un peu en retard dans ses mails