Skip to content
This repository has been archived by the owner. It is now read-only.

Reduce color fringes in FreeType subpixel rendering #229

Closed
jgneff opened this issue Sep 30, 2018 · 16 comments
Closed

Reduce color fringes in FreeType subpixel rendering #229

jgneff opened this issue Sep 30, 2018 · 16 comments
Labels
bug

Comments

@jgneff
Copy link
Contributor

@jgneff jgneff commented Sep 30, 2018

Report Classification

Issue Type: Bug
Component: JavaFX
Subcomponent: graphics: JavaFX Graphics
Operating System: Ubuntu
Java Release: 11
Regression: none
Frequency: Always

Report Details

Synopsis

Reduce color fringes in FreeType subpixel rendering

Description

The text rendered on Linux by JavaFX has severe color fringes because it fails to set the FreeType LCD filter.

The graphics module attempts to set the default LCD filter in FTFactory.java, shown below. Setting the filter is crucial. If the function returns an error, JavaFX disables subpixel rendering and reverts to grayscale anti-aliasing.

javafx.graphics/src/main/java/com/sun/javafx/font/freetype/FTFactory.java

/* This implementation only supports LCD if freetype has support. */
error = OSFreetype.FT_Library_SetLcdFilter(library, OSFreetype.FT_LCD_FILTER_DEFAULT);
LCD_SUPPORT = error == 0;

Yet its implementation in freetype.c, shown below, has a deceptive bug causing Linux users to see unfiltered subpixel rendering, while those working on the JavaFX FreeType support are unable to see the problem on their development workstations. On Linux systems with a default installation, the function fails silently and returns success (0) because the FreeType library is not found.

javafx.graphics/src/main/native-font/freetype.c

#define LIB_FREETYPE "libfreetype.so"
JNIEXPORT jint JNICALL OS_NATIVE(FT_1Library_1SetLcdFilter)
    (JNIEnv *env, jclass that, jlong arg0, jint arg1)
{
//  return (jint)FT_Library_SetLcdFilter((FT_Library)arg0, (FT_LcdFilter)arg1);
    static void *fp = NULL;
    if (!fp) {
        void* handle = dlopen(LIB_FREETYPE, RTLD_LAZY);
        if (handle) fp = dlsym(handle, "FT_Library_SetLcdFilter");
    }
    jint rc = 0;
    if (fp) {
        rc = (jint)((jint (*)(jlong, jint))fp)(arg0, arg1);
    }
    return rc;
}

There are two problems, with two alternative solutions:

  1. The runtime reference to the shared object is the versioned file name libfreetype.so.6, installed from libfreetype6. The name libfreetype.so used above is the compilation file name, also called the linker name, installed from libfreetype6-dev. This development package, libfreetype6-dev, contains the supplementary files needed to develop programs using the FreeType library and is not installed by default.

    One solution, therefore, is to add ".6" to the end of the LIB_FREETYPE string.

  2. The feature detection is no longer necessary. FreeType added the function in version 2.3.0, released on January 17, 2007. The initial support for FreeType was added to JavaFX on July 5, 2013, but we now have more than a decade of support in FreeType for setting the LCD filter.

    So another solution is to call the function directly.

I propose the second solution, replacing the native function with:

JNIEXPORT jint JNICALL OS_NATIVE(FT_1Library_1SetLcdFilter)
    (JNIEnv *env, jclass that, jlong arg0, jint arg1)
{
    return (jint)FT_Library_SetLcdFilter((FT_Library)arg0, (FT_LcdFilter)arg1);
}

System / OS / Java Runtime Information

I produced the problem on a QEMU/KVM virtual machine guest with 4 GB of RAM running on a Dell Precision Tower 3420 workstation host with 16 GB of RAM and a 4-core 3.30 GHz Intel Xeon Processor E3-1225 v5. This is a 64-bit Intel guest machine running Ubuntu 18.04.1 LTS (Bionic Beaver) with:

$ uname -a
Linux bionic 4.15.0-34-generic #37-Ubuntu SMP
Mon Aug 27 15:21:48 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

$ ldd --version
ldd (Ubuntu GLIBC 2.27-3ubuntu1) 2.27

$ getconf GNU_LIBPTHREAD_VERSION
NPTL 2.27

$ $HOME/opt/jdk-11/bin/java -version
openjdk version "11" 2018-09-25
OpenJDK Runtime Environment 18.9 (build 11+28)
OpenJDK 64-Bit Server VM 18.9 (build 11+28, mixed mode)

