Необходимо реализовать обрезку изображения по кругу с выбором области обрезки по типу, как это сделано, например, тут либо другой способ выбора области обрезки, лишь бы ее было удобно выбирать мышкой с возможностью масштаба/перемещения. В какую сторону смотреть? Может, кто-то сталкивался с инструкциями на эту тему?
Пример получается довольно большой, поэтому я его упрощу: пути для входного и выходного файла захардкожены, диалоги для их выбора добавите самостоятельно; исходное изображение не масштабируется, вместо этого масштабируется окружность; для перемещения области вырезания используются простые клики левой кнопкой мыши.
Итак, содержимое окна:
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="128"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel>
<TextBlock Text="Исходное:"/>
<Image Source="{Binding BlankImage}" Margin="0,0,0,5"/>
<TextBlock Text="Обрезанное:"/>
<Image Source="{Binding CroppedImage}"/>
</StackPanel>
<Grid Grid.Column="1" Margin="5,0,0,0">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid local:GesturesEx.MouseWheelDownCommand="{Binding DecRadius}"
local:GesturesEx.MouseWheelUpCommand="{Binding IncRadius}"
local:GesturesEx.MouseLeftButtonDownCommand="{Binding MoveTo}">
<Image Source="{Binding BlankImage}" Stretch="None"
VerticalAlignment="Top" HorizontalAlignment="Left"/>
<Path Fill="#66000000">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Exclude">
<CombinedGeometry.Geometry1>
<!--Чтобы накрыть всю картинку, в идеале здесь должна быть привязка к размерам родительского Grid-->
<RectangleGeometry Rect="0,0,10000,10000"/>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry Center="{Binding Center}"
RadiusX="{Binding Radius}"
RadiusY="{Binding Radius}"/>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
</Grid>
<UniformGrid Grid.Row="1" Margin="0,5,0,0" Rows="1"
HorizontalAlignment="Right">
<UniformGrid.Resources>
<Style TargetType="Button">
<Setter Property="Margin" Value="5,0,0,0"/>
<Setter Property="Padding" Value="10,2"/>
</Style>
</UniformGrid.Resources>
<Button Content="Вырезать" Command="{Binding Crop}"/>
<Button Content="Сохранить" Command="{Binding Save}"/>
</UniformGrid>
</Grid>
</Grid>
Вроде ничего сильно сложного, обычные привязки, всё самое интересное происходит в VM. Ну и для запуска команд при прокрутке колеса мыши и кликов написан простой класс с тремя AP:
public static class GesturesEx
{
public static ICommand GetMouseWheelUpCommand(DependencyObject obj)
=> (ICommand)obj.GetValue(MouseWheelUpCommandProperty);
public static void SetMouseWheelUpCommand(DependencyObject obj, ICommand value)
=> obj.SetValue(MouseWheelUpCommandProperty, value);
public static readonly DependencyProperty MouseWheelUpCommandProperty =
DependencyProperty.RegisterAttached("MouseWheelUpCommand", typeof(ICommand),
typeof(GesturesEx), new PropertyMetadata(null, MouseWheelUpCommandChangedCallback));
static void MouseWheelUpCommandChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = (UIElement)d;
if (e.OldValue != null) element.MouseWheel -= OnMouseWheel;
if (e.NewValue != null) element.MouseWheel += OnMouseWheel;
void OnMouseWheel(object sender, MouseWheelEventArgs args)
{
if (args.Delta > 0) GetMouseWheelUpCommand(d).Execute(null);
}
}
public static ICommand GetMouseWheelDownCommand(DependencyObject obj)
=> (ICommand)obj.GetValue(MouseWheelDownCommandProperty);
public static void SetMouseWheelDownCommand(DependencyObject obj, ICommand value)
=> obj.SetValue(MouseWheelDownCommandProperty, value);
public static readonly DependencyProperty MouseWheelDownCommandProperty =
DependencyProperty.RegisterAttached("MouseWheelDownCommand", typeof(ICommand),
typeof(GesturesEx), new PropertyMetadata(null, MouseWheelDownCommandChangedCallback));
static void MouseWheelDownCommandChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = (UIElement)d;
if (e.OldValue != null) element.MouseWheel -= OnMouseWheel;
if (e.NewValue != null) element.MouseWheel += OnMouseWheel;
void OnMouseWheel(object sender, MouseWheelEventArgs args)
{
if (args.Delta < 0) GetMouseWheelDownCommand(d).Execute(null);
}
}
public static ICommand GetMouseLeftButtonDownCommand(DependencyObject obj)
=> (ICommand)obj.GetValue(MouseLeftButtonDownCommandProperty);
public static void SetMouseLeftButtonDownCommand(DependencyObject obj, ICommand value)
=> obj.SetValue(MouseLeftButtonDownCommandProperty, value);
public static readonly DependencyProperty MouseLeftButtonDownCommandProperty =
DependencyProperty.RegisterAttached("MouseLeftButtonDownCommand", typeof(ICommand),
typeof(GesturesEx), new PropertyMetadata(null, MouseLeftButtonDownCommandChangedCallback));
static void MouseLeftButtonDownCommandChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = (UIElement)d;
if (e.OldValue != null) element.MouseLeftButtonDown -= OnMouseLeftButtonDown;
if (e.NewValue != null) element.MouseLeftButtonDown += OnMouseLeftButtonDown;
void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
=> GetMouseLeftButtonDownCommand(element).Execute(args.GetPosition(element));
}
}
В нем тоже ничего сложного, обычные AP, да и к делу он не сильно относится.
Теперь VM, используется стандартный базовый класс для нее:
abstract class Vm : INotifyPropertyChanged
{
protected bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
NotifyPropertyChanged(propertyName);
return true;
}
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged;
}
Также повсеместно в VM используется следующая реализация ICommand
:
public class DelegateCommand : ICommand
{
protected readonly Predicate<object> _canExecute;
protected readonly Action<object> _execute;
public event EventHandler CanExecuteChanged;
public DelegateCommand(Action<object> execute) : this(execute, _ => true) { }
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute ?? throw new ArgumentNullException(nameof(canExecute));
}
public bool CanExecute(object parameter) => _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
public void RaiseCanExecuteChanged()
=> CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
Сама главная VM:
class MainVm : Vm
{
// Исходное изображение
ImageSource blankImage;
public ImageSource BlankImage
{
get => blankImage;
set => Set(ref blankImage, value);
}
// Обрезанное изображение
BitmapFrame croppedImage;
public BitmapFrame CroppedImage
{
get => croppedImage;
set => Set(ref croppedImage, value);
}
// Координаты центра окружности
Point center;
public Point Center
{
get => center;
set => Set(ref center, value);
}
// Радиус окружности
double radius;
public double Radius
{
get => radius;
set => Set(ref radius, value);
}
// Команды для увеличения/уменьшения радиуса и перемещения окружности
public ICommand IncRadius { get; }
public ICommand DecRadius { get; }
public ICommand MoveTo { get; }
// Команды для обрезки и сохранения картинки
public ICommand Crop { get; }
public ICommand Save { get; }
public MainVm()
{
IncRadius = new DelegateCommand(_ => Radius = Math.Min(Radius + 10, 1000));
DecRadius = new DelegateCommand(_ => Radius = Math.Max(Radius - 10, 10));
MoveTo = new DelegateCommand(o => Center = (Point)o);
Crop = new DelegateCommand(_ => CropImage());
Save = new DelegateCommand(_ => SaveImage());
BlankImage = new BitmapImage(new Uri(@"...\bear.jpg"));
Radius = 50;
Center = new Point(BlankImage.Width / 2, BlankImage.Height / 2);
}
void CropImage()
{
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
// Прямоугольник (квадрат), который нужно взять в исходном изображении
var cropRect = new Rect(
x: Center.X - Radius,
y: Center.Y - Radius,
width: 2 * Radius,
height: 2 * Radius);
// Вырезаем из исходного изображения и делаем из него кисть
var brush = new ImageBrush(BlankImage)
{
ViewboxUnits = BrushMappingMode.Absolute,
Viewbox = cropRect
};
// Рисуем эллипс, закрашенный полученной кистью
drawingContext.DrawEllipse(
brush, pen: null,
center: new Point(Radius, Radius),
radiusX: Radius,
radiusY: Radius);
}
// Переносим полученное рисование в точечное изображение
var bmp = new RenderTargetBitmap(
(int)(2 * Radius), (int)(2 * Radius),
96, 96, PixelFormats.Pbgra32);
bmp.Render(drawingVisual);
var frame = BitmapFrame.Create(bmp);
frame.Freeze();
CroppedImage = frame;
}
void SaveImage()
{
if (CroppedImage == null) return;
// Сохраняем точечное изображение в файл в PNG-формате
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(CroppedImage);
using (var stream = File.Create(@"...\crop.png"))
encoder.Save(stream);
}
}
Ну и не забудьте установить DataContext для окна, например, как здесь в App.xaml.cs
Пример работы программы:
На выходе получаем:
Виртуальный выделенный сервер (VDS) становится отличным выбором
Делаю постер товаров в ОдноклассникиВсе права получены, использую вечный токен доступа
Например у меня есть таблицы campaigns и campaign_channels, я создаю обьект Campaign и CampaignChannels, в базе создаются воответствующие записи, но когда я хочу вытянуть...
Какие есть бесплатные способы сжатия изображений при загрузке на сайт? Сайт построен на FuelPHP