5

So I was doing this in WinForms .NET 3.5... I am now using WPF .NET 4.0... and I cant figure out how to do it.

This is what I was doing in Windows .NET 3.5

using (Bitmap eventImg = new Bitmap("input.png"))
{
    Graphics eventGfx = Graphics.FromImage(eventImg);

    buildText(eventGfx, this.event1.Text);

    eventImg.Save("output.png", ImageFormat.Png);
    eventGfx.Dispose();
}

The above code took the existing image at "input.png", created a new image from it, wrote text from it and then saved the new image at "output.png". Text was written using the following function:

private void buildText(Graphics graphic, string text)
{
    if (text.Length == 0) { return; }

    FontStyle weight = FontStyle.Regular;

    switch (this.font_style)
    {
        case "regular":     weight = FontStyle.Regular;     break;
        case "bold":        weight = FontStyle.Bold;        break;
        case "italic":      weight = FontStyle.Italic;      break;
        case "underline":   weight = FontStyle.Underline;   break;
        case "strikeout":   weight = FontStyle.Strikeout;   break;
    }

    using (Font font = new Font(this.font_family, this.font_size, weight, GraphicsUnit.Pixel))
    {
        Rectangle rect = new Rectangle(this.left, this.top, this.width, this.height);
        Brush brush = new SolidBrush(Color.FromArgb(this.font_color));

        StringFormat format = new StringFormat();

        switch (this.align_x)
        {
            case "left":    format.Alignment = StringAlignment.Near;     break;
            case "right":   format.Alignment = StringAlignment.Far;      break;
            default:        format.Alignment = StringAlignment.Center;   break;
        }

        switch (this.align_y)
        {
            case "top":     format.LineAlignment = StringAlignment.Near;    break;
            case "bottom":  format.LineAlignment = StringAlignment.Far;     break;
            default:        format.LineAlignment = StringAlignment.Center;  break;
        }

        graphic.TextRenderingHint = TextRenderingHint.AntiAlias;
        graphic.DrawString(text, font, brush, rect, format);
    }
}

However, since System.Drawing does not exist in WPF .NET 4.0, I can't use these functions anymore. How would I do what I am trying to do in WPF .NET 4.0? I've gotten as far as the code below in order to do the first step of making an image based on the old image:

using (var fileStream = new FileStream(@"z:\ouput.png", FileMode.Create))
{
    BitmapEncoder encoder = new PngBitmapEncoder();
    encoder.Frames.Add(BitmapFrame.Create(new Uri(@"z:\input.png")));
    encoder.Save(fileStream);
}
Jason Axelrod
  • 7,155
  • 10
  • 50
  • 78
  • Based on the same thing in Silverlight, you would load the bitmap into an Image element that simply has a text element over it with your desired text, then render the parent container to a writable bitmap and save that. – iCollect.it Ltd Aug 10 '12 at 14:21
  • HiTech, can you please explain how to do that more thoroughly? This is my first time using WPF. – Jason Axelrod Aug 10 '12 at 14:24

3 Answers3

8

Having read answers and comments here I thought you might appreciate a more comprehensive solution. Here is a little method that does the job:

public static void WriteTextToImage(string inputFile, string outputFile, FormattedText text, Point position)
{
    BitmapImage bitmap = new BitmapImage(new Uri(inputFile)); // inputFile must be absolute path
    DrawingVisual visual = new DrawingVisual();

    using (DrawingContext dc = visual.RenderOpen())
    {
        dc.DrawImage(bitmap, new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
        dc.DrawText(text, position);
    }

    RenderTargetBitmap target = new RenderTargetBitmap(bitmap.PixelWidth, bitmap.PixelHeight,
                                                       bitmap.DpiX, bitmap.DpiY, PixelFormats.Default);
    target.Render(visual);

    BitmapEncoder encoder = null;

    switch (Path.GetExtension(outputFile))
    {
        case ".png":
            encoder = new PngBitmapEncoder();
            break;
        // more encoders here
    }

    if (encoder != null)
    {
        encoder.Frames.Add(BitmapFrame.Create(target));
        using (FileStream outputStream = new FileStream(outputFile, FileMode.Create))
        {
            encoder.Save(outputStream);
        }
    }
}

You would use this method with a FormattedText object and a position:

FormattedText text = new FormattedText(
    "Hello",
    CultureInfo.InvariantCulture,
    FlowDirection.LeftToRight,
    new Typeface("Segeo UI"),
    20,
    Brushes.Red);

WriteTextToImage(
    @"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg",
    "Desert.png",
    text,
    new Point(10, 10));

EDIT: If you want to draw the text horizontally and vertically aligned relative to a certain rectangle, you might replace the position parameter by that rectangle and two alignment parameters and calculate the text position like this:

public static void WriteTextToImage(string inputFile, string outputFile, FormattedText text,
    Rect textRect, HorizontalAlignment hAlign, VerticalAlignment vAlign)
{
    BitmapImage bitmap = new BitmapImage(new Uri(inputFile));
    DrawingVisual visual = new DrawingVisual();
    Point position = textRect.Location;

    switch (hAlign)
    {
        case HorizontalAlignment.Center:
            position.X += (textRect.Width - text.Width) / 2;
            break;
        case HorizontalAlignment.Right:
            position.X += textRect.Width - text.Width;
            break;
    }

    switch (vAlign)
    {
        case VerticalAlignment.Center:
            position.Y += (textRect.Height - text.Height) / 2;
            break;
        case VerticalAlignment.Bottom:
            position.Y += textRect.Height - text.Height;
            break;
    }

    using (DrawingContext dc = visual.RenderOpen())
    {
        dc.DrawImage(bitmap, new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight));
        dc.DrawText(text, position);
    }

    RenderTargetBitmap target = new RenderTargetBitmap(bitmap.PixelWidth, bitmap.PixelHeight,
                                                       bitmap.DpiX, bitmap.DpiY, PixelFormats.Default);
    target.Render(visual);

    BitmapEncoder encoder = null;

    switch (Path.GetExtension(outputFile))
    {
        case ".png":
            encoder = new PngBitmapEncoder();
            break;
        case ".jpg":
            encoder = new JpegBitmapEncoder();
            break;
    }

    if (encoder != null)
    {
        encoder.Frames.Add(BitmapFrame.Create(target));

        using (FileStream outputStream = new FileStream(outputFile, FileMode.Create))
        {
            encoder.Save(outputStream);
        }
    }
}

