Category: PHP

Adjust text color to be readable on light and dark backgrounds of user interfaces

Most modern user interfaces are supporting different color schemes for day and night: the so called light and dark modes. Selecting a text color for each of those modes is not a big deal and it’s the way to go when designing the user interface.

In some cases, the text color is driven by the displayed contents. In the example below, the tint color is matched to the color of the drink. The global tint color of this app is totally different, but this color adjustment gives a very nice effect. But as you might already see, there is a small problem when it comes to very light or very dark colors: each color either has a good readability on light or dark backgrounds. Some colors might fit to both, but that’s not always the case. In the example below, the light yellow is still visible, but when it comes to small icons or small text, the details are lost.

To overcome this issue, a simple solution is to select two colors for each recipe so that each mode has a different one. That’s fine, but it might totally change the effect of this colored pages.

Can we calculate a suitable color?

Some time ago, there was an article about Black or white text on a colour background? In this one, I described different algorithms to calculate the best text color (black or white) for a colored background. But now, we need the opposite: a colored text that has a good readability on white (light) or black (dark) backgrounds.

When we look at HSL and HSV/HSB color models, we already have a value for ‘lightness’ or ‘brightness’. The idea is to find a color that matches a given hue and saturation and that has a brightness which is readable on light and dark background. For this, we can use different algorithms. Very good results could be achieved with a ‘Weighted W3C Formula‘. This formula take into consideration that the human eye perceives some of the primary colors darker than others.

f'(x) = r ? 0.299 + g ? 0.587 + b ? 0.11

Each color that is located at the border between the black and white overlay is suitable for light and dark backgrounds.

Step 1: convert the given color to HSV/HSB

Step 2: keep hue and saturation constant and adjust the brightness (make the color lighter or darker)

Step 3: convert the HSV/HSB value back to the required color format

Implementation in PHP

A simple calculation for a given RGB color is shown below. The classes used in this snippet are available on GitHub. The code checks the initial brightness of the color and lightens or darkens the values until the ‘border’ calculated by the ‘Weighted W3C Formula’ is reached. This is the case for the value 127, the total range of the brightness is 0 to 255.

$hsv = Convert::rgb2hsv($rgb);

$step = 0.01;
$brightness = Calculate::weightedW3C($rgb);
if ($brightness < 127) {
    while ($brightness < 127 && $hsv[2] >= 0 && $hsv[2] <= 1) {
        $hsv[2] += $step;
        $brightness = Calculate::weightedW3C(Convert::hsv2rgb($hsv));
    }
} else {
    while ($brightness > 127 && $hsv[2] >= 0 && $hsv[2] <= 1) {
        $hsv[2] -= $step;
        $brightness = Calculate::weightedW3C(Convert::hsv2rgb($hsv));
    }
}

return Convert::hsv2rgb($hsv);

Some examples

But how does this result look for different colors? Let’s start with some dark colors. Those are fine for a light background, but they become unreadable on a dark one. The top colors show the input color (before) and the color below shows the output of the calculation above (after).

Color #632300 adjusted to be readable on light and dark background
Color #454545 adjusted to be readable on light and dark background

And now let’s look at some light colors which are fine for dark backgrounds, but they are totally unreadable on light backgrounds.

Color #73FEFF adjusted to be readable on light and dark background
Color #F0C&96 adjusted to be readable on light and dark background

The last color is similar to the example at the beginning and as you can see, the optimized color has a much better readability. This could be achieved for both light and dark colors. The code example shown above is written in PHP. An adoption should be easily possible for any other coding or scripting language

The algorithm mentioned in this post is also available on GitHub https://github.com/mixable/color-utils. This package is usable with composer:

composer require mixable/color-utils

The optimized color can be calculated with:

use Mixable\Color\Calculate;
// ...

$hex = '#ffcc00';
$optimizedColor = Calculate::readableColorForLightAndDarkBackground($hex);

Enable debugging output in PHPUnit

When running PHPunit there are only dots and letters for each test by default:

To enable debug output and get some more details about the tests running, simply add the logging section to phpunit.xml.dist:

    <logging>
        <log type="testdox-text" target="php://stdout"/>
    </logging>

This will create a debug output and helps to track the tests:

In my case, this helped when my code reached an infinite loop due to an error. This results in a RuntimeException without any outputs or log messages. The process just ended with:

[Symfony\Component\Process\Exception\RuntimeException]  
The process has been signaled with signal "11". 

PHPUnit: faster and better unit tests with pcov

