I have a WPF .xaml file which contains an image framework element which is bound to a BitmapSource in my C# code. At the moment, I'm hard coding the image width and height of my bitmap (ie. 512px) and I've specified the framework element not to stretch the image. So, if my window is bigger than the bitmap, the image seems to float inside the bounds of the window. However, what I'd like is that the bitmap width and height is automatically bound to the width and height of the .xaml window. So, when I resize the window, the bitmap gets resized along with it. I don't simply want the image to be 'stretched'... rather, I want the bitmap width and height to match the window that contains it. I'm not entirely sure how to do this, but I'll post the setup that I have so far.
In my .xaml file, I have my Image framework element (Note: the render transform merely flips the image along the horizontal axis):
<Image Source="{Binding WpfPreview}" Name="VectorPreview" Stretch="None" Width="{Binding Xres}" Height="{Binding Yres}" RenderTransformOrigin="0.5,0.5">
<Image.RenderTransform>
<ScaleTransform ScaleY="-1"/>
</Image.RenderTransform>
</Image>
My C# code then has the following code in it:
protected BitmapSource wpfPreview;
public BitmapSource WpfPreview
{
get { return wpfPreview; }
set
{
wpfPreview = value;
NotifyPropertyChanged("WpfPreview");
}
}
protected static int xres = 512;
public int Xres
{
get { return xres; }
set
{
xres = value;
NotifyPropertyChanged("Xres");
UpdateBitmapPreview();
}
}
protected static int yres = 512;
public int Yres
{
get { return yres; }
set
{
yres = value;
NotifyPropertyChanged("Yres");
UpdateBitmapPreview();
}
}
protected GDI.Bitmap gdiPreviewImage = new GDI.Bitmap(xres, yres);
public void UpdateBitmapPreview()
{
if(gdiPreviewImage.Width != xres || gdiPreviewImage.Height != yres) gdiPreviewSurface = new GDI.Bitmap(xres, yres);
using (var graphics = GDI.Graphics.FromImage(gdiPreviewImage))
{
graphics.Clear(Color.White);
graphics.ResetTransform();
currentSlicer.DrawBitmapPreview(graphics, PreviewOptions);
}
WpfPreview = Imaging.CreateBitmapSourceFromHBitmap(
gdiPreviewImage.GetHbitmap(),
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions()
);
}
You basically want to keep aspect ratio while resizing? I think these properties can fix this up. set ClipToBounds to true and Stretch to Uniform.
<Image Stretch="Uniform" ...
Related
I'm creating OCR app and my idea is to draw rectangle on a image to create bounding box from where I want to extract text from image so not to take all ocr recognized text.
I have canvas and inside it image and rectangle. The image is put by default to top=0 and left=0 and then stretched uniform to fit on the canvas so top and left still is the same. How can I get position of rectangle mapped to position on image.
With my code bellow and on result image, because I draw rectangle on canvas top of the rectangle is 100 and left 300 and. But I want to get position of rectangle on the image.
<Grid Grid.Row="1">
<Canvas
Background="Aquamarine"
x:Name="CanvasImagePreview"
SizeChanged="Canvas_SizeChanged"
PointerPressed="Canvas_PointerPressed"
PointerReleased="Canvas_PointerReleased"
PointerMoved="Canvas_PointerMoved"
>
<Image x:Name="ImagePreview"
Width="{Binding Path=ActualWidth, ElementName=CanvasImagePreview}"
Height="{Binding Path=ActualHeight, ElementName=CanvasImagePreview}"
Stretch="Uniform"/>
<Rectangle
x:Name="BoudingBox_Rect"
RadiusX="10"
RadiusY="10"
StrokeThickness="3"
Stroke="Red"
Visibility="Collapsed"
/>
</Canvas>
<Grid/>
result image
By testing, we cannot get the actual size of image when the image is stretched to fill the Canvas panel by using the {Binding} extension to set the Width and Height property. The value of Width and Heigtht property of Image is actual the value of Canvas.
You need to calculate the actual size of the image based on the original size of the image which can be get from the corresponding BitmapImage. Then, get the position of rectangle on the image by calculating.
Please check the following code:
var CanvasActualHeight = CanvasImagePreview.ActualHeight;
var CanvasActualWidth = CanvasImagePreview.ActualWidth;
double visualHeight=0.0;
double visualWeight=0.0;
BitmapImage bitmapImage = (BitmapImage)ImagePreview.Source;
if(bitmapImage!=null)
{
var originalHeight = bitmapImage.PixelHeight;
var originalWidth = bitmapImage.PixelWidth;
if((CanvasActualHeight / originalHeight) >(CanvasActualWidth / originalWidth))
{
visualHeight = originalHeight* CanvasActualWidth / originalWidth;
visualWeight = CanvasActualWidth;
}
else
{
visualHeight = CanvasActualHeight;
visualWeight = originalWidth * CanvasActualHeight / originalHeight;
}
}
var relativeToCanvasTop = BoudingBox_Rect.Margin.Top;
var relativeToCanvasLeft = BoudingBox_Rect.Margin.Left;
var relativeToImageTop = BoudingBox_Rect.Margin.Top - (CanvasActualHeight - visualHeight)/2;
var relativeToImageLeft = BoudingBox_Rect.Margin.Left-(CanvasActualWidth - visualWeight)/2;
Simple xaml:
<WrapPanel Orientation="Vertical">
<Ellipse Width="100" Height="100" Fill="Red" />
<Ellipse Width="100" Height="100" Fill="Yellow" />
<Ellipse Width="100" Height="100" Fill="Green" />
</WrapPanel>
Resizing window:
How to show vertical and horizontal scrollbars when content doesn't fit?
Note: this should work for any content.
I tried to put it into ScrollViewer:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<WrapPanel Orientation="Vertical">
...
</WrapPanel>
</ScrollViewer>
but then WrapPanel stops wrapping anything (always one column):
The problem here is that ScrollViewer gives (NaN, NaN) size to it child, so wrapping never occurs.
I tried to fix it by binding scroll viewer available height to max height of panel:
<ScrollViewer ...>
<WrapPanel MaxHeight="{Binding ViewportHeight, RelativeSource={RelativeSource AncestorType=ScrollViewer}}" ...>
This will limit panel height (not NaN anymore), so wrapping now occurs. But because this also adjust the height of panel - the vertical scrollbar will never appears:
How to add vertical scrollbar?
In my case WrapPanel is vertical, means it will fill columns as much as it can and then wrap to a new column from left to right. Scrollbars are needed when children doesn't fit either vertically (when available space is less than children height) or horizontally.
The idea thought can be used for a standard (horizontal) WrapPanel: from left to right, creating new rows when full. Absolutely same problem will arise (just tried it).
That sort of behavior is not possible with a WrapPanel without setting explicitly its Height/MinHeight for a Vertical orientation or Width/MinWidth for a Horizontal orientation. The ScrollViewer will only show the scrollbars when the FrameworkElement this scroll viewer wraps doesn't fit into the viewport.
You can create your own wrap panel that calculates its minimum size based on its children.
Alternatively, you can implement a Behavior<WrapPanel> or an attached property. This won't be as easy as just adding a couple of XAML tags, as you might expect.
We have solved this issue with an attached property. Let me give you an idea of what we did.
static class ScrollableWrapPanel
{
public static readonly DependencyProperty IsEnabledProperty =
DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ScrollableWrapPanel), new PropertyMetadata(false, IsEnabledChanged));
// DP Get/Set static methods omitted
static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var panel = (WrapPanel)d;
if (!panel.IsInitialized)
{
panel.Initialized += PanelInitialized;
}
// Add here the IsEnabled == false logic, if you wish
}
static void PanelInitialized(object sender, EventArgs e)
{
var panel = (WrapPanel)sender;
// Monitor the Orientation property.
// There is no OrientationChanged event, so we use the DP tools.
DependencyPropertyDescriptor.FromProperty(
WrapPanel.OrientationProperty,
typeof(WrapPanel))
.AddValueChanged(panel, OrientationChanged);
panel.Unloaded += PanelUnloaded;
// Sets up our custom behavior for the first time
OrientationChanged(panel, EventArgs.Empty);
}
static void OrientationChanged(object sender, EventArgs e)
{
var panel = (WrapPanel)sender;
if (panel.Orientation == Orientation.Vertical)
{
// We might have set it for the Horizontal orientation
BindingOperations.ClearBinding(panel, WrapPanel.MinWidthProperty);
// This multi-binding monitors the heights of the children
// and returns the maximum height.
var converter = new MaxValueConverter();
var minHeightBiding = new MultiBinding { Converter = converter };
foreach (var child in panel.Children.OfType<FrameworkElement>())
{
minHeightBiding.Bindings.Add(new Binding("ActualHeight") { Mode = BindingMode.OneWay, Source = child });
}
BindingOperations.SetBinding(panel, WrapPanel.MinHeightProperty, minHeightBiding);
// We might have set it for the Horizontal orientation
BindingOperations.ClearBinding(panel, WrapPanel.WidthProperty);
// We have to define the wrap panel's height for the vertical orientation
var binding = new Binding("ViewportHeight")
{
RelativeSource = new RelativeSource { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(ScrollViewer)}
};
BindingOperations.SetBinding(panel, WrapPanel.HeightProperty, binding);
}
else
{
// The "transposed" case for the horizontal wrap panel
}
}
static void PanelUnloaded(object sender, RoutedEventArgs e)
{
var panel = (WrapPanel)sender;
panel.Unloaded -= PanelUnloaded;
// This is really important to prevent the memory leaks.
DependencyPropertyDescriptor.FromProperty(WrapPanel.OrientationProperty, typeof(WrapPanel))
.RemoveValueChanged(panel, OrientationChanged);
}
private class MaxValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Cast<double>().Max();
}
// ConvertBack omitted
}
}
It is maybe not the easiest way, and there are a little bit more lines that just a few XAML tags, but it works flawlessly.
You have to be careful with the error handling though. I've just omitted all the checks and exception handling in the sample code.
The usage is simple:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<WrapPanel Orientation="Vertical" local:ScrollableWrapPanel.IsEnabled="True">
<!-- Content -->
</WrapPanel>
</ScrollViewer
It seems monitoring for children is one of important task to achieve wanted. So why not creating custom panel:
public class ColumnPanel : Panel
{
public double ViewportHeight
{
get { return (double)GetValue(ViewportHeightProperty); }
set { SetValue(ViewportHeightProperty, value); }
}
public static readonly DependencyProperty ViewportHeightProperty =
DependencyProperty.Register("ViewportHeight", typeof(double), typeof(ColumnPanel),
new FrameworkPropertyMetadata(double.PositiveInfinity, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange));
protected override Size MeasureOverride(Size constraint)
{
var location = new Point(0, 0);
var size = new Size(0, 0);
foreach (UIElement child in Children)
{
child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
if (location.Y != 0 && ViewportHeight < location.Y + child.DesiredSize.Height)
{
location.X = size.Width;
location.Y = 0;
}
if (size.Width < location.X + child.DesiredSize.Width)
size.Width = location.X + child.DesiredSize.Width;
if (size.Height < location.Y + child.DesiredSize.Height)
size.Height = location.Y + child.DesiredSize.Height;
location.Offset(0, child.DesiredSize.Height);
}
return size;
}
protected override Size ArrangeOverride(Size finalSize)
{
var location = new Point(0, 0);
var size = new Size(0, 0);
foreach (UIElement child in Children)
{
if (location.Y != 0 && ViewportHeight < location.Y + child.DesiredSize.Height)
{
location.X = size.Width;
location.Y = 0;
}
child.Arrange(new Rect(location, child.DesiredSize));
if (size.Width < location.X + child.DesiredSize.Width)
size.Width = location.X + child.DesiredSize.Width;
if (size.Height < location.Y + child.DesiredSize.Height)
size.Height = location.Y + child.DesiredSize.Height;
location.Offset(0, child.DesiredSize.Height);
}
return size;
}
}
The usage (instead of WrapPanel) is following:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<local:ColumnPanel ViewportHeight="{Binding ViewportHeight, RelativeSource={RelativeSource AncestorType=ScrollViewer}}" ... >
...
</local:ColumnPanel>
</ScrollViewer>
The idea is to calculate layout manually, while MeasureOverride and ArrangeOverride will be automatically called whenever children are changed: added, deleted, resized, etc.
Measure logic is simple: start from (0,0) and measure next child size, if it fits into current column - add it, otherwise start and new column by offsetting location. During whole measurement cycle adjust the resulting size.
The only missing part of puzzle is to provide into measure/arrange cycles ViewportHeight from parent ScrollViewer. This is the role of ColumnPanel.ViewportHeight.
Here is the demo (button add purple circle):
You can do this by wrapping your wrappanel in a scrollviewer, but then binding the height and width of the inner panel to the Height and Width of the Viewport of the scrollviewer, so it stretches and contracts with the rest of the screen.
I've also added minimum Height and Width to my sample, which ensures that the scrollbars appear once the wrap panel is pushed smaller than it's min dimensions
<ScrollViewer x:Name="sv" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<WrapPanel MinWidth="200" Width="{Binding ElementName=sv, Path=ViewportWidth}" MinHeight="200" Height="{Binding ElementName=sv, Path=ViewportHeight}">
<Ellipse Fill="Red" Height="200" Width="200"/>
<Ellipse Fill="Yellow" Height="200" Width="200"/>
<Ellipse Fill="Green" Height="200" Width="200"/>
</WrapPanel>
</ScrollViewer>
The Source of the Image is bound to a URL which points to an image.
If the image at the URL is smaller then the MaxHeight and MaxWidth, the following code works great. The image size is exactly the same as the url and the window is sized properly.
If the image at the URL is larger then the MaxHeight and MaxWidth, only a portion of the image is displayed. The image does not get shrunk to fit into the window.
If I remove Stretch="None", the large picture then shrinks to fit into the MaxHeight and MaxWidth, looks great, but the small image gets expanded to consume all available space and looks like crap.
Here are two images I have been testing with:
http://imgur.com/iaBp2Fv,fiRrTJS#0
<Window x:Class="MyNamespace.Windows.PictureWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Profile Picture" ResizeMode="NoResize" UseLayoutRounding="True" SizeToContent="WidthAndHeight" MaxHeight="750" MaxWidth="750">
<Image Stretch="None" HorizontalAlignment="Center" VerticalAlignment="Center" Source="{Binding}" />
</Window>
What you could do is to to remove the Stretch="None", as you say, to make the large image shrink down. But, to avoid the small image to be scaled up, you just add this property:
StretchDirection="DownOnly"
This prevents small images from being scaled upwards, and allows large images to be scaled down. The Window resizes appropriately as well.
This is the code that I have tested in LinqPad. Just change showLarge to true and false to switch between the images.
bool showLarge = false;
var w = new Window();
w.ResizeMode = ResizeMode.NoResize;
w.UseLayoutRounding = true;
w.SizeToContent = SizeToContent.WidthAndHeight;
w.MaxHeight = 750;
w.MaxWidth = 750;
Image img = new Image();
img.HorizontalAlignment = HorizontalAlignment.Center;
img.VerticalAlignment = VerticalAlignment.Center;
img.StretchDirection = StretchDirection.DownOnly;
if(showLarge)
img.Source = new BitmapImage(new System.Uri(#"http://i.imgur.com/iaBp2Fv.jpg"));
else
img.Source = new BitmapImage(new System.Uri(#"http://i.imgur.com/fiRrTJS.jpg"));
w.Content = img;
w.ShowDialog();
In your code behind, create a property as
public Point ImageSize {get;set}
Get the image from the URL in the constructor/Initialize() and set ImageSize accordingly
Bind the height and width of your window to ImageSize.X and ImageSize.Y
Height="{Binding ImageSize.Y}" Width="{Binding ImageSize.Y}"
I am trying to create some rotated text and save that image to a PNG file. The resulting PNG should be no larger than needed (or minimal padding). I have it working as long as there is no rotation, but as soon as I rotate the text, it is getting clipped off in the file. I am sure it has something to do with adjusting the either the CenterX and CenterY of the RotateTransform or creating a TranslateTransform, but I can't find anything on how to do it correctly and my trial-and-error testing has turned into trial-and-frustration.
My sample code is below. I looking for a solution that would work with an arbitrary angle and not just -45 degrees.
Finally, if someone knows how to meet these requirements, but say using an "old style" Graphics object instead of WPF tools, I am open to that solution to that too.
private static void CreateImageFile()
{
FormattedText ft;
Geometry textBox;
string fontName;
Typeface face;
DrawingVisual viz;
RotateTransform rt;
TranslateTransform tt;
Rect rect;
RenderTargetBitmap bmp;
PngBitmapEncoder encoder;
ft = CreateText("Lorem ipsum dolor sit amet, consectetur adipisicing" + Environment.NewLine + "elit, sed do eiusmod tempor", "Verdana", 12, false, false);
textBox = ft.BuildHighlightGeometry(new Point());
fontName = "Arial";
face = new Typeface(fontName);
// now create the visual we'll draw them to
viz = new DrawingVisual();
rt = new RotateTransform() { Angle = -45 };
rect = rt.TransformBounds(ft.BuildHighlightGeometry(new Point(0, 0)).Bounds);
using (DrawingContext dc = viz.RenderOpen())
{
dc.PushTransform(rt);
dc.DrawText(ft, new Point(0, 0));
dc.Pop();
}
bmp = new RenderTargetBitmap((int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(viz);
encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
using (FileStream file = new FileStream("TextImage.png", FileMode.Create))
encoder.Save(file);
}
private static FormattedText CreateText(string text, string typeface, double fontSize, bool bold, bool italic)
{
FontStyle fontStyle = FontStyles.Normal;
FontWeight fontWeight = FontWeights.Medium;
if (bold == true) fontWeight = FontWeights.Bold;
if (italic == true) fontStyle = FontStyles.Italic;
// Create the formatted text based on the properties set.
FormattedText formattedText = new FormattedText(
text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(new FontFamily(typeface),
fontStyle,
fontWeight,
FontStretches.Normal),
fontSize,
Brushes.Black, // This brush does not matter since we use the geometry of the text.
null,
TextFormattingMode.Display
);
return formattedText;
}
Update
Based upon some of the suggestions below, I decided to try a different tack and experiment in the GUI. I created a window like this:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="160" Width="160" Loaded="Window_Loaded">
<Grid>
<Canvas Name="WorkCanvas" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-45"/>
<TranslateTransform/>
</TransformGroup>
</TextBlock.RenderTransform>This is a test</TextBlock>
</Canvas>
</Grid>
</Window>
As you can see, it uses both the RotateTransform and the suggested RenderTransformOrigin, and the result is like the image below. And, as you can see, the text does not go through the middle of the Canvas. And that seems to be my entire problem. How to rotate the text and get it correctly centered.
Update 2
I decided to try a Grid instead of a Canvas this time and I can now get the text properly centered in the grid, but since I can't use a FormattedText object, I can't seem to measure the actual bounding box. All measurements of the TextBlock or the Grid come back as if it was not rotated at all (looking at ActualWidth, ActualHeight, and DesiredSize). If I can't get the rotated bounding box size, I can't save the PNG without it getting clipped.
Oh, and I tried rotating the text in an unrotated grid and rotating the grid itself, both give the same results when trying to determine the dimensions.
You could try to wrap your text in an element that has a rendertransformOrigin. Make you changes to that element. Try a canvas or a grid.
I think what you are missing is that you need to set RenderTransformOrigin to 0.5,0.5 so that your rotation transformation is around the center of you image and not the upper left-hand edge.
Update
In response to your update above. The problem is using canvas. If you remove your transform altogether, you'll see your TextBlock isn't centered to start with. It actually is rotating around it's center, it's just that the center isn't the center of the canvas. Try this:
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock TextAlignment="Center" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<RotateTransform Angle="-45"/>
</TextBlock.RenderTransform>
This is a test
</TextBlock>
</Grid>
After much poking around and with help from Matt and kbo4sho88, I finally found the correct way of doing it. In addition to the help from the other posters, I finally found that I need to call TransformToVisual and TransformBounds to get the bounding box that I need for the correct file size. But, before that, I had to call Measure and Arrange since these objects are not shown on a screen.
Phew!
private static void CreateImageFile()
{
Grid workGrid;
TextBlock workTextBlock;
RenderTargetBitmap bitmap;
PngBitmapEncoder encoder;
Rect textBlockBounds;
GeneralTransform transform;
workGrid = new Grid()
{
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center
};
workTextBlock = new TextBlock()
{
Text = "Lorem ipsum dolor sit amet, consectetur adipisicing" + Environment.NewLine + "elit, sed do eiusmod tempor",
FontFamily = new FontFamily("Verdana"),
FontSize = 36,
TextAlignment = TextAlignment.Center,
RenderTransformOrigin = new Point(0.5, 0.5),
LayoutTransform = new RotateTransform(-45)
};
workGrid.Children.Add(workTextBlock);
/*
* We now must measure and arrange the controls we just created to fill in the details (like
* ActualWidth and ActualHeight before we call TransformToVisual() and TransformBounds()
*/
workGrid.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
workGrid.Arrange(new Rect(0, 0, workGrid.DesiredSize.Width, workGrid.DesiredSize.Height));
transform = workTextBlock.TransformToVisual(workGrid);
textBlockBounds = transform.TransformBounds(new Rect(0, 0, workTextBlock.ActualWidth, workTextBlock.ActualHeight));
/*
* Now, create the bitmap that will be used to save the image. We will make the image the
* height and width we need at 96DPI and 32-bit RGBA (so the background will be transparent).
*/
bitmap = new RenderTargetBitmap((int)textBlockBounds.Width, (int)textBlockBounds.Height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(workGrid);
encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (FileStream file = new FileStream("TextImage.png", FileMode.Create))
encoder.Save(file);
}
Of course this is just a sample (but working) method and the final one will be parameterized.
Final part of the puzzle was found at WPF: Getting new coordinates after a Rotation
The way I generally handle custom rendering is using my own FrameworkElement and overriding the OnRender method. So, as an example:
using System;
using System.Windows; // For the FrameworkElement baseclass
using System.Windows.Media; // To get the drawing context
public class CustomThing : FrameworkElement
{
// Constructor
CustomThing()
{
}
// Custom render code called whenever control is invalidated
protected override OnRender(DrawingContext context)
{
if (!this.IsVisible || this.ActualHeight <= 0 || this.ActualWidth <= 0)
{
return; // Don't do anything if this thing isn't renderable
}
Typeface tf = new Typeface(this.FontFamily, FontStyles.Normal, this.FontWeight, FontStretches.Normal);
FormattedText fText = new FormattedText(this.Text, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, tf, this.FontSize, this.Foreground);
// You could have accessors so that the various properties such as Fonts, etc. are
// Properties of the class, and using DependencyProperties, they can be set so they
// automatically cause an invalidation when they change.
double txWidth = fText.Width;
double txHeight = fText.Height;
// This measures the text
double w = this.ActualWidth;
double h = this.ActualHeight;
double w2 = w / 2.0;
double h2 = h / 2.0;
// Get the center point for the rotation
// In this case, the center of the control
Transform trans = new RotateTransform(-90.0, w2, h2);
// The transform is for counter-clockwise 90 degrees, centered
// in the center of the control.
context.PushTransform(trans);
// All drawing operations will be performed with the
// transformation applied
Point txPos = new Point(w2 - (txWidth / 2.0), h2 - (txHeight / 2.0));
// The render origin for the text is the upper left
// hand corner of the bounding box. This uses the same origin for
// the rotation, then shifts it by half the dimensions of the text.
context.DrawText(fText, txPos);
context.Pop();
// The pop method is only needed if you need to continue work
// with the drawing context and don't want the transform
// operating on future rendering.
}
}
There are other details, such as if you want the control to be mouse interactive (include a using System.Windows.Input), then you'll want to start the render by painting the entire control region a background color. The main thing is you don't need to measure the shape of the rotated text, only the unrotated text. If you want to position the final text in any other place besides the center of the control, just make sure your rotation center is also the same reference point as your text offset.
The other thing I typically do is to use Dependency Properties so that all the styling can be done in XAML. This way, the custom object can be previewed in the designer while changing the properties dynamically, a typical entry for an item would look like so:
public static readonly RoutedEvent TextChangedEvent = EventManager.RegisterRoutedEvent("TextChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<string>), typeof(CustomThing));
public event RoutedPropertyChangedEventHandler<string> TextChanged
{
add { AddHandler(TextChangedEvent, value); }
remove { RemoveHandler(TextChangedEvent, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CustomThing), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OnTextChanged)));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
private static void OnTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
CustomThing cntrl = (CustomThing)obj;
RoutedPropertyChangedEventArgs<string> e = new RoutedPropertyChangedEventArgs<string>((string)args.OldValue, (string)args.NewValue, TextChangedEvent);
cntrl.OnTextChanged(e);
}
protected virtual void OnTextChanged(RoutedPropertyChangedEventArgs<string> e)
{
RaiseEvent(e);
}
I have an Image on my wpf control
and I am trying to generate croped part of it - this is ok more or less.
I have used a codeproject solution to generate BitmapSource of croped image (http://www.codeproject.com/KB/WPF/CropAdorner.aspx) but when I am trying to
replace current image with generated BitmapSource like this
imgCurrent.Source = generatedBitmapSource;
I see very strange behaviour ((
I need an advice how to change current Image with new based on BitmapSource.
my XAML(there is nothing extraordinary - and by the right click I am trying to replace currentImage with croped):
<DockPanel Height="395" Width="926">
<!--Went with a DockPanel here so that the image would always be centered in its parent control.-->
<Image x:Name="imgCurrent" VerticalAlignment="Center" HorizontalAlignment="Center" MouseRightButtonDown="imgCurrent_MouseRightButtonDown"/>
</DockPanel>
right click:
private void imgCurrent_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
generatedBitmapSource = _clp.BpsCrop();
//this clears croping adonder
AdornerLayer aly = AdornerLayer.GetAdornerLayer(_felCur);
aly.Remove(_clp);
//
imageCurrent.Source = generatedBitmapSource;
}
croping method (from codeproject):
public BitmapSource BpsCrop()
{
Thickness margin = AdornerMargin();
Rect rcInterior = _prCropMask.RectInterior;
Point pxFromSize = UnitsToPx(rcInterior.Width, rcInterior.Height);
// It appears that CroppedBitmap indexes from the upper left of the margin whereas RenderTargetBitmap renders the
// control exclusive of the margin. Hence our need to take the margins into account here...
Point pxFromPos = UnitsToPx(rcInterior.Left + margin.Left, rcInterior.Top + margin.Top);
Point pxWhole = UnitsToPx(AdornedElement.RenderSize.Width + margin.Left, AdornedElement.RenderSize.Height + margin.Left);
pxFromSize.X = Math.Max(Math.Min(pxWhole.X - pxFromPos.X, pxFromSize.X), 0);
pxFromSize.Y = Math.Max(Math.Min(pxWhole.Y - pxFromPos.Y, pxFromSize.Y), 0);
if (pxFromSize.X == 0 || pxFromSize.Y == 0)
{
return null;
}
System.Windows.Int32Rect rcFrom = new System.Windows.Int32Rect(pxFromPos.X, pxFromPos.Y, pxFromSize.X, pxFromSize.Y);
RenderTargetBitmap rtb = new RenderTargetBitmap(pxWhole.X, pxWhole.Y, s_dpiX, s_dpiY, PixelFormats.Default);
rtb.Render(AdornedElement);
return new CroppedBitmap(rtb, rcFrom);
}
Are you sure you don't have issue with your "BitmapSource of croped image"? Can you for test purpose replace it with another valid Bitmap and try if it works. If it works with another one, but not "BitmapSource of croped image" then maybe you have issue with creating "BitmapSource of croped image".