Zoom feature for the editor

This commit is contained in:
Killy 2020-04-26 16:50:17 +03:00
parent 38739ce427
commit 8b45489b11
18 changed files with 585 additions and 52 deletions

View file

@ -116,6 +116,23 @@ namespace Greenshot.Drawing.Adorners
}
}
/// <summary>
/// Return the bounds of the Adorner as displayed on the parent Surface
/// </summary>
protected virtual Rectangle SurfaceBounds
{
get
{
Point[] points = { Location };
var matrix = Owner.Parent.ZoomMatrix;
if (!matrix.IsIdentity)
{
matrix.TransformPoints(points);
}
return new Rectangle(points[0].X - _size.Width / 2, points[0].Y - _size.Height / 2, _size.Width, _size.Height);
}
}
/// <summary>
/// The adorner is active if the edit status is not idle or undrawn
/// </summary>

View file

@ -142,7 +142,7 @@ namespace Greenshot.Drawing.Adorners
{
Graphics targetGraphics = paintEventArgs.Graphics;
var bounds = Bounds;
var bounds = SurfaceBounds;
GraphicsState state = targetGraphics.Save();
targetGraphics.SmoothingMode = SmoothingMode.None;

View file

@ -169,7 +169,7 @@ namespace Greenshot.Drawing.Adorners
{
Graphics targetGraphics = paintEventArgs.Graphics;
var bounds = Bounds;
var bounds = SurfaceBounds;
GraphicsState state = targetGraphics.Save();
targetGraphics.SmoothingMode = SmoothingMode.None;

View file

@ -96,7 +96,7 @@ namespace Greenshot.Drawing.Adorners
{
Graphics targetGraphics = paintEventArgs.Graphics;
var bounds = Bounds;
var bounds = SurfaceBounds;
targetGraphics.FillRectangle(Brushes.Green, bounds.X, bounds.Y, bounds.Width, bounds.Height);
}

View file

@ -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)
/// </summary>
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 new Rectangle();
}
}
}
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 {

View file

@ -298,7 +298,7 @@ namespace Greenshot.Drawing
public virtual void Invalidate() {
if (Status != EditStatus.UNDRAWN) {
_parent?.Invalidate(DrawingBounds);
_parent?.InvalidateElements(DrawingBounds);
}
}

View file

@ -286,7 +286,7 @@ namespace Greenshot.Drawing {
{
region = Rectangle.Union(region, dc.DrawingBounds);
}
Parent.Invalidate(region);
Parent.InvalidateElements(region);
}
/// <summary>
/// Indicates whether the given list of elements can be pulled up,

View file

@ -298,10 +298,34 @@ namespace Greenshot.Drawing
set
{
_image = value;
Size = _image.Size;
UpdateSize();
}
}
[NonSerialized]
private float _zoomFactor = 1.0f;
public float ZoomFactor
{
get => _zoomFactor;
set
{
_zoomFactor = value;
ZoomMatrix = new Matrix(_zoomFactor, 0, 0, _zoomFactor, 0, 0);
UpdateSize();
}
}
public Matrix ZoomMatrix { get; private set; } = new Matrix(1, 0, 0, 1, 0, 0);
/// <summary>
/// Sets the surface size as zoomed image size.
/// </summary>
private void UpdateSize()
{
var size = _image.Size;
Size = new Size((int)(size.Width * _zoomFactor), (int)(size.Height * _zoomFactor));
}
/// <summary>
/// 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 +471,6 @@ namespace Greenshot.Drawing
// Set new values
Image = newImage;
Size = newImage.Size;
_modified = true;
}
@ -1086,6 +1109,12 @@ namespace Greenshot.Drawing
return null;
}
/// <summary>
/// Translate mouse coordinates as if they were applied directly to unscaled image.
/// </summary>
private MouseEventArgs InverseZoomMouseCoordinates(MouseEventArgs e)
=> new MouseEventArgs(e.Button, e.Clicks, (int)(e.X / _zoomFactor), (int)(e.Y / _zoomFactor), e.Delta);
/// <summary>
/// This event handler is called when someone presses the mouse on a surface.
/// </summary>
@ -1093,6 +1122,7 @@ namespace Greenshot.Drawing
/// <param name="e"></param>
private void SurfaceMouseDown(object sender, MouseEventArgs e)
{
e = InverseZoomMouseCoordinates(e);
// Handle Adorners
var adorner = FindActiveAdorner(e);
@ -1187,6 +1217,7 @@ namespace Greenshot.Drawing
/// <param name="e"></param>
private void SurfaceMouseUp(object sender, MouseEventArgs e)
{
e = InverseZoomMouseCoordinates(e);
// Handle Adorners
var adorner = FindActiveAdorner(e);
@ -1276,6 +1307,8 @@ namespace Greenshot.Drawing
/// <param name="e"></param>
private void SurfaceMouseMove(object sender, MouseEventArgs e)
{
e = InverseZoomMouseCoordinates(e);
// Handle Adorners
var adorner = FindActiveAdorner(e);
if (adorner != null)
@ -1371,6 +1404,25 @@ namespace Greenshot.Drawing
return GetImage(RenderMode.EXPORT);
}
private Rectangle ZoomClipRectangle(Rectangle rc, double scale)
=> new Rectangle(
(int)(rc.X * scale),
(int)(rc.Y * scale),
(int)((rc.Width + 1) * scale) + 1, // making sure to redraw enough pixels when moving scaled image
(int)((rc.Height + 1) * scale) + 1
);
private RectangleF ZoomClipRectangle(RectangleF rc, double scale)
=> new RectangleF(
(float)Math.Floor(rc.X * scale),
(float)Math.Floor(rc.Y * scale),
(float)Math.Ceiling(rc.Width * scale),
(float)Math.Ceiling(rc.Height * scale)
);
public void InvalidateElements(Rectangle rc)
=> Invalidate(ZoomClipRectangle(rc, _zoomFactor));
/// <summary>
/// This is the event handler for the Paint Event, try to draw as little as possible!
/// </summary>
@ -1379,14 +1431,16 @@ 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;
}
Rectangle imageClipRectangle = ZoomClipRectangle(targetClipRectangle, 1.0 / _zoomFactor);
if (_elements.HasIntersectingFilters(clipRectangle))
bool isZoomedIn = ZoomFactor > 1f;
if (_elements.HasIntersectingFilters(imageClipRectangle) || isZoomedIn)
{
if (_buffer != null)
{
@ -1409,18 +1463,38 @@ 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(targetGraphics.ClipBounds, 1.0 / _zoomFactor));
_elements.Draw(graphics, _buffer, RenderMode.EDIT, imageClipRectangle);
}
targetGraphics.DrawImage(_buffer, clipRectangle, clipRectangle, GraphicsUnit.Pixel);
targetGraphics.ScaleTransform(_zoomFactor, _zoomFactor);
if (isZoomedIn)
{
var state = targetGraphics.Save();
targetGraphics.SmoothingMode = SmoothingMode.None;
targetGraphics.InterpolationMode = InterpolationMode.NearestNeighbor;
targetGraphics.CompositingQuality = CompositingQuality.HighQuality;
targetGraphics.PixelOffsetMode = PixelOffsetMode.None;
targetGraphics.DrawImage(_buffer, imageClipRectangle, imageClipRectangle, GraphicsUnit.Pixel);
targetGraphics.Restore(state);
}
else
{
DrawBackground(targetGraphics, clipRectangle);
targetGraphics.DrawImage(Image, clipRectangle, clipRectangle, GraphicsUnit.Pixel);
_elements.Draw(targetGraphics, null, RenderMode.EDIT, clipRectangle);
targetGraphics.DrawImage(_buffer, imageClipRectangle, imageClipRectangle, GraphicsUnit.Pixel);
}
targetGraphics.ResetTransform();
}
else
{
DrawBackground(targetGraphics, targetClipRectangle);
targetGraphics.ScaleTransform(_zoomFactor, _zoomFactor);
targetGraphics.DrawImage(Image, imageClipRectangle, imageClipRectangle, GraphicsUnit.Pixel);
_elements.Draw(targetGraphics, null, RenderMode.EDIT, imageClipRectangle);
targetGraphics.ResetTransform();
}
// No clipping for the adorners

