ForNeVeR / xaml-math

A collection of .NET libraries for rendering mathematical formulae using the LaTeX typesetting style, for the WPF and Avalonia XAML-based frameworks
MIT License
637 stars 100 forks source link

Padding and Background #417

Open seghier opened 1 year ago

seghier commented 1 year ago

Hi The padding area remain transparent , is there a solution for that? image

ForNeVeR commented 1 year ago

What's the input formula? What do you mean by the "padding area"?

seghier commented 1 year ago

I mean this (space), you called it (x,y)

var pngBytes = latexformula.RenderToPng(40.0, space, space, "Arial");

The background color applied to the bounds of the png and the rest of image remain transparent.

Any formula does not matter: \matrix{1 & 2 & 3 \\ 4 & 5 & 6}

ForNeVeR commented 1 year ago

And you set the background by calling Box.SetBackground, right?

seghier commented 1 year ago

I use this way:

using System.IO;
using WpfMath.Parsers;
using WpfMath;
using System.Windows.Media;
using XamlMath.Exceptions;

private void ConvertLatex(string formula, string fileName, System.Drawing.Color textClr, System.Drawing.Color backClr, double scale, double space)
try
    {
      var parser = WpfTeXFormulaParser.Instance;
      var latexformula = parser.Parse(formula);

      var mediaTextClr = System.Windows.Media.Color.FromRgb(textClr.R, textClr.G, textClr.B);
      var textsolidbrush = new System.Windows.Media.SolidColorBrush(mediaTextClr);
      var textBrush = WpfMath.Rendering.WpfBrush.FromBrush(textsolidbrush);
      latexformula.SetForeground(textBrush);

      var mediaBackClr = System.Windows.Media.Color.FromArgb(backClr.A, backClr.R, backClr.G, backClr.B);
      var backsolidbrush = new System.Windows.Media.SolidColorBrush(mediaBackClr);
      var backBrush = WpfMath.Rendering.WpfBrush.FromBrush(backsolidbrush);
      latexformula.SetBackground(backBrush);

      var pngBytes = latexformula.RenderToPng(scale, space, space, "Arial");
      File.WriteAllBytes(fileName, pngBytes);
    catch (TexException e)
    {
      Error = "Error when parsing formula: " + e.Message;
    }
ForNeVeR commented 1 year ago

First things first, for further investigation, here's a full snipped I was able to somewhat reproduce the issue with:

    private void DoTest()
    {
        var parser = WpfTeXFormulaParser.Instance;
        var latexformula = parser.Parse(@"\matrix{1 & 2 & 3 \\ 4 & 5 & 6}");

        var mediaTextClr = System.Windows.Media.Color.FromRgb(0, 0, 0);
        var textsolidbrush = new System.Windows.Media.SolidColorBrush(mediaTextClr);
        var textBrush = WpfMath.Rendering.WpfBrush.FromBrush(textsolidbrush);
        latexformula.SetForeground(textBrush);

        var mediaBackClr = System.Windows.Media.Color.FromArgb(0xff, 0xff, 0, 0);
        var backsolidbrush = new System.Windows.Media.SolidColorBrush(mediaBackClr);
        var backBrush = WpfMath.Rendering.WpfBrush.FromBrush(backsolidbrush);
        latexformula.SetBackground(backBrush);

        var pngBytes = latexformula.RenderToPng(100, 10, 10, "Arial");
        File.WriteAllBytes(@"T:\Temp\foo.png", pngBytes);
    }

For whatever reason, this adds a padding of about 10 transparent pixels around the whole image.

(while I'd expect that to only move it from the top left corner by 10 px, and not add padding around everything)

Notably, even after replacing latexformula.RenderToPng(100, 10, 10, "Arial") with latexformula.RenderToPng(100, 0, 0, "Arial"), I didn't get a clean image, see this:

image

There's apparently one transparent pixel to the right and one semi-transparent to the bottom.

The core reason is that the image consists of various primitives, and SetBackground only changes the color of the root primitive, and not of the whole image.

Ideally, the bounding box of the root primitive would be the same as the whole image (well, without this weird padding), but in reality apparently it is not.

So, my first advice is to remove the padding from your call to RenderToPng, that should make it significantly better.

I have an idea how to make it even better, but it would be much easier if we provided a method requested in #289.

My idea is to just create a canvas filled with the background color, and then add the result generated by WPF-Math to it. This is doable, but I've tried to write some code using a (horrible) WriteableBitmap WPF API, and I am sorry but I failed. It's already 23:00 in my time zone, I am very tired, and so you'll have to do it yourself for now if that's required :(

I am leaving the issue open because we'll need to think on how to improve the API to provide better background handling. And also, yeah, implement the API for #289.

seghier commented 1 year ago

Thank you very much fo you work and effort I tried to create images using this way to save: jpg, bmp which add white background automatically

using(Image image = Image.FromStream(new MemoryStream(pngBytes)))
        {
          image.Save(fileName, imageFormats[fileType]);
        }

image

And the new update works well now

https://github.com/ForNeVeR/xaml-math/assets/6026588/17e27ffa-c73b-48b9-b2eb-77ac527fdfcd

ygra commented 1 year ago

My idea is to just create a canvas filled with the background color, and then add the result generated by WPF-Math to it. This is doable, but I've tried to write some code using a (horrible) WriteableBitmap WPF API, and I am sorry but I failed.

Having semi-recently rewritten the image export code (using WriteableBitmap) at work to work properly, I could take a look if you want. I would expect things based on a Drawing to be significantly less problematic in practice than a few thousand FrameworkElements.

seghier commented 1 year ago

Hi I use this way to fill the trasnparent area and it work fine

Bitmap CreateImage(string strLatex, double scale, double space, System.Drawing.Color tcolor, System.Drawing.Color bcolor)
  {
    Bitmap bmp;

    var parser = WpfTeXFormulaParser.Instance;
    var latexformula = parser.Parse(strLatex);

    var mediaTextClr = System.Windows.Media.Color.FromRgb(tcolor.R, tcolor.G, tcolor.B);
    var textsolidbrush = new System.Windows.Media.SolidColorBrush(mediaTextClr);
    var textBrush = WpfMath.Rendering.WpfBrush.FromBrush(textsolidbrush);
    latexformula.SetForeground(textBrush);

    var mediaBackClr = System.Windows.Media.Color.FromArgb(bcolor.A, bcolor.R, bcolor.G, bcolor.B);
    var backsolidbrush = new System.Windows.Media.SolidColorBrush(mediaBackClr);
    var backBrush = WpfMath.Rendering.WpfBrush.FromBrush(backsolidbrush);
    latexformula.SetBackground(backBrush);

    var pngBytes = latexformula.RenderToPng(scale, space, space, "Arial");

    using (var ms = new MemoryStream(pngBytes))
    {
      bmp = new Bitmap(ms);
    }

    Bitmap img = new Bitmap(bmp.Width, bmp.Height);

    img.SetResolution(bmp.HorizontalResolution, bmp.VerticalResolution);
    using (var g = Graphics.FromImage(img))
    {
      g.Clear(bcolor);
      g.DrawImageUnscaled(bmp, 0, 0);
    }

    return img;
  }

https://github.com/ForNeVeR/xaml-math/assets/6026588/5fdba4db-f59e-4ee9-b3bf-4ad605caaed7

ForNeVeR commented 1 year ago

Yep, folks, that's what I meant. I am still unsure on how to better integrate this with the rest of XAML-Math API. Maybe an additional parameter of RenderToPng? But we'd still have a (somewhat) defunct SetBackground exposed.

Also, while it's totally fine for your end-user app, I'd like to avoid, if possible, using System.Drawing in WPF-related code. Not sure of the current state of things on that matter, but some time ago System.Drawing (together with WinForms) was a separate thing from WPF on the SDK level, and you'd get in some small problems if you reference that from a WPF project without also defining <UseWinForms>true</UseWinForms>.

It may be already fixed, or maybe I am misremembering, but still: conceptually, it would be better to stay in pure WPF (in a WPF-related part of the library, of course; again, not talking about an end-user app where the user is free to do whatever they choose).