Электронная картина с автонастройкой яркости на C++-Qt

в 12:16, , рубрики: diy-проекты, qt, Авторегулировка яркости, Электронная картина

Всю свою жизнь я обходил самоделки. Не привлекало, да и на непосредственной работе вполне хватало сложных программистских и инженерных задач. Но любовь к сыну сподвигла взяться за один DIY-проект.

Дело в том, что я давно люблю и ценю изобразительное искусство. И также люблю изучение истории (и вообще считаю, что не программированием единым должен жить человек). И я подумал, что изучение истории и живописи можно слить в единый флакон – будет и не скучно, и полезно. Особенно полезно подрастающему школьному и дошкольному поколению. Я захотел электронную картину – чтобы выглядела как картина, а изображения бы настраивались мной, автоматически сменяясь по таймеру. Каждый день – новая картина. Каждый день – повод обсудить с сыном, как видели этот мир художники 200, 300, 500 лет назад, и вообще, а что это за разновидность палаша, аркебузы или фрегата запечатлел здесь художник. А здесь какое важное для мировой истории событие показано?

Изучив рынок электроники, я понял, что подходящего под мои требования продукта просто не найти (хотя отдаленно похожие есть, конечно). Потому я прикинул доступную мне элементную базу, расчехлил клавиатуру и принялся писать код. В итоге получилось десктоп-приложение на С++/Qt, которое я закинул на мини-ПК под Windows, а сама картина представила собой обрамленный в багет сенсорный дисплей на 18,5 дюймов с прекрасной цветопередачей. По итогу заинтересовался не только сын, но, неожиданно, и жена.

Вид готового продукта

Вид готового продукта

Что конкретно я хотел?

Вот список моих требований к будущему продукту.

  1. Дисплей от 17 до 22 дюймов

  2. Очень желательно, чтобы сенсорный.

  3. Суммарная толщина картины – чем меньше, тем лучше. Точно не больше 5 см на все про все. Это, кстати, оказалось, наверное, самым тяжелым условием.

  4. Подключение к электрическому проводу (или на крайняк, к розетке) за картиной.

  5. Никаких внешних, выходящих за пределы картины проводов.

  6. Работать должно без интернета.

  7. База интересных картин (сменяющих друг друга изображений) – от 1000 штук.

  8. Картину со стены должно быть относительно легко снимать и вешать.

  9. Возможность корректировать список изображений.

  10. Яркость изображения должна автоматически подстраиваться под яркость окружающего помещения. Зачем? Чтобы выглядело реалистично, а не как ночной фонарь. Просто представьте себе, что ночью, проснувшись в туалет, вы неожиданно увидите на полной яркости одно из творений Босха (загуглите, если не знакомы).

  11. Вывод на экран (по запросу, по нажатии на дисплей) фамилии художника и названия картины. Очень желательно русскими буквами.

  12. Возможность настраивать таймер смены картины.

  13. Автонастройка списка картин под ориентацию дисплея. Не надо на вертикальном дисплее показывать горизонтальные картины, и наоборот.

  14. Аналоговая кнопка включения/выключения в удобном доступе.

  15. Возможность вручную перелистывать картины (это единственное, пожалуй, необязательное условие).

Что есть на рынке и почему мне не подошло?  

Во-первых, есть телевизор The Frame от Samsung. Это большой и очень тонкий телик, но с кучей нюансов. Внешний блок и кабель, идущий к этому внешнему блоку. Нужен интернет. База картин – не та, какая тебе хочется, а та, какую посчитал нужным дать тебе корейско-американский дядя. Плюс чтобы все работало, плати абонентскую плату. Спасибо, не надо.

Электронная картина с автонастройкой яркости на C++-Qt - 2

Во-вторых, был на рынке такой продукт, как фоторамки. Лет 10-15 назад было популярным. Сейчас их почти нет. Те, что есть, имеют маленькие дисплеи с минимумом пикселей, яркость там постоянная, возможности по настройке и кастомизации часто очень ограниченные. Очень жаль.

Электронная картина с автонастройкой яркости на C++-Qt - 3

