diff --git a/Greenshot/Drawing/Adorners/AbstractAdorner.cs b/Greenshot/Drawing/Adorners/AbstractAdorner.cs index 0612753aa..fc3d72371 100644 --- a/Greenshot/Drawing/Adorners/AbstractAdorner.cs +++ b/Greenshot/Drawing/Adorners/AbstractAdorner.cs @@ -116,6 +116,23 @@ namespace Greenshot.Drawing.Adorners } } + /// + /// Return the bounds of the Adorner as displayed on the parent Surface + /// + 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); + } + } + /// /// 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..21c42a9fa 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 = SurfaceBounds; GraphicsState state = targetGraphics.Save(); targetGraphics.SmoothingMode = SmoothingMode.None; diff --git a/Greenshot/Drawing/Adorners/ResizeAdorner.cs b/Greenshot/Drawing/Adorners/ResizeAdorner.cs index e75126f10..ef61b17bc 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 = SurfaceBounds; GraphicsState state = targetGraphics.Save(); targetGraphics.SmoothingMode = SmoothingMode.None; diff --git a/Greenshot/Drawing/Adorners/TargetAdorner.cs b/Greenshot/Drawing/Adorners/TargetAdorner.cs index 8ca0a91ef..23cf29d09 100644 --- a/Greenshot/Drawing/Adorners/TargetAdorner.cs +++ b/Greenshot/Drawing/Adorners/TargetAdorner.cs @@ -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); } diff --git a/Greenshot/Drawing/CropContainer.cs b/Greenshot/Drawing/CropContainer.cs index 2dc9761c0..f5288074d 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 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 { diff --git a/Greenshot/Drawing/DrawableContainer.cs b/Greenshot/Drawing/DrawableContainer.cs index 8d805b1f0..65ff42de3 100644 --- a/Greenshot/Drawing/DrawableContainer.cs +++ b/Greenshot/Drawing/DrawableContainer.cs @@ -298,7 +298,7 @@ namespace Greenshot.Drawing public virtual void Invalidate() { if (Status != EditStatus.UNDRAWN) { - _parent?.Invalidate(DrawingBounds); + _parent?.InvalidateElements(DrawingBounds); } } diff --git a/Greenshot/Drawing/DrawableContainerList.cs b/Greenshot/Drawing/DrawableContainerList.cs index d3c325f5b..17b879538 100644 --- a/Greenshot/Drawing/DrawableContainerList.cs +++ b/Greenshot/Drawing/DrawableContainerList.cs @@ -286,7 +286,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, diff --git a/Greenshot/Drawing/Surface.cs b/Greenshot/Drawing/Surface.cs index bd56db3a2..5c38bea8e 100644 --- a/Greenshot/Drawing/Surface.cs +++ b/Greenshot/Drawing/Surface.cs @@ -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); + + /// + /// 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 +471,6 @@ namespace Greenshot.Drawing // Set new values Image = newImage; - Size = newImage.Size; _modified = true; } @@ -1086,6 +1109,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 +1122,7 @@ namespace Greenshot.Drawing /// private void SurfaceMouseDown(object sender, MouseEventArgs e) { + e = InverseZoomMouseCoordinates(e); // Handle Adorners var adorner = FindActiveAdorner(e); @@ -1187,6 +1217,7 @@ namespace Greenshot.Drawing /// private void SurfaceMouseUp(object sender, MouseEventArgs e) { + e = InverseZoomMouseCoordinates(e); // Handle Adorners var adorner = FindActiveAdorner(e); @@ -1276,6 +1307,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 +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)); + /// /// This is the event handler for the Paint Event, try to draw as little as possible! /// @@ -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 + { + targetGraphics.DrawImage(_buffer, imageClipRectangle, imageClipRectangle, GraphicsUnit.Pixel); + } + targetGraphics.ResetTransform(); } else { - DrawBackground(targetGraphics, clipRectangle); - targetGraphics.DrawImage(Image, clipRectangle, clipRectangle, GraphicsUnit.Pixel); - _elements.Draw(targetGraphics, null, RenderMode.EDIT, clipRectangle); + 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 diff --git a/Greenshot/Drawing/TextContainer.cs b/Greenshot/Drawing/TextContainer.cs index 0f4866d71..bd4c75274 100644 --- a/Greenshot/Drawing/TextContainer.cs +++ b/Greenshot/Drawing/TextContainer.cs @@ -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) diff --git a/Greenshot/Forms/ImageEditorForm.Designer.cs b/Greenshot/Forms/ImageEditorForm.Designer.cs index 9a653cc21..5789a19a6 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+*"; + 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; } } diff --git a/Greenshot/Forms/ImageEditorForm.cs b/Greenshot/Forms/ImageEditorForm.cs index bcb8c275c..dd9ec94e1 100644 --- a/Greenshot/Forms/ImageEditorForm.cs +++ b/Greenshot/Forms/ImageEditorForm.cs @@ -66,6 +66,12 @@ 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 int[] ZOOM_VALUES = new[] { 25, 50, 66, 75, 100, 200, 300, 400, 600 }; + private int _zoomValue = 100; + /// /// An Implementation for the IImageEditor, this way Plugins have access to the HWND handles wich can be used with Win32 API calls. /// @@ -420,20 +426,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 +860,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.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; } } + + /// + /// 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 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; + + /// + /// 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.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(); + } } } 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/GreenshotPlugin/Interfaces/ISurface.cs b/GreenshotPlugin/Interfaces/ISurface.cs index 44a150364..8ab44c810 100644 --- a/GreenshotPlugin/Interfaces/ISurface.cs +++ b/GreenshotPlugin/Interfaces/ISurface.cs @@ -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 /// /// 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; @@ -189,6 +195,15 @@ namespace GreenshotPlugin.Interfaces int Width { get; } int Height { get; } + /// + /// Zoom value applied to the surface. 1.0f for actual size (100%). + /// + float ZoomFactor { get; set; } + /// + /// Matrix representing zoom applied to the surface. + /// + Matrix ZoomMatrix { get; } + void MakeUndoable(IMemento memento, bool allowMerge); } -} \ No newline at end of file +}