Search code examples
javascriptfabricjs

Fabric.js - Why does this svg have such poor quality upon importing into my canvas?


This is an svg image loaded onto the canvas. As you can tell, it looks very much like a raster image. To give a little more context, the light blue box is 1000px by 400px.

Raster image

Notice also the image of the IText object looks like a raster image instead of a vector image.

Raster text

Anyone know what's happening here and how to fix it?

---update---

Sorry, this took so long to get out... I got side tracked, but as requested:

<html>
    <head>
        <title>
            Debug Me
        </title>
        
        <!--jQuery CDN-->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

        <!--Path to fabric.js-->
        <script type='text/javascript' src="./CreekWareJava/fabric.js-3.6.3/dist/fabric.js"></script>

        <!--bootstrap related-->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.bundle.min.js" integrity="sha384-1CmrxMRARb6aLqgBO7yyAxTOQE2AKb9GfXnEo760AUcUmFx3ibVJJAzGytlQcNXd" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>

    </head>

<body>
    <div class="container">
        <div class="row">
            <div class="col" id="canvas_container">
                <canvas id="canvas">
                    Canvas is not supported by this browser! Please upgrade your browser.
                </canvas>
            </div>
        </div>
    </div>

    <style>

#canvas_container
{
    display: flex !important;
    justify-content: center !important;
}
canvas
{
    position: absolute !important;
    height: 100% !important;
    width: 100% !important;

    top: 50% !important;
    left: 50% !important;
    transform: translate(-50%, -50%) !important;
}
.canvas-container
{
    width: 1000px !important;
    height: 400px !important;
    border: 5px solid green !important;
    position: relative !important;
}

    </style>

    <script>

window.onload = function()
{
    canvas = new fabric.Canvas('canvas');

    // – Raman Nikitsenka's proposal
    // it shouldn't hurt to have it in here...
    fabric.Object.prototype.objectCaching = false;

    //Adding some text
    addText("Some text");
    addImage("http://fabricjs.com/assets/1.svg");

}

function addText(text)
{
    var canH = canvas.getHeight() / 2;
    var canW = canvas.getWidth() / 2;
    var text = new fabric.IText(text, { left: canW, top: canH, fill: "Black", textAlign: 'center' });
    text.originX = 'center';
    text.originY = 'center';
    canvas.add(text);
    canvas.bringToFront(text);
    canvas.requestRenderAll();
}

function addImage(source)
{
    fabric.loadSVGFromURL(source ,function(objects,options) {

    var loadedObjects = fabric.util.groupSVGElements(objects, options);

        loadedObjects.set({
            width: 200,
            height: 200
        });

        canvas.add(loadedObjects);
        canvas.renderAll();

    });
}

    </script>

</body>
</html>

This is the simplified code. As you can tell when you load it, both the svg and the text will come out very blurry. When you resize the dragon or the text, you can tell that it is trying to render it but doesn't do so properly.

Some additional observations:

fabric.js takes the initial canvas element and creates 2 new canvases, then wraps them in a div with a class called .canvas-container. I have a wrapper div for that container with id #canvas_container.

-- Update again... --

I added the following code to the window.onload eventlistener directly after the new fabric object...

for (var i = 0; i < 4; i++)
{
    canvas.setHeight(document.getElementById('canvas').height);
    canvas.setWidth(document.getElementById('canvas').width);
    canvas.requestRenderAll();
    console.log(i, "fw", canvas.width, "fh", canvas.height, "cw", document.getElementById("canvas").width, "ch", document.getElementById("canvas").height, "fr", canvas.width / canvas.height, "cr", document.getElementById("canvas").width / document.getElementById("canvas").height);
}

My observations from this are that the more I increase the counter for the loop, the better the image quality and text quality. On the other hand however, it will make the resize boxes and bounding box much thinner and decrease the physical size of the objects. The console will output fabric's canvas's width and height, then the physical html canvas element's width and height, followed by the ratios of each respectively. Thoughts?


Solution

  • So the problem is that fabric.js checks the <canvas> tag for a width and height attribute (not in the style - it might, also, check the style, but when I wrote this, I meant the height and width attributes) to initialize the new fabric canvas to, and if you don't include it in the tag, it won't render the canvas properly. To fix it, you can use

    canvas.setHeight(document.getElementById("your_canvas_id").style.height);
    canvas.requestRenderAll();
    

    and likewise for the width.

    Fabric.js changes my canvas size to 300x150 after initialization is another resource for this question.