В-третьих, планшеты на Andoid плюс приложение на Andoid. Это почти что идеальный вариант. Есть классные приложения с хорошими настройками (лучшее здесь - Fotoo). Но больших планшетов особо не выпускают, предел мечтаний – дюймов 12. А андроидовская настройка, затемняющяя экран, глушит только яркость самого экрана, оставляя его в полной темноте довольно ярко светиться. Аналоговая картина так бы себя не вела. Мне недостаточно.

На чем я решил собирать?

Я решил брать готовые компоненты: дисплей, мини-ПК, датчик освещенности в виде usb-камеры, соединить все проводами, обрамить в багет и написать ПО. Фактически, труд вылился только в написание ПО. Приложение я написал на C++/Qt – та платформа, где мне было сделать это быстрее всего.

Что касается компонентов, то, возможно, если бы я имел достаточное представление о схемотехнике (и умел бы паять), я смог бы собрать более компактный и более дешевый набор. Но сделать именно так, как я сделал, мне было наиболее оптимально в плане моих личных трудозатрат с учетом текущей квалификации. Это был приоритет. Цена компонент составила меньше 25 тысяч рублей (если не считать багет), и это меня вполне устроило.

Дисплей

Дисплей брал такой: ZEUSLAP 18.5" Z18T, черный. Я брал за 16200, а в момент написания статьи он стоит 12700. Дисплей тонкий, менее 2 см, качество изображения хорошее, сенсорый. Самое важное: порты подключения располагаются не на боковой грани, как почти на всех подобных мониторах, а утоплены вглубь! Это дает возможность нормально подключить провода, а не подпиливать для этого багет. Также у этого монитора есть возможность запитаться по USB от мини-ПК. Это уменьшает сложность системы на один блок питания и пару-тройку проводов, что очень приятно.

Электронная картина с автонастройкой яркости на C++-Qt - 4

Для упрощения соединения HDMI кабеля я приобрел U-образный переходник с Mini-HDMI на HDMI за 150 рублей типа такого.

Электронная картина с автонастройкой яркости на C++-Qt - 5

Мини-ПК

Мини-ПК брал такой: MLLSE M2, Intel Celeron N3350 ЦП 6 ГБ ОЗУ 64 Гб. Брал за 6300, сейчас его на AliExpress нет, но есть море аналогичных. Брал по принципу наименьшей толщины, она там 25 мм. Процессор подойдет любой слабый, памяти на дисках там много не надо (пару-тройку гигабайт весят произведения великих художников, всего-то). Винда на компе уже была, и хорошо.

Электронная картина с автонастройкой яркости на C++-Qt - 6

Дополнительно мне пришлось закупить еще блок питания за 300 рублей, поскольку тот блок, что шел в комплекте с мини-ПК, был слишком толстым.

Датчик освещенности

Изначально я хотел найти готовый датчик освещенности с usb-подключением и готовыми драйверами. Но не нашел. Благо, это оказалось несложно исправить тем, что в качестве датчика освещения можно использовать любую готовую веб-камеру, подключаемую по usb. Замеряя среднюю яркость пикселей полученного изображения, можно замерять яркость окружающей обстановки. В теории. На практике пришлось немного алгоритмически поколдовать, чтобы добиться приемлемого результата.

В общем, камеру купил такую: ENWOR за 350 рублей, с ней вообще-то удобно по канализационным трубам шариться, но мне она подошла лучше других тем своим качеством, что у нее головка маленькая и круглая, то есть можно просто в багете просверлить дырочку и аккуратно ее вывести с минимумом плясок с бубном.

Электронная картина с автонастройкой яркости на C++-Qt - 7

В целом, у всех таких простых камер есть один значительный нюанс – у них есть автоматическая настройка светочувствительности (ISO). То есть, затемняя комнату, камера автоматически повышает свою светочувствительность, и средняя яркость картинки остается такой же, как и была. И отключить это никак нельзя. Видимо, прямо на микросхеме они такую логику заложили. В общем, это было бы неустранимой проблемой, если бы не одно но. Китайцы свой auto-ISO сделали хреновеньким, и границы его применения оказались небольшими. То есть яркий солнечный день от пасмурного дня отличить, может, и не получится, а вот одни потемки от других – да, выйдет. Мне именно это и надо. Днем пусть картина работает на полной яркости, в сумерках – как-то несильно светится, а вот ночью пусть будет темнота.

