/* * Greenshot - a free and open source screenshot tool * Copyright (C) 2007-2012 Thomas Braun, Jens Klingen, Robin Krom * * For more information see: http://getgreenshot.org/ * The Greenshot project is hosted on Sourceforge: http://sourceforge.net/projects/greenshot/ * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Windows.Forms; using Greenshot.Configuration; using Greenshot.Drawing.Fields; using Greenshot.Helpers; using Greenshot.Plugin; using Greenshot.Plugin.Drawing; using GreenshotPlugin.Core; using Greenshot.Memento; using Greenshot.IniFile; using Greenshot.Drawing.Filters; using System.Drawing.Drawing2D; using GreenshotPlugin.Controls; namespace Greenshot.Drawing { /// /// Description of Surface. /// public class Surface : Control, ISurface { private static log4net.ILog LOG = log4net.LogManager.GetLogger(typeof(Surface)); public static int Count = 0; private static CoreConfiguration conf = IniConfig.GetIniSection(); /// /// Event handlers (do not serialize!) /// [NonSerialized] private SurfaceElementEventHandler movingElementChanged; public event SurfaceElementEventHandler MovingElementChanged { add { movingElementChanged += value; } remove { movingElementChanged -= value; } } [NonSerialized] private SurfaceDrawingModeEventHandler drawingModeChanged; public event SurfaceDrawingModeEventHandler DrawingModeChanged { add { drawingModeChanged += value; } remove { drawingModeChanged -= value; } } [NonSerialized] private SurfaceSizeChangeEventHandler surfaceSizeChanged; public event SurfaceSizeChangeEventHandler SurfaceSizeChanged { add { surfaceSizeChanged += value; } remove { surfaceSizeChanged -= value; } } [NonSerialized] private SurfaceMessageEventHandler surfaceMessage; public event SurfaceMessageEventHandler SurfaceMessage { add { surfaceMessage += value; } remove { surfaceMessage -= value; } } /// /// inUndoRedo makes sure we don't undo/redo while in a undo/redo action /// [NonSerialized] private bool inUndoRedo = false; /// /// Make only one surfacemove cycle undoable, see SurfaceMouseMove /// [NonSerialized] private bool isSurfaceMoveMadeUndoable = false; /// /// Undo/Redo stacks, should not be serialized as the file would be way to big /// [NonSerialized] private Stack undoStack = new Stack(); [NonSerialized] private Stack redoStack = new Stack(); /// /// Last save location, do not serialize! /// [NonSerialized] private string lastSaveFullPath = null; /// /// current drawing mode, do not serialize! /// [NonSerialized] private DrawingModes drawingMode = DrawingModes.None; /// /// the keyslocked flag helps with focus issues /// [NonSerialized] private bool keysLocked = false; /// /// Location of the mouse-down (it "starts" here), do not serialize /// [NonSerialized] private Point mouseStart = Point.Empty; /// /// are we in a mouse down, do not serialize /// [NonSerialized] private bool mouseDown = false; /// /// are we dragging, do not serialize /// [NonSerialized] private bool draggingInProgress = false; /// /// The selected element for the mouse down, do not serialize /// [NonSerialized] private IDrawableContainer mouseDownElement = null; /// /// all selected elements, do not serialize /// [NonSerialized] private DrawableContainerList selectedElements = new DrawableContainerList(); /// /// the element we are drawing with, do not serialize /// [NonSerialized] private IDrawableContainer drawingElement = null; /// /// the element we want to draw with (not yet drawn), do not serialize /// [NonSerialized] private IDrawableContainer undrawnElement = null; /// /// the cropcontainer, when cropping this is set, do not serialize /// [NonSerialized] private IDrawableContainer cropContainer = null; /// /// the brush which is used for transparent backgrounds, set by the editor, do not serialize /// [NonSerialized] private Brush transparencyBackgroundBrush; /// /// The buffer is only for drawing on it when using filters (to supply access) /// This saves a lot of "create new bitmap" commands /// Should not be serialized, as it's generated. /// The actual bitmap is in the paintbox... /// TODO: Check if this buffer is still needed! /// [NonSerialized] private Bitmap buffer = null; /// /// all elements on the surface, needed with serialization /// private DrawableContainerList elements = new DrawableContainerList(); /// /// all elements on the surface, needed with serialization /// private FieldAggregator fieldAggregator = new FieldAggregator(); /// /// the cursor container, needed with serialization as we need a direct acces to it. /// private IDrawableContainer cursorContainer = null; /// /// the capture details, needed with serialization /// private ICaptureDetails captureDetails = null; /// /// the modified flag specifies if the surface has had modifications after the last export. /// Initial state is modified, as "it's not saved" /// After serialization this should actually be "false" (the surface came from a stream) /// For now we just serialize it... /// private bool modified = true; /// /// The image is the actual captured image, needed with serialization /// private Image image = null; public Image Image { get { return image; } set { image = value; Size = image.Size; } } /// /// The field aggregator is that which is used to have access to all the fields inside the currently selected elements. /// e.g. used to decided if and which line thickness is shown when multiple elements are selected. /// public FieldAggregator FieldAggregator { get { return fieldAggregator; } set { fieldAggregator = value; } } /// /// The cursor container has it's own accessor so we can find and remove this (when needed) /// public IDrawableContainer CursorContainer { get { return cursorContainer; } } /// /// A simple getter to ask if this surface has a cursor /// public bool HasCursor { get { return cursorContainer != null; } } /// /// A simple helper method to remove the cursor from the surface /// public void RemoveCursor() { RemoveElement(cursorContainer, true); cursorContainer = null; } /// /// The brush which is used to draw the transparent background /// public Brush TransparencyBackgroundBrush { get { return transparencyBackgroundBrush; } set { transparencyBackgroundBrush = value; } } /// /// Are the keys on this surface locked? /// public bool KeysLocked { get { return keysLocked; } set { keysLocked = value; } } /// /// Is this surface modified? This is only true if the surface has not been exported. /// public bool Modified { get { return modified; } set { modified = value; } } /// /// The DrawingMode property specifies the mode for drawing, more or less the element type. /// public DrawingModes DrawingMode { get {return drawingMode;} set { drawingMode = value; if (drawingModeChanged != null) { drawingModeChanged.Invoke(this, drawingMode); } DeselectAllElements(); CreateUndrawnElement(); } } /// /// Property for accessing the last save "full" path /// public string LastSaveFullPath { get { return lastSaveFullPath; } set { lastSaveFullPath = value; } } /// /// Property for accessing the URL to which the surface was recently uploaded /// public string UploadURL { get; set; } /// /// Property for accessing the capture details /// public ICaptureDetails CaptureDetails { get { return captureDetails; } set { captureDetails = value; } } /// /// Base Surface constructor /// public Surface() : base(){ Count++; LOG.Debug("Creating surface!"); this.MouseDown += new MouseEventHandler(SurfaceMouseDown); this.MouseUp += new MouseEventHandler(SurfaceMouseUp); this.MouseMove += new MouseEventHandler(SurfaceMouseMove); this.MouseDoubleClick += new MouseEventHandler(SurfaceDoubleClick); this.Paint += new PaintEventHandler(SurfacePaint); this.AllowDrop = true; this.DragDrop += new DragEventHandler(OnDragDrop); this.DragEnter += new DragEventHandler(OnDragEnter); // bind selected & elements to this, otherwise they can't inform of modifications this.selectedElements.Parent = this; this.elements.Parent = this; // Make sure we are visible this.Visible = true; this.TabStop = false; // Enable double buffering this.DoubleBuffered = true; this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw | ControlStyles.ContainerControl | ControlStyles.OptimizedDoubleBuffer | ControlStyles.SupportsTransparentBackColor, true); } /// /// Private method, the current image is disposed the new one will stay. /// /// The new image /// true if the old image needs to be disposed, when using undo this should not be true!! private void SetImage(Image newImage, bool dispose) { // Dispose if (image != null && dispose) { image.Dispose(); } // Set new values Image = newImage; Size = newImage.Size; modified = true; } /// /// Surface constructor with an image /// /// public Surface(Image newImage) : this() { LOG.Debug("Got image with dimensions " + newImage.Width + "," + newImage.Height + " bpp: " + newImage.PixelFormat); SetImage(newImage, true); } /// /// Surface contructor with a capture /// /// public Surface(ICapture capture) : this(capture.Image) { // Make sure the image is NOT disposed, we took the reference directly into ourselves ((Capture)capture).NullImage(); if (capture.Cursor != null && capture.CursorVisible) { cursorContainer = AddIconContainer(capture.Cursor, capture.CursorLocation.X, capture.CursorLocation.Y); SelectElement(cursorContainer); } captureDetails = capture.CaptureDetails; } /// /// The public accessible Dispose /// Will call the GarbageCollector to SuppressFinalize, preventing being cleaned twice /// public new void Dispose() { Count--; LOG.Debug("Disposing surface!"); if (buffer != null) { buffer.Dispose(); buffer = null; } if (transparencyBackgroundBrush != null) { transparencyBackgroundBrush.Dispose(); transparencyBackgroundBrush = null; } // Cleanup undo/redo stacks while (undoStack != null && undoStack.Count > 0) { undoStack.Pop().Dispose(); } while (redoStack != null && redoStack.Count > 0) { redoStack.Pop().Dispose(); } base.Dispose(); GC.SuppressFinalize(this); } /// /// Undo the last action /// public void Undo() { if (undoStack.Count > 0) { inUndoRedo = true; IMemento top = undoStack.Pop(); redoStack.Push(top.Restore()); inUndoRedo = false; } } /// /// Undo an undo (=redo) /// public void Redo() { if (redoStack.Count > 0) { inUndoRedo = true; IMemento top = redoStack.Pop(); undoStack.Push(top.Restore()); inUndoRedo = false; } } /// /// Returns if the surface can do a undo /// public bool CanUndo { get { return undoStack.Count > 0; } } /// /// Returns if the surface can do a redo /// public bool CanRedo { get { return redoStack.Count > 0; } } /// /// Get the language key for the undo action /// public LangKey UndoActionLanguageKey { get { if (CanUndo) { return undoStack.Peek().ActionLanguageKey; } else { return LangKey.none; } } } /// /// Get the language key for redo action /// public LangKey RedoActionLanguageKey { get { if (CanRedo) { return redoStack.Peek().ActionLanguageKey; } else { return LangKey.none; } } } /// /// Make an action undo-able /// /// The memento implementing the undo public void MakeUndoable(IMemento memento, bool allowMerge) { if (inUndoRedo) { throw new InvalidOperationException("Involking do within an undo/redo action."); } if (memento != null) { bool allowPush = true; if (undoStack.Count > 0 && allowMerge) { // Check if merge is possible allowPush = !undoStack.Peek().Merge(memento); } if (allowPush) { // Clear the redo-stack and dispose while(redoStack.Count > 0) { redoStack.Pop().Dispose(); } undoStack.Push(memento); } } } /// /// This saves the elements of this surface to a stream. /// Is used to save a template of the complete surface /// /// /// public long SaveElementsToStream(Stream streamWrite) { long bytesWritten = 0; try { long lengtBefore = streamWrite.Length; BinaryFormatter binaryWrite = new BinaryFormatter(); binaryWrite.Serialize(streamWrite, elements); bytesWritten = streamWrite.Length - lengtBefore; } catch (Exception e) { LOG.Error("Error serializing elements to stream.", e); } return bytesWritten; } /// /// This loads elements from a stream, among others this is used to load a surface. /// /// public void LoadElementsFromStream(Stream streamRead) { try { BinaryFormatter binaryRead = new BinaryFormatter(); DrawableContainerList loadedElements = (DrawableContainerList) binaryRead.Deserialize(streamRead); if (loadedElements != null) { loadedElements.Parent = this; DeselectAllElements(); AddElements(loadedElements); SelectElements(loadedElements); FieldAggregator.BindElements(loadedElements); } } catch (Exception e) { LOG.Error("Error serializing elements from stream.", e); } } /// /// This is called from the DrawingMode setter, which is not very correct... /// But here an element is created which is not yet draw, thus "undrawnElement". /// The element is than used while drawing on the surface. /// private void CreateUndrawnElement() { if(undrawnElement != null) { FieldAggregator.UnbindElement(undrawnElement); } switch (DrawingMode) { case DrawingModes.Rect: undrawnElement = new RectangleContainer(this); break; case DrawingModes.Ellipse: undrawnElement = new EllipseContainer(this); break; case DrawingModes.Text: undrawnElement = new TextContainer(this); break; case DrawingModes.Line: undrawnElement = new LineContainer(this); break; case DrawingModes.Arrow: undrawnElement = new ArrowContainer(this); break; case DrawingModes.Highlight: undrawnElement = new HighlightContainer(this); break; case DrawingModes.Obfuscate: undrawnElement = new ObfuscateContainer(this); break; case DrawingModes.Crop: cropContainer = new CropContainer(this); undrawnElement = cropContainer; break; case DrawingModes.Bitmap: undrawnElement = new BitmapContainer(this); break; case DrawingModes.Path: undrawnElement = new FreehandContainer(this); break; case DrawingModes.None: undrawnElement = null; break; } if (undrawnElement != null) { FieldAggregator.BindElement(undrawnElement); } } #region Plugin interface implementations public IBitmapContainer AddBitmapContainer(Bitmap bitmap, int x, int y) { BitmapContainer bitmapContainer = new BitmapContainer(this); bitmapContainer.Bitmap = bitmap; bitmapContainer.Left = x; bitmapContainer.Top = y; AddElement(bitmapContainer); return bitmapContainer; } public IBitmapContainer AddBitmapContainer(string filename, int x, int y) { BitmapContainer bitmapContainer = new BitmapContainer(this); bitmapContainer.Load(filename); bitmapContainer.Left = x; bitmapContainer.Top = y; AddElement(bitmapContainer); return bitmapContainer; } public IIconContainer AddIconContainer(Icon icon, int x, int y) { IconContainer iconContainer = new IconContainer(this); iconContainer.Icon = icon; iconContainer.Left = x; iconContainer.Top = y; AddElement(iconContainer); return iconContainer; } public IIconContainer AddIconContainer(string filename, int x, int y) { IconContainer iconContainer = new IconContainer(this); iconContainer.Load(filename); iconContainer.Left = x; iconContainer.Top = y; AddElement(iconContainer); return iconContainer; } public ICursorContainer AddCursorContainer(Cursor cursor, int x, int y) { CursorContainer cursorContainer = new CursorContainer(this); cursorContainer.Cursor = cursor; cursorContainer.Left = x; cursorContainer.Top = y; AddElement(cursorContainer); return cursorContainer; } public ICursorContainer AddCursorContainer(string filename, int x, int y) { CursorContainer cursorContainer = new CursorContainer(this); cursorContainer.Load(filename); cursorContainer.Left = x; cursorContainer.Top = y; AddElement(cursorContainer); return cursorContainer; } public IMetafileContainer AddMetafileContainer(string filename, int x, int y) { MetafileContainer metafileContainer = new MetafileContainer(this); metafileContainer.Load(filename); metafileContainer.Left = x; metafileContainer.Top = y; AddElement(metafileContainer); return metafileContainer; } public IMetafileContainer AddMetafileContainer(Metafile metafile, int x, int y) { MetafileContainer metafileContainer = new MetafileContainer(this); metafileContainer.Metafile = metafile; metafileContainer.Left = x; metafileContainer.Top = y; AddElement(metafileContainer); return metafileContainer; } public ITextContainer AddTextContainer(string text, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment, FontFamily family, float size, bool italic, bool bold, bool shadow, int borderSize, Color color, Color fillColor) { TextContainer textContainer = new TextContainer(this); textContainer.Text = text; textContainer.SetFieldValue(FieldType.FONT_FAMILY, family.Name); textContainer.SetFieldValue(FieldType.FONT_BOLD, bold); textContainer.SetFieldValue(FieldType.FONT_ITALIC, italic); textContainer.SetFieldValue(FieldType.FONT_SIZE, size); textContainer.SetFieldValue(FieldType.FILL_COLOR, fillColor); textContainer.SetFieldValue(FieldType.LINE_COLOR, color); textContainer.SetFieldValue(FieldType.LINE_THICKNESS, borderSize); textContainer.SetFieldValue(FieldType.SHADOW, shadow); // Make sure the Text fits textContainer.FitToText(); // Align to Surface textContainer.AlignToParent(horizontalAlignment, verticalAlignment); //AggregatedProperties.UpdateElement(textContainer); AddElement(textContainer); return textContainer; } #endregion #region DragDrop private List GetFilenames(DragEventArgs e) { List filenames = new List(); string[] dropFileNames = (string[])e.Data.GetData(DataFormats.FileDrop); if (dropFileNames != null && dropFileNames.Length > 0) { foreach(string filename in dropFileNames) { LOG.Debug("Found filename: " + filename); string ext=Path.GetExtension(filename).ToLower(); if ((ext==".jpg") || (ext==".jpeg") ||(ext==".tiff") || (ext==".gif") || (ext==".png") || (ext==".bmp") || (ext==".ico") ||(ext==".wmf")) { filenames.Add(filename); } } } return filenames; } private void OnDragEnter(object sender, DragEventArgs e) { if(LOG.IsDebugEnabled) { LOG.Debug("DragEnter got following formats: "); foreach(string format in e.Data.GetFormats()) { LOG.Debug(format); } } if (draggingInProgress || (e.AllowedEffect & DragDropEffects.Copy) != DragDropEffects.Copy) { e.Effect=DragDropEffects.None; } else { List filenames = GetFilenames(e); if ((filenames != null && filenames.Count > 0) || e.Data.GetDataPresent("DragImageBits") || e.Data.GetDataPresent(DataFormats.Bitmap, true) || e.Data.GetDataPresent(DataFormats.EnhancedMetafile, true)) { e.Effect=DragDropEffects.Copy; } else { e.Effect=DragDropEffects.None; } } } /// /// Handle the drag/drop /// /// /// private void OnDragDrop(object sender, DragEventArgs e) { List filenames = GetFilenames(e); Point mouse = this.PointToClient(new Point(e.X, e.Y)); if (e.Data.GetDataPresent("Text")) { string possibleUrl = (string)e.Data.GetData("Text"); // Test if it's an url and try to download the image so we have it in the original form if (possibleUrl != null && possibleUrl.StartsWith("http")) { using (Bitmap image = NetworkHelper.DownloadImage(possibleUrl)) { if (image != null) { AddBitmapContainer(image, mouse.X, mouse.Y); return; } } } } if ((filenames != null && filenames.Count > 0)) { foreach (string filename in filenames) { if (filename != null && filename.Trim().Length > 0) { LOG.Debug("Drop - filename: " + filename); if (filename.ToLower().EndsWith("wmf")) { AddMetafileContainer(filename, mouse.X, mouse.Y); } else { AddBitmapContainer(filename, mouse.X, mouse.Y); } mouse.Offset(10, 10); } } } else if (e.Data.GetDataPresent(DataFormats.Bitmap)) { AddBitmapContainer((Bitmap)e.Data.GetData(DataFormats.Bitmap, true), mouse.X, mouse.Y); } else if (e.Data.GetDataPresent(DataFormats.EnhancedMetafile)) { AddMetafileContainer((Metafile)e.Data.GetData(DataFormats.EnhancedMetafile, true), mouse.X, mouse.Y); } } // private void QueryContinueDragDrop(object sender, QueryContinueDragEventArgs e) { // LOG.Debug("QueryContinueDrag: " + e.Action); // if (e.EscapePressed) { // e.Action = DragAction.Cancel; // } // } // // private void GiveFeedbackDragDrop(object sender, GiveFeedbackEventArgs e) { // e.UseDefaultCursors = true; // } #endregion /// /// Auto crop the image /// /// true if cropped public bool AutoCrop() { Rectangle cropRectangle = ImageHelper.FindAutoCropRectangle(Image, conf.AutoCropDifference); if (isCropPossible(ref cropRectangle)) { DeselectAllElements(); // Maybe a bit obscure, but the following line creates a drop container // It's available as "undrawnElement" DrawingMode = DrawingModes.Crop; undrawnElement.Left = cropRectangle.X; undrawnElement.Top = cropRectangle.Y; undrawnElement.Width = cropRectangle.Width; undrawnElement.Height = cropRectangle.Height; undrawnElement.Status = EditStatus.UNDRAWN; AddElement(undrawnElement); SelectElement(undrawnElement); drawingElement = null; undrawnElement = null; return true; } return false; } /// /// "Grow" the canvas with the specified pixels on the left, right, top and bottom. Using the backgroundColor. /// /// /// /// /// /// public void GrowCanvas(Color backgroundColor, int left, int right, int top, int bottom) { Bitmap newImage = ImageHelper.GrowCanvas((Bitmap)Image, backgroundColor, left, right, top, bottom); // Make sure the elements move according to the offset the effect made the bitmap move elements.MoveBy(left, top); // Make undoable MakeUndoable(new SurfaceBackgroundChangeMemento(this, new Point(left, top)), false); SetImage(newImage, false); Invalidate(); if (surfaceSizeChanged != null) { surfaceSizeChanged(this); } } /// /// Resize bitmap /// /// /// /// /// /// public void ResizeBitmap(bool lockAspectRatio, bool canvasUsedNewSize, Color backgroundColor, int newWidth, int newHeight) { Point offset; Bitmap newImage = ImageHelper.ResizeBitmap((Bitmap)Image, lockAspectRatio, canvasUsedNewSize, backgroundColor, newWidth, newHeight, out offset); // Make sure the elements move according to the offset the effect made the bitmap move elements.MoveBy(offset.X, offset.Y); // Make undoable MakeUndoable(new SurfaceBackgroundChangeMemento(this, offset), false); SetImage(newImage, false); Invalidate(); if (surfaceSizeChanged != null) { surfaceSizeChanged(this); } } /// /// A simple clear /// /// The color for the background public void Clear(Color newColor) { //create a blank bitmap the same size as original Bitmap newBitmap = ImageHelper.CreateEmptyLike((Bitmap)Image, Color.Empty); if (newBitmap != null) { // Make undoable MakeUndoable(new SurfaceBackgroundChangeMemento(this, Point.Empty), false); SetImage(newBitmap, false); Invalidate(); } } /// /// Apply a bitmap effect to the surface /// /// public void ApplyBitmapEffect(Effects effect) { BackgroundForm backgroundForm = new BackgroundForm("Effect", "Please wait"); backgroundForm.Show(); Application.DoEvents(); try { Rectangle imageRectangle = new Rectangle(Point.Empty, Image.Size); Bitmap newImage = null; Point offset = new Point(-1,-1); switch (effect) { case Effects.Shadow: newImage = ImageHelper.CreateShadow((Bitmap)Image, 1f, 9, ref offset, PixelFormat.Format32bppArgb); //Image.PixelFormat); break; case Effects.TornEdge: using (Bitmap tmpImage = ImageHelper.CreateTornEdge((Bitmap)Image)) { newImage = ImageHelper.CreateShadow(tmpImage, 1f, 7, ref offset, PixelFormat.Format32bppArgb); //Image.PixelFormat); } break; case Effects.Border: newImage = ImageHelper.CreateBorder((Bitmap)Image, 2, Color.Black, Image.PixelFormat, out offset); break; case Effects.Grayscale: newImage = ImageHelper.CreateGrayscale((Bitmap)Image); break; case Effects.Invert: newImage = ImageHelper.CreateNegative((Bitmap)Image); break; case Effects.RotateClockwise: case Effects.RotateCounterClockwise: RotateFlipType rotateFlipType = RotateFlipType.Rotate270FlipNone; if (effect == Effects.RotateClockwise) { rotateFlipType = RotateFlipType.Rotate90FlipNone; } // Do not rotate the drawable containers until this works! //MakeUndoable(new DrawableContainerBoundsChangeMemento(elements.AsIDrawableContainerList()), false); //foreach (DrawableContainer drawableContainer in elements) { // if (drawableContainer.CanRotate) { // drawableContainer.Rotate(rotateFlipType); // } //} newImage = ImageHelper.RotateFlip((Bitmap)Image, rotateFlipType); break; } // The following was added to correct any unneeded pixels, had the bad effect that sometimes everything was cropped... :( //Rectangle autoCropRectangle = ImageHelper.FindAutoCropRectangle(newImage, 0); //if (!Size.Empty.Equals(autoCropRectangle.Size) && !autoCropRectangle.Size.Equals(newImage.Size)) { // LOG.InfoFormat("Crop to {0}", autoCropRectangle); // using (Bitmap tmpImage = newImage) { // newImage = ImageHelper.CloneArea(newImage, autoCropRectangle, PixelFormat.DontCare); // } // // Fix offset // offset = new Point(offset.X - autoCropRectangle.X, offset.Y - autoCropRectangle.Y); //} else { // LOG.DebugFormat("No cropping needed!"); //} if (newImage != null) { // Make sure the elements move according to the offset the effect made the bitmap move elements.MoveBy(offset.X, offset.Y); // Make undoable MakeUndoable(new SurfaceBackgroundChangeMemento(this, offset), false); SetImage(newImage, false); Invalidate(); if (surfaceSizeChanged != null && !imageRectangle.Equals(new Rectangle(Point.Empty, newImage.Size))) { surfaceSizeChanged(this); } } } finally { // Always close the background form backgroundForm.CloseDialog(); } } /// /// check if a crop is possible /// /// /// true if this is possible public bool isCropPossible(ref Rectangle cropRectangle) { cropRectangle = Helpers.GuiRectangle.GetGuiRectangle(cropRectangle.Left, cropRectangle.Top, cropRectangle.Width, cropRectangle.Height); if (cropRectangle.Left < 0) { cropRectangle = new Rectangle(0, cropRectangle.Top, cropRectangle.Width + cropRectangle.Left, cropRectangle.Height); } if (cropRectangle.Top < 0) { cropRectangle = new Rectangle(cropRectangle.Left, 0, cropRectangle.Width, cropRectangle.Height + cropRectangle.Top); } if (cropRectangle.Left + cropRectangle.Width > Width) { cropRectangle = new Rectangle(cropRectangle.Left, cropRectangle.Top, Width - cropRectangle.Left, cropRectangle.Height); } if (cropRectangle.Top + cropRectangle.Height > Height) { cropRectangle = new Rectangle(cropRectangle.Left, cropRectangle.Top, cropRectangle.Width, Height - cropRectangle.Top); } if (cropRectangle.Height > 0 && cropRectangle.Width > 0) { return true; } return false; } /// /// Use to send any registered SurfaceMessageEventHandler a message, e.g. used for the notification area /// /// Who send /// Type of message /// Message itself public void SendMessageEvent(object source, SurfaceMessageTyp messageType, string message) { if (surfaceMessage != null) { SurfaceMessageEventArgs eventArgs = new SurfaceMessageEventArgs(); eventArgs.Message = message; eventArgs.MessageType = messageType; eventArgs.Surface = this; surfaceMessage(source, eventArgs); } } /// /// Crop the surface /// /// /// public bool ApplyCrop(Rectangle cropRectangle) { if (isCropPossible(ref cropRectangle)) { Rectangle imageRectangle = new Rectangle(Point.Empty, Image.Size); Bitmap tmpImage; // Make sure we have information, this this fails try { tmpImage = ImageHelper.CloneArea(Image, cropRectangle, PixelFormat.DontCare); } catch (Exception ex) { ex.Data.Add("CropRectangle", cropRectangle); ex.Data.Add("Width", Image.Width); ex.Data.Add("Height", Image.Height); ex.Data.Add("Pixelformat", Image.PixelFormat); throw ex; } Point offset = new Point(-cropRectangle.Left, -cropRectangle.Top); // Make undoable MakeUndoable(new SurfaceBackgroundChangeMemento(this, offset), false); // Do not dispose otherwise we can't undo the image! SetImage(tmpImage, false); elements.MoveBy(offset.X, offset.Y); if (surfaceSizeChanged != null && !imageRectangle.Equals(new Rectangle(Point.Empty, tmpImage.Size))) { surfaceSizeChanged(this); } Invalidate(); return true; } return false; } /// /// The background here is the captured image. /// This is called from the SurfaceBackgroundChangeMemento. /// /// /// public void UndoBackgroundChange(Image previous, Point offset) { SetImage(previous, false); elements.MoveBy(offset.X, offset.Y); if (surfaceSizeChanged != null) { surfaceSizeChanged(this); } Invalidate(); } /// /// This event handler is called when someone presses the mouse on a surface. /// /// /// void SurfaceMouseDown(object sender, MouseEventArgs e) { mouseStart = e.Location; // check contextmenu if (e.Button == MouseButtons.Right) { DrawableContainerList selectedList = null; if (selectedElements != null && selectedElements.Count > 0) { selectedList = selectedElements; } else { // Single element IDrawableContainer rightClickedContainer = elements.ClickableElementAt(mouseStart.X, mouseStart.Y); if (rightClickedContainer != null) { selectedList = new DrawableContainerList(); selectedList.Add(rightClickedContainer); } } if (selectedList != null && selectedList.Count > 0) { selectedList.ShowContextMenu(e, this); } return; } mouseDown = true; isSurfaceMoveMadeUndoable = false; if (cropContainer != null && ((undrawnElement == null) || (undrawnElement != null && DrawingMode != DrawingModes.Crop))) { RemoveElement(cropContainer, false); cropContainer = null; drawingElement = null; } if (drawingElement == null && DrawingMode != DrawingModes.None) { if (undrawnElement == null) { DeselectAllElements(); if (undrawnElement == null) { CreateUndrawnElement(); } } drawingElement = undrawnElement; drawingElement.Status = EditStatus.DRAWING; undrawnElement = null; // if a new element has been drawn, set location and register it if (drawingElement != null) { drawingElement.PropertyChanged += ElementPropertyChanged; if (!drawingElement.HandleMouseDown(mouseStart.X, mouseStart.Y)) { drawingElement.Left = mouseStart.X; drawingElement.Top = mouseStart.Y; } AddElement(drawingElement); drawingElement.Selected = true; } } else { // check whether an existing element was clicked // we save mouse down element separately from selectedElements (checked on mouse up), // since it could be moved around before it is actually selected mouseDownElement = elements.ClickableElementAt(mouseStart.X, mouseStart.Y); if (mouseDownElement != null) { mouseDownElement.Status = EditStatus.MOVING; } } } /// /// This event handle is called when the mouse button is unpressed /// /// /// void SurfaceMouseUp(object sender, MouseEventArgs e) { Point currentMouse = new Point(e.X, e.Y); elements.Status = EditStatus.IDLE; if (mouseDownElement != null) { mouseDownElement.Status = EditStatus.IDLE; } mouseDown = false; mouseDownElement = null; if (DrawingMode == DrawingModes.None) { // check whether an existing element was clicked IDrawableContainer element = elements.ClickableElementAt(currentMouse.X, currentMouse.Y); bool shiftModifier = (Control.ModifierKeys & Keys.Shift) == Keys.Shift; if (element != null) { element.Invalidate(); bool alreadySelected = selectedElements.Contains(element); if (shiftModifier) { if (alreadySelected) { DeselectElement(element); } else { SelectElement(element); } } else { if (!alreadySelected) { DeselectAllElements(); SelectElement(element); } } } else if(!shiftModifier) { DeselectAllElements(); } } if (selectedElements.Count > 0) { selectedElements.ShowGrippers(); selectedElements.Selected = true; } if (drawingElement != null) { if (!drawingElement.InitContent()) { elements.Remove(drawingElement); drawingElement.Invalidate(); } else { drawingElement.HandleMouseUp(currentMouse.X, currentMouse.Y); drawingElement.Invalidate(); if (Math.Abs(drawingElement.Width) < 5 && Math.Abs(drawingElement.Height) < 5) { drawingElement.Width = 25; drawingElement.Height = 25; } SelectElement(drawingElement); drawingElement.Selected = true; } drawingElement = null; } } /// /// This event handler is called when the mouse moves over the surface /// /// /// void SurfaceMouseMove(object sender, MouseEventArgs e) { Point currentMouse = e.Location; if (DrawingMode != DrawingModes.None) { Cursor = Cursors.Cross; } else { Cursor = Cursors.Default; } if (mouseDown) { if (mouseDownElement != null) { // an element is currently dragged mouseDownElement.Invalidate(); selectedElements.HideGrippers(); // Move the element if (mouseDownElement.Selected) { if (!isSurfaceMoveMadeUndoable) { // Only allow one undoable per mouse-down/move/up "cycle" isSurfaceMoveMadeUndoable = true; selectedElements.MakeBoundsChangeUndoable(false); } // dragged element has been selected before -> move all selectedElements.MoveBy(currentMouse.X - mouseStart.X, currentMouse.Y - mouseStart.Y); } else { if (!isSurfaceMoveMadeUndoable) { // Only allow one undoable per mouse-down/move/up "cycle" isSurfaceMoveMadeUndoable = true; mouseDownElement.MakeBoundsChangeUndoable(false); } // dragged element is not among selected elements -> just move dragged one mouseDownElement.MoveBy(currentMouse.X - mouseStart.X, currentMouse.Y - mouseStart.Y); } mouseStart = currentMouse; mouseDownElement.Invalidate(); modified = true; } else if(drawingElement != null) { drawingElement.HandleMouseMove(currentMouse.X, currentMouse.Y); modified = true; } } } /// /// This event handler is called when the surface is double clicked. /// /// /// void SurfaceDoubleClick(object sender, MouseEventArgs e) { selectedElements.OnDoubleClick(); selectedElements.Invalidate(); } /// /// Privately used to get the rendered image with all the elements on it. /// /// /// private Image GetImage(RenderMode renderMode) { // Generate a copy of the original image with a dpi equal to the default... Bitmap clone = ImageHelper.Clone(image); // otherwise we would have a problem drawing the image to the surface... :( using (Graphics graphics = Graphics.FromImage(clone)) { // Do not set the following, the containers need to decide themselves //graphics.SmoothingMode = SmoothingMode.HighQuality; //graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; //graphics.CompositingQuality = CompositingQuality.HighQuality; //graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; elements.Draw(graphics, clone, renderMode, new Rectangle(Point.Empty, clone.Size)); } return clone; } /// /// This returns the image "result" of this surface, with all the elements rendered on it. /// /// public Image GetImageForExport() { return GetImage(RenderMode.EXPORT); } /// /// This is the event handler for the Paint Event, try to draw as little as possible! /// /// /// void SurfacePaint(object sender, PaintEventArgs e) { Graphics targetGraphics = e.Graphics; Rectangle clipRectangle = e.ClipRectangle; if (Rectangle.Empty.Equals(clipRectangle)) { LOG.Debug("Empty cliprectangle??"); return; } if (elements.hasIntersectingFilters(clipRectangle)) { if (buffer != null) { if (buffer.Width != Image.Width || buffer.Height != Image.Height || buffer.PixelFormat != Image.PixelFormat) { buffer.Dispose(); buffer = null; } } if (buffer == null) { buffer = ImageHelper.CreateEmpty(Image.Width, Image.Height, Image.PixelFormat, Color.Empty, Image.HorizontalResolution, Image.VerticalResolution); LOG.DebugFormat("Created buffer with size: {0}x{1}", Image.Width, Image.Height); } // Elements might need the bitmap, so we copy the part we need using (Graphics graphics = Graphics.FromImage(buffer)) { // do not set the following, the containers need to decide this themselves! //graphics.SmoothingMode = SmoothingMode.HighQuality; //graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; //graphics.CompositingQuality = CompositingQuality.HighQuality; //graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; graphics.DrawImage(Image, clipRectangle, clipRectangle, GraphicsUnit.Pixel); graphics.SetClip(targetGraphics); elements.Draw(graphics, buffer, RenderMode.EDIT, clipRectangle); } targetGraphics.DrawImage(buffer, clipRectangle, clipRectangle, GraphicsUnit.Pixel); } else { targetGraphics.DrawImage(Image, clipRectangle, clipRectangle, GraphicsUnit.Pixel); elements.Draw(targetGraphics, null, RenderMode.EDIT, clipRectangle); } } /// /// Draw a checkboard when capturing with transparency /// /// PaintEventArgs protected override void OnPaintBackground(PaintEventArgs e) { // check if we need to draw the checkerboard if (Image.IsAlphaPixelFormat(Image.PixelFormat) && transparencyBackgroundBrush != null) { Graphics targetGraphics = e.Graphics; Rectangle clipRectangle = e.ClipRectangle; targetGraphics.FillRectangle(transparencyBackgroundBrush, clipRectangle); } else { Graphics targetGraphics = e.Graphics; targetGraphics.Clear(this.BackColor); //base.OnPaintBackground(e); } } /// /// Wrapper for makeUndoable flag which was introduced later, will call AddElement with makeundoable set to true /// /// the new element public void AddElement(IDrawableContainer element) { AddElement(element, true); } /// /// Add a new element to the surface /// /// the new element /// true if the adding should be undoable public void AddElement(IDrawableContainer element, bool makeUndoable) { elements.Add(element); DrawableContainer container = element as DrawableContainer; if (container != null) { container.FieldChanged += element_FieldChanged; } element.PropertyChanged += ElementPropertyChanged; if (element.Status == EditStatus.UNDRAWN) { element.Status = EditStatus.IDLE; } element.Invalidate(); if (makeUndoable) { MakeUndoable(new AddElementMemento(this, element), false); } modified = true; } /// /// Remove an element of the elements list /// /// Element to remove /// flag specifying if the remove needs to be undoable public void RemoveElement(IDrawableContainer elementToRemove, bool makeUndoable) { DeselectElement(elementToRemove); elements.Remove(elementToRemove); DrawableContainer element = elementToRemove as DrawableContainer; if (element != null) { element.FieldChanged -= element_FieldChanged; } elementToRemove.PropertyChanged -= ElementPropertyChanged; // Do not dispose, the memento should!! element.Dispose(); elementToRemove.Invalidate(); if (makeUndoable) { MakeUndoable(new DeleteElementMemento(this, elementToRemove), false); } modified = true; } /// /// Add the supplied elements to the surface /// /// public void AddElements(DrawableContainerList elementsToAdd) { foreach(IDrawableContainer element in elementsToAdd) { AddElement(element, true); } } /// /// Returns if this surface has selected elements /// /// public bool HasSelectedElements { get { return (selectedElements != null && selectedElements.Count > 0); } } /// /// Remove all the selected elements /// public void RemoveSelectedElements() { if (HasSelectedElements) { // As RemoveElement will remove the element from the selectedElements list we need to copy the element // to another list. List elementsToRemove = new List(); foreach (DrawableContainer element in selectedElements) { // Collect to remove later elementsToRemove.Add(element); } // Remove now foreach(DrawableContainer element in elementsToRemove) { RemoveElement(element, true); } selectedElements.Clear(); if (movingElementChanged != null) { movingElementChanged(this, selectedElements); } } } /// /// Cut the selected elements from the surface to the clipboard /// public void CutSelectedElements() { if (HasSelectedElements) { ClipboardHelper.SetClipboardData(typeof(DrawableContainerList), selectedElements); RemoveSelectedElements(); } } /// /// Copy the selected elements to the clipboard /// public void CopySelectedElements() { if (HasSelectedElements) { ClipboardHelper.SetClipboardData(typeof(DrawableContainerList), selectedElements); } } /// /// This method is called to confirm/cancel "confirmable" elements, like the crop-container. /// Called when pressing enter or using the "check" in the editor. /// /// public void ConfirmSelectedConfirmableElements(bool confirm){ // create new collection so that we can iterate safely (selectedElements might change due with confirm/cancel) List selectedDCs = new List(selectedElements); foreach (IDrawableContainer dc in selectedDCs){ if (dc.Equals(cropContainer)){ DrawingMode = DrawingModes.None; // No undo memento for the cropcontainer itself, only for the effect RemoveElement(cropContainer, false); if (confirm) { ApplyCrop(cropContainer.Bounds); } cropContainer.Dispose(); } } } /// /// Paste all the elements that are on the clipboard /// public void PasteElementFromClipboard() { List formats = ClipboardHelper.GetFormats(); if (formats == null || formats.Count == 0) { return; } if (LOG.IsDebugEnabled) { LOG.Debug("List of clipboard formats available for pasting:"); foreach(string format in formats) { LOG.Debug("\tgot format: " + format); } } if (formats.Contains(typeof(DrawableContainerList).FullName)) { DrawableContainerList dcs = (DrawableContainerList)ClipboardHelper.GetClipboardData(typeof(DrawableContainerList)); if (dcs != null) { dcs.Parent = this; dcs.MoveBy(10,10); AddElements(dcs); FieldAggregator.BindElements(dcs); DeselectAllElements(); SelectElements(dcs); } } else if (ClipboardHelper.ContainsImage()) { using (Image clipboardImage = ClipboardHelper.GetImage()) { if (clipboardImage != null) { DeselectAllElements(); IBitmapContainer bitmapContainer = AddBitmapContainer(clipboardImage as Bitmap, 0, 0); SelectElement(bitmapContainer); } } } else if (ClipboardHelper.ContainsText()) { string text = ClipboardHelper.GetText(); if (text != null) { DeselectAllElements(); ITextContainer textContainer = AddTextContainer(text, HorizontalAlignment.Center, VerticalAlignment.CENTER, FontFamily.GenericSansSerif, 12f, false, false, false, 2, Color.Black, Color.Transparent); SelectElement(textContainer); } } } /// /// Duplicate all the selecteded elements /// public void DuplicateSelectedElements() { LOG.DebugFormat("Duplicating {0} selected elements", selectedElements.Count); DrawableContainerList dcs = selectedElements.Clone(); dcs.Parent = this; dcs.MoveBy(10,10); AddElements(dcs); DeselectAllElements(); SelectElements(dcs); } /// /// Deselect the specified element /// /// public void DeselectElement(IDrawableContainer container) { container.HideGrippers(); container.Selected = false; selectedElements.Remove(container); FieldAggregator.UnbindElement(container); if (movingElementChanged != null) { movingElementChanged(this, selectedElements); } } /// /// Deselect all the selected elements /// public void DeselectAllElements() { if (HasSelectedElements) { while(selectedElements.Count > 0) { IDrawableContainer element = selectedElements[0]; element.Invalidate(); element.HideGrippers(); element.Selected = false; selectedElements.Remove(element); FieldAggregator.UnbindElement(element); } if (movingElementChanged != null) { movingElementChanged(this, selectedElements); } } } /// /// Select the supplied element /// /// public void SelectElement(IDrawableContainer container) { if (!selectedElements.Contains(container)) { selectedElements.Add(container); container.ShowGrippers(); container.Selected = true; FieldAggregator.BindElement(container); if (movingElementChanged != null) { movingElementChanged(this, selectedElements); } container.Invalidate(); } } /// /// Select all elements, this is called when Ctrl+A is pressed /// public void SelectAllElements() { SelectElements(elements); } /// /// Select the supplied elements /// /// public void SelectElements(DrawableContainerList elements) { foreach(DrawableContainer element in elements) { SelectElement(element); } } /// /// Process key presses on the surface, this is called from the editor (and NOT an override from the Control) /// /// Keys /// false if no keys were processed public bool ProcessCmdKey(Keys k) { if (selectedElements.Count > 0) { bool shiftModifier = (Control.ModifierKeys & Keys.Shift) == Keys.Shift; int px = shiftModifier ? 10 : 1; Point moveBy = Point.Empty; switch (k) { case Keys.Left: case Keys.Left | Keys.Shift: moveBy = new Point(-px, 0); break; case Keys.Up: case Keys.Up | Keys.Shift: moveBy = new Point(0, -px); break; case Keys.Right: case Keys.Right | Keys.Shift: moveBy = new Point(px, 0); break; case Keys.Down: case Keys.Down | Keys.Shift: moveBy = new Point(0, px); break; case Keys.PageUp: PullElementsUp(); break; case Keys.PageDown: PushElementsDown(); break; case Keys.Home: PullElementsToTop(); break; case Keys.End: PushElementsToBottom(); break; case Keys.Enter: ConfirmSelectedConfirmableElements(true); break; case Keys.Escape: ConfirmSelectedConfirmableElements(false); break; /*case Keys.Delete: RemoveSelectedElements(); break;*/ default: return false; } if (!Point.Empty.Equals(moveBy)) { selectedElements.MakeBoundsChangeUndoable(true); selectedElements.MoveBy(moveBy.X, moveBy.Y); } return true; } return false; } /// /// Property for accessing the elements on the surface /// public DrawableContainerList Elements { get { return elements; } } /// /// pulls selected elements up one level in hierarchy /// public void PullElementsUp() { elements.PullElementsUp(selectedElements); elements.Invalidate(); } /// /// pushes selected elements up to top in hierarchy /// public void PullElementsToTop() { elements.PullElementsToTop(selectedElements); elements.Invalidate(); } /// /// pushes selected elements down one level in hierarchy /// public void PushElementsDown() { elements.PushElementsDown(selectedElements); elements.Invalidate(); } /// /// pushes selected elements down to bottom in hierarchy /// public void PushElementsToBottom() { elements.PushElementsToBottom(selectedElements); elements.Invalidate(); } /// /// indicates whether the selected elements could be pulled up in hierarchy /// /// true if selected elements could be pulled up, false otherwise public bool CanPullSelectionUp() { return elements.CanPullUp(selectedElements); } /// /// indicates whether the selected elements could be pushed down in hierarchy /// /// true if selected elements could be pushed down, false otherwise public bool CanPushSelectionDown() { return elements.CanPushDown(selectedElements); } public void ElementPropertyChanged(object sender, PropertyChangedEventArgs e) { //Invalidate(); } public void element_FieldChanged(object sender, FieldChangedEventArgs e) { selectedElements.HandleFieldChangedEvent(sender, e); } } }