Piotr Karocki
Piotr Karocki

Reputation: 1

WPF, ListView. RenderTargetBitmap - how to render whole ListView, not limited to screen

I have ListView, which displays some pictures. Not all pictures are visible on screen (in Window). I want to make something like 'contact sheet' - all pictures.

I got ScrollViewer from ListView, and got its full height (scrVwr.ExtentHeight). So, I call render:

    var bmp = new RenderTargetBitmap(scrVwr.Width, scrVwr.ExtentHeight, 96, 96, PixelFormats.Pbgra32);
    bmp.Render(scrVwr);

After saving

    var encoder = new JpegBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(bmp));
    var fileStream = IO.File.Create(picker.FileName);
    encoder.Save(fileStream);

I got this: saved JPG

(so only ListView.ActualHeight is rendered)

I want to have rendered whole ListView/ScrollViewer (all items).

Upvotes: 0

Views: 55

Answers (1)

BionicCode
BionicCode

Reputation: 28968

I think in your case you should not try to create the image from the rendered UI. This introduces avoidable problems like UI virtualization.
Instead, I recommend creating the tile image from the raw image data sources that the ListView displays.

The following example uses a WriteableBitMap to dynamically create a tiled image.

Usage Example

var uris = new List<Uri>
{
  new Uri(@"C:\some_image.png", UriKind.Absolute),
  new Uri(@"C:\some_image.png", UriKind.Absolute),
  new Uri(@"C:\some_image.png", UriKind.Absolute),
};

int minTilePixelWidth = 1000;
int tileRowPixelHeight = 200;
var tileImageCreator = new TileImageCreator();
BitmapSource imageSource = tileImageCreator.CreateTileImage(uris, minTilePixelWidth, tileRowPixelHeight);
SaveToFile(imageSource);

Tile.cs

internal readonly struct Tile
{
  public Tile(BitmapImage image, int horizontalOffset, int verticalOffset)
  {
    this.Image = image;
    this.HorizontalOffset = horizontalOffset;
    this.VerticalOffset = verticalOffset;
  }

  public int HorizontalOffset { get; }
  public BitmapImage Image { get; }
  public int VerticalOffset { get; }
}

TileImageCreator.cs

class TileImageCreator
{  
  public BitmapSource CreateTileImage(IEnumerable<Uri> imageUris, int minTilePixelWidth, int tileRowPixelHeight)
  {
    int tileBitmapHeight = tileRowPixelHeight;
    int tileBitmapWidth = minTilePixelWidth;
    int horizontalOffset = 0;
    int verticalOffset = 0;
    var tileInfos = new List<Tile>();

    foreach (Uri uri in imageUris)
    {
      var image = new BitmapImage();
      image.BeginInit();
      image.UriSource = uri;
      image.DecodePixelHeight = tileRowPixelHeight;
      image.EndInit();
      image.Freeze();

      tileBitmapWidth = Math.Max(tileBitmapWidth, image.PixelWidth);

      int newRowWidth = horizontalOffset + image.PixelWidth;
      bool isOverflowing = newRowWidth > tileWidth;
      if (isOverflowing)
      {
        horizontalOffset = 0;
        verticalOffset += tileRowPixelHeight;
        tileBitmapHeight += tileRowPixelHeight;
      }

      var tileInfo = new Tile(image, horizontalOffset, verticalOffset);
      tileInfos.Add(tileInfo);

      horizontalOffset += image.PixelWidth;
    }

    var tileBitmap = new WriteableBitmap(tileBitmapidth, tileBitmapHeight, 96, 96, PixelFormats.Pbgra32, null);
    foreach (Tile tile in tileInfos)
    {
      BitmapImage tileImage = tile.Image;
      int bytesPerPixel = (tileImage.Format.BitsPerPixel + 7) / 8;
      int pixelStride = tileImage.PixelWidth * bytesPerPixel;
      int bufferSize = tileImage.PixelHeight * pixelStride;
      byte[] sourceBuffer = new byte[bufferSize];
      tileImage.CopyPixels(sourceBuffer, pixelStride, 0);
      tileBitmap.WritePixels(new Int32Rect(0, 0, tileImage.PixelWidth, tileImage.PixelHeight), sourceBuffer, pixelStride, tile.HorizontalOffset, tile.VerticalOffset);
    }

    tileBitmap.Freeze();
    return tileBitmap;
  }
}

Upvotes: 0

Related Questions