Basis compression of font png created from font asset

In the font asset inspector there is a process font button. I tried with a Japanese font and included about 2000 characters. The font works fine but the resulting font image is understandably big 4096 x 2048, and causes a 50MB of vram usage. I wondered if it is possible to somehow apply basis compression to this image?

1 Like

Good question :thinking: Chances are because data is encoded in the PNG RGBA channels via SDF that a lossy compression format like Basis would screw up the rendering of the text

@slimbuck can confirm but is away at the moment

OK cheers. I’ll follow up later.

Maybe @will has an idea too about this?

@RyutoHaga may have some approaches around the Japanese language to make it more efficient too with the UI

2 Likes

Yes, Basis compression will make a font look terrible unfortunately.

2 Likes

Astc basis compression could be an option. Though devices without astc support will fall back to rgba8.

1 Like

Yes, in the case of Japanese,
if you try to support all Japanese(Kanji) characters, the process will probably not be completed.

Here are a few approaches I use when using it in Japanese.

  1. I use Process Font for the minimum number of characters required.(It’s the easiest.)

  2. I also often use the HTML.(The end user can copy and paste.)


    Demo: https://playcanv.as/p/s7ptagcO?overlay=false
    Demo Project: https://playcanvas.com/editor/project/860734
    Tutorial: https://developer.playcanvas.com/ja/tutorials/?tags=html

  3. If I want to use the Element component, I generate the font texture dynamically.

Demo: https://playcanv.as/p/dcasNAoo/
Demo Project: https://playcanvas.com/editor/scene/1300675
Gist: https://gist.github.com/yushimatenjin/206ad29ad62fb18fb98cb658838357d9

class FontManager extends pc.ScriptType {
    get textElements() {
        const elements = this.app.root.findComponents("element");
        return elements || elements.map((element) => element.type === "text");
    }

    _canvasFontInit() {
        const chars = [];
        for (let element of this.textElements) {
            chars.push(element.text);
        }
        this._createTexture(chars.toString());
    }

    _createTexture(chars) {
        if (this.app.canvasFont) {
            this.app.canvasFont.destroy();
        }
        this.app.canvasFont = new pc.CanvasFont(pc.app, this.options);
        this.app.canvasFont.createTextures(chars);
        for (let element of this.textElements) {
            element.font = this.app.canvasFont;
        }
    }
    _setText(element, char) {
        if (this.app.canvasFont) {
            this.app.canvasFont.updateTextures(char);
            element.text = char;
        }
    }

    initialize() {
        this.app.canvasFont = null;
        this.options = {
            fontName: "Meiryo Arial Helvetica",
            fontSize: 128,
        };

        this.app.on("text:set", this._setText, this);
        this.on("attr", this._canvasFontInit, this);

        this._canvasFontInit();
    }
}

pc.registerScript(FontManager);

Usage (Faker.js).

const JapaneseRandom = pc.createScript('japaneseRandom');

JapaneseRandom.prototype.initialize = function () {
    setInterval(() => {
        faker.locale = "ja";

        const randomName = faker.lorem.word();
        this.app.fire("text:set", this.entity.element, randomName);
    }, 200);
};

2 Likes

@RyutoHaga Thanks for your detailed response. That is really helpful! For point 3 element components, I was able to use your FontManager. I will also checkout the pc.CanvasFont code later. Is the “Meiryo Arial Helvetica” font provided by the browser? Would it be possible to make the demo project for point 3 public, it’s giving 404?

1 Like

I’m sorry! I have now changed the project to Public !
Demo Project: https://playcanvas.com/editor/scene/1300675

We can use the font in browser or Google Fonts.

When using Google Fonts

  1. Use loadGoogleFonts.js in the project

  2. Change the value to the loaded font.(FontManager.js)

        this.options = {
            //  Before
            //// Load from Browser
            // fontName: "Meiryo",

            //  After
            // Load From Google Fonts
            fontName: "Dela Gothic One",
            fontSize: 128,
        };
1 Like

I used PlayCanvas to process all Japanese fonts. Textures of 4096x4096 were generated, with a file size of 10MB to 20MB.

The project can be found here:
https://playcanvas.com/editor/scene/1678016

The Japanese data can be found here:
https://gist.github.com/yushimatenjin/26b1a8a2223c09a2bd65341bec19fa6f

We tried to see if we could reduce the size when using Japanese:

1. Original


6.9MB + 6.8MB = 13.7MB

2. Basis-compressed( ETC / Default ) textures ( I downloaded the contents of Basis compression in the editor locally and replaced it. )


1.4MB + 1.4MB = 2.8MB

When the extension was Basis, the display was corrupted because one or more texture files were not used (not incremented).

The result of rewriting this code locally to Basis is shown here.

3. I used an online compression service to compress png files.


https://compresspng.com/ja/
3.0MB + 2.9MB = 5.9MB

4. Resized from 4096 to 2048, then compressed png.


1.3MB + 1.2MB = 2.5MB

