Windows Phone 8 - Creating Lockscreen Images in the Background

I am trying to create a Windows Phone 8 application (an update to my Citadel now published) that uses the new Windows Phone 8 Live Lockscreen API. I basically want to randomly select the background image from the application package and place a text block with a random quote on it to create a screen lock image. How can I do this in a background periodic job? There is definitely a way to do this (many current applications, including various weather and news applications, create a local screen lock locally in the background), but I just can’t understand how so far no internet search has given me nothing useful.

Any help is much appreciated!

Many thanks!

EDIT:

I managed to find a way to create a UserControl with my content and take a screenshot as follows:

var bmp = new WriteableBitmap(768, 1280); bmp.Render(LayoutRoot, null); String tempJPEG = "TempJPEG.jpg"; var myStore = IsolatedStorageFile.GetUserStoreForApplication(); if (myStore.FileExists(tempJPEG)) { myStore.DeleteFile(tempJPEG); } IsolatedStorageFileStream myFileStream = myStore.CreateFile(tempJPEG); WriteableBitmap wb = new WriteableBitmap(bmp); wb.SaveJpeg(myFileStream, wb.PixelWidth, wb.PixelHeight, 0, 100); myFileStream.Close(); 

This approach caused me three different problems:

  • If I did not set the WriteableBitmap size in the constructor, he selected it incorrectly, and the lockscreen was useless.

  • If I run the code above, it throws an OutOfMemory error

  • In one case, there was also a problem with the control background (it went black, although I installed the main Grid Background brush for ImageBrush, referring to the local file from the main Appx package.

Is this completely wrong? Is there a better (working) way?

Thanks to everyone, I appreciate your help.

+6
source share
3 answers

Most likely you are using a memory limit in the background agent, which is 11 MB on WP8. I would recommend that you display your images on a server / Azure and just upload it to the Background Agent, save it on your phone and display it on a locked screen, or maybe use the Resource target for rendering?
I use fragment display in one of my applications, and I came across a memory cover when I tried to reproduce only 2 images with tiles of size 336x336 + 159x159 pixels, so you can imagine that a 768x1280 image can easily reach this cap.

+4
source

If I did not set the WriteableBitmap size in the constructor, he selected it incorrectly, and the lockscreen was useless.

Have you tried using Application.Current.Host.ActualHeight and ActualWidth as the size of the created lock screen images? Adapt the lock screen image to the size currently used by the OS? I am sure that Application.Current may be empty in the background, so you have to cache it in ApplicationSettings from the main application and use this information in the background agent.

If I run the code above, it throws an OutOfMemory error

Yes, this is because you are using ImageQuality = 100 in a call to SaveJpeg. Remember that background agents running on WP8 have a memory limit of 11 MB of working set. Remove ImageQuality to 70-80 and everything will be fine.

In 1 case, there was also a problem with the control background (it went black, although I set the main Grid Background brush to ImageBrush associated with the local file from the main Appx package.

Download may take a little longer. First try calling WriteableBitmap.Invalidate () before saving it to a file. If this does not work (due to the image being from a remote source), you will have to wait for the BitmapImage.ImageOpened event of the image you are trying to capture.

+2
source

You can generate an image, but this requires a bit more work. As you pointed out, you cannot just save the whole image. So I changed the save logic to scroll the image and save 60 lines at a time. Note that I call GC.Collect 3 times to go through all 3 generations of GC and get rid of all memory allocations.

 static IEnumerable<ExtendedImage> GetImageSegments(LockScreenTile tileControl) { const int segmentMaxHight = 60; var aggregatePixelHeight = (int)tileControl.ActualHeight; tileControl.LayoutRoot.Height = aggregatePixelHeight; for (int row = 0; row < aggregatePixelHeight; row += segmentMaxHight) { tileControl.LayoutRoot.Margin = new Thickness(0, -row, 0, 0); tileControl.Height = Math.Min(segmentMaxHight, aggregatePixelHeight - row); yield return tileControl.ToExtendedImage(); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); } } 

ExtendedImage comes from the ImageTools library. And as soon as I get an enumerated back, I call PNG compression (which I fixed to continue using fragments of the image):

 var segments = GetImageSegments(tileControl); double pixelRatio = (template.PixelWidth) / tileControl.ActualWidth; segments.SaveToPngImage( aggregatePixelHeight: (int)(savedActualHeight * pixelRatio), pixelWidth: template.PixelWidth, densityX: template.DensityX, densityY: template.DensityY, directoryName: tileDirectoryName, filePath: filePath); 

Finally, SaveToPngImage uses the PngEncoder from ImageTools, which I changed:

  private void WriteDataChunks(IEnumerable<ExtendedImage> verticalImageSegments) { byte[] compressedBytes; MemoryStream underlyingMemoryStream = null; DeflaterOutputStream compressionStream = null; try { underlyingMemoryStream = new MemoryStream(); compressionStream = new DeflaterOutputStream(underlyingMemoryStream); foreach (var verticalImageSegment in verticalImageSegments) { byte[] pixels = verticalImageSegment.Pixels; var data = new byte[verticalImageSegment.PixelWidth*verticalImageSegment.PixelHeight*4 + verticalImageSegment.PixelHeight]; int rowLength = verticalImageSegment.PixelWidth*4 + 1; for (int y = 0; y < verticalImageSegment.PixelHeight; y++) { byte compression = 0; if (y > 0) { compression = 2; } data[y*rowLength] = compression; for (int x = 0; x < verticalImageSegment.PixelWidth; x++) { // Calculate the offset for the new array. int dataOffset = y*rowLength + x*4 + 1; // Calculate the offset for the original pixel array. int pixelOffset = (y*verticalImageSegment.PixelWidth + x)*4; data[dataOffset + 0] = pixels[pixelOffset + 0]; data[dataOffset + 1] = pixels[pixelOffset + 1]; data[dataOffset + 2] = pixels[pixelOffset + 2]; data[dataOffset + 3] = pixels[pixelOffset + 3]; if (y > 0) { int lastOffset = ((y - 1)*verticalImageSegment.PixelWidth + x)*4; data[dataOffset + 0] -= pixels[lastOffset + 0]; data[dataOffset + 1] -= pixels[lastOffset + 1]; data[dataOffset + 2] -= pixels[lastOffset + 2]; data[dataOffset + 3] -= pixels[lastOffset + 3]; } } } compressionStream.Write(data, 0, data.Length); } compressionStream.Flush(); compressionStream.Finish(); compressedBytes = underlyingMemoryStream.GetBuffer(); } finally { if (compressionStream != null) { compressionStream.Dispose(); underlyingMemoryStream = null; } if (underlyingMemoryStream != null) { underlyingMemoryStream.Dispose(); } } int numChunks = compressedBytes.Length/ MaxBlockSize; if (compressedBytes.Length % MaxBlockSize != 0) { numChunks++; } for (int i = 0; i < numChunks; i++) { int length = compressedBytes.Length - i * MaxBlockSize; if (length > MaxBlockSize) { length = MaxBlockSize; } WriteChunk(PngChunkTypes.Data, compressedBytes, i*MaxBlockSize, length); } } 
0
source

All Articles