Aero: Как нарисовать сплошные (непрозрачные) цвета на стекле?
Использование GDI+ для рисования различных цветов:
brush = new SolidBrush(color);
graphics.FillRectangle(brush, x, y, width, height);
Вы заметите, что непрозрачный цвет не показывает должным образом на стекле:
Как мне нарисовать сплошные цвета на стекле?
Вы также заметите, что полностью непрозрачный цвет обрабатывается по-разному в зависимости от того, какой это цвет:
- непрозрачный черный: полностью прозрачный
- непрозрачный цвет: частично прозрачный
- непрозрачный белый: полностью непрозрачный
Может кто-нибудь указать мне на документацию по настольному композитору, которая объясняет, как обрабатываются разные цвета?
Обновление 3
Вы также заметите, что FillRectangle
ведет себя иначе, чем FillEllipse
:
FillEllipse
с непрозрачным цветом рисует непрозрачный цветFillRectangle
с непрозрачным цветом рисует частично (или полностью) прозрачный
Пояснения по поводу бессмысленного поведения, пожалуйста.
Обновление 4
AlwaysLearning предложил мне изменить режим композитинга. Из MSDN:
Перечисление CompositingMode
Перечисление CompositingMode указывает, как отображаемые цвета комбинируются с фоновыми цветами. Это перечисление используется
Graphics::GetCompositingMode
и методы Graphics::SetCompositingMode класса Graphics.CompositingModeSourceOver
Указывает, что при отображении цвета он смешивается с цветом фона. Смесь определяется альфа-компонентом отображаемого цвета.
CompositingModeSourceCopy
Указывает, что при отображении цвета он перезаписывает цвет фона. Этот режим нельзя использовать вместе с TextRenderingHintClearTypeGridFit.
Из описания CompositingModeSourceCopy
Похоже, это не тот вариант, который я хочу. Из ограничений, которые он накладывает, это звучит как вариант, который я хочу. И с отключенной композицией или прозрачностью это не тот вариант, который мне нужен, так как он выполняет SourceCopy, а не SourceBlend:
К счастью, это не зло, которое я должен созерцать, потому что это не решает мою актуальную проблему. После построения моего graphics
объект, я попытался изменить режим композитинга:
graphics = new Graphics(hDC);
graphics.SetCompositingMode(CompositingModeSourceCopy); //CompositingModeSourceCopy = 1
Результат не влияет на вывод:
Заметки
- Win32 родной
- не.NET (т.е. родной)
- не Winforms (т.е. родной)
- GDI+ (т.е. родной)
Смотрите также
6 ответов
Другой день, другое решение от меня.
- Нарисуйте все, что вы хотите, чтобы появиться на стекле в растровое изображение.
- Затем очистите фон формы с черным цветом.
- Сразу после этого нарисуйте растровое изображение на вашей форме.
Однако (как и в любом другом решении, не использующем DrawThemeTextEx): рендеринг текста не будет работать правильно, потому что он всегда принимает задний цвет вашей формы в качестве подсказки против сглаживания / ясного типа. Вместо этого используйте DrawThemeTextEx, который также поддерживает текст с эффектом свечения позади.
Кажется, работает хорошо для меня. Из-за отсутствия полного примера кода, я предполагаю, что вы неправильно использовали режим компоновки.
public void RenderGdiPlus()
{
List<string> colors = new List<string>(new string[] { "000000", "ff0000", "00ff00", "0000ff", "ffffff" });
List<string> alphas = new List<string>(new string[] { "00", "01", "40", "80", "c0", "fe", "ff" });
Bitmap bmp = new Bitmap(200, 300, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics graphics = Graphics.FromImage(bmp);
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None;
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
SolidBrush backBrush = new SolidBrush(Color.FromArgb(254, 131, 208, 129));
graphics.FillRectangle(backBrush, 0, 0, 300, 300);
graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
Pen pen = new Pen(Color.Gray);
for (int row = 0; row < alphas.Count; row++)
{
string alpha = alphas[row];
for (int column=0; column<colors.Count; column++)
{
string color = "#" + alpha + colors[column];
SolidBrush brush = new SolidBrush(ColorTranslator.FromHtml(color));
graphics.DrawRectangle(pen, 40*column, 40*row, 32, 32);
graphics.FillRectangle(brush, 1+40*column, 1+40*row, 31, 31);
}
}
Graphics gr2 = Graphics.FromHwnd(this.Handle);
gr2.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceCopy;
gr2.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
gr2.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.None;
gr2.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
gr2.DrawImage(bmp, 0, 0);
}
У меня была похожая проблема, но она заключалась в рисовании на многослойном окне, а не на стекле Аэро. У меня нет кода, с помощью которого я могу проверить, решает ли это вашу проблему, но я решил, что стоит попробовать, поскольку симптомы вашей проблемы такие же, как у меня.
Как вы заметили, кажется, что есть некоторые qwerks с FillRectangle
Это видно по различиям между его поведением и FillEllipse
"S.
Вот два обходных пути, которые я придумал, каждый из которых решает мою проблему:
Вызов
FillRectangle
дваждыSolidBrush b(Color(254, 255, 0, 0)); gfx.FillRectangle(&b, Rect(0, 0, width, height)); gfx.FillRectangle(&b, Rect(0, 0, width, height));
Поскольку одна и та же область заполняется дважды, они должны смешиваться и создавать RGB(255, 0, 0) независимо от цвета стекла, что приводит к 100% непрозрачной форме. Я не предпочитаю этот метод, поскольку он требует, чтобы каждый прямоугольник был нарисован дважды.
использование
FillPolygon
вместоТак же, как с
FillEllipse
,FillPolygon
Похоже, не возникает проблема с цветом / непрозрачностью, если вы не называете это так:SolidBrush b(Color(255, 255, 0, 0)); Point points[4]; points[0] = Point(0, 0); points[1] = Point(width, 0); points[2] = Point(width, height); points[4] = Point(0, height); gfx.FillPolygon(&b, points, 4); //don't copy and paste - this won't work
Для меня приведенный выше код привел к 100% прозрачной форме. Я предполагаю, что это либо из-за некоторой формы оптимизации, которая передает вызов
FillRectangle
вместо. Или - скорее всего - есть какая-то проблема сFillPolygon
который называетсяFillRectangle
, Независимо от того, если вы добавите дополнительныйPoint
к массиву, вы можете обойти это:SolidBrush b(Color(255, 255, 0, 0)); Point points[5]; points[0] = Point(0, 0); points[1] = Point(0, 0); //<- points[2] = Point(width, 0); points[3] = Point(width, height); points[4] = Point(0, height); gfx.FillPolygon(&b, points, 5);
Приведенный выше код действительно рисует для меня 100% непрозрачную форму. Я надеюсь, что это также решает вашу проблему.
Вы хотите глупое решение? Здесь вы получите глупое решение. По крайней мере, это всего лишь одна строка кода. И вызывая небольшой, но игнорируемый побочный эффект.
предположение
При рисовании сплошных прямоугольных прямоугольников GDI+ стремится ускорить процесс, рисуя их более быстрым способом, чем другие объекты. Эта техника называется битблитингом. Это на самом деле довольно умно, так как это самый быстрый способ рисовать прямоугольники на поверхности. Тем не менее, прямоугольники, которые должны быть нарисованы, должны соответствовать правилу, согласно которому они находятся под прямым углом.
Эта умная оптимизация была сделана до того, как появились DWM, Aero, Glass и все новые интересные вещи.
Внутренне, битовое копирование просто копирует данные цвета RGBA пикселей из одной области памяти в другую (так сказать, из вашего рисунка в вашем окне). К сожалению, формат RGB, который он записывает, несовместим со стеклянными областями, что приводит к странным эффектам прозрачности, которые вы наблюдали.
Решение
Итак, здесь начинается поворот. GDI+ может уважать матрицу преобразования, с которой каждый чертеж может быть масштабирован, перекошен, повернут или как угодно. Если мы применяем такую матрицу, правило, что прямоугольники больше расположены под прямым углом, больше не гарантируется. Таким образом, GDI+ прекратит разбивать эти биты и нарисует их подобно эллипсам.
Но мы также не хотим искажать, масштабировать или поворачивать наш рисунок. Мы просто применяем наименьшее возможное преобразование: мы создаем матрицу преобразования, которая перемещает каждый рисунок на один пиксель вниз:
// If you don't get that matrix instance, ignore it, it's just boring math
e.Graphics.Transform = new Matrix(1f, 0.001f, 0f, 1f, 0f, 0f);
Теперь битбитинг отключен, прямоугольники сплошные, фиалки синие. Если бы был простой способ контролировать это, особенно если не перемещать чертежи!
Таким образом, если вы хотите нарисовать в первой строке пикселей, используйте -1 в качестве координаты Y.
Вы можете решить, действительно ли это решение для вас, или просто проигнорировать его.
Я нашел другой способ обойти это. использование LinearGradientBrush
с одинаковыми цветами:
LinearGradientBrush brush(Point(0,0), Point(0,0), Color(255,231,45,56), Color(255,231,45,56));
g.FillRectangle(&brush, 25, 25, 30, 30);
Это, возможно, медленнее, чем SolidBrush, но работает нормально.
Я встретил ту же проблему с GDI.
GDI использует нулевое значение альфа-канала, поэтому самое простое решение состоит в том, чтобы исправить альфа-канал, как этот код:
void fix_alpha_channel()
{
std::vector<COLORREF> pixels(cx * cy);
BITMAPINFOHEADER bmpInfo = {0};
bmpInfo.biSize = sizeof(bmpInfo);
bmpInfo.biWidth = cx;
bmpInfo.biHeight = -int(cy);
bmpInfo.biPlanes = 1;
bmpInfo.biBitCount = 32;
bmpInfo.biCompression = BI_RGB;
GetDIBits(memDc, hBmp, 0, cy, &pixels[0], (LPBITMAPINFO)&bmpInfo, DIB_RGB_COLORS);
std::for_each(pixels.begin(), pixels.end(), [](COLORREF& pixel){
if(pixel != 0) // black pixels stay transparent
pixel |= 0xFF000000; // set alpha channel to 100%
});
SetDIBits(memDc, hBmp, 0, cy, &pixels[0], (LPBITMAPINFO)&bmpInfo, DIB_RGB_COLORS);
}