Багет

Багет заказывал в ближайшем багетном магазине за 6500 рублей. Выяснилось два момента.

Во-первых, багетов большой глубины очень мало на рынке, видимо, из-за санкций. Толщина моей картины составила около 37 мм, а багет удалось подобрать только глубины 30 мм. В маркетплейсах я вообще бы ничего не подобрал, поскольку такого параметра, как глубина, они вообще нормально не показывают.

Во-вторых, материал багета имеет значение. Мне достался багет из какого-то полиуретана (похож на материал плинтусов), а при его сверлении он плавится. Расплавленная масса оседает на сверле и на стенках отверстия. В общем, не очень удобно, а сверло просто в расход. Я сверлил два отверстия – для кнопки включения/выключения и для вывода датчика освещенности.

Сама картина вешается на алюминиевую многожильную нить, крепящуюся к задним стенкам багета. Это самое удобное крепление, если планируете периодически снимать/вешать, как планирую я.

Электронная картина с автонастройкой яркости на C++-Qt - 8

Сборка

В ближайшем строительном супермаркете я купил кусок провода, две розетки и одну вилку (тонкие должны быть), а на маркетплейсе заказал мебельную кнопку включения/выключения, под цвет багета.

Электронная картина с автонастройкой яркости на C++-Qt - 9

В готовом багете просверлил дырки для кнопки включения и датчика освещенности.

Кнопка включения

Кнопка включения
Датчик освещенности

Датчик освещенности

Приклеил мини-ПК к задней стенке дисплея на двусторонний скотч. Соединил все проводами. Кабель-менеджмент вышел не очень, но за задней стенкой никто не увидит.

Электронная картина с автонастройкой яркости на C++-Qt - 12

Из стены у меня торчал провод, я подключил его к вилке. Так как я планировал периодически снимать/вешать картину, то оголенные провода мне ни к чему.

Электронная картина с автонастройкой яркости на C++-Qt - 13

После этого оставалось только подключить вилку к розетке и повесить за алюминиевую нить. Само приложение я поставил заранее, залил папку с картинами и внес приложение в автозагрузку.

Приложение WallPicture

Исходный код выложен на GitHub (и там же собранный релиз). В целом могу сказать то, что далеко идущих целей я не ставил, писал только ту функциональность, которой собирался пользоваться. И вообще, старался сократить свое время на разработку так сильно, как можно. Скажем, понимая, что мини-ПК у меня все равно под Windows, то я не тратил ни минуты ни на какую кроссплатформенность. Даже проект делал в родном стиле Visual Studio, без CMake. В общем, кто захочет продолжить и развить – все возможно, кроссплатформенность не убита, просто не поддержана по-нормальному. То же касается и подгонки под разные размеры экрана, резиновый дизайн и вообще под мобильное (сенсорное) использование: все сделано по минимуму.

Функциональность приложения

Есть два основных режима: спящий и активный. Спящий режим – это основной режим работы, в нем показывается только текущая картина, причем яркость изображения зависит от яркости окружающего пространства. В активном режиме показаны все элементы управления плюс название полотна и имя художника, а яркость монитора максимальна, то есть это та яркость, которая была при старте приложения. Элементы управления – кнопки вперёд/назад, растянуть на полный экран и доступ к настройкам. Смена режимов происходит касанием экрана или движением пальца по экрану.  Через 5 секунд нахождения в активном режиме, если за это время не было активных касаний, приложение переходит в спящий режим.

Спящий режим

Спящий режим
Активный режим

Активный режим

Имя художника и название полотна берется просто из названия файла. Почти в 100% случаев, если качать какие-либо архивы с картинами, так оно и будет – в названии файлов эта инфа есть. А вот в инфе exif её как раз и нет, как правило.

Настройки приложения незамысловаты. Во-первых, это путь к папке, где в любом иерархическом порядке хранятся файлы jpg, png, bmp, являющиеся картинами. В-вторых, это порядок воспроизведения – последовательный или случайный. В-третьих, это фильтр по картинам, учитывающий ориентацию монитора и соотношение его сторон. Например, для альбомной ориентации монитора можно выбрать показ только альбомно-ориентированных картин, а можно только те картины, соотношение сторон которых будет очень близко к 16:9. В-четвертых, это таймер смены картины. В-пятых, это профиль настройки датчика освещенности. Несколько системных профилей я подобрал эмпирически, чтобы покрыть разные возможные ситуации.

