I want to build a small image and text editor.
I have two Images in a UserControl.
On the left side is the editor and looks momentarily (thanks to some try and error) a little bit overstated, but it works nonetheless. (I will clean up the code later)
<Canvas x:Name="EditCanvas" Grid.Column="0"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
MaxWidth="426"
MouseLeftButtonDown="EditCanvas_MouseLeftButtonDown">
<Canvas.Background>
<VisualBrush TileMode="Tile" Viewport="0, 0, 1, 1" Stretch="{Binding SelectedStretch}">
<VisualBrush.Visual>
<Image x:Name="EditorImage"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
Source="{Binding FileImagePath}" Margin="1"/>
</VisualBrush.Visual>
</VisualBrush>
</Canvas.Background>
</Canvas>
Here I bind the Image to a UriSource. The stretching will change the Image itself, not the underlying source. Now I want to crop this image 'as it is'! So, when it is set to Stretch.UniformToFill or Stretch.Fill (for example) I want the cropped image to look exactly as it is shown in the left area. That means, the cropped image can be cropped on the bottom, the right, not at all or something like this. The formula to calculate this by myself would be a little bit greater.
Now I wonder if there isn't a better way. I have to draw text later on to this cropped image, so I cannot simply convert it to a DrawingContext, DrawingVisual, and so on and draw text in it, because that would also stretch the text later on the right side.
And if possible it would be nice to get a fast method, that can be redrawn a few times per second without killing the GPU.
For showing the preview image I use this XAML at the moment:
<Canvas Grid.Column="1"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
MaxWidth="426">
<Canvas.Background>
<VisualBrush TileMode="Tile"
Viewport="0, 0, 1, 1"
Stretch="{Binding SelectedStretch}">
<VisualBrush.Visual>
<Image Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
Source="{Binding PreviewImage}"
Margin="1" x:Name="TestImage"/>
</VisualBrush.Visual>
</VisualBrush>
</Canvas.Background>
</Canvas>
<Border Grid.Column="1"
BorderThickness="1" BorderBrush="Black"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}" />
And this code behind to calculate the image and fill it with the desired text (even if it is faulted at this moment):
BitmapSource imageSource = EditorImage.Source as BitmapSource;
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawImage(imageSource, new Rect(0, 0, imageSource.PixelWidth, imageSource.PixelHeight));
List<TextPosition> textPositions = GetTextPositions();
if (textPositions.Any())
{
textPositions.ForEach(x =>
drawingContext.DrawText(new FormattedText(x.Text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface(x.FontFamily, x.Style, x.Weight, FontStretch), x.FontSize, new SolidColorBrush(x.ForegroundColor),
VisualTreeHelper.GetDpi(drawingVisual).PixelsPerDip), x.Position)
);
}
}
PreviewImage = new RenderTargetBitmap(imageSource.PixelWidth, imageSource.PixelHeight, 96, 96, PixelFormats.Pbgra32);
PreviewImage.Render(drawingVisual);
TestImage.InvalidateVisual();
Ok, it doesn't seem to be possible. So I took a detour and do it now like this (if ever someone else does have a similar problem):
// Get image from the editor
BitmapSource bitmapSource = EditorImage.Source as BitmapSource;
// Get bitmap and transform it to the stretched version
Size scaleFactor = ComputeScaleFactor(new Size(AreaWidth, AreaHeight), new Size(bitmapSource.PixelWidth, bitmapSource.PixelHeight), SelectedStretch, StretchDirection.Both);
Size newCompleteSize = new Size(bitmapSource.PixelWidth * scaleFactor.Width, bitmapSource.PixelHeight * scaleFactor.Height);
Size renderSize = new Size
{
Width = newCompleteSize.Width > AreaWidth ? AreaWidth : newCompleteSize.Width,
Height = newCompleteSize.Height > AreaHeight ? AreaHeight : newCompleteSize.Height
};
Point imagePosition = GetImagePosition(renderSize);
BitmapImage bitmapImage = new BitmapImage();
using (MemoryStream memoryStream = new MemoryStream())
{
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
encoder.Save(memoryStream);
memoryStream.Position = 0;
bitmapImage.BeginInit();
bitmapImage.StreamSource = memoryStream;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.DecodePixelWidth = (int)Math.Round(newCompleteSize.Width, 0, MidpointRounding.AwayFromZero);
bitmapImage.DecodePixelHeight = (int)Math.Round(newCompleteSize.Height, 0, MidpointRounding.AwayFromZero);
bitmapImage.EndInit();
}
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
// Draw the copy of the image
drawingContext.DrawImage(bitmapImage, new Rect(imagePosition, new Size(bitmapImage.PixelWidth, bitmapImage.PixelHeight)));
// Check if text has to be drawn
List<TextPosition> textPositions = GetTextPositions();
if (textPositions.Any())
{
textPositions.ForEach(x =>
{
FormattedText text = new FormattedText(x.ReplacedText, CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
new Typeface(x.FontFamily, x.Style, x.Weight, FontStretch), x.FontSize, new SolidColorBrush(x.ForegroundColor),
VisualTreeHelper.GetDpi(drawingVisual).PixelsPerDip)
{
TextAlignment = x.Alignment
};
TextOptions.SetTextRenderingMode(this, TextRenderingMode.Aliased);
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
drawingContext.DrawText(text, new Point(x.Position.X, x.Position.Y));
});
}
}
PreviewImage = new RenderTargetBitmap(
(int)Math.Round(AreaWidth, 0, MidpointRounding.AwayFromZero),
(int)Math.Round(AreaHeight, 0, MidpointRounding.AwayFromZero),
96, 96, PixelFormats.Pbgra32);
PreviewImage.Render(drawingVisual);
And in the XAML I use two images. The first one stretched, the other one not:
<Image x:Name="EditorImage"
HorizontalAlignment="{Binding AlignX}" VerticalAlignment="{Binding AlignY}"
MaxHeight="426" MaxWidth="426" Margin="1"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
Source="{Binding FileImagePath}" Stretch="{Binding SelectedStretch}" />
<Image Grid.Column="1"
VerticalAlignment="Center" HorizontalAlignment="Center"
Source="{Binding PreviewImage}"
Height="{Binding AreaHeight}" Width="{Binding AreaWidth}"
Stretch="None"/>
Related
I have a struggle, I don't know how to set the background and size of a canvas with respect to MVVM, currently, I handle this only in the View part, and work ok, over this image (canvas) I want to draw some rectangle, but I want to be able to do everything according to MVVM pattern.
var bmp = new BitmapImage(new Uri(filename, UriKind.Relative));
ImageBrush brush = new ImageBrush();
brush.ImageSource = bmp;
canvas.Width = bmp.PixelWidth;
canvas.Height = bmp.PixelHeight;
canvas.Background = brush;
canvas.SnapsToDevicePixels = true;
The view model could expose a property with e.g. the image file path
public string ImagePath { get; set; }
to which you would bind like this
<Canvas Width="{Binding Background.ImageSource.PixelWidth,
RelativeSource={RelativeSource Self}}"
Height="{Binding Background.ImageSource.PixelHeight,
RelativeSource={RelativeSource Self}}"
SnapsToDevicePixels="True">
<Canvas.Background>
<ImageBrush ImageSource="{Binding ImagePath}"/>
</Canvas.Background>
</Canvas>
The conversion from string to ImageSource would automatically be performed by an ImageSourceConverter instance in WPF.
The Bindings would be simpler when the view model exposes a property of type BitmapSource:
public BitmapSource Image { get; set; }
XAML:
<Canvas Width="{Binding Image.PixelWidth}"
Height="{Binding Image.PixelHeight}"
SnapsToDevicePixels="True">
<Canvas.Background>
<ImageBrush ImageSource="{Binding Image}"/>
</Canvas.Background>
</Canvas>
I'm using the code below to draw text over a rectangle in a WPF canvas but it seems to stretch/squash the text and sometimes the back colour does not fill the entire box.
I'm looking for a way to make sure the box is always filled and the text is clear. Probably some form of dynamic font sizing?
Thanks.
Rectangle elip = new Rectangle();
elip.Height = 6;
elip.Width = 6;
Brush brush = new SolidColorBrush(Color.FromRgb(n.Value.R,
n.Value.G, n.Value.B));
Label TB = new Label();
TB.HorizontalAlignment = HorizontalAlignment.Stretch;
TB.Margin = new Thickness(0, 0, 0, 0);
TB.Background = brush;
TB.FontSize = 12;
TB.HorizontalContentAlignment = HorizontalAlignment.Center;
TB.Content = n.Value.Stations[0].TrackId;
BitmapCacheBrush bcb = new BitmapCacheBrush(TB);
elip.Fill = bcb;
elip.Stroke = Brushes.Black;
elip.StrokeThickness = 0.5;
elip.MouseDown += ElipOnMouseDown;
Canvas.SetTop(elip, n.Value.Y - elip.Width / 2);
Canvas.SetLeft(elip, n.Value.X - elip.Height / 2);
cMain.Children.Add(elip);
You can stretch the text using a viewbox.
I recommend using a usercontrol to encapsulate all your markup for things like this. You would have to have many thousands of these things before the overhead of a usercontrol vs building rectangles and whatnot would be significant.
I built this usercontrol:
Height="18" Width="24">
<Border BorderBrush="Black"
BorderThickness="1"
Background="Magenta"
>
<Viewbox Stretch="Uniform">
<TextBlock Text="{Binding Tag, RelativeSource={RelativeSource AncestorType=UserControl}}"
TextAlignment="Center"
Margin="2"
/>
</Viewbox>
</Border>
</UserControl>
Might not be precisely what you want in terms of width, height or whatever.
You could instantiate one of these, set it's Tag, canvas top and left and add it to the canvas.
Here's the equivalent markup I used to prove it:
Title="MainWindow" >
<Grid>
<Canvas>
<local:StationView Canvas.Left="20"
Canvas.Top="100"
Tag="16B"/>
</Canvas>
</Grid>
</Window>
I have encounteres a very weird issue with drawing shapes on a canvas in WPF.
<DockPanel Grid.Row="3">
<Canvas Name="BottomCanvas" Margin="15" Background="Yellow">
<Canvas Name="TransparentCanvas" Background="Transparent"
MouseDown="TransparentCanvas_MouseDown"
MouseUp="TransparentCanvas_MouseUp"
MouseMove="TransparentCanvas_MouseMove"
Width="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Canvas}},Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Canvas}},Path=ActualHeight}">
</Canvas>
</Canvas>
</DockPanel>
Above there is my canvas defined.
Below there is a simple example of drawing an elipse; I have no idea why this has no effect.
Ellipse myEllipse = new Ellipse();
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Color.FromArgb(0, 0, 255, 0);
myEllipse.Fill = mySolidColorBrush;
myEllipse.Width = myEllipse.Height = 100;
Canvas.SetTop(myEllipse, 15);
Canvas.SetLeft(myEllipse, 15); ;
bottomCanvas.Children.Add(myEllipse);
Can anyone see what is wrong?
EDIT:
I'm sorry. As you can see in XAML canvas is named 'BottomCanvas' and in code I've used bottomCanvas which is a property added to a MainWindow I don't know what for. That was the deal.
You mean the Ellipse is not visible? It's just because of the transparent fill of SolidColorBrush. The Alpha value of the Color should not be zero, which means invisible. If so, just change the Alpha value of the Color:
mySolidColorBrush.Color = Color.FromArgb(255, 0, 255, 0);
I need to change the parent of elements. (for group/ungroup shapes)
But I cant set the new position for an element if it has a rotation.
I saw this ,this , this and this pages and many other ways, but none worked correctly.
Please see my sample project and the following image:
The parent of Rect1 is ChildCanvas1 and The parent of Rect2 is ChildCanvas2, I want to move the Rect1 and Rect2 to the MainCanvas. (and remove the ChildCanvas and ChildCanvas2)
I don't have any problem to do that for the Rect1 because it has not any rotation.
But the Rect2 has a rotation (-20 degree) and I cant set the new coordinates for it correctly.
Please see this image:
How to change the parent of an element after a rotation and setting new coordinates correctly?
UPDATE:
Note I need a general way (for the group/ungroup elements in a big app that each element (maybe) has TranslateTransform and SkewTransform and RotateTransform and ScaleTransform)
XAML:
<Canvas x:Name="MainCanvas">
<Canvas x:Name="ChildCanvas1" Width="500" Height="250" Background="Bisque" Canvas.Top="54">
<Rectangle x:Name="Rect1" Width="200" Height="100" Fill="Red" Canvas.Left="150" Canvas.Top="100"/>
</Canvas>
<Canvas Name="ChildCanvas2" Width="500" Height="250" Background="Bisque" Canvas.Left="516" Canvas.Top="54">
<Rectangle Name="Rect2" Width="200" Height="100" Fill="Red" Canvas.Left="150" Canvas.Top="100">
<Rectangle.RenderTransform>
<TransformGroup>
<SkewTransform AngleX="-40"/>
<RotateTransform Angle="-20"/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Canvas>
<Button Name="btn1" Click="btn1_Click" Content="Move Rect1 to MainCanvas and Remove ChildCanvas1" Width="356" Height="30" Canvas.Left="59" Canvas.Top="310"/>
<Button Name="btn2" Click="btn2_Click" Content="Move Rect2 to MainCanvas and Remove ChildCanvas2" Width="350" Height="30" Canvas.Left="590" Canvas.Top="310"/>
C# code :
GeneralTransform transform = Rect2.TransformToVisual(MainCanvas);
Rect rect = transform.TransformBounds(new Rect(0, 0, Rect2.Width, Rect2.Height));
double ChildCanvas2Left = Canvas.GetLeft(ChildCanvas2);
double ChildCanvas2Top = Canvas.GetLeft(ChildCanvas2);
ChildCanvas2.Children.Remove(Rect2);
MainCanvas.Children.Add(Rect2);
Canvas.SetLeft(Rect2, rect.Left);
Canvas.SetTop(Rect2, rect.Top);
MainCanvas.Children.Remove(ChildCanvas2);
You need to recalculate the Rect2 after moving the Rect2 to the MainCanvas. Like this:
Canvas.SetTop(Rect2, Canvas.GetTop(Rect2) + Canvas.GetTop(ChildCanvas2));
The complete code:
private void btn2_Click(object sender, RoutedEventArgs e)
{
GeneralTransform transform = Rect2.TransformToVisual(MainCanvas);
Rect rect = transform.TransformBounds(new Rect(0, 0, Rect2.Width, Rect2.Height));
ChildCanvas2.Children.Remove(Rect2);
MainCanvas.Children.Remove(ChildCanvas2);
Canvas.SetLeft(Rect2, rect.Left);
Canvas.SetTop(Rect2, Canvas.GetTop(Rect2) + Canvas.GetTop(ChildCanvas2));
MainCanvas.Children.Add(Rect2);
}
However Rect2.TransformToVisual and transform.TransformBounds are not necessary in your case and you can do it more cleaner and easier without them and get same result. Like this:
ChildCanvas2.Children.Remove(Rect2);
MainCanvas.Children.Remove(ChildCanvas2);
Canvas.SetLeft(Rect2, Canvas.GetLeft(Rect2) + Canvas.GetLeft(ChildCanvas2));
Canvas.SetTop(Rect2, Canvas.GetTop(Rect2) + Canvas.GetTop(ChildCanvas2));
MainCanvas.Children.Add(Rect2);
EDIT: A general way:
ChildCanvas2.Children.Remove(Rect2);
MainCanvas.Children.Remove(ChildCanvas2);
Canvas.SetLeft(Rect2, Canvas.GetLeft(Rect2) + Canvas.GetLeft(ChildCanvas2));
Canvas.SetTop(Rect2, Canvas.GetTop(Rect2) + Canvas.GetTop(ChildCanvas2));
Canvas.SetRight(Rect2, Canvas.GetRight(Rect2) + Canvas.GetRight(ChildCanvas2));
Canvas.SetBottom(Rect2, Canvas.GetBottom(Rect2) + Canvas.GetBottom(ChildCanvas2));
MainCanvas.Children.Add(Rect2);
I have changed RenderTransform to LayoutTransform of Rect2:
<Rectangle Name="Rect2" Width="200" Height="100" Fill="Red" Canvas.Left="150" Canvas.Top="100">
<Rectangle.LayoutTransform>
<RotateTransform Angle="-20"/>
</Rectangle.LayoutTransform>
</Rectangle>
Now Rect2 looks like below:
When I press the second button I see this result:
So, Rect2 stays at the original position.
I need to create a brush in WPF that will be used as a background for a panel. The brush has a fixed height but variable width. The middle image needs to tile to fill up the space while the left and right images are fixed. I tried with a VisualBrush and a grid with images but it just doesn't align/scale correctly. Here's a drawing brush that works for fixed width. How do I make it work so that it tiles the middle image for panels of variable width?
<DrawingBrush x:Key="Background">
<DrawingBrush.Drawing>
<DrawingGroup>
<ImageDrawing Rect="0 0 16 16" ImageSource="Resources/Left.png"/>
<ImageDrawing Rect="16 0 16 16" ImageSource="Resources/Middle.png"/>
<ImageDrawing Rect="48 0 16 16" ImageSource="Resources/Right.png"/>
</DrawingGroup>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
I'm not sure you can actually do that, so as an alternative use a Binding and Converter to generate a new Brush to fit the appropriate size.
<Grid VerticalAlignment="Center" Height="16" Margin="16,0" HorizontalAlignment="Stretch"
Background="{Binding Path=ActualWidth, RelativeSource={RelativeSource Self},Converter={StaticResource widthToBrushConverter}}"/>
and in Width to brush converter
public class WidthToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var width = (double)value;
var result = new DrawingBrush();
var group = new DrawingGroup();
result.Drawing = group;
group.Children.Add(new ImageDrawing(new BitmapImage(new Uri(#"Resources\Left.png", UriKind.Relative)), new Rect(0, 0, 16, 16)));
group.Children.Add(new ImageDrawing(new BitmapImage(new Uri(#"Resources\Centre.png", UriKind.Relative)), new Rect(16, 0, width, 16)));
group.Children.Add(new ImageDrawing(new BitmapImage(new Uri(#"Resources\Right.png", UriKind.Relative)), new Rect(16 + width, 0, 16, 16)));
return result;
}
UPDATE
The only problem is this doesn't seem to work. I just get a black screen