The display is a 27-inch Dell UltraSharp U2717D monitor with a resolution of 2560 × 1440 pixels at 109 pixels per inch and a one-to-one ratio of device pixels to logical pixels.

Reproduce the Issue

Steps to Reproduce

The easiest way to reproduce the problem is to run the Ensemble JavaFX application available in the JDK 8 Demos and Samples download. The application is packaged as the file Ensemble8.jar. Make sure that you have not installed the libfreetype6-dev package, and remove it if it's installed. It is not installed by default, and you should not find the file libfreetype.so anywhere on the system.

I ran the application with the Bash script:

#!/bin/bash
# Run the Ensemble JavaFX application
$HOME/opt/jdk-11/bin/java \
    --module-path $HOME/lib \
    --add-modules javafx.controls \
    -jar $HOME/opt/jdk1.8.0_181/demo/javafx_samples/Ensemble8.jar

where $HOME\lib contains the OpenJFX modules built from the latest sources on September 28, 2018:

javafx.base-linux.jar
javafx.controls-linux.jar
javafx.fxml-linux.jar
javafx.graphics-linux.jar
javafx.media-linux.jar
javafx.swing-linux.jar
javafx.web-linux.jar

When the Ensemble application starts, click the menu icon (☰) in the tool bar to see a list of JavaFX samples.

Expected Results

The text in the list appears as subpixel-rendered glyphs with no visible rendering artifacts, as shown below.

Text rendered with the default LCD filter. Screenshot. (02-lcddefault)

The following image shows a detail cropped from the image above and scaled by 800 percent.

Enlarged detail showing the word "Animation". (8x02-lcddefault)

Actual Result

The text in the list appears as glyphs with severe color fringes, as shown below. You should be able to see the color fringes in the image if you view it on a display with (1) a common pixel geometry of horizontal RGB or BGR stripes, and (2) a conventional resolution of 96 to 120 pixels per inch.

Text rendered without an LCD filter. Screenshot. (01-lcdnone)

The following image shows a detail cropped from the image above and scaled by 800 percent.

Enlarged detail showing the word "Animation". (8x01-lcdnone)

Source code for an executable test case

I can provide a separate test case, if necessary, but the Ensemble application in the JDK 8 Demos and Samples download can produce the problem directly with no compiling required.

Workaround

The workaround is to install the package containing the FreeType development files as follows:

$ sudo apt-get install libfreetype6-dev

This package is normally installed only by developers who compile and build software, like OpenJFX, that uses the FreeType library.

@prrace
Copy link

@prrace prrace commented Oct 1, 2018

On the JBS side this can be tracked via https://bugs.openjdk.java.net/browse/JDK-8188810

Kevin has confirmed our build machines .. even the older ones .. all have this symbol defined in the freetype library, so a fix can just use the symbol directly.

@kevinrushforth kevinrushforth added the bug label Oct 2, 2018
@kevinrushforth
Copy link
Collaborator

@kevinrushforth kevinrushforth commented Oct 2, 2018

@jgneff would you like to contribute a fix for this issue? If so, you can go ahead and create a pull request (please reference the JBS bug ID that Phil mentioned above).

@jgneff
Copy link
Contributor Author

@jgneff jgneff commented Oct 2, 2018

Thank you, Phil and Kevin, for looking into this issue so quickly! A pull request is on its way shortly.

Regarding the support in Ubuntu, I downloaded a few old releases yesterday and found the FT_Library_SetLcdFilter function at least as far back as Ubuntu 8.04 LTS (Hardy Heron), released on April 24, 2008, and including FreeType version 2.3.5.

@kevinrushforth
Copy link
Collaborator

@kevinrushforth kevinrushforth commented Oct 2, 2018

All the more puzzling that the initial FreeType code for FX didn't access the function directly...

kevinrushforth pushed a commit that referenced this issue Oct 4, 2018
Reduce color fringes in FreeType subpixel rendering with a direct call
to the function FT_Library_SetLcdFilter, available since FreeType 2.3.0.
Note that the runtime reference to the shared library is the versioned
file name libfreetype.so.6.

Fixes #229
@dlemmermann
Copy link

@dlemmermann dlemmermann commented May 14, 2020

I see the same coloured pixels / blurring on Mac. Is this a known issue? I am currently using OpenJFX 14.0.1 and Java 14.0.1

@jgneff
Copy link
Contributor Author

@jgneff jgneff commented May 14, 2020