Настройки приложения

Настройки приложения

Программистские нюансы

Полный код выложен здесь. Поясню лишь некоторые не самые очевидные нюансы по некоторым ключевым классам.

Класс LightSensor. Использует первую доступную камеру (из списка QCameraInfo::availableCameras()) для снимков. С интервалом 100 мс делает снимки, по ним считает среднюю яркость пикселей изображения. Эта яркость попадает в кольцевой буфер размером 50, и итоговая яркость сенсора считается как среднее из находящихся там значений. Это, фактически, пятисекундное усреднение позволяет более-менее адекватно считать истинную яркость помещения. Иначе на китайских камерах, подверженных каким-то своим глюкам и случайностям, работать будет плохо. Яркость от замера к замеру будет прыгать аки бешеный заяц во время спаривания.

Класс ScaledPixmap. К сожалению, в Qt нет класса, который мог бы рисовать на подложке любого размера изображение с сохранением отношения его сторон. Потому такой класс пришлось писать самому.

Класс PictureLoader. Это менеджер загрузки изображений. Самая сложная его часть – это фильтрация списка изображений по критерию соотношения сторон. Размер изображения можно узнать, просто вызвав QPixmap(pathToPicture).size()

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

QSize jpegSize(const QString& path)
{
	static QTextCodec* codec = QTextCodec::codecForName("CP1251");
	std::string path1 =  codec->fromUnicode(path).data();
	const char* strPath = path1.data();

	FILE* image = fopen(strPath, "rb");  // open JPEG image file
	if (!image)
		return QSize(0, 0);

	int width = 0, height = 0;
	int i = 0;
	unsigned char* data;

	fseek(image, 0, SEEK_END);
	int size = ftell(image);
	fseek(image, 0, SEEK_SET);
	data = (unsigned char*)malloc(size);
	fread(data, 1, size, image);
	/* verify valid JPEG header */
	if (data[i] == 0xFF && data[i + 1] == 0xD8 && data[i + 2] == 0xFF && data[i + 3] == 0xE0)
	{
		i += 4;
		/* Check for null terminated JFIF */
		if (data[i + 2] == 'J' && data[i + 3] == 'F' && data[i + 4] == 'I'
			&& data[i + 5] == 'F' && data[i + 6] == 0x00)
		{
			while (i < size)
			{
				i++;
				if (data[i] == 0xFF)
				{
					if (data[i + 1] == 0xC0)
					{
						height = data[i + 5] * 256 + data[i + 6];
						width = data[i + 7] * 256 + data[i + 8];
						break;
					}
				}
			}
		}
	}
	fclose(image);
	free(data);

	if (width == 0 && height == 0)
	{
		// very slow
		QSize size =  QPixmap(path).size();
		width = size.width();
		height = size.height();
	}

	return QSize(width, height);
}

Изменение яркости. Просто изменять яркость дисплея недостаточно, необходимо еще менять яркость самого изображения. Только в результате этого трюка происходит то, что в темноте вы будете видеть черный монитор, а не просто слегка приглушенный.

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

void WallPicture::convert(uchar* value, float multiplier) const
{
    *value = *value * multiplier;
};

QPixmap WallPicture::changePixmapBrightness(const QPixmap& pixmap, double multiplier) const
{
    QImage image = pixmap.toImage();
    auto format = image.format();
    if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32)
        image = image.convertToFormat(QImage::Format_RGB32);

    uchar* ptr = image.bits();
    int sizeInBytes = image.sizeInBytes();
    constexpr int pixelSizeInBytes = 4;
    for (int i = 0; i < sizeInBytes / pixelSizeInBytes; i++)
    {
        convert(ptr + i * pixelSizeInBytes, multiplier);
        convert(ptr + i * pixelSizeInBytes + 1, multiplier);
        convert(ptr + i * pixelSizeInBytes + 2, multiplier);
    }
    return QPixmap::fromImage(image);
}

