В этот раз статья Андрея Коновалова о работе с графикой в .NET Compact Framework.

Вступление

Как известно, на Windows Mobile устройствах существует возможность смены цветовой схемы. В случае, если приложение не использует графические элементы, достаточно воспользоваться набором цветов, предоставляемых классом SystemColors, чтобы приложение соответствовало текущей схеме. Из наиболее часто используемых имеет смысл отметить ActiveCaption, ActiveCaptionText, InactiveCaption, InactiveCaptionText, WindowText и.т.д. Также не стоит забывать про класс SystemBrushes, в котором представлены готовые для работы кисти — нет необходимости вызывать конструкторы и т.д.

Но что делать, когда есть набор изображений, которые должны соответствовать текущей цветовой схеме? Неужели делать набор картинок под все основные цвета?

Это не самое лучшее решение — при любых изменениях в базовом изображении пришлось бы заново создавать все версии этого же изображения в разных оттенках.

Итак, что же остаётся? Очевидно, необходимо каким-то способом трансформировать базовое изображение «на лету». Известно, что самая главная компонента цветовой схемы содержится в реестре по адресу HKLM\Software\Microsoft\Color, в DWORD переменной BaseHue. В случае, если значение находится в диапазоне от 0 до 255, то у нас градации серого. От 256 до 510 — основная радуга 🙂 Опытным путём было установлено, что различные темы частенько кладут в эту переменную «что попало», т.е. значение, существенно превышающее диапазон 0..510. В итоге, чтобы получить честный BaseHue, воспользуемся следующей функцией:

private const String BASEHUE_PATH = “HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Color”;

public static int GetBaseHue()
{
object baseHue = Registry.GetValue(BASEHUE_PATH, “BaseHue”, 0);
int bh = baseHue == null ? 0: (int)baseHue;

if (bh < 255) return bh; else return (bh & 0xFF) + 255; } [/sourcecode] Про реальный смысл значения BaseHue можно почитать тут: HSL color space.

Если коротко, то при значении BaseHue от 0 до 255 у нас градации серого, значит saturation должен быть 0 (т.е. гарантированно grayscale изображение). В случае с диапазоном от 256 до 510, saturation уже идёт на наше усмотрение, по желанию. Меня устраивает и 255, т.е. максимально цветное изображение. Сейчас поясню, причём тут saturation.

Всё дело в том, что изображение у нас хранится в RGB модели, а BaseHue к RGB никакого отношения не имеет. В итоге получается, что есть необходимость произвести RGB -> HSL преобразование для получения возможности «раскраски» базового изображения, а потом обратное HSL -> RGB преобразование, чтобы получить уже реальные цвета для пикселей.

Применение на примере кнопки с чекбоксом

Итак, разберём последовательность действий на примере графической кнопки с чек-боксом и изготовим из неактивной кнопки активную, причём она будет гармонировать с текущей цветовой схемой. Возьмём приготовленное заранее изображение неактивной кнопки. Замечу, что у кнопки есть прозрачные зоны, они цвета magenta, их можно заметить по углам.

Рис. 1 - Оригинал
Рис. 1 - Оригинал

Первая трансформация — просто сделаем так, чтобы вся кнопка ушла в указанный BaseHue (в моём случае 391).

Рис. 2 - Произведено преобразование
Рис. 2 - Произведено преобразование

Вот незадача, угловые пиксели, отвечающие за прозрачность тоже сменили цвет! Пройдёмся по полученной картинке и восстановим справедливость (перебирая оригинал и находя там прозрачные пиксели):

Рис. 3 - «Прозрачные» пиксели восстановлены
Рис. 3 - «Прозрачные» пиксели восстановлены

Да, с прозрачностью теперь всё хорошо, но больно уж некрасивыми остались галочка и бокс под ней. Добавим ещё справедливости:

Рис. 4 - Восстановлена зона чек-бокса
Рис. 4 - Восстановлена зона чек-бокса

Вот этот проход, пожалуй, не такой простой, как предыдущий. Как же это было сделано?

Если приглядеться, то совершенно очевидно, что изображение кнопки, свободное от чекбокса, в общем-то, повторяется (кроме угловых скруглений). И ровно такая же подложка находится под чек-боксом. Какой вывод? У нас есть возможность сравнивать «пустой» фон с частью, где поверх этого фона лежит чек-бокс и в случае, если расхождение в R, G или B более чем некая константа (путём простого перебора мне подошло число 25), то в раскрашенной картинке можно заменить пиксель на пиксель из оригинала.

А вот пример того, если попробовать не использовать порог, а вырезать оригинал «в лоб»:

Рис. 5 - Восстановлена зона чек-бокса без учёта порога
Рис. 5 - Восстановлена зона чек-бокса без учёта порога

Немного кода

Теперь о тонкостях реализации. В Compact Framework нет ни слова про RGB <-> HSL. Гугление достаточно быстро решило вопрос с преобразованиями — RGB <-> HSL. Но не сразу решило вопрос со скоростью преобразования. Как известно, managed код небыстр при работе с графикой, т.к. GetPixel жутко тормозит. Но и для этого решение было найдено. В MSDN-блоге про Windows Mobile обнаружился отличный пост про UnsafeBitmap для оперативных манипуляций с пикселями.

