Исключить текст метки из границы
У меня есть серебряная рамка с текстом сверху, которую я хотел бы исключить (обрезать текст от границы), чтобы видеть фон за границей, к которому применен растровый эффект в виде капли тени. Возможно ли это, не заходя в фотошоп, чтобы создать изображение, которое в основном делает то же самое, не будучи настолько гибким?
Если возможно, как бы я пошел для выполнения такой задачи?
2 ответа
Я немного поиграл с этим и в итоге получил следующее.
Сложнее было "инвертировать" OpacityMask для границы. Я создал класс, производный от Image, который добавляет несколько свойств зависимости, таких как Text, FontFamily и EmSize. Затем он превращает текст в геометрию, используя подход из этой ссылки.
Вы можете поэкспериментировать с Text, FontFamily, EmSize, Width и Height, пока не получите нужный результат. Конечно, вы также можете добавить дополнительные DP в InvertOpacityText для повышения гибкости.
Я закончил с этим
<Grid Background="Blue">
<Border Background="Gray" CornerRadius="8,8,8,8" Width="240" Height="220">
<Border.Effect>
<DropShadowEffect ShadowDepth="10"
Direction="310"
Color="Black"
Opacity="0.8"
BlurRadius="4"/>
</Border.Effect>
<Border.OpacityMask>
<VisualBrush>
<VisualBrush.Visual>
<local:InvertOpacityText Text=" Now Playing"
EmSize="70"
Stretch="Fill"
Width="510"
Height="414"
FontFamily="Broadway">
<local:InvertOpacityText.LayoutTransform>
<RotateTransform Angle="-90"/>
</local:InvertOpacityText.LayoutTransform>
</local:InvertOpacityText>
</VisualBrush.Visual>
</VisualBrush>
</Border.OpacityMask>
<Image Margin="45,5,5,5" Source="C:\PhilCollins.png"/>
</Border>
</Grid>
InvertOpacityText.cs
public class InvertOpacityText : Image
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text",
typeof(string),
typeof(InvertOpacityText),
new FrameworkPropertyMetadata(string.Empty,
TargetPropertyChanged));
public static readonly DependencyProperty EmSizeProperty =
DependencyProperty.Register("EmSize",
typeof(double),
typeof(InvertOpacityText),
new FrameworkPropertyMetadata(70.0,
TargetPropertyChanged));
public static readonly DependencyProperty FontFamilyProperty =
DependencyProperty.Register("FontFamily",
typeof(FontFamily),
typeof(InvertOpacityText),
new FrameworkPropertyMetadata(new FontFamily(),
TargetPropertyChanged));
private static void TargetPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
InvertOpacityText invertOpacityText = (InvertOpacityText)source;
invertOpacityText.OnTextChanged();
}
public string Text
{
get { return (string)base.GetValue(TextProperty); }
set { base.SetValue(TextProperty, value); }
}
public double EmSize
{
get { return (double)base.GetValue(EmSizeProperty); }
set { base.SetValue(EmSizeProperty, value); }
}
public FontFamily FontFamily
{
get { return (FontFamily)base.GetValue(FontFamilyProperty); }
set { base.SetValue(FontFamilyProperty, value); }
}
private void OnTextChanged()
{
if (Source == null)
{
Source = CreateBitmapSource();
}
FormattedText tx = new FormattedText(Text,
Thread.CurrentThread.CurrentUICulture,
FlowDirection.LeftToRight,
new Typeface(FontFamily,
FontStyles.Normal,
FontWeights.Bold,
FontStretches.Normal),
EmSize,
Brushes.Black);
Geometry textGeom = tx.BuildGeometry(new Point(0, 0));
Rect boundingRect = new Rect(new Point(-100000, -100000), new Point(100000, 100000));
RectangleGeometry boundingGeom = new RectangleGeometry(boundingRect);
GeometryGroup group = new GeometryGroup();
group.Children.Add(boundingGeom);
group.Children.Add(textGeom);
Clip = group;
}
private BitmapSource CreateBitmapSource()
{
int width = 128;
int height = width;
int stride = width / 8;
byte[] pixels = new byte[height * stride];
List<System.Windows.Media.Color> colors = new List<System.Windows.Media.Color>();
colors.Add(System.Windows.Media.Colors.Red);
colors.Add(System.Windows.Media.Colors.Blue);
colors.Add(System.Windows.Media.Colors.Green);
BitmapPalette myPalette = new BitmapPalette(colors);
return BitmapSource.Create(width,
height,
96,
96,
PixelFormats.Indexed1,
myPalette,
pixels,
stride);
}
}
Вот решение, которое работает путем создания нового FrameworkElement
называется HollowTextBlock
который заполняет свой выделенный размер с его Background
и отсекает Text
оставляя это прозрачным. Полноценная реализация потребует поддержки гораздо большего количества свойств, но это доказывает, что концепция работает.
Во-первых, вот пример XAML:
<Grid>
<Grid>
<Grid.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="#FF0000" Offset="0" />
<GradientStop Color="#0000FF" Offset="1" />
</LinearGradientBrush>
</Grid.Background>
</Grid>
<Grid>
<local:HollowTextBlock Width="200" Height="50" Text="Hello, world!" Background="White" HorizontalAlignment="Center"/>
</Grid>
</Grid>
который определяет красочный градиентный фон позади текста, чтобы мы могли видеть, что он работает. Затем мы создаем наш HollowTextBlock
и в прототипе Width
, Height
, Text
а также Background
все должны быть указаны.
Тогда вот наша реализация HollowTextBlock
:
public class HollowTextBlock : FrameworkElement
{
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(HollowTextBlock), new UIPropertyMetadata(string.Empty));
public Brush Background
{
get { return (Brush)GetValue(BackgroundProperty); }
set { SetValue(BackgroundProperty, value); }
}
public static readonly DependencyProperty BackgroundProperty =
TextElement.BackgroundProperty.AddOwner(typeof(HollowTextBlock), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender));
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var extent = new RectangleGeometry(new Rect(0.0, 0.0, RenderSize.Width, RenderSize.Height));
var face = new Typeface("Arial");
var size = 32;
var ft = new FormattedText(Text, Thread.CurrentThread.CurrentUICulture, FlowDirection.LeftToRight, face, size, Brushes.Black);
var hole = ft.BuildGeometry(new Point((RenderSize.Width - ft.Width) / 2, (RenderSize.Height - ft.Height) / 2));
var combined = new CombinedGeometry(GeometryCombineMode.Exclude, extent, hole);
drawingContext.PushClip(combined);
drawingContext.DrawRectangle(Background, null, new Rect(0.0, 0.0, RenderSize.Width, RenderSize.Height));
drawingContext.Pop();
}
}
и визуализированный вывод выглядит так:
NB При использовании вы должны быть осторожны, чтобы не положить его поверх чего-то, что затемнит реальный фон, который вы хотели бы показать через.