Ну а яркость дисплея меняется просто через WinAPI, вызовом функции SetMonitorBrightness (к сожалению, в Qt такое пока не завезли). В эту функцию надо передать идентификатор монитора. Его я получаю таким образом:

HANDLE monitor()
{
	DWORD NumberOfPhysicalMonitors{ 0 };
	PHYSICAL_MONITOR* PhysicalMonitors{ nullptr };

	HMONITOR hMonitor{ MonitorFromWindow(GetTopWindow(nullptr), NULL) };

	if (!hMonitor || hMonitor == INVALID_HANDLE_VALUE)
		return INVALID_HANDLE_VALUE;

	if (!GetNumberOfPhysicalMonitorsFromHMONITOR(hMonitor, &NumberOfPhysicalMonitors))
		return INVALID_HANDLE_VALUE;

	PhysicalMonitors = static_cast<PHYSICAL_MONITOR*>(malloc(
		NumberOfPhysicalMonitors * sizeof(PHYSICAL_MONITOR)
	));

	if (!GetPhysicalMonitorsFromHMONITOR(hMonitor,
		NumberOfPhysicalMonitors,
		PhysicalMonitors))
	{
		free(PhysicalMonitors);
		return INVALID_HANDLE_VALUE;
	}

	return PhysicalMonitors->hPhysicalMonitor;
}

Менять яркость надо от того уровня, который был при старте приложения (он будет считаться максимальным). Этот уровень можно узнать вызовом функции GetMonitorBrightness. Неожиданный сюрприз настиг меня в том, что получить с первого раза текущую яркость монитора не всегда получается (не забываем, что монитор-то истинно китайский). Потому пришлось делать несколько попыток (на моей модели монитора достаточно было сделать две, но я решил заложиться на всякий случай на 5).

Monitor::Monitor()
{
	_monitorHandle = monitor();

	constexpr int tryMaxCount = 5;
	for (int i = 0; i < tryMaxCount; i++)
	{
		GetMonitorBrightness(_monitorHandle, &_minBrightness, &_brightness, &_maxBrightness);
		if (_minBrightness < _maxBrightness)
		{
			_initialized = true;
			break;
		}
	}
}

Откуда брать изображения?

Реальность такова, что фактически единственное место, где есть хорошие коллекции картин – это Торренты. Там, в разделе Живопись, Графика, Скульптура, Digital Art можно найти не один десяток больших архивов в более-менее приличном качестве и, главное, содержащих информацию о картине в названии файла, это очень удобно. Я для себя выкачал Коллекцию картин русских и советских живописцев, Коллекцию Эрмитажа, Коллекцию музея Прадо и некоторые другие сборники.

Если не смотреть на торренты, то, считайте, что бесплатных картин в интернете нет (по крайней мере, если искать с прицелом на русских художников и русскоязычные названия картин, как искал я). Есть условно-бесплатные сайты типа Gallerix или Артхив, но выкачивать там нужно каждую картину отдельно, вручную, да еще и название файла будет представлено нечитаемым буквенно-цифровым набором.

В целом, даже на торрентах есть далеко не все, что хочется. К примеру, в направлении соцреализма я не нашел ни одной достойной коллекции. Вряд ли художники этого направления творили для того, чтобы через век некоторые уважаемые господа объявили себя владельцами и выгодоприобретателями этих полотен и вывели бы их из бесплатного доступа для общественности. Также я не думаю, что такое положение дел привело бы в восторг, скажем, Караваджо, Ботичелли или Леонардо да Винчи. Да и любого художника, чьи произведения пробили себе дорогу в веках. Я думаю, у творцов должно быть право общаться через свои произведения со всем человечеством, а не только с горсткой избранных.

Полезный совет напоследок

Для тех, кто захочет повторить мою самоделку и пользоваться опцией автонастройки яркости, помните, что запускать приложение нужно в хорошо освещенном помещении, а не в потемках. Ибо тот уровень яркости, который зафиксируется при старте приложения, будет принят программой за максимальный. И этой яркости помещения будет соответствовать та яркость дисплея, которая была при старте приложения. Следовательно, запустив приложение ночью или в потемках, вы получите вечно яркий монитор, что неестественно для картины.

Автор: rpirf

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js