1

回転したテキストを作成し、その画像を PNG ファイルに保存しようとしています。結果の PNG は、必要以上に大きくしないでください (または最小限のパディング)。回転がない限り機能していますが、テキストを回転させるとすぐに、ファイル内で切り取られます。RotateTransform の CenterX と CenterY を調整するか、TranslateTransform を作成することと関係があると確信していますが、それを正しく行う方法について何も見つけることができず、試行錯誤のテストが試行錯誤になりました-そしてフラストレーション

私のサンプルコードは以下です。-45度だけでなく、任意の角度で機能するソリューションを探しています。

最後に、誰かがこれらの要件を満たす方法を知っていても、WPF ツールの代わりに「古いスタイル」の Graphics オブジェクトを使用すると言う場合、私はそれに対する解決策も受け入れます。

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>

ご覧のとおり、RotateTransformと の両方を使用RenderTransformOriginし、結果は下の画像のようになります。そして、ご覧のとおり、テキストはの真ん中を通っていませCanvas。そして、それが私の全体的な問題のようです。テキストを回転させ正しく中央に配置する方法。

例


更新 2

今回は a のGrid代わりに aを試してみることにしました。テキストをグリッドの中央に適切に配置できるようになりましたが、オブジェクトを使用できないため、実際の境界ボックスを測定できないようです。または のすべての測定値は、まったく回転していないかのように戻ります ( 、、および を参照)。回転したバウンディング ボックスのサイズを取得できない場合、PNG をクリップせずに保存することはできません。CanvasFormattedTextTextBlockGridActualWidthActualHeightDesiredSize

ああ、回転していないグリッドでテキストを回転させ、グリッド自体を回転させてみましたが、寸法を決定しようとすると、どちらも同じ結果になります。

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 で見つかりました: 回転後の新しい座標の取得

于 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ます。次に、コントロール領域全体を背景色でペイントしてレンダリングを開始します。主なことは、回転したテキストの形状を測定する必要はなく、回転していないテキストのみを測定する必要があるということです。最終的なテキストをコントロールの中心以外の場所に配置する場合は、回転の中心がテキスト オフセットと同じ基準点であることを確認してください。

私が通常行うもう 1 つのことは、依存関係プロパティを使用して、すべてのスタイリングを 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 に答える