I was wondering if a font texture resized to 2048 pixels could be used in some cases. If possible, it would be nice to have an option to select the texture size in the editor, since the file size becomes very large when trying to handle Japanese textures, making it difficult to use on the Web.

2 Likes
  1. I would ensure that Normals is enabled as that ensures the the alpha channel is kept lossless since font files use all 4 channels. I think that would increase the quality of the font/text. It will increase the file size but you would get the benefits of using less VRAM.

If download size is an issue, I would consider using WebP lossless instead of PNG which would could save 30-50% off the file size. It just doesn’t work on iOS 13/Safari 13 and lower WebP image format | Can I use... Support tables for HTML5, CSS3, etc

1 Like

Added a feature request here: Set the size of the font texture asset generated · Issue #999 · playcanvas/editor · GitHub

2 Likes

Thank you for sharing your approach for WebP! I have created a tool to convert to WebP format.

To use it, simply execute playcanvas-cli webp or npx playcanvas-tools webp in the downloaded project. This will create a config_webp.json file (if using the default settings), which you can then add to the __settings.js__ file by specifying the new path.

After using this tool on the entire project, the 33.29MB texture was reduced to approximately 3.55MB (89%). While I believe that further testing is necessary before using this tool in production, I found it to be very effective.

Repository: GitHub - yushimatenjin/playcanvas-cli: The PlayCanvas Command Line Tools.

1 Like

It looks like you are converting fonts with lossy compression. That may cause rendering issues because the font is SDF and therefore the values in the colours of the pixels are important.

1 Like

Hi, @RyutoHaga. I appreciate to share your cool project.
I forked this project - PlayCanvas | HTML5 Game Engine

But, I have one minor problem. I want to choice font style in the TextElement.
Currently, Only one font that ‘Noto Sans Kor’ is applies to all TextElement. How can I fix this?
My project - PlayCanvas 3D HTML5 Game Engine

Hello!

From my research, Canvas Font and Font assets are separate, so I believe it is not possible to configure a Text Element to use them. I think the only values that can be set from the editor are the text color and font size.

Engine: renderCharacter

I know one way to change the style. This is to add a script that overrides the Engine like this.However, this method may cause bugs and is not recommended. I would appreciate it if someone more knowledgeable in the community could provide guidance.

// override-render-character.js
window.onload = () => {
    // https://github.com/playcanvas/engine/blob/168d59f4bba51e0f0945a8aa754ff08d59577e8b/src/framework/font/canvas-font.js
    /**
     * @param {CanvasRenderingContext2D} context - The canvas 2D context.
     * @param {string} char - The character to render.
     * @param {number} x - The x position to render the character at.
     * @param {number} y - The y position to render the character at.
     * @param {number} color - The color to render the character in.
     * @ignore
     */
    pc.CanvasFont.prototype.renderCharacter = function (context, char, x, y, color) {
        context.fillStyle = color;
        context.fillText(char, x, y);

        // Shadow
        context.shadowColor = 'rgba(0, 0, 0, 0.5)';
        context.shadowBlur = 4;
        context.shadowOffsetX = 3;
        context.shadowOffsetY = 3;

        // Gradient 
        const gradient = context.createLinearGradient(0, 0, context.canvas.width, 0);
        gradient.addColorStop(0, 'cyan');
        gradient.addColorStop(0.5, 'magenta');
        gradient.addColorStop(1.0, 'yellow');
        context.strokeStyle = gradient;
        context.lineWidth = 2;
        context.strokeText(char, x, y);

        context.strokeStyle = 'lime';
        context.lineWidth = 1;
        context.setLineDash([4, 4]);
        context.strokeText(char, x, y);
        context.setLineDash([]);
    };
};

Forked Project - PlayCanvas 3D HTML5 Game Engine

I find controlling CanvasFont a bit difficult, so I would like to use a regular Font asset. However, the large number of characters still results in a huge file size. So I’m currently considering whether it is possible to reduce the file size of Japanese fonts using something like WebP.

1 Like

Thank you! I had been using lossy compression, which introduced noise into the font. When I switched to lossless compression, it worked beautifully in several sample projects!

I apologize for sending another issue in quick succession.

To use WebP, there was one issue: when two font textures are generated, only the first texture is loaded. Since this deviates from the normal workflow, it seems that the current engine does not load fonts other than PNG.

Capture

Here are the file size and structure after converting to webp with the lossless option.

Repository: GitHub - yushimatenjin/playcanvas-webp-font-test: https://yushimatenjin.github.io/playcanvas-webp-font-test/webp.html

Execution link
png(original)
https://yushimatenjin.github.io/playcanvas-webp-font-test/png.html

webp (after conversion)
https://yushimatenjin.github.io/playcanvas-webp-font-test/webp.html

Hm, short term there’s little you can do besides to patch the engine for FontHandler to use WebP extension instead :thinking:

Until WebP is officially supported, we can’t really make changes here.

1 Like

For now, I’ll remember that it’s one possible option to use if you deviate from the normal PlayCanvas workflow. It was great to be able to test several methods, such as Basis and WebP. Thank you very much for all the advice you gave me!