View file

@ -442,14 +442,20 @@ namespace Greenshot.Drawing
correction = -1;
}
Rectangle absRectangle = GuiRectangle.GetGuiRectangle(Left, Top, Width, Height);
_textBox.Left = absRectangle.Left + lineWidth;
_textBox.Top = absRectangle.Top + lineWidth;
Point[] points = { absRectangle.Location, absRectangle.Location + absRectangle.Size };
var matrix = Parent.ZoomMatrix;
if (!matrix.IsIdentity)
{
matrix.TransformPoints(points);
}
_textBox.Left = points[0].X + lineWidth;
_textBox.Top = points[0].Y + lineWidth;
if (lineThickness <= 1)
{
lineWidth = 0;
}
_textBox.Width = absRectangle.Width - 2 * lineWidth + correction;
_textBox.Height = absRectangle.Height - 2 * lineWidth + correction;
_textBox.Width = points[1].X - points[0].X - 2 * lineWidth + correction;
_textBox.Height = points[1].Y - points[0].Y - 2 * lineWidth + correction;
}
public override void ApplyBounds(RectangleF newBounds)

View file

@ -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+*";
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 = "25";
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 = "50";
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 = "66";
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 = "75";
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+/";
this.zoomActualSizeMenuItem.Size = new System.Drawing.Size(209, 22);
this.zoomActualSizeMenuItem.Tag = "100";
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 = "200";
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 = "300";
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 = "400";
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 = "600";
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;
}
}