I think it might be a known issue. See the mailing list thread starting with the message, "Text rendering on Mojave (macOS)." I said in that thread that I would test it on the Mac, but I never did. I'll test it today with the early-access build of JavaFX 15 on macOS Catalina Version 10.15.4 and a non-Retina display.

@dlemmermann
Copy link

@dlemmermann dlemmermann commented May 14, 2020

@jgneff
Copy link
Contributor Author

@jgneff jgneff commented May 20, 2020

Yes, it does look as if macOS now has the same problem. Below are my test results using the detail cropped from the JavaFX Ensemble application as in the original description, scaled by 800 percent.

Windows

windows-10pro-javafx15-jdk14
Windows 10 Pro Version 1909 Build 18363.836, OpenJDK 14.0.1+7, JavaFX 15-ea+5

Linux

ubuntu-xenial-javafx15-jdk14
Ubuntu 16.04.6 LTS (Xenial Xerus), OpenJDK 14.0.1+7, JavaFX 15-ea+5
ubuntu-focal-javafx15-jdk14
Ubuntu 20.04 LTS (Focal Fossa), OpenJDK 14.0.1+7, JavaFX 15-ea+5

macOS

macos-catalina-javafx15-jdk14
macOS Version 10.15.4 "Catalina", OpenJDK 14.0.1+7, JavaFX 15-ea+5
macos-catalina-javafx15-jdk15
macOS Version 10.15.4 "Catalina", OpenJDK 15-lanai+1-101, JavaFX 15-ea+5

@dlemmermann, I'll e-mail you directly to coordinate opening a bug report.

@dlemmermann
Copy link

@dlemmermann dlemmermann commented May 21, 2020

I have filed a bug report. When / if it gets accepted I will post the issue number and link here.

@frou
Copy link

@frou frou commented Nov 3, 2020

@dlemmermann Are you still waiting on something? Or did you forget to post a link?

@dlemmermann
Copy link

@dlemmermann dlemmermann commented Nov 3, 2020

@dlemmermann
Copy link

@dlemmermann dlemmermann commented Nov 3, 2020

It was marked as a duplicate of this ticket (which is still open and currently targeted for 16): https://bugs.openjdk.java.net/browse/JDK-8236689

@frou
Copy link

@frou frou commented Nov 3, 2020

@dlemmermann Thanks. I'm a bit worried that that ticket talks about it being "fine" in Retina/HiDPI mode. Those bright yellow and bright cyan pixels ain't right in any mode!

I noticed the problem immediately on a Retina Mac, and that's what caused me to google and find this issue.

@mgroth0
Copy link

@mgroth0 mgroth0 commented Mar 23, 2021

Hi all I think this is the same as the issue I'm having where text looks bad on an external monitor

My app on a retina screen:
Screen Shot 2021-03-22 at 10 57 22 PM

On external monitor
Screen Shot 2021-03-22 at 10 57 50 PM

Can you all see the strange clipping on the external monitor version?

I see https://bugs.openjdk.java.net/browse/JDK-8236689 has fix version marked as openjfx 17, however this was built open openjfx17-ea+3. Is it because openjfx 17 is still in early access?

@jgneff
Copy link
Contributor Author

@jgneff jgneff commented Mar 24, 2021

Thanks, Matt (@mgroth0). I cropped and scaled your two images. I really think it's the same problem of severe color fringes on both the Retina Display and your external monitor. It seems to be a simple problem with the "LCD Filter" when rendering the text. It's just that the Retina Display has a high-enough density that the sub-pixel rendering is a small part of each letter, making it less visible.

Retina Display

Your app on a Retina Display (cropped and scaled 400 percent):
mgroth0-screen-2x-detail-4x

External monitor

Your app on an external monitor (cropped and scaled 800 percent):
mgroth0-screen-1x-detail-8x

Make sure to view those images at their full 800-pixel width so that you're not just seeing your browser's resized version. Right-click the image and select "View image" if necessary. I'll update the bug report with those images and link to your comment here.

@jgneff
Copy link
Contributor Author

@jgneff jgneff commented Mar 24, 2021

I see https://bugs.openjdk.java.net/browse/JDK-8236689 has fix version marked as openjfx 17, however this was built open openjfx17-ea+3. Is it because openjfx 17 is still in early access?

Yes, the fix version of openjfx17 means that the goal is to fix it by the OpenJFX 17 General-Availability Release, which is in September 2021. That will happen only if someone is able to fix it in time.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug
Projects
None yet
Development

No branches or pull requests

6 participants