You can generate an image but it requires quite a bit of more work. As you pointed out - you can't just save the entire image. So I've changed saving logic to loop through the image and save 60 lines at a time. Notice that I call GC.Collect 3 times to go over all 3 GC generations 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 is coming from ImageTools library. And once I get the enumerable back I call PNG compression (which I tweaked to persist using image fragments):
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 is using PngEncoder from ImageTools, which I've modified:
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);
}
}