Windows Phone 8 - 在后台生成锁屏图片

6
我正在尝试创建一个使用新的Windows Phone 8 Live Lockscreen API的Windows Phone 8应用程序(更新我目前已发布的“The Quote”)。我想从应用程序包中随机选择一张图像背景并在其上放置一个带有随机引语的文本块,以创建锁屏图像。我该如何在后台定期任务中完成这个任务?肯定有一种方法可以做到这一点(许多当前应用程序,包括不同的天气和新闻应用程序,在后台本地创建实时锁屏),但是我似乎找不到如何做到这一点的方法,到目前为止,没有任何互联网搜索给我带来任何有用的东西。

非常感谢您的帮助!

非常感谢!

编辑:

我能够找到一种使用我的内容创建UserControl并采取屏幕截图的方法:

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();

这种方法让我遇到了三个问题:
  1. 如果我没有在构造函数中设置WriteableBitmap的大小,它会选择错误的大小,这样锁屏就没用了。

  2. 如果运行上面的代码,就会抛出OutOfMemory错误。

  3. 在第一种情况下,控件的背景也存在问题(变成黑色),尽管我已经将主Grid的Background brush设置为链接到主Appx包中的本地文件的ImageBrush。

这样做完全不正确吗?有更好的(可行的)方法吗?
非常感谢你们的帮助。
3个回答

4
您很可能在后台代理中遇到了内存容量限制,这在WP8上为11 MB。我建议您在服务器/ Azure上渲染图像,然后只需在后台代理中下载它,将其保存到手机并在锁屏上显示,或者使用资源密集型任务进行渲染?
我在我的一个应用程序中使用瓷砖渲染,并且当我尝试渲染大小为336x336 + 159x159px的2个瓷砖图像时,我遇到了内存容量限制,因此您可以想象渲染768x1280像素的图像也很容易达到这个限制。

嗨,我已经尝试了这种方法。但是我遇到了其他问题。http://stackoverflow.com/questions/20491566/how-to-load-high-resolution-image-windows-phone-taskagent-out-of-memory - kai chen

2
如果我在构造函数中没有设置WriteableBitmap的大小,它会选择错误的大小,导致锁屏无效。
您是否尝试使用Application.Current.Host.ActualHeight和ActualWidth作为生成的锁屏图像的大小?基本上将锁屏图像适应当前操作系统使用的大小?我相信Application.Current在后台可能为空,因此您必须从主应用程序中将其缓存到ApplicationSettings中,并在后台代理中使用该信息。
如果我运行上面的代码,它会抛出OutOfMemory错误。
是的,那是因为您在SaveJpeg调用中使用了ImageQuality = 100。请记住,运行在WP8上的后台代理具有11MB的工作集内存限制。将ImageQuality降至70-80,您就可以正常运行了。
在第1种情况下,控件的背景也存在问题(变成黑色,即使我已将主Grid的Background brush设置为链接到来自主Appx包的本地文件的ImageBrush)。
图片加载可能需要一点时间。首先,请尝试在将其保存到文件之前调用WriteableBitmap.Invalidate()。如果这不起作用(因为图像来自远程源),则必须等待您要捕获的图像的BitmapImage.ImageOpened事件。

OutOfMemory异常发生在JPEG保存之前的那一行(WriteableBitmap wb = new WriteableBitmap(bmp);)。我该如何在那里防止它? - Martin Zikmund
经过一番调查,我发现了另一个不寻常的事情——当我创建一个全新的干净项目并使用我在第一篇帖子中编写的代码正常生成图像时,如果在构造函数中没有指定分辨率(创建新的WriteableBitmap(LayoutRoot, null)),那么生成的图像始终具有480x800像素,即使我在我的Lumia 920上测试应用程序(768x1280)。 - Martin Zikmund
现在,当我在后台代理中使用这个更改后的代码时,我会得到一个新的异常 - 在SaveJpeg调用中的ArgumentException。 - Martin Zikmund

0

你可以生成一张图片,但需要更多的工作。正如你所指出的 - 你不能只保存整个图像。因此,我已经改变了保存逻辑,循环遍历图像并每次保存60行。请注意,我调用GC.Collect 3次来遍历所有3个GC代,并且清除所有内存分配。

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来自ImageTools库。一旦我获得可枚举回调,就会调用PNG压缩(我对其进行了调整以使用图像片段)。
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);

最后,SaveToPngImage使用了我修改过的ImageTools中的PngEncoder。
    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);
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接