View file

@ -66,6 +66,12 @@ namespace Greenshot {
// whether part of the editor controls are disabled depending on selected item(s)
private bool _controlsDisabledDueToConfirmable;
/// <summary>
/// All provided zoom values (in percents) in ascending order.
/// </summary>
private readonly int[] ZOOM_VALUES = new[] { 25, 50, 66, 75, 100, 200, 300, 400, 600 };
private int _zoomValue = 100;
/// <summary>
/// An Implementation for the IImageEditor, this way Plugins have access to the HWND handles wich can be used with Win32 API calls.
/// </summary>
@ -420,20 +426,14 @@ namespace Greenshot {
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
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 {
@ -863,12 +863,31 @@ namespace Greenshot {
case Keys.OemPeriod: // Rotate CW Ctrl + .
RotateCwToolstripButtonClick(sender, e);
break;
case Keys.Add: // 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.Divide: // Ctrl + Num/
case Keys.OemQuestion: // Ctrl + / (?)
ZoomSetValueMenuItemClick(zoomActualSizeMenuItem, e);
break;
case Keys.Multiply: // Ctrl + Num*
case Keys.D8: // Ctrl + 8 (*)
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 +1463,130 @@ 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;
}
}
/// <summary>
/// Compute a size as a sum of surface size and chrome.
/// Upper bound is working area of the screen. Lower bound is fixed value.
/// </summary>
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 screen = Screen.FromControl(this);
var workingArea = screen.WorkingArea;
newWidth = Math.Min(newWidth, workingArea.Width);
newHeight = Math.Min(newHeight, workingArea.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;
/// <summary>
/// Compute a size that the form can take without getting out of working area of the screen.
/// </summary>
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.Width - Left, workingArea.Height - Top);
} else {
return workingArea.Size;
}
}
private void ZoomInMenuItemClick(object sender, EventArgs e) {
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 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;
int zoomPercent = int.Parse((string)senderMenuItem.Tag);
ZoomSetValue(zoomPercent);
}
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(int zoom, int source, int boundary)
=> source * zoom / 100 <= boundary;
var nextValue = Array.FindLast(
ZOOM_VALUES,
zoom => isFit(zoom, imageSize.Width, maxImageSize.Width)
&& isFit(zoom, imageSize.Height, maxImageSize.Height)
);
ZoomSetValue(nextValue);
}
private void ZoomSetValue(int value) {
_zoomValue = value;
Surface.ZoomFactor = 1f * value / 100;
// Update zoom controls
string valueString = value.ToString();
zoomStatusDropDownBtn.Text = valueString + "%";
foreach (var item in zoomMenuStrip.Items) {
if (item is ToolStripMenuItem menuItem) {
menuItem.Checked = menuItem.Tag as string == valueString;
}
}
Size = GetOptimalWindowSize();
AlignCanvasPositionAfterResize();
}
}
}