When using PHPUnit there are different ways to create a code coverage report. By default, XDebug is used. But as mention on different sites, XDebug is very slow and the generation of a code coverage report might take several minutes for big projects.

phpdbg

To speed up things, phpdbg can be used. This significantly speeds up unit tests and code coverage. Phpdbg can be used as follows:

phpdbg -qrr ./vendor/bin/phpunit --coverage-text --colors=never

But there are different problems with the code coverage report of phpdbg. For example phpdbg does not cover a case line of a switch statement:

pcov

A better solution to cover this is pcov, which can be installed using pecl:

pecl install pcov

To create a code coverage report, phpunit can be called with:

php -dpcov.enabled=1 -dpcov.directory=. ./vendor/bin/phpunit --coverage-text

To exclude a directory, the following parameter can be used:

php -dpcov.enabled=1 -dpcov.directory=.  -dpcov.exclude="~vendor~" ./vendor/bin/phpunit --coverage-text

I don’t know if it’s really a problem with pcov, but: with pcov installed, it is not possible to use phpdbg anymore!

PHP: realpath() for non-existing path

The php method realpath() can transform the string format of a path into a real path. Means, a path string like:

/Users/mathias/data/../projects/website

will become:

/Users/mathias/projects/website

But this only works, if the path really exists. For non-existing paths, this function cannot be used. To get the same functionality, the following function can be used:

/**
 * Get normalized path, like realpath() for non-existing path or file
 *
 * @param string $path path to be normalized
 * @return false|string|string[]
 */
public function normalizePath(string $path)
{
    return array_reduce(explode('/', $path), function($a, $b) {
        if ($a === null) {
            $a = "/";
        }
        if ($b === "" || $b === ".") {
            return $a;
        }
        if ($b === "..") {
            return dirname($a);
        }

        return preg_replace("/\/+/", "/", "$a/$b");
    });
}

How to ignore PHP_CodeSniffer warning: Line exceeds 120 characters;

When using codesniffer to check your code, a lot of warnings might appear when the lines are too long:

----------------------------------------------------------------------
FOUND 0 ERRORS AND 4 WARNINGS AFFECTING 4 LINES
----------------------------------------------------------------------
  73 | WARNING | Line exceeds 120 characters; contains 162 characters
  75 | WARNING | Line exceeds 120 characters; contains 124 characters
 102 | WARNING | Line exceeds 120 characters; contains 168 characters
 108 | WARNING | Line exceeds 120 characters; contains 121 characters
----------------------------------------------------------------------

To ignore those warnings, we can add // phpcs:ignore as a comment to the end of a (too long) line. For example:

<?php
$message = 'This is my long message. It\'s not only long, it\'s extreeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeemly long'; // phpcs:ignore

PNG – deactivate interlace to avoid ‘libpng warning: Interlace handling should be turned on when using png_read_image’

I stumbled appon a warning message that was thrown by PHP when handling images with GD lib (e.g. imagecreatefrompng()). The message shown was:

libpng warning: Interlace handling should be turned on when using png_read_image

This message even exists, when deactivating ‘interlace’ with the help of:

imageinterlace($img, false);

The point is that this message is not caused by any wrong php code, it is caused by the processed images itself. The only solution is to deactivate interlace for the processed image(s). This is possible with ImageMagick. To deactivate interlace on all images of a folder, the following command can be used:

magick mogrify -interlace none *.png

I used ImageMagick on macos and installed it with with HomeBrew:

brew install imagemagick

PHP ColorUtils

A neat collection of useful methods to convert colors between different color spaces is available on GitHub (MIT License). It can convert any values between RGB, HSL, CMYK, YUV and HEX colors.

I created a ready-to-use php class out of this method collection:

https://github.com/mixable/color-utils

Usage

The methods itself are mostly self-explaining. To use the class, just include the php file into your script and you are ready to go:

<?php
require('ColorUtils.php');

The methods itself are static, they can be used like:

$hex = '#ffcc00';
$rgb = ColorUtils::hex2rgb($hex);

Which results in an array with three values (red, blue, green) in $rgb:

// [0 => 255, 1 => 204, 2 => 0]

Photo by Kelli Tungay on Unsplash

Fatal error: Call to undefined function bindtextdomain()

Fatal error: Call to undefined function bindtextdomain() in [...]

This error might be caused by a missing GetText extension. If this is the case, you have to enable the extension in your php.ini:

extension=php_gettext.dll

If the extension is not installed yet, this is how to install gettext on macOS (for PHP7.0):

sudo port install php70-gettext

Afterwards, do not forget to restart Apache:

sudo port unload apache2
sudo port load apache2