Вступление
К большому сожалению разработчиков, Compact Framework, да и native-функции тоже, не поддерживают альфа-канал с разной прозрачностью у индивидуальных пикселей. Это означает, что нет возможности создавать красоту неописуемую с плавными переходами между изображениями. Однако, что же делать, если хочется иметь хотя бы подобие «полного» альфа-канала, а именно, выводить полупрозрачные изображения, у которых есть ещё и полностью прозрачные участки?
Рассмотрим два способа вывода изображений с прозрачностью.
Способ №1. Фиксированый цвет является прозрачным
public static void DrawImageTransparent(Graphics g, Bitmap b, Point location, Color transColor)
{
if (b == null || g == null)
return;
ImageAttributes attrib = new ImageAttributes();
attrib.SetColorKey(transColor, transColor);
Rectangle destRect = new Rectangle(location.X, location.Y, b.Width, b.Height);
g.DrawImage(b, destRect, 0, 0, b.Width, b.Height, GraphicsUnit.Pixel, attrib);
}
* This source code was highlighted with Source Code Highlighter.
Стоит заметить, что только эта хитрая разновидность DrawImage позволяет выводить изображение с указанным ColorKey, по которому определяется, какие пиксели не рисовать. Шикарный набор параметров, не находите? :) Куда рисовать, мы задаём через Rectange, а откуда — через 4 параметра. Ну это я так, лирическое отступление в сторону Microsoft.
Собственно, именно DrawImageTransparent и есть основной способ рисования изображений с прозрачными пикселями. Однако минус этого способа очевиден, состояния прозрачности всего два: полностью прозрачно и совсем непрозрачно.
Пример:
На самом деле, вполне неплохо, можно на этом и остановиться. Но хочется-то большего :)
Способ №2. У всего изображения фиксированный коэффициент непрозрачности
В этом случае без DllImport уже не обойтись, приготовим всё, что для этого необходимо:
public struct BlendFunction
{
public byte BlendOp;
public byte BlendFlags;
public byte SourceConstantAlpha;
public byte AlphaFormat;
}
public enum BlendOperation : byte
{
AC_SRC_OVER = 0x00
}
public enum BlendFlags : byte
{
Zero = 0x00
}
public enum SourceConstantAlpha : byte
{
Transparent = 0x00,
Opaque = 0xFF
}
public enum AlphaFormat : byte
{
AC_SRC_ALPHA = 0x01
}
public class PlatformAPI
{
[DllImport("coredll.dll")]
extern public static Int32 AlphaBlend(IntPtr hdcDest, Int32 xDest, Int32 yDest, Int32 cxDest, Int32 cyDest, IntPtr hdcSrc, Int32 xSrc, Int32 ySrc, Int32 cxSrc, Int32 cySrc, BlendFunction blendFunction);
}
* This source code was highlighted with Source Code Highlighter.
Как видно, обрезано всё, что только можно обрезать — в enum-ах по одному параметру и т.д. Но тем не менее, продолжаем. Собственно, наша функция:
public static void DrawAlpha(Graphics g, Bitmap b, Point location, byte opacity)
{
if (b == null || g == null)
return;
using (Graphics gxSrc = Graphics.FromImage(g))
{
IntPtr hdcDst = g.GetHdc();
IntPtr hdcSrc = gxSrc.GetHdc();
BlendFunction blendFunction = new BlendFunction();
blendFunction.BlendOp = (byte)BlendOperation.AC_SRC_OVER;
blendFunction.BlendFlags = (byte)BlendFlags.Zero;
blendFunction.SourceConstantAlpha = opacity;
blendFunction.AlphaFormat = (byte)0;
PlatformAPI.AlphaBlend(hdcDst, location.X, location.Y, b.Width, b.Height, hdcSrc, 0, 0, b.Width, b.Height, blendFunction);
g.ReleaseHdc(hdcDst);
gxSrc.ReleaseHdc(hdcSrc);
}
}
* This source code was highlighted with Source Code Highlighter.
Небольшие комментарии по коду — параметры у BlendFunction нельзя менять, они проставляются единственно возможные. Это обидно, но делать нечего.
Пример:
Жутковато, да? Противные фиолетовые пиксели никуда не делись и тоже стали немного прозрачными :(
Комбинированное использование обоих способов
Вариантов комбинирования у нас, к сожалению, немного. На первый взгляд их совсем нет :) Но есть всё-таки один способ.
Итак, решение следующее. Раз мы не можем одновременно задать ColorKey и вызвать AlphaBlend, будем использовать их по очереди. Сначала нарисуем фон стандартным спосбом без изысков, затем кнопку первым спосбом, а в конце… вторым спосбом нарисуем поверх фон с небольшим коэффициентом непрозрачности!
g.DrawImage(background, 0, 0);
DrawImageTransparent(g, button, new Point(10, 10), Color.FromArgb(255, 0, 255));
DrawAlpha(g, background, new Point(0, 0), 75);
* This source code was highlighted with Source Code Highlighter.
Результат:
Описанный выше способ вполне жизнеспособен. Я им пользуюсь и вполне удовлетворён скоростью работы — на отрисовку всех элементов интерфейса в подобном стиле уходит в среднем от 60 до 80 миллисекунд (проверялось на разнообразных устройствах). Для создания приложения в таком стиле, безусловно, стандартные контролы не подойдут, но а кто обещал, что будет легко? В любом случае, для создания неописуемой красоты без собственного фреймворка рендеринга графических элементов не обойтись.