1

我正在尝试创建一些旋转的文本并将该图像保存到 PNG 文件中。生成的 PNG 不应大于所需(或最小填充)。只要没有旋转,我就可以使用它,但是一旦我旋转文本,它就会在文件中被剪掉。我确信这与调整 RotateTransform 的 CenterX 和 CenterY 或创建 TranslateTransform 有关,但我找不到任何关于如何正确执行此操作的信息,并且我的试错测试已经变成了试验-和-沮丧

我的示例代码如下。我正在寻找一种可以使用任意角度而不仅仅是-45度的解决方案。

最后,如果有人知道如何满足这些要求,但说使用“旧式”图形对象而不是 WPF 工具,我也愿意接受该解决方案。

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;
}

更新

基于以下一些建议,我决定在 GUI 中尝试不同的策略和实验。我创建了一个这样的窗口:

<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>

如您所见,它同时使用了 theRotateTransform和建议RenderTransformOrigin的 ,结果如下图所示。而且,如您所见,文本没有通过Canvas. 这似乎是我的全部问题。如何旋转文本使其正确居中。

例子


更新 2

这次我决定尝试 aGrid而不是 a Canvas,我现在可以让文本在网格中正确居中,但由于我不能使用FormattedText对象,我似乎无法测量实际的边界框。TextBlock或 的所有测量值Grid都返回,就好像它根本没有旋转(查看ActualWidthActualHeightDesiredSize)。如果我无法获得旋转的边界框大小,我无法保存 PNG 而不会被剪裁。

哦,我尝试在未旋转的网格中旋转文本并旋转网格本身,在尝试确定尺寸时都给出相同的结果。

4

4 回答 4

2

您可以尝试将文本包装在具有 rendertransformOrigin 的元素中。让您更改该元素。尝试画布或网格。

于 2012-09-28T04:24:22.753 回答
1

我认为您缺少的是您需要将RenderTransformOrigin设置为 0.5,0.5 以便您的旋转变换围绕图像中心而不是左上边缘。

更新

回应您上面的更新。问题是使用画布。如果你完全删除你的变换,你会看到你的 TextBlock 不是从一开始就居中的。它实际上是围绕它的中心旋转的,只是中心不是画布的中心。试试这个:

<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>
于 2012-09-27T19:41:04.840 回答
1

经过一番摸索,在 Matt 和 kbo4sho88 的帮助下,我终于找到了正确的方法。除了其他发帖者的帮助外,我终于发现我需要调用 TransformToVisual 和 TransformBounds 来获取正确文件大小所需的边界框。但是,在那之前,我不得不调用 Measure 和 Arrange,因为这些对象没有显示在屏幕上。

呸!

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);
}

当然,这只是一个示例(但有效)方法,最后一个将被参数化。

谜题的最后一部分是在WPF 中找到的:Getting new coordinates after a Rotation

于 2012-09-28T16:46:45.177 回答
0

我通常处理自定义渲染的方式是使用我自己的FrameworkElement并覆盖该OnRender方法。所以,举个例子:

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.
    }   
}

还有其他细节,例如,如果您希望控件是鼠标交互的(包括 using System.Windows.Input),那么您需要通过为整个控件区域绘制背景颜色来开始渲染。最主要的是你不需要测量旋转文本的形状,只需要测量未旋转的文本。如果您想将最终文本定位在控件中心以外的任何其他位置,只需确保您的旋转中心也是与文本偏移相同的参考点。

我通常做的另一件事是使用依赖属性,以便所有样式都可以在 XAML 中完成。这样,可以在设计器中预览自定义对象,同时动态更改属性,项目的典型条目如下所示:

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);
}
于 2021-09-07T20:55:27.337 回答