Now you might use the method like this:

WriteTextToImage(@"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg", "Desert.png", text,
    new Rect(80, 50, 430, 200),
    HorizontalAlignment.Center, VerticalAlignment.Center);
Clemens
  • 123,504
  • 12
  • 155
  • 268
  • Okay, this is pretty much what I was looking for... however, there is one fundamental issue. DrawText puts the text at a specific x,y coordinate with `new Point(x,y)`. The problem is that sometimes I have text centered within a rectangular area. Is there a way to draw the text to a rectangular area, and then aligning the text with vertical alignment and horizontal alignment? – Jason Axelrod Aug 10 '12 at 22:20
  • @JasonAxelrod Calculating the aligned text position is quite easy, since you know the size of the image and the size of the text. See my edited answer. – Clemens Aug 11 '12 at 05:45
  • I dont know, that math doesn't make sense to me... okay, lets say the image width is `1280`; I want to `center` align the text in a rectangular area starting at `80` (stored in a variable called `left`) pixels in from the left. The rectangular area continues for `430` (stored in a variable called `width`) pixels in width. How would I do the math to get the text center aligned within that rectangular area... right aligned? – Jason Axelrod Aug 11 '12 at 06:02
  • I'm wracking my brain trying to figure the math out... but it doesn't make sense to me. – Jason Axelrod Aug 11 '12 at 06:06
  • Alright, thanks, I actually figured out the math: `position.X = left + ((width - text.Width)/2);`... two final questions and I think I've got this all figured out. In WinForms .NET 3.5 I was able to do `graphic.TextRenderingHint = TextRenderingHint.AntiAlias;` to clean up the text and make it look nicer. As well, I was able to set `GraphicsUnit.Pixel` so that the size unit of the text was in pixels, instead of points or em. How do I do this stuff now? – Jason Axelrod Aug 11 '12 at 06:28
  • In WPF all rendering is anti-aliased by default. You might read [here](http://msdn.microsoft.com/en-us/library/aa970908(v=VS.90).aspx) about some details. WPF also does not have the notion of a "pixel", instead it calculates in device independent units, which are 1/96 of an inch. Since a pt is 1/72 of an inch, it's easy to transform from one to the other. – Clemens Aug 11 '12 at 06:42
  • Well the text is clearly not anti-aliased... How do I transform from em to px? (not pt). – Jason Axelrod Aug 11 '12 at 06:44
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/15206/discussion-between-jason-axelrod-and-clemens) – Jason Axelrod Aug 11 '12 at 06:45
  • 1
    Don't you think that it is time to do some research on your own? There are tons of documentation about WPF. And see [this SO question](http://stackoverflow.com/q/653918/1136211) about `em` size. – Clemens Aug 11 '12 at 06:53
  • This pointed me in the right direction. Unfortunatelly this algorithm loses image quality. I found [this post](http://stackoverflow.com/questions/11699219/save-an-image-as-a-bitmap-without-losing-quality/11699592#11699592), which uses a different approach without loosing quality. – danielQ Jun 11 '14 at 21:38
1

you can use grid or any other panel which suits your needs in the following way

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto" /> 
    </Grid.ColumnDefinitions>

    <Image x:name="My image" .. bind it to the bintmap 
           Grid.row="0"
           Grid.colomn="0"/>
    <TextBlock x:name="MyText"  
               Text="....."
                Grid.row="0"
                Grid.colomn="0"/>
</Grid>

the text and the image are drawn in the same space in the grid and you can manipulate the alignment anyway you like

makc
  • 2,569
  • 1
  • 18
  • 28
-4

In WPF you are not allowed to use Graphics because wpf offers new tec. to do that. Graphics referes to old Windows API's but WPF uses DirectX. As example WPF does not work anymore with pixels and so on.

http://msdn.microsoft.com/de-de/library/system.windows.media.drawingcontext.aspx

Florian
  • 5,918
  • 3
  • 47
  • 86