First pass at adding zoom to image editor, via CTRL+mouse wheel.

This commit is contained in:
Eric Tetz 2022-01-31 18:01:18 -07:00
parent f5afd208b4
commit 2ee7843393
14 changed files with 140 additions and 54 deletions

View file

@ -144,6 +144,11 @@ public static Point Add(this Point point, Point offset)
return new Point(point.X + offset.X, point.Y + offset.Y);
}
public static Point Scale(this Point point, float scaleFactor)
{
return new Point((int)Math.Round(point.X * scaleFactor), (int)Math.Round(point.Y * scaleFactor));
}
public static Size Offset(this Size size, int offset)
{
return size.Offset(offset, offset);
@ -159,6 +164,15 @@ public static Rectangle Offset(this Rectangle rect, int offset)
return new Rectangle(rect.X - offset, rect.Y - offset, rect.Width + (offset * 2), rect.Height + (offset * 2));
}
public static Rectangle Scale(this Rectangle rect, float scaleFactor)
{
return new Rectangle(
(int)Math.Round(rect.X * scaleFactor),
(int)Math.Round(rect.Y * scaleFactor),
(int)Math.Round(rect.Width * scaleFactor),
(int)Math.Round(rect.Height * scaleFactor));
}
public static Rectangle LocationOffset(this Rectangle rect, int x, int y)
{
return new Rectangle(rect.X + x, rect.Y + y, rect.Width, rect.Height);

View file

@ -31,6 +31,7 @@ You should have received a copy of the GNU General Public License
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
@ -63,6 +64,8 @@ public sealed class RegionCaptureForm : Form
public Point CurrentPosition { get; private set; }
public Point PanningStrech = new Point();
public float ZoomFactor { get; private set; } = 1;
public SimpleWindowInfo SelectedWindow { get; private set; }
public Vector2 CanvasCenterOffset { get; set; } = new Vector2(0f, 0f);
@ -129,6 +132,8 @@ public RegionCaptureForm(RegionCaptureMode mode, RegionCaptureOptions options, B
infoFontBig = new Font("Verdana", 16, FontStyle.Bold);
markerPen = new Pen(Color.FromArgb(200, Color.Red));
MouseWheel += onMouseWheel;
if (ShareXResources.UseCustomTheme)
{
canvasBackgroundColor = ShareXResources.Theme.BackgroundColor;
@ -162,6 +167,27 @@ public RegionCaptureForm(RegionCaptureMode mode, RegionCaptureOptions options, B
InitializeComponent();
}
public Point ScaledClientMousePosition => InputManager.ClientMousePosition.Scale(1 / ZoomFactor);
public Point ScaledClientMouseVelocity => InputManager.MouseVelocity.Scale(1 / ZoomFactor);
private void onMouseWheel(object sender, MouseEventArgs e)
{
// TODO: center zoom on mouse position
if ((ModifierKeys & Keys.Control) == 0)
{
return;
}
if (e.Delta > 0)
{
ZoomFactor += 0.1F;
}
else if (ZoomFactor > 0.2)
{
ZoomFactor -= 0.1F;
}
UpdateTitle();
}
private void InitializeComponent()
{
SuspendLayout();
@ -249,35 +275,40 @@ internal void UpdateTitle()
{
if (forceClose) return;
string text;
var title = new StringBuilder();
if (IsEditorMode)
{
text = "ShareX - " + Resources.RegionCaptureForm_InitializeComponent_ImageEditor;
title.AppendFormat("ShareX - {0}", Resources.RegionCaptureForm_InitializeComponent_ImageEditor);
if (Canvas != null)
{
text += $" - {Canvas.Width}x{Canvas.Height}";
title.AppendFormat(" - {0}x{1}", Canvas.Width, Canvas.Height);
}
string fileName = Helpers.GetFileNameSafe(ImageFilePath);
if (!string.IsNullOrEmpty(fileName))
{
text += " - " + fileName;
title.AppendFormat(" - {0}", fileName);
}
if (!IsFullscreen && Options.ShowFPS)
{
text += " - FPS: " + FPSManager.FPS.ToString();
title.AppendFormat(" - FPS: {0}", FPSManager.FPS.ToString());
}
if (ZoomFactor != 1.0)
{
title.AppendFormat(" - ZOOM: {0}%", Math.Round(ZoomFactor * 100));
}
}
else
{
text = "ShareX - " + Resources.BaseRegionForm_InitializeComponent_Region_capture;
title.AppendFormat("ShareX - {0}", Resources.BaseRegionForm_InitializeComponent_Region_capture);
}
Text = text;
Text = title.ToString();
}
private void Prepare(Bitmap canvas = null)
@ -409,6 +440,8 @@ private void Pan(int deltaX, int deltaY, bool usePanningStretch = true)
Rectangle limitRectangle = new Rectangle(ClientArea.X + panLimitSize.Width, ClientArea.Y + panLimitSize.Height,
ClientArea.Width - (panLimitSize.Width * 2), ClientArea.Height - (panLimitSize.Height * 2));
limitRectangle = limitRectangle.Scale(1 / ZoomFactor);
deltaX = Math.Max(deltaX, limitRectangle.Left - CanvasRectangle.Right);
deltaX = Math.Min(deltaX, limitRectangle.Right - CanvasRectangle.Left);
deltaY = Math.Max(deltaY, limitRectangle.Top - CanvasRectangle.Bottom);
@ -727,7 +760,7 @@ private void UpdateCoordinates()
if (ShapeManager.IsPanning)
{
Pan(InputManager.MouseVelocity);
Pan(ScaledClientMouseVelocity);
UpdateCenterOffset();
}
@ -758,17 +791,18 @@ protected override void OnPaint(PaintEventArgs e)
Graphics g = e.Graphics;
g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
g.InterpolationMode = InterpolationMode.NearestNeighbor;
if (IsEditorMode && !CanvasRectangle.Contains(ClientArea))
{
g.ScaleTransform(ZoomFactor, ZoomFactor);
g.Clear(canvasBackgroundColor);
g.DrawRectangleProper(canvasBorderPen, CanvasRectangle.Offset(1));
}
g.CompositingMode = CompositingMode.SourceCopy;
g.FillRectangle(backgroundBrush, CanvasRectangle);
g.CompositingMode = CompositingMode.SourceOver;
Draw(g);
DrawBackground(g);
DrawShapes(g);
if (Options.ShowFPS && IsFullscreen)
{
@ -779,9 +813,23 @@ protected override void OnPaint(PaintEventArgs e)
{
Invalidate();
}
g.PixelOffsetMode = PixelOffsetMode.Default;
g.InterpolationMode = InterpolationMode.Default;
}
private void Draw(Graphics g)
private void DrawBackground(Graphics g)
{
// TODO: dynamically adjust scaling quality when zoom != 1 based on FPS
// Quality is more important when scaling down than up.
g.PixelOffsetMode = PixelOffsetMode.HighSpeed;
g.InterpolationMode = InterpolationMode.NearestNeighbor;
g.SmoothingMode = SmoothingMode.HighSpeed;
g.CompositingQuality = CompositingQuality.HighSpeed;
g.DrawImage(backgroundBrush.Image, CanvasRectangle);
}
private void DrawShapes(Graphics g)
{
// Draw snap rectangles
if (ShapeManager.IsCreating && ShapeManager.IsSnapResizing)
@ -1040,7 +1088,13 @@ private void DrawTextAnimation(Graphics g, TextAnimation textAnimation, Rectangl
using (Brush textBrush = new SolidBrush(Color.FromArgb((int)(textAnimation.Opacity * 255), textColor)))
using (Brush textShadowBrush = new SolidBrush(Color.FromArgb((int)(textAnimation.Opacity * 255), textShadowColor)))
{
DrawInfoText(g, textAnimation.Text, textRectangle, infoFontMedium, padding, backgroundBrush, outerBorderPen, innerBorderPen, textBrush, textShadowBrush);
var transform = g.Transform;
using (Matrix identity = new Matrix())
{
g.Transform = identity; // ignore zoom
DrawInfoText(g, textAnimation.Text, textRectangle, infoFontMedium, padding, backgroundBrush, outerBorderPen, innerBorderPen, textBrush, textShadowBrush);
}
g.Transform = transform;
}
}
@ -1105,7 +1159,7 @@ private string GetInfoText()
private void DrawCrosshair(Graphics g)
{
int offset = 5;
Point mousePos = InputManager.ClientMousePosition;
Point mousePos = ScaledClientMousePosition;
Point left = new Point(mousePos.X - offset, mousePos.Y), left2 = new Point(0, mousePos.Y);
Point right = new Point(mousePos.X + offset, mousePos.Y), right2 = new Point(ClientArea.Width - 1, mousePos.Y);
Point top = new Point(mousePos.X, mousePos.Y - offset), top2 = new Point(mousePos.X, 0);
@ -1194,9 +1248,12 @@ private void DrawCursorGraphics(Graphics g)
if (Options.ShowMagnifier)
{
Matrix transform = g.Transform;
using (GraphicsQualityManager quality = new GraphicsQualityManager(g))
using (TextureBrush brush = new TextureBrush(magnifier))
using (Matrix identity = new Matrix())
{
g.Transform = identity; // ignore zoom
brush.TranslateTransform(x, y + magnifierPosition);
if (Options.UseSquareMagnifier)
@ -1212,6 +1269,7 @@ private void DrawCursorGraphics(Graphics g)
g.DrawEllipse(Pens.Black, x, y + magnifierPosition, magnifier.Width - 1, magnifier.Height - 1);
}
}
g.Transform = transform;
}
if (Options.ShowInfo)

View file

@ -60,11 +60,13 @@ public void Update()
if (Orientation == Orientation.Horizontal)
{
isScrollbarNeeded = form.CanvasRectangle.Left < form.ClientArea.Left || form.CanvasRectangle.Right > form.ClientArea.Right;
isScrollbarNeeded = form.CanvasRectangle.Left < form.ClientArea.Left
|| (form.CanvasRectangle.Right * form.ZoomFactor) > form.ClientArea.Right;
}
else
{
isScrollbarNeeded = form.CanvasRectangle.Top < form.ClientArea.Top || form.CanvasRectangle.Bottom > form.ClientArea.Bottom;
isScrollbarNeeded = form.CanvasRectangle.Top < form.ClientArea.Top
|| (form.CanvasRectangle.Bottom * form.ZoomFactor) > form.ClientArea.Bottom;
}
Visible = isScrollbarNeeded || IsDragging;
@ -142,9 +144,12 @@ public override void OnDraw(Graphics g)
thumbColor = ThumbColor;
}
var transform = g.Transform;
using (Brush trackBrush = new SolidBrush(TrackColor))
using (Brush thumbBrush = new SolidBrush(thumbColor))
using (Matrix identity = new Matrix())
{
g.Transform = identity; // ignore editor scaling
if (IsCapsule)
{
g.SmoothingMode = SmoothingMode.HighQuality;
@ -162,6 +167,7 @@ public override void OnDraw(Graphics g)
g.FillRectangle(thumbBrush, ThumbRectangle);
}
}
g.Transform = transform;
}
private void Scroll(Point position)

View file

@ -225,7 +225,7 @@ public virtual BaseShape Duplicate()
public virtual void OnCreating()
{
Point pos = InputManager.ClientMousePosition;
Point pos = Manager.Form.ScaledClientMousePosition;
if (Options.IsFixedSize && ShapeCategory == ShapeCategory.Region)
{
@ -270,11 +270,11 @@ public virtual void OnUpdate()
{
if (Manager.IsCreating)
{
Point pos = InputManager.ClientMousePosition;
Point pos = Manager.Form.ScaledClientMousePosition;
if (Manager.IsCornerMoving && !Manager.IsPanning)
{
StartPosition = StartPosition.Add(InputManager.MouseVelocity);
StartPosition = StartPosition.Add(Manager.Form.ScaledClientMouseVelocity);
}
if (Manager.IsProportionalResizing || ForceProportionalResizing)
@ -303,7 +303,7 @@ public virtual void OnUpdate()
}
else if (Manager.IsMoving && !Manager.IsPanning)
{
Move(InputManager.MouseVelocity);
Move(Manager.Form.ScaledClientMouseVelocity);
}
if (LimitRectangleToInsideCanvas)
@ -361,13 +361,13 @@ public virtual void OnNodeUpdate()
if (Manager.IsCornerMoving || Manager.IsPanning)
{
tempStartPos.Offset(InputManager.MouseVelocity);
tempEndPos.Offset(InputManager.MouseVelocity);
tempNodePos.Offset(InputManager.MouseVelocity);
tempRectangle.LocationOffset(InputManager.MouseVelocity);
tempStartPos.Offset(Manager.Form.ScaledClientMouseVelocity);
tempEndPos.Offset(Manager.Form.ScaledClientMouseVelocity);
tempNodePos.Offset(Manager.Form.ScaledClientMouseVelocity);
tempRectangle.LocationOffset(Manager.Form.ScaledClientMouseVelocity);
}
Point pos = InputManager.ClientMousePosition;
Point pos = Manager.Form.ScaledClientMousePosition;
Point startPos = tempStartPos;
Point endPos = tempEndPos;

View file

@ -49,7 +49,7 @@ public override void ShowNodes()
public override void OnCreating()
{
Manager.IsMoving = true;
UpdateCursor(Manager.GetSelectedCursor().Handle, InputManager.ClientMousePosition);
UpdateCursor(Manager.GetSelectedCursor().Handle, Manager.Form.ScaledClientMousePosition);
OnCreated();
}

View file

@ -73,11 +73,11 @@ public override void OnUpdate()
{
if (Manager.IsCornerMoving && !Manager.IsPanning)
{
Move(InputManager.MouseVelocity);
Move(Manager.Form.ScaledClientMouseVelocity);
}
else
{
Point pos = InputManager.ClientMousePosition;
Point pos = Manager.Form.ScaledClientMousePosition;
if (positions.Count == 0 || (!Manager.IsProportionalResizing && LastPosition != pos))
{
@ -101,7 +101,7 @@ public override void OnUpdate()
}
else if (Manager.IsMoving)
{
Move(InputManager.MouseVelocity);
Move(Manager.Form.ScaledClientMouseVelocity);
}
}

View file

@ -32,7 +32,7 @@ public class ImageFileDrawingShape : ImageDrawingShape
{
public override void OnCreating()
{
Point pos = InputManager.ClientMousePosition;
Point pos = Manager.Form.ScaledClientMousePosition;
Rectangle = new Rectangle(pos.X, pos.Y, 1, 1);
if (Manager.IsCtrlModifier && LoadImageFile(AnnotationOptions.LastImageFilePath, true))

View file

@ -236,7 +236,7 @@ public override void OnNodeUpdate()
CenterNodeActive = true;
}
Points[i] = InputManager.ClientMousePosition;
Points[i] = Manager.Form.ScaledClientMousePosition;
}
}
}

View file

@ -82,7 +82,7 @@ public override void OnNodeUpdate()
if (TailNode.IsDragging)
{
TailPosition = InputManager.ClientMousePosition;
TailPosition = Manager.Form.ScaledClientMousePosition;
}
}

View file

@ -71,7 +71,7 @@ public StepDrawingShape()
public override void OnCreating()
{
Manager.IsMoving = true;
Point pos = InputManager.ClientMousePosition;
Point pos = Manager.Form.ScaledClientMousePosition;
Rectangle = new Rectangle(new Point(pos.X - (Rectangle.Width / 2), pos.Y - (Rectangle.Height / 2)), Rectangle.Size);
int tailOffset = 5;
TailPosition = Rectangle.Location.Add(Rectangle.Width + tailOffset, Rectangle.Height + tailOffset);
@ -94,7 +94,7 @@ public override void OnNodeUpdate()
if (TailNode.IsDragging)
{
IsTailActive = true;
TailPosition = InputManager.ClientMousePosition;
TailPosition = Manager.Form.ScaledClientMousePosition;
}
}

View file

@ -48,7 +48,7 @@ public override void ShowNodes()
public override void OnCreating()
{
Point pos = InputManager.ClientMousePosition;
Point pos = Manager.Form.ScaledClientMousePosition;
Rectangle = new Rectangle(pos.X, pos.Y, 1, 1);
if (Manager.IsCtrlModifier && LoadSticker(AnnotationOptions.LastStickerPath, AnnotationOptions.StickerSize))

View file

@ -100,7 +100,7 @@ protected void DrawText(Graphics g, string text, Color textColor, TextDrawingOpt
public override void OnCreating()
{
Point pos = InputManager.ClientMousePosition;
Point pos = Manager.Form.ScaledClientMousePosition;
Rectangle = new Rectangle(pos.X, pos.Y, 1, 1);
if (ShowTextInputBox())

View file

@ -68,11 +68,11 @@ public override void OnUpdate()
{
if (Manager.IsCornerMoving)
{
Move(InputManager.MouseVelocity);
Move(Manager.Form.ScaledClientMouseVelocity);
}
else
{
Point pos = InputManager.ClientMousePosition;
Point pos = Manager.Form.ScaledClientMousePosition;
if (points.Count == 0 || (!Manager.IsProportionalResizing && LastPosition != pos))
{
@ -96,7 +96,7 @@ public override void OnUpdate()
}
else if (Manager.IsMoving)
{
Move(InputManager.MouseVelocity);
Move(Manager.Form.ScaledClientMouseVelocity);
}
}

View file

@ -501,6 +501,10 @@ private void form_MouseDoubleClick(object sender, MouseEventArgs e)
private void form_MouseWheel(object sender, MouseEventArgs e)
{
if ((Control.ModifierKeys & Keys.Control) != 0)
{
return;
}
if (e.Delta > 0)
{
if (Options.ShowMagnifier)
@ -1035,12 +1039,8 @@ private void EndPanning()
IsPanning = false;
}
internal void UpdateObjects()
internal void UpdateObjects(ImageEditorControl[] objects, Point mousePosition)
{
ImageEditorControl[] objects = DrawableObjects.OrderByDescending(x => x.Order).ToArray();
Point position = InputManager.ClientMousePosition;
if (objects.All(x => !x.IsDragging))
{
for (int i = 0; i < objects.Length; i++)
@ -1049,13 +1049,13 @@ internal void UpdateObjects()
if (obj.Visible)
{
obj.IsCursorHover = obj.Rectangle.Contains(position);
obj.IsCursorHover = obj.Rectangle.Contains(mousePosition);
if (obj.IsCursorHover)
{
if (InputManager.IsMousePressed(MouseButtons.Left))
{
obj.OnMouseDown(position);
obj.OnMouseDown(mousePosition);
}
for (int j = i + 1; j < objects.Length; j++)
@ -1080,13 +1080,21 @@ internal void UpdateObjects()
{
if (obj.IsDragging)
{
obj.OnMouseUp(position);
obj.OnMouseUp(mousePosition);
}
}
}
}
}
internal void UpdateObjects()
{
ImageEditorControl[] scrollbars = DrawableObjects.Where(x => x is ImageEditorScrollbar).ToArray();
ImageEditorControl[] shapes = DrawableObjects.Except(scrollbars).OrderByDescending(x => x.Order).ToArray();
UpdateObjects(shapes, Form.ScaledClientMousePosition);
UpdateObjects(scrollbars, InputManager.ClientMousePosition);
}
internal void DrawObjects(Graphics g)
{
foreach (ImageEditorControl obj in DrawableObjects)
@ -1288,7 +1296,7 @@ private BaseShape CheckHover()
if (Options.IsFixedSize && IsCurrentShapeTypeRegion)
{
Point location = InputManager.ClientMousePosition;
Point location = Form.ScaledClientMousePosition;
BaseShape rectangleRegionShape = CreateShape(ShapeType.RegionRectangle);
rectangleRegionShape.Rectangle = new Rectangle(new Point(location.X - (Options.FixedSize.Width / 2),
@ -1484,7 +1492,7 @@ private void ClearTools()
public BaseShape GetIntersectShape()
{
return GetIntersectShape(InputManager.ClientMousePosition);
return GetIntersectShape(Form.ScaledClientMousePosition);
}
public BaseShape GetIntersectShape(Point position)
@ -1680,7 +1688,7 @@ private void DuplicateCurrrentShape(bool insertMousePosition)
{
if (insertMousePosition)
{
shapeCopy.MoveAbsolute(InputManager.ClientMousePosition, true);
shapeCopy.MoveAbsolute(Form.ScaledClientMousePosition, true);
}
else
{
@ -1726,7 +1734,7 @@ private void PasteFromClipboard(bool insertMousePosition)
if (insertMousePosition)
{
pos = InputManager.ClientMousePosition;
pos = Form.ScaledClientMousePosition;
}
else
{
@ -1821,7 +1829,7 @@ public Color GetColor(Bitmap bmp, Point pos)
public Color GetCurrentColor(Bitmap bmp)
{
return GetColor(bmp, InputManager.ClientMousePosition);
return GetColor(bmp, Form.ScaledClientMousePosition);
}
public Color GetCurrentColor()