Merge pull request #201 from KillyMXI/feature/zoom

* Zoom feature for the editor
- better interface for coordinate translation;
- correct context menu location;
- speech bubble tail can be dragged over the whole image.
* Image size in OneNoteExporter
There is now a distinction between Surface size and Surface.Image size.
* Changed shortcuts - Ctrl+0, Ctrl+9
* GetOptimalWindowSize - bound by available screen space
Limiting by working area size was not good enough - having whole chrome on screen is important.
* More fixes for Surface.Image size
- crop works properly;
- Freehand drawings are selectable everywhere at all zoom levels.
Known issue: freehand selection outline is not drawn

* Rework for paste from clipboard

- location is determined by the same code for images and text;
- use visible area to determine the location.

* Fix NPE

* Width and Height shouldn't be exposed through ISurface anymore

* Fix GetAvailableScreenSpace on secondary screens

Co-Authored-By: jklingen <jklingen@users.noreply.github.com>

* Fix rendering quality at small zoom levels when redrawing small parts

* Robust clip region resize

* Keep center of visible area static on zoom when possible

* Unneeded using

after 169dbdc

* Fix for Best Fit on images bigger than 4 times the available space

* Fix weird scroll position when going from no scroll zoom value

Also prefer top left corner in that situation - it is less disorienting when working with screenshots.

* Use Fractions to represent zoom factor

The goal is to be able to get as close as possible to perfect 66.(6)% (2/3) zoom factor, and also remove types mismatch between the editor form and the surface.

* Fix for pixel jerk

* Naming consistency - not a percentage value anymore

* TextContainer's TextBox on zoom - fix for position, update font size

* Smarter zoom - keep selected elements in sight

* Fix: prevent image blurring at 100% zoom

Even at 100% GDI+ manages to do some smoothing.
Also minor optimization - not messing with Graphics state when not needed.

* No ScaleTransform is needed at 100% zoom

* Fix: zoom-in when selection is out of sight

Co-authored-by: jklingen <jklingen@users.noreply.github.com>
This commit is contained in:
Robin Krom 2020-05-10 23:02:04 +02:00 committed by GitHub
commit 10f227da51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1059 additions and 138 deletions

View file

@ -116,6 +116,18 @@ namespace Greenshot.Drawing.Adorners
}
}
/// <summary>
/// Return the bounds of the Adorner as displayed on the parent Surface
/// </summary>
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);
}
}
/// <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 = BoundsOnSurface;
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 = BoundsOnSurface;
GraphicsState state = targetGraphics.Save();
targetGraphics.SmoothingMode = SmoothingMode.None;

View file

@ -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);
}

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 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 {

View file

@ -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);
}
}

View file

@ -235,6 +235,30 @@ namespace Greenshot.Drawing {
return false;
}
/// <summary>
/// A rectangle containing DrawingBounds of all drawableContainers in this list,
/// or empty rectangle if nothing is there.
/// </summary>
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;
}
}
}
/// <summary>
/// Triggers all elements in the list ot be redrawn.
/// </summary>
@ -286,7 +310,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,
@ -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();

View file

@ -47,8 +47,8 @@ namespace Greenshot.Drawing {
/// Constructor
/// </summary>
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;
}
}
}

View file

@ -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();
}
}
/// <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 +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;
}
/// <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 +1125,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 +1220,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 +1310,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 +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));
/// <summary>
/// This is the event handler for the Paint Event, try to draw as little as possible!
/// </summary>
@ -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);
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="horizontalRatio">0.0f for the left edge of visible area, 1.0f for the right edge of visible area.</param>
/// <param name="verticalRatio">0.0f for the top edge of visible area, 1.0f for the bottom edge of visible area.</param>
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);
}
/// <summary>
/// Get the rectangle bounding the part of this Surface currently visible in the editor (in surface coordinate space).
/// </summary>
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
);
}
/// <summary>
/// Get the rectangle bounding all selected elements (in surface coordinates space),
/// or empty rectangle if nothing is selcted.
/// </summary>
public Rectangle GetSelectionRectangle()
=> ToSurfaceCoordinates(selectedElements.DrawingBounds);
/// <summary>
/// Duplicate all the selecteded elements
/// </summary>
@ -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
);
}
}
}
}

View file

@ -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
}
/// <summary>
/// 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.
/// </summary>
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;
}
/// <summary>
/// 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...
/// </summary>
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;
}
/// <summary>
/// Set TextBox text align and fore color according to field values.
/// </summary>
private void UpdateTextBoxFormat()
{
if (_textBox == null)

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+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;
}
}

View file

@ -66,6 +66,11 @@ 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 Fraction[] ZOOM_VALUES = new Fraction[] { (1, 4), (1, 2), (2, 3), (3, 4), (1 ,1), (2, 1), (3, 1), (4, 1), (6, 1) };
/// <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 +425,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 {
@ -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;
}
}
/// <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 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;
/// <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.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
);
}
}
}

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

@ -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);

View file

@ -0,0 +1,152 @@
using System;
using System.Text.RegularExpressions;
namespace GreenshotPlugin.Core
{
/// <summary>
/// Basic Fraction (Rational) numbers with features only needed to represent scale factors.
/// </summary>
public readonly struct Fraction : IEquatable<Fraction>, IComparable<Fraction>
{
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;
}
}

View file

@ -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);

View file

@ -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.
/// </summary>
/// <param name="text">String to show</param>
/// <param name="horizontalAlignment">Left, Center, Right</param>
/// <param name="verticalAlignment">TOP, CENTER, BOTTOM</param>
/// <param name="x">Where to put the container, X coordinate in the Image coordinate space</param>
/// <param name="y">Where to put the container, Y coordinate in the Image coordinate space</param>
/// <param name="family">FontFamily</param>
/// <param name="size">Font Size in float</param>
/// <param name="italic">bool true if italic</param>
@ -94,7 +95,7 @@ namespace GreenshotPlugin.Interfaces
/// <param name="borderSize">size of border (0 for none)</param>
/// <param name="color">Color of string</param>
/// <param name="fillColor">Color of background (e.g. Color.Transparent)</param>
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
/// <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;
@ -186,9 +192,32 @@ namespace GreenshotPlugin.Interfaces
get;
set;
}
int Width { get; }
int Height { get; }
/// <summary>
/// Zoom value applied to the surface.
/// </summary>
Fraction ZoomFactor { get; set; }
/// <summary>
/// Translate a point from image coorditate space to surface coordinate space.
/// </summary>
/// <param name="point">A point in the coordinate space of the image.</param>
Point ToSurfaceCoordinates(Point point);
/// <summary>
/// Translate a rectangle from image coorditate space to surface coordinate space.
/// </summary>
/// <param name="rc">A rectangle in the coordinate space of the image.</param>
Rectangle ToSurfaceCoordinates(Rectangle rc);
/// <summary>
/// Translate a point from surface coorditate space to image coordinate space.
/// </summary>
/// <param name="point">A point in the coordinate space of the surface.</param>
Point ToImageCoordinates(Point point);
/// <summary>
/// Translate a rectangle from surface coorditate space to image coordinate space.
/// </summary>
/// <param name="rc">A rectangle in the coordinate space of the surface.</param>
Rectangle ToImageCoordinates(Rectangle rc);
void MakeUndoable(IMemento memento, bool allowMerge);
}
}
}