0

My program is sort of copy version of MS paint and Pickpick. and one of features is rasterizing the selected object such as textblock or shape.

Regarding the selectable object, in order to resize and move with adorner, it has 1 ContentControl which comprise 1 textblock + 1 shape.

ContentControl (able to resize, rotate, move)
└─> Textblock (bold, italic, V-align, H-align, word wrap...)
└─> Shape (can be a triangle, rectangle etc...)

Like this

It was not hard to convert to draw the shape with drawing context instead of render at Canvas.

var SH = CC.GetShape();
var TB = CC.GetTextBlock();
var visual = new DrawingVisual();

Geometry geo = null;
System.Windows.Media.Pen pen = null;
System.Windows.Media.Brush brush = null;
if (SH != null)
{
    geo = SH.RenderedGeometry;  // shape to geo
    if (geo == null)
        return;
    pen = new System.Windows.Media.Pen(SH.Stroke, SH.StrokeThickness);
    brush = SH.Fill;
}

using (var dc = visual.RenderOpen())
{
    // Draw the background first
    dc.DrawImage(first, new Rect(0, 0, first.Width, first.Height));
    dc.PushTransform(new TranslateTransform(left, top));
    // Draw the shape
    if (SH != null && geo != null)
        dc.DrawGeometry(brush, pen, geo);
}

But while drawing Textblock with drawing context, I've referred below link to calculate the position of Textblock Vertical alignment with DrawingContext.DrawText
but the problem is when the Textblock has multiline or word wrapped.
screenshot of my program enter image description here

 if (TB.Text.Equals(string.Empty) == false)
 {
      var typeface = new Typeface(CC.txtSetting.fontFamily,
      CC.txtSetting.fontStyle,
      CC.txtSetting.fontWeight,
      FontStretches.Normal);

      var formattedText = new FormattedText(TB.Text
                 , CultureInfo.CurrentCulture
                 , FlowDirection.LeftToRight
                 , typeface
                 , CC.txtSetting.fontSize
                 , new SolidColorBrush(CC.txtSetting.fontColor));

      double centerX = CC.ActualWidth / 2;
      double centerY = CC.ActualHeight / 2;

      double txtPositionX = 0.0f;
      double txtPositionY = 0.0f;
      if (TB.TextAlignment == TextAlignment.Left)
      {
          txtPositionX = 1.0f;
      }
      else if (TB.TextAlignment == TextAlignment.Center)
      {
          txtPositionX = centerX - formattedText.WidthIncludingTrailingWhitespace / 2;
      }
      else if (TB.TextAlignment == TextAlignment.Right)
      {
          txtPositionX = CC.Width - 
formattedText.WidthIncludingTrailingWhitespace - 1.0f;
      }

      if (TB.VerticalAlignment == VerticalAlignment.Top)
      {
          txtPositionY = 1.0f;
      }
      else if (TB.VerticalAlignment == VerticalAlignment.Center)
      {
          txtPositionY = centerY - formattedText.Height / 2;
      }
      else if (TB.VerticalAlignment == VerticalAlignment.Bottom)
      {
          txtPositionY = CC.Height - formattedText.Height - 1.0f;
      }

      var ptLocation = new System.Windows.Point(txtPositionX, txtPositionY);
                dc.DrawText(formattedText, ptLocation);
  }

Additionally, the textblock is wrapped by ContentControl so depending on user change the property of textblock, it will vary so much. I guess it seems not possible to convert every variable. So, I'm thinking alternative ways to draw.

  1. Draw with GDI+ instead of drawing with drawing context. (still uncertain)
  2. Use drawing context while the user is editing the text. (so it'll be the same before rasterizing and vice-versa)
  3. Any way to directly convert/capture the Textblock into an image or Geometry? (it would be the best way if it's possible.) For example, to get a shader effect applied image source, I did like this. so.. probably there's the way. How can I get the object of effect-applied source

You can also refer to this program from http://ngwin.com/picpick
screenshot of picpick enter image description here

Any better ideas? Thank you in advance.

Mark Choi
  • 420
  • 6
  • 16

1 Answers1

0

I made it!
I could capture the particular control with RenderTargetBimap. Since ContentControl is a part of Visual Element.
CustomControl is inherited control from ContentControl.

    public static BitmapSource ControlToBitmap(CustomControl control)
    {
        int W = (int)control.ActualWidth;
        int H = (int)control.ActualHeight;
        RenderTargetBitmap renderBitmap = new RenderTargetBitmap(
         W, H,
         96d, 96d, PixelFormats.Pbgra32);

        // needed otherwise the image output is black
        control.Measure(new System.Windows.Size(W, H));
        control.Arrange(new Rect(new System.Windows.Size(W, H)));

        renderBitmap.Render(control);

        var BS = RenderTargetBitmapToBitmap(renderBitmap);
        return BS;
    }

the wrong behaviour to be fixed
Additionally, I had to deal with the angle. Because I couldn't capture the angled control directly. but my idea is

  1. back up the angle value first.
  2. And restore the control to be non-rotated(RotateTransform = 0.0)
  3. Capture a non-rotated control to a bitmap.
  4. Then rotate the captured bitmap again.
  5. Combine both bitmaps into one.

    public static void OverlayControl(ImageSource first, CustomControl CC)
    {
        if (CC == null)
            return;
    
        var visual = new DrawingVisual();
    
        double left = Canvas.GetLeft(CC);
        double top = Canvas.GetTop(CC);
    
        // Get control's angle.
        double rotationInDegrees = 0.0f;
        RotateTransform rotation = CC.RenderTransform as RotateTransform;
        if (rotation != null) // Make sure the transform is actually a RotateTransform
        {
            rotationInDegrees = rotation.Angle; // back up this to temp var.
            rotation.Angle = 0.0f; // Set this to 0.0 to capture properly.
        }
    
        var second = ControlToBitmap(CC);
    
    
        using (var dc = visual.RenderOpen())
        {
            // Draw the background image frist.
            dc.DrawImage(first, new Rect(0, 0, first.Width, first.Height));
    
            // Push angle if the control has rotated.
            if (rotationInDegrees != 0.0f)
                dc.PushTransform(new RotateTransform(rotationInDegrees, left + (CC.Width / 2), top + (CC.Height / 2)));
    
            // transfrom as much as control moved from the origin.
            dc.PushTransform(new TranslateTransform(left, top));
            // Draw the second image. (captured image from the control)
            dc.DrawImage(second, new Rect(0, 0, second.Width, second.Height));
    
            // pop transforms
            dc.Pop();
        }
    
        var rtb = new RenderTargetBitmap((int)first.Width, (int)first.Height,
                                    96, 96, PixelFormats.Default);
        rtb.Render(visual);
        // Set as a one combined image.
        MainWindow.VM.RenderedImage = rtb;
    }
    

Now, everything seems alright. enter image description here

Mark Choi
  • 420
  • 6
  • 16