This commit is contained in:
Manuel Kamper 2019-03-23 15:00:30 +01:00
parent 3fc4d7e642
commit cb0b2954e5
9 changed files with 1428 additions and 1 deletions

View File

@ -0,0 +1,29 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.438
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mk0.Tools.ImageCropper", "Mk0.Tools.ImageCropper\Mk0.Tools.ImageCropper.csproj", "{E39D1D47-4E7D-42AA-908B-9BB1EBC014FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{E39D1D47-4E7D-42AA-908B-9BB1EBC014FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E39D1D47-4E7D-42AA-908B-9BB1EBC014FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E39D1D47-4E7D-42AA-908B-9BB1EBC014FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E39D1D47-4E7D-42AA-908B-9BB1EBC014FA}.Release|Any CPU.Build.0 = Release|Any CPU
{C090F0F5-27B1-4C18-A884-8E61D23CC2F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C090F0F5-27B1-4C18-A884-8E61D23CC2F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C090F0F5-27B1-4C18-A884-8E61D23CC2F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C090F0F5-27B1-4C18-A884-8E61D23CC2F6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {2074C3F7-F2C9-4CDA-B037-28990AF7E74F}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,233 @@
using System;
using System.Drawing;
namespace Mk0.Tools.ImageCropper
{
#region Enumerations
public enum RasterOps
{
R2_BLACK = 1,
R2_NOTMERGEPEN,
R2_MASKNOTPEN,
R2_NOTCOPYPEN,
R2_MASKPENNOT,
R2_NOT,
R2_XORPEN,
R2_NOTMASKPEN,
R2_MASKPEN,
R2_NOTXORPEN,
R2_NOP,
R2_MERGENOTPEN,
R2_COPYPEN,
R2_MERGEPENNOT,
R2_MERGEPEN,
R2_WHITE,
R2_LAST
}
public enum BrushStyles
{
BS_SOLID = 0,
BS_NULL = 1,
BS_HATCHED = 2,
BS_PATTERN = 3,
BS_INDEXED = 4,
BS_DIBPATTERN = 5,
BS_DIBPATTERNPT = 6,
BS_PATTERN8X8 = 7,
BS_MONOPATTERN = 9
}
public enum PenStyles
{
PS_SOLID = 0,
PS_DASH = 1,
PS_DOT = 2,
PS_DASHDOT = 3,
PS_DASHDOTDOT = 4
}
#endregion
public sealed class GDIWrap : IDisposable
{
#region Variables
private Color _borderColor;
private Color _fillColor;
private int _lineWidth;
private IntPtr _hdc, _oldBrush, _oldPen, _gdiPen, _gdiBrush;
private BrushStyles _brushStyle;
private PenStyles _penStyle;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the GDIWrap class.
/// </summary>
public GDIWrap()
{ // Set up for XOR drawing to begin with
this._borderColor = Color.Transparent;
this._fillColor = Color.Black;
this._lineWidth = 2;
this._brushStyle = BrushStyles.BS_NULL;
this._penStyle = PenStyles.PS_SOLID;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the current BrushColor
/// </summary>
public Color BrushColor
{
get { return _fillColor; }
set { _fillColor = value; }
}
/// <summary>
/// Gets or sets the current BrushStyle. Set to BS_NULL for no brush.
/// </summary>
public BrushStyles BrushStyle
{
get { return _brushStyle; }
set { _brushStyle = value; }
}
/// <summary>
/// Gets or sets the current PenColor. Set to Color.Transparent for a XOR line.
/// </summary>
public Color PenColor
{
get { return _borderColor; }
set { _borderColor = value; }
}
/// <summary>
/// Gets or sets the current PenStyle.
/// </summary>
public PenStyles PenStyle
{
get { return _penStyle; }
set { _penStyle = value; }
}
/// <summary>
/// Gets or sets the current PenWidth.
/// </summary>
public int PenWidth
{
get { return _lineWidth; }
set { _lineWidth = value; }
}
#endregion
#region Methods
/// <summary>
/// Draws a line with the pen that has been set by the user. Uses gdi32->MoveToEx and gdi32->LineTo
/// </summary>
/// <param name="g">Graphics object. You can use CreateGraphics().</param>
/// <param name="p1">Initial point of line.</param>
/// <param name="p2">Termination point of line.</param>
public void DrawLine(Graphics g, Point p1, Point p2)
{
InitPenAndBrush(g);
NativeMethods.MoveToEx(_hdc, p1.X, p1.Y, (IntPtr)null);
NativeMethods.LineTo(_hdc, p2.X, p2.Y);
Dispose(g);
}
/// <summary>
/// Draws a rectangle with the pen and brush that have been set by the user. Uses gdi32->Rectangle
/// </summary>
/// <param name="g">Graphics object. You can use CreateGraphics().</param>
/// <param name="myRect">The shape to draw.</param>
public void DrawRectangle(Graphics g, Rectangle myRect)
{
InitPenAndBrush(g);
NativeMethods.Rectangle(_hdc, myRect.Left, myRect.Top, myRect.Right, myRect.Bottom);
Dispose(g);
}
/// <summary>
/// Draws an ellipse with the pen and brush that have been set by the user. Uses gdi32->Ellipse
/// </summary>
/// <param name="g">Graphics object. You can use CreateGraphics().</param>
/// <param name="p1">First corner of ellipse (if you imagine its size as a rectangle).</param>
/// <param name="p2">Second corner of ellipse (if you imagine its size as a rectangle).</param>
public void DrawEllipse(Graphics g, Point p1, Point p2)
{
InitPenAndBrush(g);
NativeMethods.Ellipse(_hdc, p1.X, p1.Y, p2.X, p2.Y);
Dispose(g);
}
public void Dispose()
{
}
private int GetRGBFromColor(Color fromColor)
{
return fromColor.ToArgb() & 0xFFFFFF;
}
/// <summary>
/// Initializes the pen and brush objects. Stores the old pen and brush so they can be recovered later.
/// </summary>
/// <param name="g"></param>
private void InitPenAndBrush(Graphics g)
{
_hdc = g.GetHdc();
_gdiPen = NativeMethods.CreatePen(_penStyle, _lineWidth, GetRGBFromColor(PenColor));
_gdiBrush = NativeMethods.GetStockObject(5); // CreateSolidBrush(GetRGBFromColor(fillColor));
if (PenColor == Color.Transparent)
NativeMethods.SetROP2(_hdc, (int)RasterOps.R2_XORPEN);
_oldPen = NativeMethods.SelectObject(_hdc, _gdiPen);
_oldBrush = NativeMethods.SelectObject(_hdc, _gdiBrush);
}
/// <summary>
/// Reloads the old pen and brush.
/// Deletes the pen that was created by InitPenAndBrush(g).
/// Releases the handle to the device context and then disposes of the Graphics object.
/// </summary>
/// <param name="g"></param>
private void Dispose(Graphics g)
{
NativeMethods.SelectObject(_hdc, _oldBrush);
NativeMethods.SelectObject(_hdc, _oldPen);
NativeMethods.DeleteObject(_gdiPen);
NativeMethods.DeleteObject(_gdiBrush);
g.ReleaseHdc(_hdc);
}
#endregion
internal static class NativeMethods
{
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
internal static extern bool Ellipse(IntPtr hdc, int x1, int y1, int x2, int y2);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
internal static extern bool Rectangle(IntPtr hdc, int X1, int Y1, int X2, int Y2);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
internal static extern IntPtr MoveToEx(IntPtr hdc, int x, int y, IntPtr lpPoint);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
internal static extern bool LineTo(IntPtr hdc, int x, int y);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
internal static extern IntPtr CreatePen(PenStyles enPenStyle, int nWidth, int crColor);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
internal static extern IntPtr CreateSolidBrush(int crColor);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
internal static extern bool DeleteObject(IntPtr hObject);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
internal static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
internal static extern IntPtr GetStockObject(int brStyle);
[System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
internal static extern int SetROP2(IntPtr hdc, int enDrawMode);
}
}
}

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{E39D1D47-4E7D-42AA-908B-9BB1EBC014FA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Mk0.Tools.ImageCropper</RootNamespace>
<AssemblyName>Mk0.Tools.ImageCropper</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<ItemGroup>
<Compile Include="RubberBand.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="RubberBand.Designer.cs">
<DependentUpon>RubberBand.cs</DependentUpon>
</Compile>
<Compile Include="GDIWrap.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="StatusEventArg.cs" />
<Service Include="{94E38DFF-614B-4cbd-B67C-F211BB35CE8B}" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="RubberBand.resx">
<DependentUpon>RubberBand.cs</DependentUpon>
</EmbeddedResource>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Mk0.Tools.ImageCropper")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Mk0.Tools.ImageCropper")]
[assembly: AssemblyCopyright("Copyright © 2019 mk0.at")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("20b6e8e2-490c-48b0-9951-af1466162c67")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,80 @@
namespace Mk0.Tools.ImageCropper
{
partial class RubberBand
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Component Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.thePicture = new System.Windows.Forms.PictureBox();
this.panel1 = new System.Windows.Forms.Panel();
((System.ComponentModel.ISupportInitialize)(this.thePicture)).BeginInit();
this.panel1.SuspendLayout();
this.SuspendLayout();
//
// thePicture
//
this.thePicture.Location = new System.Drawing.Point(3, 3);
this.thePicture.Name = "thePicture";
this.thePicture.Size = new System.Drawing.Size(151, 134);
this.thePicture.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
this.thePicture.TabIndex = 0;
this.thePicture.TabStop = false;
this.thePicture.Paint += new System.Windows.Forms.PaintEventHandler(this.OnPaint);
this.thePicture.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.OnMouseDoubleClick);
this.thePicture.MouseDown += new System.Windows.Forms.MouseEventHandler(this.OnMouseDown);
this.thePicture.MouseMove += new System.Windows.Forms.MouseEventHandler(this.OnMouseMove);
this.thePicture.MouseUp += new System.Windows.Forms.MouseEventHandler(this.OnMouseUp);
this.thePicture.Resize += new System.EventHandler(this.OnResize);
//
// panel1
//
this.panel1.AutoScroll = true;
this.panel1.Controls.Add(this.thePicture);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(150, 150);
this.panel1.TabIndex = 1;
//
// RubberBand
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.panel1);
this.Name = "RubberBand";
((System.ComponentModel.ISupportInitialize)(this.thePicture)).EndInit();
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.PictureBox thePicture;
private System.Windows.Forms.Panel panel1;
}
}

View File

@ -0,0 +1,841 @@
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace Mk0.Tools.ImageCropper
{
public enum Operation
{
none,
draw,
move,
left,
top,
right,
bottom,
topLeft,
topRight,
bottomRight,
bottomLeft
}
/// <summary>
/// Manages the cropping rectangle - allows drawing, resizing, and moving. _theRectangle and _backupRectangle are stored
/// in units of pixels.
/// </summary>
public partial class RubberBand : UserControl
{
#region Member Variables;
private Operation _curOp; // Indicates the current operation
private bool _isDragging = false; // Indicates if the left mouse button is down
private Point _startPoint; // Set in the mouse down event. The anchor for our rectangle
private bool _disabled = false; // Indicates cropping is not allowed
private Rectangle _theRectDrawing = new Rectangle( // We draw this rectangle on the screen as our cropping rectangle
new Point(0, 0), new Size(0, 0)); // It is populated in the MouseUp event
private Rectangle _backupRectangle = new Rectangle( // Helps us handle double click event - we need to restore the rectangle
new Point(0, 0), new Size(0, 0)); // because double click also triggers a single click event.
private Rectangle _picRectangle = new Rectangle( // The area of our image - not the same as area of PictureBox control
new Point(0, 0), new Size(0, 0));
private GDIWrap _myGDI = new GDIWrap(); // DrawReversibleFrame caused a lot of flickering - so we use GDI methods
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the CropperBox class
/// </summary>
public RubberBand()
{
InitializeComponent();
}
#endregion
public event EventHandler ImageCropped;
public event EventHandler<StatusEventArg> CursorMove;
#region Public Properties and Methods
/// <summary>
/// Gets or sets how images will be sized in the control - true sized or sized to fit the screen
/// </summary>
[Category("Custom"), Description("Size mode of the picturebox control")]
public PictureBoxSizeMode SizeMode
{
get
{
return thePicture.SizeMode;
}
set
{
Rectangle origSize = ImageRect(); // This defines the current image rectangle
// Set docking style before we set size mode
// Otherwise we have repaint problems if pic was scrolled.
if (value == PictureBoxSizeMode.AutoSize)
thePicture.Dock = DockStyle.None;
else
thePicture.Dock = DockStyle.Fill;
thePicture.SizeMode = value;
_picRectangle = ImageRect();
// Now we can transform our cropping rectangle
_theRectDrawing.Height = (int)Math.Round(_theRectDrawing.Height * (float)_picRectangle.Height / origSize.Height);
_theRectDrawing.Width = (int)Math.Round(_theRectDrawing.Width * (float)_picRectangle.Width / origSize.Width);
// Get the offset for the cropping rectangle
Point offset = new Point(_theRectDrawing.X - origSize.X, _theRectDrawing.Y - origSize.Y);
// Scale the offset
offset.X = (int)Math.Round(offset.X * (float)_picRectangle.Width / origSize.Width);
offset.Y = (int)Math.Round(offset.Y * (float)_picRectangle.Height / origSize.Height);
// Set our new position
_theRectDrawing.X = offset.X + ImageRect().X;
_theRectDrawing.Y = offset.Y + ImageRect().Y;
}
}
/// <summary>
/// Gets or sets the image that will be displayed. When we set a new image, we check if the current
/// cropping rectangle fits within the image. If not, we reset it to zero.
/// </summary>
[Category("Custom"), Description("Gets or sets the image from the picturebox")]
public Image Image
{
get
{
return thePicture.Image;
}
set
{
// Change the image
thePicture.Image = value;
// Is the cropping rectangle completely within the new image?
if (!ImageRect().Contains(_theRectDrawing))
_theRectDrawing = new Rectangle(0, 0, 0, 0);
}
}
/// <summary>
/// Gets the image selected by the cropping rectangle. Returns null if no image is selected.
/// Return the entire image if no cropping rectangle is selected.
/// </summary>
[Category("Custom"), Description("Gets the image selected by the cropping rectangle")]
public Image SelectedImage
{
get
{
bool tempRect = false;
// Create a bitmap from our image
Bitmap bmpImage = new Bitmap(thePicture.Image);
// If we dont have a cropping rectangle, set the cropping rectangle to the
// dimensions of the entire image.
if (_theRectDrawing.Width <= 0 || _theRectDrawing.Height <= 0)
{
_theRectDrawing = ImageRect();
tempRect = true;
}
// Scale the cropping rectangle
Rectangle cropRect = CroppingArea;
// Clone the image defined by cropRect. Use the same pixel format
// as our original image.
Bitmap bmpCrop = bmpImage.Clone(cropRect, bmpImage.PixelFormat);
bmpImage.Dispose();
if (tempRect)
_theRectDrawing = new Rectangle(0, 0, 0, 0);
return (Image)bmpCrop;
}
}
/// <summary>
/// Gets or sets the Disabled property. We disable the control when we display the home page.
/// </summary>
[Category("Custom"), Description("Gets or sets the cropping capability of the control")]
public bool Disabled
{
get
{
return _disabled;
}
set
{
_disabled = value;
if (_disabled)
_theRectDrawing = new Rectangle(0, 0, 0, 0);
}
}
#endregion
#region Private Properties And Methods
/// <summary>
/// Gets the cropping area scaled to the specified size
/// </summary>
/// <param name="basis"></param>
/// <returns></returns>
[Category("Custom"), Description("Obtain the current cropping rectangle scaled to the size of the specified image")]
private Rectangle CroppingArea
{
get
{
Rectangle cropRect = _theRectDrawing;
double ratio = GetScaleFactor(SizeMode == PictureBoxSizeMode.AutoSize);
if (ratio != 0.0)
{
cropRect.Offset(-_picRectangle.Left, -_picRectangle.Top);
cropRect.Height = (int)Math.Round(cropRect.Height / ratio);
cropRect.X = (int)Math.Round(cropRect.X / ratio);
cropRect.Width = (int)Math.Round(cropRect.Width / ratio);
cropRect.Y = (int)Math.Round(cropRect.Y / ratio);
}
return cropRect;
}
}
/// <summary>
/// Update the point based on the image size
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
private Point CroppingPoint(Point pt)
{
Point adjustedPt = pt;
double ratio = GetScaleFactor(SizeMode == PictureBoxSizeMode.AutoSize);
if (ratio != 0.0)
{
adjustedPt.Offset(-_picRectangle.Left, -_picRectangle.Top);
adjustedPt.X = (int)Math.Round(adjustedPt.X / ratio);
adjustedPt.Y = (int)Math.Round(adjustedPt.Y / ratio);
}
return adjustedPt;
}
/// <summary>
/// If the cursor is outside a cropping area, start a new rubberband. If the cursor is within the
/// bounds of a rubber band, trigger a move operation. If the cursor is on the border of a rubber
/// band, trigger a resize operation. This event is triggered by the PictureBox control.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnMouseDown(object sender, MouseEventArgs e)
{
// Set the isDrag variable to true and get the starting point
// by using the PointToScreen method to convert form
// coordinates to screen coordinates.
Point myPoint = new Point(e.X, e.Y); // PictureBox coordinates are passed in
if (_disabled)
_curOp = Operation.none;
else if (e.Button == MouseButtons.Left)
{
this._isDragging = true;
this._picRectangle = ImageRect();
this._backupRectangle = _theRectDrawing; // Helps with undo if this is part of a double click event
this._curOp = DetermineMouseOperation(myPoint);
}
else
{
_curOp = Operation.none;
}
switch (_curOp)
{
case Operation.draw:
_startPoint = myPoint;
// Reset the rectangle.
_theRectDrawing = new Rectangle(0, 0, 0, 0);
thePicture.Invalidate();
break;
case Operation.none:
_isDragging = false;
// Reset the rectangle.
_theRectDrawing = new Rectangle(0, 0, 0, 0);
thePicture.Invalidate();
break;
default:
_startPoint = myPoint;
// Erase the inked rectangle
thePicture.Refresh();
// Draw the rectangle in reverse ink
Graphics g = thePicture.CreateGraphics();
_myGDI.DrawRectangle(g, _theRectDrawing);
g.Dispose();
break;
}
}
/// <summary>
/// If curOp is none, we will set the cursor depending on the current position. Otherwise,
/// we will modify or move the rubberand. This event is triggered by the PictureBox control.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnMouseMove(object sender, MouseEventArgs e)
{
Point myPoint = new Point(e.X, e.Y); // Picture box coordinates
Graphics g = thePicture.CreateGraphics();
// Determine what our current operation is. This depends on if we are dragging the mouse or not
if (_isDragging)
{
_curOp = CheckOperationChange(_curOp, myPoint); // The operation can change during a mouse drag
// Erase the old rectangle created in the mouse down event
_myGDI.DrawRectangle(g, _theRectDrawing);
}
else
{
_curOp = DetermineMouseOperation(myPoint); // If not dragging, see which cursor to use
}
bool isActive = IsActivePoint(ref myPoint);
switch (_curOp)
{
case Operation.draw:
// Calculate the endpoint and dimensions for the new
_theRectDrawing = NormalizeRectangle(_startPoint, myPoint);
break;
case Operation.move:
Cursor = Cursors.SizeAll;
if (_isDragging)
{
// Compute how much the cursor has moved since the previous call
Point myDiff = new Point(myPoint.X - _startPoint.X, myPoint.Y - _startPoint.Y);
myDiff = CalcMaxOffset(myDiff);
_theRectDrawing.Offset(myDiff);
_startPoint = myPoint;
}
break;
case Operation.left:
Cursor = Cursors.SizeWE;
if (_isDragging)
_theRectDrawing = NormalizeRectangle(myPoint.X, _theRectDrawing.Top, _theRectDrawing.Right, _theRectDrawing.Bottom);
break;
case Operation.top:
Cursor = Cursors.SizeNS;
if (_isDragging)
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Left, myPoint.Y, _theRectDrawing.Right, _theRectDrawing.Bottom);
break;
case Operation.right:
Cursor = Cursors.SizeWE;
if (_isDragging)
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Left, _theRectDrawing.Top, myPoint.X, _theRectDrawing.Bottom);
break;
case Operation.bottom:
Cursor = Cursors.SizeNS;
if (_isDragging)
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Left, _theRectDrawing.Top, _theRectDrawing.Right, myPoint.Y);
break;
case Operation.topLeft:
Cursor = Cursors.SizeNWSE;
if (_isDragging)
_theRectDrawing = NormalizeRectangle(myPoint.X, myPoint.Y, _theRectDrawing.Right, _theRectDrawing.Bottom);
break;
case Operation.topRight:
Cursor = Cursors.SizeNESW;
if (_isDragging)
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Left, myPoint.Y, myPoint.X, _theRectDrawing.Bottom);
break;
case Operation.bottomRight:
Cursor = Cursors.SizeNWSE;
if (_isDragging)
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Left, _theRectDrawing.Top, myPoint.X, myPoint.Y);
break;
case Operation.bottomLeft:
Cursor = Cursors.SizeNESW;
if (_isDragging)
_theRectDrawing = NormalizeRectangle(myPoint.X, _theRectDrawing.Top, _theRectDrawing.Right, myPoint.Y);
break;
default:
Cursor = Cursors.Cross;
break;
}
// Draw our new rectangle
if (_isDragging)
_myGDI.DrawRectangle(g, _theRectDrawing);
g.Dispose();
// Notify the host that the mouse is moved. If we have a cropping rectangle,
// size of the cropping rectangle. Otherwise, report the mouse position
if (CursorMove != null)
{
Rectangle crop = CroppingArea;
if (crop.Height <= 0 || crop.Width <= 0)
{
crop.Width = thePicture.Image.Width;
crop.Height = thePicture.Image.Height;
crop.X = 0;
crop.Y = 0;
// If we are within the picture, report the current mouse position
if (isActive)
CursorMove(this, new StatusEventArg(CroppingPoint(myPoint), crop));
else
CursorMove(this, new StatusEventArg(crop.Location, crop));
}
else
CursorMove(this, new StatusEventArg(crop.Location, crop));
}
}
/// <summary>
/// We are done with our mouse operation. This event is triggered by
/// the PictureBox.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnMouseUp(object sender, MouseEventArgs e)
{
Point myPoint = new Point(e.X, e.Y);
IsActivePoint(ref myPoint);
// Erase the current rectangle
Graphics g = thePicture.CreateGraphics();
if (_curOp != Operation.none)
_myGDI.DrawRectangle(g, _theRectDrawing);
g.Dispose();
// If the MouseUp event occurs, the user has stopped dragging
_isDragging = false;
// Note that some operations can change mid stride depending on where the user moves the mouse
_curOp = CheckOperationChange(_curOp, myPoint);
switch (_curOp)
{
case Operation.move:
myPoint = new Point(myPoint.X - _startPoint.X, myPoint.Y - _startPoint.Y);
myPoint = CalcMaxOffset(myPoint);
_theRectDrawing.Offset(myPoint);
break;
case Operation.left:
_theRectDrawing = NormalizeRectangle(myPoint.X, _theRectDrawing.Top, _theRectDrawing.Right, _theRectDrawing.Bottom);
break;
case Operation.right:
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Left, _theRectDrawing.Top, myPoint.X, _theRectDrawing.Bottom);
break;
case Operation.top:
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Left, myPoint.Y, _theRectDrawing.Right, _theRectDrawing.Bottom);
break;
case Operation.bottom:
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Left, _theRectDrawing.Top, _theRectDrawing.Right, myPoint.Y);
break;
case Operation.topLeft:
_theRectDrawing = NormalizeRectangle(myPoint.X, myPoint.Y, _theRectDrawing.Right, _theRectDrawing.Bottom);
break;
case Operation.topRight:
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Left, myPoint.Y, myPoint.X, _theRectDrawing.Bottom);
break;
case Operation.bottomLeft:
_theRectDrawing = NormalizeRectangle(myPoint.X, _theRectDrawing.Top, _theRectDrawing.Right, myPoint.Y);
break;
case Operation.bottomRight:
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Left, _theRectDrawing.Top, myPoint.X, myPoint.Y);
break;
}
if (_curOp != Operation.none)
{
// Create our new cropping rectangle - scale it in terms of the current image size
// The rectangle is currently in pictureBox coordinates. We need to scale it in terms
// of the current image.
myPoint.X = _theRectDrawing.Right;
myPoint.Y = _theRectDrawing.Bottom;
_theRectDrawing = NormalizeRectangle(_theRectDrawing.Location, myPoint);
thePicture.Invalidate();
_curOp = Operation.none;
}
}
/// <summary>
/// Notify our host that someone double clicked the pictureBox. Keep in mind that a
/// double click event also causes two mouse down events and one mouse up event prior to
/// triggering this event.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnMouseDoubleClick(object sender, MouseEventArgs e)
{
_theRectDrawing = _backupRectangle;
_curOp = Operation.none;
_isDragging = false;
ImageCropped?.Invoke(this, e);
}
/// <summary>
/// Paint event of the picturebox control. Draw the rectangle only if not being drawn by
/// mouse events. The rectangle is drawn using client coordinates of the PictureBox
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnPaint(object sender, PaintEventArgs e)
{
if (!_isDragging)
_myGDI.DrawRectangle(e.Graphics, _theRectDrawing);
}
/// <summary>
/// Fired when the picturebox control is resized. We may also need to resize our cropping rectangle
/// and refresh the dimensions of picRectangle (thePicture is the PictureBox control).
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnResize(object sender, EventArgs e)
{
_picRectangle = ImageRect();
}
/// <summary>
/// Create a normalized rectangle from two points. There does not seem to be
/// any built in method to do this. The two points represent the top left and bottom
/// right corners.
/// </summary>
/// <param name="p1"></param>
/// <param name="p2"></param>
/// <returns></returns>
private Rectangle NormalizeRectangle(Point p1, Point p2)
{
Rectangle rc = new Rectangle();
// Normalize the rectangle.
if (p1.X < p2.X)
{
rc.X = p1.X;
rc.Width = p2.X - p1.X;
}
else
{
rc.X = p2.X;
rc.Width = p1.X - p2.X;
}
if (p1.Y < p2.Y)
{
rc.Y = p1.Y;
rc.Height = p2.Y - p1.Y;
}
else
{
rc.Y = p2.Y;
rc.Height = p1.Y - p2.Y;
}
return rc;
}
private Rectangle NormalizeRectangle(int x1, int y1, int x2, int y2)
{
Point p1 = new Point(x1, y1);
Point p2 = new Point(x2, y2);
return NormalizeRectangle(p1, p2);
}
/// <summary>
/// The amount to change picture size to fit within our panel. If the
/// image is true sized, return 1. Otherwise return the factor we will
/// increase/reduce the actual image size.
/// </summary>
/// <param name="isTrueSize"></param>
/// <returns></returns>
private double GetScaleFactor(bool isTrueSize)
{
double ratio = 0.0;
double ratio1 = 0.0;
if (isTrueSize)
ratio = 1.0; // Image is true sized
else
{
// Get the dimensions of the picture so we can compute our sizing ratio
if (thePicture.Image != null)
{
ratio = ((float)panel1.Size.Width) / thePicture.Image.Width;
ratio1 = ((float)panel1.Size.Height) / thePicture.Image.Height;
}
if (ratio1 < ratio)
ratio = ratio1;
}
return ratio;
}
/// <summary>
/// When the picture is sized to fit, it is also centered inside the panel.
/// We need to determine how much it is offset from the 0,0 position.
/// </summary>
/// <param name="isTrueSize"></param>
/// <returns></returns>
private Point GetScaleOffset(bool isTrueSize)
{
Point topLeft = new Point();
if (thePicture.Image == null)
return topLeft;
double ratio = GetScaleFactor(isTrueSize);
int width = (int)(thePicture.Image.Width * ratio);
int height = (int)(thePicture.Image.Height * ratio);
// Get the location of the picture. If not trueSize, it is centered in the panel
// smaller than the panel.
if (isTrueSize || width > panel1.Width)
topLeft.X = 0;
else
topLeft.X = (panel1.Width - width) / 2;
if (isTrueSize || height > panel1.Height)
topLeft.Y = 0;
else
topLeft.Y = (panel1.Height - height) / 2;
return topLeft;
}
/// <summary>
/// Return the rectangle that defines the current displayed image
/// in the picture box. Seems there should already be a method to
/// do this, but I could not find one. thePicture is our PictureBox
/// control. theImage is the size of the image contained therein.
/// The size of the image depends on the current SizeMode - the
/// image might be scaled to fit the picturebox.
/// </summary>
/// <returns></returns>
private Rectangle ImageRect()
{
double ratio;
Point topLeft = new Point();
Rectangle imageRect = new Rectangle();
if (thePicture.Image == null)
return imageRect;
ratio = GetScaleFactor(thePicture.SizeMode == PictureBoxSizeMode.AutoSize);
topLeft = GetScaleOffset(thePicture.SizeMode == PictureBoxSizeMode.AutoSize);
imageRect.Width = (int)(thePicture.Image.Width * ratio);
imageRect.Height = (int)(thePicture.Image.Height * ratio);
imageRect.Location = topLeft;
return imageRect;
}
/// <summary>
/// Return true if the point is within the boundaries of the current
/// picturebox image. If not, modify pt so it is on the offending boundary.
/// </summary>
/// <param name="pt"></param>
/// <returns></returns>
private bool IsActivePoint(ref Point pt)
{
bool isActive = false;
if (_picRectangle.Size.IsEmpty)
_picRectangle = ImageRect();
// Is the point within the picture area? The point must be in client coords
isActive = _picRectangle.Contains(pt);
if (!isActive)
{
// We will adjust the point to make it active
Rectangle myRect = _picRectangle;
if (pt.X < myRect.Left)
pt.X = myRect.Left;
if (pt.X > myRect.Right)
pt.X = myRect.Right;
if (pt.Y < myRect.Top)
pt.Y = myRect.Top;
if (pt.Y > myRect.Bottom)
pt.Y = myRect.Bottom;
}
return isActive;
}
/// <summary>
/// Return the appropriate operation type based on the point passed in
/// </summary>
/// <param name="pt">Client coordinates are passed in</param>
/// <returns></returns>
private Operation DetermineMouseOperation(Point pt)
{
Operation retVal = Operation.none;
Rectangle outerFrame = _theRectDrawing;
Rectangle innerFrame = outerFrame;
Size sz = new Size(2, 2); // The amount of leeway to give our user moving the mouse
outerFrame.Inflate(sz); // Create an outer rectangle around our cropping area
innerFrame.Inflate(-sz.Width, -sz.Height); // Create an inner rectangle within our cropping area
// Check if we are near any of the rectangle corners. We have to offset these four rectangles because the
// hotspot on the stock cursor for SizeNESW and SizeNWSE is not in the center of the cursor where it should be.
Rectangle picCorner;
// Check for top left corner
picCorner = new Rectangle(outerFrame.Left, outerFrame.Top, sz.Width, sz.Height);
picCorner.Inflate(8, 8);
picCorner.Offset(-8, -8);
if (picCorner.Contains(pt))
retVal = Operation.topLeft;
// Check for top right corner
picCorner = new Rectangle(innerFrame.Right, outerFrame.Top, sz.Width, sz.Height);
picCorner.Inflate(8, 8);
picCorner.Offset(-8, -8);
if (picCorner.Contains(pt))
retVal = Operation.topRight;
// Check for bottom right corner
picCorner = new Rectangle(innerFrame.Right, innerFrame.Bottom, sz.Width, sz.Height);
picCorner.Inflate(8, 8);
picCorner.Offset(-8, -8);
if (picCorner.Contains(pt))
retVal = Operation.bottomRight;
// Check for bottom left corner
picCorner = new Rectangle(outerFrame.Left, innerFrame.Bottom, sz.Width, sz.Height);
picCorner.Inflate(8, 8);
picCorner.Offset(-8, -8);
if (picCorner.Contains(pt))
retVal = Operation.bottomLeft;
// If we are not within any corners, do a little more checking
if (retVal == Operation.none)
{
// Are we inside the outer rectangle?
if (outerFrame.Contains(pt))
{
// Are we inside the inner rectangle?
if (innerFrame.Contains(pt))
retVal = Operation.move;
else if (pt.Y >= outerFrame.Top && pt.Y <= innerFrame.Top)
retVal = Operation.top;
else if (pt.Y >= innerFrame.Bottom && pt.Y <= outerFrame.Bottom)
{
System.Diagnostics.Debug.Print("Outer: " + outerFrame.ToString());
System.Diagnostics.Debug.Print("Inner: " + innerFrame.ToString());
System.Diagnostics.Debug.Print("Point: " + pt.ToString());
retVal = Operation.bottom;
}
else if (pt.X >= innerFrame.Right && pt.X <= outerFrame.Right)
retVal = Operation.right;
else if (pt.X >= outerFrame.Left && pt.X <= innerFrame.Left)
retVal = Operation.left;
}
}
// Are we inside the picture area?
if (retVal == Operation.none)
{
if (_picRectangle.Contains(pt) && _isDragging)
retVal = Operation.draw;
}
System.Diagnostics.Debug.Print(retVal.ToString());
return retVal;
}
/// <summary>
/// The operation can change depending on where the user moves the mouse
/// </summary>
/// <param name="curOp"></param>
/// <param name="myPoint"></param>
/// <returns></returns>
private Operation CheckOperationChange(Operation curOp, Point myPoint)
{
switch (curOp)
{
case Operation.left:
if (myPoint.X > _theRectDrawing.Right)
curOp = Operation.right;
break;
case Operation.right:
if (myPoint.X < _theRectDrawing.Left)
curOp = Operation.left;
break;
case Operation.top:
if (myPoint.Y > _theRectDrawing.Bottom)
curOp = Operation.bottom;
break;
case Operation.bottom:
if (myPoint.Y < _theRectDrawing.Top)
curOp = Operation.top;
break;
case Operation.topLeft:
if ((myPoint.X > _theRectDrawing.Right) && (myPoint.Y > _theRectDrawing.Bottom))
curOp = Operation.bottomRight;
else if (myPoint.X > _theRectDrawing.Right)
curOp = Operation.topRight;
else if (myPoint.Y > _theRectDrawing.Bottom)
curOp = Operation.bottomLeft;
break;
case Operation.topRight:
if ((myPoint.X < _theRectDrawing.Left) && (myPoint.Y > _theRectDrawing.Bottom))
curOp = Operation.bottomLeft;
else if (myPoint.X < _theRectDrawing.Left)
curOp = Operation.topLeft;
else if (myPoint.Y > _theRectDrawing.Bottom)
curOp = Operation.bottomRight;
break;
case Operation.bottomLeft:
if ((myPoint.X > _theRectDrawing.Right) && (myPoint.Y < _theRectDrawing.Top))
curOp = Operation.topRight;
else if (myPoint.X > _theRectDrawing.Right)
curOp = Operation.bottomRight;
else if (myPoint.Y < _theRectDrawing.Top)
curOp = Operation.topLeft;
break;
case Operation.bottomRight:
if ((myPoint.X < _theRectDrawing.Left) && (myPoint.Y < _theRectDrawing.Top))
curOp = Operation.topLeft;
else if (myPoint.X < _theRectDrawing.Left)
curOp = Operation.bottomLeft;
else if (myPoint.Y < _theRectDrawing.Top)
curOp = Operation.topRight;
break;
}
return curOp;
}
/// <summary>
/// Move the cropping rectangle - but not out of the picture boundaries
/// </summary>
/// <param name="offSet"></param>
/// <returns></returns>
private Point CalcMaxOffset(Point offSet)
{
Rectangle testRect = _theRectDrawing;
// Move the rectangle the specified amount and see if Kosher
testRect.Offset(offSet);
// Do we need to adjust our offset?
if (!_picRectangle.Contains(testRect))
{
if (testRect.Top < _picRectangle.Top)
offSet.Y = offSet.Y + (_picRectangle.Top - testRect.Top);
if (testRect.Right > _picRectangle.Right)
offSet.X = offSet.X + (_picRectangle.Right - testRect.Right);
if (testRect.Bottom > _picRectangle.Bottom)
offSet.Y = offSet.Y + (_picRectangle.Bottom - testRect.Bottom);
if (testRect.Left < _picRectangle.Left)
offSet.X = offSet.X + (_picRectangle.Left - testRect.Left);
}
return offSet;
}
#endregion
}
}

View File

@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@ -0,0 +1,20 @@
using System;
using System.Drawing;
namespace Mk0.Tools.ImageCropper
{
/// <summary>
/// Passes the cropping rectangle and current cursor position to the host object
/// </summary>
public class StatusEventArg : EventArgs
{
public readonly Rectangle CroppingRect;
public readonly Point CursorPos;
public StatusEventArg(Point pos, Rectangle crop)
{
CursorPos = pos;
CroppingRect = crop;
}
}
}

View File

@ -1 +1,4 @@
# Mk0.Tools.ImageCropper
# Mk0.Tools.ImageCropper
(C) 2019 mk0.at
This Tool provides a new form control to crop images.