diff --git a/Greenshot/Drawing/Adorners/AbstractAdorner.cs b/Greenshot/Drawing/Adorners/AbstractAdorner.cs
index 0612753aa..70a56f29e 100644
--- a/Greenshot/Drawing/Adorners/AbstractAdorner.cs
+++ b/Greenshot/Drawing/Adorners/AbstractAdorner.cs
@@ -116,6 +116,18 @@ namespace Greenshot.Drawing.Adorners
}
}
+ ///
+ /// Return the bounds of the Adorner as displayed on the parent Surface
+ ///
+ protected virtual Rectangle BoundsOnSurface
+ {
+ get
+ {
+ Point displayLocation = Owner.Parent.ToSurfaceCoordinates(Location);
+ return new Rectangle(displayLocation.X - _size.Width / 2, displayLocation.Y - _size.Height / 2, _size.Width, _size.Height);
+ }
+ }
+
///
/// The adorner is active if the edit status is not idle or undrawn
///
diff --git a/Greenshot/Drawing/Adorners/MoveAdorner.cs b/Greenshot/Drawing/Adorners/MoveAdorner.cs
index ec3dcf2d3..d07a25ef7 100644
--- a/Greenshot/Drawing/Adorners/MoveAdorner.cs
+++ b/Greenshot/Drawing/Adorners/MoveAdorner.cs
@@ -142,7 +142,7 @@ namespace Greenshot.Drawing.Adorners
{
Graphics targetGraphics = paintEventArgs.Graphics;
- var bounds = Bounds;
+ var bounds = BoundsOnSurface;
GraphicsState state = targetGraphics.Save();
targetGraphics.SmoothingMode = SmoothingMode.None;
diff --git a/Greenshot/Drawing/Adorners/ResizeAdorner.cs b/Greenshot/Drawing/Adorners/ResizeAdorner.cs
index e75126f10..dce78b461 100644
--- a/Greenshot/Drawing/Adorners/ResizeAdorner.cs
+++ b/Greenshot/Drawing/Adorners/ResizeAdorner.cs
@@ -169,7 +169,7 @@ namespace Greenshot.Drawing.Adorners
{
Graphics targetGraphics = paintEventArgs.Graphics;
- var bounds = Bounds;
+ var bounds = BoundsOnSurface;
GraphicsState state = targetGraphics.Save();
targetGraphics.SmoothingMode = SmoothingMode.None;
diff --git a/Greenshot/Drawing/Adorners/TargetAdorner.cs b/Greenshot/Drawing/Adorners/TargetAdorner.cs
index 8ca0a91ef..67c01a533 100644
--- a/Greenshot/Drawing/Adorners/TargetAdorner.cs
+++ b/Greenshot/Drawing/Adorners/TargetAdorner.cs
@@ -61,26 +61,26 @@ namespace Greenshot.Drawing.Adorners
Owner.Invalidate();
Point newGripperLocation = new Point(mouseEventArgs.X, mouseEventArgs.Y);
- Rectangle surfaceBounds = new Rectangle(0, 0, Owner.Parent.Width, Owner.Parent.Height);
+ Rectangle imageBounds = new Rectangle(0, 0, Owner.Parent.Image.Width, Owner.Parent.Image.Height);
// Check if gripper inside the parent (surface), if not we need to move it inside
// This was made for BUG-1682
- if (!surfaceBounds.Contains(newGripperLocation))
+ if (!imageBounds.Contains(newGripperLocation))
{
- if (newGripperLocation.X > surfaceBounds.Right)
+ if (newGripperLocation.X > imageBounds.Right)
{
- newGripperLocation.X = surfaceBounds.Right - 5;
+ newGripperLocation.X = imageBounds.Right - 5;
}
- if (newGripperLocation.X < surfaceBounds.Left)
+ if (newGripperLocation.X < imageBounds.Left)
{
- newGripperLocation.X = surfaceBounds.Left;
+ newGripperLocation.X = imageBounds.Left;
}
- if (newGripperLocation.Y > surfaceBounds.Bottom)
+ if (newGripperLocation.Y > imageBounds.Bottom)
{
- newGripperLocation.Y = surfaceBounds.Bottom - 5;
+ newGripperLocation.Y = imageBounds.Bottom - 5;
}
- if (newGripperLocation.Y < surfaceBounds.Top)
+ if (newGripperLocation.Y < imageBounds.Top)
{
- newGripperLocation.Y = surfaceBounds.Top;
+ newGripperLocation.Y = imageBounds.Top;
}
}
@@ -96,7 +96,7 @@ namespace Greenshot.Drawing.Adorners
{
Graphics targetGraphics = paintEventArgs.Graphics;
- var bounds = Bounds;
+ var bounds = BoundsOnSurface;
targetGraphics.FillRectangle(Brushes.Green, bounds.X, bounds.Y, bounds.Width, bounds.Height);
}
diff --git a/Greenshot/Drawing/CropContainer.cs b/Greenshot/Drawing/CropContainer.cs
index 2dc9761c0..58cee17b1 100644
--- a/Greenshot/Drawing/CropContainer.cs
+++ b/Greenshot/Drawing/CropContainer.cs
@@ -56,7 +56,15 @@ namespace Greenshot.Drawing {
/// We need to override the DrawingBound, return a rectangle in the size of the image, to make sure this element is always draw
/// (we create a transparent brown over the complete picture)
///
- public override Rectangle DrawingBounds => new Rectangle(0,0,_parent?.Width??0, _parent?.Height ?? 0);
+ public override Rectangle DrawingBounds {
+ get {
+ if (_parent?.Image is Image image) {
+ return new Rectangle(0, 0, image.Width, image.Height);
+ } else {
+ return Rectangle.Empty;
+ }
+ }
+ }
public override void Draw(Graphics g, RenderMode rm) {
if (_parent == null)
@@ -67,17 +75,18 @@ namespace Greenshot.Drawing {
using Brush cropBrush = new SolidBrush(Color.FromArgb(100, 150, 150, 100));
Rectangle cropRectangle = GuiRectangle.GetGuiRectangle(Left, Top, Width, Height);
Rectangle selectionRect = new Rectangle(cropRectangle.Left - 1, cropRectangle.Top - 1, cropRectangle.Width + 1, cropRectangle.Height + 1);
+ Size imageSize = _parent.Image.Size;
DrawSelectionBorder(g, selectionRect);
// top
- g.FillRectangle(cropBrush, new Rectangle(0, 0, _parent.Width, cropRectangle.Top));
+ g.FillRectangle(cropBrush, new Rectangle(0, 0, imageSize.Width, cropRectangle.Top));
// left
g.FillRectangle(cropBrush, new Rectangle(0, cropRectangle.Top, cropRectangle.Left, cropRectangle.Height));
// right
- g.FillRectangle(cropBrush, new Rectangle(cropRectangle.Left + cropRectangle.Width, cropRectangle.Top, _parent.Width - (cropRectangle.Left + cropRectangle.Width), cropRectangle.Height));
+ g.FillRectangle(cropBrush, new Rectangle(cropRectangle.Left + cropRectangle.Width, cropRectangle.Top, imageSize.Width - (cropRectangle.Left + cropRectangle.Width), cropRectangle.Height));
// bottom
- g.FillRectangle(cropBrush, new Rectangle(0, cropRectangle.Top + cropRectangle.Height, _parent.Width, _parent.Height - (cropRectangle.Top + cropRectangle.Height)));
+ g.FillRectangle(cropBrush, new Rectangle(0, cropRectangle.Top + cropRectangle.Height, imageSize.Width, imageSize.Height - (cropRectangle.Top + cropRectangle.Height)));
}
public override bool HasContextMenu {
diff --git a/Greenshot/Drawing/DrawableContainer.cs b/Greenshot/Drawing/DrawableContainer.cs
index 8d805b1f0..33339ee00 100644
--- a/Greenshot/Drawing/DrawableContainer.cs
+++ b/Greenshot/Drawing/DrawableContainer.cs
@@ -33,7 +33,6 @@ using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Runtime.Serialization;
-using System.Windows.Forms;
using GreenshotPlugin.IniFile;
using GreenshotPlugin.Interfaces;
using GreenshotPlugin.Interfaces.Drawing.Adorners;
@@ -298,34 +297,7 @@ namespace Greenshot.Drawing
public virtual void Invalidate() {
if (Status != EditStatus.UNDRAWN) {
- _parent?.Invalidate(DrawingBounds);
- }
- }
-
- public void AlignToParent(HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment) {
- if (_parent == null)
- {
- return;
- }
- int lineThickness = GetFieldValueAsInt(FieldType.LINE_THICKNESS);
- if (horizontalAlignment == HorizontalAlignment.Left) {
- Left = lineThickness/2;
- }
- if (horizontalAlignment == HorizontalAlignment.Right) {
- Left = _parent.Width - Width - lineThickness/2;
- }
- if (horizontalAlignment == HorizontalAlignment.Center) {
- Left = (_parent.Width / 2) - (Width / 2) - lineThickness/2;
- }
-
- if (verticalAlignment == VerticalAlignment.TOP) {
- Top = lineThickness/2;
- }
- if (verticalAlignment == VerticalAlignment.BOTTOM) {
- Top = _parent.Height - Height - lineThickness/2;
- }
- if (verticalAlignment == VerticalAlignment.CENTER) {
- Top = (_parent.Height / 2) - (Height / 2) - lineThickness/2;
+ _parent?.InvalidateElements(DrawingBounds);
}
}
diff --git a/Greenshot/Drawing/DrawableContainerList.cs b/Greenshot/Drawing/DrawableContainerList.cs
index d3c325f5b..3d200598e 100644
--- a/Greenshot/Drawing/DrawableContainerList.cs
+++ b/Greenshot/Drawing/DrawableContainerList.cs
@@ -235,6 +235,30 @@ namespace Greenshot.Drawing {
return false;
}
+ ///
+ /// A rectangle containing DrawingBounds of all drawableContainers in this list,
+ /// or empty rectangle if nothing is there.
+ ///
+ public Rectangle DrawingBounds
+ {
+ get
+ {
+ if (Count == 0)
+ {
+ return Rectangle.Empty;
+ }
+ else
+ {
+ var result = this[0].DrawingBounds;
+ for (int i = 1; i < Count; i++)
+ {
+ result = Rectangle.Union(result, this[i].DrawingBounds);
+ }
+ return result;
+ }
+ }
+ }
+
///
/// Triggers all elements in the list ot be redrawn.
///
@@ -286,7 +310,7 @@ namespace Greenshot.Drawing {
{
region = Rectangle.Union(region, dc.DrawingBounds);
}
- Parent.Invalidate(region);
+ Parent.InvalidateElements(region);
}
///
/// Indicates whether the given list of elements can be pulled up,
@@ -523,9 +547,9 @@ namespace Greenshot.Drawing {
}
}
- public virtual void ShowContextMenu(MouseEventArgs e, ISurface surface)
+ public virtual void ShowContextMenu(MouseEventArgs e, ISurface iSurface)
{
- if (!(surface is Surface))
+ if (!(iSurface is Surface surface))
{
return;
}
@@ -542,8 +566,7 @@ namespace Greenshot.Drawing {
ContextMenuStrip menu = new ContextMenuStrip();
AddContextMenuItems(menu, surface);
if (menu.Items.Count > 0) {
- // TODO: cast should be somehow avoided
- menu.Show((Surface)surface, e.Location);
+ menu.Show(surface, surface.ToSurfaceCoordinates(e.Location));
while (true) {
if (menu.Visible) {
Application.DoEvents();
diff --git a/Greenshot/Drawing/FreehandContainer.cs b/Greenshot/Drawing/FreehandContainer.cs
index 129ba4822..c7d5cf1e8 100644
--- a/Greenshot/Drawing/FreehandContainer.cs
+++ b/Greenshot/Drawing/FreehandContainer.cs
@@ -47,8 +47,8 @@ namespace Greenshot.Drawing {
/// Constructor
///
public FreehandContainer(Surface parent) : base(parent) {
- Width = parent.Width;
- Height = parent.Height;
+ Width = parent.Image.Width;
+ Height = parent.Image.Height;
Top = 0;
Left = 0;
}
@@ -236,7 +236,14 @@ namespace Greenshot.Drawing {
int safetymargin = 10;
return new Rectangle(myBounds.Left + Left - (safetymargin+lineThickness), myBounds.Top + Top - (safetymargin+lineThickness), myBounds.Width + 2*(lineThickness+safetymargin), myBounds.Height + 2*(lineThickness+safetymargin));
}
- return new Rectangle(0, 0, _parent?.Width??0, _parent?.Height?? 0);
+ if (_parent?.Image is Image image)
+ {
+ return new Rectangle(0, 0, image.Width, image.Height);
+ }
+ else
+ {
+ return Rectangle.Empty;
+ }
}
}
diff --git a/Greenshot/Drawing/Surface.cs b/Greenshot/Drawing/Surface.cs
index bd56db3a2..6c89238f0 100644
--- a/Greenshot/Drawing/Surface.cs
+++ b/Greenshot/Drawing/Surface.cs
@@ -1,4 +1,4 @@
-/*
+/*
* Greenshot - a free and open source screenshot tool
* Copyright (C) 2007-2020 Thomas Braun, Jens Klingen, Robin Krom
*
@@ -298,10 +298,39 @@ namespace Greenshot.Drawing
set
{
_image = value;
- Size = _image.Size;
+ UpdateSize();
}
}
+ [NonSerialized]
+ private Matrix _zoomMatrix = new Matrix(1, 0, 0, 1, 0, 0);
+ [NonSerialized]
+ private Matrix _inverseZoomMatrix = new Matrix(1, 0, 0, 1, 0, 0);
+ [NonSerialized]
+ private Fraction _zoomFactor = Fraction.Identity;
+ public Fraction ZoomFactor
+ {
+ get => _zoomFactor;
+ set
+ {
+ _zoomFactor = value;
+ var inverse = _zoomFactor.Inverse();
+ _zoomMatrix = new Matrix(_zoomFactor, 0, 0, _zoomFactor, 0, 0);
+ _inverseZoomMatrix = new Matrix(inverse, 0, 0, inverse, 0, 0);
+ UpdateSize();
+ }
+ }
+
+
+ ///
+ /// Sets the surface size as zoomed image size.
+ ///
+ private void UpdateSize()
+ {
+ var size = _image.Size;
+ Size = new Size((int)(size.Width * _zoomFactor), (int)(size.Height * _zoomFactor));
+ }
+
///
/// 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.
@@ -447,7 +476,6 @@ namespace Greenshot.Drawing
// Set new values
Image = newImage;
- Size = newImage.Size;
_modified = true;
}
@@ -779,9 +807,9 @@ namespace Greenshot.Drawing
return cursorContainer;
}
- 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)
+ public ITextContainer AddTextContainer(string text, int x, int y, FontFamily family, float size, bool italic, bool bold, bool shadow, int borderSize, Color color, Color fillColor)
{
- TextContainer textContainer = new TextContainer(this) {Text = text};
+ TextContainer textContainer = new TextContainer(this) {Text = text, Left = x, Top = y};
textContainer.SetFieldValue(FieldType.FONT_FAMILY, family.Name);
textContainer.SetFieldValue(FieldType.FONT_BOLD, bold);
textContainer.SetFieldValue(FieldType.FONT_ITALIC, italic);
@@ -792,8 +820,6 @@ namespace Greenshot.Drawing
textContainer.SetFieldValue(FieldType.SHADOW, shadow);
// Make sure the Text fits
textContainer.FitToText();
- // Align to Surface
- textContainer.AlignToParent(horizontalAlignment, verticalAlignment);
//AggregatedProperties.UpdateElement(textContainer);
AddElement(textContainer);
@@ -967,13 +993,13 @@ namespace Greenshot.Drawing
{
cropRectangle = new Rectangle(cropRectangle.Left, 0, cropRectangle.Width, cropRectangle.Height + cropRectangle.Top);
}
- if (cropRectangle.Left + cropRectangle.Width > Width)
+ if (cropRectangle.Left + cropRectangle.Width > Image.Width)
{
- cropRectangle = new Rectangle(cropRectangle.Left, cropRectangle.Top, Width - cropRectangle.Left, cropRectangle.Height);
+ cropRectangle = new Rectangle(cropRectangle.Left, cropRectangle.Top, Image.Width - cropRectangle.Left, cropRectangle.Height);
}
- if (cropRectangle.Top + cropRectangle.Height > Height)
+ if (cropRectangle.Top + cropRectangle.Height > Image.Height)
{
- cropRectangle = new Rectangle(cropRectangle.Left, cropRectangle.Top, cropRectangle.Width, Height - cropRectangle.Top);
+ cropRectangle = new Rectangle(cropRectangle.Left, cropRectangle.Top, cropRectangle.Width, Image.Height - cropRectangle.Top);
}
if (cropRectangle.Height > 0 && cropRectangle.Width > 0)
{
@@ -1086,6 +1112,12 @@ namespace Greenshot.Drawing
return null;
}
+ ///
+ /// Translate mouse coordinates as if they were applied directly to unscaled image.
+ ///
+ private MouseEventArgs InverseZoomMouseCoordinates(MouseEventArgs e)
+ => new MouseEventArgs(e.Button, e.Clicks, (int)(e.X / _zoomFactor), (int)(e.Y / _zoomFactor), e.Delta);
+
///
/// This event handler is called when someone presses the mouse on a surface.
///
@@ -1093,6 +1125,7 @@ namespace Greenshot.Drawing
///
private void SurfaceMouseDown(object sender, MouseEventArgs e)
{
+ e = InverseZoomMouseCoordinates(e);
// Handle Adorners
var adorner = FindActiveAdorner(e);
@@ -1187,6 +1220,7 @@ namespace Greenshot.Drawing
///
private void SurfaceMouseUp(object sender, MouseEventArgs e)
{
+ e = InverseZoomMouseCoordinates(e);
// Handle Adorners
var adorner = FindActiveAdorner(e);
@@ -1276,6 +1310,8 @@ namespace Greenshot.Drawing
///
private void SurfaceMouseMove(object sender, MouseEventArgs e)
{
+ e = InverseZoomMouseCoordinates(e);
+
// Handle Adorners
var adorner = FindActiveAdorner(e);
if (adorner != null)
@@ -1371,6 +1407,25 @@ namespace Greenshot.Drawing
return GetImage(RenderMode.EXPORT);
}
+ private static Rectangle ZoomClipRectangle(Rectangle rc, double scale, int inflateAmount = 0)
+ {
+ rc = new Rectangle(
+ (int)(rc.X * scale),
+ (int)(rc.Y * scale),
+ (int)(rc.Width * scale) + 1,
+ (int)(rc.Height * scale) + 1
+ );
+ if (scale > 1)
+ {
+ inflateAmount = (int)(inflateAmount * scale);
+ }
+ rc.Inflate(inflateAmount, inflateAmount);
+ return rc;
+ }
+
+ public void InvalidateElements(Rectangle rc)
+ => Invalidate(ZoomClipRectangle(rc, _zoomFactor, 1));
+
///
/// This is the event handler for the Paint Event, try to draw as little as possible!
///
@@ -1379,14 +1434,34 @@ namespace Greenshot.Drawing
private void SurfacePaint(object sender, PaintEventArgs paintEventArgs)
{
Graphics targetGraphics = paintEventArgs.Graphics;
- Rectangle clipRectangle = paintEventArgs.ClipRectangle;
- if (Rectangle.Empty.Equals(clipRectangle))
+ Rectangle targetClipRectangle = paintEventArgs.ClipRectangle;
+ if (Rectangle.Empty.Equals(targetClipRectangle))
{
LOG.Debug("Empty cliprectangle??");
return;
}
- if (_elements.HasIntersectingFilters(clipRectangle))
+ // Correction to prevent rounding errors at certain zoom levels.
+ // When zooming to N/M, clip rectangle top and left coordinates should be multiples of N.
+ if (_zoomFactor.Numerator > 1 && _zoomFactor.Denominator > 1)
+ {
+ int horizontalCorrection = targetClipRectangle.Left % (int)_zoomFactor.Numerator;
+ int verticalCorrection = targetClipRectangle.Top % (int)_zoomFactor.Numerator;
+ if (horizontalCorrection != 0)
+ {
+ targetClipRectangle.X -= horizontalCorrection;
+ targetClipRectangle.Width += horizontalCorrection;
+ }
+ if (verticalCorrection != 0)
+ {
+ targetClipRectangle.Y -= verticalCorrection;
+ targetClipRectangle.Height += verticalCorrection;
+ }
+ }
+
+ Rectangle imageClipRectangle = ZoomClipRectangle(targetClipRectangle, _zoomFactor.Inverse(), 2);
+
+ if (_elements.HasIntersectingFilters(imageClipRectangle) || _zoomFactor > Fraction.Identity)
{
if (_buffer != null)
{
@@ -1409,18 +1484,44 @@ namespace Greenshot.Drawing
//graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
//graphics.CompositingQuality = CompositingQuality.HighQuality;
//graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
- DrawBackground(graphics, clipRectangle);
- graphics.DrawImage(Image, clipRectangle, clipRectangle, GraphicsUnit.Pixel);
- graphics.SetClip(targetGraphics);
- _elements.Draw(graphics, _buffer, RenderMode.EDIT, clipRectangle);
+ DrawBackground(graphics, imageClipRectangle);
+ graphics.DrawImage(Image, imageClipRectangle, imageClipRectangle, GraphicsUnit.Pixel);
+ graphics.SetClip(ZoomClipRectangle(Rectangle.Round(targetGraphics.ClipBounds), _zoomFactor.Inverse(), 2));
+ _elements.Draw(graphics, _buffer, RenderMode.EDIT, imageClipRectangle);
+ }
+ if (_zoomFactor == Fraction.Identity)
+ {
+ targetGraphics.DrawImage(_buffer, imageClipRectangle, imageClipRectangle, GraphicsUnit.Pixel);
+ }
+ else
+ {
+ targetGraphics.ScaleTransform(_zoomFactor, _zoomFactor);
+ if (_zoomFactor > Fraction.Identity)
+ {
+ DrawSharpImage(targetGraphics, _buffer, imageClipRectangle);
+ }
+ else
+ {
+ DrawSmoothImage(targetGraphics, _buffer, imageClipRectangle);
+ }
+ targetGraphics.ResetTransform();
}
- targetGraphics.DrawImage(_buffer, clipRectangle, clipRectangle, GraphicsUnit.Pixel);
}
else
{
- DrawBackground(targetGraphics, clipRectangle);
- targetGraphics.DrawImage(Image, clipRectangle, clipRectangle, GraphicsUnit.Pixel);
- _elements.Draw(targetGraphics, null, RenderMode.EDIT, clipRectangle);
+ DrawBackground(targetGraphics, targetClipRectangle);
+ if (_zoomFactor == Fraction.Identity)
+ {
+ targetGraphics.DrawImage(Image, imageClipRectangle, imageClipRectangle, GraphicsUnit.Pixel);
+ _elements.Draw(targetGraphics, null, RenderMode.EDIT, imageClipRectangle);
+ }
+ else
+ {
+ targetGraphics.ScaleTransform(_zoomFactor, _zoomFactor);
+ DrawSmoothImage(targetGraphics, Image, imageClipRectangle);
+ _elements.Draw(targetGraphics, null, RenderMode.EDIT, imageClipRectangle);
+ targetGraphics.ResetTransform();
+ }
}
// No clipping for the adorners
@@ -1435,6 +1536,32 @@ namespace Greenshot.Drawing
}
}
+ private void DrawSmoothImage(Graphics targetGraphics, Image image, Rectangle imageClipRectangle)
+ {
+ var state = targetGraphics.Save();
+ targetGraphics.SmoothingMode = SmoothingMode.HighQuality;
+ targetGraphics.InterpolationMode = InterpolationMode.HighQualityBilinear;
+ targetGraphics.CompositingQuality = CompositingQuality.HighQuality;
+ targetGraphics.PixelOffsetMode = PixelOffsetMode.None;
+
+ targetGraphics.DrawImage(image, imageClipRectangle, imageClipRectangle, GraphicsUnit.Pixel);
+
+ targetGraphics.Restore(state);
+ }
+
+ private void DrawSharpImage(Graphics targetGraphics, Image image, Rectangle imageClipRectangle)
+ {
+ var state = targetGraphics.Save();
+ targetGraphics.SmoothingMode = SmoothingMode.None;
+ targetGraphics.InterpolationMode = InterpolationMode.NearestNeighbor;
+ targetGraphics.CompositingQuality = CompositingQuality.HighQuality;
+ targetGraphics.PixelOffsetMode = PixelOffsetMode.None;
+
+ targetGraphics.DrawImage(image, imageClipRectangle, imageClipRectangle, GraphicsUnit.Pixel);
+
+ targetGraphics.Restore(state);
+ }
+
private void DrawBackground(Graphics targetGraphics, Rectangle clipRectangle)
{
// check if we need to draw the checkerboard
@@ -1742,43 +1869,79 @@ namespace Greenshot.Drawing
}
else if (ClipboardHelper.ContainsImage(clipboard))
{
- int x = 10;
- int y = 10;
-
- // FEATURE-995: Added a check for the current mouse cursor location, to paste the image on that location.
- var mousePositionOnControl = PointToClient(MousePosition);
- if (ClientRectangle.Contains(mousePositionOnControl))
- {
- x = mousePositionOnControl.X;
- y = mousePositionOnControl.Y;
- }
+ Point pasteLocation = GetPasteLocation(0.1f, 0.1f);
foreach (Image clipboardImage in ClipboardHelper.GetImages(clipboard))
{
if (clipboardImage != null)
{
DeselectAllElements();
- IImageContainer container = AddImageContainer(clipboardImage as Bitmap, x, y);
+ IImageContainer container = AddImageContainer(clipboardImage as Bitmap, pasteLocation.X, pasteLocation.Y);
SelectElement(container);
clipboardImage.Dispose();
- x += 10;
- y += 10;
+ pasteLocation.X += 10;
+ pasteLocation.Y += 10;
}
}
}
else if (ClipboardHelper.ContainsText(clipboard))
{
+ Point pasteLocation = GetPasteLocation(0.4f, 0.4f);
+
string text = ClipboardHelper.GetText(clipboard);
if (text != null)
{
DeselectAllElements();
- ITextContainer textContainer = AddTextContainer(text, HorizontalAlignment.Center, VerticalAlignment.CENTER,
+ ITextContainer textContainer = AddTextContainer(text, pasteLocation.X, pasteLocation.Y,
FontFamily.GenericSansSerif, 12f, false, false, false, 2, Color.Black, Color.Transparent);
SelectElement(textContainer);
}
}
}
+ ///
+ /// Find a location to paste elements.
+ /// If mouse is over the surface - use it's position, otherwise use the visible area.
+ /// Return a point in image coordinate space.
+ ///
+ /// 0.0f for the left edge of visible area, 1.0f for the right edge of visible area.
+ /// 0.0f for the top edge of visible area, 1.0f for the bottom edge of visible area.
+ private Point GetPasteLocation(float horizontalRatio = 0.5f, float verticalRatio = 0.5f)
+ {
+ var point = PointToClient(MousePosition);
+ var rc = GetVisibleRectangle();
+ if (!rc.Contains(point))
+ {
+ point = new Point(
+ rc.Left + (int)(rc.Width * horizontalRatio),
+ rc.Top + (int)(rc.Height * verticalRatio)
+ );
+ }
+ return ToImageCoordinates(point);
+ }
+
+ ///
+ /// Get the rectangle bounding the part of this Surface currently visible in the editor (in surface coordinate space).
+ ///
+ public Rectangle GetVisibleRectangle()
+ {
+ var bounds = Bounds;
+ var clientArea = Parent.ClientRectangle;
+ return new Rectangle(
+ Math.Max(0, -bounds.Left),
+ Math.Max(0, -bounds.Top),
+ clientArea.Width,
+ clientArea.Height
+ );
+ }
+
+ ///
+ /// Get the rectangle bounding all selected elements (in surface coordinates space),
+ /// or empty rectangle if nothing is selcted.
+ ///
+ public Rectangle GetSelectionRectangle()
+ => ToSurfaceCoordinates(selectedElements.DrawingBounds);
+
///
/// Duplicate all the selecteded elements
///
@@ -2034,5 +2197,57 @@ namespace Greenshot.Drawing
{
return _elements.Contains(container);
}
+
+ public Point ToSurfaceCoordinates(Point point)
+ {
+ Point[] points = { point };
+ _zoomMatrix.TransformPoints(points);
+ return points[0];
+ }
+
+ public Rectangle ToSurfaceCoordinates(Rectangle rc)
+ {
+ if (_zoomMatrix.IsIdentity)
+ {
+ return rc;
+ }
+ else
+ {
+ Point[] points = { rc.Location, rc.Location + rc.Size };
+ _zoomMatrix.TransformPoints(points);
+ return new Rectangle(
+ points[0].X,
+ points[0].Y,
+ points[1].X - points[0].X,
+ points[1].Y - points[0].Y
+ );
+ }
+ }
+
+ public Point ToImageCoordinates(Point point)
+ {
+ Point[] points = { point };
+ _inverseZoomMatrix.TransformPoints(points);
+ return points[0];
+ }
+
+ public Rectangle ToImageCoordinates(Rectangle rc)
+ {
+ if (_inverseZoomMatrix.IsIdentity)
+ {
+ return rc;
+ }
+ else
+ {
+ Point[] points = { rc.Location, rc.Location + rc.Size };
+ _inverseZoomMatrix.TransformPoints(points);
+ return new Rectangle(
+ points[0].X,
+ points[0].Y,
+ points[1].X - points[0].X,
+ points[1].Y - points[0].Y
+ );
+ }
+ }
}
}
diff --git a/Greenshot/Drawing/TextContainer.cs b/Greenshot/Drawing/TextContainer.cs
index 0f4866d71..939dd3de6 100644
--- a/Greenshot/Drawing/TextContainer.cs
+++ b/Greenshot/Drawing/TextContainer.cs
@@ -22,6 +22,7 @@
using Greenshot.Drawing.Fields;
using Greenshot.Helpers;
using Greenshot.Memento;
+using GreenshotPlugin.Core;
using GreenshotPlugin.Interfaces.Drawing;
using System;
using System.ComponentModel;
@@ -155,6 +156,24 @@ namespace Greenshot.Drawing
FieldChanged += TextContainer_FieldChanged;
}
+ protected override void SwitchParent(Surface newParent)
+ {
+ _parent.SizeChanged -= Parent_SizeChanged;
+ base.SwitchParent(newParent);
+ _parent.SizeChanged += Parent_SizeChanged;
+ }
+
+ private void Parent_SizeChanged(object sender, EventArgs e)
+ {
+ UpdateTextBoxPosition();
+ UpdateTextBoxFont();
+ }
+
+ public override void ApplyBounds(RectangleF newBounds)
+ {
+ base.ApplyBounds(newBounds);
+ UpdateTextBoxPosition();
+ }
public override void Invalidate()
{
@@ -255,7 +274,8 @@ namespace Greenshot.Drawing
AcceptsTab = true,
AcceptsReturn = true,
BorderStyle = BorderStyle.None,
- Visible = false
+ Visible = false,
+ Font = new Font(FontFamily.GenericSansSerif, 1) // just need something non-default here
};
_textBox.DataBindings.Add("Text", this, "Text", false, DataSourceUpdateMode.OnPropertyChanged);
@@ -388,7 +408,6 @@ namespace Greenshot.Drawing
var newFont = CreateFont(fontFamily, fontBold, fontItalic, fontSize);
_font?.Dispose();
_font = newFont;
- _textBox.Font = _font;
}
catch (Exception ex)
{
@@ -400,7 +419,6 @@ namespace Greenshot.Drawing
var newFont = CreateFont(fontFamily, fontBold, fontItalic, fontSize);
_font?.Dispose();
_font = newFont;
- _textBox.Font = _font;
}
catch (Exception)
{
@@ -413,6 +431,8 @@ namespace Greenshot.Drawing
}
}
+ UpdateTextBoxFont();
+
UpdateAlignment();
}
@@ -423,12 +443,34 @@ namespace Greenshot.Drawing
}
///
- /// This will create the textbox exactly to the inner size of the element
+ /// Set TextBox font according to the TextContainer font and the parent zoom factor.
+ ///
+ private void UpdateTextBoxFont()
+ {
+ if (_textBox == null || _font == null)
+ {
+ return;
+ }
+
+ var textBoxFontScale = _parent?.ZoomFactor ?? Fraction.Identity;
+
+ var newFont = new Font(
+ _font.FontFamily,
+ _font.Size * textBoxFontScale,
+ _font.Style,
+ GraphicsUnit.Pixel
+ );
+ _textBox.Font.Dispose();
+ _textBox.Font = newFont;
+ }
+
+ ///
+ /// This will align the textbox exactly to the inner size of the element
/// is a bit of a hack, but for now it seems to work...
///
private void UpdateTextBoxPosition()
{
- if (_textBox == null)
+ if (_textBox == null || Parent == null)
{
return;
}
@@ -442,22 +484,20 @@ namespace Greenshot.Drawing
correction = -1;
}
Rectangle absRectangle = GuiRectangle.GetGuiRectangle(Left, Top, Width, Height);
- _textBox.Left = absRectangle.Left + lineWidth;
- _textBox.Top = absRectangle.Top + lineWidth;
+ Rectangle displayRectangle = Parent.ToSurfaceCoordinates(absRectangle);
+ _textBox.Left = displayRectangle.X + lineWidth;
+ _textBox.Top = displayRectangle.Y + lineWidth;
if (lineThickness <= 1)
{
lineWidth = 0;
}
- _textBox.Width = absRectangle.Width - 2 * lineWidth + correction;
- _textBox.Height = absRectangle.Height - 2 * lineWidth + correction;
- }
-
- public override void ApplyBounds(RectangleF newBounds)
- {
- base.ApplyBounds(newBounds);
- UpdateTextBoxPosition();
+ _textBox.Width = displayRectangle.Width - 2 * lineWidth + correction;
+ _textBox.Height = displayRectangle.Height - 2 * lineWidth + correction;
}
+ ///
+ /// Set TextBox text align and fore color according to field values.
+ ///
private void UpdateTextBoxFormat()
{
if (_textBox == null)
diff --git a/Greenshot/Forms/ImageEditorForm.Designer.cs b/Greenshot/Forms/ImageEditorForm.Designer.cs
index 9a653cc21..04da3d0a0 100644
--- a/Greenshot/Forms/ImageEditorForm.Designer.cs
+++ b/Greenshot/Forms/ImageEditorForm.Designer.cs
@@ -194,6 +194,26 @@ namespace Greenshot {
this.alignLeftToolStripMenuItem = new GreenshotPlugin.Controls.GreenshotToolStripMenuItem();
this.alignCenterToolStripMenuItem = new GreenshotPlugin.Controls.GreenshotToolStripMenuItem();
this.alignRightToolStripMenuItem = new GreenshotPlugin.Controls.GreenshotToolStripMenuItem();
+ this.zoomMenuStrip = new System.Windows.Forms.ContextMenuStrip(this.components);
+ this.zoomInMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoomOutMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoomMenuSeparator1 = new System.Windows.Forms.ToolStripSeparator();
+ this.zoomBestFitMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoomMenuSeparator2 = new System.Windows.Forms.ToolStripSeparator();
+ this.zoom25MenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoom50MenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoom66MenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoom75MenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoomMenuSeparator3 = new System.Windows.Forms.ToolStripSeparator();
+ this.zoomActualSizeMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoomMenuSeparator4 = new System.Windows.Forms.ToolStripSeparator();
+ this.zoom200MenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoom300MenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoom400MenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoom600MenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.zoomStatusDropDownBtn = new System.Windows.Forms.ToolStripDropDownButton();
+ this.zoomMainMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.statusStripSpacer = new System.Windows.Forms.ToolStripStatusLabel();
this.topToolStripContainer.BottomToolStripPanel.SuspendLayout();
this.topToolStripContainer.ContentPanel.SuspendLayout();
this.topToolStripContainer.LeftToolStripPanel.SuspendLayout();
@@ -203,6 +223,7 @@ namespace Greenshot {
this.tableLayoutPanel1.SuspendLayout();
this.toolsToolStrip.SuspendLayout();
this.menuStrip1.SuspendLayout();
+ this.zoomMenuStrip.SuspendLayout();
this.destinationsToolStrip.SuspendLayout();
this.propertiesToolStrip.SuspendLayout();
this.fileSavedStatusContextMenu.SuspendLayout();
@@ -238,7 +259,9 @@ namespace Greenshot {
this.statusStrip1.Dock = System.Windows.Forms.DockStyle.None;
this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.dimensionsLabel,
- this.statusLabel});
+ this.statusLabel,
+ this.statusStripSpacer,
+ this.zoomStatusDropDownBtn});
this.statusStrip1.Location = new System.Drawing.Point(0, 0);
this.statusStrip1.Name = "statusStrip1";
this.statusStrip1.Size = new System.Drawing.Size(785, 24);
@@ -538,6 +561,7 @@ namespace Greenshot {
this.editToolStripMenuItem,
this.objectToolStripMenuItem,
this.pluginToolStripMenuItem,
+ this.zoomMainMenuItem,
this.helpToolStripMenuItem});
this.menuStrip1.Name = "menuStrip1";
this.menuStrip1.BackColor = System.Drawing.SystemColors.Control;
@@ -1624,6 +1648,171 @@ namespace Greenshot {
this.alignRightToolStripMenuItem.Name = "alignRightToolStripMenuItem";
this.alignRightToolStripMenuItem.Tag = System.Drawing.StringAlignment.Far;
//
+ // zoomMainMenuItem
+ //
+ this.zoomMainMenuItem.DropDown = this.zoomMenuStrip;
+ this.zoomMainMenuItem.Name = "zoomMainMenuItem";
+ this.zoomMainMenuItem.Size = new System.Drawing.Size(51, 20);
+ this.zoomMainMenuItem.Text = "Zoom";
+ //
+ // zoomMenuStrip
+ //
+ this.zoomMenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.zoomInMenuItem,
+ this.zoomOutMenuItem,
+ this.zoomMenuSeparator1,
+ this.zoomBestFitMenuItem,
+ this.zoomMenuSeparator2,
+ this.zoom25MenuItem,
+ this.zoom50MenuItem,
+ this.zoom66MenuItem,
+ this.zoom75MenuItem,
+ this.zoomMenuSeparator3,
+ this.zoomActualSizeMenuItem,
+ this.zoomMenuSeparator4,
+ this.zoom200MenuItem,
+ this.zoom300MenuItem,
+ this.zoom400MenuItem,
+ this.zoom600MenuItem});
+ this.zoomMenuStrip.Name = "zoomMenuStrip";
+ this.zoomMenuStrip.Size = new System.Drawing.Size(210, 292);
+ //
+ // zoomInMenuItem
+ //
+ this.zoomInMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("zoomInMenuItem.Image")));
+ this.zoomInMenuItem.Name = "zoomInMenuItem";
+ this.zoomInMenuItem.ShortcutKeyDisplayString = "Ctrl++";
+ this.zoomInMenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoomInMenuItem.Text = "Zoom In";
+ this.zoomInMenuItem.Click += new System.EventHandler(this.ZoomInMenuItemClick);
+ //
+ // zoomOutMenuItem
+ //
+ this.zoomOutMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("zoomOutMenuItem.Image")));
+ this.zoomOutMenuItem.Name = "zoomOutMenuItem";
+ this.zoomOutMenuItem.ShortcutKeyDisplayString = "Ctrl+-";
+ this.zoomOutMenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoomOutMenuItem.Text = "Zoom Out";
+ this.zoomOutMenuItem.Click += new System.EventHandler(this.ZoomOutMenuItemClick);
+ //
+ // zoomMenuSeparator1
+ //
+ this.zoomMenuSeparator1.Name = "zoomMenuSeparator1";
+ this.zoomMenuSeparator1.Size = new System.Drawing.Size(206, 6);
+ //
+ // zoomBestFitMenuItem
+ //
+ this.zoomBestFitMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("zoomBestFitMenuItem.Image")));
+ this.zoomBestFitMenuItem.Name = "zoomBestFitMenuItem";
+ this.zoomBestFitMenuItem.ShortcutKeyDisplayString = "Ctrl+9";
+ this.zoomBestFitMenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoomBestFitMenuItem.Text = "Best Fit";
+ this.zoomBestFitMenuItem.Click += new System.EventHandler(this.ZoomBestFitMenuItemClick);
+ //
+ // zoomMenuSeparator2
+ //
+ this.zoomMenuSeparator2.Name = "zoomMenuSeparator2";
+ this.zoomMenuSeparator2.Size = new System.Drawing.Size(206, 6);
+ //
+ // zoom25MenuItem
+ //
+ this.zoom25MenuItem.Name = "zoom25MenuItem";
+ this.zoom25MenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoom25MenuItem.Tag = "1/4";
+ this.zoom25MenuItem.Text = "25%";
+ this.zoom25MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
+ //
+ // zoom50MenuItem
+ //
+ this.zoom50MenuItem.Name = "zoom50MenuItem";
+ this.zoom50MenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoom50MenuItem.Tag = "1/2";
+ this.zoom50MenuItem.Text = "50%";
+ this.zoom50MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
+ //
+ // zoom66MenuItem
+ //
+ this.zoom66MenuItem.Name = "zoom66MenuItem";
+ this.zoom66MenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoom66MenuItem.Tag = "2/3";
+ this.zoom66MenuItem.Text = "66%";
+ this.zoom66MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
+ //
+ // zoom75MenuItem
+ //
+ this.zoom75MenuItem.Name = "zoom75MenuItem";
+ this.zoom75MenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoom75MenuItem.Tag = "3/4";
+ this.zoom75MenuItem.Text = "75%";
+ this.zoom75MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
+ //
+ // zoomMenuSeparator3
+ //
+ this.zoomMenuSeparator3.Name = "zoomMenuSeparator3";
+ this.zoomMenuSeparator3.Size = new System.Drawing.Size(206, 6);
+ //
+ // zoomActualSizeMenuItem
+ //
+ this.zoomActualSizeMenuItem.Image = ((System.Drawing.Image)(resources.GetObject("zoomActualSizeMenuItem.Image")));
+ this.zoomActualSizeMenuItem.Name = "zoomActualSizeMenuItem";
+ this.zoomActualSizeMenuItem.ShortcutKeyDisplayString = "Ctrl+0";
+ this.zoomActualSizeMenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoomActualSizeMenuItem.Tag = "1/1";
+ this.zoomActualSizeMenuItem.Text = "100% - Actual Size";
+ this.zoomActualSizeMenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
+ //
+ // zoomMenuSeparator4
+ //
+ this.zoomMenuSeparator4.Name = "zoomMenuSeparator4";
+ this.zoomMenuSeparator4.Size = new System.Drawing.Size(206, 6);
+ //
+ // zoom200MenuItem
+ //
+ this.zoom200MenuItem.Name = "zoom200MenuItem";
+ this.zoom200MenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoom200MenuItem.Tag = "2/1";
+ this.zoom200MenuItem.Text = "200%";
+ this.zoom200MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
+ //
+ // zoom300MenuItem
+ //
+ this.zoom300MenuItem.Name = "zoom300MenuItem";
+ this.zoom300MenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoom300MenuItem.Tag = "3/1";
+ this.zoom300MenuItem.Text = "300%";
+ this.zoom300MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
+ //
+ // zoom400MenuItem
+ //
+ this.zoom400MenuItem.Name = "zoom400MenuItem";
+ this.zoom400MenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoom400MenuItem.Tag = "4/1";
+ this.zoom400MenuItem.Text = "400%";
+ this.zoom400MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
+ //
+ // zoom600MenuItem
+ //
+ this.zoom600MenuItem.Name = "zoom600MenuItem";
+ this.zoom600MenuItem.Size = new System.Drawing.Size(209, 22);
+ this.zoom600MenuItem.Tag = "6/1";
+ this.zoom600MenuItem.Text = "600%";
+ this.zoom600MenuItem.Click += new System.EventHandler(this.ZoomSetValueMenuItemClick);
+ //
+ // statusStripSpacer
+ //
+ this.statusStripSpacer.Name = "statusStripSpacer";
+ this.statusStripSpacer.Size = new System.Drawing.Size(599, 19);
+ this.statusStripSpacer.Spring = true;
+ //
+ // zoomStatusDropDownBtn
+ //
+ this.zoomStatusDropDownBtn.DropDown = this.zoomMenuStrip;
+ this.zoomStatusDropDownBtn.Image = ((System.Drawing.Image)(resources.GetObject("zoomStatusDropDownBtn.Image")));
+ this.zoomStatusDropDownBtn.ImageTransparentColor = System.Drawing.Color.Magenta;
+ this.zoomStatusDropDownBtn.Name = "zoomStatusDropDownBtn";
+ this.zoomStatusDropDownBtn.Size = new System.Drawing.Size(64, 22);
+ this.zoomStatusDropDownBtn.Text = "100%";
+ //
// ImageEditorForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
@@ -1645,6 +1834,7 @@ namespace Greenshot {
this.statusStrip1.ResumeLayout(true);
this.tableLayoutPanel1.ResumeLayout(true);
this.toolsToolStrip.ResumeLayout(true);
+ this.zoomMenuStrip.ResumeLayout(false);
this.menuStrip1.ResumeLayout(true);
this.destinationsToolStrip.ResumeLayout(true);
this.propertiesToolStrip.ResumeLayout(true);
@@ -1794,5 +1984,25 @@ namespace Greenshot {
private Greenshot.Controls.ToolStripColorButton btnLineColor;
private GreenshotPlugin.Controls.GreenshotToolStripMenuItem autoCropToolStripMenuItem;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator17;
+ private System.Windows.Forms.ContextMenuStrip zoomMenuStrip;
+ private System.Windows.Forms.ToolStripMenuItem zoomInMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem zoomOutMenuItem;
+ private System.Windows.Forms.ToolStripSeparator zoomMenuSeparator1;
+ private System.Windows.Forms.ToolStripMenuItem zoomBestFitMenuItem;
+ private System.Windows.Forms.ToolStripSeparator zoomMenuSeparator2;
+ private System.Windows.Forms.ToolStripMenuItem zoom25MenuItem;
+ private System.Windows.Forms.ToolStripMenuItem zoom50MenuItem;
+ private System.Windows.Forms.ToolStripMenuItem zoom66MenuItem;
+ private System.Windows.Forms.ToolStripMenuItem zoom75MenuItem;
+ private System.Windows.Forms.ToolStripSeparator zoomMenuSeparator3;
+ private System.Windows.Forms.ToolStripMenuItem zoomActualSizeMenuItem;
+ private System.Windows.Forms.ToolStripSeparator zoomMenuSeparator4;
+ private System.Windows.Forms.ToolStripMenuItem zoom200MenuItem;
+ private System.Windows.Forms.ToolStripMenuItem zoom300MenuItem;
+ private System.Windows.Forms.ToolStripMenuItem zoom400MenuItem;
+ private System.Windows.Forms.ToolStripMenuItem zoom600MenuItem;
+ private System.Windows.Forms.ToolStripDropDownButton zoomStatusDropDownBtn;
+ private System.Windows.Forms.ToolStripMenuItem zoomMainMenuItem;
+ private System.Windows.Forms.ToolStripStatusLabel statusStripSpacer;
}
}
diff --git a/Greenshot/Forms/ImageEditorForm.cs b/Greenshot/Forms/ImageEditorForm.cs
index bcb8c275c..d397b8bc8 100644
--- a/Greenshot/Forms/ImageEditorForm.cs
+++ b/Greenshot/Forms/ImageEditorForm.cs
@@ -66,6 +66,11 @@ namespace Greenshot {
// whether part of the editor controls are disabled depending on selected item(s)
private bool _controlsDisabledDueToConfirmable;
+ ///
+ /// All provided zoom values (in percents) in ascending order.
+ ///
+ private readonly Fraction[] ZOOM_VALUES = new Fraction[] { (1, 4), (1, 2), (2, 3), (3, 4), (1 ,1), (2, 1), (3, 1), (4, 1), (6, 1) };
+
///
/// An Implementation for the IImageEditor, this way Plugins have access to the HWND handles wich can be used with Win32 API calls.
///
@@ -420,20 +425,14 @@ namespace Greenshot {
///
///
///
- private void SurfaceSizeChanged(object sender, EventArgs e) {
- if (EditorConfiguration.MatchSizeToCapture) {
- // Set editor's initial size to the size of the surface plus the size of the chrome
- Size imageSize = Surface.Image.Size;
- Size currentFormSize = Size;
- Size currentImageClientSize = panel1.ClientSize;
- int minimumFormWidth = 650;
- int minimumFormHeight = 530;
- int newWidth = Math.Max(minimumFormWidth, currentFormSize.Width - currentImageClientSize.Width + imageSize.Width);
- int newHeight = Math.Max(minimumFormHeight, currentFormSize.Height - currentImageClientSize.Height + imageSize.Height);
- Size = new Size(newWidth, newHeight);
+ private void SurfaceSizeChanged(object sender, EventArgs e)
+ {
+ if (EditorConfiguration.MatchSizeToCapture)
+ {
+ Size = GetOptimalWindowSize();
}
dimensionsLabel.Text = Surface.Image.Width + "x" + Surface.Image.Height;
- ImageEditorFormResize(sender, new EventArgs());
+ AlignCanvasPositionAfterResize();
}
public ISurface Surface {
@@ -860,15 +859,34 @@ namespace Greenshot {
case Keys.Oemcomma: // Rotate CCW Ctrl + ,
RotateCcwToolstripButtonClick(sender, e);
break;
- case Keys.OemPeriod: // Rotate CW Ctrl + .
+ case Keys.OemPeriod: // Rotate CW Ctrl + .
RotateCwToolstripButtonClick(sender, e);
break;
- case Keys.Add: // Ctrl + +
- case Keys.Oemplus: // Ctrl + +
+ case Keys.Add: // Ctrl + Num+
+ case Keys.Oemplus: // Ctrl + +
+ ZoomInMenuItemClick(sender, e);
+ break;
+ case Keys.Subtract: // Ctrl + Num-
+ case Keys.OemMinus: // Ctrl + -
+ ZoomOutMenuItemClick(sender, e);
+ break;
+ case Keys.NumPad0: // Ctrl + Num0
+ case Keys.D0: // Ctrl + 0
+ ZoomSetValueMenuItemClick(zoomActualSizeMenuItem, e);
+ break;
+ case Keys.NumPad9: // Ctrl + Num9
+ case Keys.D9: // Ctrl + 9
+ ZoomBestFitMenuItemClick(sender, e);
+ break;
+ }
+ } else if (e.Modifiers.Equals(Keys.Control | Keys.Shift)) {
+ switch (e.KeyCode) {
+ case Keys.Add: // Ctrl + Shift + Num+
+ case Keys.Oemplus: // Ctrl + Shift + +
EnlargeCanvasToolStripMenuItemClick(sender, e);
break;
- case Keys.Subtract: // Ctrl + -
- case Keys.OemMinus: // Ctrl + -
+ case Keys.Subtract: // Ctrl + Shift + Num-
+ case Keys.OemMinus: // Ctrl + Shift + -
ShrinkCanvasToolStripMenuItemClick(sender, e);
break;
}
@@ -1444,28 +1462,178 @@ namespace Greenshot {
}
private void ImageEditorFormResize(object sender, EventArgs e) {
+ AlignCanvasPositionAfterResize();
+ }
+
+ private void AlignCanvasPositionAfterResize() {
if (Surface?.Image == null || panel1 == null) {
return;
}
- Size imageSize = Surface.Image.Size;
- Size currentClientSize = panel1.ClientSize;
var canvas = Surface as Control;
+ Size canvasSize = canvas.Size;
+ Size currentClientSize = panel1.ClientSize;
Panel panel = (Panel) canvas?.Parent;
if (panel == null) {
return;
}
int offsetX = -panel.HorizontalScroll.Value;
int offsetY = -panel.VerticalScroll.Value;
- if (currentClientSize.Width > imageSize.Width) {
- canvas.Left = offsetX + (currentClientSize.Width - imageSize.Width) / 2;
+ if (currentClientSize.Width > canvasSize.Width) {
+ canvas.Left = offsetX + (currentClientSize.Width - canvasSize.Width) / 2;
} else {
canvas.Left = offsetX + 0;
}
- if (currentClientSize.Height > imageSize.Height) {
- canvas.Top = offsetY + (currentClientSize.Height - imageSize.Height) / 2;
+ if (currentClientSize.Height > canvasSize.Height) {
+ canvas.Top = offsetY + (currentClientSize.Height - canvasSize.Height) / 2;
} else {
canvas.Top = offsetY + 0;
}
}
+
+ ///
+ /// Compute a size as a sum of surface size and chrome.
+ /// Upper bound is working area of the screen. Lower bound is fixed value.
+ ///
+ private Size GetOptimalWindowSize() {
+ var surfaceSize = (Surface as Control).Size;
+ var chromeSize = GetChromeSize();
+ var newWidth = chromeSize.Width + surfaceSize.Width;
+ var newHeight = chromeSize.Height + surfaceSize.Height;
+
+ // Upper bound. Don't make it bigger than the available working area.
+ var maxWindowSize = GetAvailableScreenSpace();
+ newWidth = Math.Min(newWidth, maxWindowSize.Width);
+ newHeight = Math.Min(newHeight, maxWindowSize.Height);
+
+ // Lower bound. Don't make it smaller than a fixed value.
+ int minimumFormWidth = 650;
+ int minimumFormHeight = 530;
+ newWidth = Math.Max(minimumFormWidth, newWidth);
+ newHeight = Math.Max(minimumFormHeight, newHeight);
+
+ return new Size(newWidth, newHeight);
+ }
+
+ private Size GetChromeSize()
+ => Size - panel1.ClientSize;
+
+ ///
+ /// Compute a size that the form can take without getting out of working area of the screen.
+ ///
+ private Size GetAvailableScreenSpace() {
+ var screen = Screen.FromControl(this);
+ var screenBounds = screen.Bounds;
+ var workingArea = screen.WorkingArea;
+ if (Left > screenBounds.Left && Top > screenBounds.Top) {
+ return new Size(workingArea.Right - Left, workingArea.Bottom - Top);
+ } else {
+ return workingArea.Size;
+ }
+ }
+
+ private void ZoomInMenuItemClick(object sender, EventArgs e) {
+ var zoomValue = Surface.ZoomFactor;
+ var nextIndex = Array.FindIndex(ZOOM_VALUES, v => v > zoomValue);
+ var nextValue = nextIndex < 0 ? ZOOM_VALUES[ZOOM_VALUES.Length - 1] : ZOOM_VALUES[nextIndex];
+
+ ZoomSetValue(nextValue);
+ }
+
+ private void ZoomOutMenuItemClick(object sender, EventArgs e) {
+ var zoomValue = Surface.ZoomFactor;
+ var nextIndex = Array.FindLastIndex(ZOOM_VALUES, v => v < zoomValue);
+ var nextValue = nextIndex < 0 ? ZOOM_VALUES[0] : ZOOM_VALUES[nextIndex];
+
+ ZoomSetValue(nextValue);
+ }
+
+ private void ZoomSetValueMenuItemClick(object sender, EventArgs e) {
+ var senderMenuItem = (ToolStripMenuItem)sender;
+ var nextValue = Fraction.Parse((string)senderMenuItem.Tag);
+
+ ZoomSetValue(nextValue);
+ }
+
+ private void ZoomBestFitMenuItemClick(object sender, EventArgs e) {
+ var maxWindowSize = GetAvailableScreenSpace();
+ var chromeSize = GetChromeSize();
+ var maxImageSize = maxWindowSize - chromeSize;
+ var imageSize = Surface.Image.Size;
+
+ static bool isFit(Fraction scale, int source, int boundary)
+ => (int)(source * scale) <= boundary;
+
+ var nextIndex = Array.FindLastIndex(
+ ZOOM_VALUES,
+ zoom => isFit(zoom, imageSize.Width, maxImageSize.Width)
+ && isFit(zoom, imageSize.Height, maxImageSize.Height)
+ );
+ var nextValue = nextIndex < 0 ? ZOOM_VALUES[0] : ZOOM_VALUES[nextIndex];
+
+ ZoomSetValue(nextValue);
+ }
+
+ private void ZoomSetValue(Fraction value) {
+ var surface = Surface as Surface;
+ var panel = surface?.Parent as Panel;
+ if (panel == null)
+ {
+ return;
+ }
+ if (value == Surface.ZoomFactor)
+ {
+ return;
+ }
+
+ // Store scroll position
+ var rc = surface.GetVisibleRectangle(); // use visible rc by default
+ var size = surface.Size;
+ if (value > Surface.ZoomFactor) // being smart on zoom-in
+ {
+ var selection = surface.GetSelectionRectangle();
+ selection.Intersect(rc);
+ if (selection != Rectangle.Empty)
+ {
+ rc = selection; // zoom to visible part of selection
+ }
+ else
+ {
+ // if image fits completely to currently visible rc and there are no things to focus on
+ // - prefer top left corner to zoom-in as less disorienting for screenshots
+ if (size.Width < rc.Width)
+ {
+ rc.Width = 0;
+ }
+ if (size.Height < rc.Height)
+ {
+ rc.Height = 0;
+ }
+ }
+ }
+ var horizontalCenter = 1.0 * (rc.Left + rc.Width / 2) / size.Width;
+ var verticalCenter = 1.0 * (rc.Top + rc.Height / 2) / size.Height;
+
+ // Set the new zoom value
+ Surface.ZoomFactor = value;
+ Size = GetOptimalWindowSize();
+ AlignCanvasPositionAfterResize();
+
+ // Update zoom controls
+ zoomStatusDropDownBtn.Text = ((int)(100 * (double)value)).ToString() + "%";
+ var valueString = value.ToString();
+ foreach (var item in zoomMenuStrip.Items) {
+ if (item is ToolStripMenuItem menuItem) {
+ menuItem.Checked = menuItem.Tag as string == valueString;
+ }
+ }
+
+ // Restore scroll position
+ rc = surface.GetVisibleRectangle();
+ size = surface.Size;
+ panel.AutoScrollPosition = new Point(
+ (int)(horizontalCenter * size.Width) - rc.Width / 2,
+ (int)(verticalCenter * size.Height) - rc.Height / 2
+ );
+ }
}
}
diff --git a/Greenshot/Forms/ImageEditorForm.resx b/Greenshot/Forms/ImageEditorForm.resx
index e4188604b..025543b10 100644
--- a/Greenshot/Forms/ImageEditorForm.resx
+++ b/Greenshot/Forms/ImageEditorForm.resx
@@ -937,6 +937,84 @@
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
+ AAAAAElFTkSuQmCC
+
+
+
+
+ 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
@@ -1026,4 +1104,7 @@
17, 17
+
+ 782, 17
+
\ No newline at end of file
diff --git a/Greenshot/icons/fugue/magnifier-zoom-actual.png b/Greenshot/icons/fugue/magnifier-zoom-actual.png
new file mode 100644
index 000000000..21cf71d63
Binary files /dev/null and b/Greenshot/icons/fugue/magnifier-zoom-actual.png differ
diff --git a/Greenshot/icons/fugue/magnifier-zoom-fit.png b/Greenshot/icons/fugue/magnifier-zoom-fit.png
new file mode 100644
index 000000000..8364359fd
Binary files /dev/null and b/Greenshot/icons/fugue/magnifier-zoom-fit.png differ
diff --git a/Greenshot/icons/fugue/magnifier-zoom-in.png b/Greenshot/icons/fugue/magnifier-zoom-in.png
new file mode 100644
index 000000000..800ec1205
Binary files /dev/null and b/Greenshot/icons/fugue/magnifier-zoom-in.png differ
diff --git a/Greenshot/icons/fugue/magnifier-zoom-out.png b/Greenshot/icons/fugue/magnifier-zoom-out.png
new file mode 100644
index 000000000..3ec2b7a5d
Binary files /dev/null and b/Greenshot/icons/fugue/magnifier-zoom-out.png differ
diff --git a/Greenshot/icons/fugue/magnifier-zoom.png b/Greenshot/icons/fugue/magnifier-zoom.png
new file mode 100644
index 000000000..941b10356
Binary files /dev/null and b/Greenshot/icons/fugue/magnifier-zoom.png differ
diff --git a/GreenshotOfficePlugin/OfficeExport/OneNoteExporter.cs b/GreenshotOfficePlugin/OfficeExport/OneNoteExporter.cs
index 23525b608..611594334 100644
--- a/GreenshotOfficePlugin/OfficeExport/OneNoteExporter.cs
+++ b/GreenshotOfficePlugin/OfficeExport/OneNoteExporter.cs
@@ -95,7 +95,7 @@ namespace GreenshotOfficePlugin.OfficeExport
var pngOutputSettings = new SurfaceOutputSettings(OutputFormat.png, 100, false);
ImageOutput.SaveToStream(surfaceToUpload, pngStream, pngOutputSettings);
var base64String = Convert.ToBase64String(pngStream.GetBuffer());
- var imageXmlStr = string.Format(XmlImageContent, base64String, surfaceToUpload.Width, surfaceToUpload.Height);
+ var imageXmlStr = string.Format(XmlImageContent, base64String, surfaceToUpload.Image.Width, surfaceToUpload.Image.Height);
var pageChangesXml = string.Format(XmlOutline, imageXmlStr, page.Id, OnenoteNamespace2010, page.Name);
LOG.InfoFormat("Sending XML: {0}", pageChangesXml);
oneNoteApplication.ComObject.UpdatePageContent(pageChangesXml, DateTime.MinValue, XMLSchema.xs2010, false);
diff --git a/GreenshotPlugin/Core/Fraction.cs b/GreenshotPlugin/Core/Fraction.cs
new file mode 100644
index 000000000..312b91bb8
--- /dev/null
+++ b/GreenshotPlugin/Core/Fraction.cs
@@ -0,0 +1,152 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace GreenshotPlugin.Core
+{
+ ///
+ /// Basic Fraction (Rational) numbers with features only needed to represent scale factors.
+ ///
+ public readonly struct Fraction : IEquatable, IComparable
+ {
+ public static Fraction Identity { get; } = new Fraction(1, 1);
+
+ public uint Numerator { get; }
+ public uint Denominator { get; }
+
+ public Fraction(uint numerator, uint denominator)
+ {
+ if (denominator == 0)
+ {
+ throw new ArgumentException("Can't divide by zero.", nameof(denominator));
+ }
+ if (numerator == 0)
+ {
+ throw new ArgumentException("Zero is not supported by this implementation.", nameof(numerator));
+ }
+ var gcd = GreatestCommonDivisor(numerator, denominator);
+ Numerator = numerator / gcd;
+ Denominator = denominator / gcd;
+ }
+
+ public Fraction Inverse()
+ => new Fraction(Denominator, Numerator);
+
+ #region Parse
+
+ private static readonly Regex PARSE_REGEX = new Regex(@"^([1-9][0-9]*)\/([1-9][0-9]*)$", RegexOptions.Compiled);
+ public static bool TryParse(string str, out Fraction result)
+ {
+ var match = PARSE_REGEX.Match(str);
+ if (!match.Success)
+ {
+ result = Identity;
+ return false;
+ }
+ var numerator = uint.Parse(match.Groups[1].Value);
+ var denominator = uint.Parse(match.Groups[2].Value);
+ result = new Fraction(numerator, denominator);
+ return true;
+ }
+
+ public static Fraction Parse(string str)
+ => TryParse(str, out var result)
+ ? result
+ : throw new ArgumentException($"Could not parse the input \"{str}\".", nameof(str));
+
+ #endregion
+
+ #region Overrides, interface implementations
+
+ public override string ToString()
+ => $"{Numerator}/{Denominator}";
+
+ public override bool Equals(object obj)
+ => obj is Fraction fraction && Equals(fraction);
+
+ public bool Equals(Fraction other)
+ => Numerator == other.Numerator && Denominator == other.Denominator;
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ int hashCode = -1534900553;
+ hashCode = hashCode * -1521134295 + Numerator.GetHashCode();
+ hashCode = hashCode * -1521134295 + Denominator.GetHashCode();
+ return hashCode;
+ }
+ }
+
+ public int CompareTo(Fraction other)
+ => (int)(Numerator * other.Denominator) - (int)(other.Numerator * Denominator);
+
+ #endregion
+
+ #region Equality operators
+
+ public static bool operator ==(Fraction left, Fraction right)
+ => left.Equals(right);
+
+ public static bool operator !=(Fraction left, Fraction right)
+ => !(left == right);
+
+ #endregion
+
+ #region Comparison operators
+
+ public static bool operator <(Fraction left, Fraction right)
+ => left.CompareTo(right) < 0;
+
+ public static bool operator <=(Fraction left, Fraction right)
+ => left.CompareTo(right) <= 0;
+
+ public static bool operator >(Fraction left, Fraction right)
+ => left.CompareTo(right) > 0;
+
+ public static bool operator >=(Fraction left, Fraction right)
+ => left.CompareTo(right) >= 0;
+
+ #endregion
+
+ #region Scale operators
+
+ public static Fraction operator *(Fraction left, Fraction right)
+ => new Fraction(left.Numerator * right.Numerator, left.Denominator * right.Denominator);
+
+ public static Fraction operator *(Fraction left, uint right)
+ => new Fraction(left.Numerator * right, left.Denominator);
+
+ public static Fraction operator *(uint left, Fraction right)
+ => new Fraction(left * right.Numerator, right.Denominator);
+
+ public static Fraction operator /(Fraction left, Fraction right)
+ => new Fraction(left.Numerator * right.Denominator, left.Denominator * right.Numerator);
+
+ public static Fraction operator /(Fraction left, uint right)
+ => new Fraction(left.Numerator, left.Denominator * right);
+
+ public static Fraction operator /(uint left, Fraction right)
+ => new Fraction(left * right.Denominator, right.Numerator);
+
+ #endregion
+
+ #region Type conversion operators
+
+ public static implicit operator double(Fraction fraction)
+ => 1.0 * fraction.Numerator / fraction.Denominator;
+
+ public static implicit operator float(Fraction fraction)
+ => 1.0f * fraction.Numerator / fraction.Denominator;
+
+ public static implicit operator Fraction(uint number)
+ => new Fraction(number, 1u);
+
+ public static implicit operator Fraction((uint numerator, uint demoninator) tuple)
+ => new Fraction(tuple.numerator, tuple.demoninator);
+
+ #endregion
+
+ private static uint GreatestCommonDivisor(uint a, uint b)
+ => (b != 0) ? GreatestCommonDivisor(b, a % b) : a;
+ }
+}
diff --git a/GreenshotPlugin/Interfaces/Drawing/Container.cs b/GreenshotPlugin/Interfaces/Drawing/Container.cs
index b2b6e3d19..f4065801a 100644
--- a/GreenshotPlugin/Interfaces/Drawing/Container.cs
+++ b/GreenshotPlugin/Interfaces/Drawing/Container.cs
@@ -102,7 +102,6 @@ namespace GreenshotPlugin.Interfaces.Drawing
get;
set;
}
- void AlignToParent(HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment);
void Invalidate();
bool ClickableAt(int x, int y);
void MoveBy(int x, int y);
@@ -146,6 +145,10 @@ namespace GreenshotPlugin.Interfaces.Drawing
get;
set;
}
+ Rectangle DrawingBounds
+ {
+ get;
+ }
void MakeBoundsChangeUndoable(bool allowMerge);
void Transform(Matrix matrix);
void MoveBy(int dx, int dy);
diff --git a/GreenshotPlugin/Interfaces/ISurface.cs b/GreenshotPlugin/Interfaces/ISurface.cs
index 44a150364..068ab0f29 100644
--- a/GreenshotPlugin/Interfaces/ISurface.cs
+++ b/GreenshotPlugin/Interfaces/ISurface.cs
@@ -23,6 +23,7 @@ using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
+using GreenshotPlugin.Core;
using GreenshotPlugin.Effects;
using GreenshotPlugin.Interfaces.Drawing;
@@ -84,8 +85,8 @@ namespace GreenshotPlugin.Interfaces
/// The TextContainer will be "re"sized to the text size.
///
/// String to show
- /// Left, Center, Right
- /// TOP, CENTER, BOTTOM
+ /// Where to put the container, X coordinate in the Image coordinate space
+ /// Where to put the container, Y coordinate in the Image coordinate space
/// FontFamily
/// Font Size in float
/// bool true if italic
@@ -94,7 +95,7 @@ namespace GreenshotPlugin.Interfaces
/// size of border (0 for none)
/// Color of string
/// Color of background (e.g. Color.Transparent)
- ITextContainer AddTextContainer(string text, HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment, FontFamily family, float size, bool italic, bool bold, bool shadow, int borderSize, Color color, Color fillColor);
+ ITextContainer AddTextContainer(string text, int x, int y, FontFamily family, float size, bool italic, bool bold, bool shadow, int borderSize, Color color, Color fillColor);
IImageContainer AddImageContainer(Image image, int x, int y);
ICursorContainer AddCursorContainer(Cursor cursor, int x, int y);
@@ -147,8 +148,13 @@ namespace GreenshotPlugin.Interfaces
///
/// This returns false if the container is deleted but still in the undo stack
bool IsOnSurface(IDrawableContainer container);
- void Invalidate(Rectangle rectangleToInvalidate);
void Invalidate();
+ ///
+ /// Invalidates the specified region of the Surface.
+ /// Takes care of the Surface zoom level, accepts rectangle in the coordinate space of the Image.
+ ///
+ /// Bounding rectangle for updated elements, in the coordinate space of the Image.
+ void InvalidateElements(Rectangle rectangleToInvalidate);
bool Modified
{
get;
@@ -186,9 +192,32 @@ namespace GreenshotPlugin.Interfaces
get;
set;
}
- int Width { get; }
- int Height { get; }
+
+ ///
+ /// Zoom value applied to the surface.
+ ///
+ Fraction ZoomFactor { get; set; }
+ ///
+ /// Translate a point from image coorditate space to surface coordinate space.
+ ///
+ /// A point in the coordinate space of the image.
+ Point ToSurfaceCoordinates(Point point);
+ ///
+ /// Translate a rectangle from image coorditate space to surface coordinate space.
+ ///
+ /// A rectangle in the coordinate space of the image.
+ Rectangle ToSurfaceCoordinates(Rectangle rc);
+ ///
+ /// Translate a point from surface coorditate space to image coordinate space.
+ ///
+ /// A point in the coordinate space of the surface.
+ Point ToImageCoordinates(Point point);
+ ///
+ /// Translate a rectangle from surface coorditate space to image coordinate space.
+ ///
+ /// A rectangle in the coordinate space of the surface.
+ Rectangle ToImageCoordinates(Rectangle rc);
void MakeUndoable(IMemento memento, bool allowMerge);
}
-}
\ No newline at end of file
+}