Ниже представлена функция, которая достаточно быстро раскрашивает изображение по указанным hue, saturation, brigtness, используя UnsafeBitmap:

public static Bitmap ApplyHueSaturation(Bitmap input, int hue, int sat, int brightness)
{
if (input == null)
return null;

ColorHandler.RGB rgb;
ColorHandler.HSV hsv;
UnsafeBitmap ibmp = new UnsafeBitmap(input);
UnsafeBitmap obmp = new UnsafeBitmap(new Bitmap(input.Width, input.Height));

ibmp.LockBitmap();
obmp.LockBitmap();

for (int y = 0; y < input.Height; y++) { for (int x = 0; x < input.Width; x++) { UnsafeBitmap.PixelData c = ibmp.GetPixel(x, y); rgb.Red = c.red; rgb.Blue = c.blue; rgb.Green = c.green; hsv = ColorHandler.RGBtoHSV(rgb); hsv.Hue = hue; hsv.Saturation = sat; hsv.value += brightness; if (hsv.value > 255)
hsv.value = 255;
if (hsv.value < 0) hsv.value = 0; ColorHandler.RGB r = ColorHandler.HSVtoRGB(hsv); obmp.SetPixel(x, y, (byte)r.Red, (byte)r.Green, (byte)r.Blue); } } obmp.UnlockBitmap(); ibmp.UnlockBitmap(); return obmp.Bitmap; } [/sourcecode] Работающий пример можно скачать здесь.

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

PSS.: Да, при преобразованиях для оптимизации производительности, безусловно, можно сразу игнорировать прозрачные пиксели, чтобы избежать лишнего перебора изображения. Подобные шаги продемонстрированы исключительно для наглядности процесса.

UPD: Недостаточно адаптировать изображения под текущую схему, необходимо также правильно их вывести. Скруглённые края могут остаться с фиолетовыми углами! 🙂 Читайте продолжение цикла про работу с графикой в Compact Framework.

Оригинал на Хабре.

Previous ArticleNext Article
Технический директор IT-Dimension, компании-разработчика кросс-платформенного программного обеспечения

Leave a Reply

Your email address will not be published. Required fields are marked *

К.

Как создать фигурное окошко в Windows Mobile

И вот еще один небольшой пример, демонстрирующий создание окна непрямоугольной формы в Windows Mobile с библиотекой wxWinCE.

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

Ну а затем нужно выполнить вот такой финт ушами:

  • Создать изображение (черно-белое)
  • Создать для него контекст устройства
  • Нарисовать что-либо (черные пикселы станут прозрачными, белые – видимыми)
  • Создать регион из изображения (wxRegion)
  • Указать форме регион для отображения
void wxMobileTransparencyMainFrame::ChangeShape()
{
	int width(0), height(0);
	// Получаем размер окна
	GetClientSize(&width, &height);
	// Создаем изображение
	wxBitmap bitmap(width, height);
	// Создаем Device Context для изображения
	wxMemoryDC mdc(bitmap);
	// Заполняем черным цветом
	mdc.SetBackground(*wxBLACK_BRUSH);
	mdc.Clear();
	// Устанавливаем кисть белого цвета
	mdc.SetPen(*wxWHITE_PEN);
	wxPoint center(width/2, height/2);
	int radius = wxMin(width, height)/2;
	// Рисуем круг в центре
	mdc.DrawCircle(center, radius);
	// Устанавливаем кисть черного цвета
	mdc.SetPen(*wxBLACK_PEN);
	mdc.SetBrush(*wxBLACK_BRUSH);
	// Рисуем
	mdc.DrawCircle(center.x - radius/3, center.y-radius/4, radius/6);
	mdc.DrawCircle(center.x + radius/3, center.y-radius/4, radius/6);
	mdc.DrawEllipticArc(center.x-radius/3, center.y+radius/4, 
		2 * radius / 3, radius/2, 
		180, 360);
	// Освобождаем Device Context
	mdc.SelectObject(wxNullBitmap);
	// Создаем новый регион
	m_Region = new wxRegion(bitmap, *wxBLACK);
#if defined(__WXWINCE__)
	// Для wxWinCE метод SetRegion() ничего не делает, просто возвращает false.
	// Поэтому приходится устанавливать регион вручную
	HRGN hRgn = (HRGN)m_Region->GetHRGN();
	::SetWindowRgn((HWND)GetHWND(), hRgn, FALSE);
#else
	int offset = GetSize().GetHeight()-GetClientSize().GetHeight();
	m_Region->Offset(0, offset);
	// Устанавливаем регион
	SetShape(*m_Region);
#endif
}

Создаем окно непрямоугольной формы в Windows Mobile
Скачать исходник: Создаем окно непрямоугольной формы в Windows Mobile

С.

Скачать Windows Mobile 6.5 Developer Tool Kit можно уже сейчас!

Ну и вот… Наконец-то дождались! Windows Mobile 6.5 SDK доступно для скачивания. Пока SDK доступно на 6ти языках. Русского нет, к сожалению.

Уже горю желанием попробовать в работе, особенно интересно как wxWinCE соберется с ним.

Скачать  Windows Mobile 6.5 SDK.