View file

@ -937,6 +937,84 @@
BIhPAPEZIL7CAAQ6fptA+D+IJskFIM2cgtoMKm6rQfg/iEY3oB9oC8jWozBbgXquAvFykGYQkDJuZlBy
WQvC/0E0QRfANIJoLmF9BnXPHTD8H8QmyQD9wBMMSPg/iE20ATK6uQwWUTeR8X8Qn2gDHJOfM6Dh/yA+
igHkZijqZSZyXQAA4IG1TpHFZ2gAAAAASUVORK5CYII=
</value>
</data>
<data name="zoomInMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAABl0RVh0U29m
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJKSURBVDhPY/j//z+DX8FVDBxZ/ZAhqeWVRkrbm7lA
fB+If0HpuSBxkDxIHU4DIqruB0bXPrzftODpia0n3+668/zLiaNXPh3oXf7yFEgcJA9SBzbALeMsCvbI
Oq/hk3fx/rSND3ccufH60MuPP259+vb7+etPP28/evPt7PztT3b5AuVB6sAGWMUcQMdz83svnth96cWB
q48/ngBpBor9B9FP3n47d+3JpxMlEy6dAqkDG6Djtwkd35+77e72M/feXbj78suDd19+fQCK/QfRID5I
fOme+3tA6sAGqLitRse/dp5/fgCkGMj+j44/fvv97PC118eA7F9gA5Rc1qLj+wu239sNsgmk+Ofvv5+B
Yv9BNDgsPv68tWrfw0MgdWAD1D13oOO52W3nz+6/+urw/ZdfT4M0AcXgYQAMyHPF3RcvgdSBDXBwcGCw
s7NjsLW1ZbCxsWEwd8q0Mgo+/GLe5gdnbz77fBoU+mCbgfSz998vbtj/6pRxyMn7egHHILGAbIC1tbU8
0JDijsl7/ltFXnjVOOP5pZNXvpx+/u7H9VNXv5zpmPvymk3crfvmkdcDDYPPQNIBzACgRi03N/eaHTv2
/BcVFWtWs2qxc0x+PheI7wPxLyg91z7xiYZF1E0GFAOAtlsEBga3HTly5r+iolIPCwuLqpJxCYNTyisM
DDSAAcUAoO3eCQkpEy5evPtfT89gGlCzMRAzEG2Aubl5ya1br/7b2zvPY2VldQFpJskAPT09LRERkRag
5hiYZhAWlrEhzgDy8X8GAJItIDq7n94UAAAAAElFTkSuQmCC
</value>
</data>
<data name="zoomOutMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAABl0RVh0U29m
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAIySURBVDhPY/j//z+DtrY2g559IYNfwVU4jqx+yJDU
8kojpe3NXCC+D8S/oPRckDhIHqQObACyRhiOqLofGF378H7Tgqcntp58u+vO8y8njl75dKB3+ctTIHGQ
PEgd2AC3jLMo2CPrvIZP3sX70zY+3HHkxutDj958O/vk7bdzIAxiz9/+ZJcvUB6kDmyAVcwBdDw3v/fi
id2XXhy4+vjjCZhmGL725NOJkgmXToHUgQ3Q8duEju/P3XZ3+5l77y7cffnlAToGiS/dc38PSB3YABW3
1ej4187zzw+AFAPZ/9Hxx2+/nx2+9voYkP0LbICSy1p0fH/B9nu7QTaBFH/69vs5Mn798eetVfseHgKp
Axug7rkDHc/Nbjt/dv/VV4fvv/x6Gj0MgAF5rrj74iWQOrABDg4ODHZ2dgy2trYMNjY2DOZOmVZGwYdf
zNv84OzNZ59RDHj2/vvFDftfnTIOOXlfL+AYJBaQDbC2tpYHGlLcMXnPf6vIC68aZzy/dPLKl9PP3/24
furqlzMdc19es4m7dd888nqgYfAZSDqAGQDUqOXm5l6zY8ee/6KiYs1qVi12jsnP5wLxfSD+BaXn2ic+
0bCIusmAYgDQdovAwOC2I0fO/FdUVOphYWFRVTIuYXBKeYWBgQYwoBgAtN07ISFlwsWLd//r6RlMA2o2
BmIGog0wNzcvuXXr1X97e+d5rKysLiDNJBmgp6enJSIi0gLUHAPTDMLCMjbEGUA+/s8AAJUZIgOF4ptY
AAAAAElFTkSuQmCC
</value>
</data>
<data name="zoomBestFitMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAABl0RVh0U29m
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJISURBVDhPnZLLaxNRFIfvIvkLTF0J0uAjNDKoWaTk
bS1RUYshLowWqZqFD/BBs/KxUEMoaKEgiJuRbqRQ6UaojTqUYGpJYmpMSFuMxklK2mmmmba2kzSZINd7
pk1p4qa6+Djn3vs73x24gzDGSKvVIsp6B3XcntzEdS+LLnt5jdtXoAksQdqoNOzDOeRkwdbBGufuso4L
D7Lso/7Z0HBYeP+DE0OfkiuB3oF8BPbhHHKywH51oo7j12OaUzfj7ADDhTJ8MbNclOZWSlUOgH5wdD50
mpxDThYYOgON0Ld646F0XsyQHgOFpYpcgZywNuPpS0QgJwsOdLxphKXfpkdAQHq8KErL0EOFNfSvGJaB
nCzYY3/diPQuxgUgmBfWMHx6Tih9gQrrX6XqXHBqYRxyskDdPtQI2z/y8wMISI8r1d+rMAwV1iAYHM1+
hJws2H/C3wh9wxebyC4UZ0iPAV4oyxVYKkpc95N4AnKywGazIYvFgsxmMzKZTEjfds1w2BmcH2JmvxdW
K5svAIjlKj8cLCR1Z8MsdWZ8/RW2CoxG424i6e55xmCD6yv/8AWXCCfFz9xieToyKUZ76PyU6WKK1bum
HYec0fX/oCYggy12+7H7fj+Dm5p2Pt5n8FqOXOFoAkuQNiptvZTTtJ7/huoE5PZWh8PpGxuL4uZm9VOF
QrFXrfOgNjf/F0SA6gTk9pNdXe6+eDyNKergczKsI6BtC/R6vSeV4rHVevSlUqlsh+F/ElAU1aJSqbxk
uLM2DOzYZdqe4P/B6A86Ah9NBTgWLgAAAABJRU5ErkJggg==
</value>
</data>
<data name="zoomActualSizeMenuItem.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAABl0RVh0U29m
dHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAJXSURBVDhPnZLda1JhHMefC/0Lcl0FMelFZhwqLxy+
t4YV1UjsImvFKi+qQS9MuujlohIZ1GAQjG7O2k0ERdALpW3SZNpQ0zllall6NKwzj1OWczId8fT8zuaY
drO6+PBwvs/393kezjkIY4ykUimitNdQ19XoGqabGXTOyknMtjmawBBqqysNOexDjxesH6xz4gZjOHU7
w9wd+eF96yuMfmPL3o8zJdfA05wfctiHHi/QXwg2cPBSSHLkcpgZepVxeD7nJ+YXaz9LlWU2X6p+/T5X
CT62Z0ePkn3o8QJFt6sZ+spA2DsWmXVlC5VMrzWESYZBQp6nYtmS1zIY8UOPF+zqet0MQ79L2kEQSBWn
i+XaPMlwMldOQwY8cTJO6PGCbfrnzdTeh1i+CMDJJFu7AeCO5SehxwvEnS+aYUbsqTEY5n4tJarLvxdI
hmGF9wCCZx8yE9DjBTsPOZqhe22h4HiUc8+VqtnT1/2YZBhWuAV5kVN998MR6PECnU6HNBoNUqvVSKVS
IXnHRcVeo3t2+E06mOYW4zBUp7BQTb0c5/yy4z6GOja58hXWC5RK5VYi6et/6MQK0zR35xEb8c2UP7HF
pbg/Wg7007mY6kyCkZvihj3GwMp/UBeQwTa9/sAth8OJW1o239uhsGr2nWdpAkOora609mxW0n7yC2oQ
kNPbDQajzeMJ4NZW8QOBQLBdLLOgDjP3F0SAGgTk9MM9PebBcDiJKWr3EBmWEdCGBXK53JJIcFir3T8s
FAo7YfifBBRFtYlEIisZ7q4PA5u2qDYm+H8w+gNv9h/ta4LougAAAABJRU5ErkJggg==
</value>
</data>
<data name="zoomStatusDropDownBtn.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHYSURBVDhPY8AH4ltfa6S0vZ6b3Pr6PhD/AtEgPkgcqgQ3
iKi6Hxhd8/B+0/ynJ7acfLvrzrPPJ45c+Xigd9nLUyBxkDxUKSZwyzmj4Z176f7UjQ92HL7++tCjN9/O
Pn7z7RwIg9jztz3e5QOUB6mDakEFVjH75xb0Xjyx++KLA1cefToO0wzD1558OlE84eJpkDqoFlSg7bvp
/pytd3aADMCFl+6+vwekDqoFFSi7rf614/xzuGJ0Fzx+++3soauvjoHUQbWgAiXnNffn77i3G6wZqBib
ASv3PTgEUgfVggrUPLfPzWo/d3bv5VeH77/4fBrdgEevv54r7rpwCaQOqgUVWDpmWhkGHXoxf/P9szef
fkIx4Nn7b5fW73t5yjj45H2doKOYsWBpaSlvbW1d3DF5z3/LyAuvGqY/vXTiyqfTz999v37qyucz7fNe
XrOOvXXfNOIaZjqwsbHRcnV1r9mxY89/UVHRZlXLZjuH5GdzHZOf3XdMevYLRIP49vFPMW0G2moRGBjc
duTImf8KCko9bGxsqlApwgBos098fPKEixfv/tfT05/KyspqDJUiDpiZmZXcuvXqv52d8zxmZmYXqDDx
wMDAQEtYWLgFaHMMVIhegIEBADK7VwLsrsplAAAAAElFTkSuQmCC
</value>
</data>
<data name="btnStepLabel01.Image" type="System.Resources.ResXFileRef, System.Windows.Forms">
@ -1026,4 +1104,7 @@
<metadata name="fileSavedStatusContextMenu.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="zoomMenuStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>782, 17</value>
</metadata>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 676 B

View file

@ -21,6 +21,7 @@
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
using GreenshotPlugin.Effects;
@ -147,8 +148,13 @@ namespace GreenshotPlugin.Interfaces
/// <param name="container"></param>
/// <returns>This returns false if the container is deleted but still in the undo stack</returns>
bool IsOnSurface(IDrawableContainer container);
void Invalidate(Rectangle rectangleToInvalidate);
void Invalidate();
/// <summary>
/// Invalidates the specified region of the Surface.
/// Takes care of the Surface zoom level, accepts rectangle in the coordinate space of the Image.
/// </summary>
/// <param name="rectangleToInvalidate">Bounding rectangle for updated elements, in the coordinate space of the Image.</param>
void InvalidateElements(Rectangle rectangleToInvalidate);
bool Modified
{
get;
@ -189,6 +195,15 @@ namespace GreenshotPlugin.Interfaces
int Width { get; }
int Height { get; }
/// <summary>
/// Zoom value applied to the surface. 1.0f for actual size (100%).
/// </summary>
float ZoomFactor { get; set; }
/// <summary>
/// Matrix representing zoom applied to the surface.
/// </summary>
Matrix ZoomMatrix { get; }
void MakeUndoable(IMemento memento, bool allowMerge);
}
}