software-development
June 10, 2023

Красивый способ определить темную тему оформления

У современного десктопа есть две основных темы оформления: темная и светлая. Темные и светлые темы есть и в Windows и в MacOS и в линуксах. Временами необходимо подстроить элементы оформления в своем софте под оформление в ОС. Но определить какая тема используется не так-то просто, поэтому рассказываю про один интересный вариант.

Проблема

Проблема не выдуманная, вы и сами наверняка неоднократно наблюдали различные визуальные артефакты — кусочки интерфейса в светлом стиле, при переключении на темную тему в ОС.

Например вот таких:

Конечно же у каждой ОС и каждого графического фреймворка есть свои способы получения названия темы оформления и ключевых цветов оформления.

Но во-первых это технически сложно и объемно, во-вторых хотелось-бы универсальное решение, без привязки к реализации.

Поэтому я решил включить голову и подумать.

Оригинальное решение

Суть решения:

сделать скриншот экрана или рабочей области, перевести полученную картинку в двухцветный формат и посчитать количество черных и белых пикселей.

Если черных больше чем белых — тема оформления темная, если наоборот — тема светлая.

Реализация на Java и Swing, но легко можно адаптировать под любой язык и графический фреймворк.

Вот весь код:

public static synchronized boolean isBlackThemedDesktop() {
    try {
        // получаем изображение со скриншотом
        final BufferedImage screenFullImage = createImage(),
                // черно-белая копия
                bw = new BufferedImage(
                        screenFullImage.getWidth(),
                        screenFullImage.getHeight(),
                        BufferedImage.TYPE_BYTE_BINARY);
        final Graphics2D g = bw.createGraphics();
        // переводим в черно-белую палитру
        g.drawImage(screenFullImage, 0, 0, null);        
        int b = 0, // количество черных пикселей
        w = 0;  // количество белых
        for (int y = 0; y < bw.getHeight(); y++) {
            for (int x = 0; x < bw.getWidth(); x++) {
                final int p = bw.getRGB(x, y);
                // если пиксель черный
                if ((p & 0x00FFFFFF) == 0) 
                    b++; // увеличиваем счетчик количества черных
                else 
                    w++; // или белых                
            }
        }
        return b > w; // если черных пикселей больше чем светлых - тема темная
    } catch (Exception e) {
        return false;
    }
}

Все просто.

Теперь про получение скриншота.

К сожалению в MacOS есть специальная проверка на вызов API отвечающего за создание скриншота экрана или любого фрагмента интерфейса:

Чтобы не смущать пользователя таким диалогом, вылезающим без повода, мы пойдем другим путем:

Вместо того чтобы делать скриншот экрана или уже существующего графического элемента, мы такой элемент создадим и отрисуем его в картинку.

Код выглядит как-то так:

private static BufferedImage createImage() {
    // создаем виртуальную кнопку
    final JButton b = new JButton();
    b.setPreferredSize(new Dimension(100, 100));
    b.setSize(100, 100);
    final BufferedImage bi = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB);
    final Graphics2D g = bi.createGraphics();
    // отрисовываем в картинку
    b.print(g);
    g.dispose();
    return bi;
}

Суть тут в том, что созданная кнопка унаследует системное оформление, в том числе ключевые цвета.

Естественно что при темном оформлении, большая часть такой картинки с кнопкой будет также темной, а при светлой — светлой.

Работает на ура под всеми ОС и любыми окружениями.