skip to Main Content

I have got a WPF application, and I would like to save a Canvas to a file with a correct DPI value. The Canvas size is the real physical size, eg. 20×10 cm, at 300 DPI, so it’s 2362×1181.

I draw images and shapes with DrawingVisual, then I create a RenderTargetBitmap. The size of the rtb is the size of the Canvas. The dpiX and dpiY are 96. I got correct image resolution only with 96 DPI. When I set it to 300, the canvas become upscale, and cropped to 2362×1181. So, it’s not good. I tried to modify the canvas width and height value by dpi factor, but didn’t work either.

After the RenderTargetBitmap, I use BitmapEncoder, BitmapFrame, and BinaryWriter. See code below. Working great, but the image DPI value will be 96. I’ve read a tons of topics about reading DPI, resaving image, using SetResolution, but I don’t want to loose quality, I don’t want to read file and resave it, I don’t want to change pixel width/height, etc.

I just really want to save a DrawingVisual with a given DPI. I could write EXIF data for “X Resolution” (uint=282) and “Y Resoulution” (uint=283), but that didn’t affect the Image DPI settings, so eg. Photoshop will read 96, not 300.

BitmapEncoder encoder = new JpegBitmapEncoder();
BitmapFrame bFrame = BitmapFrame.Create(rtb, null, meta, icc);
encoder.Frames.Add(bFrame);

using (var stream = new MemoryStream())
{
encoder.Save(stream);
byte[] imageData = stream.ToArray();
using (FileStream fs = new FileStream(path, ...)
{
BinaryWriter bw = new BinaryWriter(fs);
bw.Write(imageData);
bw.Close();
}
}

3

Answers


  1. Chosen as BEST ANSWER

    I tried to apply DPI scaling to the DrawingVisual, but it produced less image quality. :( Also I tried to set the canvas to 756x378 (the 96 DPI size), then set the RenderTargetBitmap to 2362x1181 and 300 DPI, but that produced less image quality too. So, it seems any downscale then upscale or upscale then downscale are not the best solution. The DrawingVisual must be in the final render size.


  2. The following method saves a UIElement to a JPEG file with the specified DPI value:

    public static void SaveElement(UIElement element, double dpi, string path)
    {
        var visual = new DrawingVisual();
        var width = element.RenderSize.Width;
        var height = element.RenderSize.Height;
    
        using (var context = visual.RenderOpen())
        {
            context.DrawRectangle(new VisualBrush(element), null,
                new Rect(0, 0, width, height));
        }
    
        var bitmap = new RenderTargetBitmap(
            (int)(width * dpi / 96), (int)(height * dpi / 96),
            dpi, dpi, PixelFormats.Default);
        bitmap.Render(visual);
    
        var encocer = new JpegBitmapEncoder();
        encocer.Frames.Add(BitmapFrame.Create(bitmap));
    
        using (var stream = File.OpenWrite(path))
        {
            encocer.Save(stream);
        }
    }
    

    Alternatively, apply the DPI scaling to the DrawingVisual:

    public static void SaveElement(UIElement element, double dpi, string path)
    {
        var visual = new DrawingVisual();
        var width = element.RenderSize.Width;
        var height = element.RenderSize.Height;
    
        using (var context = visual.RenderOpen())
        {
            context.DrawRectangle(new VisualBrush(element), null,
                new Rect(0, 0, width / dpi * 96, height / dpi * 96));
        }
    
        var bitmap = new RenderTargetBitmap(
            (int)width, (int)height, dpi, dpi, PixelFormats.Default);
        bitmap.Render(visual);
    
        var encocer = new JpegBitmapEncoder();
        encocer.Frames.Add(BitmapFrame.Create(bitmap));
    
        using (var stream = File.OpenWrite(path))
        {
            encocer.Save(stream);
        }
    }
    
    Login or Signup to reply.
  3. It seems the solution is the Bitmap SetResolution, after all. I tested it, and it looks like not affect the image quality after the JpegBitmapEncoder()! And the image resolution is untouched, keep the metadata, only the DPI will change!

    Helped this documentation: https://learn.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-set-jpeg-compression-level

    JpegBitmapEncoder encoder = new JpegBitmapEncoder();
    encoder.QualityLevel = 100;
    BitmapFrame bFrame = BitmapFrame.Create(rtb, null, meta, icc);
    encoder.Frames.Add(bFrame);
    
    using (var stream = new MemoryStream())
    {
        encoder.Save(stream);
    
        using (var bmpOutput = new System.Drawing.Bitmap(stream))
        {
            System.Drawing.Imaging.ImageCodecInfo myEncoder = GetEncoder(ImageFormat.Jpeg); 
    
            var encoderParameters = new System.Drawing.Imaging.EncoderParameters(1);
            encoderParameters.Param[0] = new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 100L);
    
            bmpOutput.SetResolution(300.0f, 300.0f);
            bmpOutput.Save(filePath, myEncoder, encoderParameters);
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search