1

I'm working in WPF application with MVVM. Loading and Editing a image in imageSlider gives me error and exceptions.

Loading Image: Insufficient memory to handle Editing Image: Sharing violation

ViewModel:

public class ImageList : INotifyPropertyChanged
    {
        #region Fields
    private ObservableCollection<ImageItem> images = new ObservableCollection<ImageItem>();
    private int selectedIndex;
    private ImageItem currentImage;

    #endregion Fields

    #region Properties

    public ObservableCollection<ImageItem> Images
    {
        get { return images; }
        set { images = value; }
    }

    public int SelectedIndex
    {
        get { return selectedIndex; }
        set
        {
            if(value < Images.Count && value > -1)
            {
                selectedIndex = value; OnPropertyChanged();
                CurrentImage = Images[selectedIndex];
            }
        }
    }

    public ImageItem CurrentImage
    {
        get { return currentImage; }
        set { currentImage = value; OnPropertyChanged(); }
    }

    #endregion Properties

    #region Public Methods

    public void Next()
    {
        SelectedIndex ++;
    }

    public void Back()
    {
        SelectedIndex--;
    }

    #endregion Public Methods

    #region Methods

    public void AddNewImage()
    {
        OpenFileDialog dlg = new OpenFileDialog();
        dlg.Filter = "Image files (*.jpg, *.jpeg, *.jpe, *.gif, *.png, *.bmp, *.tif) | *.jpg; *.jpeg; *.jpe; *.gif; *.png, *.bmp, *.tif";
        dlg.ShowDialog();

        if(dlg.FileName != "")
        {
            Images.Add(new ImageItem() { URI = new Uri(dlg.FileName) });
            SelectedIndex = Images.Count - 1;
        }
    }

    #endregion Methods

    #region Constructors

    public ImageList()
    {
        _canExecute = true;
    }

    #endregion Constructors

    #region Commands

    private ICommand _clickCommand;
    public ICommand ClickCommand
    {
        get
        {
            return _clickCommand ?? (_clickCommand = new CommandHandler(() => AddNewImage(), _canExecute));
        }
    }

    private bool _canExecute;

    private ICommand nextCommand;

    public ICommand NextCommand
    {
        get
        {
            if (nextCommand == null)
            {
                nextCommand = new CommandHandler(()=> OnNextCommand(), true);
            }
            return nextCommand;
        }
        set { nextCommand = value; }
    }

    private void OnNextCommand()
    {
        Next();
    }
    private ICommand backCommand;

    public ICommand BackCommand
    {
        get
        {
            if(backCommand == null)
            {
                backCommand = new CommandHandler(() => OnBackCommand(), true);
            }
            return backCommand;
        }
        set { backCommand = value; }
    }

    private void OnBackCommand()
    {
        Back();
    }

 private ICommand _clickCommand;
        public ICommand ClickCommand
        {
            get
            {
                return _clickCommand ?? (_clickCommand = new CommandHandler(() => AddNewImage(), _canExecute));
            }
        }

        private ICommand _EditImageCommand;
        public ICommand EditImageCommand
        {
            get
            {
                return _EditImageCommand ?? (_EditImageCommand = new CommandHandler(obj => EditImage(obj), _canExecute));
            }
        }

    #endregion Commands

 public void EditImage(object obj)
        {
            string ss = ((ImageItem)obj).URI.AbsolutePath;
            Process proc = Process.Start(ss);
            if (proc != null)
            {
                proc.EnableRaisingEvents = true;
                ProcessStartInfo startInfo = new ProcessStartInfo();
                //startInfo.Verb = "edit";
                startInfo.FileName = ("mspaint.exe");
                proc.StartInfo = startInfo;

            }
        }


    #region INotifyPropertyChanged

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion INotifyPropertyChanged

}`

Model:

 public class ImageItem
    {
        public Uri URI { get; set; }

        private BitmapSource _Source;

        public BitmapSource Source
        {
            get
            {
                try
                {
                    if (_Source == null) _Source = new BitmapImage(URI);//lazy loading

                }
                catch (Exception)
                {
                    _Source = null;
                }
                return _Source;
            }
        }

        public void Save(string filename)
        {
            var img = BitmapFrame.Create(Source);
            var encoder = new JpegBitmapEncoder();

            encoder.Frames.Add(img);
            using (var saveStream = System.IO.File.OpenWrite(filename))
                encoder.Save(saveStream);

        }


    }

XAML

<DockPanel>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*"></RowDefinition>
                <RowDefinition Height="30"></RowDefinition>
                <RowDefinition Height="200"></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="30"></ColumnDefinition>
                <ColumnDefinition Width="*"></ColumnDefinition>
                <ColumnDefinition Width="30"></ColumnDefinition>
            </Grid.ColumnDefinitions>

        <Button Content="&lt;" Command="{Binding BackCommand}" Width="25" Grid.Row="0" Grid.Column="0"/>
            <Button DockPanel.Dock="Right" Content="&gt;" Command="{Binding NextCommand}" Width="25" Grid.Row="0" Grid.Column="2"/>
            <Image Source="{Binding CurrentImage.Source, Mode=OneWay}" Grid.Row="0" Grid.Column="1">
                <Image.ContextMenu>
                    <ContextMenu>
                        <MenuItem Header="Edit Image" Command="{Binding EditImageCommand, Source={StaticResource SliderViewModel}}" CommandParameter="{Binding CurrentImage}"></MenuItem>
                    </ContextMenu>
                </Image.ContextMenu>
            </Image>
            <Button Content="Add" Command="{Binding ClickCommand}" Height="30" Width="50" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4"></Button>
            <!--<Image Source="{Binding CurrentImage.Source, Mode=OneWay}" Grid.Row="2" Grid.Column="1"/>-->
        </Grid>
    </DockPanel>

The code above works fine with the imageSlider(Next and Back). But during edit it opens in mspaint and when i save it throws me Sharing violation error. Tired few solutions posted in SO here and here . When trying these i can able to edit the image and save without any error. But when using the Slider control (Next and Back) and when it loads heavier images, frequently it throws me Insufficient memory to handle exception.

So now i need to eliminate both these issues. Kindly help.

Community
  • 1
  • 1
A Coder
  • 3,039
  • 7
  • 58
  • 129
  • You already get an [answer](http://stackoverflow.com/questions/29097171/sharing-violation-on-image-view-and-edit-wpf), just change image loading mechanism at `ImageItem` on a given there, so it will create read file and create it's copy in the memory. About memory, WPF is total "crap" at image handling due to it's image weight. You 5Mb jpeg might grow up to ~100Mb at memory, so it's better to load image from file on the fly when someone switch to it, and holding only image's file path at other time. – Shakra Jul 29 '16 at 07:11
  • @Shakra So may be i could be wrong at my implementation. Handling BitmapSource , BitmapImage and ImageItem may be the issue. Can you please post the way? – A Coder Jul 29 '16 at 07:16

1 Answers1

1

You need to do things a little differently for an editor to a viewer

your Model needs to be updated to a ViewModel as it will be changing ( you can use the model to build the ViewModel, however in this case there is so little functionality in the Model that you might as well use the FIleInfo class as your model)

public class ImageEditor: IDisposable,INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private List<FileInfo> images = new List<FileInfo>();

    private FileInfo _ImageFile;
    public static readonly PropertyChangedEventArgs FilenameProperty = new PropertyChangedEventArgs(nameof(ImageFile));

    public FileInfo ImageFile
    {
        get { return _ImageFile; }
        set
        {
            _ImageFile = value;
            Strokes.Clear();
            PropertyChanged?.Invoke(this, ImageFrameProperty);
        }
    }

    private int selectedIndex;
    public static readonly PropertyChangedEventArgs SelectedIndexProperty = new PropertyChangedEventArgs(nameof(SelectedIndex));
    public int SelectedIndex
    {
        get { return selectedIndex; }
        private set
        {
            if (value < images.Count && value > -1)
            {
                selectedIndex = value;
                PropertyChanged?.Invoke(this, SelectedIndexProperty);
                ImageFile = images[selectedIndex];
                Load();
            }
        }
    }

    MemoryStream mem;
    private BitmapSource _ImageFrame;
    public static readonly PropertyChangedEventArgs ImageFrameProperty = new PropertyChangedEventArgs(nameof(ImageFrame));
    public BitmapSource ImageFrame
    {
        get { return _ImageFrame; }
        set
        {
            _ImageFrame = value;
            PropertyChanged?.Invoke(this, ImageFrameProperty);
        }
    }

    public StrokeCollection Strokes { get;  } = new StrokeCollection();

    public void Open(FileInfo file)
    {
        images.Add(file);
        SelectedIndex = images.Count - 1;
    }
    public void Next()
    {
        SelectedIndex++;
    }

    public void Back()
    {
        SelectedIndex--;
    }

    public void Load()
    {
        ImageFile.Refresh();
        if (ImageFile.Exists)
        {
            if (mem != null)
            {
                mem.Dispose();
            }
            using (var stream = ImageFile.OpenRead())
            {
                mem = new MemoryStream();
                stream.CopyTo(mem);
            }
            ImageFrame = BitmapFrame.Create(mem);
        }
    }
    public void Dispose()
    {
        if (mem != null)
        {
            mem.Dispose();
        }
        ImageFrame = null;
    }

    public void Save()
    {

        DrawingVisual drawingVisual = new DrawingVisual();
        using (DrawingContext drawingContext = drawingVisual.RenderOpen())
        {

            drawingContext.DrawImage(ImageFrame, new Rect(0, 0, ImageFrame.Width, ImageFrame.Height));
            foreach (var item in Strokes)
            {
                item.Draw(drawingContext);
            }
            drawingContext.Close();
            Strokes.Clear();
            var width = ImageFrame.Width;
            var height = ImageFrame.Height;
            var bitmap = new RenderTargetBitmap((int)width, (int)height, 96, 96, PixelFormats.Pbgra32);
            bitmap.Render(drawingVisual);

            ImageFrame = bitmap;

            var encoder = new PngBitmapEncoder();
            encoder.Frames.Add(BitmapFrame.Create(ImageFrame));

            using (var stream = ImageFile.OpenWrite())
            {
                encoder.Save(stream);
            }
        }
    }
}

note: as we are using a memory stream with out the using statement then its a good idea to us the IDisposable interface for clean up

what this is doing is creating a memory resident bitmap then using that as a Frame, this removes the openRead lock on on the file that you get with a normal bitmap with a URI

next we have the Editor itself

<Window x:Class="ImageDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ImageDemo"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525" Closing="Window_Closing" >
    <Window.DataContext>
        <local:ImageEditor x:Name="editor" />

    </Window.DataContext>

    <DockPanel>
        <Menu DockPanel.Dock="Top" >
            <MenuItem Header="File" >
                <MenuItem Command="ApplicationCommands.Open" />
                <MenuItem Command="ApplicationCommands.Save" />
            </MenuItem>
            <MenuItem Command="ApplicationCommands.Undo" />
            <ComboBox SelectedValue="{Binding DefaultDrawingAttributes.Color, ElementName=inkCanvas}">
                <Color>White</Color>
                <Color>Black</Color>
                <Color>Yellow</Color>
                <Color>Red</Color>
                <Color>Cyan</Color>
                <Color>SpringGreen</Color>
                <ComboBox.ItemTemplate>
                    <DataTemplate>
                        <Rectangle Width="15" Height="15">
                            <Rectangle.Fill>
                                <SolidColorBrush Color="{Binding Mode=OneWay}" />
                            </Rectangle.Fill>
                        </Rectangle>
                    </DataTemplate>
                </ComboBox.ItemTemplate>
            </ComboBox>
        </Menu>
        <Button Content="&lt;" Click="Back_Click"/>
        <Button Content="&gt;" DockPanel.Dock="Right" Click="Next_Click"/>
        <Grid HorizontalAlignment="Left" VerticalAlignment="Top" ScrollViewer.HorizontalScrollBarVisibility="Auto" ScrollViewer.VerticalScrollBarVisibility="Auto">
                <Image Source="{Binding ImageFrame}" Stretch="None"/>
                <InkCanvas x:Name="inkCanvas" Background="Transparent" Strokes="{Binding Strokes}" >
                    <InkCanvas.DefaultDrawingAttributes>
                        <DrawingAttributes x:Name="DrawSetting" />
                    </InkCanvas.DefaultDrawingAttributes>
                </InkCanvas>
            </Grid>
    </DockPanel>
</Window>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        CommandBindings.Add(new CommandBinding(ApplicationCommands.Save, execSave, hasChanged));
        CommandBindings.Add(new CommandBinding(ApplicationCommands.Open, execOpen));
        CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, execUndo, hasChanged));
    }

    private void execOpen(object sender, ExecutedRoutedEventArgs e)
    {
        OpenFileDialog dlg = new OpenFileDialog();

        if(dlg.ShowDialog() ?? false)
        {
            editor.Open(new System.IO.FileInfo(dlg.FileName));
        }
        e.Handled = true;
    }

    private void hasChanged(object sender, CanExecuteRoutedEventArgs e)
    {
        e.CanExecute = editor.Strokes.Count > 0;
        e.Handled = true;
    }

    private void execUndo(object sender, ExecutedRoutedEventArgs e)
    {
        editor.Strokes.Remove(editor.Strokes.Last());
        e.Handled = true;
    }

    private void execSave(object sender, ExecutedRoutedEventArgs e)
    {
        editor.Save();
        e.Handled = true;
    }

    private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        editor.Dispose();
    }

    private void Next_Click(object sender, RoutedEventArgs e)
    {
        editor.Next();
    }
    private void Back_Click(object sender, RoutedEventArgs e)
    {
        editor.Back();
    }
}

Note if you are doing any scaling/stretching on the image then you need to reverse the operation on the Strokes before rendering them or or the edits will be in relation to the screen not the image

MikeT
  • 5,398
  • 3
  • 27
  • 43
  • On Loading a image i'm getting the `FileFormatException` in the line `ImageFrame = BitmapFrame.Create(mem)`. The error is `the image cannot be decoded. the image header might be corrupted`. – A Coder Aug 01 '16 at 06:41
  • the error is saying that the file is not a valid image, check first that the path is correct when the error is thrown, if your path is invalid pointing at the wrong file that would explain it, second you may need to reset the memory stream position, it worked in my tests but sometimes needs resetting `mem.Position = 0;` before the frame create – MikeT Aug 01 '16 at 11:02
  • Worked good with the second option. But the Save/Undo isn't enabled after the edit. Can u see it? I wonder how about this working with `mspaint`. Does it? – A Coder Aug 01 '16 at 11:21
  • the enabled or disabled is in the hasChanged method debug to see if you are getting the right values out of it, or just remove it entirely it adds a nice touch to have the buttons disabled when you don't need them but its not a vital function, also double check you have the command bound ie `Command="ApplicationCommands.Undo"` and `CommandBindings.Add(new CommandBinding(ApplicationCommands.Undo, execUndo, hasChanged));` o prefer the code behind route to command binding as it seems mor logical to me but its not the only way to do it see http://www.wpf-tutorial.com/commands/using-commands/ – MikeT Aug 01 '16 at 12:06
  • 1
    also double check your stroke binding `Strokes="{Binding Strokes}"` this is pointing at the strokes collection on the data context, if your 'hasChanged' function is referring to a different strokes collection then it wont be updating when you use the Inkcanvas to draw – MikeT Aug 01 '16 at 12:14
  • Working good. But using `mspaint` and calling Save method isn't working. Is there anyway to override the Save event of `mspaint`? so that i can merge this method into that event. How far it is possible? – A Coder Aug 02 '16 at 08:11
  • Not entirely sure what you mean by using MS paint – MikeT Aug 02 '16 at 09:06
  • I want to edit the image using MS paint. So is it possible here? – A Coder Aug 02 '16 at 09:16
  • Did u get what i'm trying to say? – A Coder Aug 02 '16 at 09:45
  • If you just want to open the viewed image in MS paint then that is much easier, you just need to feed the filename into MS paint using a Process see https://msdn.microsoft.com/en-us/library/system.diagnostics.process(v=vs.110).aspx but as you a launching an external process you can't control it programatically – MikeT Aug 02 '16 at 15:32
  • Integrated into my application and working fine. One question is what is the difference between the save in MS paint vs Save method here? – A Coder Aug 04 '16 at 07:01
  • The save marked here processes the image then saves it, MS paint will do something similar but almost certainly different – MikeT Aug 04 '16 at 08:09
  • Kindly take a look http://stackoverflow.com/questions/38782898/images-loading-partially – A Coder Aug 05 '16 at 11:36