5

Code :

private void Foo(Canvas canvas)
{
    // The content is a bit larger...
    Size size = new Size(canvas.ActualWidth * 1.1, canvas.ActualHeight * 1.2);

    // Create a render bitmap and push the surface to it
    RenderTargetBitmap renderBitmap =
        new RenderTargetBitmap(
        (int)size.Width,
        (int)size.Height,
        96d,
        96d,
        PixelFormats.Pbgra32
    );
    renderBitmap.Render(canvas);

    // Then copy to clipboard
    Clipboard.SetImage(renderBitmap);
}

What I need :

Render a canvas with transparent background to image, then copy it to clipboard (Quit simple? Not really)

Problem :

When paste, I get a ugly image with black background

Solution 1 :

canvas.Background = new SolidColorBrush(Colors.White);

No. This thick do not work, the background of canvas will not change in the next renderBitmap.Render(canvas);

Instead, I have to use a timer, give WPF some time to change the background, then render it in the tick event of that timer. It works, but unfortunately, the content of canvas is larger than it's size...so the white background can only cover part of it, still ugly result. (BTW anyone know why it takes sometime to change the background? I thought it should be changed instantly)

Did I do something wrong? How can I get a white background ex-transparent image in clipboard?

What's more, I've noticed that the background of some PNG images remain white if you paste it into mspaint.exe which do not support alpha channel, but some others become black.

Is there something like, say, alternative color, which is used as background if the place where you paste your image do not support alpha channel? Can we custom it?

Now I rendered another BitmapSource with white content, if there is a way to combine it with the renderBitmap as background, the problem solved, but I don't know how...

int dWidth = (int)size.Width;
int dHeight = (int)size.Height;
int dStride = dWidth * 4;
byte[] pixels = new byte[dHeight * dStride];
for (int i = 0; i < pixels.Length; i++)
{
    pixels[i] = 0xFF;
}
BitmapSource bg = BitmapSource.Create(
    dWidth,
    dHeight,
    96,
    96,
    PixelFormats.Pbgra32,
    null,
    pixels,
    dStride
);
// Combine bg with renderBitmap
Byzod
  • 466
  • 4
  • 18
  • It appears to save the bitmap after removing the alpha channel instead of all channels. Is the image on the clipboard an MS BMP? – Cole Tobin May 29 '12 at 00:21
  • 1
    @ColeJohnson I don't know. As the results I've searched, seems MS clipboard image do not support transparent channel. I'm trying to find a way add a white background for this image before set it to clipboard – Byzod May 29 '12 at 01:08
  • you can always base64 encode it and base64 decode it on the other end. But that might break compatibility. For apps with images, I don't know how they do it `:(` – Cole Tobin May 29 '12 at 01:10
  • Thank you @ColeJohnson, I rendered a white `BitmapSource` image with same size, searching the way to combine it with the original canvas, hope it'd helps – Byzod May 29 '12 at 01:23

2 Answers2

2

Here's my last solution, hope it would help others with the same problem

// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap =
    new RenderTargetBitmap(
    (int)size.Width,
    (int)size.Height,
    96d,
    96d,
    PixelFormats.Pbgra32
);
renderBitmap.Render(surface);

// Create a white background render bitmap
int dWidth = (int)size.Width;
int dHeight = (int)size.Height;
int dStride = dWidth * 4;
byte[] pixels = new byte[dHeight * dStride];
for (int i = 0; i < pixels.Length; i++)
{
    pixels[i] = 0xFF;
}
BitmapSource bg = BitmapSource.Create(
    dWidth,
    dHeight,
    96,
    96,
    PixelFormats.Pbgra32,
    null,
    pixels,
    dStride
);

// Adding those two render bitmap to the same drawing visual
DrawingVisual dv = new DrawingVisual();
DrawingContext dc = dv.RenderOpen();
dc.DrawImage(bg, new Rect(size));
dc.DrawImage(renderBitmap, new Rect(size));
dc.Close();

// Render the result
RenderTargetBitmap resultBitmap =
    new RenderTargetBitmap(
    (int)size.Width,
    (int)size.Height,
    96d,
    96d,
    PixelFormats.Pbgra32
);
resultBitmap.Render(dv);

// Copy it to clipboard
try
{
    Clipboard.SetImage(resultBitmap);
} catch { ... }
Byzod
  • 466
  • 4
  • 18
0

I have found a smaller and better-to-read solution, I found it at https://social.msdn.microsoft.com/Forums/vstudio/en-US/a6972b7f-5ccb-422d-b203-134ef9f10084/how-to-capture-entire-usercontrol-image-to-clipboard?forum=wpf :

// Create a render bitmap and push the surface to it
RenderTargetBitmap renderBitmap =
    new RenderTargetBitmap(
    (int)size.Width,
    (int)size.Height,
    96d,
    96d,
    PixelFormats.Pbgra32
);

// Render a white background into buffer for clipboard to avoid black background on some elements
Rectangle vRect = new Rectangle()
{
    Width = (int)size.Width,
    Height = (int)size.Height,
    Fill = Brushes.White,
};
vRect.Arrange(new Rect(size));
renderBitmap.Render(vRect);

// renderBitmap is now white, so render your object on it
renderBitmap.Render(surface);

// Copy it to clipboard
try
{
    Clipboard.SetImage(resultBitmap);
} catch { ... }
MiroJanosik
  • 675
  • 5
  • 30