"Волшебная" отрицательная кисть WPF? - PullRequest
10 голосов
/ 16 февраля 2010

У меня есть градиент, который меняет свои цвета, я хочу, чтобы текст внутри него всегда был виден.

Я скорее делаю это динамически, если есть какой-либо готовый ресурс; Я хочу «волшебную кисть», которая нейтрализует цвет.

Есть эксперименты?

Ответы [ 2 ]

8 голосов
/ 17 февраля 2010

Джоэл дал отличный ответ о том, как выровнять градиентные кисти. Я хотел бы коснуться сложности автоматического создания новой градиентной кисти, которая гарантированно будет видна на фоне старой.

В WPF цвета моделируются трехмерно, поскольку для определения цвета WPF требуются три числа (например, R / G / B или H / S / B), не считая альфа-компонента. Заданную градиентную заливку можно рассматривать как путь, который проходит от одной цветовой точки к другой в трехмерном цветовом пространстве. Для создания обратного градиента, который контрастирует в каждой точке, требуется создание дополнительного пути, который ни в коем случае не «слишком близок» к исходному пути. «Слишком близкими» для этой цели являются любые два цвета, которые трудно различить человеческому глазу. Это на самом деле субъективно. Около 4% людей, страдающих дальтонизмом, будут толковать «слишком близко» иначе, чем те, кто этого не делает.

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

С другой стороны, консервативное определение «слишком близко», которое учитывает восприятие цвета каждым человеком, такое как «яркость должна отличаться как минимум на 25%», будет страдать от противоположной проблемы: единственный градиент, который удовлетворяет условию при каждая точка должна быть на самом деле прерывистой, то есть она должна сразу перейти от одного цвета к удаленному. Рассмотрим, например, простой градиент от черного к белому. Если задействована только яркость, контрастный фон должен иметь разрыв, в противном случае он будет совпадать в какой-то момент.

По этим причинам создание общего алгоритма для создания контрастного фона - больше искусство, чем наука. Можно использовать несколько алгоритмов, и в разных ситуациях будут подходить разные алгоритмы.

Простой алгоритм, который использовался для этой цели, состоит в том, чтобы сохранить оттенок и насыщенность на одном и том же градиенте и установить яркость на (luminance + 50%) mod 100%. Однако этот алгоритм не дает очень эстетичных результатов в большинстве случаев и никогда не имеет вариации яркости более 50%. Модификация этого алгоритма заключается в инвертировании или сдвиге значений оттенков и насыщенности.

Еще более простой алгоритм для вычисления контрастной яркости - luminance>50% ? 0% : 100%. Это также может иметь эстетические проблемы.

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

6 голосов
/ 16 февраля 2010

Что ж, инверсия цвета могла бы быть выполнена как эффект растрового изображения, но есть более простой способ.

Создайте Grid, который будет контейнером для 3 дочерних панелей, чтобы эти дочерние панели полностью перекрывали друг друга:

Поместите текст в нужное место на панели с фоном Transparent (по умолчанию). Назовите эту панель "маска".

Создайте еще одну панель с именем 'mainbackground' и задайте ей основной градиент в качестве фона. Поместите это после панели «маска», чтобы она покрывала текст

Создайте еще одну панель под названием «перевернутый передний план» и задайте ей противоположный градиент. Для каждого значения цвета в главном градиенте задайте противоположное (например, если один цвет равен #FF0000, укажите #00FFFF). Вы можете анимировать этот градиент так же, как вы можете анимировать первый, просто с противоположными значениями. Затем вы устанавливаете OpacityMask этой панели на VisualBrush и устанавливаете для VisualBrushes s Visual свойство {Binding ElementName=mask}.

<Grid>
    <Grid.Resources>
        <local:MyColorConverter x:Key="colorConverter" />
    </Grid.Resources>
    <Grid
        Name="mask">
        <TextBlock
            Name="mytext"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            FontSize="32"
            Foreground="White"
            FontWeight="Bold">Blah blah blah</TextBlock>
    </Grid>

    <Grid Name="mainbackground">
        <Grid.Background>
            <LinearGradientBrush
                ColorInterpolationMode="ScRgbLinearInterpolation"
                EndPoint="1,0">
                <GradientStop x:Name="stop1"
                    Color="#FF0000"
                    Offset="0" />
                <GradientStop x:Name="stop2"
                    Color="#00FF00"
                    Offset="0.5" />
                <GradientStop x:Name="stop3"
                    Color="#0000FF"
                    Offset="1" />
            </LinearGradientBrush>
        </Grid.Background>
    </Grid>

    <Grid Name="invertedforeground">
        <Grid.Background>
            <LinearGradientBrush
                ColorInterpolationMode="ScRgbLinearInterpolation"
                EndPoint="1,0">
                <GradientStop
                    Color="{Binding ElementName=stop1, Path=Color, Converter={StaticResource colorConverter}}"
                    Offset="0" />
                <GradientStop
                    Color="{Binding ElementName=stop2, Path=Color, Converter={StaticResource colorConverter}}"
                    Offset="0.5" />
                <GradientStop
                    Color="{Binding ElementName=stop3, Path=Color, Converter={StaticResource colorConverter}}"
                    Offset="1" />
            </LinearGradientBrush>
        </Grid.Background>
        <Grid.OpacityMask>
            <VisualBrush
                Visual="{Binding ElementName=mask}" />
        </Grid.OpacityMask>
    </Grid>
</Grid>

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


Редактировать: Я попытался установить перевернутую кисть переднего плана для текста, но она будет придерживаться координат TextBlock, поэтому я вернулся к предыдущему решению использовать текст как OpacityMask.


Редактировать 2: Я добавил пример использования пользовательского IValueConverter и привязал цвета текстового градиента к исходному градиенту. Вы также можете использовать привязку и конвертер где-то выше, например, связывание свойства invertedforeground Background со свойством mainbackground Background, и конвертер принимает входную градиентную кисть и возвращает другую градиентную кисть (это позволяет создавать градиент с конфигурацией, значительно отличающейся от исходной).

...