In a page I display a picture that I receive from CameraCapureTask in the View
<Grid x:Name="EditPageGrid" Margin="{Binding}">
<Grid Name="ViewportContainer" Margin="12,0,12,24">
<Image x:Name="Viewport" LayoutUpdated="Viewport_LayoutUpdated"
Source="{Binding}"/>
</Grid>
</Grid>
And I wish to be able to place a border around this image. How would it be possible to do this? I was thinking perhaps on a click event of some sort a border could be toggled on or off, but actually applying a border is where I am at a loss.
You can contain the image in a Border, like this:
<Grid x:Name="EditPageGrid" Margin="{Binding}">
<Grid Name="ViewportContainer" Margin="12,0,12,24">
<Border HorizontalAlignment="Center" BorderThickness="4" BorderBrush="Red">
<Image Source="C:\Users\Public\Pictures\Sample Pictures\Jellyfish.jpg"/>
</Border>
</Grid>
</Grid>
I figured out such an event: (probably there is better method, but this also works)
private void Viewport_Tap(object sender, System.Windows.Input.GestureEventArgs e)
{
int imageHeight = (Viewport.Source as BitmapImage).PixelHeight;
int imageWidth = (Viewport.Source as BitmapImage).PixelWidth;
Canvas myCanvas = new Canvas();
Rectangle myBorder = new Rectangle();
myBorder.Width = imageWidth;
myBorder.Height = imageHeight;
myBorder.Stroke = new SolidColorBrush(Colors.Red);
myBorder.StrokeThickness = 10;
Image toBorder = new Image();
toBorder.Source = Viewport.Source as BitmapImage;
myCanvas.Children.Add(toBorder);
myCanvas.Children.Add(myBorder);
WriteableBitmap newImage = new WriteableBitmap(myCanvas, null);
//Viewport.Source = newImage; - you can use this but watch out that Viewport.Source now is not BitmapImage
//Below is one method how to make it BitmapImage
//You can of course save newImage to file or whatever you want
//You can also unsubscribe this event to prevent it from second tap which will cause Exception at first line (BitmaImage != WriteableBitmap)
MemoryStream memoryStream = new MemoryStream();
newImage.SaveJpeg(memoryStream, imageWidth, imageHeight, 0, 100);
BitmapImage newBitmap = new BitmapImage();
newBitmap.SetSource(memoryStream);
Viewport.Source = newBitmap;
}
Playing with this memory stream isn't good, but I've not known what you are planning to do with your new Bitmap.
As I've said - it's only example and I'm sure better methods exist (which I don't know). Hope this helps.
Related
I have a series of rectangles in which the user can add images to, by dragging the images in.
The image is then scaled down in proportion and the rectangle is then filled with an ImageBrush.
I need for the user to be able to manipulate the image within the rectangle to fit their needs. Like any photo collage app does.
My question is: How can I show the full, unmasked image on top of the rectangle so that the user can manipulate it to their needs? I don't know where to start with this one.
private async void Mask_Drop(object sender, DragEventArgs e)
{
Rectangle maskSq = e.OriginalSource as Rectangle;
var maskW = maskSq.Width.ToSingle();
var maskH = maskSq.Height.ToSingle();
double maskX = Canvas.GetLeft(maskSq);
double maskY = Canvas.GetTop(maskSq);
// Image sizes for bounding to mask
float boundH = Convert.ToSingle(size.Height);
float boundW = Convert.ToSingle(size.Width);
maskSq.Fill = new ImageBrush
{
ImageSource = new BitmapImage(new Uri("ms-appdata:///local/" + SelectedImage.Name, UriKind.Absolute)),
Stretch = Stretch.UniformToFill
};
}
private void Tap_Collage(object sender, TappedRoutedEventArgs e)
{
//Gets the full image from ImageBrush
ImageBrush brush = (ImageBrush)(((Rectangle)sender).Fill);
Rectangle rect = sender as Rectangle;
//Mask sure rectangle does not drag, just the image brush
rect.CanDrag = false;
rect.StrokeThickness = 6;
//Drag Image Functionality
rect.ManipulationDelta += ImageManipulation.Resize_ImageEdit;
ImageManipulation.ImageEdit_Drag = new TranslateTransform();
brush.Transform = ImageManipulation.ImageEdit_Drag;
//Zoom Image Functionality
ImageManipulation.ImageEdit_Zoom = new ScaleTransform();
brush.RelativeTransform = ImageManipulation.ImageEdit_Zoom;
}
Class
public static class ImageManipulation
{
public static TranslateTransform ImageEdit_Drag;
public static ScaleTransform ImageEdit_Zoom;
public static RotateTransform ImageEdit_Rotate;
public static void Resize_ImageEdit(object sender, ManipulationDeltaRoutedEventArgs e)
{
ImageEdit_Drag.X += e.Delta.Translation.X;
ImageEdit_Drag.Y += e.Delta.Translation.Y;
ImageEdit_Zoom.ScaleX *= e.Delta.Scale;
ImageEdit_Zoom.ScaleY *= e.Delta.Scale;
if (ImageEdit_Zoom.ScaleX < 1.0)
{
ImageEdit_Zoom.ScaleX = 1.0;
ImageEdit_Zoom.ScaleY = 1.0;
}
ImageEdit_Rotate.Angle += e.Delta.Rotation;
}
}
XAML
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="local:CollageGrid">
<Rectangle Width="{Binding CollageW}" Height="{Binding CollageH}" AllowDrop="True" CanDrag="True" Fill="Transparent"
Drop="Mask_Drop"
DragOver="Mask_DragOver"
ManipulationMode="TranslateX, TranslateY" Stroke="Black" StrokeThickness="2" DragEnter="Mask_DragEnter" DragLeave="Mask_DragLeave" Tapped="Tap_Collage">
<Rectangle.RenderTransform>
<TranslateTransform X="{Binding CollageX}" Y="{Binding CollageY}"/>
</Rectangle.RenderTransform>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
Example of what I'm looking to acheive:
How it looks currently:
You can try to replace the Rectangle with the Polyline to draw the Rectangle so that you can access the Image which is on the bottom of the Rectangle.
<Grid>
<Image Source="Assets/image1.jpg" Width="800"
Height="400" Tapped="Image_Tapped" />
<Polyline Stroke="Black" HorizontalAlignment="Center"
VerticalAlignment="Center"
StrokeThickness="4" Tapped="Polyline_Tapped"
Points="0,0,0,200,200,200,200,0,0,0" />
</Grid>
---Update---
UniformToFill will cause the image is resized to fill the destination dimensions while it preserves its native aspect ratio. If the aspect ratio of the destination rectangle differs from the source, the source content is clipped to fit in the destination dimensions. So it is not suitable for your requirement. You should manually scale the Image to make image fit one of your Rectangle's border and keep other part out of the Rectangle.
It seems you put the image as the Rectangle's background image brush, there are no other place to display the image out of the Rectangle. So I think, we may take a considerition for a new scenario.
As my pervious reply, using Image control to display the image and Polylines to draw a Rectangle above the Image so that we can operate the Image which is below the Rectangle using the manipulation events to fit the rectangle, meanwhile we can also use the community toolkit BackdropBlurBrush on the xaml layout to Blur the outer image.
Given a bitmap:
Swamp1 = new BitmapImage(new Uri("pack://application:,,,/Images/Swamp-Corner-Transparent.png"));
How can I specify the coordinates that it will appear on a canvas:
<Canvas Grid.Column="2" HorizontalAlignment="Right" Height="822" VerticalAlignment="Top" Width="1198" Name="MainCanvas">
<Image Name="MapBorderSource" />
</Canvas>
I've done this before, but it was a long time ago. Specifically, I need to draw the BitmapImage 'Swamp1' at the coordinates X,Y of the Canvas 'MainCanvas' on top of the Image 'MapBorderSource'. The PNG has white set to Alpha 0.
In code behind, you would write
var image = new Image
{
Source = new BitmapImage(new Uri(
"pack://application:,,,/Images/Swamp-Corner-Transparent.png"));
};
Canvas.SetLeft(image, x);
Canvas.SetTop(image, y);
MainCanvas.Children.Add(image);
If you need to put the new Image directly on top of MapBorderSource, below any possible other child elements, you could write
var index = MainCanvas.Children.IndexOf(MapBorderSource) + 1;
MainCanvas.Children.Insert(index, image);
You can specify coordinates like this..
<Image Name="MapBorderSource" Canvas.Top="10" Canvas.Left="10" />
It seems like variations of this question have been asked before, but not this specifically. Also, it seems that BitmapImages are different from straight Bitmaps. So here we go:
I've got a BitmapImage x:Name="HeightMapImage" that is pointing to an Image x:Name="image" that is inside ContentPresenter x:Name="contentPresenter" that is in Viewbox x:Name="viewBox". I want to draw both semi-transparently and non-transparently at specific X,Y coordinates on the HeightMapImage.
The reason for this set-up is that the BitmapImage is being scrolled and zoomed. When I draw on the BitmapImage at X,Y I want that to automatically scroll and zoom, too.
I'm a very old geek having written for many machines in many different GDIs over the years. This seems like a 'get the handle' to some graphic device context problem and once I've got it I can merrily draw away.
Your help is greatly appreciated.
Somebody wanted to see the code. Here's the XAML:
<Viewbox x:Name="viewBox" Margin="0,0,0,0">
<ContentPresenter x:Name="contentPresenter" Width="350" Height="350" >
<ContentPresenter.Content>
<Image x:Name="image" Width="350" Height="350">
<Image.Source>
<BitmapImage x:Name="HeightMapImage" UriSource="DinoIslandLogo.bmp" />
</Image.Source>
</Image>
</ContentPresenter.Content>
</ContentPresenter>
</Viewbox>
And here's a screen capture that somebody wanted:
And here's the code that gets the user's selected bitmap and loads and displays it:
string selectedFileName = dlg.FileName;
BitmapImage bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.UriSource = new Uri(selectedFileName);
bitmap.EndInit();
image.Source = bitmap;
Does this need to be rewritten for a Writeable Bitmap?
You may use a WriteableBitmap instead of (or actually in addition to) a BitmapImage. First create your BitmapImage as usual (but with less code):
var selectedFileName = dlg.FileName;
var bitmap = new BitmapImage(new Uri(selectedFileName));
Then create a WritableBitmap from the BitmapImage and assign that to the Image control:
var writeableBitmap = new WriteableBitmap(bitmap);
image.Source = writeableBitmap;
Now you may modify the WriteableBitmap in order to draw your overlay data. The following code snippet shows how to get and modify a pixel in the bitmap:
if (writeableBitmap.Format.BitsPerPixel == 32)
{
var x = 10;
var y = 20;
var pixelRect = new Int32Rect(x, y, 1, 1);
var pixelBuffer = new byte[4];
writeableBitmap.CopyPixels(pixelRect, pixelBuffer, 4, 0);
// modify pixelBuffer and write it back
writeableBitmap.WritePixels(pixelRect, pixelBuffer, 4, 0);
}
EDIT: A suggestion for a SetPixel method that takes the overlay color alpha value into account. Please note that this method assumes that the bitmap's pixel format is Bgr32.
public void SetPixel(WriteableBitmap wb, int x, int y, Color color)
{
var pixelRect = new Int32Rect(x, y, 1, 1);
var pixelBuffer = new byte[4];
wb.CopyPixels(pixelRect, pixelBuffer, 4, 0);
pixelBuffer[0] = (byte)(pixelBuffer[0] * (1F - color.ScA) + color.B * color.ScA);
pixelBuffer[1] = (byte)(pixelBuffer[1] * (1F - color.ScA) + color.G * color.ScA);
pixelBuffer[2] = (byte)(pixelBuffer[2] * (1F - color.ScA) + color.R * color.ScA);
wb.WritePixels(pixelRect, pixelBuffer, 4, 0);
}
Please note also that it is a lot more efficient to set a larger number of pixels in one go. Ideally you would set all overlay pixels at once. You would copy all pixel values into one large array, calculate their new RGB values as shown above, and then write them all back at once.
My requirements:
a persistent UserControl that handles logic for a custom image, such as a map or drawing
a set of containers to implement caching on the image during zoom or pan movements
VisualBrush copies of the UserControl that I can add to the containers for use with Effects
I currently implement image caching with a RenderTargetBitmap, but that seems to have trouble with the VisualBrush-covered Rectangle objects I'm using.
My question: What can I add/change in this code to get the VisualBrush objects to render correctly after RenderTargetBitmap uses them? What strange thing is RenderTargetBitmap doing that makes the VisualBrush invisible?
This is a problem that I have been unable to reproduce without a decent amount of code.
In my xaml file I have:
<Window x:Class="ElementRender.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<Grid>
<Grid Name="_contentContainer">
<Rectangle Fill="White"/>
<Grid Name="_content">
<Grid Name="_back"/>
<Grid Name="_body"/>
</Grid>
</Grid>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<Button Content="New" Name="New"/>
<Button Content="Move" Name="Move"/>
<Button Content="Update" Name="Update"/>
</StackPanel>
</Grid>
</Window>
and the .xaml.cs:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
public partial class MainWindow : Window
{
private const int imageWidth = 150;
private const int imageHeight = 150;
private readonly UserControl Control;
public MainWindow()
{
InitializeComponent();
// User Control setup
Control = new UserControl() {
Width = imageWidth, Height = imageHeight,
Content = BuildImage()
};
_body.Children.Add(SoftCopy(Control));
// event setup
Move.Click += (sender, e) => _content.RenderTransform = new TranslateTransform(50, 50);
New.Click += (sender, e) => {
HardCopy();
_content.RenderTransform = null;
Control.Content = BuildImage();
};
}
private FrameworkElement BuildImage()
{
return new Rectangle{Fill=Brushes.Blue};
}
private void HardCopy()
{
int width = (int) _contentContainer.ActualWidth;
int height = (int) _contentContainer.ActualHeight;
// render the current image
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual dv = new DrawingVisual();
using (var context = dv.RenderOpen())
{
var brush = new VisualBrush(_contentContainer) { Opacity = .5 };
context.DrawRectangle(brush, null, new Rect(0, 0, width, height));
}
rtb.Render(dv);
var lastRender = new Image
{
Source = rtb,
Stretch = Stretch.None,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
Width = width,
Height = height
};
_back.Children.Clear();
_back.Children.Add(lastRender);
}
private FrameworkElement SoftCopy(FrameworkElement element)
{
return new Rectangle{Fill= new VisualBrush(element), Width=element.Width, Height=element.Height};
}
}
A few helping notes about the code:
the xaml's _contentContainer works with HardCopy() to copy the current images into the image cache, _back.
SoftCopy returns a FrameworkElement that looks exactly like the one past in, but without any transforms, effects, or visual parents. This is very important.
BuildImage simulates building a new image to be pasted over the cache after the initial image has been transformed somehow.
If you build and run the application removing the SoftCopy() from the _body.Children.Add(SoftCopy(Control));, you see the effect that I want to get: the new element is pasted above the old element, and the old element seems to retain its transform.
Alternatively, if you cut out the line var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); from HardCopy, the caching function is broken, but the SoftCopy is displayed correctly.
However, if you run the application as-is, you notice that the new BlueRectangle (as rendered through a VisualBrush) doesn't display at all, until you hit the "New" button again, pushing the image to the cache, and still not showing you the new created image.
I'm going to be pompous enough to call this a bug in WPF. I eventually found out how to fix the strange behavior I was getting:
var visual = visualBrush.Visual;
visualBrush.Visual = null;
visualBrush.Visual = visual;
This should essentially be a null operation: by the end, the visual brush has the same visual as when it started. However, adding this code segment after rendering the VisualBrush into the RenderTargetBitmap fixed the issue I was having.
I didn't quite understand the post but there are few important things:
If you apply RenderTransform/Margins to element and take picture of it(RenderTargetBItmap), you're gonna have bad time. It will be offseted and you will get only sub-picture.
The idea is to take picture without any rendertransforms, and then later copy RenderTransform over from the old one. If needed.
Current in code-behind, I dynamically create a WPF Image control and bind the source to a custom databinding. This will eventually be added to a grid to provide a background image:
Image myImage = new Image();
myImage.Stretch = Stretch.UniformToFill;
myImage.SetBinding(Image.SourceProperty, myBinding);
The problem is that I want to tile this image, so the only way I can find to do this is to create an ImageBrush and set the TileMode property. But there is no "SetBinding" function, so how can I accomplish what I need?
ImageBrush myBrush = new ImageBrush();
myBrush.TileMode = TileMode.Tile;
// Can't do this!
myBrush.SetBinding(ImageBrush.SourceImageProperty, myBinding);
Are there any other ways to tile an image like this in code-behind?
You need not to change anything but use the BindingOperations:
BindingOperations.SetBinding(myBrush, ImageBrush.ImageSourceProperty, myBinding);
And you need to define the Viewport and the fill the viewport with brush:
MyBrush.Viewport = new Rect(0, 0, 0.1, 0.1);
// Create a rectangle and paint it with the ImageBrush.
Rectangle rec = new Rectangle();
rec.Stroke = Brushes.LimeGreen;
rec.StrokeThickness = 1;
rec.Fill = MyBrush;
I've tried the following. In debugmode the property of the VisualBrush is set correctly. Certainly the image is show as a stretched image. Don't know why. Hope it helps.
the property
public TileMode Mode { get; set; }
the binding
VisualBrush myBrush = new VisualBrush();
Uri uri = new Uri("picture.png", UriKind.RelativeOrAbsolute);
ImageSource src = new BitmapImage(uri);
myBrush.Visual = new Image() { Source = src };
this.Mode = TileMode.Tile;
Binding bind = new Binding() { Source = Mode };
BindingOperations.SetBinding(myBrush, VisualBrush.TileModeProperty, bind);
this.Background = myBrush;
I don't like code behind, so it's hard to me to write code-behind sample quickly.
Here's markup sample:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.Background>
<ImageBrush ImageSource="Sample.jpg" TileMode="Tile" Viewport="0,0,0.5,0.5"/>
</Grid.Background>
</Grid>
Instead of hard-coded image (ImageSource="Sample.jpg") you can write any binding expression like this: ImageSource="{Binding MyBackgroundImageUri}".